From 77cccdf9c446fb05a7e82ae9adbb96ba7212ac05 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Mon, 28 Jan 2019 22:09:53 +0000 Subject: [PATCH 01/69] Tutorial 1 mos --- .gitignore | 1 + CMakeLists.txt | 5 + Tutorial 1/CMakeLists.txt | 41 +++++ Tutorial 1/src/Tutorial 1.cpp | 108 ++++++++++++ Tutorial 1/src/kernels/my_kernels_1.cl | 24 +++ include/Utils.h | 224 +++++++++++++++++++++++++ 6 files changed, 403 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Tutorial 1/CMakeLists.txt create mode 100644 Tutorial 1/src/Tutorial 1.cpp create mode 100644 Tutorial 1/src/kernels/my_kernels_1.cl create mode 100644 include/Utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1dc9aec --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8) + +project("OpenCL Tutorials") + +add_subdirectory("Tutorial 1") diff --git a/Tutorial 1/CMakeLists.txt b/Tutorial 1/CMakeLists.txt new file mode 100644 index 0000000..cea8cf1 --- /dev/null +++ b/Tutorial 1/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 2.6) + +project("Tutorial 1") + +set(CMAKE_CXX_STANDARD 11) + +set(EXTRA_INCLUDE_DIRS ../include) +set(EXTRA_LIBRARY_DIRS "") +set(EXTRA_LIBRARIES "") + +find_package(OpenCL REQUIRED) + +set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) + +include_directories(${EXTRA_INCLUDE_DIRS}) +link_directories(${EXTRA_LIBRARY_DIRS}) + +set (SOURCES + ../include/Utils.h + "src/Tutorial 1.cpp" + src/kernels/my_kernels_1.cl) + +add_executable(Tutorial_1 ${SOURCES}) + +target_link_libraries (Tutorial_1 ${EXTRA_LIBRARIES}) + +#copy kernel files to target directory +add_custom_command(TARGET Tutorial_1 PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/Tutorial 1/src/kernels/" + $/kernels/) + +#additional command to copy kernels into a working directory allowing debugging directly from VS +if (MSVC) +add_custom_command(TARGET Tutorial_1 PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/Tutorial 1/src/kernels/" + kernels) +endif(MSVC) \ No newline at end of file diff --git a/Tutorial 1/src/Tutorial 1.cpp b/Tutorial 1/src/Tutorial 1.cpp new file mode 100644 index 0000000..ec52f90 --- /dev/null +++ b/Tutorial 1/src/Tutorial 1.cpp @@ -0,0 +1,108 @@ +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define __CL_ENABLE_EXCEPTIONS + +#include +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "Utils.h" + +void print_help() { + std::cerr << "Application usage:" << std::endl; + + std::cerr << " -p : select platform " << std::endl; + std::cerr << " -d : select device" << std::endl; + std::cerr << " -l : list all platforms and devices" << std::endl; + std::cerr << " -h : print this message" << std::endl; +} + +int main(int argc, char **argv) { + //Part 1 - handle command line options such as device selection, verbosity, etc. + int platform_id = 0; + int device_id = 0; + + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } + else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } + else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } + else if (strcmp(argv[i], "-h") == 0) { print_help(); } + } + + //detect any potential exceptions + try { + //Part 2 - host operations + //2.1 Select computing devices + cl::Context context = GetContext(platform_id, device_id); + + //display the selected device + std::cout << "Runinng on " << GetPlatformName(platform_id) << ", " << GetDeviceName(platform_id, device_id) << std::endl; + + //create a queue to which we will push commands for the device + cl::CommandQueue queue(context); + + //2.2 Load & build the device code + cl::Program::Sources sources; + + AddSources(sources, "kernels/my_kernels_1.cl"); + + cl::Program program(context, sources); + + //build and debug the kernel code + try { + program.build(); + } + catch (const cl::Error& err) { + std::cout << "Build Status: " << program.getBuildInfo(context.getInfo()[0]) << std::endl; + std::cout << "Build Options:\t" << program.getBuildInfo(context.getInfo()[0]) << std::endl; + std::cout << "Build Log:\t " << program.getBuildInfo(context.getInfo()[0]) << std::endl; + throw err; + } + + //Part 4 - memory allocation + //host - input + std::vector A = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //C++11 allows this type of initialisation + std::vector B = { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 }; + + size_t vector_elements = A.size();//number of elements + size_t vector_size = A.size()*sizeof(int);//size in bytes + + //host - output + std::vector C(vector_elements); + + //device - buffers + cl::Buffer buffer_A(context, CL_MEM_READ_WRITE, vector_size); + cl::Buffer buffer_B(context, CL_MEM_READ_WRITE, vector_size); + cl::Buffer buffer_C(context, CL_MEM_READ_WRITE, vector_size); + + //Part 5 - device operations + + //5.1 Copy arrays A and B to device memory + queue.enqueueWriteBuffer(buffer_A, CL_TRUE, 0, vector_size, &A[0]); + queue.enqueueWriteBuffer(buffer_B, CL_TRUE, 0, vector_size, &B[0]); + + //5.2 Setup and execute the kernel (i.e. device code) + cl::Kernel kernel_add = cl::Kernel(program, "add"); + kernel_add.setArg(0, buffer_A); + kernel_add.setArg(1, buffer_B); + kernel_add.setArg(2, buffer_C); + + queue.enqueueNDRangeKernel(kernel_add, cl::NullRange, cl::NDRange(vector_elements), cl::NullRange); + + //5.3 Copy the result from device to host + queue.enqueueReadBuffer(buffer_C, CL_TRUE, 0, vector_size, &C[0]); + + std::cout << "A = " << A << std::endl; + std::cout << "B = " << B << std::endl; + std::cout << "C = " << C << std::endl; + } + catch (cl::Error err) { + std::cerr << "ERROR: " << err.what() << ", " << getErrorString(err.err()) << std::endl; + } + + return 0; +} \ No newline at end of file diff --git a/Tutorial 1/src/kernels/my_kernels_1.cl b/Tutorial 1/src/kernels/my_kernels_1.cl new file mode 100644 index 0000000..02ba7e3 --- /dev/null +++ b/Tutorial 1/src/kernels/my_kernels_1.cl @@ -0,0 +1,24 @@ +//a simple OpenCL kernel which adds two vectors A and B together into a third vector C +kernel void add(global const int* A, global const int* B, global int* C) { + int id = get_global_id(0); + C[id] = A[id] + B[id]; +} + +//a simple smoothing kernel averaging values in a local window (radius 1) +kernel void avg_filter(global const int* A, global int* B) { + int id = get_global_id(0); + B[id] = (A[id - 1] + A[id] + A[id + 1])/3; +} + +//a simple 2D kernel +kernel void add2D(global const int* A, global const int* B, global int* C) { + int x = get_global_id(0); + int y = get_global_id(1); + int width = get_global_size(0); + int height = get_global_size(1); + int id = x + y*width; + + printf("id = %d x = %d y = %d w = %d h = %d\n", id, x, y, width, height); + + C[id]= A[id]+ B[id]; +} diff --git a/include/Utils.h b/include/Utils.h new file mode 100644 index 0000000..652c644 --- /dev/null +++ b/include/Utils.h @@ -0,0 +1,224 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +using namespace std; + +template +ostream& operator<< (ostream& out, const vector& v) { + if (!v.empty()) { + out << '['; + copy(v.begin(), v.end(), ostream_iterator(out, ", ")); + out << "\b\b]"; + } + return out; +} + +string GetPlatformName(int platform_id) { + vector platforms; + cl::Platform::get(&platforms); + return platforms[platform_id].getInfo(); +} + +string GetDeviceName(int platform_id, int device_id) { + vector platforms; + cl::Platform::get(&platforms); + vector devices; + platforms[platform_id].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + return devices[device_id].getInfo(); +} + +const char *getErrorString(cl_int error) { + switch (error){ + // run-time and JIT compiler errors + case 0: return "CL_SUCCESS"; + case -1: return "CL_DEVICE_NOT_FOUND"; + case -2: return "CL_DEVICE_NOT_AVAILABLE"; + case -3: return "CL_COMPILER_NOT_AVAILABLE"; + case -4: return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; + case -5: return "CL_OUT_OF_RESOURCES"; + case -6: return "CL_OUT_OF_HOST_MEMORY"; + case -7: return "CL_PROFILING_INFO_NOT_AVAILABLE"; + case -8: return "CL_MEM_COPY_OVERLAP"; + case -9: return "CL_IMAGE_FORMAT_MISMATCH"; + case -10: return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; + case -11: return "CL_BUILD_PROGRAM_FAILURE"; + case -12: return "CL_MAP_FAILURE"; + case -13: return "CL_MISALIGNED_SUB_BUFFER_OFFSET"; + case -14: return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST"; + case -15: return "CL_COMPILE_PROGRAM_FAILURE"; + case -16: return "CL_LINKER_NOT_AVAILABLE"; + case -17: return "CL_LINK_PROGRAM_FAILURE"; + case -18: return "CL_DEVICE_PARTITION_FAILED"; + case -19: return "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; + + // compile-time errors + case -30: return "CL_INVALID_VALUE"; + case -31: return "CL_INVALID_DEVICE_TYPE"; + case -32: return "CL_INVALID_PLATFORM"; + case -33: return "CL_INVALID_DEVICE"; + case -34: return "CL_INVALID_CONTEXT"; + case -35: return "CL_INVALID_QUEUE_PROPERTIES"; + case -36: return "CL_INVALID_COMMAND_QUEUE"; + case -37: return "CL_INVALID_HOST_PTR"; + case -38: return "CL_INVALID_MEM_OBJECT"; + case -39: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; + case -40: return "CL_INVALID_IMAGE_SIZE"; + case -41: return "CL_INVALID_SAMPLER"; + case -42: return "CL_INVALID_BINARY"; + case -43: return "CL_INVALID_BUILD_OPTIONS"; + case -44: return "CL_INVALID_PROGRAM"; + case -45: return "CL_INVALID_PROGRAM_EXECUTABLE"; + case -46: return "CL_INVALID_KERNEL_NAME"; + case -47: return "CL_INVALID_KERNEL_DEFINITION"; + case -48: return "CL_INVALID_KERNEL"; + case -49: return "CL_INVALID_ARG_INDEX"; + case -50: return "CL_INVALID_ARG_VALUE"; + case -51: return "CL_INVALID_ARG_SIZE"; + case -52: return "CL_INVALID_KERNEL_ARGS"; + case -53: return "CL_INVALID_WORK_DIMENSION"; + case -54: return "CL_INVALID_WORK_GROUP_SIZE"; + case -55: return "CL_INVALID_WORK_ITEM_SIZE"; + case -56: return "CL_INVALID_GLOBAL_OFFSET"; + case -57: return "CL_INVALID_EVENT_WAIT_LIST"; + case -58: return "CL_INVALID_EVENT"; + case -59: return "CL_INVALID_OPERATION"; + case -60: return "CL_INVALID_GL_OBJECT"; + case -61: return "CL_INVALID_BUFFER_SIZE"; + case -62: return "CL_INVALID_MIP_LEVEL"; + case -63: return "CL_INVALID_GLOBAL_WORK_SIZE"; + case -64: return "CL_INVALID_PROPERTY"; + case -65: return "CL_INVALID_IMAGE_DESCRIPTOR"; + case -66: return "CL_INVALID_COMPILER_OPTIONS"; + case -67: return "CL_INVALID_LINKER_OPTIONS"; + case -68: return "CL_INVALID_DEVICE_PARTITION_COUNT"; + + // extension errors + case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; + case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; + case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; + case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; + case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; + case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; + default: return "Unknown OpenCL error"; + } +} + +void CheckError(cl_int error) { + if (error != CL_SUCCESS) { + cerr << "OpenCL call failed with error " << getErrorString(error) << endl; + exit(1); + } +} + +void AddSources(cl::Program::Sources& sources, const string& file_name) { + //TODO: add file existence check + ifstream file(file_name); + string* source_code = new string(istreambuf_iterator(file), (istreambuf_iterator())); + sources.push_back(make_pair((*source_code).c_str(), source_code->length() + 1)); +} + +string ListPlatformsDevices() { + + stringstream sstream; + vector platforms; + + cl::Platform::get(&platforms); + + sstream << "Found " << platforms.size() << " platform(s):" << endl; + + for (unsigned int i = 0; i < platforms.size(); i++) + { + sstream << "\nPlatform " << i << ", " << platforms[i].getInfo() << ", version: " << platforms[i].getInfo(); + + sstream << ", vendor: " << platforms[i].getInfo() << endl; + // sstream << ", extensions: " << platforms[i].getInfo() << endl; + + vector devices; + + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + sstream << "\n Found " << devices.size() << " device(s):" << endl; + + for (unsigned int j = 0; j < devices.size(); j++) + { + sstream << "\n Device " << j << ", " << devices[j].getInfo() << ", version: " << devices[j].getInfo(); + + sstream << ", vendor: " << devices[j].getInfo(); + cl_device_type device_type = devices[j].getInfo(); + sstream << ", type: "; + if (device_type & CL_DEVICE_TYPE_DEFAULT) + sstream << "DEFAULT "; + if (device_type & CL_DEVICE_TYPE_CPU) + sstream << "CPU "; + if (device_type & CL_DEVICE_TYPE_GPU) + sstream << "GPU "; + if (device_type & CL_DEVICE_TYPE_ACCELERATOR) + sstream << "ACCELERATOR "; + sstream << ", compute units: " << devices[j].getInfo(); + sstream << ", clock freq [MHz]: " << devices[j].getInfo(); + sstream << ", max memory size [B]: " << devices[j].getInfo(); + sstream << ", max allocatable memory [B]: " << devices[j].getInfo(); + + sstream << endl; + } + } + sstream << "----------------------------------------------------------------" << endl; + + return sstream.str(); +} + +cl::Context GetContext(int platform_id, int device_id) { + vector platforms; + + cl::Platform::get(&platforms); + + for (unsigned int i = 0; i < platforms.size(); i++) + { + vector devices; + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + for (unsigned int j = 0; j < devices.size(); j++) + { + if ((i == platform_id) && (j == device_id)) + return cl::Context({ devices[j] }); + } + } + + return cl::Context(); +} + +enum ProfilingResolution { + PROF_NS = 1, + PROF_US = 1000, + PROF_MS = 1000000, + PROF_S = 1000000000 +}; + +string GetFullProfilingInfo(const cl::Event& evnt, ProfilingResolution resolution) { + stringstream sstream; + + sstream << "Queued " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Submitted " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Executed " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Total " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + + switch (resolution) { + case PROF_NS: sstream << " [ns]"; break; + case PROF_US: sstream << " [us]"; break; + case PROF_MS: sstream << " [ms]"; break; + case PROF_S: sstream << " [s]"; break; + default: break; + } + + return sstream.str(); +} \ No newline at end of file From 99e30c25cfd1e01f0125d6bbfec8855ca4398b14 Mon Sep 17 00:00:00 2001 From: Grzegorz Cielniak Date: Thu, 31 Jan 2019 17:33:14 +0000 Subject: [PATCH 02/69] Tutorial 2 prototype --- CMakeLists.txt | 4 +- Tutorial 1/CMakeLists.txt | 22 +- Tutorial 2/CMakeLists.txt | 43 + Tutorial 2/src/Tutorial 2.cpp | 111 + Tutorial 2/src/kernels/my_kernels_2.cl | 24 + include/CImg.h | 62163 +++++++++++++++++++++++ 6 files changed, 62356 insertions(+), 11 deletions(-) create mode 100644 Tutorial 2/CMakeLists.txt create mode 100644 Tutorial 2/src/Tutorial 2.cpp create mode 100644 Tutorial 2/src/kernels/my_kernels_2.cl create mode 100644 include/CImg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1dc9aec..b7d5520 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.6) project("OpenCL Tutorials") add_subdirectory("Tutorial 1") + +add_subdirectory("Tutorial 2") diff --git a/Tutorial 1/CMakeLists.txt b/Tutorial 1/CMakeLists.txt index cea8cf1..d0eeb13 100644 --- a/Tutorial 1/CMakeLists.txt +++ b/Tutorial 1/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 2.6) project("Tutorial 1") +set(TARGET_NAME Tutorial_1) + set(CMAKE_CXX_STANDARD 11) set(EXTRA_INCLUDE_DIRS ../include) @@ -14,28 +16,28 @@ set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) -include_directories(${EXTRA_INCLUDE_DIRS}) -link_directories(${EXTRA_LIBRARY_DIRS}) - set (SOURCES ../include/Utils.h "src/Tutorial 1.cpp" src/kernels/my_kernels_1.cl) -add_executable(Tutorial_1 ${SOURCES}) +include_directories(${EXTRA_INCLUDE_DIRS}) +link_directories(${EXTRA_LIBRARY_DIRS}) + +add_executable(${TARGET_NAME} ${SOURCES}) -target_link_libraries (Tutorial_1 ${EXTRA_LIBRARIES}) +target_link_libraries (${TARGET_NAME} ${EXTRA_LIBRARIES}) #copy kernel files to target directory -add_custom_command(TARGET Tutorial_1 PRE_BUILD +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_SOURCE_DIR}/Tutorial 1/src/kernels/" - $/kernels/) + "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" + $/kernels/) #additional command to copy kernels into a working directory allowing debugging directly from VS if (MSVC) -add_custom_command(TARGET Tutorial_1 PRE_BUILD +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_SOURCE_DIR}/Tutorial 1/src/kernels/" + "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" kernels) endif(MSVC) \ No newline at end of file diff --git a/Tutorial 2/CMakeLists.txt b/Tutorial 2/CMakeLists.txt new file mode 100644 index 0000000..5cac5fa --- /dev/null +++ b/Tutorial 2/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 2.6) + +project("Tutorial 2") + +set(TARGET_NAME Tutorial_2) + +set(CMAKE_CXX_STANDARD 11) + +set(EXTRA_INCLUDE_DIRS ../include) +set(EXTRA_LIBRARY_DIRS "") +set(EXTRA_LIBRARIES "") + +find_package(OpenCL REQUIRED) + +set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) + +set (SOURCES + ../include/Utils.h + "src/Tutorial 2.cpp" + src/kernels/my_kernels_2.cl) + +include_directories(${EXTRA_INCLUDE_DIRS}) +link_directories(${EXTRA_LIBRARY_DIRS}) + +add_executable(${TARGET_NAME} ${SOURCES}) + +target_link_libraries (${TARGET_NAME} ${EXTRA_LIBRARIES}) + +#copy kernel files to target directory +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" + $/kernels/) + +#additional command to copy kernels into a working directory allowing debugging directly from VS +if (MSVC) +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" + kernels) +endif(MSVC) \ No newline at end of file diff --git a/Tutorial 2/src/Tutorial 2.cpp b/Tutorial 2/src/Tutorial 2.cpp new file mode 100644 index 0000000..f64bbd1 --- /dev/null +++ b/Tutorial 2/src/Tutorial 2.cpp @@ -0,0 +1,111 @@ +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define __CL_ENABLE_EXCEPTIONS + +#include +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "Utils.h" +#include "CImg.h" + +using namespace cimg_library; + +void print_help() { + std::cerr << "Application usage:" << std::endl; + + std::cerr << " -p : select platform " << std::endl; + std::cerr << " -d : select device" << std::endl; + std::cerr << " -l : list all platforms and devices" << std::endl; + std::cerr << " -h : print this message" << std::endl; +} + +int main(int argc, char **argv) { + //Part 1 - handle command line options such as device selection, verbosity, etc. + int platform_id = 0; + int device_id = 0; + + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } + else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } + else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } + else if (strcmp(argv[i], "-h") == 0) { print_help(); } + } + + //detect any potential exceptions + try { + CImg image_before("test.ppm"); + CImgDisplay main_disp(image_before,"before"); + + std::vector image_after; + + //a 3x3 convolution mask implementing an averaging filter + std::vector convolution_mask = { 1.f / 9, 1.f / 9, 1.f / 9, + 1.f / 9, 1.f / 9, 1.f / 9, + 1.f / 9, 1.f / 9, 1.f / 9 }; + + //Part 3 - host operations + //3.1 Select computing devices + cl::Context context = GetContext(platform_id, device_id); + + //display the selected device + std::cout << "Runing on " << GetPlatformName(platform_id) << ", " << GetDeviceName(platform_id, device_id) << std::endl; + + //create a queue to which we will push commands for the device + cl::CommandQueue queue(context); + + //3.2 Load & build the device code + cl::Program::Sources sources; + + AddSources(sources, "my_kernels_2.cl"); + + cl::Program program(context, sources); + + //build and debug the kernel code + try { + program.build(); + } + catch (const cl::Error& err) { + std::cout << "Build Status: " << program.getBuildInfo(context.getInfo()[0]) << std::endl; + std::cout << "Build Options:\t" << program.getBuildInfo(context.getInfo()[0]) << std::endl; + std::cout << "Build Log:\t " << program.getBuildInfo(context.getInfo()[0]) << std::endl; + throw err; + } + + //Part 4 - device operations + + //device - buffers + cl::Buffer dev_image_before(context, CL_MEM_READ_ONLY, image_before.size()); + cl::Buffer dev_image_after(context, CL_MEM_READ_WRITE, image_after.size()); + //cl::Buffer dev_convolution_mask(context, CL_MEM_READ_ONLY, convolution_mask.size()*sizeof(float)); + + //4.1 Copy images to device memory + queue.enqueueWriteBuffer(dev_image_before, CL_TRUE, 0, image_before.size(), &image_before[0]); + //queue.enqueueWriteBuffer(dev_convolution_mask, CL_TRUE, 0, convolution_mask.size()*sizeof(float), &convolution_mask[0]); + + //4.2 Setup and execute the kernel (i.e. device code) + cl::Kernel kernel = cl::Kernel(program, "identity"); + kernel.setArg(0, dev_image_before); + kernel.setArg(1, dev_image_after); + //kernel.setArg(2, dev_convolution_mask); + + queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_before.width()*image_before.height()), cl::NullRange); + + //4.3 Copy the result from device to host + queue.enqueueReadBuffer(dev_image_after, CL_TRUE, 0, image_after.size(), &image_after[0]); + + while (!main_disp.is_closed()) { + main_disp.wait(); + } + + } + catch (const cl::Error& err) { + std::cerr << "ERROR: " << err.what() << ", " << getErrorString(err.err()) << std::endl; + } + + return 0; +} diff --git a/Tutorial 2/src/kernels/my_kernels_2.cl b/Tutorial 2/src/kernels/my_kernels_2.cl new file mode 100644 index 0000000..02ba7e3 --- /dev/null +++ b/Tutorial 2/src/kernels/my_kernels_2.cl @@ -0,0 +1,24 @@ +//a simple OpenCL kernel which adds two vectors A and B together into a third vector C +kernel void add(global const int* A, global const int* B, global int* C) { + int id = get_global_id(0); + C[id] = A[id] + B[id]; +} + +//a simple smoothing kernel averaging values in a local window (radius 1) +kernel void avg_filter(global const int* A, global int* B) { + int id = get_global_id(0); + B[id] = (A[id - 1] + A[id] + A[id + 1])/3; +} + +//a simple 2D kernel +kernel void add2D(global const int* A, global const int* B, global int* C) { + int x = get_global_id(0); + int y = get_global_id(1); + int width = get_global_size(0); + int height = get_global_size(1); + int id = x + y*width; + + printf("id = %d x = %d y = %d w = %d h = %d\n", id, x, y, width, height); + + C[id]= A[id]+ B[id]; +} diff --git a/include/CImg.h b/include/CImg.h new file mode 100644 index 0000000..d9d2a21 --- /dev/null +++ b/include/CImg.h @@ -0,0 +1,62163 @@ +/* + # + # File : CImg.h + # ( C++ header file ) + # + # Description : The C++ Template Image Processing Toolkit. + # This file is the main component of the CImg Library project. + # ( http://cimg.eu ) + # + # Project manager : David Tschumperle. + # ( http://tschumperle.users.greyc.fr/ ) + # + # A complete list of contributors is available in file 'README.txt' + # distributed within the CImg package. + # + # Licenses : This file is 'dual-licensed', you have to choose one + # of the two licenses below to apply. + # + # CeCILL-C + # The CeCILL-C license is close to the GNU LGPL. + # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html ) + # + # or CeCILL v2.1 + # The CeCILL license is compatible with the GNU GPL. + # ( http://www.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". + # + # As a counterpart to the access to the source code and rights to copy, + # modify and redistribute granted by the license, users are provided only + # with a limited warranty and the software's author, the holder of the + # economic rights, and the successive licensors have only limited + # liability. + # + # In this respect, the user's attention is drawn to the risks associated + # with loading, using, modifying and/or developing or reproducing the + # software by the user in light of its specific status of free software, + # that may mean that it is complicated to manipulate, and that also + # therefore means that it is reserved for developers and experienced + # professionals having in-depth computer knowledge. Users are therefore + # encouraged to load and test the software's suitability as regards their + # requirements in conditions enabling the security of their systems and/or + # data to be ensured and, more generally, to use and operate it in the + # same conditions as regards security. + # + # The fact that you are presently reading this means that you have had + # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms. + # +*/ + +// Set version number of the library. +#ifndef cimg_version +#define cimg_version 250 + +/*----------------------------------------------------------- + # + # Test and possibly auto-set CImg configuration variables + # and include required headers. + # + # If you find that the default configuration variables are + # not adapted to your system, you can override their values + # before including the header file "CImg.h" + # (use the #define directive). + # + ------------------------------------------------------------*/ + +// Include standard C++ headers. +// This is the minimal set of required headers to make CImg-based codes compile. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Detect/configure OS variables. +// +// Define 'cimg_OS' to: '0' for an unknown OS (will try to minize library dependencies). +// '1' for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...). +// '2' for Microsoft Windows. +// (auto-detection is performed if 'cimg_OS' is not set by the user). +#ifndef cimg_OS +#if defined(unix) || defined(__unix) || defined(__unix__) \ + || defined(linux) || defined(__linux) || defined(__linux__) \ + || defined(sun) || defined(__sun) \ + || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__FreeBSD__) || defined (__DragonFly__) \ + || defined(sgi) || defined(__sgi) \ + || defined(__MACOSX__) || defined(__APPLE__) \ + || defined(__CYGWIN__) +#define cimg_OS 1 +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \ + || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) +#define cimg_OS 2 +#else +#define cimg_OS 0 +#endif +#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2) +#error CImg Library: Invalid configuration variable 'cimg_OS'. +#error (correct values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows'). +#endif +#ifndef cimg_date +#define cimg_date __DATE__ +#endif +#ifndef cimg_time +#define cimg_time __TIME__ +#endif + +// Disable silly warnings on some Microsoft VC++ compilers. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) +#pragma warning(disable:4244) +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4319) +#pragma warning(disable:4512) +#pragma warning(disable:4571) +#pragma warning(disable:4640) +#pragma warning(disable:4706) +#pragma warning(disable:4710) +#pragma warning(disable:4800) +#pragma warning(disable:4804) +#pragma warning(disable:4820) +#pragma warning(disable:4996) + +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#endif +#endif + +// Define correct string functions for each compiler and OS. +#if cimg_OS==2 && defined(_MSC_VER) +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#include +#if defined(__MACOSX__) || defined(__APPLE__) +#define cimg_sscanf cimg::_sscanf +#define cimg_sprintf cimg::_sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf snprintf +#define cimg_vsnprintf vsnprintf +#endif +#endif + +// Include OS-specific headers. +#if cimg_OS==1 +#include +#include +#include +#include +#include +#include +#elif cimg_OS==2 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#ifndef _WIN32_IE +#define _WIN32_IE 0x0400 +#endif +#include +#include +#include +#endif + +// Look for C++11 features. +#ifndef cimg_use_cpp11 +#if __cplusplus>201100 +#define cimg_use_cpp11 1 +#else +#define cimg_use_cpp11 0 +#endif +#endif +#if cimg_use_cpp11==1 +#include +#include +#endif + +// Convenient macro to define pragma +#ifdef _MSC_VER +#define cimg_pragma(x) __pragma(x) +#else +#define cimg_pragma(x) _Pragma(#x) +#endif + +// Define own types 'cimg_long/ulong' and 'cimg_int64/uint64' to ensure portability. +// ( constrained to 'sizeof(cimg_ulong/cimg_long) = sizeof(void*)' and 'sizeof(cimg_int64/cimg_uint64)=8' ). +#if cimg_OS==2 + +#define cimg_uint64 unsigned __int64 +#define cimg_int64 __int64 +#define cimg_ulong UINT_PTR +#define cimg_long INT_PTR +#ifdef _MSC_VER +#define cimg_fuint64 "%I64u" +#define cimg_fint64 "%I64d" +#else +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#endif + +#else + +#if UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX)) +#define cimg_uint64 unsigned long long +#define cimg_int64 long long +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#else +#define cimg_uint64 unsigned long +#define cimg_int64 long +#define cimg_fuint64 "%lu" +#define cimg_fint64 "%ld" +#endif + +#if defined(__arm__) || defined(_M_ARM) +#define cimg_ulong unsigned long long +#define cimg_long long long +#else +#define cimg_ulong unsigned long +#define cimg_long long +#endif + +#endif + +// Configure filename separator. +// +// Filename separator is set by default to '/', except for Windows where it is '\'. +#ifndef cimg_file_separator +#if cimg_OS==2 +#define cimg_file_separator '\\' +#else +#define cimg_file_separator '/' +#endif +#endif + +// Configure verbosity of output messages. +// +// Define 'cimg_verbosity' to: '0' to hide library messages (quiet mode). +// '1' to output library messages on the console. +// '2' to output library messages on a basic dialog window (default behavior). +// '3' to do as '1' + add extra warnings (may slow down the code!). +// '4' to do as '2' + add extra warnings (may slow down the code!). +// +// Define 'cimg_strict_warnings' to replace warning messages by exception throwns. +// +// Define 'cimg_use_vt100' to allow output of color messages on VT100-compatible terminals. +#ifndef cimg_verbosity +#if cimg_OS==2 +#define cimg_verbosity 2 +#else +#define cimg_verbosity 1 +#endif +#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4) +#error CImg Library: Configuration variable 'cimg_verbosity' is badly defined. +#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }). +#endif + +// Configure display framework. +// +// Define 'cimg_display' to: '0' to disable display capabilities. +// '1' to use the X-Window framework (X11). +// '2' to use the Microsoft GDI32 framework. +#ifndef cimg_display +#if cimg_OS==0 +#define cimg_display 0 +#elif cimg_OS==1 +#define cimg_display 1 +#elif cimg_OS==2 +#define cimg_display 2 +#endif +#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2) +#error CImg Library: Configuration variable 'cimg_display' is badly defined. +#error (should be { 0=none | 1=X-Window (X11) | 2=Microsoft GDI32 }). +#endif + +// Configure the 'abort' signal handler (does nothing by default). +// A typical signal handler can be defined in your own source like this: +// #define cimg_abort_test if (is_abort) throw CImgAbortException("") +// +// where 'is_abort' is a boolean variable defined somewhere in your code and reachable in the method. +// 'cimg_abort_test2' does the same but is called more often (in inner loops). +#if defined(cimg_abort_test) && defined(cimg_use_openmp) + +// Define abort macros to be used with OpenMP. +#ifndef _cimg_abort_init_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 + +// Include display-specific headers. +#if cimg_display==1 +#include +#include +#include +#include +#ifdef cimg_use_xshm +#include +#include +#include +#endif +#ifdef cimg_use_xrandr +#include +#endif +#endif +#ifndef cimg_appname +#define cimg_appname "CImg" +#endif + +// Configure OpenMP support. +// (http://www.openmp.org) +// +// Define 'cimg_use_openmp' to enable OpenMP support (requires OpenMP 3.0+). +// +// OpenMP directives are used in many 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/) +// +// Define 'cimg_use_opencv' to enable OpenCV support. +// +// OpenCV library may be used to access images from cameras +// (see method 'CImg::load_camera()'). +#ifdef cimg_use_opencv +#ifdef True +#undef True +#define _cimg_redefine_True +#endif +#ifdef False +#undef False +#define _cimg_redefine_False +#endif +#include +#include "cv.h" +#include "highgui.h" +#endif + +// Configure LibPNG support. +// (http://www.libpng.org) +// +// Define 'cimg_use_png' to enable LibPNG support. +// +// PNG library may be used to get a native support of '.png' files. +// (see methods 'CImg::{load,save}_png()'. +#ifdef cimg_use_png +extern "C" { +#include "png.h" +} +#endif + +// Configure LibJPEG support. +// (http://en.wikipedia.org/wiki/Libjpeg) +// +// Define 'cimg_use_jpeg' to enable LibJPEG support. +// +// JPEG library may be used to get a native support of '.jpg' files. +// (see methods 'CImg::{load,save}_jpeg()'). +#ifdef cimg_use_jpeg +extern "C" { +#include "jpeglib.h" +#include "setjmp.h" +} +#endif + +// Configure LibTIFF support. +// (http://www.libtiff.org) +// +// Define 'cimg_use_tiff' to enable LibTIFF support. +// +// TIFF library may be used to get a native support of '.tif' files. +// (see methods 'CImg[List]::{load,save}_tiff()'). +#ifdef cimg_use_tiff +extern "C" { +#define uint64 uint64_hack_ +#define int64 int64_hack_ +#include "tiffio.h" +#undef uint64 +#undef int64 +} +#endif + +// Configure LibMINC2 support. +// (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference) +// +// Define 'cimg_use_minc2' to enable LibMINC2 support. +// +// MINC2 library may be used to get a native support of '.mnc' files. +// (see methods 'CImg::{load,save}_minc2()'). +#ifdef cimg_use_minc2 +#include "minc_io_simple_volume.h" +#include "minc_1_simple.h" +#include "minc_1_simple_rw.h" +#endif + +// Configure Zlib support. +// (http://www.zlib.net) +// +// Define 'cimg_use_zlib' to enable Zlib support. +// +// Zlib library may be used to allow compressed data in '.cimgz' files +// (see methods 'CImg[List]::{load,save}_cimg()'). +#ifdef cimg_use_zlib +extern "C" { +#include "zlib.h" +} +#endif + +// Configure libcurl support. +// (http://curl.haxx.se/libcurl/) +// +// Define 'cimg_use_curl' to enable libcurl support. +// +// Libcurl may be used to get a native support of file downloading from the network. +// (see method 'cimg::load_network()'.) +#ifdef cimg_use_curl +#include "curl/curl.h" +#endif + +// Configure Magick++ support. +// (http://www.imagemagick.org/Magick++) +// +// Define 'cimg_use_magick' to enable Magick++ support. +// +// Magick++ library may be used to get a native support of various image file formats. +// (see methods 'CImg::{load,save}()'). +#ifdef cimg_use_magick +#include "Magick++.h" +#endif + +// Configure FFTW3 support. +// (http://www.fftw.org) +// +// Define 'cimg_use_fftw3' to enable libFFTW3 support. +// +// FFTW3 library may be used to efficiently compute the Fast Fourier Transform +// of image data, without restriction on the image size. +// (see method 'CImg[List]::FFT()'). +#ifdef cimg_use_fftw3 +extern "C" { +#include "fftw3.h" +} +#endif + +// Configure LibBoard support. +// (http://libboard.sourceforge.net/) +// +// Define 'cimg_use_board' to enable Board support. +// +// Board library may be used to draw 3D objects in vector-graphics canvas +// that can be saved as '.ps' or '.svg' files afterwards. +// (see method 'CImg::draw_object3d()'). +#ifdef cimg_use_board +#include "Board.h" +#endif + +// Configure OpenEXR support. +// (http://www.openexr.com/) +// +// Define 'cimg_use_openexr' to enable OpenEXR support. +// +// OpenEXR library may be used to get a native support of '.exr' files. +// (see methods 'CImg::{load,save}_exr()'). +#ifdef cimg_use_openexr +#include "ImfRgbaFile.h" +#include "ImfInputFile.h" +#include "ImfChannelList.h" +#include "ImfMatrixAttribute.h" +#include "ImfArray.h" +#endif + +// Configure TinyEXR support. +// (https://github.com/syoyo/tinyexr) +// +// Define 'cimg_use_tinyexr' to enable TinyEXR support. +// +// TinyEXR is a small, single header-only library to load and save OpenEXR(.exr) images. +#ifdef cimg_use_tinyexr +#ifndef TINYEXR_IMPLEMENTATION +#define TINYEXR_IMPLEMENTATION +#endif +#include "tinyexr.h" +#endif + +// Lapack configuration. +// (http://www.netlib.org/lapack) +// +// Define 'cimg_use_lapack' to enable LAPACK support. +// +// Lapack library may be used in several CImg methods to speed up +// matrix computations (eigenvalues, inverse, ...). +#ifdef cimg_use_lapack +extern "C" { + extern void sgetrf_(int*, int*, float*, int*, int*, int*); + extern void sgetri_(int*, float*, int*, int*, float*, int*, int*); + extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*); + extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*); + extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*); + extern void dgetrf_(int*, int*, double*, int*, int*, int*); + extern void dgetri_(int*, double*, int*, int*, double*, int*, int*); + extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*); + extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, + int*, double*, int*, double*, int*, int*); + extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*); + extern void dgels_(char*, int*,int*,int*,double*,int*,double*,int*,double*,int*,int*); + extern void sgels_(char*, int*,int*,int*,float*,int*,float*,int*,float*,int*,int*); +} +#endif + +// Check if min/max/PI macros are defined. +// +// CImg does not compile if macros 'min', 'max' or 'PI' are defined, +// because it redefines functions min(), max() and const variable PI in the cimg:: namespace. +// so it '#undef' these macros if necessary, and restore them to reasonable +// values at the end of this file. +#ifdef min +#undef min +#define _cimg_redefine_min +#endif +#ifdef max +#undef max +#define _cimg_redefine_max +#endif +#ifdef PI +#undef PI +#define _cimg_redefine_PI +#endif + +// Define 'cimg_library' namespace suffix. +// +// You may want to add a suffix to the 'cimg_library' namespace, for instance if you need to work +// with several versions of the library at the same time. +#ifdef cimg_namespace_suffix +#define __cimg_library_suffixed(s) cimg_library_##s +#define _cimg_library_suffixed(s) __cimg_library_suffixed(s) +#define cimg_library_suffixed _cimg_library_suffixed(cimg_namespace_suffix) +#else +#define cimg_library_suffixed cimg_library +#endif + +/*------------------------------------------------------------------------------ + # + # Define user-friendly macros. + # + # These CImg macros are prefixed by 'cimg_' and can be used safely in your own + # code. They are useful to parse command line options, or to write image loops. + # + ------------------------------------------------------------------------------*/ + +// Macros to define program usage, and retrieve command line arguments. +#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false) +#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0) +#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage) + +// Macros to define and manipulate local neighborhoods. +#define CImg_2x2(I,T) T I[4]; \ + T& I##cc = I[0]; T& I##nc = I[1]; \ + T& I##cn = I[2]; T& I##nn = I[3]; \ + I##cc = I##nc = \ + I##cn = I##nn = 0 + +#define CImg_3x3(I,T) T I[9]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \ + T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \ + T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \ + I##pp = I##cp = I##np = \ + I##pc = I##cc = I##nc = \ + I##pn = I##cn = I##nn = 0 + +#define CImg_4x4(I,T) T I[16]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \ + T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \ + T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \ + T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \ + I##pp = I##cp = I##np = I##ap = \ + I##pc = I##cc = I##nc = I##ac = \ + I##pn = I##cn = I##nn = I##an = \ + I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_5x5(I,T) T I[25]; \ + T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \ + T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \ + T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \ + T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \ + T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \ + I##bb = I##pb = I##cb = I##nb = I##ab = \ + I##bp = I##pp = I##cp = I##np = I##ap = \ + I##bc = I##pc = I##cc = I##nc = I##ac = \ + I##bn = I##pn = I##cn = I##nn = I##an = \ + I##ba = I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_2x2x2(I,T) T I[8]; \ + T& I##ccc = I[0]; T& I##ncc = I[1]; \ + T& I##cnc = I[2]; T& I##nnc = I[3]; \ + T& I##ccn = I[4]; T& I##ncn = I[5]; \ + T& I##cnn = I[6]; T& I##nnn = I[7]; \ + I##ccc = I##ncc = \ + I##cnc = I##nnc = \ + I##ccn = I##ncn = \ + I##cnn = I##nnn = 0 + +#define CImg_3x3x3(I,T) T I[27]; \ + T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \ + T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \ + T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \ + T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \ + T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \ + T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \ + T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \ + T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \ + T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \ + I##ppp = I##cpp = I##npp = \ + I##pcp = I##ccp = I##ncp = \ + I##pnp = I##cnp = I##nnp = \ + I##ppc = I##cpc = I##npc = \ + I##pcc = I##ccc = I##ncc = \ + I##pnc = I##cnc = I##nnc = \ + I##ppn = I##cpn = I##npn = \ + I##pcn = I##ccn = I##ncn = \ + I##pnn = I##cnn = I##nnn = 0 + +#define cimg_get2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_p1##x,y,z,c), I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), \ + I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), I[8] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get4x4(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_n2##x,_p1##y,z,c), I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), \ + I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), I[8] = (T)(img)(_p1##x,_n1##y,z,c), \ + I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \ + I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[15] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get5x5(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), I[8] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \ + I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), \ + I[15] = (T)(img)(_p2##x,_n1##y,z,c), I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), I[20] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get6x6(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), I[8] = (T)(img)(x,_p1##y,z,c), \ + I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), \ + I[15] = (T)(img)(_n1##x,y,z,c), I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), \ + I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), I[20] = (T)(img)(x,_n1##y,z,c), \ + I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), \ + I[27] = (T)(img)(_n1##x,_n2##y,z,c), I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), I[32] = (T)(img)(x,_n3##y,z,c), \ + I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get7x7(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), I[8] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), \ + I[15] = (T)(img)(_p2##x,_p1##y,z,c), I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), I[20] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \ + I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), \ + I[27] = (T)(img)(_n3##x,y,z,c), I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), I[32] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \ + I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), \ + I[39] = (T)(img)(_n1##x,_n2##y,z,c), I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), I[44] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[48] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get8x8(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), I[8] = (T)(img)(_p3##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \ + I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[15] = (T)(img)(_n4##x,_p2##y,z,c), I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), I[20] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), \ + I[27] = (T)(img)(x,y,z,c), I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), \ + I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), I[32] = (T)(img)(_p3##x,_n1##y,z,c), \ + I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \ + I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[39] = (T)(img)(_n4##x,_n1##y,z,c), I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), I[44] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[51] = (T)(img)(x,_n3##y,z,c), I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), I[56] = (T)(img)(_p3##x,_n4##y,z,c), \ + I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \ + I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), \ + I[63] = (T)(img)(_n4##x,_n4##y,z,c); + +#define cimg_get9x9(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), \ + I[3] = (T)(img)(_p1##x,_p4##y,z,c), I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), \ + I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), I[8] = (T)(img)(_n4##x,_p4##y,z,c), \ + I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \ + I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), \ + I[15] = (T)(img)(_n2##x,_p3##y,z,c), I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), \ + I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), I[20] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), \ + I[27] = (T)(img)(_p4##x,_p1##y,z,c), I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), I[32] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), \ + I[39] = (T)(img)(_p1##x,y,z,c), I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), \ + I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), I[44] = (T)(img)(_n4##x,y,z,c), \ + I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[51] = (T)(img)(_n2##x,_n1##y,z,c), I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), \ + I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), I[56] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[63] = (T)(img)(_p4##x,_n3##y,z,c), I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), \ + I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), I[68] = (T)(img)(_n1##x,_n3##y,z,c), \ + I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \ + I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), \ + I[75] = (T)(img)(_p1##x,_n4##y,z,c), I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), \ + I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), I[80] = (T)(img)(_n4##x,_n4##y,z,c) + +#define cimg_get2x2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c), I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), \ + I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +#define cimg_get3x3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), \ + I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), \ + I[5] = (T)(img)(_n1##x,y,_p1##z,c), I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), \ + I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), \ + I[11] = (T)(img)(_n1##x,_p1##y,z,c), I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), \ + I[14] = (T)(img)(_n1##x,y,z,c), I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), \ + I[17] = (T)(img)(_n1##x,_n1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), \ + I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), \ + I[23] = (T)(img)(_n1##x,y,_n1##z,c), I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), \ + I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +// Macros to perform various image loops. +// +// These macros are simpler to use than loops with C++ iterators. +#define cimg_for(img,ptrs,T_ptrs) \ + for (T_ptrs *ptrs = (img)._data, *_max##ptrs = (img)._data + (img).size(); ptrs<_max##ptrs; ++ptrs) +#define cimg_rof(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size() - 1; ptrs>=(img)._data; --ptrs) +#define cimg_foroff(img,off) for (cimg_ulong off = 0, _max##off = (img).size(); off<_max##off; ++off) +#define cimg_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) +#define cimg_forY(img,y) cimg_for1((img)._height,y) +#define cimg_forZ(img,z) cimg_for1((img)._depth,z) +#define cimg_forC(img,c) cimg_for1((img)._spectrum,c) +#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x) +#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x) +#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y) +#define cimg_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x) +#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y) +#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z) +#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y) +#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y) +#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z) +#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z) +#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) cimg_forXYZ(img,x,y,z) + +#define cimg_rof1(bound,i) for (int i = (int)(bound) - 1; i>=0; --i) +#define cimg_rofX(img,x) cimg_rof1((img)._width,x) +#define cimg_rofY(img,y) cimg_rof1((img)._height,y) +#define cimg_rofZ(img,z) cimg_rof1((img)._depth,z) +#define cimg_rofC(img,c) cimg_rof1((img)._spectrum,c) +#define cimg_rofXY(img,x,y) cimg_rofY(img,y) cimg_rofX(img,x) +#define cimg_rofXZ(img,x,z) cimg_rofZ(img,z) cimg_rofX(img,x) +#define cimg_rofYZ(img,y,z) cimg_rofZ(img,z) cimg_rofY(img,y) +#define cimg_rofXC(img,x,c) cimg_rofC(img,c) cimg_rofX(img,x) +#define cimg_rofYC(img,y,c) cimg_rofC(img,c) cimg_rofY(img,y) +#define cimg_rofZC(img,z,c) cimg_rofC(img,c) cimg_rofZ(img,z) +#define cimg_rofXYZ(img,x,y,z) cimg_rofZ(img,z) cimg_rofXY(img,x,y) +#define cimg_rofXYC(img,x,y,c) cimg_rofC(img,c) cimg_rofXY(img,x,y) +#define cimg_rofXZC(img,x,z,c) cimg_rofC(img,c) cimg_rofXZ(img,x,z) +#define cimg_rofYZC(img,y,z,c) cimg_rofC(img,c) cimg_rofYZ(img,y,z) +#define cimg_rofXYZC(img,x,y,z,c) cimg_rofC(img,c) cimg_rofXYZ(img,x,y,z) + +#define cimg_for_in1(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound) - 1; i<=_max##i; ++i) +#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img)._width,x0,x1,x) +#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img)._height,y0,y1,y) +#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img)._depth,z0,z1,z) +#define cimg_for_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c) +#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inZ(img,z0,z1,z) +#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z) +#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_inC(img,c0,c1,c) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_insideXYZ(img,x,y,z,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_insideXYZC(img,x,y,z,c,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) + +#define cimg_for_out1(boundi,i0,i1,i) \ + for (int i = (int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1) + 1:i) +#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \ + for (int j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \ + for (int k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \ + for (int l = 0; l<(int)(boundl); ++l) \ + for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1) + 1; \ + i<(int)(boundi); ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img)._width,x0,x1,x) +#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img)._height,y0,y1,y) +#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img)._depth,z0,z1,z) +#define cimg_for_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c) +#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img)._width,(img)._height,x0,y0,x1,y1,x,y) +#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img)._width,(img)._depth,x0,z0,x1,z1,x,z) +#define cimg_for_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c) +#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img)._height,(img)._depth,y0,z0,y1,z1,y,z) +#define cimg_for_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c) +#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c) +#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) \ + cimg_for_out3((img)._width,(img)._height,(img)._depth,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) \ + cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c) +#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) \ + cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c) +#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) \ + cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c) +#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) +#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_borderXYZ(img,x,y,z,n) \ + cimg_for_outXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_borderXYZC(img,x,y,z,c,n) \ + cimg_for_outXYZC(img,n,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n), \ + (img)._depth - 1 - (n),(img)._spectrum - 1 - (n),x,y,z,c) + +#define cimg_for_spiralXY(img,x,y) \ + for (int x = 0, y = 0, _n1##x = 1, _n1##y = (img).width()*(img).height(); _n1##y; \ + --_n1##y, _n1##x+=(_n1##x>>2) - ((!(_n1##x&3)?--y:((_n1##x&3)==1?(img)._width - 1 - ++x:\ + ((_n1##x&3)==2?(img)._height - 1 - ++y:--x))))?0:1) + +#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \ + for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \ + _dx=(x1)>(x0)?(int)(x1) - (int)(x0):(_sx=-1,(int)(x0) - (int)(x1)), \ + _dy=(y1)>(y0)?(int)(y1) - (int)(y0):(_sy=-1,(int)(y0) - (int)(y1)), \ + _counter = _dx, \ + _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \ + _counter>=0; \ + --_counter, x+=_steep? \ + (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \ + (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx)) + +#define cimg_for2(bound,i) \ + for (int i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + ++i, ++_n1##i) +#define cimg_for2X(img,x) cimg_for2((img)._width,x) +#define cimg_for2Y(img,y) cimg_for2((img)._height,y) +#define cimg_for2Z(img,z) cimg_for2((img)._depth,z) +#define cimg_for2C(img,c) cimg_for2((img)._spectrum,c) +#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x) +#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x) +#define cimg_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x) +#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y) +#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y) +#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z) +#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y) +#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z) +#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z) +#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) cimg_for2XYZ(img,x,y,z) + +#define cimg_for_in2(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + ++i, ++_n1##i) +#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img)._width,x0,x1,x) +#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img)._height,y0,y1,y) +#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img)._depth,z0,z1,z) +#define cimg_for_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c) +#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Z(img,z0,z1,z) +#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in2C(img,c0,c1,c) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i) +#define cimg_for3X(img,x) cimg_for3((img)._width,x) +#define cimg_for3Y(img,y) cimg_for3((img)._height,y) +#define cimg_for3Z(img,z) cimg_for3((img)._depth,z) +#define cimg_for3C(img,c) cimg_for3((img)._spectrum,c) +#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x) +#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x) +#define cimg_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x) +#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y) +#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y) +#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z) +#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y) +#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z) +#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z) +#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) cimg_for3XYZ(img,x,y,z) + +#define cimg_for_in3(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + _p1##i = i++, ++_n1##i) +#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img)._width,x0,x1,x) +#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img)._height,y0,y1,y) +#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img)._depth,z0,z1,z) +#define cimg_for_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c) +#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Z(img,z0,z1,z) +#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in3C(img,c0,c1,c) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for4(bound,i) \ + for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for4X(img,x) cimg_for4((img)._width,x) +#define cimg_for4Y(img,y) cimg_for4((img)._height,y) +#define cimg_for4Z(img,z) cimg_for4((img)._depth,z) +#define cimg_for4C(img,c) cimg_for4((img)._spectrum,c) +#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x) +#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x) +#define cimg_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x) +#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y) +#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y) +#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z) +#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y) +#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z) +#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z) +#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) cimg_for4XYZ(img,x,y,z) + +#define cimg_for_in4(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img)._width,x0,x1,x) +#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img)._height,y0,y1,y) +#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img)._depth,z0,z1,z) +#define cimg_for_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c) +#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Z(img,z0,z1,z) +#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in4C(img,c0,c1,c) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for5(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for5X(img,x) cimg_for5((img)._width,x) +#define cimg_for5Y(img,y) cimg_for5((img)._height,y) +#define cimg_for5Z(img,z) cimg_for5((img)._depth,z) +#define cimg_for5C(img,c) cimg_for5((img)._spectrum,c) +#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x) +#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x) +#define cimg_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x) +#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y) +#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y) +#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z) +#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y) +#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z) +#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z) +#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) cimg_for5XYZ(img,x,y,z) + +#define cimg_for_in5(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img)._width,x0,x1,x) +#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img)._height,y0,y1,y) +#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img)._depth,z0,z1,z) +#define cimg_for_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c) +#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Z(img,z0,z1,z) +#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in5C(img,c0,c1,c) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for6(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for6X(img,x) cimg_for6((img)._width,x) +#define cimg_for6Y(img,y) cimg_for6((img)._height,y) +#define cimg_for6Z(img,z) cimg_for6((img)._depth,z) +#define cimg_for6C(img,c) cimg_for6((img)._spectrum,c) +#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x) +#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x) +#define cimg_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x) +#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y) +#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y) +#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z) +#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y) +#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z) +#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z) +#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) cimg_for6XYZ(img,x,y,z) + +#define cimg_for_in6(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img)._width,x0,x1,x) +#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img)._height,y0,y1,y) +#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img)._depth,z0,z1,z) +#define cimg_for_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c) +#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Z(img,z0,z1,z) +#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in6C(img,c0,c1,c) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for7(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for7X(img,x) cimg_for7((img)._width,x) +#define cimg_for7Y(img,y) cimg_for7((img)._height,y) +#define cimg_for7Z(img,z) cimg_for7((img)._depth,z) +#define cimg_for7C(img,c) cimg_for7((img)._spectrum,c) +#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x) +#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x) +#define cimg_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x) +#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y) +#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y) +#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z) +#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y) +#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z) +#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z) +#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) cimg_for7XYZ(img,x,y,z) + +#define cimg_for_in7(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img)._width,x0,x1,x) +#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img)._height,y0,y1,y) +#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img)._depth,z0,z1,z) +#define cimg_for_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c) +#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Z(img,z0,z1,z) +#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in7C(img,c0,c1,c) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for8(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for8X(img,x) cimg_for8((img)._width,x) +#define cimg_for8Y(img,y) cimg_for8((img)._height,y) +#define cimg_for8Z(img,z) cimg_for8((img)._depth,z) +#define cimg_for8C(img,c) cimg_for8((img)._spectrum,c) +#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x) +#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x) +#define cimg_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x) +#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y) +#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y) +#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z) +#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y) +#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z) +#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z) +#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) cimg_for8XYZ(img,x,y,z) + +#define cimg_for_in8(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img)._width,x0,x1,x) +#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img)._height,y0,y1,y) +#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img)._depth,z0,z1,z) +#define cimg_for_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c) +#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Z(img,z0,z1,z) +#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in8C(img,c0,c1,c) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for9(bound,i) \ + for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(int)(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(int)(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(int)(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(int)(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for9X(img,x) cimg_for9((img)._width,x) +#define cimg_for9Y(img,y) cimg_for9((img)._height,y) +#define cimg_for9Z(img,z) cimg_for9((img)._depth,z) +#define cimg_for9C(img,c) cimg_for9((img)._spectrum,c) +#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x) +#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x) +#define cimg_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x) +#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y) +#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y) +#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z) +#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y) +#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z) +#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z) +#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) cimg_for9XYZ(img,x,y,z) + +#define cimg_for_in9(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p4##i = i - 4<0?0:i - 4, \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img)._width,x0,x1,x) +#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img)._height,y0,y1,y) +#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img)._depth,z0,z1,z) +#define cimg_for_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c) +#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Z(img,z0,z1,z) +#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[4] = (T)(img)(x,y,z,c)), \ + (I[7] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for4x4(img,x,y,z,c,I,T) \ + cimg_for4((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = I[5] = (T)(img)(0,y,z,c)), \ + (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in4((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = (T)(img)(_p1##x,y,z,c)), \ + (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[5] = (T)(img)(x,y,z,c)), \ + (I[9] = (T)(img)(x,_n1##y,z,c)), \ + (I[13] = (T)(img)(x,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for5x5(img,x,y,z,c,I,T) \ + cimg_for5((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \ + (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \ + (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in5((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[10] = (T)(img)(_p2##x,y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[11] = (T)(img)(_p1##x,y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[7] = (T)(img)(x,_p1##y,z,c)), \ + (I[12] = (T)(img)(x,y,z,c)), \ + (I[17] = (T)(img)(x,_n1##y,z,c)), \ + (I[22] = (T)(img)(x,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for6x6(img,x,y,z,c,I,T) \ + cimg_for6((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \ + (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \ + (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \ + (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in6((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p2##x,y,z,c)), \ + (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_p1##x,y,z,c)), \ + (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[8] = (T)(img)(x,_p1##y,z,c)), \ + (I[14] = (T)(img)(x,y,z,c)), \ + (I[20] = (T)(img)(x,_n1##y,z,c)), \ + (I[26] = (T)(img)(x,_n2##y,z,c)), \ + (I[32] = (T)(img)(x,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for7x7(img,x,y,z,c,I,T) \ + cimg_for7((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \ + (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \ + (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \ + (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \ + (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \ + (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in7((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[21] = (T)(img)(_p3##x,y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[22] = (T)(img)(_p2##x,y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[23] = (T)(img)(_p1##x,y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[10] = (T)(img)(x,_p2##y,z,c)), \ + (I[17] = (T)(img)(x,_p1##y,z,c)), \ + (I[24] = (T)(img)(x,y,z,c)), \ + (I[31] = (T)(img)(x,_n1##y,z,c)), \ + (I[38] = (T)(img)(x,_n2##y,z,c)), \ + (I[45] = (T)(img)(x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for8x8(img,x,y,z,c,I,T) \ + cimg_for8((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \ + (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \ + (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \ + (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \ + (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \ + (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \ + (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in8((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[24] = (T)(img)(_p3##x,y,z,c)), \ + (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_p2##x,y,z,c)), \ + (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_p1##x,y,z,c)), \ + (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[11] = (T)(img)(x,_p2##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,z,c)), \ + (I[27] = (T)(img)(x,y,z,c)), \ + (I[35] = (T)(img)(x,_n1##y,z,c)), \ + (I[43] = (T)(img)(x,_n2##y,z,c)), \ + (I[51] = (T)(img)(x,_n3##y,z,c)), \ + (I[59] = (T)(img)(x,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for9x9(img,x,y,z,c,I,T) \ + cimg_for9((img)._height,y) for (int x = 0, \ + _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \ + (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \ + (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \ + (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \ + (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \ + (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \ + (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \ + (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in9((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p4##x = x - 4<0?0:x - 4, \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \ + (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \ + (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \ + (I[36] = (T)(img)(_p4##x,y,z,c)), \ + (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \ + (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \ + (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \ + (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \ + (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[37] = (T)(img)(_p3##x,y,z,c)), \ + (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \ + (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[38] = (T)(img)(_p2##x,y,z,c)), \ + (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[39] = (T)(img)(_p1##x,y,z,c)), \ + (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[4] = (T)(img)(x,_p4##y,z,c)), \ + (I[13] = (T)(img)(x,_p3##y,z,c)), \ + (I[22] = (T)(img)(x,_p2##y,z,c)), \ + (I[31] = (T)(img)(x,_p1##y,z,c)), \ + (I[40] = (T)(img)(x,y,z,c)), \ + (I[49] = (T)(img)(x,_n1##y,z,c)), \ + (I[58] = (T)(img)(x,_n2##y,z,c)), \ + (I[67] = (T)(img)(x,_n3##y,z,c)), \ + (I[76] = (T)(img)(x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for2x2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + (I[4] = (T)(img)(0,y,_n1##z,c)), \ + (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in2((img)._depth,z0,z1,z) cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + (I[4] = (T)(img)(x,y,_n1##z,c)), \ + (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for3x3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \ + (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \ + (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \ + (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \ + (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in3((img)._depth,z0,z1,z) cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = (T)(img)(_p1##x,y,_p1##z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \ + (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,y,z,c)), \ + (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \ + (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \ + (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \ + (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \ + (I[4] = (T)(img)(x,y,_p1##z,c)), \ + (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \ + (I[10] = (T)(img)(x,_p1##y,z,c)), \ + (I[13] = (T)(img)(x,y,z,c)), \ + (I[16] = (T)(img)(x,_n1##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \ + (I[22] = (T)(img)(x,y,_n1##z,c)), \ + (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l) +#define cimglist_for_in(list,l0,l1,l) \ + for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width - 1; \ + l<=_max##l; ++l) + +#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn + +// Macros used to display error messages when exceptions are thrown. +// You should not use these macros is your own code. +#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::" +#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']' +#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::" +#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type() +#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::" +#define cimglist_instance _width,_allocated_width,_data,pixel_type() + +/*------------------------------------------------ + # + # + # Define cimg_library:: namespace + # + # + -------------------------------------------------*/ +//! Contains all classes and functions of the \CImg library. +/** + This namespace is defined to avoid functions and class names collisions + that could happen with the inclusion of other C++ header files. + Anyway, it should not happen often and you should reasonnably start most of your + \CImg-based programs with + \code + #include "CImg.h" + using namespace cimg_library; + \endcode + to simplify the declaration of \CImg Library objects afterwards. +**/ +namespace cimg_library_suffixed { + + // Declare the four classes of the CImg Library. + template struct CImg; + template struct CImgList; + struct CImgDisplay; + struct CImgException; + + // Declare cimg:: namespace. + // This is an uncomplete namespace definition here. It only contains some + // necessary stuff to ensure a correct declaration order of the classes and functions + // defined afterwards. + namespace cimg { + + // Define Ascii sequences for colored terminal output. +#ifdef cimg_use_vt100 + static const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 }; + static const char t_black[] = { 0x1b, '[', '0', ';', '3', '0', ';', '5', '9', 'm', 0 }; + static const char t_red[] = { 0x1b, '[', '0', ';', '3', '1', ';', '5', '9', 'm', 0 }; + static const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 }; + static const char t_yellow[] = { 0x1b, '[', '0', ';', '3', '3', ';', '5', '9', 'm', 0 }; + static const char t_blue[] = { 0x1b, '[', '0', ';', '3', '4', ';', '5', '9', 'm', 0 }; + static const char t_magenta[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 }; + static const char t_cyan[] = { 0x1b, '[', '0', ';', '3', '6', ';', '5', '9', 'm', 0 }; + static const char t_white[] = { 0x1b, '[', '0', ';', '3', '7', ';', '5', '9', 'm', 0 }; + static const char t_bold[] = { 0x1b, '[', '1', 'm', 0 }; + static const char t_underscore[] = { 0x1b, '[', '4', 'm', 0 }; +#else + static const char t_normal[] = { 0 }; + static const char *const t_black = cimg::t_normal, + *const t_red = cimg::t_normal, + *const t_green = cimg::t_normal, + *const t_yellow = cimg::t_normal, + *const t_blue = cimg::t_normal, + *const t_magenta = cimg::t_normal, + *const t_cyan = cimg::t_normal, + *const t_white = cimg::t_normal, + *const t_bold = cimg::t_normal, + *const t_underscore = cimg::t_normal; +#endif + + inline std::FILE* output(std::FILE *file=0); + inline void info(); + + //! Avoid warning messages due to unused parameters. Do nothing actually. + template + inline void unused(const T&, ...) {} + + // [internal] Lock/unlock a mutex for managing concurrent threads. + // 'lock_mode' can be { 0=unlock | 1=lock | 2=trylock }. + // 'n' can be in [0,31] but mutex range [0,15] is reserved by CImg. + inline int mutex(const unsigned int n, const int lock_mode=1); + + inline unsigned int& exception_mode(const unsigned int value, const bool is_set) { + static unsigned int mode = cimg_verbosity; + if (is_set) { cimg::mutex(0); mode = value<4?value:4; cimg::mutex(0,0); } + return mode; + } + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + inline FILE* _stdin(const bool throw_exception=true); + inline FILE* _stdout(const bool throw_exception=true); + inline FILE* _stderr(const bool throw_exception=true); + + // Mandatory because Microsoft's _snprintf() and _vsnprintf() do not add the '\0' character + // at the end of the string. +#if cimg_OS==2 && defined(_MSC_VER) + inline int _snprintf(char *const s, const size_t size, const char *const format, ...) { + va_list ap; + va_start(ap,format); + const int result = _vsnprintf(s,size,format,ap); + va_end(ap); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char *const format, va_list ap) { + int result = -1; + cimg::mutex(6); + if (size) result = _vsnprintf_s(s,size,_TRUNCATE,format,ap); + if (result==-1) result = _vscprintf(format,ap); + cimg::mutex(6,0); + return result; + } + + // Mutex-protected version of sscanf, sprintf and snprintf. + // Used only MacOSX, as it seems those functions are not re-entrant on MacOSX. +#elif defined(__MACOSX__) || defined(__APPLE__) + inline int _sscanf(const char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsscanf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _sprintf(char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsprintf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _snprintf(char *const s, const size_t n, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsnprintf(s,n,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char* format, va_list ap) { + cimg::mutex(6); + const int result = std::vsnprintf(s,size,format,ap); + cimg::mutex(6,0); + return result; + } +#endif + + //! Set current \CImg exception mode. + /** + The way error messages are handled by \CImg can be changed dynamically, using this function. + \param mode Desired exception mode. Possible values are: + - \c 0: Hide library messages (quiet mode). + - \c 1: Print library messages on the console. + - \c 2: Display library messages on a dialog window. + - \c 3: Do as \c 1 + add extra debug warnings (slow down the code!). + - \c 4: Do as \c 2 + add extra debug warnings (slow down the code!). + **/ + inline unsigned int& exception_mode(const unsigned int mode) { + return exception_mode(mode,true); + } + + //! Return current \CImg exception mode. + /** + \note By default, return the value of configuration macro \c cimg_verbosity + **/ + inline unsigned int& exception_mode() { + return exception_mode(0,false); + } + + 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; + } + + //! Set current \CImg openmp mode. + /** + The way openmp-based methods are handled by \CImg can be changed dynamically, using this function. + \param mode Desired openmp mode. Possible values are: + - \c 0: Never parallelize. + - \c 1: Always parallelize. + - \c 2: Adaptive parallelization mode (default behavior). + **/ + inline unsigned int openmp_mode(const unsigned int mode) { + return openmp_mode(mode,true); + } + + //! Return current \CImg openmp mode. + inline unsigned int openmp_mode() { + return openmp_mode(0,false); + } + +#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); + + // Evaluate math expression. + inline double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0); + + } + + /*--------------------------------------- + # + # Define the CImgException structures + # + --------------------------------------*/ + //! Instances of \c CImgException are thrown when errors are encountered in a \CImg function call. + /** + \par Overview + + CImgException is the base class of all exceptions thrown by \CImg (except \b CImgAbortException). + CImgException is never thrown itself. Derived classes that specify the type of errord are thrown instead. + These classes can be: + + - \b CImgAbortException: Thrown when a computationally-intensive function is aborted by an external signal. + This is the only \c non-derived exception class. + + - \b CImgArgumentException: Thrown when one argument of a called \CImg function is invalid. + This is probably one of the most thrown exception by \CImg. + For instance, the following example throws a \c CImgArgumentException: + \code + CImg img(100,100,1,3); // Define a 100x100 color image with float-valued pixels + img.mirror('e'); // Try to mirror image along the (non-existing) 'e'-axis + \endcode + + - \b CImgDisplayException: Thrown when something went wrong during the display of images in CImgDisplay instances. + + - \b CImgInstanceException: Thrown when an instance associated to a called \CImg method does not fit + the function requirements. For instance, the following example throws a \c CImgInstanceException: + \code + const CImg img; // Define an empty image + const float value = img.at(0); // Try to read first pixel value (does not exist) + \endcode + + - \b CImgIOException: Thrown when an error occured when trying to load or save image files. + This happens when trying to read files that do not exist or with invalid formats. + For instance, the following example throws a \c CImgIOException: + \code + const CImg img("missing_file.jpg"); // Try to load a file that does not exist + \endcode + + - \b CImgWarningException: Thrown only if configuration macro \c cimg_strict_warnings is set, and + when a \CImg function has to display a warning message (see cimg::warn()). + + It is not recommended to throw CImgException instances by yourself, + since they are expected to be thrown only by \CImg. + When an error occurs in a library function call, \CImg may display error messages on the screen or on the + standard output, depending on the current \CImg exception mode. + The \CImg exception mode can be get and set by functions cimg::exception_mode() and + cimg::exception_mode(unsigned int). + + \par Exceptions handling + + In all cases, when an error occurs in \CImg, an instance of the corresponding exception class is thrown. + This may lead the program to break (this is the default behavior), but you can bypass this behavior by + handling the exceptions by yourself, + using a usual try { ... } catch () { ... } bloc, as in the following example: + \code + #define "CImg.h" + using namespace cimg_library; + int main() { + cimg::exception_mode(0); // Enable quiet exception mode + try { + ... // Here, do what you want to stress CImg + } catch (CImgException& e) { // You succeeded: something went wrong! + std::fprintf(stderr,"CImg Library Error: %s",e.what()); // Display your custom error message + ... // Do what you want now to save the ship! + } + } + \endcode + **/ + struct CImgException : public std::exception { +#define _cimg_exception_err(etype,disp_flag) \ + std::va_list ap, ap2; \ + va_start(ap,format); va_start(ap2,format); \ + int size = cimg_vsnprintf(0,0,format,ap2); \ + if (size++>=0) { \ + delete[] _message; \ + _message = new char[size]; \ + cimg_vsnprintf(_message,size,format,ap); \ + if (cimg::exception_mode()) { \ + std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \ + if (cimg_display && disp_flag && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } \ + catch (CImgException&) {} \ + if (cimg::exception_mode()>=3) cimg_library_suffixed::cimg::info(); \ + } \ + } \ + va_end(ap); va_end(ap2); \ + + char *_message; + CImgException() { _message = new char[1]; *_message = 0; } + CImgException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgException",true); } + CImgException(const CImgException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgException() throw() { delete[] _message; } + CImgException& operator=(const CImgException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgAbortException class is used to throw an exception when + // a computationally-intensive function has been aborted by an external signal. + struct CImgAbortException : public std::exception { + char *_message; + CImgAbortException() { _message = new char[1]; *_message = 0; } + CImgAbortException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgAbortException",true); } + CImgAbortException(const CImgAbortException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgAbortException() throw() { delete[] _message; } + CImgAbortException& operator=(const CImgAbortException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgArgumentException class is used to throw an exception related + // to invalid arguments encountered in a library function call. + struct CImgArgumentException : public CImgException { + CImgArgumentException(const char *const format, ...) { _cimg_exception_err("CImgArgumentException",true); } + }; + + // The CImgDisplayException class is used to throw an exception related + // to display problems encountered in a library function call. + struct CImgDisplayException : public CImgException { + CImgDisplayException(const char *const format, ...) { _cimg_exception_err("CImgDisplayException",false); } + }; + + // The CImgInstanceException class is used to throw an exception related + // to an invalid instance encountered in a library function call. + struct CImgInstanceException : public CImgException { + CImgInstanceException(const char *const format, ...) { _cimg_exception_err("CImgInstanceException",true); } + }; + + // The CImgIOException class is used to throw an exception related + // to input/output file problems encountered in a library function call. + struct CImgIOException : public CImgException { + CImgIOException(const char *const format, ...) { _cimg_exception_err("CImgIOException",true); } + }; + + // The CImgWarningException class is used to throw an exception for warnings + // encountered in a library function call. + struct CImgWarningException : public CImgException { + CImgWarningException(const char *const format, ...) { _cimg_exception_err("CImgWarningException",false); } + }; + + /*------------------------------------- + # + # Define cimg:: namespace + # + -----------------------------------*/ + //! Contains \a low-level functions and variables of the \CImg Library. + /** + Most of the functions and variables within this namespace are used by the \CImg library for low-level operations. + You may use them to access specific const values or environment variables internally used by \CImg. + \warning Never write using namespace cimg_library::cimg; in your source code. Lot of functions in the + cimg:: namespace have the same names as standard C functions that may be defined in the global + namespace ::. + **/ + namespace cimg { + + // Define traits that will be used to determine the best data type to work in CImg functions. + // + template struct type { + static const char* string() { + static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24", + "unknown32", "unknown40", "unknown48", "unknown56", + "unknown64", "unknown72", "unknown80", "unknown88", + "unknown96", "unknown104", "unknown112", "unknown120", + "unknown128" }; + return s[(sizeof(T)<17)?sizeof(T):0]; + } + static bool is_float() { return false; } + static bool is_inf(const T) { return false; } + static bool is_nan(const T) { return false; } + static T min() { return ~max(); } + static T max() { return (T)1<<(8*sizeof(T) - 1); } + static T inf() { return max(); } + static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const T& val) { static const char *const s = "unknown"; cimg::unused(val); return s; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "bool"; return s; } + static bool is_float() { return false; } + static bool is_inf(const bool) { return false; } + static bool is_nan(const bool) { return false; } + static bool min() { return false; } + static bool max() { return true; } + static bool inf() { return max(); } + static bool is_inf() { return false; } + static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned char) { return false; } + static bool is_nan(const unsigned char) { return false; } + static unsigned char min() { return 0; } + static unsigned char max() { return (unsigned char)-1; } + static unsigned char inf() { return max(); } + static unsigned char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned char val) { return (unsigned int)val; } + }; + +#if defined(CHAR_MAX) && CHAR_MAX==255 + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return 0; } + static char max() { return (char)-1; } + static char inf() { return max(); } + static char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const char val) { return (unsigned int)val; } + }; +#else + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return ~max(); } + static char max() { return (char)((unsigned char)-1>>1); } + static char inf() { return max(); } + static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const char val) { return (int)val; } + }; +#endif + + template<> struct type { + static const char* string() { static const char *const s = "signed char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const signed char) { return false; } + static bool is_nan(const signed char) { return false; } + static signed char min() { return ~max(); } + static signed char max() { return (signed char)((unsigned char)-1>>1); } + static signed char inf() { return max(); } + static signed char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(signed char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const signed char val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned short) { return false; } + static bool is_nan(const unsigned short) { return false; } + static unsigned short min() { return 0; } + static unsigned short max() { return (unsigned short)-1; } + static unsigned short inf() { return max(); } + static unsigned short cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned short val) { return (unsigned int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const short) { return false; } + static bool is_nan(const short) { return false; } + static short min() { return ~max(); } + static short max() { return (short)((unsigned short)-1>>1); } + static short inf() { return max(); } + static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const short val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned int) { return false; } + static bool is_nan(const unsigned int) { return false; } + static unsigned int min() { return 0; } + static unsigned int max() { return (unsigned int)-1; } + static unsigned int inf() { return max(); } + static unsigned int cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const int) { return false; } + static bool is_nan(const int) { return false; } + static int min() { return ~max(); } + static int max() { return (int)((unsigned int)-1>>1); } + static int inf() { return max(); } + static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_uint64) { return false; } + static bool is_nan(const cimg_uint64) { return false; } + static cimg_uint64 min() { return 0; } + static cimg_uint64 max() { return (cimg_uint64)-1; } + static cimg_uint64 inf() { return max(); } + static cimg_uint64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; } + static const char* format() { return cimg_fuint64; } + static const char* format_s() { return cimg_fuint64; } + static unsigned long format(const cimg_uint64 val) { return (unsigned long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_int64) { return false; } + static bool is_nan(const cimg_int64) { return false; } + static cimg_int64 min() { return ~max(); } + static cimg_int64 max() { return (cimg_int64)((cimg_uint64)-1>>1); } + static cimg_int64 inf() { return max(); } + static cimg_int64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; + } + static const char* format() { return cimg_fint64; } + static const char* format_s() { return cimg_fint64; } + static long format(const long val) { return (long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const double val) { // Custom version that works with '-ffast-math' + if (sizeof(double)==8) { + cimg_uint64 u; + std::memcpy(&u,&val,sizeof(double)); + return ((unsigned int)(u>>32)&0x7fffffff) + ((unsigned int)u!=0)>0x7ff00000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static double min() { return -DBL_MAX; } + static double max() { return DBL_MAX; } + static double inf() { +#ifdef INFINITY + return (double)INFINITY; +#else + return max()*max(); +#endif + } + static double nan() { +#ifdef NAN + return (double)NAN; +#else + const double val_nan = -std::sqrt(-1.); return val_nan; +#endif + } + static double cut(const double val) { return val; } + static const char* format() { return "%.17g"; } + static const char* format_s() { return "%g"; } + static double format(const double val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "float"; return s; } + static bool is_float() { return true; } + static bool is_inf(const float val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const float val) { // Custom version that works with '-ffast-math' + if (sizeof(float)==4) { + unsigned int u; + std::memcpy(&u,&val,sizeof(float)); + return (u&0x7fffffff)>0x7f800000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static float min() { return -FLT_MAX; } + static float max() { return FLT_MAX; } + static float inf() { return (float)cimg::type::inf(); } + static float nan() { return (float)cimg::type::nan(); } + static float cut(const double val) { return (float)val; } + static float cut(const float val) { return (float)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const float val) { return (double)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "long double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const long double val) { +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static long double min() { return -LDBL_MAX; } + static long double max() { return LDBL_MAX; } + static long double inf() { return max()*max(); } + static long double nan() { const long double val_nan = -std::sqrt(-1.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"; } + static double format(const long double val) { return (double)val; } + }; + +#ifdef cimg_use_half + template<> struct type { + static const char* string() { static const char *const s = "half"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const half val) { // Custom version that works with '-ffast-math' + if (sizeof(half)==2) { + short u; + std::memcpy(&u,&val,sizeof(short)); + return (bool)((u&0x7fff)>0x7c00); + } + return cimg::type::is_nan((float)val); + } + 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.); return val_nan; } + static half cut(const double val) { return (half)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const half val) { return (double)val; } + }; +#endif + + template struct superset { typedef T type; }; + template<> struct superset { typedef unsigned char type; }; + template<> struct superset { typedef char type; }; + template<> struct superset { typedef signed char type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 float type; }; + template<> struct superset { typedef double 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 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; }; + template<> struct superset { typedef double type; }; +#ifdef cimg_use_half + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; +#endif + + template struct superset2 { + typedef typename superset::type>::type type; + }; + + template struct superset3 { + typedef typename superset::type>::type type; + }; + + template struct last { typedef t2 type; }; + +#define _cimg_Tt 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 { + unsigned int nb_wins; + pthread_t *events_thread; + pthread_cond_t wait_event; + pthread_mutex_t wait_event_mutex; + CImgDisplay **wins; + Display *display; + unsigned int nb_bits; + bool is_blue_first; + bool is_shm_enabled; + bool byte_order; +#ifdef cimg_use_xrandr + XRRScreenSize *resolutions; + Rotation curr_rotation; + unsigned int curr_resolution; + unsigned int nb_resolutions; +#endif + X11_info():nb_wins(0),events_thread(0),display(0), + nb_bits(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) { +#ifdef __FreeBSD__ + XInitThreads(); +#endif + wins = new CImgDisplay*[1024]; + pthread_mutex_init(&wait_event_mutex,0); + pthread_cond_init(&wait_event,0); +#ifdef cimg_use_xrandr + resolutions = 0; + curr_rotation = 0; + curr_resolution = nb_resolutions = 0; +#endif + } + + ~X11_info() { + delete[] wins; + /* + if (events_thread) { + pthread_cancel(*events_thread); + delete events_thread; + } + if (display) { } // XCloseDisplay(display); } + pthread_cond_destroy(&wait_event); + pthread_mutex_unlock(&wait_event_mutex); + pthread_mutex_destroy(&wait_event_mutex); + */ + } + }; +#if defined(cimg_module) + X11_info& X11_attr(); +#elif defined(cimg_main) + X11_info& X11_attr() { static X11_info val; return val; } +#else + inline X11_info& X11_attr() { static X11_info val; return val; } +#endif + +#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 +#define cimg_lock_display() cimg::mutex(15) +#define cimg_unlock_display() cimg::mutex(15,0) + + struct Mutex_info { +#ifdef _PTHREAD_H + pthread_mutex_t mutex[32]; + Mutex_info() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); } + void lock(const unsigned int n) { pthread_mutex_lock(&mutex[n]); } + void unlock(const unsigned int n) { pthread_mutex_unlock(&mutex[n]); } + int trylock(const unsigned int n) { return pthread_mutex_trylock(&mutex[n]); } +#elif 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; } +#else + Mutex_info() {} + void lock(const unsigned int) {} + void unlock(const unsigned int) {} + int trylock(const unsigned int) { return 0; } +#endif + }; +#if defined(cimg_module) + Mutex_info& Mutex_attr(); +#elif defined(cimg_main) + Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#else + inline Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#endif + +#if defined(cimg_use_magick) + static struct Magick_info { + Magick_info() { + Magick::InitializeMagick(""); + } + } _Magick_info; +#endif + +#if cimg_display==1 + // Define keycodes for X11-based graphical systems. + const unsigned int keyESC = XK_Escape; + const unsigned int keyF1 = XK_F1; + const unsigned int keyF2 = XK_F2; + const unsigned int keyF3 = XK_F3; + const unsigned int keyF4 = XK_F4; + const unsigned int keyF5 = XK_F5; + const unsigned int keyF6 = XK_F6; + const unsigned int keyF7 = XK_F7; + const unsigned int keyF8 = XK_F8; + const unsigned int keyF9 = XK_F9; + const unsigned int keyF10 = XK_F10; + const unsigned int keyF11 = XK_F11; + const unsigned int keyF12 = XK_F12; + const unsigned int keyPAUSE = XK_Pause; + const unsigned int key1 = XK_1; + const unsigned int key2 = XK_2; + const unsigned int key3 = XK_3; + const unsigned int key4 = XK_4; + const unsigned int key5 = XK_5; + const unsigned int key6 = XK_6; + const unsigned int key7 = XK_7; + const unsigned int key8 = XK_8; + const unsigned int key9 = XK_9; + const unsigned int key0 = XK_0; + const unsigned int keyBACKSPACE = XK_BackSpace; + const unsigned int keyINSERT = XK_Insert; + const unsigned int keyHOME = XK_Home; + const unsigned int keyPAGEUP = XK_Page_Up; + const unsigned int keyTAB = XK_Tab; + const unsigned int keyQ = XK_q; + const unsigned int keyW = XK_w; + const unsigned int keyE = XK_e; + const unsigned int keyR = XK_r; + const unsigned int keyT = XK_t; + const unsigned int keyY = XK_y; + const unsigned int keyU = XK_u; + const unsigned int keyI = XK_i; + const unsigned int keyO = XK_o; + const unsigned int keyP = XK_p; + const unsigned int keyDELETE = XK_Delete; + const unsigned int keyEND = XK_End; + const unsigned int keyPAGEDOWN = XK_Page_Down; + const unsigned int keyCAPSLOCK = XK_Caps_Lock; + const unsigned int keyA = XK_a; + const unsigned int keyS = XK_s; + const unsigned int keyD = XK_d; + const unsigned int keyF = XK_f; + const unsigned int keyG = XK_g; + const unsigned int keyH = XK_h; + const unsigned int keyJ = XK_j; + const unsigned int keyK = XK_k; + const unsigned int keyL = XK_l; + const unsigned int keyENTER = XK_Return; + const unsigned int keySHIFTLEFT = XK_Shift_L; + const unsigned int keyZ = XK_z; + const unsigned int keyX = XK_x; + const unsigned int keyC = XK_c; + const unsigned int keyV = XK_v; + const unsigned int keyB = XK_b; + const unsigned int keyN = XK_n; + const unsigned int keyM = XK_m; + const unsigned int keySHIFTRIGHT = XK_Shift_R; + const unsigned int keyARROWUP = XK_Up; + const unsigned int keyCTRLLEFT = XK_Control_L; + const unsigned int keyAPPLEFT = XK_Super_L; + const unsigned int keyALT = XK_Alt_L; + const unsigned int keySPACE = XK_space; + const unsigned int keyALTGR = XK_Alt_R; + const unsigned int keyAPPRIGHT = XK_Super_R; + const unsigned int keyMENU = XK_Menu; + const unsigned int keyCTRLRIGHT = XK_Control_R; + const unsigned int keyARROWLEFT = XK_Left; + const unsigned int keyARROWDOWN = XK_Down; + const unsigned int keyARROWRIGHT = XK_Right; + const unsigned int keyPAD0 = XK_KP_0; + const unsigned int keyPAD1 = XK_KP_1; + const unsigned int keyPAD2 = XK_KP_2; + const unsigned int keyPAD3 = XK_KP_3; + const unsigned int keyPAD4 = XK_KP_4; + const unsigned int keyPAD5 = XK_KP_5; + const unsigned int keyPAD6 = XK_KP_6; + const unsigned int keyPAD7 = XK_KP_7; + const unsigned int keyPAD8 = XK_KP_8; + const unsigned int keyPAD9 = XK_KP_9; + const unsigned int keyPADADD = XK_KP_Add; + const unsigned int keyPADSUB = XK_KP_Subtract; + const unsigned int keyPADMUL = XK_KP_Multiply; + const unsigned int keyPADDIV = XK_KP_Divide; + +#elif cimg_display==2 + // Define keycodes for Windows. + const unsigned int keyESC = VK_ESCAPE; + const unsigned int keyF1 = VK_F1; + const unsigned int keyF2 = VK_F2; + const unsigned int keyF3 = VK_F3; + const unsigned int keyF4 = VK_F4; + const unsigned int keyF5 = VK_F5; + const unsigned int keyF6 = VK_F6; + const unsigned int keyF7 = VK_F7; + const unsigned int keyF8 = VK_F8; + const unsigned int keyF9 = VK_F9; + const unsigned int keyF10 = VK_F10; + const unsigned int keyF11 = VK_F11; + const unsigned int keyF12 = VK_F12; + const unsigned int keyPAUSE = VK_PAUSE; + const unsigned int key1 = '1'; + const unsigned int key2 = '2'; + const unsigned int key3 = '3'; + const unsigned int key4 = '4'; + const unsigned int key5 = '5'; + const unsigned int key6 = '6'; + const unsigned int key7 = '7'; + const unsigned int key8 = '8'; + const unsigned int key9 = '9'; + const unsigned int key0 = '0'; + const unsigned int keyBACKSPACE = VK_BACK; + const unsigned int keyINSERT = VK_INSERT; + const unsigned int keyHOME = VK_HOME; + const unsigned int keyPAGEUP = VK_PRIOR; + const unsigned int keyTAB = VK_TAB; + const unsigned int keyQ = 'Q'; + const unsigned int keyW = 'W'; + const unsigned int keyE = 'E'; + const unsigned int keyR = 'R'; + const unsigned int keyT = 'T'; + const unsigned int keyY = 'Y'; + const unsigned int keyU = 'U'; + const unsigned int keyI = 'I'; + const unsigned int keyO = 'O'; + const unsigned int keyP = 'P'; + const unsigned int keyDELETE = VK_DELETE; + const unsigned int keyEND = VK_END; + const unsigned int keyPAGEDOWN = VK_NEXT; + const unsigned int keyCAPSLOCK = VK_CAPITAL; + const unsigned int keyA = 'A'; + const unsigned int keyS = 'S'; + const unsigned int keyD = 'D'; + const unsigned int keyF = 'F'; + const unsigned int keyG = 'G'; + const unsigned int keyH = 'H'; + const unsigned int keyJ = 'J'; + const unsigned int keyK = 'K'; + const unsigned int keyL = 'L'; + const unsigned int keyENTER = VK_RETURN; + const unsigned int keySHIFTLEFT = VK_SHIFT; + const unsigned int keyZ = 'Z'; + const unsigned int keyX = 'X'; + const unsigned int keyC = 'C'; + const unsigned int keyV = 'V'; + const unsigned int keyB = 'B'; + const unsigned int keyN = 'N'; + const unsigned int keyM = 'M'; + const unsigned int keySHIFTRIGHT = VK_SHIFT; + const unsigned int keyARROWUP = VK_UP; + const unsigned int keyCTRLLEFT = VK_CONTROL; + const unsigned int keyAPPLEFT = VK_LWIN; + const unsigned int keyALT = VK_LMENU; + const unsigned int keySPACE = VK_SPACE; + const unsigned int keyALTGR = VK_CONTROL; + const unsigned int keyAPPRIGHT = VK_RWIN; + const unsigned int keyMENU = VK_APPS; + const unsigned int keyCTRLRIGHT = VK_CONTROL; + const unsigned int keyARROWLEFT = VK_LEFT; + const unsigned int keyARROWDOWN = VK_DOWN; + const unsigned int keyARROWRIGHT = VK_RIGHT; + const unsigned int keyPAD0 = 0x60; + const unsigned int keyPAD1 = 0x61; + const unsigned int keyPAD2 = 0x62; + const unsigned int keyPAD3 = 0x63; + const unsigned int keyPAD4 = 0x64; + const unsigned int keyPAD5 = 0x65; + const unsigned int keyPAD6 = 0x66; + const unsigned int keyPAD7 = 0x67; + const unsigned int keyPAD8 = 0x68; + const unsigned int keyPAD9 = 0x69; + const unsigned int keyPADADD = VK_ADD; + const unsigned int keyPADSUB = VK_SUBTRACT; + const unsigned int keyPADMUL = VK_MULTIPLY; + const unsigned int keyPADDIV = VK_DIVIDE; + +#else + // Define random keycodes when no display is available. + // (should rarely be used then!). + const unsigned int keyESC = 1U; //!< Keycode for the \c ESC key (architecture-dependent) + const unsigned int keyF1 = 2U; //!< Keycode for the \c F1 key (architecture-dependent) + const unsigned int keyF2 = 3U; //!< Keycode for the \c F2 key (architecture-dependent) + const unsigned int keyF3 = 4U; //!< Keycode for the \c F3 key (architecture-dependent) + const unsigned int keyF4 = 5U; //!< Keycode for the \c F4 key (architecture-dependent) + const unsigned int keyF5 = 6U; //!< Keycode for the \c F5 key (architecture-dependent) + const unsigned int keyF6 = 7U; //!< Keycode for the \c F6 key (architecture-dependent) + const unsigned int keyF7 = 8U; //!< Keycode for the \c F7 key (architecture-dependent) + const unsigned int keyF8 = 9U; //!< Keycode for the \c F8 key (architecture-dependent) + const unsigned int keyF9 = 10U; //!< Keycode for the \c F9 key (architecture-dependent) + const unsigned int keyF10 = 11U; //!< Keycode for the \c F10 key (architecture-dependent) + const unsigned int keyF11 = 12U; //!< Keycode for the \c F11 key (architecture-dependent) + const unsigned int keyF12 = 13U; //!< Keycode for the \c F12 key (architecture-dependent) + const unsigned int keyPAUSE = 14U; //!< Keycode for the \c PAUSE key (architecture-dependent) + const unsigned int key1 = 15U; //!< Keycode for the \c 1 key (architecture-dependent) + const unsigned int key2 = 16U; //!< Keycode for the \c 2 key (architecture-dependent) + const unsigned int key3 = 17U; //!< Keycode for the \c 3 key (architecture-dependent) + const unsigned int key4 = 18U; //!< Keycode for the \c 4 key (architecture-dependent) + const unsigned int key5 = 19U; //!< Keycode for the \c 5 key (architecture-dependent) + const unsigned int key6 = 20U; //!< Keycode for the \c 6 key (architecture-dependent) + const unsigned int key7 = 21U; //!< Keycode for the \c 7 key (architecture-dependent) + const unsigned int key8 = 22U; //!< Keycode for the \c 8 key (architecture-dependent) + const unsigned int key9 = 23U; //!< Keycode for the \c 9 key (architecture-dependent) + const unsigned int key0 = 24U; //!< Keycode for the \c 0 key (architecture-dependent) + const unsigned int keyBACKSPACE = 25U; //!< Keycode for the \c BACKSPACE key (architecture-dependent) + const unsigned int keyINSERT = 26U; //!< Keycode for the \c INSERT key (architecture-dependent) + const unsigned int keyHOME = 27U; //!< Keycode for the \c HOME key (architecture-dependent) + const unsigned int keyPAGEUP = 28U; //!< Keycode for the \c PAGEUP key (architecture-dependent) + const unsigned int keyTAB = 29U; //!< Keycode for the \c TAB key (architecture-dependent) + const unsigned int keyQ = 30U; //!< Keycode for the \c Q key (architecture-dependent) + const unsigned int keyW = 31U; //!< Keycode for the \c W key (architecture-dependent) + const unsigned int keyE = 32U; //!< Keycode for the \c E key (architecture-dependent) + const unsigned int keyR = 33U; //!< Keycode for the \c R key (architecture-dependent) + const unsigned int keyT = 34U; //!< Keycode for the \c T key (architecture-dependent) + const unsigned int keyY = 35U; //!< Keycode for the \c Y key (architecture-dependent) + const unsigned int keyU = 36U; //!< Keycode for the \c U key (architecture-dependent) + const unsigned int keyI = 37U; //!< Keycode for the \c I key (architecture-dependent) + const unsigned int keyO = 38U; //!< Keycode for the \c O key (architecture-dependent) + const unsigned int keyP = 39U; //!< Keycode for the \c P key (architecture-dependent) + const unsigned int keyDELETE = 40U; //!< Keycode for the \c DELETE key (architecture-dependent) + const unsigned int keyEND = 41U; //!< Keycode for the \c END key (architecture-dependent) + const unsigned int keyPAGEDOWN = 42U; //!< Keycode for the \c PAGEDOWN key (architecture-dependent) + const unsigned int keyCAPSLOCK = 43U; //!< Keycode for the \c CAPSLOCK key (architecture-dependent) + const unsigned int keyA = 44U; //!< Keycode for the \c A key (architecture-dependent) + const unsigned int keyS = 45U; //!< Keycode for the \c S key (architecture-dependent) + const unsigned int keyD = 46U; //!< Keycode for the \c D key (architecture-dependent) + const unsigned int keyF = 47U; //!< Keycode for the \c F key (architecture-dependent) + const unsigned int keyG = 48U; //!< Keycode for the \c G key (architecture-dependent) + const unsigned int keyH = 49U; //!< Keycode for the \c H key (architecture-dependent) + const unsigned int keyJ = 50U; //!< Keycode for the \c J key (architecture-dependent) + const unsigned int keyK = 51U; //!< Keycode for the \c K key (architecture-dependent) + const unsigned int keyL = 52U; //!< Keycode for the \c L key (architecture-dependent) + const unsigned int keyENTER = 53U; //!< Keycode for the \c ENTER key (architecture-dependent) + const unsigned int keySHIFTLEFT = 54U; //!< Keycode for the \c SHIFTLEFT key (architecture-dependent) + const unsigned int keyZ = 55U; //!< Keycode for the \c Z key (architecture-dependent) + const unsigned int keyX = 56U; //!< Keycode for the \c X key (architecture-dependent) + const unsigned int keyC = 57U; //!< Keycode for the \c C key (architecture-dependent) + const unsigned int keyV = 58U; //!< Keycode for the \c V key (architecture-dependent) + const unsigned int keyB = 59U; //!< Keycode for the \c B key (architecture-dependent) + const unsigned int keyN = 60U; //!< Keycode for the \c N key (architecture-dependent) + const unsigned int keyM = 61U; //!< Keycode for the \c M key (architecture-dependent) + const unsigned int keySHIFTRIGHT = 62U; //!< Keycode for the \c SHIFTRIGHT key (architecture-dependent) + const unsigned int keyARROWUP = 63U; //!< Keycode for the \c ARROWUP key (architecture-dependent) + const unsigned int keyCTRLLEFT = 64U; //!< Keycode for the \c CTRLLEFT key (architecture-dependent) + const unsigned int keyAPPLEFT = 65U; //!< Keycode for the \c APPLEFT key (architecture-dependent) + const unsigned int keyALT = 66U; //!< Keycode for the \c ALT key (architecture-dependent) + const unsigned int keySPACE = 67U; //!< Keycode for the \c SPACE key (architecture-dependent) + const unsigned int keyALTGR = 68U; //!< Keycode for the \c ALTGR key (architecture-dependent) + const unsigned int keyAPPRIGHT = 69U; //!< Keycode for the \c APPRIGHT key (architecture-dependent) + const unsigned int keyMENU = 70U; //!< Keycode for the \c MENU key (architecture-dependent) + const unsigned int keyCTRLRIGHT = 71U; //!< Keycode for the \c CTRLRIGHT key (architecture-dependent) + const unsigned int keyARROWLEFT = 72U; //!< Keycode for the \c ARROWLEFT key (architecture-dependent) + const unsigned int keyARROWDOWN = 73U; //!< Keycode for the \c ARROWDOWN key (architecture-dependent) + const unsigned int keyARROWRIGHT = 74U; //!< Keycode for the \c ARROWRIGHT key (architecture-dependent) + const unsigned int keyPAD0 = 75U; //!< Keycode for the \c PAD0 key (architecture-dependent) + const unsigned int keyPAD1 = 76U; //!< Keycode for the \c PAD1 key (architecture-dependent) + const unsigned int keyPAD2 = 77U; //!< Keycode for the \c PAD2 key (architecture-dependent) + const unsigned int keyPAD3 = 78U; //!< Keycode for the \c PAD3 key (architecture-dependent) + const unsigned int keyPAD4 = 79U; //!< Keycode for the \c PAD4 key (architecture-dependent) + const unsigned int keyPAD5 = 80U; //!< Keycode for the \c PAD5 key (architecture-dependent) + const unsigned int keyPAD6 = 81U; //!< Keycode for the \c PAD6 key (architecture-dependent) + const unsigned int keyPAD7 = 82U; //!< Keycode for the \c PAD7 key (architecture-dependent) + const unsigned int keyPAD8 = 83U; //!< Keycode for the \c PAD8 key (architecture-dependent) + const unsigned int keyPAD9 = 84U; //!< Keycode for the \c PAD9 key (architecture-dependent) + const unsigned int keyPADADD = 85U; //!< Keycode for the \c PADADD key (architecture-dependent) + const unsigned int keyPADSUB = 86U; //!< Keycode for the \c PADSUB key (architecture-dependent) + const unsigned int keyPADMUL = 87U; //!< Keycode for the \c PADMUL key (architecture-dependent) + const unsigned int keyPADDIV = 88U; //!< Keycode for the \c PADDDIV key (architecture-dependent) +#endif + + const double PI = 3.14159265358979323846; //!< Value of the mathematical constant PI + + // Define a 10x13 binary font (small sans). + static const char *const data_font_small[] = { + " UwlwnwoyuwHwlwmwcwlwnw[xuwowlwmwoyuwRwlwnxcw Mw (wnwnwuwpwuypwuwoy" + "ZwnwmwuwowuwmwnwnwuwowuwfwuxnwnwmwuwpwuypwuwZwnwnwtwpwtwow'y Hw cwnw >{ 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 " + "rwswewnwuwowuwozswtwuwqwtwmwnwlwowuwuwowOxpxuxqwuwowswqwswoxpwlwjwqwswqwswy~}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 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

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

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 std::printf(). + \note If configuration macro \c cimg_strict_warnings is set, this function throws a + \c CImgWarningException instead. + \warning As the first argument is a format string, it is highly recommended to write + \code + cimg::warn("%s",warning_message); + \endcode + instead of + \code + cimg::warn(warning_message); + \endcode + if \c warning_message can be arbitrary, to prevent nasty memory access. + **/ + inline void warn(const char *const format, ...) { + if (cimg::exception_mode()>=1) { + char *const message = new char[16384]; + std::va_list ap; + va_start(ap,format); + cimg_vsnprintf(message,16384,format,ap); + va_end(ap); +#ifdef cimg_strict_warnings + throw CImgWarningException(message); +#else + std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s\n",cimg::t_red,cimg::t_normal,message); +#endif + delete[] message; + } + } + + // Execute an external system command. + /** + \param command C-string containing the command line to execute. + \param module_name Module name. + \return Status value of the executed command, whose meaning is OS-dependent. + \note This function is similar to std::system() + but it does not open an extra console windows + on Windows-based systems. + **/ + inline int system(const char *const command, const char *const module_name=0, const bool is_verbose=false) { + cimg::unused(module_name); +#ifdef cimg_no_system_calls + return -1; +#else + if (is_verbose) return std::system(command); +#if cimg_OS==1 + const unsigned int l = (unsigned int)std::strlen(command); + if (l) { + char *const ncommand = new char[l + 24]; + std::memcpy(ncommand,command,l); + 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; + std::memset(&pi,0,sizeof(PROCESS_INFORMATION)); + std::memset(&si,0,sizeof(STARTUPINFO)); + GetStartupInfo(&si); + si.cb = sizeof(si); + si.wShowWindow = SW_HIDE; + si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW; + const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi); + if (res) { + WaitForSingleObject(pi.hProcess,INFINITE); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; + } else return std::system(command); +#else + return std::system(command); +#endif +#endif + } + + //! Return a reference to a temporary variable of type T. + template + inline T& temporary(const T&) { + static T temp; + return temp; + } + + //! Exchange values of variables \c a and \c b. + template + inline void swap(T& a, T& b) { T t = a; a = b; b = t; } + + //! Exchange values of variables (\c a1,\c a2) and (\c b1,\c b2). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) { + cimg::swap(a1,b1); cimg::swap(a2,b2); + } + + //! Exchange values of variables (\c a1,\c a2,\c a3) and (\c b1,\c b2,\c b3). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) { + cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a4) and (\c b1,\c b2,...,\c b4). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) { + cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a5) and (\c b1,\c b2,...,\c b5). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a6) and (\c b1,\c b2,...,\c b6). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a7) and (\c b1,\c b2,...,\c b7). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a8) and (\c b1,\c b2,...,\c b8). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7, T8& a8, T8& b8) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8); + } + + //! Return the endianness of the current architecture. + /** + \return \c false for Little Endian or \c true for Big Endian. + **/ + inline bool endianness() { + const int x = 1; + return ((unsigned char*)&x)[0]?false:true; + } + + //! Reverse endianness of all elements in a memory buffer. + /** + \param[in,out] buffer Memory buffer whose endianness must be reversed. + \param size Number of buffer elements to reverse. + **/ + template + inline void invert_endianness(T* const buffer, const cimg_ulong size) { + if (size) switch (sizeof(T)) { + case 1 : break; + case 2 : { + for (unsigned short *ptr = (unsigned short*)buffer + size; ptr>(unsigned short*)buffer; ) { + const unsigned short val = *(--ptr); + *ptr = (unsigned short)((val>>8) | ((val<<8))); + } + } break; + case 4 : { + for (unsigned int *ptr = (unsigned int*)buffer + size; ptr>(unsigned int*)buffer; ) { + const unsigned int val = *(--ptr); + *ptr = (val>>24) | ((val>>8)&0xff00) | ((val<<8)&0xff0000) | (val<<24); + } + } break; + case 8 : { + const cimg_uint64 + m0 = (cimg_uint64)0xff, m1 = m0<<8, m2 = m0<<16, m3 = m0<<24, + m4 = m0<<32, m5 = m0<<40, m6 = m0<<48, m7 = m0<<56; + for (cimg_uint64 *ptr = (cimg_uint64*)buffer + size; ptr>(cimg_uint64*)buffer; ) { + const cimg_uint64 val = *(--ptr); + *ptr = (((val&m7)>>56) | ((val&m6)>>40) | ((val&m5)>>24) | ((val&m4)>>8) | + ((val&m3)<<8) |((val&m2)<<24) | ((val&m1)<<40) | ((val&m0)<<56)); + } + } break; + default : { + for (T* ptr = buffer + size; ptr>buffer; ) { + unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T); + for (int i = 0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe)); + } + } + } + } + + //! Reverse endianness of a single variable. + /** + \param[in,out] a Variable to reverse. + \return Reference to reversed variable. + **/ + template + inline T& invert_endianness(T& a) { + invert_endianness(&a,1); + return a; + } + + // Conversion functions to get more precision when trying to store unsigned ints values as floats. + inline unsigned int float2uint(const float f) { + int tmp = 0; + std::memcpy(&tmp,&f,sizeof(float)); + if (tmp>=0) return (unsigned int)f; + unsigned int u; + // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&u,&f,sizeof(float)); + return ((u)<<1)>>1; // set sign bit to 0 + } + + inline float uint2float(const unsigned int u) { + if (u<(1U<<19)) return (float)u; // Consider safe storage of unsigned int as floats until 19bits (i.e 524287) + float f; + const unsigned int v = u|(1U<<(8*sizeof(unsigned int)-1)); // set sign bit to 1 + // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&f,&v,sizeof(float)); + return f; + } + + //! Return the value of a system timer, with a millisecond precision. + /** + \note The timer does not necessarily starts from \c 0. + **/ + inline cimg_ulong time() { +#if cimg_OS==1 + struct timeval st_time; + gettimeofday(&st_time,0); + return (cimg_ulong)(st_time.tv_usec/1000 + st_time.tv_sec*1000); +#elif cimg_OS==2 + SYSTEMTIME st_time; + GetLocalTime(&st_time); + return (cimg_ulong)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour))); +#else + return 0; +#endif + } + + // Implement a tic/toc mechanism to display elapsed time of algorithms. + inline cimg_ulong tictoc(const bool is_tic); + + //! Start tic/toc timer for time measurement between code instructions. + /** + \return Current value of the timer (same value as time()). + **/ + inline cimg_ulong tic() { + return cimg::tictoc(true); + } + + //! End tic/toc timer and displays elapsed time from last call to tic(). + /** + \return Time elapsed (in ms) since last call to tic(). + **/ + inline cimg_ulong toc() { + return cimg::tictoc(false); + } + + //! Sleep for a given numbers of milliseconds. + /** + \param milliseconds Number of milliseconds to wait for. + \note This function frees the CPU ressources during the sleeping time. + It can be used to temporize your program properly, without wasting CPU time. + **/ + inline void sleep(const unsigned int milliseconds) { +#if cimg_OS==1 + struct timespec tv; + tv.tv_sec = milliseconds/1000; + tv.tv_nsec = (milliseconds%1000)*1000000; + nanosleep(&tv,0); +#elif cimg_OS==2 + Sleep(milliseconds); +#else + cimg::unused(milliseconds); +#endif + } + + inline unsigned int wait(const unsigned int milliseconds, cimg_ulong *const p_timer) { + if (!*p_timer) *p_timer = cimg::time(); + const cimg_ulong current_time = cimg::time(); + if (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; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \return Number of milliseconds elapsed since the last call to wait(). + \note Same as sleep() with a waiting time computed with regard to the last call + of wait(). It may be used to temporize your program properly, without wasting CPU time. + **/ + inline cimg_long wait(const unsigned int milliseconds) { + cimg::mutex(3); + static cimg_ulong timer = cimg::time(); + cimg::mutex(3,0); + return cimg::wait(milliseconds,&timer); + } + + // Custom random number generator (allow re-entrance). + inline cimg_ulong& rng() { // Used as a shared global number for rng + static cimg_ulong rng = 0xB16B00B5U; + return rng; + } + + inline unsigned int _rand(cimg_ulong *const p_rng) { + *p_rng = *p_rng*1103515245 + 12345U; + return (unsigned int)*p_rng; + } + + inline unsigned int _rand() { + cimg::mutex(4); + const unsigned int res = cimg::_rand(&cimg::rng()); + cimg::mutex(4,0); + return res; + } + + inline void srand(cimg_ulong *const p_rng) { +#if cimg_OS==1 + *p_rng = cimg::time() + (cimg_ulong)getpid(); +#elif cimg_OS==2 + *p_rng = cimg::time() + (cimg_ulong)_getpid(); +#endif + } + + inline void srand() { + cimg::mutex(4); + cimg::srand(&cimg::rng()); + cimg::mutex(4,0); + } + + inline void srand(const cimg_ulong seed) { + cimg::mutex(4); + cimg::rng() = seed; + cimg::mutex(4,0); + } + + inline double rand(const double val_min, const double val_max, cimg_ulong *const p_rng) { + const double val = cimg::_rand(p_rng)/(double)~0U; + return val_min + (val_max - val_min)*val; + } + + 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; + } + + inline double rand(const double val_max, cimg_ulong *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_ulong *const p_rng) { + double x1, w; + do { + 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.); + return x1*std::sqrt((-2*std::log(w))/w); + } + + 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_ulong *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.; 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; + } + + //! Bitwise-rotate value on the left. + template + inline T rol(const T& a, const unsigned int n=1) { + return n?(T)((a<>((sizeof(T)<<3) - n))):a; + } + + inline float rol(const float a, const unsigned int n=1) { + return (float)rol((int)a,n); + } + + inline double rol(const double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + + inline double rol(const long double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half rol(const half a, const unsigned int n=1) { + return (half)rol((int)a,n); + } +#endif + + //! Bitwise-rotate value on the right. + template + inline T ror(const T& a, const unsigned int n=1) { + return n?(T)((a>>n)|(a<<((sizeof(T)<<3) - n))):a; + } + + inline float ror(const float a, const unsigned int n=1) { + return (float)ror((int)a,n); + } + + inline double ror(const double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + + inline double ror(const long double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half ror(const half a, const unsigned int n=1) { + return (half)ror((int)a,n); + } +#endif + + //! Return absolute value of a value. + template + inline T abs(const T& a) { + return a>=0?a:-a; + } + inline bool abs(const bool a) { + return a; + } + inline int abs(const unsigned char a) { + return (int)a; + } + inline int abs(const unsigned short a) { + return (int)a; + } + inline int abs(const unsigned int a) { + return (int)a; + } + inline int abs(const int a) { + return std::abs(a); + } + inline cimg_int64 abs(const cimg_uint64 a) { + return (cimg_int64)a; + } + inline double abs(const double a) { + return std::fabs(a); + } + inline float abs(const float a) { + return (float)std::fabs((double)a); + } + + //! Return hyperbolic arcosine of a value. + inline double acosh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::acosh(x); +#else + return std::log(x + std::sqrt(x*x - 1)); +#endif + } + + //! Return hyperbolic arcsine of a value. + inline double asinh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::asinh(x); +#else + return std::log(x + std::sqrt(x*x + 1)); +#endif + } + + //! Return hyperbolic arctangent of a value. + inline double atanh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::atanh(x); +#else + return 0.5*std::log((1. + x)/(1. - x)); +#endif + } + + //! Return the sinc of a given value. + inline double sinc(const double x) { + return x?std::sin(x)/x:1; + } + + //! Return base-2 logarithm of a value. + inline double log2(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::log2(x); +#else + const double base2 = std::log(2.); + return std::log(x)/base2; +#endif + } + + //! Return square of a value. + template + inline T sqr(const T& val) { + return val*val; + } + + //! 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./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) { + return std::min(std::min(a,b),c); + } + + //! Return the minimum between four values. + template + inline t min(const t& a, const t& b, const t& c, const t& d) { + return std::min(std::min(a,b),std::min(c,d)); + } + + //! Return the maximum between three values. + template + inline t max(const t& a, const t& b, const t& c) { + return std::max(std::max(a,b),c); + } + + //! Return the maximum between four values. + template + inline t max(const t& a, const t& b, const t& c, const t& d) { + return std::max(std::max(a,b),std::max(c,d)); + } + + //! Return the sign of a value. + template + inline T sign(const T& x) { + return (T)(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; + while (x>i) i<<=1; + return i; + } + + //! Return the modulo of a value. + /** + \param x Input value. + \param m Modulo value. + \note This modulo function accepts negative and floating-points modulo numbers, as well as variables of any type. + **/ + template + inline T mod(const T& x, const T& m) { + const double dx = (double)x, dm = (double)m; + return (T)(dx - dm * std::floor(dx / dm)); + } + inline int mod(const bool x, const bool m) { + return m?(x?1:0):0; + } + inline int mod(const unsigned char x, const unsigned char m) { + return x%m; + } + inline int mod(const char x, const char m) { +#if defined(CHAR_MAX) && CHAR_MAX==255 + return x%m; +#else + return x>=0?x%m:(x%m?m + x%m:0); +#endif + } + inline int mod(const unsigned short x, const unsigned short m) { + return x%m; + } + inline int mod(const short x, const short m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline int mod(const unsigned int x, const unsigned int m) { + return (int)(x%m); + } + inline int mod(const int x, const int m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline cimg_int64 mod(const cimg_uint64 x, const cimg_uint64 m) { + return x%m; + } + inline cimg_int64 mod(const cimg_int64 x, const cimg_int64 m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + + //! Return the min-mod of two values. + /** + \note minmod(\p a,\p b) is defined to be: + - minmod(\p a,\p b) = min(\p a,\p b), if \p a and \p b have the same sign. + - minmod(\p a,\p b) = 0, if \p a and \p b have different signs. + **/ + template + inline T minmod(const T& a, const T& b) { + return a*b<=0?0:(a>0?(a + inline T round(const T& x) { + return (T)std::floor((_cimg_Tfloat)x + 0.5f); + } + + //! Return rounded value. + /** + \param x Value to be rounded. + \param y Rounding precision. + \param rounding_type Type of rounding operation (\c 0 = nearest, \c -1 = backward, \c 1 = forward). + \return Rounded value, having the same type as input value \c x. + **/ + template + inline T round(const T& x, const double y, const int rounding_type=0) { + if (y<=0) return x; + if (y==1) switch (rounding_type) { + case 0 : return cimg::round(x); + case 1 : return (T)std::ceil((_cimg_Tfloat)x); + default : return (T)std::floor((_cimg_Tfloat)x); + } + const double sx = (double)x/y, floor = std::floor(sx), delta = sx - floor; + return (T)(y*(rounding_type<0?floor:rounding_type>0?std::ceil(sx):delta<0.5?floor:std::ceil(sx))); + } + + // Code to compute fast median from 2,3,5,7,9,13,25 and 49 values. + // (contribution by RawTherapee: http://rawtherapee.com/). + template + inline T median(T val0, T val1) { + return (val0 + val1)/2; + } + + template + inline T median(T val0, T val1, T val2) { + return std::max(std::min(val0,val1),std::min(val2,std::max(val0,val1))); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = std::max(val0,tmp); val1 = std::min(val1,val4); tmp = std::min(val1,val2); val2 = std::max(val1,val2); + val1 = tmp; tmp = std::min(val2,val3); + return std::max(val1,tmp); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6) { + T tmp = std::min(val0,val5); + val5 = std::max(val0,val5); val0 = tmp; tmp = std::min(val0,val3); val3 = std::max(val0,val3); val0 = tmp; + tmp = std::min(val1,val6); val6 = std::max(val1,val6); val1 = tmp; tmp = std::min(val2,val4); + val4 = std::max(val2,val4); val2 = tmp; val1 = std::max(val0,val1); tmp = std::min(val3,val5); + val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); + val3 = std::max(tmp,val3); val3 = std::min(val3,val6); tmp = std::min(val4,val5); val4 = std::max(val1,tmp); + tmp = std::min(val1,tmp); val3 = std::max(tmp,val3); + return std::min(val3,val4); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8) { + T tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val7 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); + val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val6,val7); + val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val3 = std::max(val0,val3); val5 = std::min(val5,val8); + val7 = std::max(val4,tmp); tmp = std::min(val4,tmp); val6 = std::max(val3,val6); + val4 = std::max(val1,tmp); val2 = std::min(val2,val5); val4 = std::min(val4,val7); + tmp = std::min(val4,val2); val2 = std::max(val4,val2); val4 = std::max(val6,tmp); + return std::min(val4,val2); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8, T val9, T val10, T val11, + T val12) { + T tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = tmp; tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; + tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val5,val8); + val8 = std::max(val5,val8); val5 = tmp; tmp = std::min(val0,val12); val12 = std::max(val0,val12); + val0 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val2,val3); val3 = std::max(val2,val3); val2 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val8,val11); + val11 = std::max(val8,val11); val8 = tmp; tmp = std::min(val7,val12); val12 = std::max(val7,val12); val7 = tmp; + tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val10,val11); val11 = std::max(val10,val11); val10 = tmp; tmp = std::min(val1,val4); + val4 = std::max(val1,val4); val1 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val8); val8 = std::max(val7,val8); val7 = tmp; val11 = std::min(val11,val12); + tmp = std::min(val4,val9); val9 = std::max(val4,val9); val4 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; + tmp = std::min(val5,val6); val6 = std::max(val5,val6); val5 = tmp; val8 = std::min(val8,val9); + val10 = std::min(val10,val11); tmp = std::min(val1,val7); val7 = std::max(val1,val7); val1 = tmp; + tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; val3 = std::max(val1,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; val8 = std::min(val8,val10); + val5 = std::max(val0,val5); val5 = std::max(val2,val5); tmp = std::min(val6,val8); val8 = std::max(val6,val8); + val5 = std::max(val3,val5); val7 = std::min(val7,val8); val6 = std::max(val4,tmp); tmp = std::min(val4,tmp); + val5 = std::max(tmp,val5); val6 = std::min(val6,val7); + return std::max(val5,val6); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, + T val5, T val6, T val7, T val8, T val9, + T val10, T val11, T val12, T val13, T val14, + T val15, T val16, T val17, T val18, T val19, + T val20, T val21, T val22, T val23, T val24) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); val2 = std::min(tmp,val3); + val3 = std::max(tmp,val3); tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; + tmp = std::min(val5,val7); val7 = std::max(val5,val7); val5 = std::min(tmp,val6); val6 = std::max(tmp,val6); + tmp = std::min(val9,val10); val10 = std::max(val9,val10); val9 = tmp; tmp = std::min(val8,val10); + val10 = std::max(val8,val10); val8 = std::min(tmp,val9); val9 = std::max(tmp,val9); + tmp = std::min(val12,val13); val13 = std::max(val12,val13); val12 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val15,val16); val16 = std::max(val15,val16); val15 = tmp; tmp = std::min(val14,val16); + val16 = std::max(val14,val16); val14 = std::min(tmp,val15); val15 = std::max(tmp,val15); + tmp = std::min(val18,val19); val19 = std::max(val18,val19); val18 = tmp; tmp = std::min(val17,val19); + val19 = std::max(val17,val19); val17 = std::min(tmp,val18); val18 = std::max(tmp,val18); + tmp = std::min(val21,val22); val22 = std::max(val21,val22); val21 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = std::min(tmp,val21); val21 = std::max(tmp,val21); + tmp = std::min(val23,val24); val24 = std::max(val23,val24); val23 = tmp; tmp = std::min(val2,val5); + val5 = std::max(val2,val5); val2 = tmp; tmp = std::min(val3,val6); val6 = std::max(val3,val6); val3 = tmp; + tmp = std::min(val0,val6); val6 = std::max(val0,val6); val0 = std::min(tmp,val3); val3 = std::max(tmp,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = std::min(tmp,val4); val4 = std::max(tmp,val4); tmp = std::min(val11,val14); + val14 = std::max(val11,val14); val11 = tmp; tmp = std::min(val8,val14); val14 = std::max(val8,val14); + val8 = std::min(tmp,val11); val11 = std::max(tmp,val11); tmp = std::min(val12,val15); + val15 = std::max(val12,val15); val12 = tmp; tmp = std::min(val9,val15); val15 = std::max(val9,val15); + val9 = std::min(tmp,val12); val12 = std::max(tmp,val12); tmp = std::min(val13,val16); + val16 = std::max(val13,val16); val13 = tmp; tmp = std::min(val10,val16); val16 = std::max(val10,val16); + val10 = std::min(tmp,val13); val13 = std::max(tmp,val13); tmp = std::min(val20,val23); + val23 = std::max(val20,val23); val20 = tmp; tmp = std::min(val17,val23); val23 = std::max(val17,val23); + val17 = std::min(tmp,val20); val20 = std::max(tmp,val20); tmp = std::min(val21,val24); + val24 = std::max(val21,val24); val21 = tmp; tmp = std::min(val18,val24); val24 = std::max(val18,val24); + val18 = std::min(tmp,val21); val21 = std::max(tmp,val21); tmp = std::min(val19,val22); + val22 = std::max(val19,val22); val19 = tmp; val17 = std::max(val8,val17); tmp = std::min(val9,val18); + val18 = std::max(val9,val18); val9 = tmp; tmp = std::min(val0,val18); val18 = std::max(val0,val18); + val9 = std::max(tmp,val9); tmp = std::min(val10,val19); val19 = std::max(val10,val19); val10 = tmp; + tmp = std::min(val1,val19); val19 = std::max(val1,val19); val1 = std::min(tmp,val10); + val10 = std::max(tmp,val10); tmp = std::min(val11,val20); val20 = std::max(val11,val20); val11 = tmp; + tmp = std::min(val2,val20); val20 = std::max(val2,val20); val11 = std::max(tmp,val11); + tmp = std::min(val12,val21); val21 = std::max(val12,val21); val12 = tmp; tmp = std::min(val3,val21); + val21 = std::max(val3,val21); val3 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val13,val22); val22 = std::max(val13,val22); val4 = std::min(val4,val22); + val13 = std::max(val4,tmp); tmp = std::min(val4,tmp); val4 = tmp; tmp = std::min(val14,val23); + val23 = std::max(val14,val23); val14 = tmp; tmp = std::min(val5,val23); val23 = std::max(val5,val23); + val5 = std::min(tmp,val14); val14 = std::max(tmp,val14); tmp = std::min(val15,val24); + val24 = std::max(val15,val24); val15 = tmp; val6 = std::min(val6,val24); tmp = std::min(val6,val15); + val15 = std::max(val6,val15); val6 = tmp; tmp = std::min(val7,val16); val7 = std::min(tmp,val19); + tmp = std::min(val13,val21); val15 = std::min(val15,val23); tmp = std::min(val7,tmp); + val7 = std::min(tmp,val15); val9 = std::max(val1,val9); val11 = std::max(val3,val11); + val17 = std::max(val5,val17); val17 = std::max(val11,val17); val17 = std::max(val9,val17); + tmp = std::min(val4,val10); val10 = std::max(val4,val10); val4 = tmp; tmp = std::min(val6,val12); + val12 = std::max(val6,val12); val6 = tmp; tmp = std::min(val7,val14); val14 = std::max(val7,val14); + val7 = tmp; tmp = std::min(val4,val6); val6 = std::max(val4,val6); val7 = std::max(tmp,val7); + tmp = std::min(val12,val14); val14 = std::max(val12,val14); val12 = tmp; val10 = std::min(val10,val14); + tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val10,val12); + val12 = std::max(val10,val12); val10 = std::max(val6,tmp); tmp = std::min(val6,tmp); + val17 = std::max(tmp,val17); tmp = std::min(val12,val17); val17 = std::max(val12,val17); val12 = tmp; + val7 = std::min(val7,val17); tmp = std::min(val7,val10); val10 = std::max(val7,val10); val7 = tmp; + tmp = std::min(val12,val18); val18 = std::max(val12,val18); val12 = std::max(val7,tmp); + val10 = std::min(val10,val18); tmp = std::min(val12,val20); val20 = std::max(val12,val20); val12 = tmp; + tmp = std::min(val10,val20); + return std::max(tmp,val12); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, + T val7, T val8, T val9, T val10, T val11, T val12, T val13, + T val14, T val15, T val16, T val17, T val18, T val19, T val20, + T val21, T val22, T val23, T val24, T val25, T val26, T val27, + T val28, T val29, T val30, T val31, T val32, T val33, T val34, + T val35, T val36, T val37, T val38, T val39, T val40, T val41, + T val42, T val43, T val44, T val45, T val46, T val47, T val48) { + T tmp = std::min(val0,val32); + val32 = std::max(val0,val32); val0 = tmp; tmp = std::min(val1,val33); val33 = std::max(val1,val33); val1 = tmp; + tmp = std::min(val2,val34); val34 = std::max(val2,val34); val2 = tmp; tmp = std::min(val3,val35); + val35 = std::max(val3,val35); val3 = tmp; tmp = std::min(val4,val36); val36 = std::max(val4,val36); val4 = tmp; + tmp = std::min(val5,val37); val37 = std::max(val5,val37); val5 = tmp; tmp = std::min(val6,val38); + val38 = std::max(val6,val38); val6 = tmp; tmp = std::min(val7,val39); val39 = std::max(val7,val39); val7 = tmp; + tmp = std::min(val8,val40); val40 = std::max(val8,val40); val8 = tmp; tmp = std::min(val9,val41); + val41 = std::max(val9,val41); val9 = tmp; tmp = std::min(val10,val42); val42 = std::max(val10,val42); + val10 = tmp; tmp = std::min(val11,val43); val43 = std::max(val11,val43); val11 = tmp; + tmp = std::min(val12,val44); val44 = std::max(val12,val44); val12 = tmp; tmp = std::min(val13,val45); + val45 = std::max(val13,val45); val13 = tmp; tmp = std::min(val14,val46); val46 = std::max(val14,val46); + val14 = tmp; tmp = std::min(val15,val47); val47 = std::max(val15,val47); val15 = tmp; + tmp = std::min(val16,val48); val48 = std::max(val16,val48); val16 = tmp; tmp = std::min(val0,val16); + val16 = std::max(val0,val16); val0 = tmp; tmp = std::min(val1,val17); val17 = std::max(val1,val17); + val1 = tmp; tmp = std::min(val2,val18); val18 = std::max(val2,val18); val2 = tmp; tmp = std::min(val3,val19); + val19 = std::max(val3,val19); val3 = tmp; tmp = std::min(val4,val20); val20 = std::max(val4,val20); val4 = tmp; + tmp = std::min(val5,val21); val21 = std::max(val5,val21); val5 = tmp; tmp = std::min(val6,val22); + val22 = std::max(val6,val22); val6 = tmp; tmp = std::min(val7,val23); val23 = std::max(val7,val23); val7 = tmp; + tmp = std::min(val8,val24); val24 = std::max(val8,val24); val8 = tmp; tmp = std::min(val9,val25); + val25 = std::max(val9,val25); val9 = tmp; tmp = std::min(val10,val26); val26 = std::max(val10,val26); + val10 = tmp; tmp = std::min(val11,val27); val27 = std::max(val11,val27); val11 = tmp; + tmp = std::min(val12,val28); val28 = std::max(val12,val28); val12 = tmp; tmp = std::min(val13,val29); + val29 = std::max(val13,val29); val13 = tmp; tmp = std::min(val14,val30); val30 = std::max(val14,val30); + val14 = tmp; tmp = std::min(val15,val31); val31 = std::max(val15,val31); val15 = tmp; + tmp = std::min(val32,val48); val48 = std::max(val32,val48); val32 = tmp; tmp = std::min(val16,val32); + val32 = std::max(val16,val32); val16 = tmp; tmp = std::min(val17,val33); val33 = std::max(val17,val33); + val17 = tmp; tmp = std::min(val18,val34); val34 = std::max(val18,val34); val18 = tmp; + tmp = std::min(val19,val35); val35 = std::max(val19,val35); val19 = tmp; tmp = std::min(val20,val36); + val36 = std::max(val20,val36); val20 = tmp; tmp = std::min(val21,val37); val37 = std::max(val21,val37); + val21 = tmp; tmp = std::min(val22,val38); val38 = std::max(val22,val38); val22 = tmp; + tmp = std::min(val23,val39); val39 = std::max(val23,val39); val23 = tmp; tmp = std::min(val24,val40); + val40 = std::max(val24,val40); val24 = tmp; tmp = std::min(val25,val41); val41 = std::max(val25,val41); + val25 = tmp; tmp = std::min(val26,val42); val42 = std::max(val26,val42); val26 = tmp; + tmp = std::min(val27,val43); val43 = std::max(val27,val43); val27 = tmp; tmp = std::min(val28,val44); + val44 = std::max(val28,val44); val28 = tmp; tmp = std::min(val29,val45); val45 = std::max(val29,val45); + val29 = tmp; tmp = std::min(val30,val46); val46 = std::max(val30,val46); val30 = tmp; + tmp = std::min(val31,val47); val47 = std::max(val31,val47); val31 = tmp; tmp = std::min(val0,val8); + val8 = std::max(val0,val8); val0 = tmp; tmp = std::min(val1,val9); val9 = std::max(val1,val9); val1 = tmp; + tmp = std::min(val2,val10); val10 = std::max(val2,val10); val2 = tmp; tmp = std::min(val3,val11); + val11 = std::max(val3,val11); val3 = tmp; tmp = std::min(val4,val12); val12 = std::max(val4,val12); val4 = tmp; + tmp = std::min(val5,val13); val13 = std::max(val5,val13); val5 = tmp; tmp = std::min(val6,val14); + val14 = std::max(val6,val14); val6 = tmp; tmp = std::min(val7,val15); val15 = std::max(val7,val15); val7 = tmp; + tmp = std::min(val16,val24); val24 = std::max(val16,val24); val16 = tmp; tmp = std::min(val17,val25); + val25 = std::max(val17,val25); val17 = tmp; tmp = std::min(val18,val26); val26 = std::max(val18,val26); + val18 = tmp; tmp = std::min(val19,val27); val27 = std::max(val19,val27); val19 = tmp; + tmp = std::min(val20,val28); val28 = std::max(val20,val28); val20 = tmp; tmp = std::min(val21,val29); + val29 = std::max(val21,val29); val21 = tmp; tmp = std::min(val22,val30); val30 = std::max(val22,val30); + val22 = tmp; tmp = std::min(val23,val31); val31 = std::max(val23,val31); val23 = tmp; + tmp = std::min(val32,val40); val40 = std::max(val32,val40); val32 = tmp; tmp = std::min(val33,val41); + val41 = std::max(val33,val41); val33 = tmp; tmp = std::min(val34,val42); val42 = std::max(val34,val42); + val34 = tmp; tmp = std::min(val35,val43); val43 = std::max(val35,val43); val35 = tmp; + tmp = std::min(val36,val44); val44 = std::max(val36,val44); val36 = tmp; tmp = std::min(val37,val45); + val45 = std::max(val37,val45); val37 = tmp; tmp = std::min(val38,val46); val46 = std::max(val38,val46); + val38 = tmp; tmp = std::min(val39,val47); val47 = std::max(val39,val47); val39 = tmp; + tmp = std::min(val8,val32); val32 = std::max(val8,val32); val8 = tmp; tmp = std::min(val9,val33); + val33 = std::max(val9,val33); val9 = tmp; tmp = std::min(val10,val34); val34 = std::max(val10,val34); + val10 = tmp; tmp = std::min(val11,val35); val35 = std::max(val11,val35); val11 = tmp; + tmp = std::min(val12,val36); val36 = std::max(val12,val36); val12 = tmp; tmp = std::min(val13,val37); + val37 = std::max(val13,val37); val13 = tmp; tmp = std::min(val14,val38); val38 = std::max(val14,val38); + val14 = tmp; tmp = std::min(val15,val39); val39 = std::max(val15,val39); val15 = tmp; + tmp = std::min(val24,val48); val48 = std::max(val24,val48); val24 = tmp; tmp = std::min(val8,val16); + val16 = std::max(val8,val16); val8 = tmp; tmp = std::min(val9,val17); val17 = std::max(val9,val17); + val9 = tmp; tmp = std::min(val10,val18); val18 = std::max(val10,val18); val10 = tmp; + tmp = std::min(val11,val19); val19 = std::max(val11,val19); val11 = tmp; tmp = std::min(val12,val20); + val20 = std::max(val12,val20); val12 = tmp; tmp = std::min(val13,val21); val21 = std::max(val13,val21); + val13 = tmp; tmp = std::min(val14,val22); val22 = std::max(val14,val22); val14 = tmp; + tmp = std::min(val15,val23); val23 = std::max(val15,val23); val15 = tmp; tmp = std::min(val24,val32); + val32 = std::max(val24,val32); val24 = tmp; tmp = std::min(val25,val33); val33 = std::max(val25,val33); + val25 = tmp; tmp = std::min(val26,val34); val34 = std::max(val26,val34); val26 = tmp; + tmp = std::min(val27,val35); val35 = std::max(val27,val35); val27 = tmp; tmp = std::min(val28,val36); + val36 = std::max(val28,val36); val28 = tmp; tmp = std::min(val29,val37); val37 = std::max(val29,val37); + val29 = tmp; tmp = std::min(val30,val38); val38 = std::max(val30,val38); val30 = tmp; + tmp = std::min(val31,val39); val39 = std::max(val31,val39); val31 = tmp; tmp = std::min(val40,val48); + val48 = std::max(val40,val48); val40 = tmp; tmp = std::min(val0,val4); val4 = std::max(val0,val4); + val0 = tmp; tmp = std::min(val1,val5); val5 = std::max(val1,val5); val1 = tmp; tmp = std::min(val2,val6); + val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val8,val12); val12 = std::max(val8,val12); val8 = tmp; tmp = std::min(val9,val13); + val13 = std::max(val9,val13); val9 = tmp; tmp = std::min(val10,val14); val14 = std::max(val10,val14); + val10 = tmp; tmp = std::min(val11,val15); val15 = std::max(val11,val15); val11 = tmp; + tmp = std::min(val16,val20); val20 = std::max(val16,val20); val16 = tmp; tmp = std::min(val17,val21); + val21 = std::max(val17,val21); val17 = tmp; tmp = std::min(val18,val22); val22 = std::max(val18,val22); + val18 = tmp; tmp = std::min(val19,val23); val23 = std::max(val19,val23); val19 = tmp; + tmp = std::min(val24,val28); val28 = std::max(val24,val28); val24 = tmp; tmp = std::min(val25,val29); + val29 = std::max(val25,val29); val25 = tmp; tmp = std::min(val26,val30); val30 = std::max(val26,val30); + val26 = tmp; tmp = std::min(val27,val31); val31 = std::max(val27,val31); val27 = tmp; + tmp = std::min(val32,val36); val36 = std::max(val32,val36); val32 = tmp; tmp = std::min(val33,val37); + val37 = std::max(val33,val37); val33 = tmp; tmp = std::min(val34,val38); val38 = std::max(val34,val38); + val34 = tmp; tmp = std::min(val35,val39); val39 = std::max(val35,val39); val35 = tmp; + tmp = std::min(val40,val44); val44 = std::max(val40,val44); val40 = tmp; tmp = std::min(val41,val45); + val45 = std::max(val41,val45); val41 = tmp; tmp = std::min(val42,val46); val46 = std::max(val42,val46); + val42 = tmp; tmp = std::min(val43,val47); val47 = std::max(val43,val47); val43 = tmp; + tmp = std::min(val4,val32); val32 = std::max(val4,val32); val4 = tmp; tmp = std::min(val5,val33); + val33 = std::max(val5,val33); val5 = tmp; tmp = std::min(val6,val34); val34 = std::max(val6,val34); + val6 = tmp; tmp = std::min(val7,val35); val35 = std::max(val7,val35); val7 = tmp; + tmp = std::min(val12,val40); val40 = std::max(val12,val40); val12 = tmp; tmp = std::min(val13,val41); + val41 = std::max(val13,val41); val13 = tmp; tmp = std::min(val14,val42); val42 = std::max(val14,val42); + val14 = tmp; tmp = std::min(val15,val43); val43 = std::max(val15,val43); val15 = tmp; + tmp = std::min(val20,val48); val48 = std::max(val20,val48); val20 = tmp; tmp = std::min(val4,val16); + val16 = std::max(val4,val16); val4 = tmp; tmp = std::min(val5,val17); val17 = std::max(val5,val17); + val5 = tmp; tmp = std::min(val6,val18); val18 = std::max(val6,val18); val6 = tmp; + tmp = std::min(val7,val19); val19 = std::max(val7,val19); val7 = tmp; tmp = std::min(val12,val24); + val24 = std::max(val12,val24); val12 = tmp; tmp = std::min(val13,val25); val25 = std::max(val13,val25); + val13 = tmp; tmp = std::min(val14,val26); val26 = std::max(val14,val26); val14 = tmp; + tmp = std::min(val15,val27); val27 = std::max(val15,val27); val15 = tmp; tmp = std::min(val20,val32); + val32 = std::max(val20,val32); val20 = tmp; tmp = std::min(val21,val33); val33 = std::max(val21,val33); + val21 = tmp; tmp = std::min(val22,val34); val34 = std::max(val22,val34); val22 = tmp; + tmp = std::min(val23,val35); val35 = std::max(val23,val35); val23 = tmp; tmp = std::min(val28,val40); + val40 = std::max(val28,val40); val28 = tmp; tmp = std::min(val29,val41); val41 = std::max(val29,val41); + val29 = tmp; tmp = std::min(val30,val42); val42 = std::max(val30,val42); val30 = tmp; + tmp = std::min(val31,val43); val43 = std::max(val31,val43); val31 = tmp; tmp = std::min(val36,val48); + val48 = std::max(val36,val48); val36 = tmp; tmp = std::min(val4,val8); val8 = std::max(val4,val8); + val4 = tmp; tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val7,val11); val11 = std::max(val7,val11); val7 = tmp; + tmp = std::min(val12,val16); val16 = std::max(val12,val16); val12 = tmp; tmp = std::min(val13,val17); + val17 = std::max(val13,val17); val13 = tmp; tmp = std::min(val14,val18); val18 = std::max(val14,val18); + val14 = tmp; tmp = std::min(val15,val19); val19 = std::max(val15,val19); val15 = tmp; + tmp = std::min(val20,val24); val24 = std::max(val20,val24); val20 = tmp; tmp = std::min(val21,val25); + val25 = std::max(val21,val25); val21 = tmp; tmp = std::min(val22,val26); val26 = std::max(val22,val26); + val22 = tmp; tmp = std::min(val23,val27); val27 = std::max(val23,val27); val23 = tmp; + tmp = std::min(val28,val32); val32 = std::max(val28,val32); val28 = tmp; tmp = std::min(val29,val33); + val33 = std::max(val29,val33); val29 = tmp; tmp = std::min(val30,val34); val34 = std::max(val30,val34); + val30 = tmp; tmp = std::min(val31,val35); val35 = std::max(val31,val35); val31 = tmp; + tmp = std::min(val36,val40); val40 = std::max(val36,val40); val36 = tmp; tmp = std::min(val37,val41); + val41 = std::max(val37,val41); val37 = tmp; tmp = std::min(val38,val42); val42 = std::max(val38,val42); + val38 = tmp; tmp = std::min(val39,val43); val43 = std::max(val39,val43); val39 = tmp; + tmp = std::min(val44,val48); val48 = std::max(val44,val48); val44 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val1,val3); val3 = std::max(val1,val3); val1 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val5,val7); + val7 = std::max(val5,val7); val5 = tmp; tmp = std::min(val8,val10); val10 = std::max(val8,val10); val8 = tmp; + tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; tmp = std::min(val12,val14); + val14 = std::max(val12,val14); val12 = tmp; tmp = std::min(val13,val15); val15 = std::max(val13,val15); + val13 = tmp; tmp = std::min(val16,val18); val18 = std::max(val16,val18); val16 = tmp; + tmp = std::min(val17,val19); val19 = std::max(val17,val19); val17 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = tmp; tmp = std::min(val21,val23); val23 = std::max(val21,val23); + val21 = tmp; tmp = std::min(val24,val26); val26 = std::max(val24,val26); val24 = tmp; + tmp = std::min(val25,val27); val27 = std::max(val25,val27); val25 = tmp; tmp = std::min(val28,val30); + val30 = std::max(val28,val30); val28 = tmp; tmp = std::min(val29,val31); val31 = std::max(val29,val31); + val29 = tmp; tmp = std::min(val32,val34); val34 = std::max(val32,val34); val32 = tmp; + tmp = std::min(val33,val35); val35 = std::max(val33,val35); val33 = tmp; tmp = std::min(val36,val38); + val38 = std::max(val36,val38); val36 = tmp; tmp = std::min(val37,val39); val39 = std::max(val37,val39); + val37 = tmp; tmp = std::min(val40,val42); val42 = std::max(val40,val42); val40 = tmp; + tmp = std::min(val41,val43); val43 = std::max(val41,val43); val41 = tmp; tmp = std::min(val44,val46); + val46 = std::max(val44,val46); val44 = tmp; tmp = std::min(val45,val47); val47 = std::max(val45,val47); + val45 = tmp; tmp = std::min(val2,val32); val32 = std::max(val2,val32); val2 = tmp; tmp = std::min(val3,val33); + val33 = std::max(val3,val33); val3 = tmp; tmp = std::min(val6,val36); val36 = std::max(val6,val36); val6 = tmp; + tmp = std::min(val7,val37); val37 = std::max(val7,val37); val7 = tmp; tmp = std::min(val10,val40); + val40 = std::max(val10,val40); val10 = tmp; tmp = std::min(val11,val41); val41 = std::max(val11,val41); + val11 = tmp; tmp = std::min(val14,val44); val44 = std::max(val14,val44); val14 = tmp; + tmp = std::min(val15,val45); val45 = std::max(val15,val45); val15 = tmp; tmp = std::min(val18,val48); + val48 = std::max(val18,val48); val18 = tmp; tmp = std::min(val2,val16); val16 = std::max(val2,val16); + val2 = tmp; tmp = std::min(val3,val17); val17 = std::max(val3,val17); val3 = tmp; + tmp = std::min(val6,val20); val20 = std::max(val6,val20); val6 = tmp; tmp = std::min(val7,val21); + val21 = std::max(val7,val21); val7 = tmp; tmp = std::min(val10,val24); val24 = std::max(val10,val24); + val10 = tmp; tmp = std::min(val11,val25); val25 = std::max(val11,val25); val11 = tmp; + tmp = std::min(val14,val28); val28 = std::max(val14,val28); val14 = tmp; tmp = std::min(val15,val29); + val29 = std::max(val15,val29); val15 = tmp; tmp = std::min(val18,val32); val32 = std::max(val18,val32); + val18 = tmp; tmp = std::min(val19,val33); val33 = std::max(val19,val33); val19 = tmp; + tmp = std::min(val22,val36); val36 = std::max(val22,val36); val22 = tmp; tmp = std::min(val23,val37); + val37 = std::max(val23,val37); val23 = tmp; tmp = std::min(val26,val40); val40 = std::max(val26,val40); + val26 = tmp; tmp = std::min(val27,val41); val41 = std::max(val27,val41); val27 = tmp; + tmp = std::min(val30,val44); val44 = std::max(val30,val44); val30 = tmp; tmp = std::min(val31,val45); + val45 = std::max(val31,val45); val31 = tmp; tmp = std::min(val34,val48); val48 = std::max(val34,val48); + val34 = tmp; tmp = std::min(val2,val8); val8 = std::max(val2,val8); val2 = tmp; tmp = std::min(val3,val9); + val9 = std::max(val3,val9); val3 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val13); val13 = std::max(val7,val13); val7 = tmp; tmp = std::min(val10,val16); + val16 = std::max(val10,val16); val10 = tmp; tmp = std::min(val11,val17); val17 = std::max(val11,val17); + val11 = tmp; tmp = std::min(val14,val20); val20 = std::max(val14,val20); val14 = tmp; + tmp = std::min(val15,val21); val21 = std::max(val15,val21); val15 = tmp; tmp = std::min(val18,val24); + val24 = std::max(val18,val24); val18 = tmp; tmp = std::min(val19,val25); val25 = std::max(val19,val25); + val19 = tmp; tmp = std::min(val22,val28); val28 = std::max(val22,val28); val22 = tmp; + tmp = std::min(val23,val29); val29 = std::max(val23,val29); val23 = tmp; tmp = std::min(val26,val32); + val32 = std::max(val26,val32); val26 = tmp; tmp = std::min(val27,val33); val33 = std::max(val27,val33); + val27 = tmp; tmp = std::min(val30,val36); val36 = std::max(val30,val36); val30 = tmp; + tmp = std::min(val31,val37); val37 = std::max(val31,val37); val31 = tmp; tmp = std::min(val34,val40); + val40 = std::max(val34,val40); val34 = tmp; tmp = std::min(val35,val41); val41 = std::max(val35,val41); + val35 = tmp; tmp = std::min(val38,val44); val44 = std::max(val38,val44); val38 = tmp; + tmp = std::min(val39,val45); val45 = std::max(val39,val45); val39 = tmp; tmp = std::min(val42,val48); + val48 = std::max(val42,val48); val42 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); + val2 = tmp; tmp = std::min(val3,val5); val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val6,val8); + val8 = std::max(val6,val8); val6 = tmp; tmp = std::min(val7,val9); val9 = std::max(val7,val9); val7 = tmp; + tmp = std::min(val10,val12); val12 = std::max(val10,val12); val10 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = tmp; tmp = std::min(val14,val16); val16 = std::max(val14,val16); + val14 = tmp; tmp = std::min(val15,val17); val17 = std::max(val15,val17); val15 = tmp; + tmp = std::min(val18,val20); val20 = std::max(val18,val20); val18 = tmp; tmp = std::min(val19,val21); + val21 = std::max(val19,val21); val19 = tmp; tmp = std::min(val22,val24); val24 = std::max(val22,val24); + val22 = tmp; tmp = std::min(val23,val25); val25 = std::max(val23,val25); val23 = tmp; + tmp = std::min(val26,val28); val28 = std::max(val26,val28); val26 = tmp; tmp = std::min(val27,val29); + val29 = std::max(val27,val29); val27 = tmp; tmp = std::min(val30,val32); val32 = std::max(val30,val32); + val30 = tmp; tmp = std::min(val31,val33); val33 = std::max(val31,val33); val31 = tmp; + tmp = std::min(val34,val36); val36 = std::max(val34,val36); val34 = tmp; tmp = std::min(val35,val37); + val37 = std::max(val35,val37); val35 = tmp; tmp = std::min(val38,val40); val40 = std::max(val38,val40); + val38 = tmp; tmp = std::min(val39,val41); val41 = std::max(val39,val41); val39 = tmp; + tmp = std::min(val42,val44); val44 = std::max(val42,val44); val42 = tmp; tmp = std::min(val43,val45); + val45 = std::max(val43,val45); val43 = tmp; tmp = std::min(val46,val48); val48 = std::max(val46,val48); + val46 = tmp; val1 = std::max(val0,val1); val3 = std::max(val2,val3); val5 = std::max(val4,val5); + val7 = std::max(val6,val7); val9 = std::max(val8,val9); val11 = std::max(val10,val11); + val13 = std::max(val12,val13); val15 = std::max(val14,val15); val17 = std::max(val16,val17); + val19 = std::max(val18,val19); val21 = std::max(val20,val21); val23 = std::max(val22,val23); + val24 = std::min(val24,val25); val26 = std::min(val26,val27); val28 = std::min(val28,val29); + val30 = std::min(val30,val31); val32 = std::min(val32,val33); val34 = std::min(val34,val35); + val36 = std::min(val36,val37); val38 = std::min(val38,val39); val40 = std::min(val40,val41); + val42 = std::min(val42,val43); val44 = std::min(val44,val45); val46 = std::min(val46,val47); + val32 = std::max(val1,val32); val34 = std::max(val3,val34); val36 = std::max(val5,val36); + val38 = std::max(val7,val38); val9 = std::min(val9,val40); val11 = std::min(val11,val42); + val13 = std::min(val13,val44); val15 = std::min(val15,val46); val17 = std::min(val17,val48); + val24 = std::max(val9,val24); val26 = std::max(val11,val26); val28 = std::max(val13,val28); + val30 = std::max(val15,val30); val17 = std::min(val17,val32); val19 = std::min(val19,val34); + val21 = std::min(val21,val36); val23 = std::min(val23,val38); val24 = std::max(val17,val24); + val26 = std::max(val19,val26); val21 = std::min(val21,val28); val23 = std::min(val23,val30); + val24 = std::max(val21,val24); val23 = std::min(val23,val26); + return std::max(val23,val24); + } + + //! Return sqrt(x^2 + y^2). + template + inline T hypot(const T x, const T y) { + return std::sqrt(x*x + y*y); + } + + template + inline T hypot(const T x, const T y, const T z) { + return std::sqrt(x*x + y*y + z*z); + } + + template + inline T _hypot(const T x, const T y) { // Slower but more precise version + T nx = cimg::abs(x), ny = cimg::abs(y), t; + if (nx0) { t/=nx; return nx*std::sqrt(1 + t*t); } + return 0; + } + + //! Return the factorial of n + inline double factorial(const int n) { + if (n<0) return cimg::type::nan(); + if (n<2) return 1; + double res = 2; + for (int i = 3; i<=n; ++i) res*=i; + return res; + } + + //! Return the number of permutations of k objects in a set of n objects. + inline double permutations(const int k, const int n, const bool with_order) { + if (n<0 || k<0) return cimg::type::nan(); + if (k>n) return 0; + double res = 1; + for (int i = n; i>=n - k + 1; --i) res*=i; + return with_order?res:res/cimg::factorial(k); + } + + inline double _fibonacci(int exp) { + double + base = (1 + std::sqrt(5.))/2, + result = 1/std::sqrt(5.); + while (exp) { + if (exp&1) result*=base; + exp>>=1; + base*=base; + } + return result; + } + + //! Calculate fibonacci number. + // (Precise up to n = 78, less precise for n>78). + inline double fibonacci(const int n) { + if (n<0) return cimg::type::nan(); + if (n<3) return 1; + if (n<11) { + cimg_uint64 fn1 = 1, fn2 = 1, fn = 0; + for (int i = 3; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + if (n<75) // precise up to n = 74, faster than the integer calculation above for n>10 + return (double)((cimg_uint64)(_fibonacci(n) + 0.5)); + + if (n<94) { // precise up to n = 78, less precise for n>78 up to n = 93, overflows for n>93 + cimg_uint64 + fn1 = (cimg_uint64)1304969544928657ULL, + fn2 = (cimg_uint64)806515533049393ULL, + fn = 0; + for (int i = 75; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + return _fibonacci(n); // Not precise, but better than the wrong overflowing calculation + } + + //! Calculate greatest common divisor. + inline long gcd(long a, long b) { + while (a) { const long c = a; a = b%a; b = c; } + return b; + } + + //! Convert Ascii character to lower case. + inline char lowercase(const char x) { + return (char)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + inline double lowercase(const double x) { + return (double)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + + //! Convert C-string to lower case. + inline void lowercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = lowercase(*ptr); + } + + //! Convert Ascii character to upper case. + inline char uppercase(const char x) { + return (char)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + inline double uppercase(const double x) { + return (double)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + //! Convert C-string to upper case. + inline void uppercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uppercase(*ptr); + } + + //! Return \c true if input character is blank (space, tab, or non-printable character). + inline bool is_blank(const char c) { + return c>=0 && c<=' '; + } + + //! Read value in a C-string. + /** + \param str C-string containing the float value to read. + \return Read value. + \note Same as std::atof() extended to manage the retrieval of fractions from C-strings, + as in "1/2". + **/ + inline double atof(const char *const str) { + double x = 0, y = 1; + return str && cimg_sscanf(str,"%lf/%lf",&x,&y)>0?x/y:0; + } + + //! Compare the first \p l characters of two C-strings, ignoring the case. + /** + \param str1 C-string. + \param str2 C-string. + \param l Number of characters to compare. + \return \c 0 if the two strings are equal, something else otherwise. + \note This function has to be defined since it is not provided by all C++-compilers (not ANSI). + **/ + inline int strncasecmp(const char *const str1, const char *const str2, const int l) { + if (!l) return 0; + if (!str1) return str2?-1:0; + const char *nstr1 = str1, *nstr2 = str2; + int k, diff = 0; for (k = 0; kp && str[q]==delimiter; ) { --q; if (!is_iterative) break; } + } + const int n = q - p + 1; + if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; } + return false; + } + + //! Remove white spaces on the start and/or end of a C-string. + inline bool strpare(char *const str, const bool is_symmetric, const bool is_iterative) { + if (!str) return false; + const int l = (int)std::strlen(str); + int p, q; + if (is_symmetric) for (p = 0, q = l - 1; 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; } + return false; + } + + //! Replace reserved characters (for Windows filename) by another character. + /** + \param[in,out] str C-string to work with (modified at output). + \param[in] c Replacement character. + **/ + inline void strwindows_reserved(char *const str, const char c='_') { + for (char *s = str; *s; ++s) { + const char i = *s; + if (i=='<' || i=='>' || i==':' || i=='\"' || i=='/' || i=='\\' || i=='|' || i=='?' || i=='*') *s = c; + } + } + + //! Replace escape sequences in C-strings by their binary Ascii values. + /** + \param[in,out] str C-string to work with (modified at output). + **/ + inline void strunescape(char *const str) { +#define cimg_strunescape(ci,co) case ci : *nd = co; ++ns; break; + unsigned int val = 0; + for (char *ns = str, *nd = str; *ns || (bool)(*nd=0); ++nd) if (*ns=='\\') switch (*(++ns)) { + cimg_strunescape('a','\a'); + cimg_strunescape('b','\b'); + cimg_strunescape('e',0x1B); + cimg_strunescape('f','\f'); + cimg_strunescape('n','\n'); + cimg_strunescape('r','\r'); + cimg_strunescape('t','\t'); + cimg_strunescape('v','\v'); + cimg_strunescape('\\','\\'); + cimg_strunescape('\'','\''); + cimg_strunescape('\"','\"'); + cimg_strunescape('\?','\?'); + case 0 : *nd = 0; break; + case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : + cimg_sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns; + *nd = (char)val; break; + case 'x' : + cimg_sscanf(++ns,"%x",&val); + while ((*ns>='0' && *ns<='9') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns; + *nd = (char)val; break; + default : *nd = *(ns++); + } else *nd = *(ns++); + } + + // Return a temporary string describing the size of a memory buffer. + inline const char *strbuffersize(const cimg_ulong size); + + // Return string that identifies the running OS. + inline const char *stros() { +#if defined(linux) || defined(__linux) || defined(__linux__) + static const char *const str = "Linux"; +#elif defined(sun) || defined(__sun) + static const char *const str = "Sun OS"; +#elif defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined (__DragonFly__) + static const char *const str = "BSD"; +#elif defined(sgi) || defined(__sgi) + static const char *const str = "Irix"; +#elif defined(__MACOSX__) || defined(__APPLE__) + static const char *const str = "Mac OS"; +#elif defined(unix) || defined(__unix) || defined(__unix__) + static const char *const str = "Generic Unix"; +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \ + defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + static const char *const str = "Windows"; +#else + const char + *const _str1 = std::getenv("OSTYPE"), + *const _str2 = _str1?_str1:std::getenv("OS"), + *const str = _str2?_str2:"Unknown OS"; +#endif + return str; + } + + //! Return the basename of a filename. + inline const char* basename(const char *const s, const char separator=cimg_file_separator) { + const char *p = 0, *np = s; + while (np>=s && (p=np)) np = std::strchr(np,separator) + 1; + return p; + } + + // Return a random filename. + inline const char* filenamerand() { + cimg::mutex(6); + static char randomid[9]; + for (unsigned int k = 0; k<8; ++k) { + const int v = (int)cimg::rand(65535)%3; + randomid[k] = (char)(v==0?('0' + ((int)cimg::rand(65535)%10)): + (v==1?('a' + ((int)cimg::rand(65535)%26)): + ('A' + ((int)cimg::rand(65535)%26)))); + } + cimg::mutex(6,0); + return randomid; + } + + // Convert filename as a Windows-style filename (short path name). + inline void winformat_string(char *const str) { + if (str && *str) { +#if cimg_OS==2 + char *const nstr = new char[MAX_PATH]; + if (GetShortPathNameA(str,nstr,MAX_PATH)) std::strcpy(str,nstr); + delete[] nstr; +#endif + } + } + + // Open a file (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. + /** + \param path Path of the filename to open. + \param mode C-string describing the opening mode. + \return Opened file. + \note Same as std::fopen() but throw a \c CImgIOException when + the specified file cannot be opened, instead of returning \c 0. + **/ + inline std::FILE *fopen(const char *const path, const char *const mode) { + if (!path) + throw CImgArgumentException("cimg::fopen(): Specified file path is (null)."); + if (!mode) + throw CImgArgumentException("cimg::fopen(): File '%s', specified mode is (null).", + path); + std::FILE *res = 0; + if (*path=='-' && (!path[1] || path[1]=='.')) { + res = (*mode=='r')?cimg::_stdin():cimg::_stdout(); +#if cimg_OS==2 + if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode +#ifdef __BORLANDC__ + if (setmode(_fileno(res),0x8000)==-1) res = 0; +#else + if (_setmode(_fileno(res),0x8000)==-1) res = 0; +#endif + } +#endif + } else res = cimg::std_fopen(path,mode); + if (!res) throw CImgIOException("cimg::fopen(): Failed to open file '%s' with mode '%s'.", + path,mode); + return res; + } + + //! Close a file. + /** + \param file File to close. + \return \c 0 if file has been closed properly, something else otherwise. + \note Same as std::fclose() but display a warning message if + the file has not been closed properly. + **/ + inline int fclose(std::FILE *file) { + if (!file) { warn("cimg::fclose(): Specified file is (null)."); return 0; } + if (file==cimg::_stdin(false) || file==cimg::_stdout(false)) return 0; + const int errn = std::fclose(file); + if (errn!=0) warn("cimg::fclose(): Error code %d returned during file closing.", + errn); + return errn; + } + + //! Version of 'fseek()' that supports >=64bits offsets everywhere (for Windows). + inline int fseek(FILE *stream, cimg_long offset, int origin) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return _fseeki64(stream,(__int64)offset,origin); +#else + return std::fseek(stream,offset,origin); +#endif + } + + //! Version of 'ftell()' that supports >=64bits offsets everywhere (for Windows). + inline cimg_long ftell(FILE *stream) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return (cimg_long)_ftelli64(stream); +#else + return (cimg_long)std::ftell(stream); +#endif + } + + //! Check if a path is a directory. + /** + \param path Specified path to test. + **/ + inline bool is_directory(const char *const path) { + if (!path || !*path) return false; +#if cimg_OS==1 + struct stat st_buf; + return (!stat(path,&st_buf) && S_ISDIR(st_buf.st_mode)); +#elif cimg_OS==2 + const unsigned int res = (unsigned int)GetFileAttributesA(path); + return res==INVALID_FILE_ATTRIBUTES?false:(res&16); +#else + return false; +#endif + } + + //! Check if a path is a file. + /** + \param path Specified path to test. + **/ + inline bool is_file(const char *const path) { + if (!path || !*path) return false; + std::FILE *const file = cimg::std_fopen(path,"rb"); + if (!file) return false; + cimg::fclose(file); + return !is_directory(path); + } + + //! Get file size. + /** + \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 = cimg::std_fopen(filename,"rb"); + if (!file) return (cimg_int64)-1; + std::fseek(file,0,SEEK_END); + const cimg_int64 siz = (cimg_int64)std::ftell(file); + cimg::fclose(file); + return siz; + } + + //! Get last write time of a given file or directory (multiple-attributes version). + /** + \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). + \param nb_attr Number of attributes to read/write. + \return Latest read attribute. + **/ + template + inline int fdate(const char *const path, T *attr, const unsigned int nb_attr) { +#define _cimg_fdate_err() for (unsigned int i = 0; i + inline int date(T *attr, const unsigned int nb_attr) { + int res = -1; + cimg::mutex(6); +#if cimg_OS==2 + 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); + attr[i] = (T)res; + } +#endif + cimg::mutex(6,0); + return res; + } + + //! 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. + **/ + 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 dcraw binary. + inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c wget binary. + inline const char *wget_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c curl binary. + inline const char *curl_path(const char *const user_path=0, const bool reinit_path=false); + + //! Split filename into two C-strings \c body and \c extension. + /** + filename and body must not overlap! + **/ + inline const char *split_filename(const char *const filename, char *const body=0) { + if (!filename) { if (body) *body = 0; return 0; } + const char *p = 0; for (const char *np = filename; np>=filename && (p=np); np = std::strchr(np,'.') + 1) {} + if (p==filename) { + if (body) std::strcpy(body,filename); + return filename + std::strlen(filename); + } + const unsigned int l = (unsigned int)(p - filename - 1); + if (body) { if (l) std::memcpy(body,filename,l); body[l] = 0; } + return p; + } + + //! Generate a numbered version of a filename. + inline char* number_filename(const char *const filename, const int number, + const unsigned int digits, char *const str) { + if (!filename) { if (str) *str = 0; return 0; } + char *const format = new char[1024], *const body = new char[1024]; + const char *const ext = cimg::split_filename(filename,body); + if (*ext) cimg_snprintf(format,1024,"%%s_%%.%ud.%%s",digits); + else cimg_snprintf(format,1024,"%%s_%%.%ud",digits); + cimg_sprintf(str,format,body,number,ext); + delete[] format; delete[] body; + return str; + } + + //! Read data from file. + /** + \param[out] ptr Pointer to memory buffer that will contain the binary data read from file. + \param nmemb Number of elements to read. + \param stream File to read data from. + \return Number of read elements. + \note Same as std::fread() but may display warning message if all elements could not be read. + **/ + template + inline size_t fread(T *const ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fread(): Invalid reading request of %u %s%s from file %p to buffer %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",stream,ptr); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_read = nmemb, al_read = 0, l_to_read = 0, l_al_read = 0; + do { + l_to_read = (to_read*sizeof(T))0); + if (to_read>0) + warn("cimg::fread(): Only %lu/%lu elements could be read from file.", + (unsigned long)al_read,(unsigned long)nmemb); + return al_read; + } + + //! Write data to file. + /** + \param ptr Pointer to memory buffer containing the binary data to write on file. + \param nmemb Number of elements to write. + \param[out] stream File to write data on. + \return Number of written elements. + \note Similar to std::fwrite but may display warning messages if all elements could not be written. + **/ + template + inline size_t fwrite(const T *ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fwrite(): Invalid writing request of %u %s%s from buffer %p to file %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",ptr,stream); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_write = nmemb, al_write = 0, l_to_write = 0, l_al_write = 0; + do { + l_to_write = (to_write*sizeof(T))0); + if (to_write>0) + warn("cimg::fwrite(): Only %lu/%lu elements could be written in file.", + (unsigned long)al_write,(unsigned long)nmemb); + return al_write; + } + + //! Create an empty file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + **/ + inline void fempty(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::fempty(): Specified filename is (null)."); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!file) cimg::fclose(nfile); + } + + // Try to guess format from an image file. + inline const char *ftype(std::FILE *const file, const char *const filename); + + // Load file from network as a local temporary file. + inline char *load_network(const char *const url, char *const filename_local, + const unsigned int timeout=0, const bool try_fallback=false, + const char *const referer=0); + + //! Return options specified on the command line. + inline const char* option(const char *const name, const int argc, const char *const *const argv, + const char *const defaut, const char *const usage, const bool reset_static) { + static bool first = true, visu = false; + if (reset_static) { first = true; return 0; } + const char *res = 0; + if (first) { + first = false; + visu = cimg::option("-h",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("-help",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("--help",argc,argv,(char*)0,(char*)0,false)!=0; + } + if (!name && visu) { + if (usage) { + std::fprintf(cimg::output(),"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal); + std::fprintf(cimg::output(),": %s",usage); + std::fprintf(cimg::output()," (%s, %s)\n\n",cimg_date,cimg_time); + } + if (defaut) std::fprintf(cimg::output(),"%s\n",defaut); + } + if (name) { + if (argc>0) { + int k = 0; + while (k Operating System: %s%-13s%s %s('cimg_OS'=%d)%s\n", + cimg::t_bold, + cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"), + cimg::t_normal,cimg::t_green, + cimg_OS, + cimg::t_normal); + + std::fprintf(cimg::output()," > CPU endianness: %s%s Endian%s\n", + cimg::t_bold, + cimg::endianness()?"Big":"Little", + cimg::t_normal); + + std::fprintf(cimg::output()," > Verbosity mode: %s%-13s%s %s('cimg_verbosity'=%d)%s\n", + cimg::t_bold, + cimg_verbosity==0?"Quiet": + cimg_verbosity==1?"Console": + cimg_verbosity==2?"Dialog": + cimg_verbosity==3?"Console+Warnings":"Dialog+Warnings", + cimg::t_normal,cimg::t_green, + cimg_verbosity, + cimg::t_normal); + + std::fprintf(cimg::output()," > Stricts warnings: %s%-13s%s %s('cimg_strict_warnings' %s)%s\n", + cimg::t_bold, +#ifdef cimg_strict_warnings + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Support for C++11: %s%-13s%s %s('cimg_use_cpp11'=%d)%s\n", + cimg::t_bold, + cimg_use_cpp11?"Yes":"No", + cimg::t_normal,cimg::t_green, + (int)cimg_use_cpp11, + cimg::t_normal); + + std::fprintf(cimg::output()," > Using VT100 messages: %s%-13s%s %s('cimg_use_vt100' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_vt100 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Display type: %s%-13s%s %s('cimg_display'=%d)%s\n", + cimg::t_bold, + cimg_display==0?"No display":cimg_display==1?"X11":cimg_display==2?"Windows GDI":"Unknown", + cimg::t_normal,cimg::t_green, + (int)cimg_display, + cimg::t_normal); + +#if cimg_display==1 + std::fprintf(cimg::output()," > Using XShm for X11: %s%-13s%s %s('cimg_use_xshm' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xshm + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using XRand for X11: %s%-13s%s %s('cimg_use_xrandr' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xrandr + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); +#endif + std::fprintf(cimg::output()," > Using OpenMP: %s%-13s%s %s('cimg_use_openmp' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_openmp + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using PNG library: %s%-13s%s %s('cimg_use_png' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_png + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using JPEG library: %s%-13s%s %s('cimg_use_jpeg' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_jpeg + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using TIFF library: %s%-13s%s %s('cimg_use_tiff' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_tiff + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using Magick++ library: %s%-13s%s %s('cimg_use_magick' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_magick + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using FFTW3 library: %s%-13s%s %s('cimg_use_fftw3' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_fftw3 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using LAPACK library: %s%-13s%s %s('cimg_use_lapack' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_lapack + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + char *const tmp = new char[1024]; + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::imagemagick_path()); + std::fprintf(cimg::output()," > Path of ImageMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::graphicsmagick_path()); + std::fprintf(cimg::output()," > Path of GraphicsMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::medcon_path()); + std::fprintf(cimg::output()," > Path of 'medcon': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::temporary_path()); + std::fprintf(cimg::output()," > Temporary path: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + std::fprintf(cimg::output(),"\n"); + delete[] tmp; + } + + // Declare LAPACK function signatures if LAPACK support is enabled. +#ifdef cimg_use_lapack + template + inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) { + dgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) { + sgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + template + inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) { + dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) { + sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + template + inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN, + T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) { + dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN, + float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) { + sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + template + inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) { + int one = 1; + dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) { + int one = 1; + sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + template + inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) { + dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) { + ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + template + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, T* lapA, int &LDA, + T* lapB, int &LDB, T* WORK, int &LWORK, int &INFO){ + dgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, float* lapA, int &LDA, + float* lapB, int &LDB, float* WORK, int &LWORK, int &INFO){ + sgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + +#endif + + // End of the 'cimg' namespace + } + + /*------------------------------------------------ + # + # + # Definition of mathematical operators and + # external functions. + # + # + -------------------------------------------------*/ + +#define _cimg_create_ext_operators(typ) \ + template \ + inline CImg::type> operator+(const typ val, const CImg& img) { \ + return img + val; \ + } \ + template \ + inline CImg::type> operator-(const typ val, const CImg& img) { \ + typedef typename cimg::superset::type Tt; \ + return CImg(img._width,img._height,img._depth,img._spectrum,val)-=img; \ + } \ + template \ + inline CImg::type> operator*(const typ val, const CImg& img) { \ + return img*val; \ + } \ + template \ + inline CImg::type> operator/(const typ val, const CImg& img) { \ + return val*img.get_invert(); \ + } \ + template \ + inline CImg::type> operator&(const typ val, const CImg& img) { \ + return img & val; \ + } \ + template \ + inline CImg::type> operator|(const typ val, const CImg& img) { \ + return img | val; \ + } \ + template \ + inline CImg::type> operator^(const typ val, const CImg& img) { \ + return img ^ val; \ + } \ + template \ + inline bool operator==(const typ val, const CImg& img) { \ + return img == val; \ + } \ + template \ + inline bool operator!=(const typ val, const CImg& img) { \ + return img != val; \ + } + + _cimg_create_ext_operators(bool) + _cimg_create_ext_operators(unsigned char) + _cimg_create_ext_operators(char) + _cimg_create_ext_operators(signed char) + _cimg_create_ext_operators(unsigned short) + _cimg_create_ext_operators(short) + _cimg_create_ext_operators(unsigned int) + _cimg_create_ext_operators(int) + _cimg_create_ext_operators(cimg_uint64) + _cimg_create_ext_operators(cimg_int64) + _cimg_create_ext_operators(float) + _cimg_create_ext_operators(double) + _cimg_create_ext_operators(long double) + + template + inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg& img) { + return img + expression; + } + + template + inline CImg<_cimg_Tfloat> operator-(const char *const expression, const CImg& img) { + return CImg<_cimg_Tfloat>(img,false).fill(expression,true)-=img; + } + + template + inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg& img) { + return img*expression; + } + + template + inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg& img) { + return expression*img.get_invert(); + } + + template + inline CImg operator&(const char *const expression, const CImg& img) { + return img & expression; + } + + template + inline CImg operator|(const char *const expression, const CImg& img) { + return img | expression; + } + + template + inline CImg operator^(const char *const expression, const CImg& img) { + return img ^ expression; + } + + template + inline bool operator==(const char *const expression, const CImg& img) { + return img==expression; + } + + template + inline bool operator!=(const char *const expression, const CImg& img) { + return img!=expression; + } + + template + inline CImg transpose(const CImg& instance) { + return instance.get_transpose(); + } + + template + inline CImg<_cimg_Tfloat> invert(const CImg& instance) { + return instance.get_invert(); + } + + template + inline CImg<_cimg_Tfloat> pseudoinvert(const CImg& instance) { + return instance.get_pseudoinvert(); + } + +#define _cimg_create_ext_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) + + /*----------------------------------- + # + # Define the CImgDisplay structure + # + ----------------------------------*/ + //! Allow the creation of windows, display images on them and manage user events (keyboard, mouse and windows events). + /** + CImgDisplay methods rely on a low-level graphic library to perform: it can be either \b X-Window + (X11, for Unix-based systems) or \b GDI32 (for Windows-based systems). + If both libraries are missing, CImgDisplay will not be able to display images on screen, and will enter + a minimal mode where warning messages will be outputed each time the program is trying to call one of the + CImgDisplay method. + + The configuration variable \c cimg_display tells about the graphic library used. + It is set automatically by \CImg when one of these graphic libraries has been detected. + But, you can override its value if necessary. Valid choices are: + - 0: Disable display capabilities. + - 1: Use \b X-Window (X11) library. + - 2: Use \b GDI32 library. + + Remember to link your program against \b X11 or \b GDI32 libraries if you use CImgDisplay. + **/ + struct CImgDisplay { + cimg_ulong _timer, _fps_frames, _fps_timer; + unsigned int _width, _height, _normalization; + float _fps_fps, _min, _max; + bool _is_fullscreen; + char *_title; + unsigned int _window_width, _window_height, _button, *_keys, *_released_keys; + int _window_x, _window_y, _mouse_x, _mouse_y, _wheel; + bool _is_closed, _is_resized, _is_moved, _is_event, + _is_keyESC, _is_keyF1, _is_keyF2, _is_keyF3, _is_keyF4, _is_keyF5, _is_keyF6, _is_keyF7, + _is_keyF8, _is_keyF9, _is_keyF10, _is_keyF11, _is_keyF12, _is_keyPAUSE, _is_key1, _is_key2, + _is_key3, _is_key4, _is_key5, _is_key6, _is_key7, _is_key8, _is_key9, _is_key0, + _is_keyBACKSPACE, _is_keyINSERT, _is_keyHOME, _is_keyPAGEUP, _is_keyTAB, _is_keyQ, _is_keyW, _is_keyE, + _is_keyR, _is_keyT, _is_keyY, _is_keyU, _is_keyI, _is_keyO, _is_keyP, _is_keyDELETE, + _is_keyEND, _is_keyPAGEDOWN, _is_keyCAPSLOCK, _is_keyA, _is_keyS, _is_keyD, _is_keyF, _is_keyG, + _is_keyH, _is_keyJ, _is_keyK, _is_keyL, _is_keyENTER, _is_keySHIFTLEFT, _is_keyZ, _is_keyX, + _is_keyC, _is_keyV, _is_keyB, _is_keyN, _is_keyM, _is_keySHIFTRIGHT, _is_keyARROWUP, _is_keyCTRLLEFT, + _is_keyAPPLEFT, _is_keyALT, _is_keySPACE, _is_keyALTGR, _is_keyAPPRIGHT, _is_keyMENU, _is_keyCTRLRIGHT, + _is_keyARROWLEFT, _is_keyARROWDOWN, _is_keyARROWRIGHT, _is_keyPAD0, _is_keyPAD1, _is_keyPAD2, _is_keyPAD3, + _is_keyPAD4, _is_keyPAD5, _is_keyPAD6, _is_keyPAD7, _is_keyPAD8, _is_keyPAD9, _is_keyPADADD, _is_keyPADSUB, + _is_keyPADMUL, _is_keyPADDIV; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- + +#ifdef cimgdisplay_plugin +#include cimgdisplay_plugin +#endif +#ifdef cimgdisplay_plugin1 +#include cimgdisplay_plugin1 +#endif +#ifdef cimgdisplay_plugin2 +#include cimgdisplay_plugin2 +#endif +#ifdef cimgdisplay_plugin3 +#include cimgdisplay_plugin3 +#endif +#ifdef cimgdisplay_plugin4 +#include cimgdisplay_plugin4 +#endif +#ifdef cimgdisplay_plugin5 +#include cimgdisplay_plugin5 +#endif +#ifdef cimgdisplay_plugin6 +#include cimgdisplay_plugin6 +#endif +#ifdef cimgdisplay_plugin7 +#include cimgdisplay_plugin7 +#endif +#ifdef cimgdisplay_plugin8 +#include cimgdisplay_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + \note If the associated window is visible on the screen, it is closed by the call to the destructor. + **/ + ~CImgDisplay() { + assign(); + delete[] _keys; + delete[] _released_keys; + } + + //! Construct an empty display. + /** + \note Constructing an empty CImgDisplay instance does not make a window appearing on the screen, until + display of valid data is performed. + \par Example + \code + CImgDisplay disp; // Does actually nothing + ... + disp.display(img); // Construct new window and display image in it + \endcode + **/ + CImgDisplay(): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(); + } + + //! Construct a display with specified dimensions. + /** \param width Window width. + \param height Window height. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note A black background is initially displayed on the associated window. + **/ + CImgDisplay(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(width,height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image. + /** \param img Image used as a model to create the window. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note The pixels of the input image are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(img,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list. + /** \param list The images list to display. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note All images of the list, appended along the X-axis, are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(list,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of an existing one. + /** + \param disp Display instance to copy. + \note The pixel buffer of the input window is initially displayed on the associated window. + **/ + CImgDisplay(const CImgDisplay& disp): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(disp); + } + + //! Take a screenshot. + /** + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(CImg& img) { + return screenshot(0,0,cimg::type::max(),cimg::type::max(),img); + } + +#if cimg_display==0 + + static void _no_display_exception() { + throw CImgDisplayException("CImgDisplay(): No display available."); + } + + //! Destructor - Empty constructor \inplace. + /** + \note Replace the current instance by an empty display. + **/ + CImgDisplay& assign() { + return flush(); + } + + //! Construct a display with specified dimensions \inplace. + /** + **/ + CImgDisplay& assign(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + cimg::unused(width,height,title,normalization,is_fullscreen,is_closed); + _no_display_exception(); + return assign(); + } + + //! Construct a display from an image \inplace. + /** + **/ + template + CImgDisplay& assign(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list \inplace. + /** + **/ + template + CImgDisplay& assign(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of another one \inplace. + /** + **/ + CImgDisplay& assign(const CImgDisplay &disp) { + _no_display_exception(); + return assign(disp._width,disp._height); + } + +#endif + + //! Return a reference to an empty display. + /** + \note Can be useful for writing function prototypes where one of the argument (of type CImgDisplay&) + must have a default value. + \par Example + \code + void foo(CImgDisplay& disp=CImgDisplay::empty()); + \endcode + **/ + static CImgDisplay& empty() { + static CImgDisplay _empty; + return _empty.assign(); + } + + //! Return a reference to an empty display \const. + static const CImgDisplay& const_empty() { + static const CImgDisplay _empty; + return _empty; + } + +#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,256,-85,false), \ + CImgDisplay::_fitscreen(dx,dy,dz,256,-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 int + u = CImgDisplay::screen_width(), + v = CImgDisplay::screen_height(); + const float + mw = dmin<0?cimg::round(u*-dmin/100.f):(float)dmin, + mh = dmin<0?cimg::round(v*-dmin/100.f):(float)dmin, + Mw = dmax<0?cimg::round(u*-dmax/100.f):(float)dmax, + Mh = dmax<0?cimg::round(v*-dmax/100.f):(float)dmax; + float + w = (float)std::max(1U,dx), + h = (float)std::max(1U,dy); + if (dz>1) { w+=dz; h+=dz; } + if (wMw) { h = h*Mw/w; w = Mw; } + if (h>Mh) { w = w*Mh/h; h = Mh; } + if (wdisp = img is equivalent to disp.display(img). + **/ + template + CImgDisplay& operator=(const CImg& img) { + return display(img); + } + + //! Display list of images on associated window. + /** + \note disp = list is equivalent to disp.display(list). + **/ + template + CImgDisplay& operator=(const CImgList& list) { + return display(list); + } + + //! Construct a display as a copy of another one \inplace. + /** + \note Equivalent to assign(const CImgDisplay&). + **/ + CImgDisplay& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return \c false if display is empty, \c true otherwise. + /** + \note if (disp) { ... } is equivalent to if (!disp.is_empty()) { ... }. + **/ + operator bool() const { + return !is_empty(); + } + + //@} + //------------------------------------------ + // + //! \name Instance Checking + //@{ + //------------------------------------------ + + //! Return \c true if display is empty, \c false otherwise. + /** + **/ + bool is_empty() const { + return !(_width && _height); + } + + //! Return \c true if display is closed (i.e. not visible on the screen), \c false otherwise. + /** + \note + - When a user physically closes the associated window, the display is set to closed. + - A closed display is not destroyed. Its associated window can be show again on the screen using show(). + **/ + bool is_closed() const { + return _is_closed; + } + + //! Return \c true if associated window has been resized on the screen, \c false otherwise. + /** + **/ + bool is_resized() const { + return _is_resized; + } + + //! Return \c true if associated window has been moved on the screen, \c false otherwise. + /** + **/ + bool is_moved() const { + return _is_moved; + } + + //! Return \c true if any event has occured on the associated window, \c false otherwise. + /** + **/ + bool is_event() const { + return _is_event; + } + + //! Return \c true if current display is in fullscreen mode, \c false otherwise. + /** + **/ + bool is_fullscreen() const { + return _is_fullscreen; + } + + //! Return \c true if any key is being pressed on the associated window, \c false otherwise. + /** + \note The methods below do the same only for specific keys. + **/ + bool is_key() const { + return _is_keyESC || _is_keyF1 || _is_keyF2 || _is_keyF3 || + _is_keyF4 || _is_keyF5 || _is_keyF6 || _is_keyF7 || + _is_keyF8 || _is_keyF9 || _is_keyF10 || _is_keyF11 || + _is_keyF12 || _is_keyPAUSE || _is_key1 || _is_key2 || + _is_key3 || _is_key4 || _is_key5 || _is_key6 || + _is_key7 || _is_key8 || _is_key9 || _is_key0 || + _is_keyBACKSPACE || _is_keyINSERT || _is_keyHOME || + _is_keyPAGEUP || _is_keyTAB || _is_keyQ || _is_keyW || + _is_keyE || _is_keyR || _is_keyT || _is_keyY || + _is_keyU || _is_keyI || _is_keyO || _is_keyP || + _is_keyDELETE || _is_keyEND || _is_keyPAGEDOWN || + _is_keyCAPSLOCK || _is_keyA || _is_keyS || _is_keyD || + _is_keyF || _is_keyG || _is_keyH || _is_keyJ || + _is_keyK || _is_keyL || _is_keyENTER || + _is_keySHIFTLEFT || _is_keyZ || _is_keyX || _is_keyC || + _is_keyV || _is_keyB || _is_keyN || _is_keyM || + _is_keySHIFTRIGHT || _is_keyARROWUP || _is_keyCTRLLEFT || + _is_keyAPPLEFT || _is_keyALT || _is_keySPACE || _is_keyALTGR || + _is_keyAPPRIGHT || _is_keyMENU || _is_keyCTRLRIGHT || + _is_keyARROWLEFT || _is_keyARROWDOWN || _is_keyARROWRIGHT || + _is_keyPAD0 || _is_keyPAD1 || _is_keyPAD2 || + _is_keyPAD3 || _is_keyPAD4 || _is_keyPAD5 || + _is_keyPAD6 || _is_keyPAD7 || _is_keyPAD8 || + _is_keyPAD9 || _is_keyPADADD || _is_keyPADSUB || + _is_keyPADMUL || _is_keyPADDIV; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode Keycode to test. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.key(cimg::keyTAB)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool is_key(const unsigned int keycode) const { +#define _cimg_iskey_test(k) if (keycode==cimg::key##k) return _is_key##k; + _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3); + _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7); + _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11); + _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2); + _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6); + _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0); + _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME); + _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W); + _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y); + _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P); + _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN); + _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D); + _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J); + _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER); + _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C); + _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M); + _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT); + _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR); + _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT); + _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT); + _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2); + _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5); + _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8); + _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB); + _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV); + return false; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode C-string containing the keycode label of the key to test. + \note Use it when the key you want to test can be dynamically set by the user. + \par Example + \code + CImgDisplay disp(400,400); + const char *const keycode = "TAB"; + while (!disp.is_closed()) { + if (disp.is_key(keycode)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool& is_key(const char *const keycode) { + static bool f = false; + f = false; +#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(keycode,#k)) return _is_key##k; + _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3); + _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7); + _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11); + _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2); + _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6); + _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0); + _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME); + _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W); + _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y); + _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P); + _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN); + _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D); + _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J); + _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER); + _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C); + _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M); + _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT); + _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR); + _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT); + _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT); + _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2); + _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5); + _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8); + _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB); + _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV); + return f; + } + + //! Return \c true if specified key sequence has been typed on the associated window, \c false otherwise. + /** + \param keycodes_sequence Buffer of keycodes to test. + \param length Number of keys in the \c keycodes_sequence buffer. + \param remove_sequence Tells if the key sequence must be removed from the key history, if found. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + const unsigned int key_seq[] = { cimg::keyCTRLLEFT, cimg::keyD }; + while (!disp.is_closed()) { + if (disp.is_key_sequence(key_seq,2)) { ... } // Test for the 'CTRL+D' keyboard event + disp.wait(); + } + \endcode + **/ + bool is_key_sequence(const unsigned int *const keycodes_sequence, const unsigned int length, + const bool remove_sequence=false) { + if (keycodes_sequence && length) { + const unsigned int + *const ps_end = keycodes_sequence + length - 1, + *const pk_end = (unsigned int*)_keys + 1 + 128 - length, + k = *ps_end; + for (unsigned int *pk = (unsigned int*)_keys; pk[0,255]. + If the range of values of the data to display is different, a normalization may be required for displaying + the data in a correct way. The normalization type can be one of: + - \c 0: Value normalization is disabled. It is then assumed that all input data to be displayed by the + CImgDisplay instance have values in range [0,255]. + - \c 1: Value normalization is always performed (this is the default behavior). + Before displaying an input image, its values will be (virtually) stretched + in range [0,255], so that the contrast of the displayed pixels will be maximum. + Use this mode for images whose minimum and maximum values are not prescribed to known values + (e.g. float-valued images). + Note that when normalized versions of images are computed for display purposes, the actual values of these + images are not modified. + - \c 2: Value normalization is performed once (on the first image display), then the same normalization + coefficients are kept for next displayed frames. + - \c 3: Value normalization depends on the pixel type of the data to display. For integer pixel types, + the normalization is done regarding the minimum/maximum values of the type (no normalization occurs then + for unsigned char). + For float-valued pixel types, the normalization is done regarding the minimum/maximum value of the image + data instead. + **/ + unsigned int normalization() const { + return _normalization; + } + + //! Return title of the associated window as a C-string. + /** + \note Window title may be not visible, depending on the used window manager or if the current display is + in fullscreen mode. + **/ + const char *title() const { + return _title?_title:""; + } + + //! Return width of the associated window. + /** + \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual width of the associated window. + **/ + int window_width() const { + return (int)_window_width; + } + + //! Return height of the associated window. + /** + \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual height of the associated window. + **/ + int window_height() const { + return (int)_window_height; + } + + //! Return X-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_x() const { + return _window_x; + } + + //! Return Y-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_y() const { + return _window_y; + } + + //! Return X-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,width()-1]. + **/ + int mouse_x() const { + return _mouse_x; + } + + //! Return Y-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,height()-1]. + **/ + int mouse_y() const { + return _mouse_y; + } + + //! Return current state of the mouse buttons. + /** + \note Three mouse buttons can be managed. If one button is pressed, its corresponding bit in the returned + value is set: + - bit \c 0 (value \c 0x1): State of the left mouse button. + - bit \c 1 (value \c 0x2): State of the right mouse button. + - bit \c 2 (value \c 0x4): State of the middle mouse button. + + Several bits can be activated if more than one button are pressed at the same time. + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.button()&1) { // Left button clicked + ... + } + if (disp.button()&2) { // Right button clicked + ... + } + if (disp.button()&4) { // Middle button clicked + ... + } + disp.wait(); + } + \endcode + **/ + unsigned int button() const { + return _button; + } + + //! Return current state of the mouse wheel. + /** + \note + - The returned value can be positive or negative depending on whether the mouse wheel has been scrolled + forward or backward. + - Scrolling the wheel forward add \c 1 to the wheel value. + - Scrolling the wheel backward substract \c 1 to the wheel value. + - The returned value cumulates the number of forward of backward scrolls since the creation of the display, + or since the last reset of the wheel value (using set_wheel()). It is strongly recommended to quickly reset + the wheel counter when an action has been performed regarding the current wheel value. + Otherwise, the returned wheel value may be for instance \c 0 despite the fact that many scrolls have been done + (as many in forward as in backward directions). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.wheel()) { + int counter = disp.wheel(); // Read the state of the mouse wheel + ... // Do what you want with 'counter' + disp.set_wheel(); // Reset the wheel value to 0 + } + disp.wait(); + } + \endcode + **/ + int wheel() const { + return _wheel; + } + + //! Return one entry from the pressed keys history. + /** + \param pos Indice to read from the pressed keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a pressed key or \c 0 for a released key. + \note + - Each CImgDisplay stores a history of the pressed keys in a buffer of size \c 128. When a new key is pressed, + its keycode is stored in the pressed keys history. When a key is released, \c 0 is put instead. + This means that up to the 64 last pressed keys may be read from the pressed keys history. + When a new value is stored, the pressed keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int key(const unsigned int pos=0) const { + return pos<128?_keys[pos]:0; + } + + //! Return one entry from the released keys history. + /** + \param pos Indice to read from the released keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a released key or \c 0 for a pressed key. + \note + - Each CImgDisplay stores a history of the released keys in a buffer of size \c 128. When a new key is released, + its keycode is stored in the pressed keys history. When a key is pressed, \c 0 is put instead. + This means that up to the 64 last released keys may be read from the released keys history. + When a new value is stored, the released keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int released_key(const unsigned int pos=0) const { + return pos<128?_released_keys[pos]:0; + } + + //! Return keycode corresponding to the specified string. + /** + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + const unsigned int keyTAB = CImgDisplay::keycode("TAB"); // Return cimg::keyTAB + \endcode + **/ + static unsigned int keycode(const char *const keycode) { +#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k; + _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3); + _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7); + _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11); + _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2); + _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6); + _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0); + _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME); + _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W); + _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y); + _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P); + _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN); + _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D); + _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J); + _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER); + _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C); + _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M); + _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT); + _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR); + _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT); + _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT); + _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2); + _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5); + _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8); + _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB); + _cimg_keycode(PADMUL); _cimg_keycode(PADDIV); + return 0; + } + + //! Return the current refresh rate, in frames per second. + /** + \note Returns a significant value when the current instance is used to display successive frames. + It measures the delay between successive calls to frames_per_second(). + **/ + float frames_per_second() { + if (!_fps_timer) _fps_timer = cimg::time(); + const float delta = (cimg::time() - _fps_timer)/1000.f; + ++_fps_frames; + if (delta>=1) { + _fps_fps = _fps_frames/delta; + _fps_frames = 0; + _fps_timer = cimg::time(); + } + 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; + } + + //@} + //--------------------------------------- + // + //! \name Window Manipulation + //@{ + //--------------------------------------- + +#if cimg_display==0 + + //! Display image on associated window. + /** + \param img Input image to display. + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImg& img) { + return assign(img); + } + +#endif + + //! Display list of images on associated window. + /** + \param list List of images to display. + \param axis Axis used to append the images along, for the visualization (can be \c x, \c y, \c z or \c c). + \param align Relative position of aligned images when displaying lists with images of different sizes + (\c 0 for upper-left, \c 0.5 for centering and \c 1 for lower-right). + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImgList& list, const char axis='x', const float align=0) { + if (list._width==1) { + const CImg& img = list[0]; + if (img._depth==1 && (img._spectrum==1 || img._spectrum>=3) && _normalization!=1) return display(img); + } + CImgList::ucharT> visu(list._width); + 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]); + dims = std::max(dims,visu[l]._spectrum); + } + cimglist_for(list,l) if (visu[l]._spectrumimg.width() become equal, as well as height() and + img.height(). + - The associated window is also resized to specified dimensions. + **/ + template + CImgDisplay& resize(const CImg& img, const bool force_redraw=true) { + return resize(img._width,img._height,force_redraw); + } + + //! Resize display to the size of another CImgDisplay instance. + /** + \param disp Input display to take size from. + \param force_redraw Tells if the previous window content must be resized and updated as well. + \note + - Calling this method ensures that width() and disp.width() become equal, as well as height() and + disp.height(). + - The associated window is also resized to specified dimensions. + **/ + CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) { + return resize(disp.width(),disp.height(),force_redraw); + } + + // [internal] Render pixel buffer with size (wd,hd) from source buffer of size (ws,hs). + template + 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) { + typedef typename cimg::last::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; xstd::printf(). + \warning As the first argument is a format string, it is highly recommended to write + \code + disp.set_title("%s",window_title); + \endcode + instead of + \code + disp.set_title(window_title); + \endcode + if \c window_title can be arbitrary, to prevent nasty memory access. + **/ + CImgDisplay& set_title(const char *const format, ...) { + return assign(0,0,format); + } + +#endif + + //! Enable or disable fullscreen mode. + /** + \param is_fullscreen Tells is the fullscreen mode must be activated or not. + \param force_redraw Tells if the previous window content must be displayed as well. + \note + - When the fullscreen mode is enabled, the associated window fills the entire screen but the size of the + current display is not modified. + - The screen resolution may be switched to fit the associated window size and ensure it appears the largest + as possible. + For X-Window (X11) users, the configuration flag \c cimg_use_xrandr has to be set to allow the screen + resolution change (requires the X11 extensions to be enabled). + **/ + CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool force_redraw=true) { + if (is_empty() || _is_fullscreen==is_fullscreen) return *this; + return toggle_fullscreen(force_redraw); + } + +#if cimg_display==0 + + //! Toggle fullscreen mode. + /** + \param force_redraw Tells if the previous window content must be displayed as well. + \note Enable fullscreen mode if it was not enabled, and disable it otherwise. + **/ + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + return assign(_width,_height,0,3,force_redraw); + } + + //! Show mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& show_mouse() { + return assign(); + } + + //! Hide mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& hide_mouse() { + return assign(); + } + + //! Move mouse pointer to a specified location. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& set_mouse(const int pos_x, const int pos_y) { + return assign(pos_x,pos_y); + } + +#endif + + //! Simulate a mouse button release event. + /** + \note All mouse buttons are considered released at the same time. + **/ + CImgDisplay& set_button() { + _button = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a mouse button press or release event. + /** + \param button Buttons event code, where each button is associated to a single bit. + \param is_pressed Tells if the mouse button is considered as pressed or released. + **/ + CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) { + const unsigned int buttoncode = button==1U?1U:button==2U?2U:button==3U?4U:0U; + if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode; + _is_event = buttoncode?true:false; + if (buttoncode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all mouse wheel events. + /** + \note Make wheel() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_wheel() { + _wheel = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a wheel event. + /** + \param amplitude Amplitude of the wheel scrolling to simulate. + \note Make wheel() to return \c amplitude, if called afterwards. + **/ + CImgDisplay& set_wheel(const int amplitude) { + _wheel+=amplitude; + _is_event = amplitude?true:false; + if (amplitude) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all key events. + /** + \note Make key() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_key() { + std::memset((void*)_keys,0,128*sizeof(unsigned int)); + std::memset((void*)_released_keys,0,128*sizeof(unsigned int)); + _is_keyESC = _is_keyF1 = _is_keyF2 = _is_keyF3 = _is_keyF4 = _is_keyF5 = _is_keyF6 = _is_keyF7 = _is_keyF8 = + _is_keyF9 = _is_keyF10 = _is_keyF11 = _is_keyF12 = _is_keyPAUSE = _is_key1 = _is_key2 = _is_key3 = _is_key4 = + _is_key5 = _is_key6 = _is_key7 = _is_key8 = _is_key9 = _is_key0 = _is_keyBACKSPACE = _is_keyINSERT = + _is_keyHOME = _is_keyPAGEUP = _is_keyTAB = _is_keyQ = _is_keyW = _is_keyE = _is_keyR = _is_keyT = _is_keyY = + _is_keyU = _is_keyI = _is_keyO = _is_keyP = _is_keyDELETE = _is_keyEND = _is_keyPAGEDOWN = _is_keyCAPSLOCK = + _is_keyA = _is_keyS = _is_keyD = _is_keyF = _is_keyG = _is_keyH = _is_keyJ = _is_keyK = _is_keyL = + _is_keyENTER = _is_keySHIFTLEFT = _is_keyZ = _is_keyX = _is_keyC = _is_keyV = _is_keyB = _is_keyN = + _is_keyM = _is_keySHIFTRIGHT = _is_keyARROWUP = _is_keyCTRLLEFT = _is_keyAPPLEFT = _is_keyALT = _is_keySPACE = + _is_keyALTGR = _is_keyAPPRIGHT = _is_keyMENU = _is_keyCTRLRIGHT = _is_keyARROWLEFT = _is_keyARROWDOWN = + _is_keyARROWRIGHT = _is_keyPAD0 = _is_keyPAD1 = _is_keyPAD2 = _is_keyPAD3 = _is_keyPAD4 = _is_keyPAD5 = + _is_keyPAD6 = _is_keyPAD7 = _is_keyPAD8 = _is_keyPAD9 = _is_keyPADADD = _is_keyPADSUB = _is_keyPADMUL = + _is_keyPADDIV = false; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a keyboard press/release event. + /** + \param keycode Keycode of the associated key. + \param is_pressed Tells if the key is considered as pressed or released. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + CImgDisplay& set_key(const unsigned int keycode, const bool is_pressed=true) { +#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = is_pressed; + _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3); + _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7); + _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11); + _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2); + _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6); + _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0); + _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME); + _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W); + _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y); + _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P); + _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN); + _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D); + _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J); + _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER); + _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C); + _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M); + _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT); + _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR); + _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT); + _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT); + _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2); + _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5); + _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8); + _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB); + _cimg_set_key(PADMUL); _cimg_set_key(PADDIV); + if (is_pressed) { + if (*_keys) + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = keycode; + if (*_released_keys) { + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = 0; + } + } else { + if (*_keys) { + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = 0; + } + if (*_released_keys) + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = keycode; + } + _is_event = keycode?true:false; + if (keycode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all display events. + /** + \note Remove all passed events from the current display. + **/ + CImgDisplay& flush() { + set_key().set_button().set_wheel(); + _is_resized = _is_moved = _is_event = false; + _fps_timer = _fps_frames = _timer = 0; + _fps_fps = 0; + return *this; + } + + //! Wait for any user event occuring on the current display. + CImgDisplay& wait() { + wait(*this); + return *this; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \note Similar to cimg::wait(). + **/ + CImgDisplay& wait(const unsigned int milliseconds) { + cimg::wait(milliseconds,&_timer); + return *this; + } + + //! Wait for any event occuring on the display \c disp1. + static void wait(CImgDisplay& disp1) { + disp1._is_event = false; + while (!disp1._is_closed && !disp1._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1 or \c disp2. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2) { + disp1._is_event = disp2._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed) && + !disp1._is_event && !disp2._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) { + disp1._is_event = disp2._is_event = disp3._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4 or \c disp5. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, + CImgDisplay& disp5) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event) + wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp6. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp7. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp8. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp9. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp10. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9, + CImgDisplay& disp10) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = disp10._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event) + wait_all(); + } + +#if cimg_display==0 + + //! Wait for any window event occuring in any opened CImgDisplay. + static void wait_all() { + return _no_display_exception(); + } + + //! Render image into internal display buffer. + /** + \param img Input image data to render. + \note + - Convert image data representation into the internal display buffer (architecture-dependent structure). + - The content of the associated window is not modified, until paint() is called. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + template + CImgDisplay& render(const CImg& img) { + return assign(img); + } + + //! Paint internal display buffer on associated window. + /** + \note + - Update the content of the associated window with the internal display buffer, e.g. after a render() call. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + CImgDisplay& paint() { + return assign(); + } + + + //! Take a snapshot of the current screen content. + /** + \param x0 X-coordinate of the upper left corner. + \param y0 Y-coordinate of the upper left corner. + \param x1 X-coordinate of the lower right corner. + \param y1 Y-coordinate of the lower right corner. + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + cimg::unused(x0,y0,x1,y1,&img); + _no_display_exception(); + } + + //! Take a snapshot of the associated window content. + /** + \param[out] img Output snapshot. Can be empty on input. + **/ + template + const CImgDisplay& snapshot(CImg& img) const { + cimg::unused(img); + _no_display_exception(); + return *this; + } +#endif + + // X11-based implementation + //-------------------------- +#if cimg_display==1 + + Atom _wm_window_atom, _wm_protocol_atom; + Window _window, _background_window; + Colormap _colormap; + XImage *_image; + void *_data; +#ifdef cimg_use_xshm + XShmSegmentInfo *_shminfo; +#endif + + static int screen_width() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_width(): Failed to open X11 display."); + res = DisplayWidth(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width; + else res = DisplayWidth(dpy,DefaultScreen(dpy)); +#else + res = DisplayWidth(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static int screen_height() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_height(): Failed to open X11 display."); + res = DisplayHeight(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height; + else res = DisplayHeight(dpy,DefaultScreen(dpy)); +#else + res = DisplayHeight(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static void wait_all() { + if (!cimg::X11_attr().display) return; + pthread_mutex_lock(&cimg::X11_attr().wait_event_mutex); + pthread_cond_wait(&cimg::X11_attr().wait_event,&cimg::X11_attr().wait_event_mutex); + pthread_mutex_unlock(&cimg::X11_attr().wait_event_mutex); + } + + void _handle_events(const XEvent *const pevent) { + Display *const dpy = cimg::X11_attr().display; + XEvent event = *pevent; + switch (event.type) { + case ClientMessage : { + if ((int)event.xclient.message_type==(int)_wm_protocol_atom && + (int)event.xclient.data.l[0]==(int)_wm_window_atom) { + XUnmapWindow(cimg::X11_attr().display,_window); + _is_closed = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case ConfigureNotify : { + while (XCheckWindowEvent(dpy,_window,StructureNotifyMask,&event)) {} + const unsigned int nw = event.xconfigure.width, nh = event.xconfigure.height; + const int nx = event.xconfigure.x, ny = event.xconfigure.y; + if (nw && nh && (nw!=_window_width || nh!=_window_height)) { + _window_width = nw; _window_height = nh; _mouse_x = _mouse_y = -1; + XResizeWindow(dpy,_window,_window_width,_window_height); + _is_resized = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + if (nx!=_window_x || ny!=_window_y) { + _window_x = nx; _window_y = ny; _is_moved = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case Expose : { + while (XCheckWindowEvent(dpy,_window,ExposureMask,&event)) {} + _paint(false); + if (_is_fullscreen) { + XWindowAttributes attr; + XGetWindowAttributes(dpy,_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XSetInputFocus(dpy,_window,RevertToParent,CurrentTime); + } + } break; + case ButtonPress : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1); break; + case 3 : set_button(2); break; + case 2 : set_button(3); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonPressMask,&event)); + } break; + case ButtonRelease : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1,false); break; + case 3 : set_button(2,false); break; + case 2 : set_button(3,false); break; + case 4 : set_wheel(1); break; + case 5 : set_wheel(-1); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonReleaseMask,&event)); + } break; + case KeyPress : { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,true); + } break; + case KeyRelease : { + char keys_return[32]; // Check that the key has been physically unpressed + XQueryKeymap(dpy,keys_return); + const unsigned int kc = event.xkey.keycode, kc1 = kc/8, kc2 = kc%8; + const bool is_key_pressed = kc1>=32?false:(keys_return[kc1]>>kc2)&1; + if (!is_key_pressed) { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,false); + } + } break; + case EnterNotify: { + while (XCheckWindowEvent(dpy,_window,EnterWindowMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + } break; + case LeaveNotify : { + while (XCheckWindowEvent(dpy,_window,LeaveWindowMask,&event)) {} + _mouse_x = _mouse_y = -1; _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + case MotionNotify : { + while (XCheckWindowEvent(dpy,_window,PointerMotionMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + } + } + + static void* _events_thread(void *arg) { // Thread to manage events for all opened display windows + Display *const dpy = cimg::X11_attr().display; + XEvent event; + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0); + if (!arg) for ( ; ; ) { + cimg_lock_display(); + bool event_flag = XCheckTypedEvent(dpy,ClientMessage,&event); + if (!event_flag) event_flag = XCheckMaskEvent(dpy, + ExposureMask | StructureNotifyMask | ButtonPressMask | + KeyPressMask | PointerMotionMask | EnterWindowMask | + LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask,&event); + if (event_flag) + for (unsigned int i = 0; i_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window) + cimg::X11_attr().wins[i]->_handle_events(&event); + cimg_unlock_display(); + pthread_testcancel(); + cimg::sleep(8); + } + return 0; + } + + void _set_colormap(Colormap& _colormap, const unsigned int dim) { + XColor *const colormap = new XColor[256]; + switch (dim) { + case 1 : { // colormap for greyscale images + for (unsigned int index = 0; index<256; ++index) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].green = colormap[index].blue = (unsigned short)(index<<8); + colormap[index].flags = DoRed | DoGreen | DoBlue; + } + } break; + case 2 : { // colormap for RG images + for (unsigned int index = 0, r = 8; r<256; r+=16) + for (unsigned int g = 8; g<256; g+=16) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].blue = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } break; + default : { // colormap for RGB images + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap[index].pixel = index; + colormap[index].red = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index].blue = (unsigned short)(b<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } + } + XStoreColors(cimg::X11_attr().display,_colormap,colormap,256); + delete[] colormap; + } + + void _map_window() { + Display *const dpy = cimg::X11_attr().display; + bool is_exposed = false, is_mapped = false; + XWindowAttributes attr; + XEvent event; + XMapRaised(dpy,_window); + do { // Wait for the window to be mapped + XWindowEvent(dpy,_window,StructureNotifyMask | ExposureMask,&event); + switch (event.type) { + case MapNotify : is_mapped = true; break; + case Expose : is_exposed = true; break; + } + } while (!is_exposed || !is_mapped); + do { // Wait for the window to be visible + XGetWindowAttributes(dpy,_window,&attr); + if (attr.map_state!=IsViewable) { XSync(dpy,0); cimg::sleep(10); } + } while (attr.map_state!=IsViewable); + _window_x = attr.x; + _window_y = attr.y; + } + + void _paint(const bool wait_expose=true) { + if (_is_closed || !_image) return; + Display *const dpy = cimg::X11_attr().display; + if (wait_expose) { // Send an expose event sticked to display window to force repaint + XEvent event; + event.xexpose.type = Expose; + event.xexpose.serial = 0; + event.xexpose.send_event = 1; + event.xexpose.display = dpy; + event.xexpose.window = _window; + event.xexpose.x = 0; + event.xexpose.y = 0; + event.xexpose.width = width(); + event.xexpose.height = height(); + event.xexpose.count = 0; + XSendEvent(dpy,_window,0,0,&event); + } else { // Repaint directly (may be called from the expose event) + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height,1); + else XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#else + XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#endif + } + } + + template + void _resize(T pixel_type, const unsigned int ndimx, const unsigned int ndimy, const bool force_redraw) { + Display *const dpy = cimg::X11_attr().display; + cimg::unused(pixel_type); + +#ifdef cimg_use_xshm + if (_shminfo) { + XShmSegmentInfo *const nshminfo = new XShmSegmentInfo; + XImage *const nimage = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy); + if (!nimage) { delete nshminfo; return; } + else { + nshminfo->shmid = shmget(IPC_PRIVATE,ndimx*ndimy*sizeof(T),IPC_CREAT | 0777); + if (nshminfo->shmid==-1) { XDestroyImage(nimage); delete nshminfo; return; } + else { + nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0); + if (nshminfo->shmaddr==(char*)-1) { + shmctl(nshminfo->shmid,IPC_RMID,0); XDestroyImage(nimage); delete nshminfo; return; + } else { + nshminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,nshminfo); + XFlush(dpy); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(nshminfo->shmaddr); + shmctl(nshminfo->shmid,IPC_RMID,0); + XDestroyImage(nimage); + delete nshminfo; + return; + } else { + T *const ndata = (T*)nimage->data; + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + XShmDetach(dpy,_shminfo); + XDestroyImage(_image); + shmdt(_shminfo->shmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = nshminfo; + _image = nimage; + _data = (void*)ndata; + } + } + } + } + } else +#endif + { + T *ndata = (T*)std::malloc(ndimx*ndimy*sizeof(T)); + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + _data = (void*)ndata; + XDestroyImage(_image); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,ndimx,ndimy,8,0); + } + } + + void _init_fullscreen() { + if (!_is_fullscreen || _is_closed) return; + Display *const dpy = cimg::X11_attr().display; + _background_window = 0; + +#ifdef cimg_use_xrandr + int foo; + if (XRRQueryExtension(dpy,&foo,&foo)) { + XRRRotations(dpy,DefaultScreen(dpy),&cimg::X11_attr().curr_rotation); + if (!cimg::X11_attr().resolutions) { + cimg::X11_attr().resolutions = XRRSizes(dpy,DefaultScreen(dpy),&foo); + cimg::X11_attr().nb_resolutions = (unsigned int)foo; + } + if (cimg::X11_attr().resolutions) { + cimg::X11_attr().curr_resolution = 0; + for (unsigned int i = 0; i=_width && nh>=_height && + nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) && + nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height)) + cimg::X11_attr().curr_resolution = i; + } + if (cimg::X11_attr().curr_resolution>0) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy), + cimg::X11_attr().curr_resolution,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + } + } + } + if (!cimg::X11_attr().resolutions) + cimg::warn(_cimgdisplay_instance + "init_fullscreen(): Xrandr extension not supported by the X server.", + cimgdisplay_instance); +#endif + + const unsigned int sx = screen_width(), sy = screen_height(); + if (sx==_width && sy==_height) return; + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _background_window = XCreateWindow(dpy,DefaultRootWindow(dpy),0,0,sx,sy,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + const cimg_ulong buf_size = (cimg_ulong)sx*sy*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + void *background_data = std::malloc(buf_size); + std::memset(background_data,0,buf_size); + XImage *background_image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)background_data,sx,sy,8,0); + XEvent event; + XSelectInput(dpy,_background_window,StructureNotifyMask); + XMapRaised(dpy,_background_window); + do XWindowEvent(dpy,_background_window,StructureNotifyMask,&event); + while (event.type!=MapNotify); + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy,0); + else XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#else + XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#endif + XWindowAttributes attr; + XGetWindowAttributes(dpy,_background_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XDestroyImage(background_image); + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + Display *const dpy = cimg::X11_attr().display; + XUngrabKeyboard(dpy,CurrentTime); +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),0,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + cimg::X11_attr().curr_resolution = 0; + } +#endif + if (_background_window) XDestroyWindow(dpy,_background_window); + _background_window = 0; + _is_fullscreen = false; + } + + static int _assign_xshm(Display *dpy, XErrorEvent *error) { + cimg::unused(dpy,error); + cimg::X11_attr().is_shm_enabled = false; + return 0; + } + + void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + cimg::mutex(14); + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous display window if existing + if (!is_empty()) assign(); + + // Open X11 display and retrieve graphical properties. + Display* &dpy = cimg::X11_attr().display; + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Failed to open X11 display.", + cimgdisplay_instance); + + cimg::X11_attr().nb_bits = DefaultDepth(dpy,DefaultScreen(dpy)); + if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 && + cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Invalid %u bits screen mode detected " + "(only 8, 16, 24 and 32 bits modes are managed).", + cimgdisplay_instance, + cimg::X11_attr().nb_bits); + XVisualInfo vtemplate; + vtemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,DefaultScreen(dpy))); + int nb_visuals; + XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals); + if (vinfo && vinfo->red_maskblue_mask) cimg::X11_attr().is_blue_first = true; + cimg::X11_attr().byte_order = ImageByteOrder(dpy); + XFree(vinfo); + + cimg_lock_display(); + cimg::X11_attr().events_thread = new pthread_t; + pthread_create(cimg::X11_attr().events_thread,0,_events_thread,0); + } else cimg_lock_display(); + + // Set display variables. + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _title = tmp_title; + flush(); + + // Create X11 window (and LUT, if 8bits display) + if (_is_fullscreen) { + if (!_is_closed) _init_fullscreen(); + const unsigned int sx = screen_width(), sy = screen_height(); + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx - _width)/2,(sy - _height)/2,_width,_height,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + } else + _window = XCreateSimpleWindow(dpy,DefaultRootWindow(dpy),0,0,_width,_height,0,0L,0L); + + XSelectInput(dpy,_window, + ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask | + EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask); + + XStoreName(dpy,_window,_title?_title:" "); + if (cimg::X11_attr().nb_bits==8) { + _colormap = XCreateColormap(dpy,_window,DefaultVisual(dpy,DefaultScreen(dpy)),AllocAll); + _set_colormap(_colormap,3); + XSetWindowColormap(dpy,_window,_colormap); + } + + static const char *const _window_class = cimg_appname; + XClassHint *const window_class = XAllocClassHint(); + window_class->res_name = (char*)_window_class; + window_class->res_class = (char*)_window_class; + XSetClassHint(dpy,_window,window_class); + XFree(window_class); + + _window_width = _width; + _window_height = _height; + + // Create XImage +#ifdef cimg_use_xshm + _shminfo = 0; + if (XShmQueryExtension(dpy)) { + _shminfo = new XShmSegmentInfo; + _image = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,_shminfo,_width,_height); + if (!_image) { delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmid = shmget(IPC_PRIVATE,_image->bytes_per_line*_image->height,IPC_CREAT|0777); + if (_shminfo->shmid==-1) { XDestroyImage(_image); delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmaddr = _image->data = (char*)(_data = shmat(_shminfo->shmid,0,0)); + if (_shminfo->shmaddr==(char*)-1) { + shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); delete _shminfo; _shminfo = 0; + } else { + _shminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,_shminfo); + XSync(dpy,0); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(_shminfo->shmaddr); shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); + delete _shminfo; _shminfo = 0; + } + } + } + } + } + if (!_shminfo) +#endif + { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + _data = std::malloc(buf_size); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)_data,_width,_height,8,0); + } + + _wm_window_atom = XInternAtom(dpy,"WM_DELETE_WINDOW",0); + _wm_protocol_atom = XInternAtom(dpy,"WM_PROTOCOLS",0); + XSetWMProtocols(dpy,_window,&_wm_window_atom,1); + + if (_is_fullscreen) XGrabKeyboard(dpy,_window,1,GrabModeAsync,GrabModeAsync,CurrentTime); + cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this; + if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type::min(); } + cimg_unlock_display(); + cimg::mutex(14,0); + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + + // Remove display window from event thread list. + unsigned int i; + for (i = 0; ishmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = 0; + } else +#endif + XDestroyImage(_image); + _data = 0; _image = 0; + if (cimg::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap); + _colormap = 0; + XSync(dpy,0); + + // Reset display variables. + delete[] _title; + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + + cimg_unlock_display(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + (cimg::X11_attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))* + (size_t)_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + cimg::X11_attr().nb_bits==16?sizeof(unsigned short): + sizeof(unsigned int))*(size_t)_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + Display *const dpy = cimg::X11_attr().display; + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*width()/100), + tmpdimy = (nheight>0)?nheight:(-nheight*height()/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + show(); + cimg_lock_display(); + if (_window_width!=dimx || _window_height!=dimy) { + XWindowAttributes attr; + for (unsigned int i = 0; i<10; ++i) { + XResizeWindow(dpy,_window,dimx,dimy); + XGetWindowAttributes(dpy,_window,&attr); + if (attr.width==(int)dimx && attr.height==(int)dimy) break; + cimg::wait(5,&_timer); + } + } + if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) { + case 8 : { unsigned char pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + case 16 : { unsigned short pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + default : { unsigned int pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } + } + _window_width = _width = dimx; _window_height = _height = dimy; + cimg_unlock_display(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - _width)/2,(screen_height() - _height)/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height* + (cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4)); + void *image_data = std::malloc(buf_size); + std::memcpy(image_data,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,image_data,buf_size); + std::free(image_data); + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + cimg_lock_display(); + if (_is_fullscreen) _init_fullscreen(); + _map_window(); + _is_closed = false; + cimg_unlock_display(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (_is_fullscreen) _desinit_fullscreen(); + XUnmapWindow(dpy,_window); + _window_x = _window_y = -1; + _is_closed = true; + cimg_unlock_display(); + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + show(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XMoveWindow(dpy,_window,posx,posy); + _window_x = posx; _window_y = posy; + cimg_unlock_display(); + } + _is_moved = false; + return paint(); + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XUndefineCursor(dpy,_window); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + static const char pix_data[8] = { 0 }; + XColor col; + col.red = col.green = col.blue = 0; + Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8); + Cursor cur = XCreatePixmapCursor(dpy,pix,pix,&col,&col,0,0); + XFreePixmap(dpy,pix); + XDefineCursor(dpy,_window,cur); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XWarpPointer(dpy,0L,_window,0,0,0,0,posx,posy); + _mouse_x = posx; _mouse_y = posy; + _is_moved = false; + XSync(dpy,0); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XStoreName(dpy,_window,tmp); + cimg_unlock_display(); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(false); + } + + CImgDisplay& paint(const bool wait_expose=true) { + if (is_empty()) return *this; + cimg_lock_display(); + _paint(wait_expose); + cimg_unlock_display(); + return *this; + } + + template + CImgDisplay& render(const CImg& img, const bool flag8=false) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + if (cimg::X11_attr().nb_bits==8 && (img._width!=_width || img._height!=_height)) + return render(img.get_resize(_width,_height,1,-100,1)); + if (cimg::X11_attr().nb_bits==8 && !flag8 && img._spectrum==3) { + static const CImg::ucharT> default_colormap = CImg::ucharT>::default_LUT256(); + return render(img.get_index(default_colormap,1,false)); + } + + const T + *data1 = img._data, + *data2 = (img._spectrum>1)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>2)?img.data(0,0,0,2):data1; + + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + cimg_lock_display(); + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, no normalization + _set_colormap(_colormap,img._spectrum); + unsigned char + *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height], + *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + (*ptrd++) = (unsigned char)*(data1++); + break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, no normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (G>>1); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (G<<5) | (G>>1); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, no normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | + (unsigned char)*(data3++); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | + ((unsigned char)*(data1++)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = 0; + ptrd[3] = 0; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = 0; + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = (unsigned char)*(data2++); + ptrd[3] = (unsigned char)*(data3++); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)*(data3++); + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, with normalization + _set_colormap(_colormap,img._spectrum); + unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char R = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = R; + } break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, with normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (val>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (G<<5) | (val>>3); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, with normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<24) | (val<<16) | (val<<8); + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8) | + (unsigned char)((*(data3++) - _min)*mm); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data3++) - _min)*mm)<<24) | + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = 0; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = val; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = val; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[3] = (unsigned char)((*(data3++) - _min)*mm); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)((*(data3++) - _min)*mm); + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } + cimg_unlock_display(); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + Display *dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to open X11 display."); + } + Window root = DefaultRootWindow(dpy); + XWindowAttributes gwa; + XGetWindowAttributes(dpy,root,&gwa); + const int width = gwa.width, height = gwa.height; + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + + XImage *image = 0; + if (_x1>=0 && _x0=0 && _y0red_mask, + green_mask = image->green_mask, + blue_mask = image->blue_mask; + img.assign(image->width,image->height,1,3); + T *pR = img.data(0,0,0,0), *pG = img.data(0,0,0,1), *pB = img.data(0,0,0,2); + cimg_forXY(img,x,y) { + const unsigned long pixel = XGetPixel(image,x,y); + *(pR++) = (T)((pixel & red_mask)>>16); + *(pG++) = (T)((pixel & green_mask)>>8); + *(pB++) = (T)(pixel & blue_mask); + } + XDestroyImage(image); + } + } + if (!cimg::X11_attr().display) XCloseDisplay(dpy); + cimg_unlock_display(); + if (img.is_empty()) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to take screenshot " + "with coordinates (%d,%d)-(%d,%d).", + x0,y0,x1,y1); + } + + template + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned char *ptrs = (unsigned char*)_data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + switch (cimg::X11_attr().nb_bits) { + case 8 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = *(ptrs++); + *(data1++) = (T)(val&0xe0); + *(data2++) = (T)((val&0x1c)<<3); + *(data3++) = (T)(val<<6); + } + } break; + case 16 : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val0&0xf8); + *(data2++) = (T)((val0<<5) | ((val1&0xe0)>>5)); + *(data3++) = (T)(val1<<3); + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned short + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val1&0xf8); + *(data2++) = (T)((val1<<5) | ((val0&0xe0)>>5)); + *(data3++) = (T)(val0<<3); + } + } break; + default : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ++ptrs; + *(data1++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data3++) = (T)ptrs[2]; + ptrs+=3; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + *(data3++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data1++) = (T)ptrs[2]; + ptrs+=3; + ++ptrs; + } + } + } + return *this; + } + + // Windows-based implementation. + //------------------------------- +#elif cimg_display==2 + + bool _is_mouse_tracked, _is_cursor_visible; + HANDLE _thread, _is_created, _mutex; + HWND _window, _background_window; + CLIENTCREATESTRUCT _ccs; + unsigned int *_data; + DEVMODE _curr_mode; + BITMAPINFO _bmi; + HDC _hdc; + + static int screen_width() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsWidth; + } + + static int screen_height() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsHeight; + } + + static void wait_all() { + WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE); + } + + static LRESULT APIENTRY _handle_events(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) { +#ifdef _WIN64 + CImgDisplay *const disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA); +#else + CImgDisplay *const disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA); +#endif + MSG st_msg; + switch (msg) { + case WM_CLOSE : + disp->_mouse_x = disp->_mouse_y = -1; + disp->_window_x = disp->_window_y = 0; + disp->set_button().set_key(0).set_key(0,false)._is_closed = true; + ReleaseMutex(disp->_mutex); + ShowWindow(disp->_window,SW_HIDE); + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + return 0; + case WM_SIZE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam); + if (nw && nh && (nw!=disp->_width || nh!=disp->_height)) { + disp->_window_width = nw; + disp->_window_height = nh; + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_resized = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_MOVE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam)); + if (nx!=disp->_window_x || ny!=disp->_window_y) { + disp->_window_x = nx; + disp->_window_y = ny; + disp->_is_moved = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_PAINT : + disp->paint(); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + break; + case WM_ERASEBKGND : + // return 0; + break; + case WM_KEYDOWN : + disp->set_key((unsigned int)wParam); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_KEYUP : + disp->set_key((unsigned int)wParam,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MOUSEMOVE : { + while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {} + disp->_mouse_x = LOWORD(lParam); + disp->_mouse_y = HIWORD(lParam); +#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT) + if (!disp->_is_mouse_tracked) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = disp->_window; + if (TrackMouseEvent(&tme)) disp->_is_mouse_tracked = true; + } +#endif + if (disp->_mouse_x<0 || disp->_mouse_y<0 || disp->_mouse_x>=disp->width() || disp->_mouse_y>=disp->height()) + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + } break; + case WM_MOUSELEAVE : { + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_mouse_tracked = false; + cimg_lock_display(); + while (ShowCursor(TRUE)<0) {} + cimg_unlock_display(); + } break; + case WM_LBUTTONDOWN : + disp->set_button(1); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONDOWN : + disp->set_button(2); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONDOWN : + disp->set_button(3); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_LBUTTONUP : + disp->set_button(1,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONUP : + disp->set_button(2,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONUP : + disp->set_button(3,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case 0x020A : // WM_MOUSEWHEEL: + disp->set_wheel((int)((short)HIWORD(wParam))/120); + SetEvent(cimg::Win32_attr().wait_event); + } + return DefWindowProc(window,msg,wParam,lParam); + } + + static DWORD WINAPI _events_thread(void* arg) { + CImgDisplay *const disp = (CImgDisplay*)(((void**)arg)[0]); + const char *const title = (const char*)(((void**)arg)[1]); + MSG msg; + delete[] (void**)arg; + disp->_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + disp->_bmi.bmiHeader.biWidth = disp->width(); + disp->_bmi.bmiHeader.biHeight = -disp->height(); + disp->_bmi.bmiHeader.biPlanes = 1; + disp->_bmi.bmiHeader.biBitCount = 32; + disp->_bmi.bmiHeader.biCompression = BI_RGB; + disp->_bmi.bmiHeader.biSizeImage = 0; + disp->_bmi.bmiHeader.biXPelsPerMeter = 1; + disp->_bmi.bmiHeader.biYPelsPerMeter = 1; + disp->_bmi.bmiHeader.biClrUsed = 0; + disp->_bmi.bmiHeader.biClrImportant = 0; + disp->_data = new unsigned int[(size_t)disp->_width*disp->_height]; + if (!disp->_is_fullscreen) { // Normal window + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)disp->_width - 1; rect.bottom = (LONG)disp->_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - disp->_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - disp->_height - border1); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_OVERLAPPEDWINDOW | (disp->_is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT, + disp->_width + 2*border1, disp->_height + border1 + border2, + 0,0,0,&(disp->_ccs)); + if (!disp->_is_closed) { + GetWindowRect(disp->_window,&rect); + disp->_window_x = rect.left + border1; + disp->_window_y = rect.top + border2; + } else disp->_window_x = disp->_window_y = 0; + } else { // Fullscreen window + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_POPUP | (disp->_is_closed?0:WS_VISIBLE), + (sx - disp->_width)/2, + (sy - disp->_height)/2, + disp->_width,disp->_height,0,0,0,&(disp->_ccs)); + disp->_window_x = disp->_window_y = 0; + } + SetForegroundWindow(disp->_window); + disp->_hdc = GetDC(disp->_window); + disp->_window_width = disp->_width; + disp->_window_height = disp->_height; + disp->flush(); +#ifdef _WIN64 + SetWindowLongPtr(disp->_window,GWLP_USERDATA,(LONG_PTR)disp); + SetWindowLongPtr(disp->_window,GWLP_WNDPROC,(LONG_PTR)_handle_events); +#else + SetWindowLong(disp->_window,GWL_USERDATA,(LONG)disp); + SetWindowLong(disp->_window,GWL_WNDPROC,(LONG)_handle_events); +#endif + SetEvent(disp->_is_created); + while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg); + return 0; + } + + CImgDisplay& _update_window_pos() { + if (_is_closed) _window_x = _window_y = -1; + else { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_width - 1; rect.bottom = (LONG)_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - _width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + GetWindowRect(_window,&rect); + _window_x = rect.left + border1; + _window_y = rect.top + border2; + } + return *this; + } + + void _init_fullscreen() { + _background_window = 0; + if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0; + else { + DEVMODE mode; + unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U; + for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) { + const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight; + if (nw>=_width && nh>=_height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) { + bestbpp = mode.dmBitsPerPel; + ibest = imode; + bw = nw; bh = nh; + } + } + if (bestbpp) { + _curr_mode.dmSize = sizeof(DEVMODE); _curr_mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&_curr_mode); + EnumDisplaySettings(0,ibest,&mode); + ChangeDisplaySettings(&mode,0); + } else _curr_mode.dmSize = 0; + + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + if (sx!=_width || sy!=_height) { + CLIENTCREATESTRUCT background_ccs; + _background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs); + SetForegroundWindow(_background_window); + } + } + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + if (_background_window) DestroyWindow(_background_window); + _background_window = 0; + if (_curr_mode.dmSize) ChangeDisplaySettings(&_curr_mode,0); + _is_fullscreen = false; + } + + CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous window if existing + if (!is_empty()) assign(); + + // Set display variables + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _is_cursor_visible = true; + _is_mouse_tracked = false; + _title = tmp_title; + flush(); + if (_is_fullscreen) _init_fullscreen(); + + // Create event thread + void *const arg = (void*)(new void*[2]); + ((void**)arg)[0] = (void*)this; + ((void**)arg)[1] = (void*)_title; + _mutex = CreateMutex(0,FALSE,0); + _is_created = CreateEvent(0,FALSE,FALSE,0); + _thread = CreateThread(0,0,_events_thread,arg,0,0); + WaitForSingleObject(_is_created,INFINITE); + return *this; + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + DestroyWindow(_window); + TerminateThread(_thread,0); + delete[] _data; + delete[] _title; + _data = 0; + _title = 0; + if (_is_fullscreen) _desinit_fullscreen(); + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,sizeof(unsigned int)*_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100), + tmpdimy = (nheight>0)?nheight:(-nheight*_height/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + if (_window_width!=dimx || _window_height!=dimy) { + RECT rect; rect.left = rect.top = 0; rect.right = (LONG)dimx - 1; rect.bottom = (LONG)dimy - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int cwidth = rect.right - rect.left + 1, cheight = rect.bottom - rect.top + 1; + SetWindowPos(_window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS); + } + if (_width!=dimx || _height!=dimy) { + unsigned int *const ndata = new unsigned int[dimx*dimy]; + if (force_redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy); + else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy); + delete[] _data; + _data = ndata; + _bmi.bmiHeader.biWidth = (LONG)dimx; + _bmi.bmiHeader.biHeight = -(int)dimy; + _width = dimx; + _height = dimy; + } + _window_width = dimx; _window_height = dimy; + show(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - width())/2,(screen_height() - height())/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*4; + void *odata = std::malloc(buf_size); + if (odata) { + std::memcpy(odata,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,odata,buf_size); + std::free(odata); + } + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + _is_closed = false; + if (_is_fullscreen) _init_fullscreen(); + ShowWindow(_window,SW_SHOW); + _update_window_pos(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + _is_closed = true; + if (_is_fullscreen) _desinit_fullscreen(); + ShowWindow(_window,SW_HIDE); + _window_x = _window_y = 0; + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + if (!_is_fullscreen) { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_window_width - 1; rect.bottom = (LONG)_window_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 -_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + SetWindowPos(_window,0,posx - border1,posy - border2,0,0,SWP_NOSIZE | SWP_NOZORDER); + } else SetWindowPos(_window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER); + _window_x = posx; + _window_y = posy; + show(); + } + _is_moved = false; + return *this; + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = true; + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = false; + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed || posx<0 || posy<0) return *this; + _update_window_pos(); + const int res = (int)SetCursorPos(_window_x + posx,_window_y + posy); + if (res) { _mouse_x = posx; _mouse_y = posy; } + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + SetWindowTextA(_window, tmp); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(); + } + + CImgDisplay& paint() { + if (_is_closed) return *this; + WaitForSingleObject(_mutex,INFINITE); + SetDIBitsToDevice(_hdc,0,0,_width,_height,0,0,0,_height,_data,&_bmi,DIB_RGB_COLORS); + ReleaseMutex(_mutex); + return *this; + } + + template + CImgDisplay& render(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + + const T + *data1 = img._data, + *data2 = (img._spectrum>=2)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>=3)?img.data(0,0,0,2):data1; + + WaitForSingleObject(_mutex,INFINITE); + unsigned int + *const ndata = (img._width==_width && img._height==_height)?_data: + new unsigned int[(size_t)img._width*img._height], + *ptrd = ndata; + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } + if (ndata!=_data) { _render_resize(ndata,img._width,img._height,_data,_width,_height); delete[] ndata; } + ReleaseMutex(_mutex); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + HDC hScreen = GetDC(GetDesktopWindow()); + if (hScreen) { + const int + width = GetDeviceCaps(hScreen,HORZRES), + height = GetDeviceCaps(hScreen,VERTRES); + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + if (_x1>=0 && _x0=0 && _y0 + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned int *ptrs = _data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned int val = *(ptrs++); + *(data1++) = (T)(unsigned char)(val>>16); + *(data2++) = (T)(unsigned char)((val>>8)&0xFF); + *(data3++) = (T)(unsigned char)(val&0xFF); + } + return *this; + } +#endif + + //@} + }; + + /* + #-------------------------------------- + # + # + # + # Definition of the CImg structure + # + # + # + #-------------------------------------- + */ + + //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T. + /** + This is the main class of the %CImg Library. It declares and constructs + an image, allows access to its pixel values, and is able to perform various image operations. + + \par Image representation + + A %CImg image is defined as an instance of the container \c CImg, which contains a regular grid of pixels, + each pixel value being of type \c T. The image grid can have up to 4 dimensions: width, height, depth + and number of channels. + Usually, the three first dimensions are used to describe spatial coordinates (x,y,z), + while the number of channels is rather used as a vector-valued dimension + (it may describe the R,G,B color channels for instance). + If you need a fifth dimension, you can use image lists \c CImgList 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, ...). + Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions. + + Concerning the pixel value type \c T: + fully supported template types are the basic C++ types: unsigned char, char, short, unsigned int, int, + unsigned long, long, float, double, ... . + Typically, fast image display can be done using CImg images, + while complex image processing algorithms may be rather coded using CImg or CImg + images that have floating-point pixel values. The default value for the template T is \c float. + Using your own template types may be possible. However, you will certainly have to define the complete set + of arithmetic and logical operators for your class. + + \par Image structure + + The \c CImg structure contains \e six fields: + - \c _width defines the number of \a columns of the image (size along the X-axis). + - \c _height defines the number of \a rows of the image (size along the Y-axis). + - \c _depth defines the number of \a slices of the image (size along the Z-axis). + - \c _spectrum defines the number of \a channels of the image (size along the C-axis). + - \c _data defines a \a pointer to the \a pixel \a data (of type \c T). + - \c _is_shared is a boolean that tells if the memory buffer \c data is shared with + another image. + + You can access these fields publicly although it is recommended to use the dedicated functions + width(), height(), depth(), spectrum() and ptr() to do so. + Image dimensions are not limited to a specific range (as long as you got enough available memory). + A value of \e 1 usually means that the corresponding dimension is \a flat. + If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty. + Empty images should not contain any pixel data and thus, will not be processed by CImg member functions + (a CImgInstanceException will be thrown instead). + Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage). + + \par Image declaration and construction + + Declaring an image can be done by using one of the several available constructors. + Here is a list of the most used: + + - Construct images from arbitrary dimensions: + - CImg img; declares an empty image. + - CImg img(128,128); declares a 128x128 greyscale image with + \c unsigned \c char pixel values. + - CImg img(3,3); declares a 3x3 matrix with \c double coefficients. + - CImg img(256,256,1,3); declares a 256x256x1x3 (color) image + (colors are stored as an image with three channels). + - CImg img(128,128,128); declares a 128x128x128 volumetric and greyscale image + (with \c double pixel values). + - CImg<> img(128,128,128,3); declares a 128x128x128 volumetric color image + (with \c float pixels, which is the default value of the template parameter \c T). + - \b Note: images pixels are not automatically initialized to 0. You may use the function \c fill() to + do it, or use the specific constructor taking 5 parameters like this: + CImg<> img(128,128,128,3,0); declares a 128x128x128 volumetric color image with all pixel values to 0. + + - Construct images from filenames: + - CImg img("image.jpg"); reads a JPEG color image from the file "image.jpg". + - CImg img("analyze.hdr"); reads a volumetric image (ANALYZE7.5 format) from the + file "analyze.hdr". + - \b Note: You need to install ImageMagick + to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io). + + - Construct images from C-style arrays: + - CImg img(data_buffer,256,256); constructs a 256x256 greyscale image from a \c int* buffer + \c data_buffer (of size 256x256=65536). + - CImg img(data_buffer,256,256,1,3); constructs a 256x256 color image + from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others). + + The complete list of constructors can be found here. + + \par Most useful functions + + The \c CImg class contains a lot of functions that operates on images. + Some of the most useful are: + + - operator()(): Read or write pixel values. + - display(): displays the image in a new window. + **/ + template + struct CImg { + + unsigned int _width, _height, _depth, _spectrum; + bool _is_shared; + T *_data; + + //! Simple iterator type, to loop through each pixel value of an image instance. + /** + \note + - The \c CImg::iterator type is defined to be a T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + CImg img("reference.jpg"); // Load image from file + // Set all pixels to '0', with a CImg iterator. + for (CImg::iterator it = img.begin(), it::const_iterator type is defined to be a \c const \c T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + 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::value_type type of a \c CImg is defined to be a \c T. + - \c CImg::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimg_plugin +#include cimg_plugin +#endif +#ifdef cimg_plugin1 +#include cimg_plugin1 +#endif +#ifdef cimg_plugin2 +#include cimg_plugin2 +#endif +#ifdef cimg_plugin3 +#include cimg_plugin3 +#endif +#ifdef cimg_plugin4 +#include cimg_plugin4 +#endif +#ifdef cimg_plugin5 +#include cimg_plugin5 +#endif +#ifdef cimg_plugin6 +#include cimg_plugin6 +#endif +#ifdef cimg_plugin7 +#include cimg_plugin7 +#endif +#ifdef cimg_plugin8 +#include cimg_plugin8 +#endif + + //@} + //--------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //--------------------------------------------------------- + + //! Destroy image. + /** + \note + - The pixel buffer data() is deallocated if necessary, e.g. for non-empty and non-shared image instances. + - Destroying an empty or shared image does nothing actually. + \warning + - When destroying a non-shared image, make sure that you will \e not operate on a remaining shared image + that shares its buffer with the destroyed instance, in order to avoid further invalid memory access + (to a deallocated buffer). + **/ + ~CImg() { + if (!_is_shared) delete[] _data; + } + + //! Construct empty image. + /** + \note + - An empty image has no pixel data and all of its dimensions width(), height(), depth(), spectrum() + are set to \c 0, as well as its pixel buffer pointer data(). + - An empty image may be re-assigned afterwards, e.g. with the family of + assign(unsigned int,unsigned int,unsigned int,unsigned int) methods, + or by operator=(const CImg&). In all cases, the type of pixels stays \c T. + - 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 + \endcode + **/ + CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {} + + //! Construct image with specified size. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \note + - It is able to create only \e non-shared images, and allocates thus a pixel buffer data() + for each constructed image instance. + - Setting one dimension \c size_x,\c size_y,\c size_z or \c size_c to \c 0 leads to the construction of + an \e empty image. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values during construction (e.g. with \c 0), use constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead. + \par Example + \code + CImg 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; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value Initialization value. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), + but it also fills the pixel buffer with the specified \c value. + \warning + - It cannot be used to construct a vector-valued image and initialize it with \e vector-valued pixels + (e.g. RGB vector, for color images). + For this task, you may use fillC() after construction. + **/ + CImg(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value): + _is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(value); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified sequence of integers \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be an \e integer). + \param value1 Second value of the initialization sequence (must be an \e integer). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \warning + - You must specify \e exactly \c size_x*\c size_y*\c size_z*\c size_c integers in the initialization sequence. + Otherwise, the constructor may crash or fill your image pixels with garbage. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _CImg_stdarg(img,a0,a1,N,t) { \ + size_t _siz = (size_t)N; \ + if (_siz--) { \ + va_list ap; \ + va_start(ap,a1); \ + T *ptrd = (img)._data; \ + *(ptrd++) = (T)a0; \ + if (_siz--) { \ + *(ptrd++) = (T)a1; \ + for ( ; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \ + } \ + va_end(ap); \ + } \ + } + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + } + +#if cimg_use_cpp11==1 + //! Construct image with specified size and initialize pixel values from an initializer list of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... } + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param { value0, value1, ... } Initialization list + \param repeat_values Tells if the value filling process is repeated over the image. + + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + { 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64 }); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _cimg_constructor_cpp11(repeat_values) \ + auto it = values.begin(); \ + size_t siz = size(); \ + if (repeat_values) for (T *ptrd = _data; siz--; ) { \ + *(ptrd++) = (T)(*(it++)); if (it==values.end()) it = values.begin(); } \ + else { siz = std::min(siz,values.size()); for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); } + assign(size_x,size_y,size_z,size_c); + _cimg_constructor_cpp11(repeat_values); + } + + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, const unsigned int size_y, + std::initializer_list values, + const bool repeat_values=true): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, + std::initializer_list values, + const bool repeat_values=true):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x); + _cimg_constructor_cpp11(repeat_values); + } + + //! Construct single channel 1D image with pixel values and width obtained from an initializer list of integers. + /** + Construct a new image instance of size \c width x \c 1 x \c 1 x \c 1, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... }. Image width is + given by the size of the initializer list. + \param { value0, value1, ... } Initialization list + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int) with height=1, depth=1, and spectrum=1, + but it also fills the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img = {10,20,30,20,10 }; // Construct a 5x1 image with one channel, and set its pixel values + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + CImg(const std::initializer_list values): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(values.size(),1,1,1); + auto it = values.begin(); + unsigned int siz = _width; + for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); + } + + template + CImg & operator=(std::initializer_list values) { + _cimg_constructor_cpp11(siz>values.size()); + return *this; + } +#endif + + //! Construct image with specified size and initialize pixel values from a sequence of doubles. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initialize pixel values from the specified sequence of doubles \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be a \e double). + \param value1 Second value of the initialization sequence (must be a \e double). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...), but + takes a sequence of double values instead of integers. + \warning + - You must specify \e exactly \c dx*\c dy*\c dz*\c dc doubles in the initialization sequence. + Otherwise, the constructor may crash or fill your image with garbage. + For instance, the code below will probably crash on most platforms: + \code + const CImg img(2,2,1,1, 0.5,0.5,255,255); // FAIL: The two last arguments are 'int', not 'double'! + \endcode + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + } + + //! Construct image with specified size and initialize pixel values from a value string. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified string \c values. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param values Value string describing the way pixel values are set. + \param repeat_values Tells if the value filling process is repeated over the image. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with values described in the value string \c values. + - Value string \c values may describe two different filling processes: + - Either \c values is a sequences of values assigned to the image pixels, as in "1,2,3,7,8,2". + In this case, set \c repeat_values to \c true to periodically fill the image with the value sequence. + - Either, \c values is a formula, as in "cos(x/10)*sin(y/20)". + In this case, parameter \c repeat_values is pointless. + - For both cases, specifying \c repeat_values is mandatory. + It disambiguates the possible overloading of constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) with \c T being a const char*. + - 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 + (img1,img2).display(); + \endcode + \image html ref_constructor2.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values):_is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(values,repeat_values); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified \c t* memory buffer. + \param values Pointer to the input memory buffer. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param is_shared Tells if input memory buffer must be shared by the current instance. + \note + - If \c is_shared is \c false, the image instance allocates its own pixel buffer, + and values from the specified input buffer are copied to the instance buffer. + If buffer types \c T and \c t are different, a regular static cast is performed during buffer copy. + - Otherwise, the image instance does \e not allocate a new buffer, and uses the input memory buffer as its + own pixel buffer. This case requires that types \c T and \c t are the same. Later, destroying such a shared + image will not deallocate the pixel buffer, this task being obviously charged to the initial buffer allocator. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - You must take care when operating on a shared image, since it may have an invalid pixel buffer pointer data() + (e.g. already deallocated). + \par Example + \code + unsigned char tab[256*256] = { 0 }; + CImg 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 + CImg(const t *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a (%u,%u,%u,%u) shared instance " + "from a (%s*) buffer (pixel types are different).", + cimg_instance, + size_x,size_y,size_z,size_c,CImg::pixel_type()); + } + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + + } + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = is_shared; + if (_is_shared) _data = const_cast(values); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy(_data,values,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image from reading an image file. + /** + Construct a new image instance with pixels of type \c T, and initialize pixel values with the data read from + an image file. + \param filename Filename, as a C-string. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it reads the image + dimensions and pixel values from the specified image file. + - The recognition of the image file format by %CImg higly depends on the tools installed on your system + and on the external libraries you used to link your code against. + - Considered pixel type \c T should better fit the file format specification, or data loss may occur during + file load (e.g. constructing a \c CImg from a float-valued image file). + - A \c CImgIOException is thrown when the specified \c filename cannot be read, or if the file format is not + recognized. + \par Example + \code + const CImg img("reference.jpg"); + img.display(); + \endcode + \image html ref_image.jpg + **/ + explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(filename); + } + + //! Construct image copy. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance. + \param img Input image to copy. + \note + - Constructed copy has the same size width() x height() x depth() x spectrum() and pixel values as the + input image \c img. + - If input image \c img is \e shared and if types \c T and \c t are the same, the constructed copy is also + \e shared, and shares its pixel buffer with \c img. + Modifying a pixel value in the constructed copy will thus also modifies it in the input image \c img. + This behavior is needful to allow functions to return shared images. + - Otherwise, the constructed copy allocates its own pixel buffer, and copies pixel values from the input + image \c img into its buffer. The copied pixel values may be eventually statically casted if types \c T and + \c t are different. + - Constructing a copy from an image \c img when types \c t and \c T are the same is significantly faster than + with different types. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. not enough available memory). + **/ + template + CImg(const CImg& img):_is_shared(false) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image copy \specialization. + CImg(const CImg& img) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = img._is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Advanced copy constructor. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance, + while forcing the shared state of the constructed copy. + \param img Input image to copy. + \param is_shared Tells about the shared state of the constructed copy. + \note + - Similar to CImg(const CImg&), except that it allows to decide the shared state of + the constructed image, which does not depend anymore on the shared state of the input image \c img: + - If \c is_shared is \c true, the constructed copy will share its pixel buffer with the input image \c img. + For that case, the pixel types \c T and \c t \e must be the same. + - If \c is_shared is \c false, the constructed copy will allocate its own pixel buffer, whether the input + image \c img is shared or not. + - A \c CImgArgumentException is thrown when a shared copy is requested with different pixel types \c T and \c t. + **/ + template + CImg(const CImg& img, const bool is_shared):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a shared instance from a " + "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).", + cimg_instance, + CImg::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data); + } + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Advanced copy constructor \specialization. + CImg(const CImg& img, const bool is_shared) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image with dimensions borrowed from another image. + /** + Construct a new image instance with pixels of type \c T, and size get from some dimensions of an existing + \c CImg instance. + \param img Input image from which dimensions are borrowed. + \param dimensions C-string describing the image size along the X,Y,Z and C-dimensions. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it takes the image dimensions + (\e not its pixel values) from an existing \c CImg instance. + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values (e.g. with \c 0), use constructor CImg(const CImg&,const char*,T) + 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') + \endcode + **/ + template + CImg(const CImg& img, const char *const dimensions): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values. + /** + Construct a new image instance with pixels of type \c T, and size get from the dimensions of an existing + \c CImg instance, and set all pixel values to specified \c value. + \param img Input image from which dimensions are borrowed. + \param dimensions String describing the image size along the X,Y,Z and V-dimensions. + \param value Value used for initialization. + \note + - Similar to CImg(const CImg&,const char*), but it also fills the pixel buffer with the specified \c value. + **/ + template + CImg(const CImg& img, const char *const dimensions, const T& value): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions).fill(value); + } + + //! Construct image from a display window. + /** + Construct a new image instance with pixels of type \c T, as a snapshot of an existing \c CImgDisplay instance. + \param disp Input display window. + \note + - The width() and height() of the constructed image instance are the same as the specified \c CImgDisplay. + - The depth() and spectrum() of the constructed image instance are respectively set to \c 1 and \c 3 + (i.e. a 2D color image). + - The image pixels are read as 8-bits RGB values. + **/ + explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + disp.snapshot(*this); + } + + // Constructor and assignment operator for rvalue references (c++11). + // This avoids an additional image copy for methods returning new images. Can save RAM for big images ! +#if cimg_use_cpp11==1 + CImg(CImg&& 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); + } +#endif + + //! Construct empty image \inplace. + /** + In-place version of the default constructor CImg(). It simply resets the instance to an empty image. + **/ + CImg& assign() { + if (!_is_shared) delete[] _data; + _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; + return *this; + } + + //! Construct image with specified size \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (siz!=curr_siz) { + if (_is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignement request of shared instance from specified " + "image (%u,%u,%u,%u).", + cimg_instance, + size_x,size_y,size_z,size_c); + else { + delete[] _data; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + return *this; + } + + //! Construct image with specified size and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,T). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value) { + return assign(size_x,size_y,size_z,size_c).fill(value); + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a sequence of doubles \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a value string \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values) { + return assign(size_x,size_y,size_z,size_c).fill(values,repeat_values); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \inplace. + /** + In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int). + **/ + template + 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; + if (!values || !siz) return assign(); + assign(size_x,size_y,size_z,size_c); + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c); + if (_is_shared || values + siz<_data || values>=_data + size()) { + assign(size_x,size_y,size_z,size_c); + if (_is_shared) std::memmove((void*)_data,(void*)values,siz*sizeof(T)); + else std::memcpy((void*)_data,(void*)values,siz*sizeof(T)); + } else { + T *new_data = 0; + try { new_data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy((void*)new_data,(void*)values,siz*sizeof(T)); + delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + } + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + template + 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) { + if (is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignment request of shared instance from (%s*) buffer" + "(pixel types are different).", + cimg_instance, + CImg::pixel_type()); + return assign(values,size_x,size_y,size_z,size_c); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const bool is_shared) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + if (!is_shared) { if (_is_shared) assign(); assign(values,size_x,size_y,size_z,size_c); } + else { + if (!_is_shared) { + if (values + siz<_data || values>=_data + size()) assign(); + else cimg::warn(_cimg_instance + "assign(): Shared image instance has overlapping memory.", + cimg_instance); + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true; + _data = const_cast(values); + } + return *this; + } + + //! Construct image from reading an image file \inplace. + /** + In-place version of the constructor CImg(const char*). + **/ + CImg& assign(const char *const filename) { + return load(filename); + } + + //! Construct image copy \inplace. + /** + In-place version of the constructor CImg(const CImg&). + **/ + template + CImg& assign(const CImg& img) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum); + } + + //! In-place version of the advanced copy constructor. + /** + In-place version of the constructor CImg(const CImg&,bool). + **/ + template + CImg& assign(const CImg& img, const bool is_shared) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum,is_shared); + } + + //! Construct image with dimensions borrowed from another image \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions) { + if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum); + unsigned int siz[4] = { 0,1,1,1 }, k = 0; + CImg item(256); + for (const char *s = dimensions; *s && k<4; ++k) { + if (cimg_sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item._data)>0) s+=std::strlen(item); + if (*s) { + unsigned int val = 0; char sep = 0; + if (cimg_sscanf(s,"%u%c",&val,&sep)>0) { + if (sep=='%') siz[k] = val*(k==0?_width:k==1?_height:k==2?_depth:_spectrum)/100; + else siz[k] = val; + while (*s>='0' && *s<='9') ++s; + if (sep=='%') ++s; + } else switch (cimg::lowercase(*s)) { + case 'x' : case 'w' : siz[k] = img._width; ++s; break; + case 'y' : case 'h' : siz[k] = img._height; ++s; break; + case 'z' : case 'd' : siz[k] = img._depth; ++s; break; + case 'c' : case 's' : siz[k] = img._spectrum; ++s; break; + default : + throw CImgArgumentException(_cimg_instance + "assign(): Invalid character '%c' detected in specified dimension string '%s'.", + cimg_instance, + *s,dimensions); + } + } + } + return assign(siz[0],siz[1],siz[2],siz[3]); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*,T). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions, const T& value) { + return assign(img,dimensions).fill(value); + } + + //! Construct image from a display window \inplace. + /** + In-place version of the constructor CImg(const CImgDisplay&). + **/ + CImg& assign(const CImgDisplay &disp) { + disp.snapshot(*this); + return *this; + } + + //! Construct empty image \inplace. + /** + Equivalent to assign(). + \note + - It has been defined for compatibility with STL naming conventions. + **/ + CImg& clear() { + return assign(); + } + + //! Transfer content of an image instance into another one. + /** + Transfer the dimensions and the pixel buffer content of an image instance into another one, + and replace instance by an empty image. It avoids the copy of the pixel buffer + when possible. + \param img Destination image. + \note + - Pixel types \c T and \c t of source and destination images can be different, though the process is + designed to be instantaneous when \c T and \c t are the same. + \par Example + \code + CImg 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 + CImg& move_to(CImg& img) { + img.assign(*this); + assign(); + return img; + } + + //! Transfer content of an image instance into another one \specialization. + CImg& move_to(CImg& img) { + if (_is_shared || img._is_shared) img.assign(*this); + else swap(img); + assign(); + return img; + } + + //! Transfer content of an image instance into a new image in an image list. + /** + Transfer the dimensions and the pixel buffer content of an image instance + into a newly inserted image at position \c pos in specified \c CImgList instance. + \param list Destination list. + \param pos Position of the newly inserted image in the list. + \note + - When optional parameter \c pos is ommited, the image instance is transfered as a new + image at the end of the specified \c list. + - It is convenient to sequentially insert new images into image lists, with no + additional copies of memory buffer. + \par Example + \code + CImgList 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 + CImgList& move_to(CImgList& list, const unsigned int pos=~0U) { + const unsigned int npos = pos>list._width?list._width:pos; + move_to(list.insert(1,npos)[npos]); + return list; + } + + //! Swap fields of two image instances. + /** + \param img Image to swap fields with. + \note + - It can be used to interchange the content of two images in a very fast way. Can be convenient when dealing + with algorithms requiring two swapping buffers. + \par Example + \code + CImg img1("lena.jpg"), + img2("milla.jpg"); + img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena' + \endcode + **/ + CImg& swap(CImg& img) { + cimg::swap(_width,img._width,_height,img._height,_depth,img._depth,_spectrum,img._spectrum); + cimg::swap(_data,img._data); + cimg::swap(_is_shared,img._is_shared); + return img; + } + + //! Return a reference to an empty image. + /** + \note + This function is useful mainly to declare optional parameters having type \c CImg in functions prototypes, + e.g. + \code + void f(const int x=0, const int y=0, const CImg& img=CImg::empty()); + \endcode + **/ + static CImg& empty() { + static CImg _empty; + return _empty.assign(); + } + + //! Return a reference to an empty image \const. + static const CImg& const_empty() { + static const CImg _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Access to a pixel value. + /** + Return a reference to a located pixel value of the image instance, + being possibly \e const, whether the image instance is \e const or not. + This is the standard method to get/set pixel values in \c CImg images. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Range of pixel coordinates start from (0,0,0,0) to + (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 + 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 + 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' + const float + valR = img(10,10,0,0), // Read red value at coordinates (10,10) + valG = img(10,10,0,1), // Read green value at coordinates (10,10) + valB = img(10,10,2), // Read blue value at coordinates (10,10) (Z-coordinate can be omitted) + avg = (valR + valG + valB)/3; // Compute average pixel value + img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the color pixel (10,10) by the average grey value + \endcode + **/ +#if cimg_verbosity>=3 + T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (!_data || off>=size()) { + cimg::warn(_cimg_instance + "operator(): Invalid pixel request, at coordinates (%d,%d,%d,%d) [offset=%u].", + cimg_instance, + (int)x,(int)y,(int)z,(int)c,off); + return *_data; + } + else return _data[off]; + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->operator()(x,y,z,c); + } + + //! Access to a pixel value. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param wh Precomputed offset, must be equal to width()*\ref height(). + \param whd Precomputed offset, must be equal to width()*\ref height()*\ref depth(). + \note + - Similar to (but faster than) operator()(). + It uses precomputed offsets to optimize memory access. You may use it to optimize + the reading/writing of several pixel values in the same image (e.g. in a loop). + **/ + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) const { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } +#else + T& operator()(const unsigned int x) { + return _data[x]; + } + + const T& operator()(const unsigned int x) const { + return _data[x]; + } + + T& operator()(const unsigned int x, const unsigned int y) { + return _data[x + y*_width]; + } + + const T& operator()(const unsigned int x, const unsigned int y) const { + return _data[x + y*_width]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) { + return _data[x + y*_width + z*wh]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) const { + return _data[x + y*_width + z*wh]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) { + return _data[x + y*_width + z*wh + c*whd]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) const { + return _data[x + y*_width + z*wh + c*whd]; + } +#endif + + //! Implicitely cast an image into a \c T*. + /** + Implicitely cast a \c CImg 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 + std::printf("'img1' is not empty, 'img2' is empty."); + } + } + \endcode + - It also allows to use brackets to access pixel values, without need for a \c CImg::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) + \endcode + **/ + operator T*() { + return _data; + } + + //! Implicitely cast an image into a \c T* \const. + operator const T*() const { + return _data; + } + + //! Assign a value to all image pixels. + /** + Assign specified \c value to each pixel value of the image instance. + \param value Value that will be assigned to image pixels. + \note + - The image size is never modified. + - The \c value may be casted to pixel type \c T if necessary. + \par Example + \code + CImg 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) { + return fill(value); + } + + //! Assign pixels values from a specified expression. + /** + Initialize all pixel values from the specified string \c expression. + \param expression Value string describing the way pixel values are set. + \note + - String parameter \c expression may describe different things: + - If \c expression is a list of values (as in \c "1,2,3,8,3,2"), or a formula (as in \c "(x*y)%255"), + the pixel values are set from specified \c expression and the image size is not modified. + - If \c expression is a filename (as in \c "reference.jpg"), the corresponding image file is loaded and + replace the image instance. The image size is modified if necessary. + \par Example + \code + CImg img1(100,100), img2(img1), img3(img1); // Declare 3 scalar images 100x100 with unitialized values + img1 = "0,50,100,150,200,250,200,150,100,50"; // Set pixel values of 'img1' from a value sequence + img2 = "10*((x*y)%25)"; // Set pixel values of 'img2' from a formula + img3 = "reference.jpg"; // Set pixel values of 'img3' from a file (image size is modified) + (img1,img2,img3).display(); + \endcode + \image html ref_operator_eq.jpg + **/ + CImg& operator=(const char *const expression) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { + _fill(expression,true,1,0,0,"operator=",0); + } catch (CImgException&) { + cimg::exception_mode(omode); + load(expression); + } + cimg::exception_mode(omode); + return *this; + } + + //! Copy an image into the current image instance. + /** + Similar to the in-place copy constructor assign(const CImg&). + **/ + template + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy an image into the current image instance \specialization. + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy the content of a display window to the current image instance. + /** + Similar to assign(const CImgDisplay&). + **/ + CImg& operator=(const CImgDisplay& disp) { + disp.snapshot(*this); + return *this; + } + + //! In-place addition operator. + /** + Add specified \c value to all pixels of an image instance. + \param value Value to add. + \note + - Resulting pixel values are casted to fit the pixel type \c T. + For instance, adding \c 0.2 to a \c CImg 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 + // 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 + (img1,img2,img3).display(); + \endcode + \image html ref_operator_plus.jpg + **/ + template + CImg& operator+=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + value,524288); + return *this; + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the specified string \c expression. + \param expression Value string describing the way pixel values are added. + \note + - Similar to operator=(const char*), except that it adds values to the pixels of the current image instance, + instead of assigning them. + **/ + CImg& operator+=(const char *const expression) { + return *this+=(+*this)._fill(expression,true,1,0,0,"operator+=",this); + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the values of the input image \c img. + \param img Input image to add. + \note + - The size of the image instance is never modified. + - It is not mandatory that input image \c img has the same size as the image instance. + If less values are available in \c img, then the values are added periodically. For instance, adding one + WxH scalar image (spectrum() equal to \c 1) to one WxH color image (spectrum() equal to \c 3) + means each color channel will be incremented with the same values at the same locations. + \par Example + \code + CImg 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 + (img2,img1).display(); + \endcode + \image html ref_operator_plus1.jpg + **/ + template + CImg& operator+=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this+=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator++() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + 1,524288); + return *this; + } + + //! In-place increment operator (postfix). + /** + Add \c 1 to all image pixels, and return a new copy of the initial (pre-incremented) image instance. + \note + - Use the prefixed version operator++() if you don't need a copy of the initial + (pre-incremented) image instance, since a useless image copy may be expensive in terms of memory usage. + **/ + CImg operator++(int) { + const CImg copy(*this,false); + ++*this; + return copy; + } + + //! Return a non-shared copy of the image instance. + /** + \note + - Use this operator to ensure you get a non-shared copy of an image instance with same pixel type \c T. + Indeed, the usual copy constructor CImg(const CImg&) returns a shared copy of a shared input image, + and it may be not desirable to work on a regular copy (e.g. for a resize operation) if you have no + information about the shared state of the input image. + - Writing \c (+img) is equivalent to \c CImg(img,false). + **/ + CImg operator+() const { + return CImg(*this,false); + } + + //! Addition operator. + /** + Similar to operator+=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator+(const t value) const { + return CImg<_cimg_Tt>(*this,false)+=value; + } + + //! Addition operator. + /** + Similar to operator+=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator+(const char *const expression) const { + return CImg(*this,false)+=expression; + } + + //! Addition 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. + **/ + template + CImg<_cimg_Tt> operator+(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)+=img; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const t), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - value,524288); + return *this; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const char*), except that it performs a substraction instead of an addition. + **/ + CImg& operator-=(const char *const expression) { + return *this-=(+*this)._fill(expression,true,1,0,0,"operator-=",this); + } + + //! In-place substraction operator. + /** + Similar to operator+=(const CImg&), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this-=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator--() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - 1,524288); + return *this; + } + + //! In-place decrement operator (postfix). + /** + Similar to operator++(int), except that it performs a decrement instead of an increment. + **/ + CImg operator--(int) { + const CImg copy(*this,false); + --*this; + return copy; + } + + //! Replace each pixel by its opposite value. + /** + \note + - If the computed opposite values are out-of-range, they are treated as with standard C++ numeric types. + For instance, the \c unsigned \c char opposite of \c 1 is \c 255. + \par Example + \code + const CImg + img1("reference.jpg"), // Load a RGB color image + img2 = -img1; // Compute its opposite (in 'unsigned char') + (img1,img2).display(); + \endcode + \image html ref_operator_minus.jpg + **/ + CImg operator-() const { + return CImg(_width,_height,_depth,_spectrum,(T)0)-=*this; + } + + //! Substraction operator. + /** + Similar to operator-=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator-(const t value) const { + return CImg<_cimg_Tt>(*this,false)-=value; + } + + //! Substraction operator. + /** + Similar to operator-=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator-(const char *const expression) const { + return CImg(*this,false)-=expression; + } + + //! Substraction 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. + **/ + template + CImg<_cimg_Tt> operator-(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)-=img; + } + + //! In-place multiplication operator. + /** + Similar to operator+=(const t), except that it performs a multiplication instead of an addition. + **/ + template + CImg& operator*=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr * value,262144); + return *this; + } + + //! In-place multiplication operator. + /** + 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,1,0,0,"operator*=",this)); + } + + //! In-place multiplication operator. + /** + Replace the image instance by the matrix multiplication between the image instance and the specified matrix + \c img. + \param img Second operand of the matrix multiplication. + \note + - It does \e not compute a pointwise multiplication between two images. For this purpose, use + mul(const CImg&) instead. + - 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' + // 'A' is now a 1x2 vector whose values are [5;11]. + \endcode + **/ + template + CImg& operator*=(const CImg& img) { + return ((*this)*img).move_to(*this); + } + + //! Multiplication operator. + /** + Similar to operator*=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator*(const t value) const { + return CImg<_cimg_Tt>(*this,false)*=value; + } + + //! Multiplication operator. + /** + Similar to operator*=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator*(const char *const expression) const { + return CImg(*this,false)*=expression; + } + + //! Multiplication 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. + **/ + 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)", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + 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_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 *ps0 = img.data(), *ps1 = img.data(0,1); + Tt *pd0 = res.data(), *pd1 = res.data(0,1); + const Ttdouble + a0 = (Ttdouble)_data[0], a1 = (Ttdouble)_data[1], + a2 = (Ttdouble)_data[2], a3 = (Ttdouble)_data[3]; + cimg_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++); + *(pd0++) = (Tt)(a0*x + a1*y); + *(pd1++) = (Tt)(a2*x + a3*y); + } + return res; + } + case 3 : { // 3x3_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *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_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z); + *(pd1++) = (Tt)(a3*x + a4*y + a5*z); + *(pd2++) = (Tt)(a6*x + a7*y + a8*z); + } + return res; + } + case 4 : { // 4x4_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2), *ps3 = img.data(0,3); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *pd2 = res.data(0,2), *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_forX(img,col) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++), + c = (Ttdouble)*(ps3++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z + a3*c); + *(pd1++) = (Tt)(a4*x + a5*y + a6*z + a7*c); + *(pd2++) = (Tt)(a8*x + a9*y + a10*z + a11*c); + *(pd3++) = (Tt)(a12*x + a13*y + a14*z + a15*c); + } + return res; + } + } + } + + // Fallback to generic version. +#ifdef cimg_use_openmp + 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 + Tt *ptrd = res._data; + cimg_forXY(res,i,j) { + Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); *(ptrd++) = (Tt)value; + } +#endif + return res; + } + + //! In-place division operator. + /** + Similar to operator+=(const t), except that it performs a division instead of an addition. + **/ + template + CImg& operator/=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr / value,32768); + return *this; + } + + //! In-place division operator. + /** + 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,1,0,0,"operator/=",this)); + } + + //! In-place division operator. + /** + Replace the image instance by the (right) matrix division between the image instance and the specified + matrix \c img. + \param img Second operand of the matrix division. + \note + - It does \e not compute a pointwise division between two images. For this purpose, use + div(const CImg&) instead. + - It returns the matrix operation \c A*inverse(img). + - The size of the image instance can be modified by this operator. + **/ + template + CImg& operator/=(const CImg& img) { + return (*this*img.get_invert()).move_to(*this); + } + + //! Division operator. + /** + Similar to operator/=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator/(const t value) const { + return CImg<_cimg_Tt>(*this,false)/=value; + } + + //! Division operator. + /** + Similar to operator/=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator/(const char *const expression) const { + return CImg(*this,false)/=expression; + } + + //! Division 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. + **/ + template + CImg<_cimg_Tt> operator/(const CImg& img) const { + return (*this)*img.get_invert(); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const t), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::mod(*ptr,(T)value),16384); + return *this; + } + + //! In-place modulo operator. + /** + 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,1,0,0,"operator%=",this); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const CImg&), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this%=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> operator%(const t value) const { + return CImg<_cimg_Tt>(*this,false)%=value; + } + + //! Modulo operator. + /** + Similar to operator%=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator%(const char *const expression) const { + return CImg(*this,false)%=expression; + } + + //! Modulo 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. + **/ + template + CImg<_cimg_Tt> operator%(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)%=img; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const t), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr & (ulongT)value,32768); + return *this; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition. + **/ + CImg& operator&=(const char *const expression) { + return *this&=(+*this)._fill(expression,true,1,0,0,"operator&=",this); + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this&=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator&(const t value) const { + return (+*this)&=value; + } + + //! Bitwise AND operator. + /** + Similar to operator&=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator&(const char *const expression) const { + return (+*this)&=expression; + } + + //! Bitwise AND 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 is \c T. + **/ + template + CImg operator&(const CImg& img) const { + return (+*this)&=img; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr | (ulongT)value,32768); + return *this; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition. + **/ + CImg& operator|=(const char *const expression) { + return *this|=(+*this)._fill(expression,true,1,0,0,"operator|=",this); + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this|=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator|(const t value) const { + return (+*this)|=value; + } + + //! Bitwise OR operator. + /** + Similar to operator|=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator|(const char *const expression) const { + return (+*this)|=expression; + } + + //! Bitwise OR 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 is \c T. + **/ + template + CImg operator|(const CImg& img) const { + return (+*this)|=img; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const t) instead. + **/ + template + CImg& operator^=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr ^ (ulongT)value,32768); + return *this; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead. + **/ + CImg& operator^=(const char *const expression) { + return *this^=(+*this)._fill(expression,true,1,0,0,"operator^=",this); + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const CImg&) instead. + **/ + template + CImg& operator^=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator^(const t value) const { + return (+*this)^=value; + } + + //! Bitwise XOR operator. + /** + Similar to operator^=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator^(const char *const expression) const { + return (+*this)^=expression; + } + + //! Bitwise XOR 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 is \c T. + **/ + template + CImg operator^(const CImg& img) const { + return (+*this)^=img; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) << (int)value,65536); + return *this; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition. + **/ + CImg& operator<<=(const char *const expression) { + return *this<<=(+*this)._fill(expression,true,1,0,0,"operator<<=",this); + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator<<(const t value) const { + return (+*this)<<=value; + } + + //! Bitwise left shift operator. + /** + Similar to operator<<=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator<<(const char *const expression) const { + return (+*this)<<=expression; + } + + //! Bitwise left shift 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 is \c T. + **/ + template + CImg operator<<(const CImg& img) const { + return (+*this)<<=img; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) >> (int)value,65536); + return *this; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition. + **/ + CImg& operator>>=(const char *const expression) { + return *this>>=(+*this)._fill(expression,true,1,0,0,"operator>>=",this); + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs> (int)*(ptrs++)); + for (const t *ptrs = img._data; ptrd> (int)*(ptrs++)); + } + return *this; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + template + CImg operator>>(const t value) const { + return (+*this)>>=value; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator>>(const char *const expression) const { + return (+*this)>>=expression; + } + + //! Bitwise right shift 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 is \c T. + **/ + template + CImg operator>>(const CImg& img) const { + return (+*this)>>=img; + } + + //! Bitwise inversion operator. + /** + Similar to operator-(), except that it compute the bitwise inverse instead of the opposite value. + **/ + CImg operator~() const { + CImg res(_width,_height,_depth,_spectrum); + const T *ptrs = _data; + cimg_for(res,ptrd,T) { const ulongT value = (ulongT)*(ptrs++); *ptrd = (T)~value; } + return res; + } + + //! Test if all pixels of an image have the same value. + /** + Return \c true is all pixels of the image instance are equal to the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator==(const t value) const { + if (is_empty()) return false; + typedef _cimg_Tt Tt; + bool is_equal = true; + for (T *ptrd = _data + size(); is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)value)) {} + return is_equal; + } + + //! Test if all pixel values of an image follow a specified expression. + /** + Return \c true is all pixels of the image instance are equal to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator==(const char *const expression) const { + return *this==(+*this)._fill(expression,true,1,0,0,"operator==",this); + } + + //! Test if two images have the same size and values. + /** + Return \c true if the image instance and the input image \c img have the same dimensions and pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - The pixel buffer pointers data() of the two compared images do not have to be the same for operator==() + to return \c true. + Only the dimensions and the pixel values matter. Thus, the comparison can be \c true even for different + pixel types \c T and \c t. + \par Example + \code + const CImg 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 + **/ + template + bool operator==(const CImg& img) const { + typedef _cimg_Tt Tt; + const ulongT siz = size(); + bool is_equal = true; + if (siz!=img.size()) return false; + t *ptrs = img._data + siz; + for (T *ptrd = _data + siz; is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)*(--ptrs))) {} + return is_equal; + } + + //! Test if pixels of an image are all different from a value. + /** + Return \c true is all pixels of the image instance are different than the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator!=(const t value) const { + return !((*this)==value); + } + + //! Test if all pixel values of an image are different from a specified expression. + /** + Return \c true is all pixels of the image instance are different to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator!=(const char *const expression) const { + return !((*this)==expression); + } + + //! Test if two images have different sizes or values. + /** + Return \c true if the image instance and the input image \c img have different dimensions or pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - Writing \c img1!=img2 is equivalent to \c !(img1==img2). + **/ + template + bool operator!=(const CImg& img) const { + return !((*this)==img); + } + + //! Construct an image list from two images. + /** + Return a new list of image (\c CImgList instance) containing exactly two elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image \c img, at position [\c 1]. + + \param img Input image that will be the second image of the resulting list. + \note + - The family of operator,() is convenient to easily create list of images, but it is also \e quite \e slow + in practice (see warning below). + - Constructed lists contain no shared images. If image instance or input image \c img are shared, they are + inserted as new non-shared copies in the resulting list. + - The pixel type of the returned list may be a superset of the initial pixel type \c T, if necessary. + \warning + - Pipelining operator,() \c N times will perform \c N copies of the entire content of a (growing) image list. + This may become very expensive in terms of speed and used memory. You should avoid using this technique to + build a new CImgList instance from several images, if you are seeking for performance. + Fast insertions of images in an image list are possible with + CImgList::insert(const CImg&,unsigned int,bool) or move_to(CImgList&,unsigned int). + \par Example + \code + const CImg + 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' + \endcode + \image html ref_operator_comma.jpg + **/ + template + CImgList<_cimg_Tt> operator,(const CImg& img) const { + return CImgList<_cimg_Tt>(*this,img); + } + + //! Construct an image list from image instance and an input image list. + /** + Return a new list of images (\c CImgList instance) containing exactly \c list.size() \c + \c 1 elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image list \c list, from positions [\c 1] to [\c list.size()]. + + \param list Input image list that will be appended to the image instance. + \note + - Similar to operator,(const CImg&) const, except that it takes an image list as an argument. + **/ + template + CImgList<_cimg_Tt> operator,(const CImgList& list) const { + return CImgList<_cimg_Tt>(list,false).insert(*this,0); + } + + //! Split image along specified axis. + /** + Return a new list of images (\c CImgList instance) containing the splitted components + of the instance image along the specified axis. + \param axis Splitting axis (can be '\c x','\c y','\c z' or '\c c') + \note + - Similar to get_split(char,int) const, with default second argument. + \par Example + \code + const CImg 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 + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the number of image columns. + /** + Return the image width, i.e. the image dimension along the X-axis. + \note + - The width() of an empty image is equal to \c 0. + - width() is typically equal to \c 1 when considering images as \e vectors for matrix calculations. + - width() returns an \c int, although the image width is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._width. + **/ + int width() const { + return (int)_width; + } + + //! Return the number of image rows. + /** + Return the image height, i.e. the image dimension along the Y-axis. + \note + - The height() of an empty image is equal to \c 0. + - height() returns an \c int, although the image height is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._height. + **/ + int height() const { + return (int)_height; + } + + //! Return the number of image slices. + /** + Return the image depth, i.e. the image dimension along the Z-axis. + \note + - The depth() of an empty image is equal to \c 0. + - depth() is typically equal to \c 1 when considering usual 2D images. When depth()\c > \c 1, the image + is said to be \e volumetric. + - depth() returns an \c int, although the image depth is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._depth. + **/ + int depth() const { + return (int)_depth; + } + + //! Return the number of image channels. + /** + Return the number of image channels, i.e. the image dimension along the C-axis. + \note + - The spectrum() of an empty image is equal to \c 0. + - spectrum() is typically equal to \c 1 when considering scalar-valued images, to \c 3 + for RGB-coded color images, and to \c 4 for RGBA-coded color images (with alpha-channel). + The number of channels of an image instance is not limited. The meaning of the pixel values is not linked + up to the number of channels (e.g. a 4-channel image may indifferently stands for a RGBA or CMYK color image). + - spectrum() returns an \c int, although the image spectrum is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._spectrum. + **/ + int spectrum() const { + return (int)_spectrum; + } + + //! Return the total number of pixel values. + /** + Return width()*\ref height()*\ref depth()*\ref spectrum(), + i.e. the total number of values of type \c T in the pixel buffer of the image instance. + \note + - The size() of an empty image is equal to \c 0. + - The allocated memory size for a pixel buffer of a non-shared \c CImg instance is equal to + size()*sizeof(T). + \par Example + \code + 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 + **/ + ulongT size() const { + return (ulongT)_width*_height*_depth*_spectrum; + } + + //! Return a pointer to the first pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the first value in the pixel buffer of the image instance, + whether the instance is \c const or not. + \note + - The data() of an empty image is equal to \c 0 (null pointer). + - The allocated pixel buffer for the image instance starts from \c data() + and goes to data()+\ref size() - 1 (included). + - To get the pointer to one particular location of the pixel buffer, use + data(unsigned int,unsigned int,unsigned int,unsigned int) instead. + **/ + T* data() { + return _data; + } + + //! Return a pointer to the first pixel value \const. + const T* data() const { + return _data; + } + + //! Return a pointer to a located pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the value located at (\c x,\c y,\c z,\c c) in the pixel buffer + of the image instance, + whether the instance is \c const or not. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)). Thus, this method has the same + properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int). + **/ +#if cimg_verbosity>=3 + T *data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (off>=size()) + cimg::warn(_cimg_instance + "data(): Invalid pointer request, at coordinates (%u,%u,%u,%u) [offset=%u].", + cimg_instance, + x,y,z,c,off); + return _data + off; + } + + //! Return a pointer to a located pixel value \const. + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->data(x,y,z,c); + } +#else + T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } + + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } +#endif + + //! Return the offset to a located pixel value, with respect to the beginning of the pixel buffer. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)) - img.data(). + 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 + \endcode + **/ + longT offset(const int x, const int y=0, const int z=0, const int c=0) const { + return x + (longT)y*_width + (longT)z*_width*_height + (longT)c*_width*_height*_depth; + } + + //! Return a CImg::iterator pointing to the first pixel value. + /** + \note + - Equivalent to data(). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + iterator begin() { + return _data; + } + + //! Return a CImg::iterator pointing to the first value of the pixel buffer \const. + const_iterator begin() const { + return _data; + } + + //! Return a CImg::iterator pointing next to the last pixel value. + /** + \note + - Writing \c img.end() is equivalent to img.data() + img.size(). + - It has been mainly defined for compatibility with STL naming conventions. + \warning + - The returned iterator actually points to a value located \e outside the acceptable bounds of the pixel buffer. + Trying to read or write the content of the returned iterator will probably result in a crash. + Use it mainly as a strict upper bound for a CImg::iterator. + \par Example + \code + 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::iterator pointing next to the last pixel value \const. + const_iterator end() const { + return _data + size(); + } + + //! Return a reference to the first pixel value. + /** + \note + - Writing \c img.front() is equivalent to img[0], or img(0,0,0,0). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& front() { + return *_data; + } + + //! Return a reference to the first pixel value \const. + const T& front() const { + return *_data; + } + + //! Return a reference to the last pixel value. + /** + \note + - Writing \c img.back() is equivalent to img[img.size() - 1], or + img(img.width() - 1,img.height() - 1,img.depth() - 1,img.spectrum() - 1). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& back() { + return *(_data + size() - 1); + } + + //! Return a reference to the last pixel value \const. + const T& back() const { + return *(_data + size() - 1); + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to a specified default value in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note + - Writing \c img.at(offset,out_value) is similar to img[offset], except that if \c offset + is outside bounds (e.g. \c offset<0 or \c offset>=img.size()), a reference to a value \c out_value + is safely returned instead. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + **/ + T& at(const int offset, const T& out_value) { + return (offset<0 || offset>=(int)size())?(cimg::temporary(out_value)=out_value):(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions \const. + T at(const int offset, const T& out_value) const { + return (offset<0 || offset>=(int)size())?out_value:(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to the nearest pixel location in the image instance in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \note + - Similar to at(int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified offset, i.e. + - If \c offset<0, then \c img[0] is returned. + - If \c offset>=img.size(), then \c img[img.size() - 1] is returned. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int). + **/ + T& at(const int offset) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + T& _at(const int offset) { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions \const. + const T& at(const int offset) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + const T& _at(const int offset) const { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to a specified default value in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c x,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to operator()(), except that an out-of-bounds access along the X-axis returns the specified value + \c out_value. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || x>=width())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate \const. + T atX(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || x>=width())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to at(int,int,int,int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified X-coordinate. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _at(int,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + T& _atX(const int x, const int y=0, const int z=0, const int c=0) { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate \const. + const T& atX(const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + const T& _atX(const int x, const int y=0, const int z=0, const int c=0) const { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X and Y-coordinates. + **/ + T& atXY(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y coordinates \const. + T atXY(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || x>=width() || y>=height())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXY(int,int,int,int). + **/ + T& atXY(const int x, const int y, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + T& _atXY(const int x, const int y, const int z=0, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates \const. + const T& atXY(const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + const T& _atXY(const int x, const int y, const int z=0, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on + X,Y and Z-coordinates. + **/ + T& atXYZ(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates \const. + T atXYZ(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZ(int,int,int,int). + **/ + T& atXYZ(const int x, const int y, const int z, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + T& _atXYZ(const int x, const int y, const int z, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates \const. + const T& atXYZ(const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + const T& _atXYZ(const int x, const int y, const int z, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed on all + X,Y,Z and C-coordinates. + **/ + T& atXYZC(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions \const. + T atXYZC(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_value: + (*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed on all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZC(int,int,int,int). + **/ + T& atXYZC(const int x, const int y, const int z, const int c) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + T& _atXYZC(const int x, const int y, const int z, const int c) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Access to a pixel value, using Neumann boundary conditions \const. + const T& atXYZC(const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + const T& _atXYZC(const int x, const int y, const int z, const int c) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to atX(int,int,int,int,const T), except that the returned pixel value is approximated by + a linear interpolation along the X-axis, if corresponding coordinates are not integers. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + const Tfloat + Ic = (Tfloat)atX(x,y,z,c,out_value), In = (Tfloat)atXY(nx,y,z,c,out_value); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access along + the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that an out-of-bounds access returns + the value of the nearest pixel in the image instance, regarding the specified X-coordinate. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atX(): Empty instance.", + cimg_instance); + + return _linear_atX(fx,y,z,c); + } + + Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1); + const unsigned int + x = (unsigned int)nfx; + const float + dx = nfx - x; + const unsigned int + nx = dx>0?x + 1:x; + const Tfloat + Ic = (Tfloat)(*this)(x,y,z,c), In = (Tfloat)(*this)(nx,y,z,c); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X and Y-coordinates. + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + const Tfloat + Icc = (Tfloat)atXY(x,y,z,c,out_value), Inc = (Tfloat)atXY(nx,y,z,c,out_value), + Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXY(float,float,int,int). + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXY(): Empty instance.", + cimg_instance); + + return _linear_atXY(fx,fy,z,c); + } + + Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy; + const float + dx = nfx - x, + dy = nfy - y; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y; + const Tfloat + Icc = (Tfloat)(*this)(x,y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X,Y and Z-coordinates. + **/ + Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + const Tfloat + Iccc = (Tfloat)atXYZ(x,y,z,c,out_value), Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), + Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), + Iccn = (Tfloat)atXYZ(x,y,nz,c,out_value), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), + Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZ(float,float,float,int). + **/ + Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZ(): Empty instance.", + cimg_instance); + + return _linear_atXYZ(fx,fy,fz,c); + } + + Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z; + const Tfloat + Iccc = (Tfloat)(*this)(x,y,z,c), Incc = (Tfloat)(*this)(nx,y,z,c), + Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c), + Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c), + Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z,C-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved for all X,Y,Z and C-coordinates. + **/ + Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1, + c = (int)fc - (fc>=0?0:1), nc = c + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z, + dc = fc - c; + const Tfloat + Icccc = (Tfloat)atXYZC(x,y,z,c,out_value), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_value), + Icncc = (Tfloat)atXYZC(x,ny,z,c,out_value), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_value), + Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_value), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_value), + Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_value), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_value), + Icccn = (Tfloat)atXYZC(x,y,z,nc,out_value), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_value), + Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_value), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_value), + Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_value), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_value), + Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_value), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_value); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn -Icccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for all X,Y,Z and C-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved for all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZC(float,float,float,float). + **/ + Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZC(): Empty instance.", + cimg_instance); + + return _linear_atXYZC(fx,fy,fz,fc); + } + + Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1), + nfc = cimg::cut(fc,0,spectrum() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz, + c = (unsigned int)nfc; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z, + dc = nfc - c; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z, + nc = dc>0?c + 1:c; + const Tfloat + Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c), + Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c), + Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c), + Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c), + Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc), + Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc), + Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc), + Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn - Icccc); + } + + //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + The cubic interpolation uses Hermite splines. + \param fx d X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a \e cubic interpolation along the X-axis. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2; + const float + dx = fx - x; + const Tfloat + Ip = (Tfloat)atX(px,y,z,c,out_value), Ic = (Tfloat)atX(x,y,z,c,out_value), + In = (Tfloat)atX(nx,y,z,c,out_value), Ia = (Tfloat)atX(ax,y,z,c,out_value); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Similar to cubic_atX(float,int,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atX(fx,y,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access + along the X-axis. The cubic interpolation uses Hermite splines. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to cubic_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a cubic interpolation along the X-axis. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atX(): Empty instance.", + cimg_instance); + return _cubic_atX(fx,y,z,c); + } + + Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::type::is_nan(fx)?0:cimg::cut(fx,0,width() - 1); + const int + x = (int)nfx; + const float + dx = nfx - x; + const int + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2; + const Tfloat + Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c), + In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atX(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 + are achieved both for X and Y-coordinates. + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2; + const float dx = fx - x, dy = fy - y; + const Tfloat + Ipp = (Tfloat)atXY(px,py,z,c,out_value), Icp = (Tfloat)atXY(x,py,z,c,out_value), + Inp = (Tfloat)atXY(nx,py,z,c,out_value), Iap = (Tfloat)atXY(ax,py,z,c,out_value), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)atXY(px,y,z,c,out_value), Icc = (Tfloat)atXY(x, y,z,c,out_value), + Inc = (Tfloat)atXY(nx,y,z,c,out_value), Iac = (Tfloat)atXY(ax,y,z,c,out_value), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)atXY(px,ny,z,c,out_value), Icn = (Tfloat)atXY(x,ny,z,c,out_value), + Inn = (Tfloat)atXY(nx,ny,z,c,out_value), Ian = (Tfloat)atXY(ax,ny,z,c,out_value), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)atXY(px,ay,z,c,out_value), Ica = (Tfloat)atXY(x,ay,z,c,out_value), + Ina = (Tfloat)atXY(nx,ay,z,c,out_value), Iaa = (Tfloat)atXY(ax,ay,z,c,out_value), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXY(fx,fy,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved for both X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXY(float,float,int,int). + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXY(): Empty instance.", + cimg_instance); + return _cubic_atXY(fx,fy,z,c); + } + + Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::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: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), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)(*this)(px,y,z,c), Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Iac = (Tfloat)(*this)(ax,y,z,c), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c), + Ian = (Tfloat)(*this)(ax,ny,z,c), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c), + Iaa = (Tfloat)(*this)(ax,ay,z,c), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int) const, except that 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 { + 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 { + return cimg::type::cut(_cubic_atXY(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 + are achieved both for X,Y and Z-coordinates. + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2, + z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2; + const float dx = fx - x, dy = fy - y, dz = fz - z; + const Tfloat + Ippp = (Tfloat)atXYZ(px,py,pz,c,out_value), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_value), + Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_value), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_value), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_value), Iccp = (Tfloat)atXYZ(x, y,pz,c,out_value), + Incp = (Tfloat)atXYZ(nx,y,pz,c,out_value), Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_value), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_value), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_value), + Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_value), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_value), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_value), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_value), + Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_value), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_value), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)atXYZ(px,py,z,c,out_value), Icpc = (Tfloat)atXYZ(x,py,z,c,out_value), + Inpc = (Tfloat)atXYZ(nx,py,z,c,out_value), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_value), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)atXYZ(px,y,z,c,out_value), Iccc = (Tfloat)atXYZ(x, y,z,c,out_value), + Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), Iacc = (Tfloat)atXYZ(ax,y,z,c,out_value), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_value), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), + Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_value), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)atXYZ(px,ay,z,c,out_value), Icac = (Tfloat)atXYZ(x,ay,z,c,out_value), + Inac = (Tfloat)atXYZ(nx,ay,z,c,out_value), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_value), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)atXYZ(px,py,nz,c,out_value), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_value), + Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_value), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_value), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_value), Iccn = (Tfloat)atXYZ(x, y,nz,c,out_value), + Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_value), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_value), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), + Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_value), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_value), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_value), + Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_value), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_value), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)atXYZ(px,py,az,c,out_value), Icpa = (Tfloat)atXYZ(x,py,az,c,out_value), + Inpa = (Tfloat)atXYZ(nx,py,az,c,out_value), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_value), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)atXYZ(px,y,az,c,out_value), Icca = (Tfloat)atXYZ(x, y,az,c,out_value), + Inca = (Tfloat)atXYZ(nx,y,az,c,out_value), Iaca = (Tfloat)atXYZ(ax,y,az,c,out_value), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)atXYZ(px,ny,az,c,out_value), Icna = (Tfloat)atXYZ(x,ny,az,c,out_value), + Inna = (Tfloat)atXYZ(nx,ny,az,c,out_value), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_value), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_value), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_value), + Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_value), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_value), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the XYZ-coordinates. + /** + Similar to cubic_atXYZ(float,float,float,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXYZ(float,float,float,int). + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXYZ(): Empty instance.", + cimg_instance); + return _cubic_atXYZ(fx,fy,fz,c); + } + + Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + const float + nfx = cimg::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 + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2, + py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2, + pz = z - 1<0?0:z - 1, nz = dz>0?z + 1:z, az = z + 2>=depth()?depth() - 1:z + 2; + const Tfloat + Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c), + Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)(*this)(px,y,pz,c), Iccp = (Tfloat)(*this)(x, y,pz,c), + Incp = (Tfloat)(*this)(nx,y,pz,c), Iacp = (Tfloat)(*this)(ax,y,pz,c), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c), + Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c), + Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c), + Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)(*this)(px,y,z,c), Iccc = (Tfloat)(*this)(x, y,z,c), + Incc = (Tfloat)(*this)(nx,y,z,c), Iacc = (Tfloat)(*this)(ax,y,z,c), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c), + Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c), + Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c), + Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)(*this)(px,y,nz,c), Iccn = (Tfloat)(*this)(x, y,nz,c), + Incn = (Tfloat)(*this)(nx,y,nz,c), Iacn = (Tfloat)(*this)(ax,y,nz,c), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c), + Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c), + Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c), + Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)(*this)(px,y,az,c), Icca = (Tfloat)(*this)(x, y,az,c), + Inca = (Tfloat)(*this)(nx,y,az,c), Iaca = (Tfloat)(*this)(ax,y,az,c), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c), + Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c), + Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the XYZ-coordinates. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atXYZ(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 + the value is spread amongst several neighbors if the pixel coordinates are float-valued. + \param value Pixel value to set. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param is_added Tells if the pixel value is added to (\c true), or simply replace (\c false) the current image + pixel(s). + \return A reference to the current image instance. + \note + - Calling this method with out-of-bounds coordinates does nothing. + **/ + CImg& set_linear_atX(const T& value, const float fx, const int y=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + if (y>=0 && y=0 && z=0 && c=0 && x=0 && nx& set_linear_atXY(const T& value, const float fx, const float fy=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + if (z>=0 && z=0 && c=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx& set_linear_atXYZ(const T& value, const float fx, const float fy=0, const float fz=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + if (c>=0 && c=0 && z=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx=0 && nz=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx image whose buffer data() is a \c char* string describing the list of all pixel values + of the image instance (written in base 10), separated by specified \c separator character. + \param separator A \c char character which specifies the separator between values in the returned C-string. + \param max_size Maximum size of the returned image (or \c 0 if no limits are set). + \param format For float/double-values, tell the printf format used to generate the Ascii representation + of the numbers (or \c 0 for default representation). + \note + - The returned image is never empty. + - For an empty image instance, the returned string is "". + - If \c max_size is equal to \c 0, there are no limits on the size of the returned string. + - Otherwise, if the maximum number of string characters is exceeded, the value string is cut off + and terminated by character \c '\0'. In that case, the returned image size is max_size + 1. + **/ + CImg value_string(const char separator=',', const unsigned int max_size=0, + const char *const format=0) const { + if (is_empty() || max_size==1) return CImg(1,1,1,1,0); + CImgList items; + CImg s_item(256); *s_item = 0; + const T *ptrs = _data; + unsigned int string_size = 0; + const char *const _format = format?format:cimg::type::format(); + for (ulongT off = 0, siz = size(); off::format(*(ptrs++))); + CImg item(s_item._data,printed_size); + item[printed_size - 1] = separator; + item.move_to(items); + if (max_size) string_size+=printed_size; + } + CImg res; + (items>'x').move_to(res); + if (max_size && res._width>=max_size) res.crop(0,max_size - 1); + res.back() = 0; + return res; + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Test shared state of the pixel buffer. + /** + Return \c true if image instance has a shared memory buffer, and \c false otherwise. + \note + - A shared image do not own his pixel buffer data() and will not deallocate it on destruction. + - Most of the time, a \c CImg image instance will \e not be shared. + - A shared image can only be obtained by a limited set of constructors and methods (see list below). + **/ + bool is_shared() const { + return _is_shared; + } + + //! Test if image instance is empty. + /** + Return \c true, if image instance is empty, i.e. does \e not contain any pixel values, has dimensions + \c 0 x \c 0 x \c 0 x \c 0 and a pixel buffer pointer set to \c 0 (null pointer), and \c false otherwise. + **/ + bool is_empty() const { + return !(_data && _width && _height && _depth && _spectrum); + } + + //! Test if image instance contains a 'inf' value. + /** + Return \c true, if image instance contains a 'inf' value, and \c false otherwise. + **/ + bool is_inf() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_inf((float)*p)) return true; + return false; + } + + //! Test if image instance contains a NaN value. + /** + Return \c true, if image instance contains a NaN value, and \c false otherwise. + **/ + bool is_nan() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_nan((float)*p)) return true; + return false; + } + + //! Test if image width is equal to specified value. + bool is_sameX(const unsigned int size_x) const { + return _width==size_x; + } + + //! Test if image width is equal to specified value. + template + bool is_sameX(const CImg& img) const { + return is_sameX(img._width); + } + + //! Test if image width is equal to specified value. + bool is_sameX(const CImgDisplay& disp) const { + return is_sameX(disp._width); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const unsigned int size_y) const { + return _height==size_y; + } + + //! Test if image height is equal to specified value. + template + bool is_sameY(const CImg& img) const { + return is_sameY(img._height); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const CImgDisplay& disp) const { + return is_sameY(disp._height); + } + + //! Test if image depth is equal to specified value. + bool is_sameZ(const unsigned int size_z) const { + return _depth==size_z; + } + + //! Test if image depth is equal to specified value. + template + bool is_sameZ(const CImg& img) const { + return is_sameZ(img._depth); + } + + //! Test if image spectrum is equal to specified value. + bool is_sameC(const unsigned int size_c) const { + return _spectrum==size_c; + } + + //! Test if image spectrum is equal to specified value. + template + bool is_sameC(const CImg& img) const { + return is_sameC(img._spectrum); + } + + //! Test if image width and height are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameY(unsigned int) const are both verified. + **/ + bool is_sameXY(const unsigned int size_x, const unsigned int size_y) const { + return _width==size_x && _height==size_y; + } + + //! Test if image width and height are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameY(const CImg&) const are both verified. + **/ + template + bool is_sameXY(const CImg& img) const { + return is_sameXY(img._width,img._height); + } + + //! Test if image width and height are the same as that of an existing display window. + /** + Test if is_sameX(const CImgDisplay&) const and is_sameY(const CImgDisplay&) const are both verified. + **/ + bool is_sameXY(const CImgDisplay& disp) const { + return is_sameXY(disp._width,disp._height); + } + + //! Test if image width and depth are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXZ(const unsigned int size_x, const unsigned int size_z) const { + return _width==size_x && _depth==size_z; + } + + //! Test if image width and depth are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXZ(const CImg& img) const { + return is_sameXZ(img._width,img._depth); + } + + //! Test if image width and spectrum are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXC(const unsigned int size_x, const unsigned int size_c) const { + return _width==size_x && _spectrum==size_c; + } + + //! Test if image width and spectrum are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXC(const CImg& img) const { + return is_sameXC(img._width,img._spectrum); + } + + //! Test if image height and depth are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameYZ(const unsigned int size_y, const unsigned int size_z) const { + return _height==size_y && _depth==size_z; + } + + //! Test if image height and depth are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameYZ(const CImg& img) const { + return is_sameYZ(img._height,img._depth); + } + + //! Test if image height and spectrum are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYC(const unsigned int size_y, const unsigned int size_c) const { + return _height==size_y && _spectrum==size_c; + } + + //! Test if image height and spectrum are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYC(const CImg& img) const { + return is_sameYC(img._height,img._spectrum); + } + + //! Test if image depth and spectrum are equal to specified values. + /** + Test if is_sameZ(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameZC(const unsigned int size_z, const unsigned int size_c) const { + return _depth==size_z && _spectrum==size_c; + } + + //! Test if image depth and spectrum are the same as that of another image. + /** + Test if is_sameZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameZC(const CImg& img) const { + return is_sameZC(img._depth,img._spectrum); + } + + //! Test if image width, height and depth are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXYZ(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z) const { + return is_sameXY(size_x,size_y) && _depth==size_z; + } + + //! Test if image width, height and depth are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXYZ(const CImg& img) const { + return is_sameXYZ(img._width,img._height,img._depth); + } + + //! Test if image width, height and spectrum are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXYC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_c) const { + return is_sameXY(size_x,size_y) && _spectrum==size_c; + } + + //! Test if image width, height and spectrum are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYC(const CImg& img) const { + return is_sameXYC(img._width,img._height,img._spectrum); + } + + //! Test if image width, depth and spectrum are equal to specified values. + /** + Test if is_sameXZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXZC(const unsigned int size_x, const unsigned int size_z, const unsigned int size_c) const { + return is_sameXZ(size_x,size_z) && _spectrum==size_c; + } + + //! Test if image width, depth and spectrum are the same as that of another image. + /** + Test if is_sameXZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXZC(const CImg& img) const { + return is_sameXZC(img._width,img._depth,img._spectrum); + } + + //! Test if image height, depth and spectrum are equal to specified values. + /** + Test if is_sameYZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYZC(const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const { + return is_sameYZ(size_y,size_z) && _spectrum==size_c; + } + + //! Test if image height, depth and spectrum are the same as that of another image. + /** + Test if is_sameYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYZC(const CImg& img) const { + return is_sameYZC(img._height,img._depth,img._spectrum); + } + + //! Test if image width, height, depth and spectrum are equal to specified values. + /** + Test if is_sameXYZ(unsigned int,unsigned int,unsigned int) const and is_sameC(unsigned int) const are both + verified. + **/ + bool is_sameXYZC(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c) const { + return is_sameXYZ(size_x,size_y,size_z) && _spectrum==size_c; + } + + //! Test if image width, height, depth and spectrum are the same as that of another image. + /** + Test if is_sameXYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYZC(const CImg& img) const { + return is_sameXYZC(img._width,img._height,img._depth,img._spectrum); + } + + //! Test if specified coordinates are inside image bounds. + /** + Return \c true if pixel located at (\c x,\c y,\c z,\c c) is inside bounds of the image instance, + and \c false otherwise. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Return \c true only if all these conditions are verified: + - The image instance is \e not empty. + - 0<=x<=\ref width() - 1. + - 0<=y<=\ref height() - 1. + - 0<=z<=\ref depth() - 1. + - 0<=c<=\ref spectrum() - 1. + **/ + bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const { + return !is_empty() && x>=0 && x=0 && y=0 && z=0 && c img(100,100,1,3); // Construct a 100x100 RGB color image + const unsigned long offset = 1249; // Offset to the pixel (49,12,0,0) + unsigned int x,y,z,c; + if (img.contains(img[offset],x,y,z,c)) { // Convert offset to (x,y,z,c) coordinates + std::printf("Offset %u refers to pixel located at (%u,%u,%u,%u).\n", + offset,x,y,z,c); + } + \endcode + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z, t& c) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = (ulongT)(ppixel - _data); + const ulongT nc = off/whd; + off%=whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc; + return true; + } + + //! Test if pixel value is inside image bounds and get its X,Y and Z-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X,Y and Z-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((ulongT)(ppixel - _data))%whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; + return true; + } + + //! Test if pixel value is inside image bounds and get its X and Y-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X and Y-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y) const { + const ulongT wh = (ulongT)_width*_height, siz = wh*_depth*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((unsigned int)(ppixel - _data))%wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; + return true; + } + + //! Test if pixel value is inside image bounds and get its X-coordinate. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X-coordinate is set. + **/ + template + bool contains(const T& pixel, t& x) const { + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + size()) return false; + x = (t)(((ulongT)(ppixel - _data))%_width); + return true; + } + + //! Test if pixel value is inside image bounds. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that no pixel coordinates are set. + **/ + bool contains(const T& pixel) const { + const T *const ppixel = &pixel; + return !is_empty() && ppixel>=_data && ppixel<_data + size(); + } + + //! Test if pixel buffers of instance and input images overlap. + /** + Return \c true, if pixel buffers attached to image instance and input image \c img overlap, + and \c false otherwise. + \param img Input image to compare with. + \note + - Buffer overlapping may happen when manipulating \e shared images. + - If two image buffers overlap, operating on one of the image will probably modify the other one. + - Most of the time, \c CImg instances are \e non-shared and do not overlap between each others. + \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 + std::printf("Buffers overlap!\n"); + } + \endcode + **/ + template + bool is_overlapped(const CImg& img) const { + const ulongT csiz = size(), isiz = img.size(); + return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz)); + } + + //! Test if the set {\c *this,\c primitives,\c colors,\c opacities} defines a valid 3D object. + /** + Return \c true is the 3D object represented by the set {\c *this,\c primitives,\c colors,\c opacities} defines a + valid 3D object, and \c false otherwise. The vertex coordinates are defined by the instance image. + \param primitives List of primitives of the 3D object. + \param colors List of colors of the 3D object. + \param opacities List (or image) of opacities of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_checking to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + template + bool is_object3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true, + char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check consistency for the particular case of an empty 3D object. + if (is_empty()) { + if (primitives || colors || opacities) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no vertices but %u primitives, " + "%u colors and %lu opacities", + _width,primitives._width,primitives._width, + colors._width,(unsigned long)opacities.size()); + return false; + } + return true; + } + + // Check consistency of vertices. + if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) has invalid vertex dimensions (%u,%u,%u,%u)", + _width,primitives._width,_width,_height,_depth,_spectrum); + return false; + } + if (colors._width>primitives._width + 1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %u colors", + _width,primitives._width,colors._width); + return false; + } + if (opacities.size()>primitives._width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %lu opacities", + _width,primitives._width,(unsigned long)opacities.size()); + return false; + } + if (!full_check) return true; + + // Check consistency of primitives. + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + const unsigned int psiz = (unsigned int)primitive.size(); + switch (psiz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)primitive(0); + if (i0>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indice %u in " + "point primitive [%u]", + _width,primitives._width,i0,l); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + if (i0>=_width || i1>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + case 2 : 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 " + "segment primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + 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 " + "triangle primitive [%u]", + _width,primitives._width,i0,i1,i2,l); + return false; + } + } break; + 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 " + "quadrangle primitive [%u]", + _width,primitives._width,i0,i1,i2,i3,l); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid primitive [%u] of size %u", + _width,primitives._width,l,(unsigned int)psiz); + return false; + } + } + + // Check consistency of colors. + cimglist_for(colors,c) { + const CImg& color = colors[c]; + if (!color) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no color for primitive [%u]", + _width,primitives._width,c); + return false; + } + } + + // Check consistency of light texture. + if (colors._width>primitives._width) { + const CImg &light = colors.back(); + if (!light || light._depth>1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid light texture (%u,%u,%u,%u)", + _width,primitives._width,light._width, + light._height,light._depth,light._spectrum); + return false; + } + } + + return true; + } + + //! Test if image instance represents a valid serialization of a 3D object. + /** + Return \c true if the image instance represents a valid serialization of a 3D object, and \c false otherwise. + \param full_check Tells if full checking of the instance must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_check to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + bool is_CImg3d(const bool full_check=true, char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check instance dimension and header. + if (_width!=1 || _height<8 || _depth!=1 || _spectrum!=1) { + if (error_message) cimg_sprintf(error_message, + "CImg3d has invalid dimensions (%u,%u,%u,%u)", + _width,_height,_depth,_spectrum); + return false; + } + const T *ptrs = _data, *const ptre = end(); + if (!_is_CImg3d(*(ptrs++),'C') || !_is_CImg3d(*(ptrs++),'I') || !_is_CImg3d(*(ptrs++),'m') || + !_is_CImg3d(*(ptrs++),'g') || !_is_CImg3d(*(ptrs++),'3') || !_is_CImg3d(*(ptrs++),'d')) { + if (error_message) cimg_sprintf(error_message, + "CImg3d header not found"); + return false; + } + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + + // Check consistency of number of vertices / primitives. + if (!full_check) { + const ulongT minimal_size = 8UL + 3*nb_points + 6*nb_primitives; + if (_data + minimal_size>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has only %lu values, while at least %lu values were expected", + nb_points,nb_primitives,(unsigned long)size(),(unsigned long)minimal_size); + return false; + } + } + + // Check consistency of vertex data. + if (!nb_points) { + if (nb_primitives) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no vertices but %u primitives", + nb_points,nb_primitives,nb_primitives); + return false; + } + if (ptrs!=ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) is an empty object but contains %u value%s " + "more than expected", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs),(ptre - ptrs)>1?"s":""); + return false; + } + return true; + } + if (ptrs + 3*nb_points>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines only %u vertices data", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs)/3); + return false; + } + ptrs+=3*nb_points; + + // Check consistency of primitive data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines %u vertices but no primitive", + nb_points,nb_primitives,nb_points); + return false; + } + + if (!full_check) return true; + + for (unsigned int p = 0; p=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive [%u]", + nb_points,nb_primitives,i0,p); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + ptrs+=3; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==6) ptrs+=4; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "segment primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==9) ptrs+=6; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in " + "triangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,p); + return false; + } + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)), + i3 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==12) ptrs+=8; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in " + "quadrangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,i3,p); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines an invalid primitive [%u] of size %u", + nb_points,nb_primitives,p,nb_inds); + return false; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete primitive data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,p,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of color data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no color/texture data", + nb_points,nb_primitives); + return false; + } + for (unsigned int c = 0; c=c) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared sprite/texture indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,c); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete color/texture data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,c,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of opacity data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no opacity data", + nb_points,nb_primitives); + return false; + } + for (unsigned int o = 0; o=o) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared opacity indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,o); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete opacity data for primitive [%u]", + nb_points,nb_primitives,o); + return false; + } + } + + // Check end of data. + if (ptrs1?"s":""); + return false; + } + return true; + } + + static bool _is_CImg3d(const T val, const char c) { + return val>=(T)c && val<(T)(c + 1); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + // Define the math formula parser/compiler and expression evaluator. + struct _cimg_math_parser { + CImg mem; + CImg memtype; + CImgList _code, &code, code_begin, code_end; + 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; + + CImg _img_stats, &img_stats, constcache_vals; + CImgList _list_stats, &list_stats, _list_median, &list_median; + 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; + ulongT 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_vector(arg) (memtype[arg]>1) // Is vector? +#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_op(s) s_op = s; ss_op = ss +#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char) +#define _cimg_mp_check_constant(arg,n_arg,mode) check_constant(arg,n_arg,mode,ss,se,saved_char) +#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char) +#define _cimg_mp_check_list(is_out) check_list(is_out,ss,se,saved_char) +#define _cimg_mp_defunc(mp) (*(mp_func)(*(mp).opcode))(mp) +#define _cimg_mp_return(x) { *se = saved_char; s_op = previous_s_op; ss_op = previous_ss_op; return x; } +#define _cimg_mp_return_nan() _cimg_mp_return(_cimg_mp_slot_nan) +#define _cimg_mp_constant(val) _cimg_mp_return(constant((double)(val))) +#define _cimg_mp_scalar0(op) _cimg_mp_return(scalar0(op)) +#define _cimg_mp_scalar1(op,i1) _cimg_mp_return(scalar1(op,i1)) +#define _cimg_mp_scalar2(op,i1,i2) _cimg_mp_return(scalar2(op,i1,i2)) +#define _cimg_mp_scalar3(op,i1,i2,i3) _cimg_mp_return(scalar3(op,i1,i2,i3)) +#define _cimg_mp_scalar4(op,i1,i2,i3,i4) _cimg_mp_return(scalar4(op,i1,i2,i3,i4)) +#define _cimg_mp_scalar5(op,i1,i2,i3,i4,i5) _cimg_mp_return(scalar5(op,i1,i2,i3,i4,i5)) +#define _cimg_mp_scalar6(op,i1,i2,i3,i4,i5,i6) _cimg_mp_return(scalar6(op,i1,i2,i3,i4,i5,i6)) +#define _cimg_mp_scalar7(op,i1,i2,i3,i4,i5,i6,i7) _cimg_mp_return(scalar7(op,i1,i2,i3,i4,i5,i6,i7)) +#define _cimg_mp_vector1_v(op,i1) _cimg_mp_return(vector1_v(op,i1)) +#define _cimg_mp_vector2_sv(op,i1,i2) _cimg_mp_return(vector2_sv(op,i1,i2)) +#define _cimg_mp_vector2_vs(op,i1,i2) _cimg_mp_return(vector2_vs(op,i1,i2)) +#define _cimg_mp_vector2_vv(op,i1,i2) _cimg_mp_return(vector2_vv(op,i1,i2)) +#define _cimg_mp_vector3_vss(op,i1,i2,i3) _cimg_mp_return(vector3_vss(op,i1,i2,i3)) + + // Constructors / Destructors. + ~_cimg_math_parser() { + cimg::srand(rng); + } + + _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), + rng((cimg::_rand(),cimg::rng())),calling_function(funcname?funcname:"cimg_math_parser") { +#ifdef cimg_use_openmp + 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 && (cimg::is_blank(*_expression) || *_expression==';')) ++_expression; + CImg::string(_expression).move_to(expr); + char *ps = &expr.back() - 1; + 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 (!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_slot_nan 29 +#define _cimg_mp_slot_x 30 +#define _cimg_mp_slot_y 31 +#define _cimg_mp_slot_z 32 +#define _cimg_mp_slot_c 33 + + mem.assign(96); + for (unsigned int i = 0; i<=10; ++i) mem[i] = (double)i; // mem[0-10] = 0...10 + for (unsigned int i = 1; i<=5; ++i) mem[i + 10] = -(double)i; // mem[11-15] = -1...-5 + mem[16] = 0.5; + mem[17] = 0; // thread_id + mem[18] = (double)imgin._width; // w + mem[19] = (double)imgin._height; // h + mem[20] = (double)imgin._depth; // d + mem[21] = (double)imgin._spectrum; // s + mem[22] = (double)imgin._is_shared; // r + mem[23] = (double)imgin._width*imgin._height; // wh + mem[24] = (double)imgin._width*imgin._height*imgin._depth; // whd + mem[25] = (double)imgin._width*imgin._height*imgin._depth*imgin._spectrum; // whds + mem[26] = (double)listin._width; // l + mem[27] = std::exp(1.); // 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 = compile-time constant | N>1 = constant ptr to vector[N-1] }. + memtype.assign(mem._width,1,1,1,0); + for (unsigned int i = 0; i<_cimg_mp_slot_x; ++i) memtype[i] = 1; + memtype[17] = 0; + memtype[_cimg_mp_slot_x] = memtype[_cimg_mp_slot_y] = memtype[_cimg_mp_slot_z] = memtype[_cimg_mp_slot_c] = -2; + mempos = _cimg_mp_slot_c + 1; + variable_pos.assign(8); + + reserved_label.assign(128,1,1,1,~0U); + // reserved_label[4-28] are used to store these two-char variables: + // [0] = wh, [1] = whd, [2] = whds, [3] = pi, [4] = im, [5] = iM, [6] = ia, [7] = iv, + // [8] = is, [9] = ip, [10] = ic, [11] = xm, [12] = ym, [13] = zm, [14] = cm, [15] = xM, + // [16] = yM, [17] = zM, [18]=cM, [19]=i0...[28]=i9, [29] = interpolation, [30] = boundary + + // Compile expression into a serie of opcodes. + s_op = ""; ss_op = expr._data; + const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0,false); + if (!_cimg_mp_is_constant(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(); + } + + // 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(); + level.assign(); + variable_pos.assign(); + reserved_label.assign(); + expr.assign(); + pexpr.assign(); + opcode.assign(); + opcode._is_shared = true; + + // Execute begin() bloc 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_begin.end(); + for (p_code = code_begin; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + p_code_end = code.end(); + } + + _cimg_math_parser(): + code(_code),p_code_end(0),p_break((CImg*)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), + 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)), + rng((cimg::_rand(),cimg::rng())),calling_function(0) { +#ifdef cimg_use_openmp + mem[17] = 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) { + 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'.", + pixel_type(),_cimg_mp_calling_function, + (ss - 4)>expr._data?"...":"", + (ss - 4)>expr._data?ss - 4:expr._data, + se<&expr.back()?"...":""); + } + char c1, c2, c3, c4; + + // Simplify expression when possible. + do { + c2 = 0; + if (ssss && (cimg::is_blank(c1 = *(se - 1)) || c1==';')) --se; + } + while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) { + ++ss; --se; c2 = 1; + } + } while (c2 && ss::%s: %s%s Missing %s, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", + *s_op=='F'?"argument":"item", + (ss_op - 4)>expr._data?"...":"", + (ss_op - 4)>expr._data?ss_op - 4:expr._data, + ss_op + std::strlen(ss_op)<&expr.back()?"...":""); + } + + const char *const previous_s_op = s_op, *const previous_ss_op = ss_op; + const unsigned int depth1 = depth + 1; + unsigned int pos, p1, p2, p3, arg1, arg2, arg3, arg4, arg5, arg6; + char + *const se1 = se - 1, *const se2 = se - 2, *const se3 = se - 3, + *const ss1 = ss + 1, *const ss2 = ss + 2, *const ss3 = ss + 3, *const ss4 = ss + 4, + *const ss5 = ss + 5, *const ss6 = ss + 6, *const ss7 = ss + 7, *const ss8 = ss + 8, + *s, *ps, *ns, *s0, *s1, *s2, *s3, sep = 0, end = 0; + double val = 0, val1, val2; + mp_func op; + + // 'p_ref' is a 'unsigned int[7]' used to return a reference to an image or vector value + // linked to the returned memory slot (reference that cannot be determined at compile time). + // p_ref[0] can be { 0 = scalar (unlinked) | 1 = vector value | 2 = image value (offset) | + // 3 = image value (coordinates) | 4 = image value as a vector (offsets) | + // 5 = image value as a vector (coordinates) }. + // Depending on p_ref[0], the remaining p_ref[k] have the following meaning: + // When p_ref[0]==0, p_ref is actually unlinked. + // When p_ref[0]==1, p_ref = [ 1, vector_ind, offset ]. + // When p_ref[0]==2, p_ref = [ 2, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==3, p_ref = [ 3, image_ind (or ~0U), is_relative, x, y, z, c ]. + // When p_ref[0]==4, p_ref = [ 4, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==5, p_ref = [ 5, image_ind (or ~0U), is_relative, x, y, z ]. + if (p_ref) { *p_ref = 0; p_ref[1] = p_ref[2] = p_ref[3] = p_ref[4] = p_ref[5] = p_ref[6] = ~0U; } + + const char saved_char = *se; *se = 0; + const unsigned int clevel = level[ss - expr._data], clevel1 = clevel + 1; + bool is_sth, is_relative; + CImg ref; + CImg variable_name; + CImgList l_opcode; + + // Look for a single value or a pre-defined variable. + 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=='-'); + 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) 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 (ss1==se) switch (*ss) { // One-char reserved variable + case 'c' : _cimg_mp_return(reserved_label['c']!=~0U?reserved_label['c']:_cimg_mp_slot_c); + case 'd' : _cimg_mp_return(reserved_label['d']!=~0U?reserved_label['d']:20); + case 'e' : _cimg_mp_return(reserved_label['e']!=~0U?reserved_label['e']:27); + case 'h' : _cimg_mp_return(reserved_label['h']!=~0U?reserved_label['h']:19); + case 'l' : _cimg_mp_return(reserved_label['l']!=~0U?reserved_label['l']:26); + case 'r' : _cimg_mp_return(reserved_label['r']!=~0U?reserved_label['r']:22); + case 's' : _cimg_mp_return(reserved_label['s']!=~0U?reserved_label['s']:21); + case 't' : _cimg_mp_return(reserved_label['t']!=~0U?reserved_label['t']:17); + case 'w' : _cimg_mp_return(reserved_label['w']!=~0U?reserved_label['w']:18); + case 'x' : _cimg_mp_return(reserved_label['x']!=~0U?reserved_label['x']:_cimg_mp_slot_x); + case 'y' : _cimg_mp_return(reserved_label['y']!=~0U?reserved_label['y']:_cimg_mp_slot_y); + case 'z' : _cimg_mp_return(reserved_label['z']!=~0U?reserved_label['z']:_cimg_mp_slot_z); + case 'u' : + if (reserved_label['u']!=~0U) _cimg_mp_return(reserved_label['u']); + _cimg_mp_scalar2(mp_u,0,1); + case 'g' : + if (reserved_label['g']!=~0U) _cimg_mp_return(reserved_label['g']); + _cimg_mp_scalar0(mp_g); + case 'i' : + if (reserved_label['i']!=~0U) _cimg_mp_return(reserved_label['i']); + _cimg_mp_scalar0(mp_i); + case 'I' : + _cimg_mp_op("Variable 'I'"); + if (reserved_label['I']!=~0U) _cimg_mp_return(reserved_label['I']); + 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); + _cimg_mp_return(pos); + case 'R' : + if (reserved_label['R']!=~0U) _cimg_mp_return(reserved_label['R']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,0,0); + case 'G' : + if (reserved_label['G']!=~0U) _cimg_mp_return(reserved_label['G']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,0,0); + case 'B' : + if (reserved_label['B']!=~0U) _cimg_mp_return(reserved_label['B']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,0,0); + case 'A' : + if (reserved_label['A']!=~0U) _cimg_mp_return(reserved_label['A']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,0,0); + } + else if (ss2==se) { // Two-chars reserved variable + arg1 = arg2 = ~0U; + if (*ss=='w' && *ss1=='h') // wh + _cimg_mp_return(reserved_label[0]!=~0U?reserved_label[0]:23); + if (*ss=='p' && *ss1=='i') // pi + _cimg_mp_return(reserved_label[3]!=~0U?reserved_label[3]:28); + if (*ss=='i') { + if (*ss1>='0' && *ss1<='9') { // i0...i9 + pos = 19 + *ss1 - '0'; + if (reserved_label[pos]!=~0U) _cimg_mp_return(reserved_label[pos]); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 19,0,0); + } + switch (*ss1) { + case 'm' : arg1 = 4; arg2 = 0; break; // im + case 'M' : arg1 = 5; arg2 = 1; break; // iM + case 'a' : arg1 = 6; arg2 = 2; break; // ia + case 'v' : arg1 = 7; arg2 = 3; break; // iv + case 's' : arg1 = 8; arg2 = 12; break; // is + case 'p' : arg1 = 9; arg2 = 13; break; // 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; + _cimg_mp_return(mem_img_median); + break; + } + } + else if (*ss1=='m') switch (*ss) { + case 'x' : arg1 = 11; arg2 = 4; break; // xm + case 'y' : arg1 = 12; arg2 = 5; break; // ym + case 'z' : arg1 = 13; arg2 = 6; break; // zm + case 'c' : arg1 = 14; arg2 = 7; break; // cm + } + else if (*ss1=='M') switch (*ss) { + case 'x' : arg1 = 15; arg2 = 8; break; // xM + case 'y' : arg1 = 16; arg2 = 9; break; // yM + case 'z' : arg1 = 17; arg2 = 10; break; // zM + case 'c' : arg1 = 18; arg2 = 11; break; // cM + } + if (arg1!=~0U) { + if (reserved_label[arg1]!=~0U) _cimg_mp_return(reserved_label[arg1]); + if (!img_stats) { + img_stats.assign(1,14,1,1,0).fill(imgin.get_stats(),false); + mem_img_stats.assign(1,14,1,1,~0U); + } + if (mem_img_stats[arg2]==~0U) mem_img_stats[arg2] = constant(img_stats[arg2]); + _cimg_mp_return(mem_img_stats[arg2]); + } + } else if (ss3==se) { // Three-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d') // whd + _cimg_mp_return(reserved_label[1]!=~0U?reserved_label[1]:24); + } else if (ss4==se) { // Four-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s') // whds + _cimg_mp_return(reserved_label[2]!=~0U?reserved_label[2]:25); + } + + pos = ~0U; + is_sth = false; + for (s0 = ss, s = ss1; s='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg2)?4:2; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + if (_cimg_mp_is_vector(arg2)) + set_variable_vector(arg2); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + } + + + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), + arg2,p1,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), + arg2,p1,arg1,_cimg_mp_size(arg2)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg1,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ss1=='(' && *ve1==')') { // i/j/I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) is_parallelizable = false; + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + } else if (s1='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg5)?5:3; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + p_ref[4] = arg2; + p_ref[5] = arg3; + p_ref[6] = arg4; + if (_cimg_mp_is_vector(arg5)) + set_variable_vector(arg5); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg5)) memtype[arg5] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2; + if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -2; + } + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg5,p1,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), + arg5,p1,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), + arg5,p1,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg5,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg5,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg5,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } + _cimg_mp_return(arg5); + } + } + + // Assign vector value (direct). + if (l_variable_name>3 && *ve1==']' && *ss!='[') { + s0 = ve1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + is_sth = true; // is_valid_variable_name? + if (*ss>='0' && *ss<='9') is_sth = false; + else for (ns = ss; nsss) { + 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 + _cimg_mp_check_type(arg3,2,1,0); + + if (variable_name[1]) { // Multi-char variable + cimglist_for(variable_def,i) if (!std::strcmp(variable_name,variable_def[i])) { + arg1 = variable_pos[i]; break; + } + } else arg1 = reserved_label[*variable_name]; // Single-char variable + if (arg1==~0U) compile(ss,s0 - 1,depth1,0,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); + } + } + } + + // 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 + 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 && cimg::is_blank(*s)) ++s; + CImg(s,(unsigned int)(se - s + 1)).move_to(macro_body,0); + + p1 = 1; // Indice of current parsed argument + for (s = s0 + 1; s<=s1; ++p1, s = ns + 1) { // Parse function arguments + if (p1>24) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Too much specified arguments (>24) in macro " + "definition '%s()', in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + while (*s && 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; nsexpr._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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + is_sth?"Empty":"Invalid",p1, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (ns==s1 || *ns==',') { // New argument found + *s3 = 0; + p2 = (unsigned int)(s3 - s2); // Argument length + for (ps = std::strstr(macro_body[0],s2); ps; ps = std::strstr(ps,s2)) { // Replace by arg number + if (!((ps>macro_body[0]._data && is_varchar(*(ps - 1))) || + (ps + 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 (cimg::is_blank(*s0)) ++s0; + variable_name.resize(variable_name.end() - s0,1,1,1,0,0,1); + } + + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + // Assign variable (direct). + if (is_sth) { + arg3 = variable_name[1]?~0U:*variable_name; // One-char variable + if (variable_name[1] && !variable_name[2]) { // Two-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + if (c1=='w' && c2=='h') arg3 = 0; // wh + else if (c1=='p' && c2=='i') arg3 = 3; // pi + else if (c1=='i') { + if (c2>='0' && c2<='9') arg3 = 19 + c2 - '0'; // i0...i9 + else if (c2=='m') arg3 = 4; // im + else if (c2=='M') arg3 = 5; // iM + else if (c2=='a') arg3 = 6; // ia + else if (c2=='v') arg3 = 7; // iv + else if (c2=='s') arg3 = 8; // is + else if (c2=='p') arg3 = 9; // ip + else if (c2=='c') arg3 = 10; // ic + } else if (c2=='m') { + if (c1=='x') arg3 = 11; // xm + else if (c1=='y') arg3 = 12; // ym + else if (c1=='z') arg3 = 13; // zm + else if (c1=='c') arg3 = 14; // cm + } else if (c2=='M') { + if (c1=='x') arg3 = 15; // xM + else if (c1=='y') arg3 = 16; // yM + else if (c1=='z') arg3 = 17; // zM + else if (c1=='c') arg3 = 18; // cM + } + } else if (variable_name[1] && variable_name[2] && !variable_name[3]) { // Three-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + if (c1=='w' && c2=='h' && c3=='d') arg3 = 1; // whd + } else if (variable_name[1] && variable_name[2] && variable_name[3] && + !variable_name[4]) { // Four-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + c4 = variable_name[3]; + if (c1=='w' && c2=='h' && c3=='d' && c4=='s') arg3 = 2; // whds + } else if (!std::strcmp(variable_name,"interpolation")) arg3 = 29; // interpolation + else if (!std::strcmp(variable_name,"boundary")) arg3 = 30; // boundary + + arg1 = ~0U; + arg2 = compile(s + 1,se,depth1,0,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; } + + if (arg1==~0U) { // Create new variable + if (_cimg_mp_is_vector(arg2)) { // Vector variable + arg1 = is_comp_vector(arg2)?arg2:vector_copy(arg2); + set_variable_vector(arg1); + } else { // Scalar variable + if (is_const) arg1 = arg2; + else { + arg1 = _cimg_mp_is_comp(arg2)?arg2:scalar1(mp_copy,arg2); + memtype[arg1] = -1; + } + } + + if (arg3!=~0U) reserved_label[arg3] = arg1; + else { + if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); + variable_pos[variable_def._width] = arg1; + variable_name.move_to(variable_def); + } + + } else { // Variable already exists -> assign a new value + if (is_const || _cimg_mp_is_constant(arg1)) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid assignment of %sconst variable '%s'%s, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"already-defined ":"non-", + variable_name._data, + !_cimg_mp_is_constant(arg1) && is_const?" as a new const variable":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + _cimg_mp_check_type(arg2,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_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)). + move_to(code); + else // From scalar + CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg2). + move_to(code); + } else // Scalar + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + } + _cimg_mp_return(arg1); + } + + // Assign lvalue (variable name was not valid for a direct assignment). + arg1 = ~0U; + is_sth = (bool)std::strchr(variable_name,'?'); // Contains_ternary_operator? + if (is_sth) break; // Do nothing and make ternary operator prioritary over assignment + + if (l_variable_name>2 && (std::strchr(variable_name,'(') || std::strchr(variable_name,'['))) { + ref.assign(7); + arg1 = compile(ss,s,depth1,ref,is_single); // Lvalue slot + arg2 = compile(s + 1,se,depth1,0,is_single); // 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). + move_to(code); + _cimg_mp_return(arg2); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg3).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg2,p1,arg3,arg4,arg5,arg6).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg2,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg3,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg2,arg3,arg4,arg5).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg2,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V = value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) // From vector + CImg::vector((ulongT)mp_vector_copy,arg1,arg2,(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). + move_to(code); + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s = scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + } + + // No assignment expressions match -> error + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + arg1!=~0U && _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Apply unary/binary/ternary operators. The operator precedences should be the same as in C++. + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='*' || *ps=='/' || *ps=='^') && *ns==*ps && + level[s - expr._data]==clevel) { // Self-operators for complex numbers only (**=,//=,^^=) + _cimg_mp_op(*ps=='*'?"Operator '**='":*ps=='/'?"Operator '//='":"Operator '^^='"); + + ref.assign(7); + arg1 = compile(ss,ns,depth1,ref,is_single); // Vector slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Right operand + _cimg_mp_check_type(arg1,1,2,2); + _cimg_mp_check_type(arg2,2,3,2); + if (_cimg_mp_is_vector(arg2)) { // Complex **= complex + if (*ps=='*') + CImg::vector((ulongT)mp_complex_mul,arg1,arg1,arg2).move_to(code); + else if (*ps=='/') + CImg::vector((ulongT)mp_complex_div_vv,arg1,arg1,arg2).move_to(code); + else + CImg::vector((ulongT)mp_complex_pow_vv,arg1,arg1,arg2).move_to(code); + } else { // Complex **= scalar + if (*ps=='*') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_mul,arg2); + } else if (*ps=='/') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_div,arg2); + } else { + if (arg2==1) _cimg_mp_return(arg1); + CImg::vector((ulongT)mp_complex_pow_vs,arg1,arg1,arg2).move_to(code); + } + } + + // Write computed value back in image if necessary. + if (*ref==4) { // Image value (vector): I/J[_#ind,off] **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + + } else if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + } + + _cimg_mp_return(arg1); + } + + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || *ps=='%' || + *ps=='&' || *ps=='^' || *ps=='|' || + (*ps=='>' && *ns=='>') || (*ps=='<' && *ns=='<')) && + level[s - expr._data]==clevel) { // Self-operators (+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=) + switch (*ps) { + case '+' : op = mp_self_add; _cimg_mp_op("Operator '+='"); break; + case '-' : op = mp_self_sub; _cimg_mp_op("Operator '-='"); break; + case '*' : op = mp_self_mul; _cimg_mp_op("Operator '*='"); break; + case '/' : op = mp_self_div; _cimg_mp_op("Operator '/='"); break; + case '%' : op = mp_self_modulo; _cimg_mp_op("Operator '%='"); break; + case '<' : op = mp_self_bitwise_left_shift; _cimg_mp_op("Operator '<<='"); break; + case '>' : op = mp_self_bitwise_right_shift; _cimg_mp_op("Operator '>>='"); break; + case '&' : op = mp_self_bitwise_and; _cimg_mp_op("Operator '&='"); break; + case '|' : op = mp_self_bitwise_or; _cimg_mp_op("Operator '|='"); break; + default : op = mp_self_pow; _cimg_mp_op("Operator '^='"); break; + } + s1 = *ps=='>' || *ps=='<'?ns:ps; + + ref.assign(7); + arg1 = compile(ss,s1,depth1,ref,is_single); // Variable slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Value to apply + + // Check for particular case to be simplified. + if ((op==mp_self_add || op==mp_self_sub) && !arg2) _cimg_mp_return(arg1); + if ((op==mp_self_mul || op==mp_self_div) && arg2==1) _cimg_mp_return(arg1); + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k] += scalar + _cimg_mp_check_type(arg2,2,1,0); + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(arg1); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + _cimg_mp_return(arg1); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V += value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) self_vector_v(arg1,op,arg2); // Vector += vector + else self_vector_s(arg1,op,arg2); // Vector += scalar + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s += scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + + variable_name.assign(ss,(unsigned int)(s - ss)).back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + for (s = ss1; s::vector((ulongT)mp_if,pos,arg1,arg2,arg3, + p3 - p2,code._width - p3,arg4).move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='|' && *ns=='|' && level[s - expr._data]==clevel) { // Logical or ('||') + _cimg_mp_op("Operator '||'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] || mem[arg2]); + if (!arg1) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_or,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='&' && *ns=='&' && level[s - expr._data]==clevel) { // Logical and ('&&') + _cimg_mp_op("Operator '&&'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] && mem[arg2]); + if (arg1>0 && arg1<=16) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_and,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se2; s>ss; --s) + if (*s=='|' && level[s - expr._data]==clevel) { // Bitwise or ('|') + _cimg_mp_op("Operator '|'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_vector2_sv(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1] | (longT)mem[arg2]); + if (!arg2) _cimg_mp_return(arg1); + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_scalar2(mp_bitwise_or,arg1,arg2); + } + + for (s = se2; s>ss; --s) + if (*s=='&' && level[s - expr._data]==clevel) { // Bitwise and ('&') + _cimg_mp_op("Operator '&'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_bitwise_and,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='!' && *ns=='=' && level[s - expr._data]==clevel) { // Not equal to ('!=') + _cimg_mp_op("Operator '!='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]!=mem[arg2]); + _cimg_mp_scalar2(mp_neq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='=' && *ns=='=' && level[s - expr._data]==clevel) { // Equal to ('==') + _cimg_mp_op("Operator '=='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]==mem[arg2]); + _cimg_mp_scalar2(mp_eq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='=' && level[s - expr._data]==clevel) { // Less or equal than ('<=') + _cimg_mp_op("Operator '<='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_lte,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='=' && level[s - expr._data]==clevel) { // Greater or equal than ('>=') + _cimg_mp_op("Operator '>='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_gte,arg1,arg2); + } + + for (s = se2, ns = se1, ps = se3; s>ss; --s, --ns, --ps) + if (*s=='<' && *ns!='<' && *ps!='<' && level[s - expr._data]==clevel) { // Less than ('<') + _cimg_mp_op("Operator '<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 ('>') + _cimg_mp_op("Operator '>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_gt,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='<' && level[s - expr._data]==clevel) { // Left bit shift ('<<') + _cimg_mp_op("Operator '<<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_left_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_left_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]<<(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_left_shift,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='>' && level[s - expr._data]==clevel) { // Right bit shift ('>>') + _cimg_mp_op("Operator '>>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_right_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_right_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]>>(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_right_shift,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='+' && (*ns!='+' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Addition ('+') + _cimg_mp_op("Operator '+'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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' + 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]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_linear_add,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_increment,arg1); + if (arg1==1) _cimg_mp_scalar1(mp_increment,arg2); + _cimg_mp_scalar2(mp_add,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='-' && (*ns!='-' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Subtraction ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_sub,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_vector1_v(mp_minus,arg2); + _cimg_mp_vector2_sv(mp_sub,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] - mem[arg2]); + if (!arg1) _cimg_mp_scalar1(mp_minus,arg2); + if (code) { // Try to spot linear cases 'a*b - c' and 'c - a*b' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)(arg3==arg1?mp_linear_sub_left:mp_linear_sub_right), + arg3,arg4,arg5,arg3==arg1?arg2:arg1).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_decrement,arg1); + _cimg_mp_scalar2(mp_sub,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='*' && *ns=='*' && level[s - expr._data]==clevel) { // Complex multiplication ('**') + _cimg_mp_op("Operator '**'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + pos = vector(2); + CImg::vector((ulongT)mp_complex_mul,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='/' && *ns=='/' && level[s - expr._data]==clevel) { // Complex division ('//') + _cimg_mp_op("Operator '//'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + _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); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]); + if (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='*' && level[s - expr._data]==clevel) { // Multiplication ('*') + _cimg_mp_op("Operator '*'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + p2 = _cimg_mp_size(arg2); + if (p2>0 && _cimg_mp_size(arg1)==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); + _cimg_mp_return(pos); + } + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_mul,arg1,arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + + if (code) { // Try to spot double multiplication 'a*b*c' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_mul2,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='/' && level[s - expr._data]==clevel) { // Division ('/') + _cimg_mp_op("Operator '/'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2, ns = se1; s>ss; --s, --ns) + if (*s=='%' && *ns!='^' && level[s - expr._data]==clevel) { // Modulo ('%') + _cimg_mp_op("Operator '%'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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])); + _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)); + } + + if (*ss=='-' && (*ss1!='-' || (ss2='0' && *ss2<='9'))) { // Unary minus ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_minus,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(-mem[arg1]); + _cimg_mp_scalar1(mp_minus,arg1); + } + + if (*ss=='!') { // Logical not ('!') + _cimg_mp_op("Operator '!'"); + if (*ss1=='!') { // '!!expr' optimized as 'bool(expr)' + arg1 = compile(ss2,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_logical_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(!mem[arg1]); + _cimg_mp_scalar1(mp_logical_not,arg1); + } + + if (*ss=='~') { // Bitwise not ('~') + _cimg_mp_op("Operator '~'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bitwise_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(~(unsigned int)mem[arg1]); + _cimg_mp_scalar1(mp_bitwise_not,arg1); + } + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='^' && *ns=='^' && level[s - expr._data]==clevel) { // Complex power ('^^') + _cimg_mp_op("Operator '^^'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + pos = vector(2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vv,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vs,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::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); + _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); + _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])); + switch (arg2) { + case 0 : _cimg_mp_return(1); + case 2 : _cimg_mp_scalar1(mp_sqr,arg1); + case 3 : _cimg_mp_scalar1(mp_pow3,arg1); + case 4 : _cimg_mp_scalar1(mp_pow4,arg1); + default : + if (_cimg_mp_is_constant(arg2)) { + if (mem[arg2]==0.5) { _cimg_mp_scalar1(mp_sqrt,arg1); } + else if (mem[arg2]==0.25) { _cimg_mp_scalar1(mp_pow0_25,arg1); } + } + _cimg_mp_scalar2(mp_pow,arg1,arg2); + } + } + + // Percentage computation. + if (*se1=='%') { + arg1 = compile(ss,se1,depth1,0,is_single); + arg2 = _cimg_mp_is_constant(arg1)?0:constant(100); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(mp_div,arg1,arg2); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(mem[arg1]/100); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + is_sth = ss1ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { // Pre/post-decrement and increment + if ((is_sth && *ss=='+') || (!is_sth && *se1=='+')) { + _cimg_mp_op("Operator '++'"); + op = mp_self_increment; + } else { + _cimg_mp_op("Operator '--'"); + op = mp_self_decrement; + } + ref.assign(7); + arg1 = is_sth?compile(ss2,se,depth1,ref,is_single): + compile(ss,se2,depth1,ref,is_single); // Variable slot + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (is_sth) pos = arg1; // Determine return indice, depending on pre/post action + else { + if (_cimg_mp_is_vector(arg1)) pos = vector_copy(arg1); + else pos = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k]++ + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,1).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(pos); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off]++ + if (!is_single) 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); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off]++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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(pos); + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg1,arg3,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V++ + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s++ + CImg::vector((ulongT)op,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (is_sth) variable_name.assign(ss2,(unsigned int)(se - ss1)); + else variable_name.assign(ss,(unsigned int)(se1 - ss)); + variable_name.back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'. + if (*se1==']' && *ss!='[') { + _cimg_mp_op("Value accessor '[]'"); + is_relative = *ss=='j' || *ss=='J'; + s0 = s1 = std::strchr(ss,'['); if (s0) { do { --s1; } while (cimg::is_blank(*s1)); cimg::swap(*s0,*++s1); } + + if ((*ss=='I' || *ss=='J') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a vector + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff), + pos,p1,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Joff:mp_Ioff), + pos,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + if ((*ss=='i' || *ss=='j') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + if (s0>ss) { // Vector value + arg1 = compile(ss,s0,depth1,0,is_single); + if (_cimg_mp_is_scalar(arg1)) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Array brackets used on non-vector variable '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + + } + s1 = s0 + 1; while (s1 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); + arg3 = (unsigned int)mem[arg3]; + pos = vector(arg3); + CImg::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + // One argument -> vector value reference + arg2 = compile(++s0,se1,depth1,0,is_single); + if (_cimg_mp_is_constant(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::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'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data,nb, + variable_name._data,_cimg_mp_size(arg1), + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (p_ref) { + *p_ref = 1; + p_ref[1] = arg1; + p_ref[2] = arg2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; // Prevent from being used in further optimization + } + pos = scalar3(mp_vector_off,arg1,_cimg_mp_size(arg1),arg2); + memtype[pos] = -2; // Prevent from being used in further optimization + _cimg_mp_return(pos); + } + } + + // Look for a function call, an access to image value, or a parenthesis. + if (*se1==')') { + if (*ss=='(') _cimg_mp_return(compile(ss1,se1,depth1,p_ref,is_single)); // Simple parentheses + _cimg_mp_op("Value accessor '()'"); + is_relative = *ss=='j' || *ss=='J'; + 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 (s01) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + if (s1::vector((ulongT)(is_relative?mp_list_Jxyz:mp_list_Ixyz), + pos,p1,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Jxyz:mp_Ixyz), + pos,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + // i/j(_#ind,_x,_y,_z,_c,_interpolation,_boundary_conditions) + if ((*ss=='i' || *ss=='j') && *ss1=='(') { // Image value as scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + if (s1::vector((ulongT)mp_arg,0,0,p2,arg1,arg2).move_to(l_opcode); + for (s = ++s2; s::vector(arg3).move_to(l_opcode); + ++p3; + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (_cimg_mp_is_constant(arg1)) { + p3-=1; // Number of args + arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]); + if (arg1::vector((ulongT)mp_break,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"breakpoint(",11)) { // Break point (for abort test) + _cimg_mp_op("Function 'breakpoint()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_breakpoint,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"bool(",5)) { // Boolean cast + _cimg_mp_op("Function 'bool()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + + if (!std::strncmp(ss,"begin(",6)) { // Begin + _cimg_mp_op("Function 'begin()'"); + code.swap(code_begin); + arg1 = compile(ss6,se1,depth1,p_ref,true); + code.swap(code_begin); + _cimg_mp_return(arg1); + } + break; + + case 'c' : + if (!std::strncmp(ss,"cabs(",5)) { // Complex absolute value + _cimg_mp_op("Function 'cabs()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_complex_abs,arg1 + 1,arg1 + 2); + } + + if (!std::strncmp(ss,"carg(",5)) { // Complex argument + _cimg_mp_op("Function 'carg()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_atan2,arg1 + 2,arg1 + 1); + } + + if (!std::strncmp(ss,"cats(",5)) { // Concatenate strings + _cimg_mp_op("Function 'cats()'"); + CImg::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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cbrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::cbrt(mem[arg1])); + _cimg_mp_scalar1(mp_cbrt,arg1); + } + + if (!std::strncmp(ss,"cconj(",6)) { // Complex conjugate + _cimg_mp_op("Function 'cconj()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_conj,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ceil(",5)) { // Ceil + _cimg_mp_op("Function 'ceil()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + pos = vector(2); + CImg::vector((ulongT)mp_complex_exp,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"clog(",5)) { // Complex logarithm + _cimg_mp_op("Function 'clog()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_log,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"continue(",9)) { // Complex absolute value + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_continue,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"copy(",5)) { // Memory copy + _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 = constant(_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); + _cimg_mp_check_type(arg6,5,1,0); + 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); + } + + if (!std::strncmp(ss,"cos(",4)) { // Cosine + _cimg_mp_op("Function 'cos()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cos,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cos(mem[arg1])); + _cimg_mp_scalar1(mp_cos,arg1); + } + + if (!std::strncmp(ss,"cosh(",5)) { // Hyperbolic cosine + _cimg_mp_op("Function 'cosh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cosh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cosh(mem[arg1])); + _cimg_mp_scalar1(mp_cosh,arg1); + } + + if (!std::strncmp(ss,"critical(",9)) { // Critical section (single thread at a time) + _cimg_mp_op("Function 'critical()'"); + p1 = code._width; + arg1 = compile(ss + 9,se1,depth1,p_ref,true); + CImg::vector((ulongT)mp_critical,arg1,code._width - p1).move_to(code,p1); + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"crop(",5)) { // Image crop + _cimg_mp_op("Function 'crop()'"); + 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); + } + 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 + 2; + break; + case 3 : + CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode); + arg1 = arg2 + 2; + break; + case 4 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,_cimg_mp_boundary). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + break; + case 5 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,opcode[4]). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + 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 = arg2 + (is_sth?2:4); + break; + case 7 : + CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, + opcode[6]).move_to(opcode); + arg1 = arg2 + (is_sth?2:4); + 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 = arg2 + (is_sth?2:5); + break; + case 9 : + arg1 = arg2 + (is_sth?2:5); + 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); + 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()?"...":""); + } + 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); + _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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"cut(",4)) { // Cut + _cimg_mp_op("Function 'cut()'"); + s1 = ss4; while (s1val2?val2:val); + } + _cimg_mp_scalar3(mp_cut,arg1,arg2,arg3); + } + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_d,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"date(",5)) { // Current date or file date + _cimg_mp_op("Function 'date()'"); + s1 = ss5; while (s1::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_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); + *se1 = 0; + variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + ((CImg::vector((ulongT)mp_debug,arg1,0,code._width - p1), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code,p1); + *se1 = ')'; + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"display(",8)) { // Display memory, vector or image + _cimg_mp_op("Function 'display()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_display_memory,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + if (*ss8!='#') { // Vector + s1 = ss8; while (s1::string(ss8,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + if (_cimg_mp_is_vector(arg1)) + ((CImg::vector((ulongT)mp_vector_print,arg1,0,(ulongT)_cimg_mp_size(arg1),0), + variable_name)>'y').move_to(opcode); + else + ((CImg::vector((ulongT)mp_print,arg1,0,0), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + + ((CImg::vector((ulongT)mp_display,arg1,0,(ulongT)_cimg_mp_size(arg1), + arg2,arg3,arg4,arg5), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + *s1 = c1; + _cimg_mp_return(arg1); + + } else { // Image + p1 = compile(ss8 + 1,se1,depth1,0,is_single); + _cimg_mp_check_list(true); + CImg::vector((ulongT)mp_image_display,_cimg_mp_slot_nan,p1).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"det(",4)) { // Matrix determinant + _cimg_mp_op("Function 'det()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + } + + if (!std::strncmp(ss,"diag(",5)) { // Diagonal matrix + _cimg_mp_op("Function 'diag()'"); + 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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"dot(",4)) { // Dot product + _cimg_mp_op("Function 'dot()'"); + s1 = ss4; while (s1::vector((ulongT)mp_do,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_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; + } else { + if (s0::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; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"eig(",4)) { // Matrix eigenvalues/eigenvector + _cimg_mp_op("Function 'eig()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + _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); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"ellipse(",8)) { // Ellipse/circle drawing + if (!is_single) 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 (!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,"fact(",5)) { // Factorial + _cimg_mp_op("Function 'fact()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + 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])); + _cimg_mp_scalar1(mp_fibonacci,arg1); + } + + if (!std::strncmp(ss,"find(",5)) { // Find + _cimg_mp_op("Function 'find()'"); + + // First argument: data to look at. + s0 = ss5; while (s0::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); + _cimg_mp_return(p3); + } + + if (!std::strncmp(ss,"floor(",6)) { // Floor + _cimg_mp_op("Function 'floor()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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_mp_return(pos); + } + break; + + case 'g' : + if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function + _cimg_mp_op("Function 'gauss()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_h,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'i' : + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_median,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (*ss1=='f' && *ss2=='(') { // If..then[..else.] + _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); + _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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_int,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((longT)mem[arg1]); + _cimg_mp_scalar1(mp_int,arg1); + } + + if (!std::strncmp(ss,"inv(",4)) { // Matrix/scalar inversion + _cimg_mp_op("Function 'inv()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) { + _cimg_mp_check_matrix_square(arg1,1); + p1 = (unsigned int)cimg::round(std::sqrt((float)_cimg_mp_size(arg1))); + pos = vector(p1*p1); + CImg::vector((ulongT)mp_matrix_inv,pos,arg1,p1).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(1/mem[arg1]); + _cimg_mp_scalar2(mp_div,1,arg1); + } + + if (*ss1=='s') { // Family of 'is_?()' functions + + if (!std::strncmp(ss,"isbool(",7)) { // Is boolean? + _cimg_mp_op("Function 'isbool()'"); + if (ss7==se1) _cimg_mp_return(0); + arg1 = compile(ss7,se1,depth1,0,is_single); + 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. || 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); + } + + if (!std::strncmp(ss,"isfile(",7)) { // Is file? + _cimg_mp_op("Function 'isfile()'"); + *se1 = 0; + is_sth = cimg::is_file(ss7); + *se1 = ')'; + _cimg_mp_return(is_sth?1U:0U); + } + + if (!std::strncmp(ss,"isin(",5)) { // Is in sequence/vector? + if (ss5>=se1) _cimg_mp_return(0); + _cimg_mp_op("Function 'isin()'"); + pos = scalar(); + CImg::vector((ulongT)mp_isin,pos,0).move_to(l_opcode); + 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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"isinf(",6)) { // Is infinite? + _cimg_mp_op("Function 'isinf()'"); + if (ss6==se1) _cimg_mp_return(0); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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)); + _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); + 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])); + _cimg_mp_scalar1(mp_isnan,arg1); + } + + if (!std::strncmp(ss,"isval(",6)) { // Is value? + _cimg_mp_op("Function 'isval()'"); + val = 0; + if (cimg_sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1); + _cimg_mp_return(0); + } + + } + break; + + case 'l' : + if (*ss1=='(') { // Size of image list + _cimg_mp_op("Function 'l()'"); + if (ss2!=se1) break; + _cimg_mp_scalar0(mp_list_l); + } + + if (!std::strncmp(ss,"log(",4)) { // Natural logarithm + _cimg_mp_op("Function 'log()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log(mem[arg1])); + _cimg_mp_scalar1(mp_log,arg1); + } + + if (!std::strncmp(ss,"log2(",5)) { // Base-2 logarithm + _cimg_mp_op("Function 'log2()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log2,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::log2(mem[arg1])); + _cimg_mp_scalar1(mp_log2,arg1); + } + + if (!std::strncmp(ss,"log10(",6)) { // Base-10 logarithm + _cimg_mp_op("Function 'log10()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log10,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log10(mem[arg1])); + _cimg_mp_scalar1(mp_log10,arg1); + } + + if (!std::strncmp(ss,"lowercase(",10)) { // Lower case + _cimg_mp_op("Function 'lowercase()'"); + arg1 = compile(ss + 10,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_lowercase,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::lowercase(mem[arg1])); + _cimg_mp_scalar1(mp_lowercase,arg1); + } + break; + + case 'm' : + if (!std::strncmp(ss,"mul(",4)) { // Matrix multiplication + _cimg_mp_op("Function 'mul()'"); + s1 = ss4; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'n' : + if (!std::strncmp(ss,"narg(",5)) { // Number of arguments + _cimg_mp_op("Function 'narg()'"); + if (ss5>=se1) _cimg_mp_return(0); + arg1 = 0; + for (s = ss5; s::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::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); + if (arg1>0 && opcode._height==4) // Special case with one argument and p>=1 + _cimg_mp_scalar1(mp_abs,opcode[3]); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'p' : + if (!std::strncmp(ss,"permut(",7)) { // Number of permutations + _cimg_mp_op("Function 'permut()'"); + s1 = ss7; while (s1::vector((ulongT)mp_polygon,_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 (!std::strncmp(ss,"print(",6) || !std::strncmp(ss,"prints(",7)) { // Print expressions + is_sth = ss[5]=='s'; // is prints() + _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); + 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()?"...":""); + } + 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,"resize(",7)) { // Vector or image resize + _cimg_mp_op("Function 'resize()'"); + if (*ss7!='#') { // Vector + s1 = ss7; while (s1::vector((ulongT)mp_vector_resize,pos,arg2,arg1,(ulongT)_cimg_mp_size(arg1), + arg3,arg4).move_to(code); + _cimg_mp_return(pos); + + } else { // Image + if (!is_single) 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); + pos = 0; + for (s = s0; s10) { + *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 arguments, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + pos<1?"Missing":"Too much", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + l_opcode[0].move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"reverse(",8)) { // Vector reverse + _cimg_mp_op("Function 'reverse()'"); + arg1 = compile(ss8,se1,depth1,0,is_single); + 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); + _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 (s11) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + arg4 = compile(++s1,se1,depth1,0,is_single); + } else { + s2 = s1 + 1; while (s2::vector((ulongT)mp_rot3d,pos,arg1,arg2,arg3,arg4).move_to(code); + } else { // 2D rotation + _cimg_mp_check_type(arg1,1,1,0); + pos = vector(4); + CImg::vector((ulongT)mp_rot2d,pos,arg1).move_to(code); + } + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"round(",6)) { // Value rounding + _cimg_mp_op("Function 'round()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_s,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"same(",5)) { // Test if operands have the same values + _cimg_mp_op("Function 'same()'"); + s1 = ss5; while (s1::vector((ulongT)mp_shift,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sign(",5)) { // Sign + _cimg_mp_op("Function 'sign()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sign,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sign(mem[arg1])); + _cimg_mp_scalar1(mp_sign,arg1); + } + + if (!std::strncmp(ss,"sin(",4)) { // Sine + _cimg_mp_op("Function 'sin()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sin,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sin(mem[arg1])); + _cimg_mp_scalar1(mp_sin,arg1); + } + + if (!std::strncmp(ss,"sinc(",5)) { // Sine cardinal + _cimg_mp_op("Function 'sinc()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinc,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sinc(mem[arg1])); + _cimg_mp_scalar1(mp_sinc,arg1); + } + + if (!std::strncmp(ss,"sinh(",5)) { // Hyperbolic sine + _cimg_mp_op("Function 'sinh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sinh(mem[arg1])); + _cimg_mp_scalar1(mp_sinh,arg1); + } + + if (!std::strncmp(ss,"size(",5)) { // Vector size + _cimg_mp_op("Function 'size()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_constant(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_size(arg1)); + } + + if (!std::strncmp(ss,"solve(",6)) { // Solve linear system + _cimg_mp_op("Function 'solve()'"); + s1 = ss6; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_solve,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sort(",5)) { // Sort vector + _cimg_mp_op("Function 'sort()'"); + 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(); + } + } + + if (!std::strncmp(ss,"sqr(",4)) { // Square + _cimg_mp_op("Function 'sqr()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqr,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sqr(mem[arg1])); + _cimg_mp_scalar1(mp_sqr,arg1); + } + + if (!std::strncmp(ss,"sqrt(",5)) { // Square root + _cimg_mp_op("Function 'sqrt()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sqrt(mem[arg1])); + _cimg_mp_scalar1(mp_sqrt,arg1); + } + + if (!std::strncmp(ss,"srand(",6)) { // Set RNG seed + _cimg_mp_op("Function 'srand()'"); + arg1 = ss6::vector((ulongT)mp_image_stats,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"stov(",5)) { // String to double + _cimg_mp_op("Function 'stov()'"); + s1 = ss5; 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()?"...":""); + } + pos = vector(p1 + p2 + p2*p2); + CImg::vector((ulongT)mp_matrix_svd,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 't' : + if (!std::strncmp(ss,"tan(",4)) { // Tangent + _cimg_mp_op("Function 'tan()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tan,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tan(mem[arg1])); + _cimg_mp_scalar1(mp_tan,arg1); + } + + if (!std::strncmp(ss,"tanh(",5)) { // Hyperbolic tangent + _cimg_mp_op("Function 'tanh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tanh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tanh(mem[arg1])); + _cimg_mp_scalar1(mp_tanh,arg1); + } + + if (!std::strncmp(ss,"trace(",6)) { // Matrix trace + _cimg_mp_op("Function 'trace()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _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); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,p2, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(p3*p2); + CImg::vector((ulongT)mp_transp,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'u' : + if (*ss1=='(') { // Random value with uniform distribution + _cimg_mp_op("Function 'u()'"); + if (*ss2==')') _cimg_mp_scalar2(mp_u,0,1); + s1 = ss2; while (s1ss6 && *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 (arg20) || + !std::strncmp(ss,"vector(",7) || + (!std::strncmp(ss,"vector",6) && ss7::sequence(arg4,arg3 + 1,arg3 + arg4).move_to(l_opcode); + arg2+=arg4; + } else { CImg::vector(arg3).move_to(l_opcode); ++arg2; } + s = ns; + } + if (arg1==~0U) arg1 = arg2; + 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); + _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)mp_vtos,pos,p1,arg1,_cimg_mp_size(arg1),arg2).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'w' : + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_w,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_wh,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss4!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whd,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss5!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whds,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"while(",6)) { // While...do + _cimg_mp_op("Function 'while()'"); + s0 = *ss5=='('?ss6:ss8; + s1 = s0; while (s1::vector((ulongT)mp_while,pos,arg1,p2 - p1,code._width - p2,arg2, + pos>=arg6 && !_cimg_mp_is_constant(pos), + arg1>=arg6 && !_cimg_mp_is_constant(arg1)).move_to(code,p1); + _cimg_mp_return(pos); + } + break; + + case 'x' : + if (!std::strncmp(ss,"xor(",4)) { // Xor + _cimg_mp_op("Function 'xor()'"); + s1 = ss4; while (s1::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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (is_sth) _cimg_mp_constant(op(*this)); + opcode.move_to(code); + _cimg_mp_return(pos); + } + + // No corresponding built-in function -> Look for a user-defined macro call. + s0 = strchr(ss,'('); + if (s0) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + + // Count number of specified arguments. + p1 = 0; + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { + while (*s && 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 + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { // Parse function arguments + while (*s && cimg::is_blank(*s)) ++s; + if (*s==')' && p1==1) break; // Function has no arguments + if (p1>p2) { ++p1; break; } + ns = s; while (ns _pexpr(_expr._width); + ns = _pexpr._data; + for (ps = _expr._data, c1 = ' '; *ps; ++ps) { + if (!cimg::is_blank(*ps)) c1 = *ps; + *(ns++) = c1; + } + *ns = 0; + + CImg _level = get_level(_expr); + expr.swap(_expr); + pexpr.swap(_pexpr); + level.swap(_level); + s0 = user_macro; + user_macro = macro_def[l]; + pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref,is_single); + user_macro = s0; + level.swap(_level); + pexpr.swap(_pexpr); + expr.swap(_expr); + _cimg_mp_return(pos); + } + + if (arg3) { // Macro name matched but number of arguments does not + CImg sig_nargs(arg3); + arg1 = 0; + cimglist_for(macro_def,l) if (!std::strcmp(macro_def[l],variable_name)) + sig_nargs[arg1++] = (unsigned int)macro_def[l].back(); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (sig_nargs._width>1) { + sig_nargs.sort(); + arg1 = sig_nargs.back(); + --sig_nargs._width; + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,sig_nargs.value_string()._data,arg1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } else + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,*sig_nargs,*sig_nargs!=1?"s":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + } + } // if (se1==')') + + // Char / string initializer. + if (*se1=='\'' && + ((se1>ss && *ss=='\'') || + (se1>ss1 && *ss=='_' && *ss1=='\''))) { + if (*ss=='_') { _cimg_mp_op("Char initializer"); s1 = ss2; } + else { _cimg_mp_op("String initializer"); s1 = ss1; } + arg1 = (unsigned int)(se1 - s1); // Original string length + if (arg1) { + CImg(s1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + if (*ss=='_') { + if (arg1==1) _cimg_mp_constant(*variable_name); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Literal %s contains more than one character, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + ss1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg1); + CImg::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); + _cimg_mp_return(pos); + } + + // Vector initializer [ ... ]. + if (*ss=='[' && *se1==']') { + _cimg_mp_op("Vector initializer"); + 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 + if (arg1) { + CImg(s1 + 1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + pos = vector(arg1); + CImg::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); + } else { // Vector values provided as list of items + arg1 = 0; // Number of specified values + if (*ss1!=']') for (s = ss1; s::sequence(arg3,arg2 + 1,arg2 + arg3).move_to(l_opcode); + arg1+=arg3; + } else { CImg::vector(arg2).move_to(l_opcode); ++arg1; } + s = ns; + } + 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); + } + _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); + _cimg_mp_return(pos); + case 'R' : // R#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0, + 0,_cimg_mp_boundary); + case 'G' : // G#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1, + 0,_cimg_mp_boundary); + case 'B' : // B#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2, + 0,_cimg_mp_boundary); + case 'A' : // A#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3, + 0,_cimg_mp_boundary); + } + } + + if (*ss1 && *ss2=='#' && ss3::vector(listin[p1].median()).move_to(list_median[p1]); + _cimg_mp_constant(*list_median[p1]); + } + _cimg_mp_scalar1(mp_list_median,arg1); + } + if (*ss1>='0' && *ss1<='9') { // i0#ind...i9#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,*ss1 - '0', + 0,_cimg_mp_boundary); + } + switch (*ss1) { + case 'm' : arg2 = 0; break; // im#ind + case 'M' : arg2 = 1; break; // iM#ind + case 'a' : arg2 = 2; break; // ia#ind + case 'v' : arg2 = 3; break; // iv#ind + case 's' : arg2 = 12; break; // is#ind + case 'p' : arg2 = 13; break; // ip#ind + } + } else if (*ss1=='m') switch (*ss) { + case 'x' : arg2 = 4; break; // xm#ind + case 'y' : arg2 = 5; break; // ym#ind + case 'z' : arg2 = 6; break; // zm#ind + case 'c' : arg2 = 7; break; // cm#ind + } else if (*ss1=='M') switch (*ss) { + case 'x' : arg2 = 8; break; // xM#ind + case 'y' : arg2 = 9; break; // yM#ind + case 'z' : arg2 = 10; break; // zM#ind + case 'c' : arg2 = 11; break; // cM#ind + } + if (arg2!=~0U) { + if (!listin) _cimg_mp_return(0); + if (_cimg_mp_is_constant(arg1)) { + if (!list_stats) list_stats.assign(listin._width); + if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(listin[p1].get_stats(),false); + _cimg_mp_constant(list_stats(p1,arg2)); + } + _cimg_mp_scalar2(mp_list_stats,arg1,arg2); + } + } + + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4 error. + is_sth = true; // is_valid_variable_name + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + *se = saved_char; + c1 = *se1; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (is_sth) + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Undefined variable '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + s1 = std::strchr(ss,'('); + s_op = s1 && c1==')'?"function call":"item"; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + s_op,variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Evaluation procedure. + double operator()(const double x, const double y, const double z, const double c) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + return *result; + } + + // Evaluation procedure (return output values in vector 'output'). + template + void operator()(const double x, const double y, const double z, const double c, t *const output) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + if (result_dim) { + const double *ptrs = result + 1; + t *ptrd = output; + for (unsigned int k = 0; k_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + + // Return type of a memory element 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); + cimg_sprintf(res._data + 6,"%u",_cimg_mp_size(arg)); + } else CImg::string("scalar").move_to(res); + return res; + } + + // Insert constant value in memory. + unsigned int constant(const double val) { + + // Search for built-in constant. + if (cimg::type::is_nan(val)) return _cimg_mp_slot_nan; + if (val==(double)(int)val) { + if (val>=0 && val<=10) return (unsigned int)val; + if (val<0 && val>=-5) return (unsigned int)(10 - val); + } + if (val==0.5) return 16; + + // Search for constant already requested before (in const cache). + unsigned int ind = ~0U; + if (constcache_size<1024) { + if (!constcache_size) { + constcache_vals.assign(16,1,1,1,0); + constcache_inds.assign(16,1,1,1,0); + *constcache_vals = val; + constcache_size = 1; + ind = 0; + } else { // Dichotomic search + const double val_beg = *constcache_vals, val_end = constcache_vals[constcache_size - 1]; + if (val_beg>=val) ind = 0; + else if (val_end==val) ind = constcache_size - 1; + else if (val_end=constcache_size || constcache_vals[ind]!=val) { + ++constcache_size; + if (constcache_size>constcache_vals._width) { + constcache_vals.resize(-200,1,1,1,0); + constcache_inds.resize(-200,1,1,1,0); + } + const int l = constcache_size - (int)ind - 1; + if (l>0) { + std::memmove(&constcache_vals[ind + 1],&constcache_vals[ind],l*sizeof(double)); + std::memmove(&constcache_inds[ind + 1],&constcache_inds[ind],l*sizeof(unsigned int)); + } + constcache_vals[ind] = val; + constcache_inds[ind] = 0; + } + } + if (constcache_inds[ind]) return constcache_inds[ind]; + } + + // Insert new constant in memory if necessary. + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(-200,1,1,1,0); } + const unsigned int pos = mempos++; + mem[pos] = val; + memtype[pos] = 1; // Set constant property + if (ind!=~0U) constcache_inds[ind] = pos; + return pos; + } + + // Insert code instructions for processing scalars. + unsigned int scalar() { // Insert new scalar in memory + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(mem._width,1,1,1,0); } + return mempos++; + } + + unsigned int scalar0(const mp_func op) { + const unsigned int pos = scalar(); + CImg::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!=~0U && 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!=~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: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: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: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: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: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: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 + if (mempos + siz>=mem._width) { + mem.resize(2*mem._width + siz,1,1,1,0); + memtype.resize(mem._width,1,1,1,0); + } + const unsigned int pos = mempos++; + mem[pos] = cimg::type::nan(); + memtype[pos] = siz + 1; + mempos+=siz; + return pos; + } + + unsigned int vector(const unsigned int siz, const double value) { // Insert new initialized vector + const unsigned int pos = vector(siz); + double *ptr = &mem[pos] + 1; + for (unsigned int i = 0; i::vector((ulongT)mp_vector_copy,pos,arg,siz).move_to(code); + return pos; + } + + void self_vector_s(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_s,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1).move_to(code[code._width - 1 - siz + k]); + } + } + + void self_vector_v(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_v,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + } + + unsigned int vector1_v(const mp_func op, const unsigned int arg1) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vs(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_sv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector3_vss(const mp_func op, const unsigned int arg1, const unsigned int arg2, + const unsigned int arg3) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2,arg3).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + // Check if a memory slot is a positive integer constant scalar value. + // 'mode' can be: + // { 0=constant | 1=integer constant | 2=positive integer constant | 3=strictly-positive integer constant } + void check_constant(const unsigned int arg, const unsigned int n_arg, + const unsigned int mode, + char *const ss, char *const se, const char saved_char) { + _cimg_mp_check_type(arg,n_arg,1,0); + if (!(_cimg_mp_is_constant(arg) && + (!mode || (double)(int)mem[arg]==mem[arg]) && + (mode<2 || mem[arg]>=(mode==3)))) { + const char *s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ": + n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth ": + n_arg==9?"Ninth ":"One of the "; + *se = saved_char; + char *const s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_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); + 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // Evaluation functions, known by the parser. + // Defining these functions 'static' ensures that sizeof(mp_func)==sizeof(ulongT), + // so we can store pointers to them directly in the opcode vectors. +#ifdef _mp_arg +#undef _mp_arg +#endif +#define _mp_arg(x) mp.mem[mp.opcode[x]] + + static double mp_abs(_cimg_math_parser& mp) { + return cimg::abs(_mp_arg(2)); + } + + static double mp_add(_cimg_math_parser& mp) { + return _mp_arg(2) + _mp_arg(3); + } + + static double mp_acos(_cimg_math_parser& mp) { + return std::acos(_mp_arg(2)); + } + + static double mp_acosh(_cimg_math_parser& mp) { + return cimg::acosh(_mp_arg(2)); + } + + static double mp_asinh(_cimg_math_parser& mp) { + return cimg::asinh(_mp_arg(2)); + } + + static double mp_atanh(_cimg_math_parser& mp) { + return cimg::atanh(_mp_arg(2)); + } + + static double mp_arg(_cimg_math_parser& mp) { + const int _ind = (int)_mp_arg(4); + const unsigned int + nb_args = (unsigned int)mp.opcode[2] - 4, + ind = _ind<0?_ind + nb_args:(unsigned int)_ind, + siz = (unsigned int)mp.opcode[3]; + if (siz>0) { + if (ind>=nb_args) std::memset(&_mp_arg(1) + 1,0,siz*sizeof(double)); + else std::memcpy(&_mp_arg(1) + 1,&_mp_arg(ind + 4) + 1,siz*sizeof(double)); + return cimg::type::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; ival) { val = _val; argval = i - 3; } + } + return (double)argval; + } + + static double mp_asin(_cimg_math_parser& mp) { + return std::asin(_mp_arg(2)); + } + + static double mp_atan(_cimg_math_parser& mp) { + return std::atan(_mp_arg(2)); + } + + static double mp_atan2(_cimg_math_parser& mp) { + return std::atan2(_mp_arg(2),_mp_arg(3)); + } + + static double mp_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; i>(unsigned int)_mp_arg(3)); + } + + static double mp_bitwise_xor(_cimg_math_parser& mp) { + return (double)((longT)_mp_arg(2) ^ (longT)_mp_arg(3)); + } + + static double mp_bool(_cimg_math_parser& mp) { + return (double)(bool)_mp_arg(2); + } + + static double mp_break(_cimg_math_parser& mp) { + mp.break_type = 1; + mp.p_code = mp.p_break - 1; + return cimg::type::nan(); + } + + static double mp_breakpoint(_cimg_math_parser& mp) { + cimg_abort_init; + cimg_abort_test; + cimg::unused(mp); + return cimg::type::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_cbrt(_cimg_math_parser& mp) { + return cimg::cbrt(_mp_arg(2)); + } + + static double mp_ceil(_cimg_math_parser& mp) { + return std::ceil(_mp_arg(2)); + } + + static double mp_complex_abs(_cimg_math_parser& mp) { + return cimg::_hypot(_mp_arg(2),_mp_arg(3)); + } + + static double mp_complex_conj(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = *(ptrs++); + *ptrd = -*(ptrs); + return cimg::type::nan(); + } + + static double mp_complex_div_sv(_cimg_math_parser& mp) { + const double + *ptr2 = &_mp_arg(3) + 1, + r1 = _mp_arg(2), + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = r1*r2/denom; + *ptrd = -r1*i2/denom; + return cimg::type::nan(); + } + + static double mp_complex_div_vv(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = (r1*r2 + i1*i2)/denom; + *ptrd = (r2*i1 - r1*i2)/denom; + return cimg::type::nan(); + } + + static double mp_complex_exp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs), er = std::exp(r); + *(ptrd++) = er*std::cos(i); + *(ptrd++) = er*std::sin(i); + return cimg::type::nan(); + } + + static double mp_complex_log(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs); + *(ptrd++) = 0.5*std::log(r*r + i*i); + *(ptrd++) = std::atan2(i,r); + return cimg::type::nan(); + } + + static double mp_complex_mul(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = r1*r2 - i1*i2; + *(ptrd++) = r1*i2 + r2*i1; + return cimg::type::nan(); + } + + static void _mp_complex_pow(const double r1, const double i1, + const double r2, const double i2, + double *ptrd) { + double ro, io; + if (cimg::abs(i2)<1e-15) { // Exponent is real + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) { + if (cimg::abs(r2)<1e-15) { ro = 1; io = 0; } + else ro = io = 0; + } else { + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2), + phio = r2*phi1; + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + } else { // Exponent is complex + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) ro = io = 0; + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2)*std::exp(-i2*phi1), + phio = r2*phi1 + 0.5*i2*std::log(mod1_2); + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + *(ptrd++) = ro; + *ptrd = io; + } + + static double mp_complex_pow_ss(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_sv(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,ptr2[0],ptr2[1],ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vs(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vv(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],ptr2[0],ptr2[1],ptrd); + return cimg::type::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_cos(_cimg_math_parser& mp) { + return std::cos(_mp_arg(2)); + } + + static double mp_cosh(_cimg_math_parser& mp) { + return std::cosh(_mp_arg(2)); + } + + static double mp_critical(_cimg_math_parser& mp) { + const double res = _mp_arg(1); + cimg_pragma_openmp(critical(mp_critical)) + { + for (const CImg *const p_end = ++mp.p_code + mp.opcode[2]; + mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + } + --mp.p_code; + return res; + } + + static double mp_crop(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const unsigned int + dx = (unsigned int)mp.opcode[7], + dy = (unsigned int)mp.opcode[8], + dz = (unsigned int)mp.opcode[9], + dc = (unsigned int)mp.opcode[10]; + const 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 (!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, + z + dz - 1,c + dc - 1, + boundary_conditions); + return cimg::type::nan(); + } + + static double mp_cross(_cimg_math_parser& mp) { + CImg + vout(&_mp_arg(1) + 1,1,3,1,1,true), + v1(&_mp_arg(2) + 1,1,3,1,1,true), + v2(&_mp_arg(3) + 1,1,3,1,1,true); + (vout = v1).cross(v2); + return cimg::type::nan(); + } + + static double mp_cut(_cimg_math_parser& mp) { + double val = _mp_arg(2), cmin = _mp_arg(3), cmax = _mp_arg(4); + return valcmax?cmax:val; + } + + 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; + } + + 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++); + cimg::strellipsize(expr); + const ulongT g_target = mp.opcode[1]; + +#ifndef cimg_use_openmp + const unsigned int n_thread = 0; +#else + const unsigned int n_thread = omp_get_thread_num(); +#endif + cimg_pragma_openmp(critical(mp_debug)) + { + std::fprintf(cimg::output(), + "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" + "Start debugging expression '%s', code length %u -> mem[%u] (memsize: %u)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)mp.opcode[3],(unsigned int)g_target,mp.mem._width); + std::fflush(cimg::output()); + mp.debug_indent+=3; + } + const CImg *const p_end = (++mp.p_code) + mp.opcode[3]; + CImg _op; + for ( ; mp.p_code &op = *mp.p_code; + mp.opcode._data = op._data; + + _op.assign(1,op._height - 1); + const ulongT *ptrs = op._data + 1; + for (ulongT *ptrd = _op._data, *const ptrde = _op._data + _op._height; ptrd mem[%u] = %g", + (void*)&mp,n_thread,mp.debug_indent,' ', + (void*)mp.opcode._data,(void*)*mp.opcode,_op.value_string().data(), + (unsigned int)target,mp.mem[target]); + std::fflush(cimg::output()); + } + } + cimg_pragma_openmp(critical(mp_debug)) + { + 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)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)g_target,mp.mem[g_target],mp.mem._width); + std::fflush(cimg::output()); + } + --mp.p_code; + return mp.mem[g_target]; + } + + static double mp_decrement(_cimg_math_parser& mp) { + return _mp_arg(2) - 1; + } + + static double mp_det(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + return CImg(ptrs,k,k,1,1,true).det(); + } + + 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; + 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"); + return cimg::type::nan(); + } + + static double mp_display(_cimg_math_parser& mp) { + const unsigned int + _siz = (unsigned int)mp.opcode[3], + siz = _siz?_siz:1; + const double *const ptr = &_mp_arg(1) + (_siz?1:0); + const int + w = (int)_mp_arg(4), + h = (int)_mp_arg(5), + d = (int)_mp_arg(6), + s = (int)_mp_arg(7); + CImg img; + if (w>0 && h>0 && d>0 && s>0) { + if ((unsigned int)w*h*d*s<=siz) img.assign(ptr,w,h,d,s,true); + else img.assign(ptr,siz).resize(w,h,d,s,-1); + } else img.assign(ptr,1,siz,1,1,true); + + CImg expr(mp.opcode[2] - 8); + const ulongT *ptrs = mp.opcode._data + 8; + cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); + ((CImg::string("[" cimg_appname "_math_parser] ",false,true),expr)>'x').move_to(expr); + cimg::strellipsize(expr); + std::fputc('\n',cimg::output()); + img.display(expr._data); + return cimg::type::nan(); + } + + static double mp_div(_cimg_math_parser& mp) { + return _mp_arg(2)/_mp_arg(3); + } + + static double mp_dot(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[4]; + return CImg(&_mp_arg(2) + 1,1,siz,1,1,true). + dot(CImg(&_mp_arg(3) + 1,1,siz,1,1,true)); + } + + static double mp_do(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_body = ++mp.p_code, + *const p_cond = p_body + mp.opcode[3], + *const p_end = p_cond + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (mp.mem[mem_cond]); + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_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; + 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); + const CImg str = _str>'x'; + std::fprintf(cimg::output(),"\n%s",str._data); + return cimg::type::nan(); + } + + static double mp_ellipse(_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]; + CImg color(img._spectrum,1,1,1,0); + bool is_invalid_arguments = false; + unsigned int i = 4; + float r1 = 0, r2 = 0, angle = 0, opacity = 1; + int x0 = 0, y0 = 0; + if (i>=i_end) is_invalid_arguments = true; + else { + x0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + y0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + r1 = (float)_mp_arg(i++); + if (i>=i_end) r2 = r1; + 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) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_eq(_cimg_math_parser& mp) { + 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); +#endif + return cimg::type::nan(); + } + + static double mp_exp(_cimg_math_parser& mp) { + return std::exp(_mp_arg(2)); + } + + static double mp_eye(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int k = (unsigned int)mp.opcode[2]; + CImg(ptrd,k,k,1,1,true).identity_matrix(); + return cimg::type::nan(); + } + + static double mp_factorial(_cimg_math_parser& mp) { + return cimg::factorial((int)_mp_arg(2)); + } + + static double mp_fibonacci(_cimg_math_parser& mp) { + return cimg::fibonacci((int)_mp_arg(2)); + } + + static double mp_find(_cimg_math_parser& mp) { + const bool is_forward = (bool)_mp_arg(5); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const double + *const ptrb = &_mp_arg(2) + 1, + *const ptre = ptrb + siz, + val = _mp_arg(4), + *ptr = ptrb + ind; + + // Forward search + if (is_forward) { + while (ptr=ptrb && *ptr!=val) --ptr; + return ptr=(longT)siz1) return -1.; + const double + *const ptr1b = &_mp_arg(2) + 1, + *const ptr1e = ptr1b + siz1, + *const ptr2b = &_mp_arg(4) + 1, + *const ptr2e = ptr2b + siz2, + *ptr1 = ptr1b + ind, + *p1 = 0, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 + *const p_init = ++mp.p_code, + *const p_cond = p_init + mp.opcode[4], + *const p_body = p_cond + mp.opcode[5], + *const p_post = p_body + mp.opcode[6], + *const p_end = p_post + mp.opcode[7]; + const unsigned int vsiz = (unsigned int)mp.opcode[2]; + bool is_cond = false; + if (mp.opcode[8]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[9]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + + for (mp.p_code = p_init; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + + if (!mp.break_type) do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + + for (mp.p_code = p_post; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_fsize(_cimg_math_parser& mp) { + const CImg filename(mp.opcode._data + 3,mp.opcode[2] - 3); + return (double)cimg::fsize(filename); + } + + static double mp_g(_cimg_math_parser& mp) { + cimg::unused(mp); + return cimg::grand(&mp.rng); + } + + 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); + } + + static double mp_gcd(_cimg_math_parser& mp) { + return cimg::gcd((long)_mp_arg(2),(long)_mp_arg(3)); + } + + static double mp_gt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>_mp_arg(3)); + } + + static double mp_gte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>=_mp_arg(3)); + } + + static double mp_i(_cimg_math_parser& mp) { + return (double)mp.imgin.atXYZC((int)mp.mem[_cimg_mp_slot_x],(int)mp.mem[_cimg_mp_slot_y], + (int)mp.mem[_cimg_mp_slot_z],(int)mp.mem[_cimg_mp_slot_c],(T)0); + } + + static double mp_if(_cimg_math_parser& mp) { + const bool is_cond = (bool)_mp_arg(2); + const ulongT + mem_left = mp.opcode[3], + mem_right = mp.opcode[4]; + const CImg + *const p_right = ++mp.p_code + mp.opcode[5], + *const p_end = p_right + mp.opcode[6]; + const unsigned int vtarget = (unsigned int)mp.opcode[1], vsiz = (unsigned int)mp.opcode[7]; + if (is_cond) for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + else for (mp.p_code = p_right; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.p_code==mp.p_break) --mp.p_code; + else mp.p_code = p_end - 1; + if (vsiz) std::memcpy(&mp.mem[vtarget] + 1,&mp.mem[is_cond?mem_left:mem_right] + 1,sizeof(double)*vsiz); + return mp.mem[is_cond?mem_left:mem_right]; + } + + static double mp_image_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]; + 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()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.display(title); + cimg::mutex(6,0); + 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]; + 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]; + return (double)img.median(); + } + + static double mp_image_print(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.print(title); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_resize(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + const double + _w = mp.opcode[3]==~0U?-100:_mp_arg(3), + _h = mp.opcode[4]==~0U?-100:_mp_arg(4), + _d = mp.opcode[5]==~0U?-100:_mp_arg(5), + _s = mp.opcode[6]==~0U?-100:_mp_arg(6); + const unsigned int + w = (unsigned int)(_w>=0?_w:-_w*img.width()/100), + h = (unsigned int)(_h>=0?_h:-_h*img.height()/100), + d = (unsigned int)(_d>=0?_d:-_d*img.depth()/100), + s = (unsigned int)(_s>=0?_s:-_s*img.spectrum()/100), + interp = (int)_mp_arg(7); + if (mp.is_fill && img._data==mp.imgout._data) { + cimg::mutex(6,0); + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'resize()': " + "Cannot both fill and resize image (%u,%u,%u,%u) " + "to new dimensions (%u,%u,%u,%u).", + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,w,h,d,s); + } + const unsigned int + boundary = (int)_mp_arg(8); + const float + cx = (float)_mp_arg(9), + cy = (float)_mp_arg(10), + cz = (float)_mp_arg(11), + cc = (float)_mp_arg(12); + img.resize(w,h,d,s,interp,boundary,cx,cy,cz,cc); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_s(_cimg_math_parser& mp) { + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; + return (double)img.spectrum(); + } + + static double mp_image_sort(_cimg_math_parser& mp) { + const bool is_increasing = (bool)_mp_arg(3); + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()), + axis = (unsigned int)_mp_arg(4); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + img.sort(is_increasing, + axis==0 || axis=='x'?'x': + axis==1 || axis=='y'?'y': + axis==2 || axis=='z'?'z': + axis==3 || axis=='c'?'c':0); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + 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(); + 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(); + } + 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]; + 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]; + 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]; + 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]; + return (double)img.width()*img.height()*img.depth()*img.spectrum(); + } + + static double mp_increment(_cimg_math_parser& mp) { + return _mp_arg(2) + 1; + } + + static double mp_int(_cimg_math_parser& mp) { + return (double)(longT)_mp_arg(2); + } + + static double mp_ioff(_cimg_math_parser& mp) { + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3); + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off::is_inf(_mp_arg(2)); + } + + static double mp_isint(_cimg_math_parser& mp) { + return (double)(cimg::mod(_mp_arg(2),1.)==0); + } + + static double mp_isnan(_cimg_math_parser& mp) { + return (double)cimg::type::is_nan(_mp_arg(2)); + } + + static double mp_ixyzc(_cimg_math_parser& mp) { + const unsigned int + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7); + const CImg &img = mp.imgin; + const double + x = _mp_arg(2), y = _mp_arg(3), + z = _mp_arg(4), c = _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.imgin; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), + z = oz + _mp_arg(4), c = oc + _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx vals(i_end - 4); + double *p = vals.data(); + for (unsigned int i = 4; i &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(4); + const ulongT siz = (ulongT)img.size(); + longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const T + *const ptrb = img.data(), + *const ptre = img.end(), + *ptr = ptrb + ind; + const double val = _mp_arg(3); + + // Forward search + if (is_forward) { + while (ptr=ptrb && (double)*ptr!=val) --ptr; + return ptr &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(5); + const ulongT + siz1 = (ulongT)img.size(), + siz2 = (ulongT)mp.opcode[4]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz1 - 1); + if (ind<0 || ind>=(longT)siz1) return -1.; + const T + *const ptr1b = img.data(), + *const ptr1e = ptr1b + siz1, + *ptr1 = ptr1b + ind, + *p1 = 0; + const double + *const ptr2b = &_mp_arg(3) + 1, + *const ptr2e = ptr2b + siz2, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + x = _mp_arg(3), y = _mp_arg(4), + z = _mp_arg(5), c = _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), + z = oz + _mp_arg(5), c = oc + _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx::vector(mp.listin[ind].median()).move_to(mp.list_median[ind]); + return *mp.list_median[ind]; + } + + static double mp_list_set_ioff(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), y = (int)_mp_arg(4), + z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(3)), y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)), c = (int)(oc + _mp_arg(6)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Ixyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_set_Joff_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Jxyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_spectrum(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._spectrum; + } + + static double mp_list_stats(_cimg_math_parser& mp) { + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + k = (unsigned int)mp.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); + return mp.list_stats(ind,k); + } + + static double mp_list_wh(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height; + } + + static double mp_list_whd(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth; + } + + static double mp_list_whds(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth*mp.listin[ind]._spectrum; + } + + static double mp_list_width(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width; + } + + static double mp_list_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const CImg &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double x = _mp_arg(3), y = _mp_arg(4), z = _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_list_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], oz = (int)mp.mem[_cimg_mp_slot_z]; + const CImg &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), z = oz + _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_log(_cimg_math_parser& mp) { + return std::log(_mp_arg(2)); + } + + static double mp_log10(_cimg_math_parser& mp) { + return std::log10(_mp_arg(2)); + } + + static double mp_log2(_cimg_math_parser& mp) { + return cimg::log2(_mp_arg(2)); + } + + static double mp_logical_and(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (!val_left) { mp.p_code = p_end - 1; return 0; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_logical_not(_cimg_math_parser& mp) { + return (double)!_mp_arg(2); + } + + static double mp_logical_or(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (val_left) { mp.p_code = p_end - 1; return 1; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_lowercase(_cimg_math_parser& mp) { + return cimg::lowercase(_mp_arg(2)); + } + + static double mp_lt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<_mp_arg(3)); + } + + static double mp_lte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<=_mp_arg(3)); + } + + static double mp_matrix_eig(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptr1 = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + CImg val, vec; + CImg(ptr1,k,k,1,1,true).symmetric_eigen(val,vec); + CImg(ptrd,1,k,1,1,true) = val; + CImg(ptrd + k,k,k,1,1,true) = vec.get_transpose(); + 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(); + return cimg::type::nan(); + } + + static double mp_matrix_mul(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr1,l,k,1,1,true)*CImg(ptr2,m,l,1,1,true); + 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; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg U, S, V; + CImg(ptr1,k,l,1,1,true).SVD(U,S,V); + CImg(ptrd,k,l,1,1,true) = U; + CImg(ptrd + k*l,1,k,1,1,true) = S; + CImg(ptrd + k*l + k,k,k,1,1,true) = V; + return cimg::type::nan(); + } + + static double mp_max(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i=mp.mem.width()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds variable pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %u).", + mp.imgin.pixel_type(),siz,inc,off,eoff,mp.mem._width - 1); + return &mp.mem[off]; + } + + static float* _mp_memcopy_float(_cimg_math_parser& mp, const ulongT *const p_ref, + const longT siz, const long inc) { + const unsigned ind = (unsigned int)p_ref[1]; + const CImg &img = ind==~0U?mp.imgin:mp.listin[cimg::mod((int)mp.mem[ind],mp.listin.width())]; + const bool is_relative = (bool)p_ref[2]; + int ox, oy, oz, oc; + longT off = 0; + if (is_relative) { + ox = (int)mp.mem[_cimg_mp_slot_x]; + oy = (int)mp.mem[_cimg_mp_slot_y]; + oz = (int)mp.mem[_cimg_mp_slot_z]; + oc = (int)mp.mem[_cimg_mp_slot_c]; + off = img.offset(ox,oy,oz,oc); + } + if ((*p_ref)%2) { + const int + x = (int)mp.mem[p_ref[3]], + y = (int)mp.mem[p_ref[4]], + z = (int)mp.mem[p_ref[5]], + c = *p_ref==5?0:(int)mp.mem[p_ref[6]]; + off+=img.offset(x,y,z,c); + } else off+=(longT)mp.mem[p_ref[3]]; + const longT eoff = off + (siz - 1)*inc; + if (off<0 || eoff>=(longT)img.size()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds image pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %lu).", + mp.imgin.pixel_type(),siz,inc,off,eoff,img.size() - 1); + return (float*)&img[off]; + } + + static double mp_memcopy(_cimg_math_parser& mp) { + longT siz = (longT)_mp_arg(4); + const longT inc_d = (longT)_mp_arg(5), inc_s = (longT)_mp_arg(6); + const float + _opacity = (float)_mp_arg(7), + opacity = (float)cimg::abs(_opacity), + omopacity = 1 - std::max(_opacity,0.f); + if (siz>0) { + const bool + is_doubled = mp.opcode[8]<=1, + is_doubles = mp.opcode[15]<=1; + if (is_doubled && is_doubles) { // (double*) <- (double*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(double)); + else std::memmove(ptrd,ptrs,siz*sizeof(double)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,double) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } else if (is_doubled && !is_doubles) { // (double*) <- (float*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + _opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else if (!is_doubled && is_doubles) { // (float*) <- (double*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = (float)*ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = (float)(omopacity**ptrd + opacity**ptrs); ptrd+=inc_d; ptrs+=inc_s; } + } else { // (float*) <- (float*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(float)); + else std::memmove(ptrd,ptrs,siz*sizeof(float)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,float) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } + } + return _mp_arg(1); + } + + static double mp_min(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i vals(i_end - 3); + double *p = vals.data(); + 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.; + } + + static double mp_permutations(_cimg_math_parser& mp) { + return cimg::permutations((int)_mp_arg(2),(int)_mp_arg(3),(bool)_mp_arg(4)); + } + + 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 (!is_invalid_arguments) { + const int nbv = (int)_mp_arg(4); + if (nbv<=0) is_invalid_arguments = true; + else { + CImg points(nbv,2,1,1,0); + CImg color(img._spectrum,1,1,1,0); + float opacity = 1; + unsigned int i = 5; + cimg_foroff(points,k) if (i args(i_end - 4); + cimg_forX(args,k) args[k] = _mp_arg(4 + k); + if (ind==~0U) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_pow(_cimg_math_parser& mp) { + const double v = _mp_arg(2), p = _mp_arg(3); + return std::pow(v,p); + } + + static double mp_pow0_25(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return std::sqrt(std::sqrt(val)); + } + + static double mp_pow3(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val; + } + + static double mp_pow4(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val*val; + } + + static double mp_print(_cimg_math_parser& mp) { + const double val = _mp_arg(1); + const bool print_char = (bool)mp.opcode[3]; + cimg_pragma_openmp(critical(mp_print)) + { + 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::mutex(6); + if (print_char) + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g = '%c'",expr._data,val,(int)val); + else + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g",expr._data,val); + std::fflush(cimg::output()); + cimg::mutex(6,0); + } + return val; + } + + static double mp_prod(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i::nan(); + } + + static double mp_rot3d(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const float x = (float)_mp_arg(2), y = (float)_mp_arg(3), z = (float)_mp_arg(4), theta = (float)_mp_arg(5); + CImg(ptrd,3,3,1,1,true) = CImg::rotation_matrix(x,y,z,theta); + return cimg::type::nan(); + } + + static double mp_round(_cimg_math_parser& mp) { + return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4)); + } + + static double mp_self_add(_cimg_math_parser& mp) { + return _mp_arg(1)+=_mp_arg(2); + } + + static double mp_self_bitwise_and(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val & (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_left_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val<<(unsigned int)_mp_arg(2)); + } + + static double mp_self_bitwise_or(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val | (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_right_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val>>(unsigned int)_mp_arg(2)); + } + + static double mp_self_decrement(_cimg_math_parser& mp) { + return --_mp_arg(1); + } + + static double mp_self_increment(_cimg_math_parser& mp) { + return ++_mp_arg(1); + } + + static double mp_self_map_vector_s(_cimg_math_parser& mp) { // Vector += scalar + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2]; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode[2] = mp.opcode[4]; // Scalar argument + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1]; + while (siz-->0) { target = ptrd++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_map_vector_v(_cimg_math_parser& mp) { // Vector += vector + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,4); + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1], &argument = mp.opcode[2]; + while (siz-->0) { target = ptrd++; argument = ptrs++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_mul(_cimg_math_parser& mp) { + return _mp_arg(1)*=_mp_arg(2); + } + + static double mp_self_div(_cimg_math_parser& mp) { + return _mp_arg(1)/=_mp_arg(2); + } + + static double mp_self_modulo(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = cimg::mod(val,_mp_arg(2)); + } + + static double mp_self_pow(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = std::pow(val,_mp_arg(2)); + } + + static double mp_self_sub(_cimg_math_parser& mp) { + return _mp_arg(1)-=_mp_arg(2); + } + + static double mp_set_ioff(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + x = (int)_mp_arg(2), y = (int)_mp_arg(3), + z = (int)_mp_arg(4), c = (int)_mp_arg(5); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(2)), y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)), c = (int)(oc + _mp_arg(5)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Ixyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_set_Joff_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Jxyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_shift(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const unsigned int siz = (unsigned int)mp.opcode[3]; + const int + shift = (int)_mp_arg(4), + boundary_conditions = (int)_mp_arg(5); + CImg(ptrd,siz,1,1,1,true) = CImg(ptrs,siz,1,1,1,true).shift(shift,0,0,0,boundary_conditions); + return cimg::type::nan(); + } + + static double mp_sign(_cimg_math_parser& mp) { + return cimg::sign(_mp_arg(2)); + } + + static double mp_sin(_cimg_math_parser& mp) { + return std::sin(_mp_arg(2)); + } + + static double mp_sinc(_cimg_math_parser& mp) { + return cimg::sinc(_mp_arg(2)); + } + + static double mp_sinh(_cimg_math_parser& mp) { + return std::sinh(_mp_arg(2)); + } + + static double mp_solve(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr2,m,l,1,1,true).get_solve(CImg(ptr1,k,l,1,1,true)); + 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 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); + return cimg::type::nan(); + } + + static double mp_sqr(_cimg_math_parser& mp) { + return cimg::sqr(_mp_arg(2)); + } + + static double mp_sqrt(_cimg_math_parser& mp) { + return std::sqrt(_mp_arg(2)); + } + + static double mp_srand(_cimg_math_parser& mp) { + mp.rng = (ulongT)_mp_arg(2); + return cimg::type::nan(); + } + + static double mp_srand0(_cimg_math_parser& mp) { + cimg::srand(&mp.rng); +#ifdef cimg_use_openmp + 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; i0) mp.mem[ptrd++] = (double)*(ptrs++); + return cimg::type::nan(); + } + + static double mp_stov(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)_mp_arg(4); + const bool is_strict = (bool)_mp_arg(5); + double val = cimg::type::nan(); + if (ind<0 || ind>=(longT)siz) return val; + 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; + + int err = cimg_sscanf(ss,"%lf%c",&val,&sep); +#if cimg_OS==2 + // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able + // to read those particular values. + if (!err && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) { + bool is_positive = true; + const char *s = ss; + if (*s=='+') ++s; else if (*s=='-') { ++s; is_positive = false; } + if (!cimg::strcasecmp(s,"inf")) { val = cimg::type::inf(); err = 1; } + else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); err = 1; } + if (err==1 && !is_positive) val = -val; + } +#endif + if (is_strict && err!=1) return cimg::type::nan(); + return val; + } + + static double mp_sub(_cimg_math_parser& mp) { + return _mp_arg(2) - _mp_arg(3); + } + + static double mp_sum(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i(ptrs,k,k,1,1,true).trace(); + } + + static double mp_transp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg(ptrd,l,k,1,1,true) = CImg(ptrs,k,l,1,1,true).get_transpose(); + return cimg::type::nan(); + } + + static double mp_u(_cimg_math_parser& mp) { + return cimg::rand(_mp_arg(2),_mp_arg(3),&mp.rng); + } + + static double mp_uppercase(_cimg_math_parser& mp) { + return cimg::uppercase(_mp_arg(2)); + } + + 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; i::nan(); + } + + static double mp_vector_crop(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const longT + length = (longT)mp.opcode[3], + start = (longT)_mp_arg(4), + sublength = (longT)mp.opcode[5]; + if (start<0 || start + sublength>length) + throw CImgArgumentException("[" cimg_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)); + return cimg::type::nan(); + } + + static double mp_vector_init(_cimg_math_parser& mp) { + unsigned int + ptrs = 4U, + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[3]; + switch (mp.opcode[2] - 4) { + case 0 : std::memset(mp.mem._data + ptrd,0,siz*sizeof(double)); break; // 0 values given + case 1 : { const double val = _mp_arg(ptrs); while (siz-->0) mp.mem[ptrd++] = val; } break; + default : while (siz-->0) { mp.mem[ptrd++] = _mp_arg(ptrs++); if (ptrs>=mp.opcode[2]) ptrs = 4U; } + } + return cimg::type::nan(); + } + + static double mp_vector_eq(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(4) + 1; + unsigned int p1 = (unsigned int)mp.opcode[3], p2 = (unsigned int)mp.opcode[5], n; + const int N = (int)_mp_arg(6); + const bool case_sensitive = (bool)_mp_arg(7); + bool still_equal = true; + double value; + if (!N) return true; + + // Compare all values. + if (N<0) { + if (p1>0 && p2>0) { // Vector == vector + if (p1!=p2) return false; + if (case_sensitive) + while (still_equal && p1--) still_equal = *(ptr1++)==*(ptr2++); + else + while (still_equal && p1--) + still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p1--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p2--) still_equal = *(ptr2++)==value; + return still_equal; + } else { // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + else return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + } + + // Compare only first N values. + if (p1>0 && p2>0) { // Vector == vector + n = cimg::min((unsigned int)N,p1,p2); + if (case_sensitive) + while (still_equal && n--) still_equal = *(ptr1++)==(*ptr2++); + else + while (still_equal && n--) still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + n = std::min((unsigned int)N,p1); + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + n = std::min((unsigned int)N,p2); + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr2++)==value; + return still_equal; + } // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + + static double mp_vector_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + return off>=0 && off<(int)siz?mp.mem[ptr + off]:cimg::type::nan(); + } + + static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[5] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(4); + l_opcode[2] = mp.opcode[4]; // Scalar argument1 + l_opcode.swap(mp.opcode); + ulongT &argument2 = mp.opcode[3]; + while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_v(_cimg_math_parser& mp) { // Operator(vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode.swap(mp.opcode); + ulongT &argument = mp.opcode[2]; + while (siz-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_vs(_cimg_math_parser& mp) { // Operator(vector,scalar) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg 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); + l_opcode.swap(mp.opcode); + ulongT &argument1 = mp.opcode[2], &argument2 = mp.opcode[3]; + while (siz-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_neq(_cimg_math_parser& mp) { + return !mp_vector_eq(mp); + } + + 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); + const ulongT *ptrs = mp.opcode._data + 5; + 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); + unsigned int count = 0; + while (siz-->0) { + if (count>=64 && siz>=64) { + std::fprintf(cimg::output(),"...,"); + ptr = (unsigned int)mp.opcode[1] + 1 + siz0 - 64; + siz = 64; + } else std::fprintf(cimg::output(),"%g%s",mp.mem[ptr++],siz?",":""); + ++count; + } + if (print_string) { + CImg str(siz0 + 1); + ptr = (unsigned int)mp.opcode[1] + 1; + for (unsigned int k = 0; k::nan(); + } + + static double mp_vector_resize(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const unsigned int p1 = (unsigned int)mp.opcode[2], p2 = (unsigned int)mp.opcode[4]; + const int + interpolation = (int)_mp_arg(5), + boundary_conditions = (int)_mp_arg(6); + if (p2) { // Resize vector + const double *const ptrs = &_mp_arg(3) + 1; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p2,1,1,1,true). + get_resize(p1,1,1,1,interpolation,boundary_conditions); + } else { // Resize scalar + const double value = _mp_arg(3); + CImg(ptrd,p1,1,1,1,true) = CImg(1,1,1,1,value).resize(p1,1,1,1,interpolation, + boundary_conditions); + } + 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; + const unsigned int p1 = (unsigned int)mp.opcode[3]; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p1,1,1,1,true).get_mirror('x'); + return cimg::type::nan(); + } + + static double mp_vector_set_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + if (off>=0 && off<(int)siz) mp.mem[ptr + off] = _mp_arg(5); + return _mp_arg(5); + } + + static double mp_vtos(_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]; + 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); + } + 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); + } else { // Scalar expression + str.assign(sizd + 1); + cimg_snprintf(str,sizd + 1,format,_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_while(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_cond = ++mp.p_code, + *const p_body = p_cond + mp.opcode[3], + *const p_end = p_body + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + bool is_cond = false; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) // Evaluate body + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double x = _mp_arg(2), y = _mp_arg(3), z = _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], + oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), z = oz + _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + +#undef _mp_arg + + }; // struct _cimg_math_parser {} + +#define _cimg_create_pointwise_functions(name,func,min_size) \ + CImg& name() { \ + if (is_empty()) return *this; \ + cimg_openmp_for(*this,func((double)*ptr),min_size); \ + return *this; \ + } \ + CImg get_##name() const { \ + return CImg(*this,false).name(); \ + } + + //! Compute the square value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square value \f$I_{(x,y,z,c)}^2\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqr().normalize(0,255)).display(); + \endcode + \image html ref_sqr.jpg + **/ + _cimg_create_pointwise_functions(sqr,cimg::sqr,524288) + + //! Compute the square root of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square root \f$\sqrt{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqrt().normalize(0,255)).display(); + \endcode + \image html ref_sqrt.jpg + **/ + _cimg_create_pointwise_functions(sqrt,std::sqrt,8192) + + //! Compute the exponential of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its exponential \f$e^{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(exp,std::exp,4096) + + //! Compute the logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its logarithm + \f$\mathrm{log}_{e}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log,std::log,262144) + + //! Compute the base-2 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-2 logarithm + \f$\mathrm{log}_{2}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log2,cimg::log2,4096) + + //! Compute the base-10 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-10 logarithm + \f$\mathrm{log}_{10}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log10,std::log10,4096) + + //! Compute the absolute value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its absolute value \f$|I_{(x,y,z,c)}|\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(abs,cimg::abs,524288) + + //! Compute the sign of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sign + \f$\mathrm{sign}(I_{(x,y,z,c)})\f$. + \note + - The sign is set to: + - \c 1 if pixel value is strictly positive. + - \c -1 if pixel value is strictly negative. + - \c 0 if pixel value is equal to \c 0. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sign,cimg::sign,32768) + + //! Compute the cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its cosine \f$\cos(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cos,std::cos,8192) + + //! Compute the sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sine \f$\sin(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sin,std::sin,8192) + + //! Compute the sinc of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sinc + \f$\mathrm{sinc}(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinc,cimg::sinc,2048) + + //! Compute the tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its tangent \f$\tan(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tan,std::tan,2048) + + //! Compute the hyperbolic cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic cosine + \f$\mathrm{cosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cosh,std::cosh,2048) + + //! Compute the hyperbolic sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic sine + \f$\mathrm{sinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinh,std::sinh,2048) + + //! Compute the hyperbolic tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic tangent + \f$\mathrm{tanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tanh,std::tanh,2048) + + //! Compute the arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosine + \f$\mathrm{acos}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acos,std::acos,8192) + + //! Compute the arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arcsine + \f$\mathrm{asin}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asin,std::asin,8192) + + //! Compute the arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent + \f$\mathrm{atan}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atan,std::atan,8192) + + //! Compute the arctangent2 of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent2 + \f$\mathrm{atan2}(I_{(x,y,z,c)})\f$. + \param img Image whose pixel values specify the second argument of the \c atan2() function. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img_y,img_atan2).display(); + \endcode + **/ + template + CImg& atan2(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return atan2(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_atan2(const CImg& img) const { + return CImg(*this,false).atan2(img); + } + + //! Compute the hyperbolic arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosineh + \f$\mathrm{acosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acosh,cimg::acosh,8192) + + //! Compute the hyperbolic arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arcsine + \f$\mathrm{asinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asinh,cimg::asinh,8192) + + //! Compute the hyperbolic arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arctangent + \f$\mathrm{atanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atanh,cimg::atanh,8192) + + //! In-place pointwise multiplication. + /** + Compute the pointwise multiplication between the image instance and the specified input image \c img. + \param img Input image, as the second operand of the multiplication. + \note + - Similar to operator+=(const CImg&), except that it performs a pointwise multiplication + instead of an addition. + - It does \e not perform a \e matrix multiplication. For this purpose, use operator*=(const CImg&) instead. + \par Example + \code + CImg + img("reference.jpg"), + shade(img.width,img.height(),1,1,"-(x-w/2)^2-(y-h/2)^2",false); + shade.normalize(0,1); + (img,shade,img.get_mul(shade)).display(); + \endcode + **/ + template + CImg& mul(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return mul(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_mul(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).mul(img); + } + + //! In-place pointwise division. + /** + Similar to mul(const CImg&), except that it performs a pointwise division instead of a multiplication. + **/ + template + CImg& div(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return div(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_div(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).div(img); + } + + //! Raise each pixel value to a specified power. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its power \f$I_{(x,y,z,c)}^p\f$. + \param p Exponent value. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img1,img2).display(); + \endcode + **/ + CImg& pow(const double p) { + if (is_empty()) 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.5) return sqrt(); + if (p==1) return *this; + if (p==2) return sqr(); + 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; + } + + //! Raise each pixel value to a specified power \newinstance. + CImg get_pow(const double p) const { + return CImg(*this,false).pow(p); + } + + //! Raise each pixel value to a power, specified from an expression. + /** + Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition. + **/ + CImg& pow(const char *const expression) { + return pow((+*this)._fill(expression,true,1,0,0,"pow",this)); + } + + //! Raise each pixel value to a power, specified from an expression \newinstance. + CImg get_pow(const char *const expression) const { + return CImg(*this,false).pow(expression); + } + + //! Raise each pixel value to a power, pointwisely specified from another image. + /** + Similar to operator+=(const CImg& img), except that it performs an exponentiation instead of an addition. + **/ + template + CImg& pow(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return pow(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_pow(const CImg& img) const { + return CImg(*this,false).pow(img); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(unsigned int), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::rol(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const unsigned int n=1) const { + return (+*this).rol(n); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const char *const expression) { + return rol((+*this)._fill(expression,true,1,0,0,"rol",this)); + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const char *const expression) const { + return (+*this).rol(expression); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const CImg&), except that it performs a left rotation instead of a left shift. + **/ + template + CImg& rol(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return rol(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_rol(const CImg& img) const { + return (+*this).rol(img); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(unsigned int), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::ror(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const unsigned int n=1) const { + return (+*this).ror(n); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const char *const expression) { + return ror((+*this)._fill(expression,true,1,0,0,"ror",this)); + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const char *const expression) const { + return (+*this).ror(expression); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const CImg&), except that it performs a right rotation instead of a right shift. + **/ + template + CImg& ror(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return ror(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_ror(const CImg& img) const { + return (+*this).ror(img); + } + + //! Pointwise min operator between instance image and a value. + /** + \param val Value used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& min(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).min(value); + } + + //! Pointwise min operator between two images. + /** + \param img Image used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& min(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return min(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_min(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).min(img); + } + + //! Pointwise min operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& min(const char *const expression) { + return min((+*this)._fill(expression,true,1,0,0,"min",this)); + } + + //! Pointwise min operator between an image and an expression \newinstance. + CImg get_min(const char *const expression) const { + return CImg(*this,false).min(expression); + } + + //! Pointwise max operator between instance image and a value. + /** + \param val Value used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& max(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).max(value); + } + + //! Pointwise max operator between two images. + /** + \param img Image used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& max(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return max(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_max(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).max(img); + } + + //! Pointwise max operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& max(const char *const expression) { + return max((+*this)._fill(expression,true,1,0,0,"max",this)); + } + + //! Pointwise max operator between an image and an expression \newinstance. + CImg get_max(const char *const expression) const { + return CImg(*this,false).max(expression); + } + + //! Return a reference to the minimum pixel value. + /** + **/ + T& min() { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min; + cimg_for(*this,ptrs,T) if (*ptrsmax_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the maximum pixel value \const. + const T& max() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max; + cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value. + /** + \param[out] max_val Maximum pixel value. + **/ + template + T& min_max(t& max_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value \const. + template + const T& min_max(t& max_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + const T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the maximum pixel value as well as the minimum pixel value. + /** + \param[out] min_val Minimum pixel value. + **/ + template + T& max_min(t& min_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val arr(*this,false); + ulongT l = 0, ir = size() - 1; + for ( ; ; ) { + if (ir<=l + 1) { + if (ir==l + 1 && arr[ir]>1; + cimg::swap(arr[mid],arr[l + 1]); + if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]); + if (arr[l + 1]>arr[ir]) cimg::swap(arr[l + 1],arr[ir]); + if (arr[l]>arr[l + 1]) cimg::swap(arr[l],arr[l + 1]); + ulongT i = l + 1, j = ir; + const T pivot = arr[l + 1]; + for ( ; ; ) { + do ++i; while (arr[i]pivot); + if (j=k) ir = j - 1; + if (j<=k) l = i; + } + } + } + + //! Return the median pixel value. + /** + **/ + T median() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "median(): Empty instance.", + cimg_instance); + const ulongT s = size(); + switch (s) { + case 1 : return _data[0]; + case 2 : return cimg::median(_data[0],_data[1]); + case 3 : return cimg::median(_data[0],_data[1],_data[2]); + case 5 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4]); + case 7 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6]); + case 9 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8]); + case 13 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8], + _data[9],_data[10],_data[11],_data[12]); + } + const T res = kth_smallest(s>>1); + return (s%2)?res:(T)((res + kth_smallest((s>>1) - 1))/2); + } + + //! Return the product of all the pixel values. + /** + **/ + double product() const { + if (is_empty()) return 0; + double res = 1; + cimg_for(*this,ptrs,T) res*=(double)*ptrs; + return res; + } + + //! Return the sum of all the pixel values. + /** + **/ + double sum() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res; + } + + //! Return the average pixel value. + /** + **/ + double mean() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res/size(); + } + + //! Return the variance of the pixel values. + /** + \param variance_method Method used to estimate the variance. Can be: + - \c 0: Second moment, computed as + \f$1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2 = + 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right)\f$ + with \f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$. + - \c 1: Best unbiased estimator, computed as \f$\frac{1}{N - 1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 \f$. + - \c 2: Least median of squares. + - \c 3: Least trimmed of squares. + **/ + double variance(const unsigned int variance_method=1) const { + double foo; + return variance_mean(variance_method,foo); + } + + //! Return the variance as well as the average of the pixel values. + /** + \param variance_method Method used to estimate the variance (see variance(const unsigned int) const). + \param[out] mean Average pixel value. + **/ + template + double variance_mean(const unsigned int variance_method, t& mean) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_mean(): Empty instance.", + cimg_instance); + + double variance = 0, average = 0; + const ulongT siz = size(); + switch (variance_method) { + case 0 : { // Least mean square (standard definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = (S2 - S*S/siz)/siz; + average = S; + } break; + case 1 : { // Least mean square (robust definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + average = S; + } break; + case 2 : { // Least Median of Squares (MAD) + CImg buf(*this,false); + buf.sort(); + const ulongT siz2 = siz>>1; + const double med_i = (double)buf[siz2]; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val; + } + buf.sort(); + const double sig = (double)(1.4828*buf[siz2]); + variance = sig*sig; + } break; + default : { // Least trimmed of Squares + CImg buf(*this,false); + const ulongT siz2 = siz>>1; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val; + } + buf.sort(); + double a = 0; + const Tfloat *ptrs = buf._data; + for (ulongT j = 0; j0?variance:0; + } + + //! Return estimated variance of the noise. + /** + \param variance_method Method used to compute the variance (see variance(const unsigned int) const). + \note Because of structures such as edges in images it is + recommanded to use a robust variance estimation. The variance of the + noise is estimated by computing the variance of the Laplacian \f$(\Delta + I)^2 \f$ scaled by a factor \f$c\f$ insuring \f$ c E[(\Delta I)^2]= + \sigma^2\f$ where \f$\sigma\f$ is the noise variance. + **/ + double variance_noise(const unsigned int variance_method=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_noise(): Empty instance.", + cimg_instance); + + const ulongT siz = size(); + if (!siz || !_data) return 0; + if (variance_method>1) { // Compute a scaled version of the Laplacian + CImg tmp(*this,false); + if (_depth==1) { + 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) { + tmp(x,y,c) = cste*((double)Inc + (double)Ipc + (double)Icn + + (double)Icp - 4*(double)Icc); + } + } + } else { + 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) { + tmp(x,y,z,c) = cste*( + (double)Incc + (double)Ipcc + (double)Icnc + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + } + } + } + return tmp.variance(variance_method); + } + + // Version that doesn't need intermediate images. + double variance = 0, S = 0, S2 = 0; + if (_depth==1) { + const double cste = 1./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 + + (double)Icn + (double)Icp - 4*(double)Icc); + S+=val; S2+=val*val; + } + } else { + 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 * + ((double)Incc + (double)Ipcc + (double)Icnc + + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + S+=val; S2+=val*val; + } + } + if (variance_method) variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + else variance = (S2 - S*S/siz)/siz; + return variance>0?variance:0; + } + + //! Compute the MSE (Mean-Squared Error) between two images. + /** + \param img Image used as the second argument of the MSE operator. + **/ + template + double MSE(const CImg& img) const { + if (img.size()!=size()) + throw CImgArgumentException(_cimg_instance + "MSE(): Instance and specified image (%u,%u,%u,%u,%p) have different dimensions.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + double vMSE = 0; + const t* ptr2 = img._data; + cimg_for(*this,ptr1,T) { + const double diff = (double)*ptr1 - (double)*(ptr2++); + vMSE+=diff*diff; + } + const ulongT siz = img.size(); + if (siz) vMSE/=siz; + return vMSE; + } + + //! Compute the PSNR (Peak Signal-to-Noise Ratio) between two images. + /** + \param img Image used as the second argument of the PSNR operator. + \param max_value Maximum theoretical value of the signal. + **/ + template + double PSNR(const CImg& img, const double max_value=255) const { + const double vMSE = (double)std::sqrt(MSE(img)); + return (vMSE!=0)?(double)(20*std::log10(max_value/vMSE)):(double)(cimg::type::max()); + } + + //! Evaluate math formula. + /** + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. + **/ + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { + return _eval(this,expression,x,y,z,c,list_inputs,list_outputs); + } + + //! Evaluate math formula \const. + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { + return _eval(0,expression,x,y,z,c,list_inputs,list_outputs); + } + + double _eval(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) return 0; + if (!expression[1]) switch (*expression) { // Single-char optimization + case 'w' : return (double)_width; + case 'h' : return (double)_height; + case 'd' : return (double)_depth; + case 's' : return (double)_spectrum; + case 'r' : return (double)_is_shared; + } + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + const double val = mp(x,y,z,c); + mp.end(); + return val; + } + + //! Evaluate math formula. + /** + \param[out] output Contains values of output vector returned by the evaluated expression + (or is empty if the returned type is scalar). + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output 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); + } + + //! 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); + } + + 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=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + output.assign(1,std::max(1U,mp.result_dim)); + mp(x,y,z,c,output._data); + mp.end(); + } + + //! Evaluate math formula on a set of variables. + /** + \param expression Math formula, as a C-string. + \param xyzc Set of values (x,y,z,c) used for the evaluation. + \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. + **/ + 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); + } + + //! 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); + } + + 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 { + 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_pragma_openmp(parallel if (res._height>=512)) + { + _cimg_math_parser + _mp = omp_get_thread_num()?mp:_cimg_math_parser(), + &lmp = omp_get_thread_num()?_mp:mp; + cimg_pragma_openmp(for) + for (unsigned int i = 0; i[min, max, mean, variance, xmin, ymin, zmin, cmin, xmax, ymax, zmax, cmax, sum, product]. + **/ + CImg get_stats(const unsigned int variance_method=1) const { + if (is_empty()) return CImg(); + const ulongT siz = size(); + const longT off_end = (longT)siz; + double S = 0, S2 = 0, P = 1; + longT offm = 0, offM = 0; + T m = *_data, M = m; + + 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) + for (longT off = 0; offlM) { lM = val; loffM = off; } + S+=_val; + S2+=_val*_val; + P*=_val; + } + cimg_pragma_openmp(critical(get_stats)) { + if (lmM || (lM==M && loffM1?(S2 - S*S/siz)/(siz - 1):0): + variance(variance_method)), + variance_value = _variance_value>0?_variance_value:0; + int + xm = 0, ym = 0, zm = 0, cm = 0, + xM = 0, yM = 0, zM = 0, cM = 0; + contains(_data[offm],xm,ym,zm,cm); + contains(_data[offM],xM,yM,zM,cM); + return CImg(1,14).fill((double)m,(double)M,mean_value,variance_value, + (double)xm,(double)ym,(double)zm,(double)cm, + (double)xM,(double)yM,(double)zM,(double)cM, + S,P); + } + + //! Compute statistics vector from the pixel values \inplace. + CImg& stats(const unsigned int variance_method=1) { + return get_stats(variance_method).move_to(*this); + } + + //@} + //------------------------------------- + // + //! \name Vector / Matrix Operations + //@{ + //------------------------------------- + + //! Compute norm of the image, viewed as a matrix. + /** + \param magnitude_type Norm type. Can be: + - \c -1: Linf-norm + - \c 0: L0-norm + - \c 1: L1-norm + - \c 2: L2-norm + **/ + double magnitude(const int magnitude_type=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "magnitude(): Empty instance.", + cimg_instance); + double res = 0; + switch (magnitude_type) { + case -1 : { + cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } + } break; + case 1 : { + cimg_for(*this,ptrs,T) res+=(double)cimg::abs(*ptrs); + } break; + default : { + cimg_for(*this,ptrs,T) res+=(double)cimg::sqr(*ptrs); + res = (double)std::sqrt(res); + } + } + return res; + } + + //! Compute the trace of the image, viewed as a matrix. + /** + **/ + double trace() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "trace(): Empty instance.", + cimg_instance); + double res = 0; + cimg_forX(*this,k) res+=(double)(*this)(k,k); + return res; + } + + //! Compute the determinant of the image, viewed as a matrix. + /** + **/ + double det() const { + if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "det(): Instance is not a square matrix.", + cimg_instance); + + switch (_width) { + case 1 : return (double)((*this)(0,0)); + case 2 : return (double)((*this)(0,0))*(double)((*this)(1,1)) - (double)((*this)(0,1))*(double)((*this)(1,0)); + case 3 : { + const double + a = (double)_data[0], d = (double)_data[1], g = (double)_data[2], + b = (double)_data[3], e = (double)_data[4], h = (double)_data[5], + c = (double)_data[6], f = (double)_data[7], i = (double)_data[8]; + return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e; + } + default : { + CImg lu(*this,false); + CImg indx; + bool d; + lu._LU(indx,d); + double res = d?(double)1:(double)-1; + cimg_forX(lu,i) res*=lu(i,i); + return res; + } + } + } + + //! Compute the dot product between instance and argument, viewed as matrices. + /** + \param img Image used as a second argument of the dot product. + **/ + template + 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 get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + CImg res; + if (res._height!=_spectrum) res.assign(1,_spectrum); + const ulongT whd = (ulongT)_width*_height*_depth; + const T *ptrs = data(x,y,z); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get (square) matrix-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \note - The spectrum() of the image must be a square. + **/ + CImg get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const { + const int n = (int)cimg::round(std::sqrt((double)_spectrum)); + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(n,n); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get tensor-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + CImg get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + if (_spectrum==6) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd),*(ptrs + 3*whd),*(ptrs + 4*whd),*(ptrs + 5*whd)); + if (_spectrum==3) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd)); + return tensor(*ptrs); + } + + //! Set vector-valued pixel at specified position. + /** + \param vec Vector to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_vector_at(const CImg& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) { + if (x<_width && y<_height && z<_depth) { + const t *ptrs = vec._data; + const ulongT whd = (ulongT)_width*_height*_depth; + T *ptrd = data(x,y,z); + for (unsigned int k = std::min((unsigned int)vec.size(),_spectrum); k; --k) { + *ptrd = (T)*(ptrs++); ptrd+=whd; + } + } + return *this; + } + + //! Set (square) matrix-valued pixel at specified position. + /** + \param mat Matrix to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_matrix_at(const CImg& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + return set_vector_at(mat,x,y,z); + } + + //! Set tensor-valued pixel at specified position. + /** + \param ten Tensor to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_tensor_at(const CImg& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + T *ptrd = data(x,y,z,0); + const ulongT siz = (ulongT)_width*_height*_depth; + if (ten._height==2) { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[3]; + } + else { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[2]; ptrd+=siz; + *ptrd = (T)ten[4]; ptrd+=siz; + *ptrd = (T)ten[5]; ptrd+=siz; + *ptrd = (T)ten[8]; + } + return *this; + } + + //! Unroll pixel values along axis \c y. + /** + \note Equivalent to \code unroll('y'); \endcode. + **/ + CImg& 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. + **/ + CImg& diagonal() { + return get_diagonal().move_to(*this); + } + + //! Resize image to become a diagonal matrix \newinstance. + CImg get_diagonal() const { + if (is_empty()) return *this; + const unsigned int siz = (unsigned int)size(); + CImg res(siz,siz,1,1,0); + cimg_foroff(*this,off) res((unsigned int)off,(unsigned int)off) = (*this)[off]; + return res; + } + + //! Replace the image by an identity matrix. + /** + \note If the instance image is not square, it is resized to a square matrix using its maximum + dimension as a reference. + **/ + CImg& identity_matrix() { + return identity_matrix(std::max(_width,_height)).move_to(*this); + } + + //! Replace the image by an identity matrix \newinstance. + CImg get_identity_matrix() const { + return identity_matrix(std::max(_width,_height)); + } + + //! Fill image with a linear sequence of values. + /** + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + CImg& sequence(const T& a0, const T& a1) { + if (is_empty()) return *this; + const ulongT siz = size() - 1; + T* ptr = _data; + if (siz) { + const double delta = (double)a1 - (double)a0; + cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz); + } else *ptr = a0; + return *this; + } + + //! Fill image with a linear sequence of values \newinstance. + CImg get_sequence(const T& a0, const T& a1) const { + return (+*this).sequence(a0,a1); + } + + //! Transpose the image, viewed as a matrix. + /** + \note Equivalent to \code permute_axes("yxzc"); \endcode. + **/ + CImg& transpose() { + if (_width==1) { _width = _height; _height = 1; return *this; } + if (_height==1) { _height = _width; _width = 1; return *this; } + if (_width==_height) { + cimg_forYZC(*this,y,z,c) for (int x = y; x get_transpose() const { + return get_permute_axes("yxzc"); + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors. + /** + \param img Image used as the second argument of the cross product. + \note The first argument of the cross product is \c *this. + **/ + template + 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.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + + const T x = (*this)[0], y = (*this)[1], z = (*this)[2]; + (*this)[0] = (T)(y*img[2] - z*img[1]); + (*this)[1] = (T)(z*img[0] - x*img[2]); + (*this)[2] = (T)(x*img[1] - y*img[0]); + return *this; + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors \newinstance. + template + CImg<_cimg_Tt> get_cross(const CImg& img) const { + return CImg<_cimg_Tt>(*this).cross(img); + } + + //! Invert the instance image, viewed as a matrix. + /** + \param use_LU Choose the inverting algorithm. Can be: + - \c true: LU-based matrix inversion. + - \c false: SVD-based matrix inversion. + **/ + CImg& invert(const bool use_LU=true) { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "invert(): Instance is not a square matrix.", + cimg_instance); +#ifdef cimg_use_lapack + int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N]; + Tfloat + *const lapA = new Tfloat[N*N], + *const WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N + l] = (Tfloat)((*this)(k,l)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + else { + cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetri_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N + l]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] WORK; +#else + const double dete = _width>3?-1.: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. && _width==3) { + const double + a = _data[0], d = _data[1], g = _data[2], + b = _data[3], e = _data[4], h = _data[5], + c = _data[6], f = _data[7], i = _data[8]; + _data[0] = (T)((i*e - f*h)/dete), _data[1] = (T)((g*f - i*d)/dete), _data[2] = (T)((d*h - g*e)/dete); + _data[3] = (T)((h*c - i*b)/dete), _data[4] = (T)((i*a - c*g)/dete), _data[5] = (T)((g*b - a*h)/dete); + _data[6] = (T)((b*f - e*c)/dete), _data[7] = (T)((d*c - a*f)/dete), _data[8] = (T)((a*e - d*b)/dete); + } else { + if (use_LU) { // LU-based inverse computation + CImg A(*this,false), indx, col(1,_width); + bool d; + A._LU(indx,d); + cimg_forX(*this,j) { + col.fill(0); + col(j) = 1; + col._solve(A,indx); + cimg_forX(*this,i) (*this)(j,i) = (T)col(i); + } + } else { // SVD-based inverse computation + CImg U(_width,_width), S(1,_width), V(_width,_width); + SVD(U,S,V,false); + U.transpose(); + cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k]; + S.diagonal(); + *this = V*S*U; + } + } +#endif + return *this; + } + + //! Invert the instance image, viewed as a matrix \newinstance. + CImg get_invert(const bool use_LU=true) const { + return CImg(*this,false).invert(use_LU); + } + + //! 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 { + 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(); + cimg_forX(V,x) { + const Tfloat s = S(x), invs = s>tolerance?1/s:0; + cimg_forY(V,y) V(x,y)*=invs; + } + return V*U.transpose(); + } + + //! Solve a system of linear equations. + /** + \param A Matrix of the linear system. + \note Solve \c AX=B where \c B=*this. + **/ + template + CImg& solve(const CImg& A) { + if (_depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "solve(): Instance and specified matrix (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + if (A._width==A._height) { // Classical linear system + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } +#ifdef cimg_use_lapack + char TRANS = 'N'; + int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N]; + Ttfloat + *const lapA = new Ttfloat[N*N], + *const lapB = new Ttfloat[N], + *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*N + l] = (Ttfloat)(A(k,l)); + cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + + if (!INFO) { + cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrs_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK; +#else + CImg lu(A,false); + CImg indx; + bool d; + lu._LU(indx,d); + _solve(lu,indx); +#endif + } else { // Least-square solution for non-square systems +#ifdef cimg_use_lapack + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } + char TRANS = 'N'; + int INFO, N = A._width, M = A._height, LWORK = -1, LDA = M, LDB = M, NRHS = _width; + Ttfloat WORK_QUERY; + Ttfloat + * const lapA = new Ttfloat[M*N], + * const lapB = new Ttfloat[M*NRHS]; + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, &WORK_QUERY, LWORK, INFO); + LWORK = (int) WORK_QUERY; + Ttfloat *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*M + l] = (Ttfloat)(A(k,l)); + cimg_forXY(*this,k,l) lapB[k*M + l] = (Ttfloat)((*this)(k,l)); + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, WORK, LWORK, INFO); + if (INFO != 0) + cimg::warn(_cimg_instance + "solve(): LAPACK library function sgels() returned error code %d.", + cimg_instance, + INFO); + assign(NRHS, N); + if (!INFO) + cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l]; + else + assign(A.get_pseudoinvert()*(*this)); + delete[] lapA; delete[] lapB; delete[] WORK; +#else + assign(A.get_pseudoinvert()*(*this)); +#endif + } + return *this; + } + + //! Solve a system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve(A); + } + + template + CImg& _solve(const CImg& A, const CImg& indx) { + typedef _cimg_Ttfloat Ttfloat; + const int N = (int)size(); + 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; + (*this)(i) = (T)sum; + } + for (int i = N - 1; i>=0; --i) { + sum = (*this)(i); + for (int j = i + 1; j + CImg& solve_tridiagonal(const CImg& A) { + const unsigned int siz = (unsigned int)size(); + if (A._width!=3 || A._height!=siz) + throw CImgArgumentException(_cimg_instance + "solve_tridiagonal(): Instance and tridiagonal matrix " + "(%u,%u,%u,%u,%p) have incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + const Ttfloat epsilon = 1e-4f; + CImg B = A.get_column(1), V(*this,false); + for (int i = 1; i<(int)siz; ++i) { + const Ttfloat m = A(0,i)/(B[i - 1]?B[i - 1]:epsilon); + B[i] -= m*A(2,i - 1); + V[i] -= m*V[i - 1]; + } + (*this)[siz - 1] = (T)(V[siz - 1]/(B[siz - 1]?B[siz - 1]:epsilon)); + for (int i = (int)siz - 2; i>=0; --i) (*this)[i] = (T)((V[i] - A(2,i)*(*this)[i + 1])/(B[i]?B[i]:epsilon)); + return *this; + } + + //! Solve a tridiagonal system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(A); + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + const CImg& eigen(CImg& val, CImg &vec) const { + if (is_empty()) { val.assign(); vec.assign(); } + else { + if (_width!=_height || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "eigen(): Instance is not a square matrix.", + cimg_instance); + + if (val.size()<(ulongT)_width) val.assign(1,_width); + if (vec.size()<(ulongT)_width*_width) vec.assign(_width,_width); + switch (_width) { + case 1 : { val[0] = (t)(*this)[0]; vec[0] = (t)1; } break; + case 2 : { + const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a + d; + double f = e*e - 4*(a*d - b*c); + if (f<0) + cimg::warn(_cimg_instance + "eigen(): Complex eigenvalues found.", + cimg_instance); + + f = std::sqrt(f); + const double + l1 = 0.5*(e - f), + l2 = 0.5*(e + f), + b2 = b*b, + norm1 = std::sqrt(cimg::sqr(l2 - a) + b2), + norm2 = std::sqrt(cimg::sqr(l1 - a) + b2); + val[0] = (t)l2; + val[1] = (t)l1; + if (norm1>0) { vec(0,0) = (t)(b/norm1); vec(0,1) = (t)((l2 - a)/norm1); } else { vec(0,0) = 1; vec(0,1) = 0; } + if (norm2>0) { vec(1,0) = (t)(b/norm2); vec(1,1) = (t)((l1 - a)/norm2); } else { vec(1,0) = 1; vec(1,1) = 0; } + } break; + default : + throw CImgInstanceException(_cimg_instance + "eigen(): Eigenvalues computation of general matrices is limited " + "to 2x2 matrices.", + cimg_instance); + } + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \return A list of two images [val; vec], whose meaning is similar as in eigen(CImg&,CImg&) const. + **/ + CImgList get_eigen() const { + CImgList res(2); + eigen(res[0],res[1]); + return res; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + 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); + + 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 + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \return A list of two images [val; vec], whose meaning are similar as in + symmetric_eigen(CImg&,CImg&) const. + **/ + CImgList get_symmetric_eigen() const { + CImgList res(2); + symmetric_eigen(res[0],res[1]); + return res; + } + + //! Sort pixel values and get sorting permutations. + /** + \param[out] permutations Permutation map used for the sorting. + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + **/ + template + CImg& sort(CImg& permutations, const bool is_increasing=true) { + permutations.assign(_width,_height,_depth,_spectrum); + if (is_empty()) return *this; + cimg_foroff(permutations,off) permutations[off] = (t)off; + return _quicksort(0,size() - 1,permutations,is_increasing,true); + } + + //! Sort pixel values and get sorting permutations \newinstance. + template + CImg get_sort(CImg& permutations, const bool is_increasing=true) const { + return (+*this).sort(permutations,is_increasing); + } + + //! Sort pixel values. + /** + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + \param axis Tells if the value sorting must be done along a specific axis. Can be: + - \c 0: All pixel values are sorted, independently on their initial position. + - \c 'x': Image columns are sorted, according to the first value in each column. + - \c 'y': Image rows are sorted, according to the first value in each row. + - \c 'z': Image slices are sorted, according to the first value in each slice. + - \c 'c': Image channels are sorted, according to the first value in each channel. + **/ + CImg& sort(const bool is_increasing=true, const char axis=0) { + if (is_empty()) return *this; + CImg perm; + switch (cimg::lowercase(axis)) { + case 0 : + _quicksort(0,size() - 1,perm,is_increasing,false); + break; + case 'x' : { + perm.assign(_width); + get_crop(0,0,0,0,_width - 1,0,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(perm[x],y,z,c); + } break; + case 'y' : { + perm.assign(_height); + get_crop(0,0,0,0,0,_height - 1,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,perm[y],z,c); + } break; + case 'z' : { + perm.assign(_depth); + get_crop(0,0,0,0,0,0,_depth - 1,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,perm[z],c); + } break; + case 'c' : { + perm.assign(_spectrum); + get_crop(0,0,0,0,0,0,0,_spectrum - 1).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,z,perm[c]); + } break; + default : + throw CImgArgumentException(_cimg_instance + "sort(): Invalid specified axis '%c' " + "(should be { x | y | z | c }).", + cimg_instance,axis); + } + return *this; + } + + //! Sort pixel values \newinstance. + CImg get_sort(const bool is_increasing=true, const char axis=0) const { + return (+*this).sort(is_increasing,axis); + } + + template + CImg& _quicksort(const long indm, const long indM, CImg& permutations, + const bool is_increasing, const bool is_permutations) { + if (indm(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]>(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]>(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } else { + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]<(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } + if (indM - indm>=3) { + const T pivot = (*this)[mid]; + long i = indm, j = indM; + if (is_increasing) { + do { + while ((*this)[i]pivot) --j; + if (i<=j) { + if (is_permutations) cimg::swap(permutations[i],permutations[j]); + cimg::swap((*this)[i++],(*this)[j--]); + } + } while (i<=j); + } else { + do { + while ((*this)[i]>pivot) ++i; + while ((*this)[j] A; // Input matrix (assumed to contain some values) + CImg<> U,S,V; + A.SVD(U,S,V) + \endcode + **/ + 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 { + if (is_empty()) { U.assign(); S.assign(); V.assign(); } + else { + U = *this; + if (lambda!=0) { + const unsigned int delta = std::min(U._width,U._height); + for (unsigned int i = 0; i rv1(_width); + t anorm = 0, c, f, g = 0, h, s, scale = 0; + int l = 0, nm = 0; + + cimg_forX(U,i) { + l = i + 1; rv1[i] = scale*g; g = s = scale = 0; + if (i=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; --i) { + if (i=0; --i) { + l = i + 1; g = S[i]; + for (int j = l; j=0; --k) { + for (unsigned int its = 0; its=1; --l) { + nm = l - 1; + if ((cimg::abs(rv1[l]) + anorm)==anorm) { flag = false; break; } + if ((cimg::abs(S[nm]) + anorm)==anorm) break; + } + if (flag) { + c = 0; s = 1; + for (int i = l; i<=k; ++i) { + f = s*rv1[i]; rv1[i] = c*rv1[i]; + if ((cimg::abs(f) + anorm)==anorm) break; + g = S[i]; h = cimg::_hypot(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h; + cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c + z*s; U(i,j) = z*c - y*s; } + } + } + + const t z = S[k]; + if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; } + nm = k - 1; + t x = S[l], y = S[nm]; + g = rv1[nm]; h = rv1[k]; + f = ((y - z)*(y + z)+(g - h)*(g + h))/std::max((t)1e-25,2*h*y); + g = cimg::_hypot(f,(t)1); + f = ((x - z)*(x + z)+h*((y/(f + (f>=0?g:-g))) - h))/std::max((t)1e-25,x); + c = s = 1; + for (int j = l; j<=nm; ++j) { + const int i = j + 1; + g = rv1[i]; h = s*g; g = c*g; + t y = S[i]; + t z = cimg::_hypot(f,h); + rv1[j] = z; c = f/std::max((t)1e-25,z); s = h/std::max((t)1e-25,z); + f = x*c + g*s; g = g*c - x*s; h = y*s; y*=c; + cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c + z*s; V(i,jj) = z*c - x*s; } + z = cimg::_hypot(f,h); S[j] = z; + if (z) { z = 1/std::max((t)1e-25,z); c = f*z; s = h*z; } + f = c*g + s*y; x = c*y - s*g; + cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c + z*s; U(i,jj) = z*c - y*s; } + } + rv1[l] = 0; rv1[k]=f; S[k]=x; + } + } + + if (sorting) { + CImg permutations; + CImg tmp(_width); + S.sort(permutations,false); + cimg_forY(U,k) { + cimg_forY(permutations,y) tmp(y) = U(permutations(y),k); + std::memcpy(U.data(0,k),tmp._data,sizeof(t)*_width); + } + cimg_forY(V,k) { + cimg_forY(permutations,y) tmp(y) = V(permutations(y),k); + std::memcpy(V.data(0,k),tmp._data,sizeof(t)*_width); + } + } + } + return *this; + } + + //! Compute the SVD of the instance image, viewed as a general matrix. + /** + \return A list of three images [U; S; V], whose meaning is similar as in + SVD(CImg&,CImg&,CImg&,bool,unsigned int,float) const. + **/ + CImgList get_SVD(const bool sorting=true, + const unsigned int max_iteration=40, const float lambda=0) const { + CImgList res(3); + SVD(res[0],res[1],res[2],sorting,max_iteration,lambda); + return res; + } + + // [internal] Compute the LU decomposition of a permuted matrix. + template + CImg& _LU(CImg& indx, bool& d) { + const int N = width(); + int imax = 0; + CImg vv(N); + indx.assign(N); + d = true; + cimg_forX(*this,i) { + Tfloat vmax = 0; + cimg_forX(*this,j) { + const Tfloat tmp = cimg::abs((*this)(j,i)); + if (tmp>vmax) vmax = tmp; + } + if (vmax==0) { indx.fill(0); return fill(0); } + vv[i] = 1/vmax; + } + cimg_forX(*this,j) { + for (int i = 0; i=vmax) { vmax=tmp; imax=i; } + } + if (j!=imax) { + cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j)); + d =!d; + vv[imax] = vv[j]; + } + indx[j] = (t)imax; + if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20; + if (j + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + 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 " + "than number of nodes %u.", + pixel_type(),starting_node,nb_nodes); + CImg dist(1,nb_nodes,1,1,cimg::type::max()); + dist(starting_node) = 0; + previous_node.assign(1,nb_nodes,1,1,(t)-1); + previous_node(starting_node) = (t)starting_node; + CImg Q(nb_nodes); + cimg_forX(Q,u) Q(u) = (unsigned int)u; + cimg::swap(Q(starting_node),Q(0)); + unsigned int sizeQ = nb_nodes; + while (sizeQ) { + // Update neighbors from minimal vertex + const unsigned int umin = Q(0); + if (umin==ending_node) sizeQ = 0; + else { + const T dmin = dist(umin); + const T infty = cimg::type::max(); + for (unsigned int q = 1; qdist(Q(left))) || + (rightdist(Q(right)));) { + if (right + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + const unsigned int starting_node, const unsigned int ending_node=~0U) { + CImg foo; + return dijkstra(distance,nb_nodes,starting_node,ending_node,foo); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + /** + \param starting_node Indice of the starting node. + \param ending_node Indice of the ending node. + \param previous_node Array that gives the previous node indice in the path to the starting node + (optional parameter). + \return Array of distances of each node to the starting node. + \note image instance corresponds to the adjacency matrix of the graph. + **/ + template + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) { + return get_dijkstra(starting_node,ending_node,previous_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + template + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) const { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "dijkstra(): Instance is not a graph adjacency matrix.", + cimg_instance); + + return dijkstra(*this,_width,starting_node,ending_node,previous_node); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) { + return get_dijkstra(starting_node,ending_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const { + CImg foo; + return get_dijkstra(starting_node,ending_node,foo); + } + + //! Return an image containing the Ascii codes of the specified string. + /** + \param str input C-string to encode as an image. + \param is_last_zero Tells if the ending \c '0' character appear in the resulting image. + \param is_shared Return result that shares its buffer with \p str. + **/ + static CImg string(const char *const str, const bool is_last_zero=true, const bool is_shared=false) { + if (!str) return CImg(); + 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 vector(const T& a0) { + CImg r(1,1); + r[0] = a0; + return r; + } + + //! Return a \c 1x2 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + **/ + static CImg vector(const T& a0, const T& a1) { + CImg r(1,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + return r; + } + + //! Return a \c 1x3 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + **/ + static CImg vector(const T& a0, const T& a1, const T& a2) { + CImg r(1,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + return r; + } + + //! Return a \c 1x4 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + \param a3 Fourth vector value. + **/ + static CImg 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; + 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; + 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; + 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; + 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; + 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; + return r; + } + + //! Return a \c 1x10 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) { + 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; + return r; + } + + //! Return a \c 1x11 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) { + 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; + return r; + } + + //! Return a \c 1x12 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) { + 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; + return r; + } + + //! Return a \c 1x13 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) { + 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; + return r; + } + + //! Return a \c 1x14 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) { + 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; + return r; + } + + //! Return a \c 1x15 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) { + 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; + return r; + } + + //! Return a \c 1x16 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) { + 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; + return r; + } + + //! Return a 1x1 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg matrix(const T& a0) { + return vector(a0); + } + + //! Return a 2x2 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, + const T& a2, const T& a3) { + CImg r(2,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + *(ptr++) = a2; *(ptr++) = a3; + return r; + } + + //! Return a 3x3 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + \param a4 Fifth matrix value. + \param a5 Sixth matrix value. + \param a6 Seventh matrix value. + \param a7 Eighth matrix value. + \param a8 Nineth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, const T& a2, + const T& a3, const T& a4, const T& a5, + const T& a6, const T& a7, const T& a8) { + CImg r(3,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; + *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; + return r; + } + + //! Return a 4x4 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13, const T& a14, const T& a15) { + CImg r(4,4); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15; + return r; + } + + //! Return a 5x5 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, + const T& a5, const T& a6, const T& a7, const T& a8, const T& a9, + const T& a10, const T& a11, const T& a12, const T& a13, const T& a14, + const T& a15, const T& a16, const T& a17, const T& a18, const T& a19, + const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) { + CImg r(5,5); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; + *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9; + *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; + *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19; + *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24; + return r; + } + + //! Return a 1x1 symmetric matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg tensor(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 symmetric matrix tensor containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2) { + return matrix(a0,a1,a1,a2); + } + + //! Return a 3x3 symmetric matrix containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { + return matrix(a0,a1,a2,a1,a3,a4,a2,a4,a5); + } + + //! Return a 1x1 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1) { + return matrix(a0,0,0,a1); + } + + //! Return a 3x3 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2) { + return matrix(a0,0,0,0,a1,0,0,0,a2); + } + + //! Return a 4x4 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3) { + return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3); + } + + //! Return a 5x5 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { + return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4); + } + + //! Return a NxN identity matrix. + /** + \param N Dimension of the matrix. + **/ + static CImg identity_matrix(const unsigned int N) { + CImg res(N,N,1,1,0); + cimg_forX(res,x) res(x,x) = 1; + return res; + } + + //! Return a N-numbered sequence vector from \p a0 to \p a1. + /** + \param N Size of the resulting vector. + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + static CImg sequence(const unsigned int N, const T& a0, const T& a1) { + if (N) return CImg(1,N).sequence(a0,a1); + return CImg(); + } + + //! Return a 3x3 rotation matrix from an { axis + angle } or a quaternion. + /** + \param x X-coordinate of the rotation axis, or first quaternion coordinate. + \param y Y-coordinate of the rotation axis, or second quaternion coordinate. + \param z Z-coordinate of the rotation axis, or third quaternion coordinate. + \param w Angle of the rotation axis (in degree), or fourth quaternion coordinate. + \param is_quaternion Tell is the four arguments denotes a set { axis + angle } or a quaternion (x,y,z,w). + **/ + static CImg rotation_matrix(const float x, const float y, const float z, const float w, + const bool is_quaternion=false) { + double X, Y, Z, W, N; + if (is_quaternion) { + N = std::sqrt((double)x*x + (double)y*y + (double)z*z + (double)w*w); + if (N>0) { X = x/N; Y = y/N; Z = z/N; W = w/N; } + else { X = Y = Z = 0; W = 1; } + return CImg::matrix((T)(X*X + Y*Y - Z*Z - W*W),(T)(2*Y*Z - 2*X*W),(T)(2*X*Z + 2*Y*W), + (T)(2*X*W + 2*Y*Z),(T)(X*X - Y*Y + Z*Z - W*W),(T)(2*Z*W - 2*X*Y), + (T)(2*Y*W - 2*X*Z),(T)(2*X*Y + 2*Z*W),(T)(X*X - Y*Y - Z*Z + W*W)); + } + N = cimg::hypot((double)x,(double)y,(double)z); + if (N>0) { X = x/N; Y = y/N; Z = z/N; } + else { X = Y = 0; Z = 1; } + const double ang = w*cimg::PI/180, c = std::cos(ang), omc = 1 - c, s = std::sin(ang); + return CImg::matrix((T)(X*X*omc + c),(T)(X*Y*omc - Z*s),(T)(X*Z*omc + Y*s), + (T)(X*Y*omc + Z*s),(T)(Y*Y*omc + c),(T)(Y*Z*omc - X*s), + (T)(X*Z*omc - Y*s),(T)(Y*Z*omc + X*s),(T)(Z*Z*omc + c)); + } + + //@} + //----------------------------------- + // + //! \name Value Manipulation + //@{ + //----------------------------------- + + //! Fill all pixel values with specified value. + /** + \param val Fill value. + **/ + CImg& fill(const T& val) { + if (is_empty()) return *this; + if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val; + else std::memset(_data,(int)(ulongT)val,sizeof(T)*size()); // Double cast to allow val to be (void*) + return *this; + } + + //! Fill all pixel values with specified value \newinstance. + CImg get_fill(const T& val) const { + return CImg(_width,_height,_depth,_spectrum).fill(val); + } + + //! Fill sequentially all pixel values with specified values. + /** + \param val0 First fill value. + \param val1 Second fill value. + **/ + CImg& fill(const T& val0, const T& val1) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 1; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 2; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 3; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 4; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 5; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 6; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 7; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 8; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 9; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 10; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 11; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 12; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 13; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 14; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 15; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14,val15); + } + + //! Fill sequentially pixel values according to a given expression. + /** + \param expression C-string describing a math formula, or a sequence of values. + \param repeat_values In case a list of values is provided, tells if this list must be repeated for the filling. + \param allow_formula Tells that mathematical formulas are authorized for the filling. + \param list_inputs In case of a mathematical expression, attach a list of images to the specified expression. + \param[out] list_outputs In case of a math expression, list of images atatched to the specified expression. + **/ + CImg& 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?1:0,list_inputs,list_outputs,"fill",0); + } + + // 'formula_mode' = { 0 = does not allow formula | 1 = allow formula | + // 2 = allow formula but do not fill image values }. + CImg& _fill(const char *const expression, const bool repeat_values, const unsigned int formula_mode, + const CImgList *const list_inputs, CImgList *const list_outputs, + const char *const calling_function, const CImg *provides_copy) { + if (is_empty() || !expression || !*expression) return *this; + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + CImg is_error; + bool is_value_sequence = false; + cimg_abort_init; + + if (formula_mode) { + + // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser. + double value; + char sep; + const int err = cimg_sscanf(expression,"%lf %c",&value,&sep); + if (err==1 || (err==2 && sep==',')) { + if (err==1) return fill((T)value); + else is_value_sequence = true; + } + + // Try to fill values according to a formula. + _cimg_abort_init_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 + + bool do_in_parallel = false; +#ifdef cimg_use_openmp + cimg_openmp_if(*expression=='*' || *expression==':' || + (mp.is_parallelizable && _width>=(cimg_openmp_sizefactor)*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; + if (formula_mode==2) 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; + if (formula_mode==2) 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 cimg_openmp_collapse(2)) + cimg_forYZ(*this,y,z) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,0); + else { + 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 + } + + } else { // Scalar-valued expression + T *ptrd = *expression=='<'?end() - 1:_data; + if (*expression=='<') { + if (formula_mode==2) 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); } + } else if (*expression=='>' || !do_in_parallel) { + if (formula_mode==2) 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); } + } 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 cimg_openmp_collapse(3)) + cimg_forYZC(*this,y,z,c) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,c); + else { + 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 + } +#endif + } + } + mp.end(); + } catch (CImgException& e) { CImg::string(e._message).move_to(is_error); } + } + + // Try to fill values according to a value sequence. + if (!formula_mode || 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?1:0,list_inputs,list_outputs); + } + + //! Fill sequentially pixel values according to the values found in another image. + /** + \param values Image containing the values used for the filling. + \param repeat_values In case there are less values than necessary in \c values, tells if these values must be + repeated for the filling. + **/ + template + CImg& fill(const CImg& values, const bool repeat_values=true) { + if (is_empty() || !values) return *this; + T *ptrd = _data, *ptre = ptrd + size(); + for (t *ptrs = values._data, *ptrs_end = ptrs + values.size(); ptrs + CImg get_fill(const CImg& values, const bool repeat_values=true) const { + return repeat_values?CImg(_width,_height,_depth,_spectrum).fill(values,repeat_values): + (+*this).fill(values,repeat_values); + } + + //! Fill pixel values along the X-axis at a specified pixel position. + /** + \param y Y-coordinate of the filled column. + \param z Z-coordinate of the filled column. + \param c C-coordinate of the filled column. + \param a0 First fill value. + **/ + CImg& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const int a0, ...) { +#define _cimg_fill1(x,y,z,c,off,siz,t) { \ + va_list ap; va_start(ap,a0); T *ptrd = data(x,y,z,c); *ptrd = (T)a0; \ + for (unsigned int k = 1; k& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const double a0, ...) { + if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,double); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position. + /** + \param x X-coordinate of the filled row. + \param z Z-coordinate of the filled row. + \param c C-coordinate of the filled row. + \param a0 First fill value. + **/ + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const int a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,int); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position \overloading. + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position. + /** + \param x X-coordinate of the filled slice. + \param y Y-coordinate of the filled slice. + \param c C-coordinate of the filled slice. + \param a0 First fill value. + **/ + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position \overloading. + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position. + /** + \param x X-coordinate of the filled channel. + \param y Y-coordinate of the filled channel. + \param z Z-coordinate of the filled channel. + \param a0 First filling value. + **/ + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position \overloading. + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double); + return *this; + } + + //! Discard specified sequence of values in the image buffer, along a specific axis. + /** + \param values Sequence of values to discard. + \param axis Axis along which the values are discarded. If set to \c 0 (default value) + the method does it for all the buffer values and returns a one-column vector. + \note Discarded values will change the image geometry, so the resulting image + is returned as a one-column vector. + **/ + template + CImg& discard(const CImg& values, const char axis=0) { + if (is_empty() || !values) return *this; + return get_discard(values,axis).move_to(*this); + } + + template + CImg get_discard(const CImg& values, const char axis=0) const { + CImg res; + if (!values) return +*this; + if (is_empty()) return res; + const ulongT vsiz = values.size(); + const char _axis = cimg::lowercase(axis); + ulongT j = 0; + unsigned int k = 0; + int i0 = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) { + if ((*this)(i)!=(T)values[j]) { + if (j) --i; + res.draw_image(k,get_columns(i0,i)); + k+=i - i0 + 1; i0 = i + 1; j = 0; + } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = (int)i + 1; }} + } + const ulongT siz = size(); + if ((ulongT)i0& discard(const char axis=0) { + return get_discard(axis).move_to(*this); + } + + //! Discard neighboring duplicates in the image buffer, along the specified axis \newinstance. + CImg get_discard(const char axis=0) const { + CImg res; + if (is_empty()) return res; + const char _axis = cimg::lowercase(axis); + T current = *_data?(T)0:(T)1; + int j = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) + if ((*this)(i)!=current) { res.draw_image(j++,get_column(i)); current = (*this)(i); } + res.resize(j,-100,-100,-100,0); + } break; + case 'y' : { + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { res.draw_image(0,j++,get_row(i)); current = (*this)(0,i); } + res.resize(-100,j,-100,-100,0); + } break; + case 'z' : { + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { res.draw_image(0,0,j++,get_slice(i)); current = (*this)(0,0,i); } + res.resize(-100,-100,j,-100,0); + } break; + case 'c' : { + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { res.draw_image(0,0,0,j++,get_channel(i)); current = (*this)(0,0,0,i); } + res.resize(-100,-100,-100,j,0); + } break; + default : { + res.unroll('y'); + cimg_foroff(*this,i) + if ((*this)[i]!=current) res[j++] = current = (*this)[i]; + res.resize(-100,j,-100,-100,0); + } + } + return res; + } + + //! Invert endianness of all pixel values. + /** + **/ + CImg& invert_endianness() { + cimg::invert_endianness(_data,size()); + return *this; + } + + //! Invert endianness of all pixel values \newinstance. + CImg get_invert_endianness() const { + return (+*this).invert_endianness(); + } + + //! Fill image with random values in specified range. + /** + \param val_min Minimal authorized random value. + \param val_max Maximal authorized random value. + \note Random variables are uniformely distributed in [val_min,val_max]. + **/ + CImg& 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_pragma_openmp(parallel cimg_openmp_if_size(size(),524288)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; + } + + //! Fill image with random values in specified range \newinstance. + CImg get_rand(const T& val_min, const T& val_max) const { + return (+*this).rand(val_min,val_max); + } + + //! Round pixel values. + /** + \param y Rounding precision. + \param rounding_type Rounding type. Can be: + - \c -1: Backward. + - \c 0: Nearest. + - \c 1: Forward. + **/ + CImg& round(const double y=1, const int rounding_type=0) { + if (y>0) cimg_openmp_for(*this,cimg::round(*ptr,y,rounding_type),8192); + return *this; + } + + //! Round pixel values \newinstance. + CImg get_round(const double y=1, const unsigned int rounding_type=0) const { + return (+*this).round(y,rounding_type); + } + + //! Add random noise to pixel values. + /** + \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the + global value range. + \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper, + \p 3=Poisson or \p 4=Rician). + \return A reference to the modified image instance. + \note + - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on + the image value itself. + - Function \p CImg::get_noise() is also defined. It returns a non-shared modified copy of the image instance. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_noise(40); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_noise.jpg + **/ + CImg& noise(const double sigma, const unsigned int noise_type=0) { + if (is_empty()) return *this; + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + Tfloat nsigma = (Tfloat)sigma, m = 0, M = 0; + if (nsigma==0 && noise_type!=3) return *this; + if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M); + if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.); + switch (noise_type) { + case 0 : { // Gaussian noise + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) { + Tfloat val = (Tfloat)(_data[off] + nsigma*cimg::grand(&rng)); + if (val>vmax) val = vmax; + if (valvmax) val = vmax; + if (val::is_float()) { --m; ++M; } + else { m = (Tfloat)cimg::type::min(); M = (Tfloat)cimg::type::max(); } + } + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) if (cimg::rand(100,&rng)vmax) val = vmax; + if (val get_noise(const double sigma, const unsigned int noise_type=0) const { + return (+*this).noise(sigma,noise_type); + } + + //! Linearly normalize pixel values. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(160,220); + (img,res).display(); + \endcode + \image html ref_normalize2.jpg + **/ + CImg& normalize(const T& min_value, const T& max_value) { + 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); + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm. + /** + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_normalize.jpg + **/ + CImg& normalize() { + const ulongT whd = (ulongT)_width*_height*_depth; + 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) { + const T *ptrs = ptrd; + float n = 0; + cimg_forC(*this,c) { n+=cimg::sqr((float)*ptrs); ptrs+=whd; } + n = (float)std::sqrt(n); + T *_ptrd = ptrd++; + if (n>0) cimg_forC(*this,c) { *_ptrd = (T)(*_ptrd/n); _ptrd+=whd; } + else cimg_forC(*this,c) { *_ptrd = (T)0; _ptrd+=whd; } + } + } + return *this; + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm \newinstance. + CImg get_normalize() const { + return CImg(*this,false).normalize(); + } + + //! Compute Lp-norm of each multi-valued pixel of the image instance. + /** + \param norm_type Type of computed vector norm (can be \p -1=Linf, or \p greater or equal than 0). + \par Example + \code + const CImg img("reference.jpg"), res = img.get_norm(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_norm.jpg + **/ + CImg& norm(const int norm_type=2) { + if (_spectrum==1 && norm_type) return abs(); + return get_norm(norm_type).move_to(*this); + } + + //! Compute L2-norm of each multi-valued pixel of the image instance \newinstance. + CImg get_norm(const int norm_type=2) const { + if (is_empty()) return *this; + if (_spectrum==1 && norm_type) return get_abs(); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(_width,_height,_depth); + switch (norm_type) { + 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { const Tfloat val = (Tfloat)cimg::abs(*_ptrs); if (val>n) n = val; _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 0 : { // L0-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + unsigned int n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=*_ptrs==0?0:1; _ptrs+=whd; } + *(ptrd++) = (Tfloat)n; + } + } + } break; + case 1 : { // L1-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 2 : { // L2-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n); + } + } + } break; + default : { // Linf-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=std::pow(cimg::abs((Tfloat)*_ptrs),(Tfloat)norm_type); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::pow((Tfloat)n,1/(Tfloat)norm_type); + } + } + } + } + return res; + } + + //! Cut pixel values in specified range. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_cut(160,220); + (img,res).display(); + \endcode + \image html ref_cut.jpg + **/ + CImg& cut(const T& min_value, const T& max_value) { + if (is_empty()) return *this; + const T a = min_value get_cut(const T& min_value, const T& max_value) const { + return (+*this).cut(min_value,max_value); + } + + //! Uniformly quantize pixel values. + /** + \param nb_levels Number of quantization levels. + \param keep_range Tells if resulting values keep the same range as the original ones. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_quantize(4); + (img,res).display(); + \endcode + \image html ref_quantize.jpg + **/ + CImg& quantize(const unsigned int nb_levels, const bool keep_range=true) { + if (!nb_levels) + throw CImgArgumentException(_cimg_instance + "quantize(): Invalid quantization request with 0 values.", + cimg_instance); + + if (is_empty()) return *this; + Tfloat m, M = (Tfloat)max_min(m), range = M - m; + if (range>0) { + if (keep_range) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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; + } + + //! Uniformly quantize pixel values \newinstance. + CImg get_quantize(const unsigned int n, const bool keep_range=true) const { + return (+*this).quantize(n,keep_range); + } + + //! Threshold pixel values. + /** + \param value Threshold value + \param soft_threshold Tells if soft thresholding must be applied (instead of hard one). + \param strict_threshold Tells if threshold value is strict. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_threshold(128); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_threshold.jpg + **/ + CImg& threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) { + if (is_empty()) return *this; + if (strict_threshold) { + if (soft_threshold) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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(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(size(),65536)) + cimg_rofoff(*this,off) _data[off] = _data[off]>=value?(T)1:(T)0; + } + return *this; + } + + //! Threshold pixel values \newinstance. + CImg get_threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) const { + return (+*this).threshold(value,soft_threshold,strict_threshold); + } + + //! Compute the histogram of pixel values. + /** + \param nb_levels Number of desired histogram levels. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \note + - The histogram H of an image I is the 1D function where H(x) counts the number of 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. + \par Example + \code + const CImg img = CImg("reference.jpg").histogram(256); + img.display_graph(0,3); + \endcode + \image html ref_histogram.jpg + **/ + CImg& histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) { + return get_histogram(nb_levels,min_value,max_value).move_to(*this); + } + + //! Compute the histogram of pixel values \overloading. + CImg& histogram(const unsigned int nb_levels) { + return get_histogram(nb_levels).move_to(*this); + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) const { + if (!nb_levels || is_empty()) return CImg(); + const double + vmin = (double)(min_value res(nb_levels,1,1,1,0); + cimg_rof(*this,ptrs,T) { + const T val = *ptrs; + if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels - 1:(unsigned int)((val - vmin)*nb_levels/(vmax - vmin))]; + } + return res; + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels) const { + if (!nb_levels || is_empty()) return CImg(); + T vmax = 0, vmin = min_max(vmax); + return get_histogram(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values. + /** + \param nb_levels Number of histogram levels used for the equalization. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_equalize(256); + (img,res).display(); + \endcode + \image html ref_equalize.jpg + **/ + CImg& equalize(const unsigned int nb_levels, const T& min_value, const T& max_value) { + if (!nb_levels || is_empty()) return *this; + const T + vmin = min_value hist = get_histogram(nb_levels,vmin,vmax); + ulongT cumul = 0; + cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; } + if (!cumul) cumul = 1; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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; + } + + //! Equalize histogram of pixel values \overloading. + CImg& equalize(const unsigned int nb_levels) { + if (!nb_levels || is_empty()) return *this; + T vmax = 0, vmin = min_max(vmax); + return equalize(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels, const T& val_min, const T& val_max) const { + return (+*this).equalize(nblevels,val_min,val_max); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels) const { + return (+*this).equalize(nblevels); + } + + //! Index multi-valued pixels regarding to a specified colormap. + /** + \param colormap Multi-valued colormap used as the basis for multi-valued pixel indexing. + \param dithering Level of dithering (0=disable, 1=standard level). + \param map_indexes Tell if the values of the resulting image are the colormap indices or the colormap vectors. + \note + - \p img.index(colormap,dithering,1) is equivalent to img.index(colormap,dithering,0).map(colormap). + \par Example + \code + const CImg img("reference.jpg"), colormap(3,1,1,3, 0,128,255, 0,128,255, 0,128,255); + const CImg res = img.get_index(colormap,1,true); + (img,res).display(); + \endcode + \image html ref_index.jpg + **/ + template + CImg& index(const CImg& colormap, const float dithering=1, const bool map_indexes=false) { + return get_index(colormap,dithering,map_indexes).move_to(*this); + } + + //! Index multi-valued pixels regarding to a specified colormap \newinstance. + template + CImg::Tuint> + get_index(const CImg& colormap, const float dithering=1, const bool map_indexes=true) const { + if (colormap._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "index(): Instance and specified colormap (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + typedef typename CImg::Tuint tuint; + if (is_empty()) return CImg(); + const ulongT + 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 + const float ndithering = cimg::cut(dithering,0,1)/16; + Tfloat valm = 0, valM = (Tfloat)max_min(valm); + if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; } + CImg cache = get_crop(-1,0,0,0,_width,1,0,_spectrum - 1); + Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0); + const ulongT cwhd = (ulongT)cache._width*cache._height*cache._depth; + switch (_spectrum) { + case 1 : { // Optimized for scalars + cimg_forYZ(*this,y,z) { + if (yvalM?valM:_val0; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1, + _val2 = (Tfloat)*ptrs2, val2 = _val2valM?valM:_val2; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrpvalM?valM:_val; + dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd; + } + if (dist=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0=(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, + *ptrs_end = ptrs0 + _width; ptrs0::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0=(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::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrp img("reference.jpg"), + colormap1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255), + colormap2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255), + res = img.get_index(colormap1,0).map(colormap2); + (img,res).display(); + \endcode + \image html ref_map.jpg + **/ + template + CImg& map(const CImg& colormap, const unsigned int boundary_conditions=0) { + return get_map(colormap,boundary_conditions).move_to(*this); + } + + //! Map predefined colormap on the scalar (indexed) image instance \newinstance. + template + 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); + + const ulongT + whd = (ulongT)_width*_height*_depth, + cwhd = (ulongT)colormap._width*colormap._height*colormap._depth, + cwhd2 = 2*cwhd; + CImg res(_width,_height,_depth,colormap._spectrum==1?_spectrum:colormap._spectrum); + switch (colormap._spectrum) { + + case 1 : { // Optimized for scalars + const T *ptrs = _data; + switch (boundary_conditions) { + case 3 : // Mirror + cimg_for(res,ptrd,t) { + const ulongT ind = ((ulongT)*(ptrs++))%cwhd2; + *ptrd = colormap[ind& label(const bool is_high_connectivity=false, const Tfloat tolerance=0) { + return get_label(is_high_connectivity,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + CImg get_label(const bool is_high_connectivity=false, + const Tfloat tolerance=0) const { + if (is_empty()) return CImg(); + + // Create neighborhood tables. + int dx[13], dy[13], dz[13], nb = 0; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 0; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 0; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 0; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 0; + } + if (_depth>1) { // 3D version + dx[nb] = 0; dy[nb] = 0; dz[nb++]=1; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = -1; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = -1; + + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 1; + } + } + return _label(nb,dx,dy,dz,tolerance); + } + + //! Label connected components \overloading. + /** + \param connectivity_mask Mask of the neighboring pixels. + \param tolerance Tolerance used to determine if two neighboring pixels belong to the same region. + **/ + template + CImg& label(const CImg& connectivity_mask, const Tfloat tolerance=0) { + return get_label(connectivity_mask,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + template + CImg get_label(const CImg& connectivity_mask, + const Tfloat tolerance=0) const { + 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); + nb = 0; + cimg_forXYZ(connectivity_mask,x,y,z) if ((x || y || z) && + connectivity_mask(x,y,z)) { + dx[nb] = x; dy[nb] = y; dz[nb++] = z; + } + return _label(nb,dx,dy,dz,tolerance); + } + + CImg _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); + + // Init label numbers. + ulongT *ptr = _res.data(); + cimg_foroff(_res,p) *(ptr++) = p; + + // For each neighbour-direction, label. + for (unsigned int n = 0; n& _system_strescape() { +#define cimg_system_strescape(c,s) case c : if (p!=ptrs) CImg(ptrs,(unsigned int)(p-ptrs),1,1,1,false).\ + move_to(list); \ + CImg(s,(unsigned int)std::strlen(s),1,1,1,false).move_to(list); ptrs = p + 1; break + CImgList list; + const T *ptrs = _data; + cimg_for(*this,p,T) switch ((int)*p) { + cimg_system_strescape('\\',"\\\\"); + cimg_system_strescape('\"',"\\\""); + cimg_system_strescape('!',"\"\\!\""); + cimg_system_strescape('`',"\\`"); + cimg_system_strescape('$',"\\$"); + } + if (ptrs(ptrs,(unsigned int)(end()-ptrs),1,1,1,false).move_to(list); + return (list>'x').move_to(*this); + } + + //@} + //--------------------------------- + // + //! \name Color Base Management + //@{ + //--------------------------------- + + //! Return colormap \e "default", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_default.jpg + **/ + static const CImg& default_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,256,1,3); + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap(0,index,0) = (Tuchar)r; + colormap(0,index,1) = (Tuchar)g; + colormap(0,index++,2) = (Tuchar)b; + } + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "HSV", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hsv.jpg + **/ + static const CImg& HSV_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + CImg tmp(1,256,1,3,1); + tmp.get_shared_channel(0).sequence(0,359); + colormap = tmp.HSVtoRGB(); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "lines", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_lines.jpg + **/ + static const CImg& 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; + } + + //! Return colormap \e "hot", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hot.jpg + **/ + static const CImg& hot_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[1] = colormap[2] = colormap[3] = colormap[6] = colormap[7] = colormap[11] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cool", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cool.jpg + **/ + static const CImg& cool_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) colormap.assign(1,2,1,3).fill((T)0,(T)255,(T)255,(T)0,(T)255,(T)255).resize(1,256,1,3,3); + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "jet", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_jet.jpg + **/ + static const CImg& jet_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[2] = colormap[3] = colormap[5] = colormap[6] = colormap[8] = colormap[9] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "flag", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_flag.jpg + **/ + static const CImg& flag_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[0] = colormap[1] = colormap[5] = colormap[9] = colormap[10] = 255; + colormap.resize(1,256,1,3,0,2); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cube", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cube.jpg + **/ + static const CImg& cube_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,8,1,3,(T)0); + colormap[1] = colormap[3] = colormap[5] = colormap[7] = + colormap[10] = colormap[11] = colormap[12] = colormap[13] = + colormap[20] = colormap[21] = colormap[22] = colormap[23] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Convert pixel values from sRGB to RGB color spaces. + CImg& sRGBtoRGB() { + if (is_empty()) return *this; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + sval = (Tfloat)_data[off]/255, + val = (Tfloat)(sval<=0.04045f?sval/12.92f:std::pow((sval + 0.055f)/(1.055f),2.4f)); + _data[off] = (T)cimg::cut(val*255,0,255); + } + return *this; + } + + //! Convert pixel values from sRGB to RGB color spaces \newinstance. + CImg get_sRGBtoRGB() const { + return CImg(*this,false).sRGBtoRGB(); + } + + //! 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(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + val = (Tfloat)_data[off]/255, + sval = (Tfloat)(val<=0.0031308f?val*12.92f:1.055f*std::pow(val,0.416667f) - 0.055f); + _data[off] = (T)cimg::cut(sval*255,0,255); + } + return *this; + } + + //! Convert pixel values from RGB to sRGB color spaces \newinstance. + CImg get_RGBtosRGB() const { + return CImg(*this,false).RGBtosRGB(); + } + + //! Convert pixel values from RGB to HSI color spaces. + CImg& RGBtoHSI() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSI(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSI color spaces \newinstance. + CImg get_RGBtoHSI() const { + return CImg(*this,false).RGBtoHSI(); + } + + //! Convert pixel values from HSI to RGB color spaces. + CImg& HSItoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSItoRGB(): Instance is not a HSI image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSItoRGB() const { + return CImg< Tuchar>(*this,false).HSItoRGB(); + } + + //! Convert pixel values from RGB to HSL color spaces. + CImg& RGBtoHSL() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSL(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSL color spaces \newinstance. + CImg get_RGBtoHSL() const { + return CImg(*this,false).RGBtoHSL(); + } + + //! Convert pixel values from HSL to RGB color spaces. + CImg& HSLtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSLtoRGB(): Instance is not a HSL image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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.f/3 - ntr):p, + G = 6*ntg<1?p + (q - p)*6*ntg:2*ntg<1?q:3*ntg<2?p + (q - p)*6*(2.f/3 - ntg):p, + B = 6*ntb<1?p + (q - p)*6*ntb:2*ntb<1?q:3*ntb<2?p + (q - p)*6*(2.f/3 - ntb):p; + p1[N] = (T)cimg::cut(255*R,0,255); + p2[N] = (T)cimg::cut(255*G,0,255); + p3[N] = (T)cimg::cut(255*B,0,255); + } + return *this; + } + + //! Convert pixel values from HSL to RGB color spaces \newinstance. + CImg get_HSLtoRGB() const { + return CImg(*this,false).HSLtoRGB(); + } + + //! Convert pixel values from RGB to HSV color spaces. + CImg& RGBtoHSV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSV color spaces \newinstance. + CImg get_RGBtoHSV() const { + return CImg(*this,false).RGBtoHSV(); + } + + //! Convert pixel values from HSV to RGB color spaces. + CImg& HSVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSVtoRGB(): Instance is not a HSV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSVtoRGB() const { + return CImg(*this,false).HSVtoRGB(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& RGBtoYCbCr() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYCbCr(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_RGBtoYCbCr() const { + return CImg(*this,false).RGBtoYCbCr(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& YCbCrtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YCbCrtoRGB(): Instance is not a YCbCr image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_YCbCrtoRGB() const { + return CImg(*this,false).YCbCrtoRGB(); + } + + //! Convert pixel values from RGB to YUV color spaces. + CImg& RGBtoYUV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYUV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_RGBtoYUV() const { + return CImg(*this,false).RGBtoYUV(); + } + + //! Convert pixel values from YUV to RGB color spaces. + CImg& YUVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YUVtoRGB(): Instance is not a YUV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_YUVtoRGB() const { + return CImg< Tuchar>(*this,false).YUVtoRGB(); + } + + //! Convert pixel values from RGB to CMY color spaces. + CImg& RGBtoCMY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoCMY(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoCMY() const { + return CImg(*this,false).RGBtoCMY(); + } + + //! Convert pixel values from CMY to RGB color spaces. + CImg& CMYtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoRGB(): Instance is not a CMY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_CMYtoRGB() const { + return CImg(*this,false).CMYtoRGB(); + } + + //! Convert pixel values from CMY to CMYK color spaces. + CImg& CMYtoCMYK() { + return get_CMYtoCMYK().move_to(*this); + } + + //! Convert pixel values from CMY to CMYK color spaces \newinstance. + CImg get_CMYtoCMYK() const { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoCMYK(): Instance is not a CMY image.", + cimg_instance); + + CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT 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; } + pd1[N] = (Tfloat)cimg::cut(C,0,255), + pd2[N] = (Tfloat)cimg::cut(M,0,255), + pd3[N] = (Tfloat)cimg::cut(Y,0,255), + pd4[N] = (Tfloat)cimg::cut(K,0,255); + } + return res; + } + + //! Convert pixel values from CMYK to CMY color spaces. + CImg& CMYKtoCMY() { + return get_CMYKtoCMY().move_to(*this); + } + + //! Convert pixel values from CMYK to CMY color spaces \newinstance. + CImg get_CMYKtoCMY() const { + if (_spectrum!=4) + throw CImgInstanceException(_cimg_instance + "CMYKtoCMY(): Instance is not a CMYK image.", + cimg_instance); + + 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT N = 0; N& RGBtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoXYZ(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).RGBtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to RGB color spaces. + /** + \param use_D65 Tell to use the D65 illuminant (D50 otherwise). + **/ + CImg& XYZtoRGB(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoRGB(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_XYZtoRGB(const bool use_D65=true) const { + return CImg(*this,false).XYZtoRGB(use_D65); + } + + //! Convert pixel values from XYZ to Lab color spaces. + CImg& XYZtoLab(const bool use_D65=true) { +#define _cimg_Labf(x) (24389*(x)>216?cimg::cbrt(x):(24389*(x)/27 + 16)/116) + + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoLab(): Instance is not a XYZ image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N get_XYZtoLab(const bool use_D65=true) const { + return CImg(*this,false).XYZtoLab(use_D65); + } + + //! Convert pixel values from Lab to XYZ color spaces. + CImg& LabtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "LabtoXYZ(): Instance is not a Lab image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N216?cX*cX*cX:(116*cX - 16)*27/24389), + Y = (Tfloat)(27*L>216?cY*cY*cY:27*L/24389), + Z = (Tfloat)(24389*cZ>216?cZ*cZ*cZ:(116*cZ - 16)*27/24389); + p1[N] = (T)(X*white[0]); + p2[N] = (T)(Y*white[1]); + p3[N] = (T)(Z*white[2]); + } + return *this; + } + + //! Convert pixel values from Lab to XYZ color spaces \newinstance. + CImg get_LabtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).LabtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to xyY color spaces. + CImg& XYZtoxyY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoxyY(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?sum:1; + p1[N] = (T)(X/nsum); + p2[N] = (T)(Y/nsum); + p3[N] = (T)Y; + } + return *this; + } + + //! Convert pixel values from XYZ to xyY color spaces \newinstance. + CImg get_XYZtoxyY() const { + return CImg(*this,false).XYZtoxyY(); + } + + //! Convert pixel values from xyY pixels to XYZ color spaces. + CImg& xyYtoXYZ() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "xyYtoXYZ(): Instance is not a xyY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?py:1; + p1[N] = (T)(px*Y/ny); + p2[N] = (T)Y; + p3[N] = (T)((1 - px - py)*Y/ny); + } + return *this; + } + + //! Convert pixel values from xyY pixels to XYZ color spaces \newinstance. + CImg get_xyYtoXYZ() const { + return CImg(*this,false).xyYtoXYZ(); + } + + //! Convert pixel values from RGB to Lab color spaces. + CImg& RGBtoLab(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoLab(use_D65); + } + + //! Convert pixel values from RGB to Lab color spaces \newinstance. + CImg get_RGBtoLab(const bool use_D65=true) const { + return CImg(*this,false).RGBtoLab(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces. + CImg& LabtoRGB(const bool use_D65=true) { + return LabtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces \newinstance. + CImg get_LabtoRGB(const bool use_D65=true) const { + return CImg(*this,false).LabtoRGB(use_D65); + } + + //! Convert pixel values from RGB to xyY color spaces. + CImg& RGBtoxyY(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoxyY(); + } + + //! Convert pixel values from RGB to xyY color spaces \newinstance. + CImg get_RGBtoxyY(const bool use_D65=true) const { + return CImg(*this,false).RGBtoxyY(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces. + CImg& xyYtoRGB(const bool use_D65=true) { + return xyYtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces \newinstance. + CImg get_xyYtoRGB(const bool use_D65=true) const { + return CImg(*this,false).xyYtoRGB(use_D65); + } + + //! Convert pixel values from RGB to CMYK color spaces. + CImg& RGBtoCMYK() { + return RGBtoCMY().CMYtoCMYK(); + } + + //! Convert pixel values from RGB to CMYK color spaces \newinstance. + CImg get_RGBtoCMYK() const { + return CImg(*this,false).RGBtoCMYK(); + } + + //! Convert pixel values from CMYK to RGB color spaces. + CImg& CMYKtoRGB() { + return CMYKtoCMY().CMYtoRGB(); + } + + //! Convert pixel values from CMYK to RGB color spaces \newinstance. + CImg get_CMYKtoRGB() const { + return CImg(*this,false).CMYKtoRGB(); + } + + //@} + //------------------------------------------ + // + //! \name Geometric / Spatial Manipulation + //@{ + //------------------------------------------ + + static float _cimg_lanczos(const float x) { + if (x<=-2 || x>=2) return 0; + const float a = (float)cimg::PI*x, b = 0.5f*a; + return (float)(x?std::sin(a)*std::sin(b)/(a*b):1); + } + + //! Resize image to new dimensions. + /** + \param size_x Number of columns (new size along the X-axis). + \param size_y Number of rows (new size along the Y-axis). + \param size_z Number of slices (new size along the Z-axis). + \param size_c Number of vector-channels (new size along the C-axis). + \param interpolation_type Method of interpolation: + - -1 = no interpolation: raw memory resizing. + - 0 = no interpolation: additional space is filled according to \p boundary_conditions. + - 1 = nearest-neighbor interpolation. + - 2 = moving average interpolation. + - 3 = linear interpolation. + - 4 = grid interpolation. + - 5 = cubic interpolation. + - 6 = lanczos interpolation. + \param boundary_conditions Type of boundary conditions used if necessary. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100). + **/ + CImg& resize(const int size_x, const int size_y=-100, + const int size_z=-100, const int size_c=-100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + if (!size_x || !size_y || !size_z || !size_c) return assign(); + const unsigned int + _sx = (unsigned int)(size_x<0?-size_x*width()/100:size_x), + _sy = (unsigned int)(size_y<0?-size_y*height()/100:size_y), + _sz = (unsigned int)(size_z<0?-size_z*depth()/100:size_z), + _sc = (unsigned int)(size_c<0?-size_c*spectrum()/100:size_c), + sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1; + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return *this; + if (is_empty()) return assign(sx,sy,sz,sc,(T)0); + if (interpolation_type==-1 && sx*sy*sz*sc==size()) { + _width = sx; _height = sy; _depth = sz; _spectrum = sc; + return *this; + } + return get_resize(sx,sy,sz,sc,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c).move_to(*this); + } + + //! Resize image to new dimensions \newinstance. + CImg get_resize(const int size_x, const int size_y = -100, + const int size_z = -100, const int size_c = -100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + if (centering_x<0 || centering_x>1 || centering_y<0 || centering_y>1 || + centering_z<0 || centering_z>1 || centering_c<0 || centering_c>1) + throw CImgArgumentException(_cimg_instance + "resize(): Specified centering arguments (%g,%g,%g,%g) are outside range [0,1].", + cimg_instance, + centering_x,centering_y,centering_z,centering_c); + + if (!size_x || !size_y || !size_z || !size_c) return CImg(); + const unsigned int + sx = std::max(1U,(unsigned int)(size_x>=0?size_x:-size_x*width()/100)), + sy = std::max(1U,(unsigned int)(size_y>=0?size_y:-size_y*height()/100)), + sz = std::max(1U,(unsigned int)(size_z>=0?size_z:-size_z*depth()/100)), + sc = std::max(1U,(unsigned int)(size_c>=0?size_c:-size_c*spectrum()/100)); + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this; + if (is_empty()) return CImg(sx,sy,sz,sc,(T)0); + CImg res; + switch (interpolation_type) { + + // Raw resizing. + // + case -1 : + std::memcpy(res.assign(sx,sy,sz,sc,(T)0)._data,_data,sizeof(T)*std::min(size(),(ulongT)sx*sy*sz*sc)); + break; + + // No interpolation. + // + case 0 : { + const int + xc = (int)(centering_x*((int)sx - width())), + yc = (int)(centering_y*((int)sy - height())), + zc = (int)(centering_z*((int)sz - depth())), + cc = (int)(centering_c*((int)sc - spectrum())); + + switch (boundary_conditions) { + case 3 : { // Mirror + res.assign(sx,sy,sz,sc); + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),65536)) + cimg_forXYZC(res,x,y,z,c) { + const int + mx = cimg::mod(x - xc,w2), my = cimg::mod(y - yc,h2), + mz = cimg::mod(z - zc,d2), mc = cimg::mod(c - cc,s2); + res(x,y,z,c) = (*this)(mx sprite; + if (xc>0) { // X-backward + res.get_crop(xc,yc,zc,cc,xc,yc + height() - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc - 1; x>=0; --x) res.draw_image(x,yc,zc,cc,sprite); + } + if (xc + width()<(int)sx) { // X-forward + res.get_crop(xc + width() - 1,yc,zc,cc,xc + width() - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc + width(); x<(int)sx; ++x) res.draw_image(x,yc,zc,cc,sprite); + } + if (yc>0) { // Y-backward + res.get_crop(0,yc,zc,cc,sx - 1,yc,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc - 1; y>=0; --y) res.draw_image(0,y,zc,cc,sprite); + } + if (yc + height()<(int)sy) { // Y-forward + res.get_crop(0,yc + height() - 1,zc,cc,sx - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc + height(); y<(int)sy; ++y) res.draw_image(0,y,zc,cc,sprite); + } + if (zc>0) { // Z-backward + res.get_crop(0,0,zc,cc,sx - 1,sy - 1,zc,cc + spectrum() - 1).move_to(sprite); + for (int z = zc - 1; z>=0; --z) res.draw_image(0,0,z,cc,sprite); + } + if (zc + depth()<(int)sz) { // Z-forward + res.get_crop(0,0,zc +depth() - 1,cc,sx - 1,sy - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int z = zc + depth(); z<(int)sz; ++z) res.draw_image(0,0,z,cc,sprite); + } + if (cc>0) { // C-backward + res.get_crop(0,0,0,cc,sx - 1,sy - 1,sz - 1,cc).move_to(sprite); + for (int c = cc - 1; c>=0; --c) res.draw_image(0,0,0,c,sprite); + } + if (cc + spectrum()<(int)sc) { // C-forward + res.get_crop(0,0,0,cc + spectrum() - 1,sx - 1,sy - 1,sz - 1,cc + spectrum() - 1).move_to(sprite); + for (int c = cc + spectrum(); c<(int)sc; ++c) res.draw_image(0,0,0,c,sprite); + } + } break; + default : // Dirichlet + res.assign(sx,sy,sz,sc,(T)0).draw_image(xc,yc,zc,cc,*this); + } + break; + } break; + + // Nearest neighbor interpolation. + // + case 1 : { + res.assign(sx,sy,sz,sc); + CImg off_x(sx), off_y(sy + 1), off_z(sz + 1), off_c(sc + 1); + const ulongT + wh = (ulongT)_width*_height, + whd = (ulongT)_width*_height*_depth, + sxy = (ulongT)sx*sy, + sxyz = (ulongT)sx*sy*sz, + 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 = (x + one)*_width/sx; + *(poff_x++) = curr - old; + } + } + if (sy==_height) off_y.fill(_width); + else { + ulongT *poff_y = off_y._data, curr = 0; + cimg_forY(res,y) { + const ulongT old = curr; + curr = (y + one)*_height/sy; + *(poff_y++) = _width*(curr - old); + } + *poff_y = 0; + } + if (sz==_depth) off_z.fill(wh); + else { + ulongT *poff_z = off_z._data, curr = 0; + cimg_forZ(res,z) { + const ulongT old = curr; + curr = (z + one)*_depth/sz; + *(poff_z++) = wh*(curr - old); + } + *poff_z = 0; + } + if (sc==_spectrum) off_c.fill(whd); + else { + ulongT *poff_c = off_c._data, curr = 0; + cimg_forC(res,c) { + const ulongT old = curr; + curr = (c + one)*_spectrum/sc; + *(poff_c++) = whd*(curr - old); + } + *poff_c = 0; + } + + T *ptrd = res._data; + const T* ptrc = _data; + const ulongT *poff_c = off_c._data; + for (unsigned int c = 0; c tmp(sx,_height,_depth,_spectrum,0); + for (unsigned int a = _width*sx, b = _width, c = sx, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d; + if (!b) { + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)/=_width; + ++t; + b = _width; + } + if (!c) { ++s; c = sx; } + } + tmp.move_to(res); + instance_first = false; + } + if (sy!=_height) { + CImg tmp(sx,sy,_depth,_spectrum,0); + for (unsigned int a = _height*sy, b = _height, c = sy, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d; + else + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d; + if (!b) { + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)/=_height; + ++t; + b = _height; + } + if (!c) { ++s; c = sy; } + } + tmp.move_to(res); + instance_first = false; + } + if (sz!=_depth) { + CImg tmp(sx,sy,sz,_spectrum,0); + for (unsigned int a = _depth*sz, b = _depth, c = sz, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d; + else + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d; + if (!b) { + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)/=_depth; + ++t; + b = _depth; + } + if (!c) { ++s; c = sz; } + } + tmp.move_to(res); + instance_first = false; + } + if (sc!=_spectrum) { + CImg tmp(sx,sy,sz,sc,0); + for (unsigned int a = _spectrum*sc, b = _spectrum, c = sc, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d; + else + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d; + if (!b) { + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=_spectrum; + ++t; + b = _spectrum; + } + if (!c) { ++s; c = sc; } + } + tmp.move_to(res); + instance_first = false; + } + } break; + + // Linear interpolation. + // + case 3 : { + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *ptrs = data(0,y,z,c), *const ptrsmax = ptrs + _width - 1; + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + 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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height - 1)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + 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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth - 1)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + 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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum - 1)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrs resx, resy, resz, resc; + if (sx!=_width) { + if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + resx.assign(sx,_height,_depth,_spectrum,(T)0); + const int dx = (int)(2*sx), dy = 2*width(); + int err = (int)(dy + centering_x*(sx*dy/width() - dy)), xs = 0; + cimg_forX(resx,x) if ((err-=dy)<=0) { + cimg_forYZC(resx,y,z,c) resx(x,y,z,c) = (*this)(xs,y,z,c); + ++xs; + err+=dx; + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (sy<_height) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + resy.assign(sx,sy,_depth,_spectrum,(T)0); + const int dx = (int)(2*sy), dy = 2*height(); + int err = (int)(dy + centering_y*(sy*dy/height() - dy)), ys = 0; + cimg_forY(resy,y) if ((err-=dy)<=0) { + cimg_forXZC(resy,x,z,c) resy(x,y,z,c) = resx(x,ys,z,c); + ++ys; + err+=dx; + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (sz<_depth) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + resz.assign(sx,sy,sz,_spectrum,(T)0); + const int dx = (int)(2*sz), dy = 2*depth(); + int err = (int)(dy + centering_z*(sz*dy/depth() - dy)), zs = 0; + cimg_forZ(resz,z) if ((err-=dy)<=0) { + cimg_forXYC(resz,x,y,c) resz(x,y,z,c) = resy(x,y,zs,c); + ++zs; + err+=dx; + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (sc<_spectrum) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + resc.assign(sx,sy,sz,sc,(T)0); + const int dx = (int)(2*sc), dy = 2*spectrum(); + int err = (int)(dy + centering_c*(sc*dy/spectrum() - dy)), cs = 0; + cimg_forC(resc,c) if ((err-=dy)<=0) { + cimg_forXYZ(resc,x,y,z) resc(x,y,z,c) = resz(x,y,z,cs); + ++cs; + err+=dx; + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Cubic interpolation. + // + case 5 : { + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - 1):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + 1):val1, + val3 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sx):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sx):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxy):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxyz):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Lanczos interpolation. + // + case 6 : { + const double vmin = (double)cimg::type::min(), vmax = (double)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1, + *const ptrsmax = ptrs0 + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - 1):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + 1):val2, + val4 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx, + *const ptrsmax = ptrs0 + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sx):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sx):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sx):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy, + *const ptrsmax = ptrs0 + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxy):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxy):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz, + *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxyz):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxyz):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Unknow interpolation. + // + default : + throw CImgArgumentException(_cimg_instance + "resize(): Invalid specified interpolation %d " + "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | " + "5=cubic | 6=lanczos }).", + cimg_instance, + interpolation_type); + } + return res; + } + + //! Resize image to dimensions of another image. + /** + \param src Reference image used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + template + CImg& resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of another image \newinstance. + template + CImg get_resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window. + /** + \param disp Reference display window used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + CImg& resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window \newinstance. + CImg get_resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to half-size along XY axes, using an optimized filter. + CImg& resize_halfXY() { + return get_resize_halfXY().move_to(*this); + } + + //! Resize image to half-size along XY axes, using an optimized filter \newinstance. + CImg get_resize_halfXY() const { + if (is_empty()) return *this; + static const Tfloat kernel[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f, + 0.1231940459f, 0.1935127547f, 0.1231940459f, + 0.07842776544f, 0.1231940459f, 0.07842776544f }; + CImg I(9), res(_width/2,_height/2,_depth,_spectrum); + T *ptrd = res._data; + cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T) + if (x%2 && y%2) *(ptrd++) = (T) + (I[0]*kernel[0] + I[1]*kernel[1] + I[2]*kernel[2] + + I[3]*kernel[3] + I[4]*kernel[4] + I[5]*kernel[5] + + I[6]*kernel[6] + I[7]*kernel[7] + I[8]*kernel[8]); + return res; + } + + //! Resize image to double-size, using the Scale2X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_doubleXY() { + return get_resize_doubleXY().move_to(*this); + } + + //! Resize image to double-size, using the Scale2X algorithm \newinstance. + CImg get_resize_doubleXY() const { +#define _cimg_gs2x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=(res)._width, ptrd2+=(res)._width) + +#define _cimg_gs2x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs2x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(_width<<1,_height<<1,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width; + _cimg_gs2x_for3x3(*this,x,y,z,c,I,T) { + if (Icp!=Icn && Ipc!=Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = Ipc==Icn?Ipc:Icc; + *(ptrd2++) = Icn==Inc?Inc:Icc; + } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; } + } + } + return res; + } + + //! Resize image to triple-size, using the Scale3X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_tripleXY() { + return get_resize_tripleXY().move_to(*this); + } + + //! Resize image to triple-size, using the Scale3X algorithm \newinstance. + CImg get_resize_tripleXY() const { +#define _cimg_gs3x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=2*(res)._width, ptrd2+=2*(res)._width, ptrd3+=2*(res)._width) + +#define _cimg_gs3x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs3x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(3*_width,3*_height,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width, + *ptrd3 = ptrd2 + res._width; + _cimg_gs3x_for3x3(*this,x,y,z,c,I,T) { + if (Icp != Icn && Ipc != Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc; + *(ptrd2++) = Icc; + *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc; + *(ptrd3++) = Ipc==Icn?Ipc:Icc; + *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc; + *(ptrd3++) = Icn==Inc?Inc:Icc; + } else { + *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc; + *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; + *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc; + } + } + } + return res; + } + + //! Mirror image content along specified axis. + /** + \param axis Mirror axis + **/ + CImg& mirror(const char axis) { + if (is_empty()) return *this; + T *pf, *pb, *buf = 0; + switch (cimg::lowercase(axis)) { + case 'x' : { + pf = _data; pb = data(_width - 1); + const unsigned int width2 = _width/2; + for (unsigned int yzv = 0; yzv<_height*_depth*_spectrum; ++yzv) { + for (unsigned int x = 0; x get_mirror(const char axis) const { + return (+*this).mirror(axis); + } + + //! Mirror image content along specified axes. + /** + \param axes Mirror axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& mirror(const char *const axes) { + for (const char *s = axes; *s; ++s) mirror(*s); + return *this; + } + + //! Mirror image content along specified axes \newinstance. + CImg get_mirror(const char *const axes) const { + return (+*this).mirror(axes); + } + + //! Shift image content. + /** + \param delta_x Amount of displacement along the X-axis. + \param delta_y Amount of displacement along the Y-axis. + \param delta_z Amount of displacement along the Z-axis. + \param delta_c Amount of displacement along the C-axis. + \param boundary_conditions Border condition. Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) { + if (is_empty()) return *this; + if (boundary_conditions==3) + return get_crop(-delta_x,-delta_y,-delta_z,-delta_c, + width() - delta_x - 1, + height() - delta_y - 1, + depth() - delta_z - 1, + spectrum() - delta_c - 1,3).move_to(*this); + if (delta_x) // Shift along X-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_x,width()), ndelta_x = (ml<=width()/2)?ml:(ml-width()); + if (!ndelta_x) return *this; + CImg buf(cimg::abs(ndelta_x)); + if (ndelta_x>0) cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(0,y,z,c),ndelta_x*sizeof(T)); + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + std::memcpy(data(_width-ndelta_x,y,z,c),buf,ndelta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(_width + ndelta_x,y,z,c),-ndelta_x*sizeof(T)); + std::memmove(data(-ndelta_x,y,z,c),data(0,y,z,c),(_width + ndelta_x)*sizeof(T)); + std::memcpy(data(0,y,z,c),buf,-ndelta_x*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_x<0) { + const int ndelta_x = (-delta_x>=width())?width() - 1:-delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(_width - 1,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width())?width() - 1:delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(ndelta_x,y,z,c),data(0,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(0,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width()) return fill((T)0); + if (delta_x<0) cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(-delta_x,y,z,c),(_width + delta_x)*sizeof(T)); + std::memset(data(_width + delta_x,y,z,c),0,-delta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memmove(data(delta_x,y,z,c),data(0,y,z,c),(_width-delta_x)*sizeof(T)); + std::memset(data(0,y,z,c),0,delta_x*sizeof(T)); + } + } + + if (delta_y) // Shift along Y-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_y,height()), ndelta_y = (ml<=height()/2)?ml:(ml-height()); + if (!ndelta_y) return *this; + CImg buf(width(),cimg::abs(ndelta_y)); + if (ndelta_y>0) cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,0,z,c),_width*ndelta_y*sizeof(T)); + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + std::memcpy(data(0,_height-ndelta_y,z,c),buf,_width*ndelta_y*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,_height + ndelta_y,z,c),-ndelta_y*_width*sizeof(T)); + std::memmove(data(0,-ndelta_y,z,c),data(0,0,z,c),_width*(_height + ndelta_y)*sizeof(T)); + std::memcpy(data(0,0,z,c),buf,-ndelta_y*_width*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_y<0) { + const int ndelta_y = (-delta_y>=height())?height() - 1:-delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,_height-ndelta_y,z,c), *ptrs = data(0,_height - 1,z,c); + for (int l = 0; l=height())?height() - 1:delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,ndelta_y,z,c),data(0,0,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c); + for (int l = 0; l=height()) return fill((T)0); + if (delta_y<0) cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,-delta_y,z,c),_width*(_height + delta_y)*sizeof(T)); + std::memset(data(0,_height + delta_y,z,c),0,-delta_y*_width*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memmove(data(0,delta_y,z,c),data(0,0,z,c),_width*(_height-delta_y)*sizeof(T)); + std::memset(data(0,0,z,c),0,delta_y*_width*sizeof(T)); + } + } + + if (delta_z) // Shift along Z-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_z,depth()), ndelta_z = (ml<=depth()/2)?ml:(ml-depth()); + if (!ndelta_z) return *this; + CImg buf(width(),height(),cimg::abs(ndelta_z)); + if (ndelta_z>0) cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,0,c),_width*_height*ndelta_z*sizeof(T)); + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,_depth-ndelta_z,c),buf,_width*_height*ndelta_z*sizeof(T)); + } else cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,_depth + ndelta_z,c),-ndelta_z*_width*_height*sizeof(T)); + std::memmove(data(0,0,-ndelta_z,c),data(0,0,0,c),_width*_height*(_depth + ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,0,c),buf,-ndelta_z*_width*_height*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_z<0) { + const int ndelta_z = (-delta_z>=depth())?depth() - 1:-delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,_depth-ndelta_z,c), *ptrs = data(0,0,_depth - 1,c); + for (int l = 0; l=depth())?depth() - 1:delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,ndelta_z,c),data(0,0,0,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c); + for (int l = 0; l=depth()) return fill((T)0); + if (delta_z<0) cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,-delta_z,c),_width*_height*(_depth + delta_z)*sizeof(T)); + std::memset(data(0,0,_depth + delta_z,c),0,_width*_height*(-delta_z)*sizeof(T)); + } else cimg_forC(*this,c) { + std::memmove(data(0,0,delta_z,c),data(0,0,0,c),_width*_height*(_depth-delta_z)*sizeof(T)); + std::memset(data(0,0,0,c),0,delta_z*_width*_height*sizeof(T)); + } + } + + if (delta_c) // Shift along C-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_c,spectrum()), ndelta_c = (ml<=spectrum()/2)?ml:(ml-spectrum()); + if (!ndelta_c) return *this; + CImg buf(width(),height(),depth(),cimg::abs(ndelta_c)); + if (ndelta_c>0) { + std::memcpy(buf,_data,_width*_height*_depth*ndelta_c*sizeof(T)); + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + std::memcpy(data(0,0,0,_spectrum-ndelta_c),buf,_width*_height*_depth*ndelta_c*sizeof(T)); + } else { + std::memcpy(buf,data(0,0,0,_spectrum + ndelta_c),-ndelta_c*_width*_height*_depth*sizeof(T)); + std::memmove(data(0,0,0,-ndelta_c),_data,_width*_height*_depth*(_spectrum + ndelta_c)*sizeof(T)); + std::memcpy(_data,buf,-ndelta_c*_width*_height*_depth*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_c<0) { + const int ndelta_c = (-delta_c>=spectrum())?spectrum() - 1:-delta_c; + if (!ndelta_c) return *this; + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,_spectrum-ndelta_c), *ptrs = data(0,0,0,_spectrum - 1); + for (int l = 0; l=spectrum())?spectrum() - 1:delta_c; + if (!ndelta_c) return *this; + std::memmove(data(0,0,0,ndelta_c),_data,_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,1); + for (int l = 0; l=spectrum()) return fill((T)0); + if (delta_c<0) { + std::memmove(_data,data(0,0,0,-delta_c),_width*_height*_depth*(_spectrum + delta_c)*sizeof(T)); + std::memset(data(0,0,0,_spectrum + delta_c),0,_width*_height*_depth*(-delta_c)*sizeof(T)); + } else { + std::memmove(data(0,0,0,delta_c),_data,_width*_height*_depth*(_spectrum-delta_c)*sizeof(T)); + std::memset(_data,0,delta_c*_width*_height*_depth*sizeof(T)); + } + } + return *this; + } + + //! Shift image content \newinstance. + CImg get_shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) const { + return (+*this).shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions); + } + + //! Permute axes order. + /** + \param order Axes permutations, as a C-string of 4 characters. + This function permutes image content regarding the specified axes permutation. + **/ + CImg& permute_axes(const char *const order) { + return get_permute_axes(order).move_to(*this); + } + + //! Permute axes order \newinstance. + CImg get_permute_axes(const char *const order) const { + const T foo = (T)0; + return _permute_axes(order,foo); + } + + template + CImg _permute_axes(const char *const order, const t&) const { + if (is_empty() || !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; + } + } + if (!res) + throw CImgArgumentException(_cimg_instance + "permute_axes(): Invalid specified permutation '%s'.", + cimg_instance, + order); + return res; + } + + //! Unroll pixel values along specified axis. + /** + \param axis Unroll axis (can be \c 'x', \c 'y', \c 'z' or c 'c'). + **/ + CImg& unroll(const char axis) { + const unsigned int siz = (unsigned int)size(); + if (siz) switch (cimg::lowercase(axis)) { + case 'x' : _width = siz; _height = _depth = _spectrum = 1; break; + case 'y' : _height = siz; _width = _depth = _spectrum = 1; break; + case 'z' : _depth = siz; _width = _height = _spectrum = 1; break; + default : _spectrum = siz; _width = _height = _depth = 1; + } + return *this; + } + + //! Unroll pixel values along specified axis \newinstance. + CImg get_unroll(const char axis) const { + return (+*this).unroll(axis); + } + + //! Rotate image with arbitrary angle. + /** + \param angle Rotation angle, in degrees. + \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle \newinstance. + CImg get_rotate(const float angle, const unsigned int interpolation=1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res; + 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) { + case 1 : { // 90 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(y,hm1 - x,z,c); + } break; + case 2 : { // 180 deg + res.assign(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - x,hm1 - y,z,c); + } break; + case 3 : { // 270 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - y,x,z,c); + } break; + default : // 0 deg + return *this; + } + } else { // Generic angle + const float + rad = (float)(nangle*cimg::PI/180.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad), + ux = cimg::abs((_width - 1)*ca), uy = cimg::abs((_width - 1)*sa), + vx = cimg::abs((_height - 1)*sa), vy = cimg::abs((_height - 1)*ca), + w2 = 0.5f*(_width - 1), h2 = 0.5f*(_height - 1); + res.assign((int)cimg::round(1 + ux + vx),(int)cimg::round(1 + uy + vy),_depth,_spectrum); + const float rw2 = 0.5f*(res._width - 1), rh2 = 0.5f*(res._height - 1); + _rotate(res,nangle,interpolation,boundary_conditions,w2,h2,rw2,rh2); + } + return res; + } + + //! Rotate image with arbitrary angle, around a center point. + /** + \param angle Rotation angle, in degrees. + \param cx X-coordinate of the rotation center. + \param cy Y-coordinate of the rotation center. + \param interpolation Type of interpolation, { 0=nearest | 1=linear | 2=cubic | 3=mirror }. + \param boundary_conditions Boundary conditions, { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) { + return get_rotate(angle,cx,cy,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle, around a center point \newinstance. + CImg get_rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + _rotate(res,angle,interpolation,boundary_conditions,cx,cy,cx,cy); + return res; + } + + // [internal] Perform 2D rotation with arbitrary angle. + void _rotate(CImg& 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.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad); + + switch (boundary_conditions) { + case 3 : { // Mirror + + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const unsigned int interpolation, const unsigned int boundary_conditions) const { + if (is_empty()) return *this; + CImg res; + const float + w1 = _width - 1, h1 = _height - 1, d1 = _depth -1, + w2 = 0.5f*w1, h2 = 0.5f*h1, d2 = 0.5f*d1; + CImg R = CImg::rotation_matrix(u,v,w,angle); + const CImg + X = R*CImg(8,3,1,1, + 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), + zm, zM = X.get_shared_row(2).max_min(zm); + const int + dx = (int)cimg::round(xM - xm), + dy = (int)cimg::round(yM - ym), + dz = (int)cimg::round(zM - zm); + R.transpose(); + res.assign(1 + dx,1 + dy,1 + dz,_spectrum); + const float rw2 = 0.5f*dx, rh2 = 0.5f*dy, rd2 = 0.5f*dz; + _rotate(res,R,interpolation,boundary_conditions,w2,h2,d2,rw2,rh2,rd2); + return res; + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point. + /** + \param u X-coordinate of the 3D rotation axis. + \param v Y-coordinate of the 3D rotation axis. + \param w Z-coordinate of the 3D rotation axis. + \param 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 }. + \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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,cx,cy,cz,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const float cx, const float cy, const float cz, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + CImg R = CImg::rotation_matrix(u,v,w,-angle); + _rotate(res,R,interpolation,boundary_conditions,cx,cy,cz,cx,cy,cz); + return res; + } + + // [internal] Perform 3D rotation with arbitrary axis and angle. + void _rotate(CImg& res, const CImg& R, + const unsigned int interpolation, const unsigned int boundary_conditions, + const float w2, const float h2, const float d2, + const float rw2, const float rh2, const float rd2) const { + switch (boundary_conditions) { + case 3 : // Mirror + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + template + CImg& warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) { + return get_warp(warp,mode,interpolation,boundary_conditions).move_to(*this); + } + + //! Warp image content by a warping field \newinstance + template + CImg get_warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty() || !warp) return *this; + if (mode && !is_sameXYZ(warp)) + throw CImgArgumentException(_cimg_instance + "warp(): Instance and specified relative warping field (%u,%u,%u,%u,%p) " + "have different XYZ dimensions.", + cimg_instance, + warp._width,warp._height,warp._depth,warp._spectrum,warp._data); + + CImg res(warp._width,warp._height,warp._depth,_spectrum); + + if (warp._spectrum==1) { // 1D warping + if (mode>=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + cimg_pragma_openmp(parallel for 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),x + (float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),(float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)), Y = y + (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)), Y = (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++), + z + (float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = x + (int)cimg::round(*(ptrs0++)), + Y = y + (int)cimg::round(*(ptrs1++)), + Z = z + (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = (int)cimg::round(*(ptrs0++)), + Y = (int)cimg::round(*(ptrs1++)), + Z = (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) const { + if (is_empty() || _depth<2) return +*this; + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + const CImg + img_xy = get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1), + img_zy = get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).permute_axes("xzyc"). + resize(_depth,_height,1,-100,-1), + img_xz = get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1); + return CImg(_width + _depth,_height + _depth,1,_spectrum,cimg::min(img_xy.min(),img_zy.min(),img_xz.min())). + draw_image(0,0,img_xy).draw_image(img_xy._width,0,img_zy). + draw_image(0,img_xy._height,img_xz); + } + + //! Construct a 2D representation of a 3D image, with XY,XZ and YZ views \inplace. + CImg& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) { + if (_depth<2) return *this; + return get_projections2d(x0,y0,z0).move_to(*this); + } + + //! Crop image region. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param y0 = Y-coordinate of the upper-left crop rectangle corner. + \param z0 = Z-coordinate of the upper-left crop rectangle corner. + \param c0 = C-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param y1 = Y-coordinate of the lower-right crop rectangle corner. + \param z1 = Z-coordinate of the lower-right crop rectangle corner. + \param c1 = C-coordinate of the lower-right crop rectangle corner. + \param boundary_conditions = Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) { + return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,boundary_conditions).move_to(*this); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "crop(): Empty instance.", + cimg_instance); + const int + nx0 = x0 res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0); + if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum()) + switch (boundary_conditions) { + case 3 : { // Mirror + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for 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), + my = cimg::mod(ny0 + y,h2), + mz = cimg::mod(nz0 + z,d2), + mc = cimg::mod(nc0 + c,s2); + res(x,y,z,c) = (*this)(mx=(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 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 + res.fill((T)0).draw_image(-nx0,-ny0,-nz0,-nc0,*this); + } + else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this); + return res; + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int x1, const unsigned int boundary_conditions=0) { + return crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int x1, const unsigned int boundary_conditions=0) const { + return get_crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Autocrop image region, regarding the specified background value. + CImg& autocrop(const T& value, const char *const axes="czyx") { + if (is_empty()) return *this; + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + const CImg coords = _autocrop(value,axis); + if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels + else switch (axis) { + case 'x' : { + const int x0 = coords[0], x1 = coords[1]; + if (x0>=0 && x1>=0) crop(x0,x1); + } break; + case 'y' : { + const int y0 = coords[0], y1 = coords[1]; + if (y0>=0 && y1>=0) crop(0,y0,_width - 1,y1); + } break; + case 'z' : { + const int z0 = coords[0], z1 = coords[1]; + if (z0>=0 && z1>=0) crop(0,0,z0,_width - 1,_height - 1,z1); + } break; + default : { + const int c0 = coords[0], c1 = coords[1]; + if (c0>=0 && c1>=0) crop(0,0,0,c0,_width - 1,_height - 1,_depth - 1,c1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background value \newinstance. + CImg get_autocrop(const T& value, const char *const axes="czyx") const { + return (+*this).autocrop(value,axes); + } + + //! Autocrop image region, regarding the specified background color. + /** + \param color Color used for the crop. If \c 0, color is guessed. + \param axes Axes used for the crop. + **/ + CImg& autocrop(const T *const color=0, const char *const axes="zyx") { + if (is_empty()) return *this; + 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); + if (_width==w && _height==h && _depth==d && _spectrum==s) { + const CImg col2 = get_vector_at(w - 1,h - 1,d - 1); + autocrop(col2,axes); + } + return *this; + } + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + switch (axis) { + case 'x' : { + int x0 = width(), x1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'x'); + const int nx0 = coords[0], nx1 = coords[1]; + if (nx0>=0 && nx1>=0) { x0 = std::min(x0,nx0); x1 = std::max(x1,nx1); } + } + if (x0==width() && x1==-1) return assign(); else crop(x0,x1); + } break; + case 'y' : { + int y0 = height(), y1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'y'); + const int ny0 = coords[0], ny1 = coords[1]; + if (ny0>=0 && ny1>=0) { y0 = std::min(y0,ny0); y1 = std::max(y1,ny1); } + } + if (y0==height() && y1==-1) return assign(); else crop(0,y0,_width - 1,y1); + } break; + default : { + int z0 = depth(), z1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'z'); + const int nz0 = coords[0], nz1 = coords[1]; + if (nz0>=0 && nz1>=0) { z0 = std::min(z0,nz0); z1 = std::max(z1,nz1); } + } + if (z0==depth() && z1==-1) return assign(); else crop(0,0,z0,_width - 1,_height - 1,z1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background color \newinstance. + CImg get_autocrop(const T *const color=0, const char *const axes="zyx") const { + return (+*this).autocrop(color,axes); + } + + //! Autocrop image region, regarding the specified background color \overloading. + template 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)) { + case 'x' : { + int x0 = -1, x1 = -1; + cimg_forX(*this,x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); } + if (x0>=0) { + for (int x = width() - 1; x>=0; --x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); } + } + res = CImg::vector(x0,x1); + } break; + case 'y' : { + int y0 = -1, y1 = -1; + cimg_forY(*this,y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); } + if (y0>=0) { + for (int y = height() - 1; y>=0; --y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); } + } + res = CImg::vector(y0,y1); + } break; + case 'z' : { + int z0 = -1, z1 = -1; + cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); } + if (z0>=0) { + for (int z = depth() - 1; z>=0; --z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); } + } + res = CImg::vector(z0,z1); + } break; + default : { + int c0 = -1, c1 = -1; + cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); } + if (c0>=0) { + for (int c = spectrum() - 1; c>=0; --c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; } + } + res = CImg::vector(c0,c1); + } + } + return res; + } + + //! Return specified image column. + /** + \param x0 Image column. + **/ + CImg get_column(const int x0) const { + return get_columns(x0,x0); + } + + //! Return specified image column \inplace. + CImg& column(const int x0) { + return columns(x0,x0); + } + + //! Return specified range of image columns. + /** + \param x0 Starting image column. + \param x1 Ending image column. + **/ + CImg& columns(const int x0, const int x1) { + return get_columns(x0,x1).move_to(*this); + } + + //! Return specified range of image columns \inplace. + CImg get_columns(const int x0, const int x1) const { + return get_crop(x0,0,0,0,x1,height() - 1,depth() - 1,spectrum() - 1); + } + + //! Return specified image row. + CImg get_row(const int y0) const { + return get_rows(y0,y0); + } + + //! Return specified image row \inplace. + /** + \param y0 Image row. + **/ + CImg& row(const int y0) { + return rows(y0,y0); + } + + //! Return specified range of image rows. + /** + \param y0 Starting image row. + \param y1 Ending image row. + **/ + CImg get_rows(const int y0, const int y1) const { + return get_crop(0,y0,0,0,width() - 1,y1,depth() - 1,spectrum() - 1); + } + + //! Return specified range of image rows \inplace. + CImg& rows(const int y0, const int y1) { + return get_rows(y0,y1).move_to(*this); + } + + //! Return specified image slice. + /** + \param z0 Image slice. + **/ + CImg get_slice(const int z0) const { + return get_slices(z0,z0); + } + + //! Return specified image slice \inplace. + CImg& slice(const int z0) { + return slices(z0,z0); + } + + //! Return specified range of image slices. + /** + \param z0 Starting image slice. + \param z1 Ending image slice. + **/ + CImg get_slices(const int z0, const int z1) const { + return get_crop(0,0,z0,0,width() - 1,height() - 1,z1,spectrum() - 1); + } + + //! Return specified range of image slices \inplace. + CImg& slices(const int z0, const int z1) { + return get_slices(z0,z1).move_to(*this); + } + + //! Return specified image channel. + /** + \param c0 Image channel. + **/ + CImg get_channel(const int c0) const { + return get_channels(c0,c0); + } + + //! Return specified image channel \inplace. + CImg& channel(const int c0) { + return channels(c0,c0); + } + + //! Return specified range of image channels. + /** + \param c0 Starting image channel. + \param c1 Ending image channel. + **/ + CImg get_channels(const int c0, const int c1) const { + return get_crop(0,0,0,c0,width() - 1,height() - 1,depth() - 1,c1); + } + + //! Return specified range of image channels \inplace. + CImg& channels(const int c0, const int c1) { + return get_channels(c0,c1).move_to(*this); + } + + //! Return stream line of a 2D or 3D vector field. + CImg get_streamline(const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false) const { + if (_spectrum!=2 && _spectrum!=3) + throw CImgInstanceException(_cimg_instance + "streamline(): Instance is not a 2D or 3D vector field.", + cimg_instance); + if (_spectrum==2) { + if (is_oriented_only) { + typename CImg::_functor4d_streamline2d_oriented func(*this); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true, + 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.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.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.f,_height - 1.f,_depth - 1.f); + } + + //! Return stream line of a 3D vector field. + /** + \param func Vector field function. + \param x X-coordinate of the starting point of the streamline. + \param y Y-coordinate of the starting point of the streamline. + \param z Z-coordinate of the starting point of the streamline. + \param L Streamline length. + \param dl Streamline length increment. + \param interpolation_type Type of interpolation. + Can be { 0=nearest int | 1=linear | 2=2nd-order RK | 3=4th-order RK. }. + \param is_backward_tracking Tells if the streamline is estimated forward or backward. + \param is_oriented_only Tells if the direction of the vectors must be ignored. + \param x0 X-coordinate of the first bounding-box vertex. + \param y0 Y-coordinate of the first bounding-box vertex. + \param z0 Z-coordinate of the first bounding-box vertex. + \param x1 X-coordinate of the second bounding-box vertex. + \param y1 Y-coordinate of the second bounding-box vertex. + \param z1 Z-coordinate of the second bounding-box vertex. + **/ + template + static CImg streamline(const tfunc& func, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + if (dl<=0) + throw CImgArgumentException("CImg<%s>::streamline(): Invalid specified integration length %g " + "(should be >0).", + pixel_type(), + dl); + + const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1); + if (L<=0 || (is_bounded && (xx1 || yy1 || zz1))) return CImg(); + const unsigned int size_L = (unsigned int)cimg::round(L/dl + 1); + CImg coordinates(size_L,3); + const float dl2 = dl/2; + float + *ptr_x = coordinates.data(0,0), + *ptr_y = coordinates.data(0,1), + *ptr_z = coordinates.data(0,2), + pu = (float)(dl*func(x,y,z,0)), + pv = (float)(dl*func(x,y,z,1)), + pw = (float)(dl*func(x,y,z,2)), + X = x, Y = y, Z = z; + + switch (interpolation_type) { + case 0 : { // Nearest integer interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + const int + xi = (int)(X>0?X + 0.5f:X - 0.5f), + yi = (int)(Y>0?Y + 0.5f:Y - 0.5f), + zi = (int)(Z>0?Z + 0.5f:Z - 0.5f); + float + u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)), + v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)), + w = (float)(dl*func((float)xi,(float)yi,(float)zi,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 1 : { // First-order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u = (float)(dl*func(X,Y,Z,0)), + v = (float)(dl*func(X,Y,Z,1)), + w = (float)(dl*func(X,Y,Z,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 2 : { // Second order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u = (float)(dl*func(X + u0,Y + v0,Z + w0,0)), + v = (float)(dl*func(X + u0,Y + v0,Z + w0,1)), + w = (float)(dl*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + default : { // Fourth order interpolation + cimg_forX(coordinates,x) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,0)), + v1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,1)), + w1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; } + float + u2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,0)), + v2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,1)), + w2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; } + float + u3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,0)), + v3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,1)), + w3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; } + const float + u = (u0 + u3)/3 + (u1 + u2)/1.5f, + v = (v0 + v3)/3 + (v1 + v2)/1.5f, + w = (w0 + w3)/3 + (w1 + w2)/1.5f; + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } + } + if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0); + return coordinates; + } + + //! Return stream line of a 3D vector field \overloading. + static CImg streamline(const char *const expression, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=true, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + _functor4d_streamline_expr func(expression); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1); + } + + struct _functor4d_streamline2d_directed { + const CImg& ref; + _functor4d_streamline2d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0; + } + }; + + struct _functor4d_streamline3d_directed { + const CImg& ref; + _functor4d_streamline3d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref._linear_atXYZ(x,y,z,c); + } + }; + + struct _functor4d_streamline2d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline2d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,1,2); } + ~_functor4d_streamline2d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign2d(i,j) \ + if (I(i,j,0)*I(0,0,0) + I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z; + const float + dx = x - xi, + dy = y - yi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1); + I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1); + I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1); + I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1); + _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1); + } + return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0; + } + }; + + struct _functor4d_streamline3d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline3d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,2,3); } + ~_functor4d_streamline3d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0) + I(i,j,k,1)*I(0,0,0,1) + I(i,j,k,2)*I(0,0,0,2)<0) { \ + I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z - (z>=0?0:1), nzi = zi + 1; + const float + dx = x - xi, + dy = y - yi, + dz = z - zi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + if (zi<0) zi = 0; + if (nzi<0) nzi = 0; + if (zi>=ref.depth()) zi = ref.depth() - 1; + if (nzi>=ref.depth()) nzi = ref.depth() - 1; + I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1); + I(0,0,0,2) = (float)ref(xi,yi,zi,2); I(1,0,0,0) = (float)ref(nxi,yi,zi,0); + I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2); + I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1); + I(1,1,0,2) = (float)ref(nxi,nyi,zi,2); I(0,1,0,0) = (float)ref(xi,nyi,zi,0); + I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,nyi,zi,2); + I(0,0,1,0) = (float)ref(xi,yi,nzi,0); I(0,0,1,1) = (float)ref(xi,yi,nzi,1); + I(0,0,1,2) = (float)ref(xi,yi,nzi,2); I(1,0,1,0) = (float)ref(nxi,yi,nzi,0); + I(1,0,1,1) = (float)ref(nxi,yi,nzi,1); I(1,0,1,2) = (float)ref(nxi,yi,nzi,2); + I(1,1,1,0) = (float)ref(nxi,nyi,nzi,0); I(1,1,1,1) = (float)ref(nxi,nyi,nzi,1); + I(1,1,1,2) = (float)ref(nxi,nyi,nzi,2); I(0,1,1,0) = (float)ref(xi,nyi,nzi,0); + I(0,1,1,1) = (float)ref(xi,nyi,nzi,1); I(0,1,1,2) = (float)ref(xi,nyi,nzi,2); + _cimg_vecalign3d(1,0,0); _cimg_vecalign3d(1,1,0); _cimg_vecalign3d(0,1,0); + _cimg_vecalign3d(0,0,1); _cimg_vecalign3d(1,0,1); _cimg_vecalign3d(1,1,1); _cimg_vecalign3d(0,1,1); + } + return (float)pI->_linear_atXYZ(dx,dy,dz,c); + } + }; + + struct _functor4d_streamline_expr { + _cimg_math_parser *mp; + ~_functor4d_streamline_expr() { mp->end(); delete mp; } + _functor4d_streamline_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,"streamline",CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)(*mp)(x,y,z,c); + } + }; + + //! Return a shared-memory image referencing a range of pixels of the image instance. + /** + \param x0 X-coordinate of the starting pixel. + \param x1 X-coordinate of the ending pixel. + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_points(const unsigned int x0, const unsigned int x1, + const unsigned int y0=0, const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(x0,y0,z0,c0), + end = (unsigned int)offset(x1,y0,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_points(): Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).", + cimg_instance, + x0,x1,y0,z0,c0); + + return CImg(_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); + 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 rows of the image instance. + /** + \param y0 Y-coordinate of the starting row. + \param y1 Y-coordinate of the ending row. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_rows(const unsigned int y0, const unsigned int y1, + const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,y0,z0,c0), + end = (unsigned int)offset(0,y1,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_rows(): Invalid request of a shared-memory subset " + "(0->%u,%u->%u,%u,%u).", + cimg_instance, + _width - 1,y0,y1,z0,c0); + + return CImg(_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); + 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 one row of the image instance. + /** + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared-memory image referencing one row of the image instance \const. + const CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared memory image referencing a range of slices of the image instance. + /** + \param z0 Z-coordinate of the starting slice. + \param z1 Z-coordinate of the ending slice. + \param c0 C-coordinate. + **/ + CImg get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,0,z0,c0), + end = (unsigned int)offset(0,0,z1,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_slices(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,%u->%u,%u).", + cimg_instance, + _width - 1,_height - 1,z0,z1,c0); + + return CImg(_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); + 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 one slice of the image instance. + /** + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing one slice of the image instance \const. + const CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) const { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing a range of channels of the image instance. + /** + \param c0 C-coordinate of the starting channel. + \param c1 C-coordinate of the ending channel. + **/ + CImg get_shared_channels(const unsigned int c0, const unsigned int c1) { + const unsigned int + beg = (unsigned int)offset(0,0,0,c0), + end = (unsigned int)offset(0,0,0,c1); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_channels(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,0->%u,%u->%u).", + cimg_instance, + _width - 1,_height - 1,_depth - 1,c0,c1); + + return CImg(_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); + 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 one channel of the image instance. + /** + \param c0 C-coordinate. + **/ + CImg get_shared_channel(const unsigned int c0) { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory image referencing one channel of the image instance \const. + const CImg get_shared_channel(const unsigned int c0) const { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory version of the image instance. + CImg get_shared() { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Return a shared-memory version of the image instance \const. + const CImg get_shared() const { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Split image into a list along specified axis. + /** + \param axis Splitting axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param nb Number of splitted parts. + \note + - If \c nb==0, instance image is splitted into blocs of egal values along the specified axis. + - If \c nb<=0, instance image is splitted into blocs of -\c nb pixel wide. + - If \c nb>0, instance image is splitted into \c nb blocs. + **/ + CImgList 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 + 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>=(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); + } break; + case 'y': { + if (_height>dp) { + res.assign(_height/dp + (_height%dp?1:0),1,1); + const unsigned int pe = _height - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'z': { + if (_depth>dp) { + res.assign(_depth/dp + (_depth%dp?1:0),1,1); + const unsigned int pe = _depth - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'c' : { + if (_spectrum>dp) { + res.assign(_spectrum/dp + (_spectrum%dp?1:0),1,1); + const unsigned int pe = _spectrum - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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 + const unsigned int siz = _axis=='x'?_width:_axis=='y'?_height:_axis=='z'?_depth:_axis=='c'?_spectrum:0; + if ((unsigned int)nb>siz) + throw CImgArgumentException(_cimg_instance + "get_split(): Instance cannot be split along %c-axis into %u blocs.", + cimg_instance, + axis,nb); + if (nb==1) res.assign(*this); + else { + int err = (int)siz; + unsigned int _p = 0; + switch (_axis) { + case 'x' : { + cimg_forX(*this,p) if ((err-=nb)<=0) { + get_crop(_p,0,0,0,p,_height - 1,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'y' : { + cimg_forY(*this,p) if ((err-=nb)<=0) { + get_crop(0,_p,0,0,_width - 1,p,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'z' : { + cimg_forZ(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,_p,0,_width - 1,_height - 1,p,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'c' : { + cimg_forC(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,0,_p,_width - 1,_height - 1,_depth - 1,p).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } + } + } + } else { // Split by egal values according to specified axis + T current = *_data; + switch (_axis) { + case 'x' : { + int i0 = 0; + cimg_forX(*this,i) + if ((*this)(i)!=current) { get_columns(i0,i - 1).move_to(res); i0 = i; current = (*this)(i); } + get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + int i0 = 0; + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { get_rows(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,i); } + get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + int i0 = 0; + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { get_slices(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,i); } + get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + int i0 = 0; + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { get_channels(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,0,i); } + get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + longT i0 = 0; + cimg_foroff(*this,i) + if ((*this)[i]!=current) { + CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); + i0 = (longT)i; current = (*this)[i]; + } + CImg(_data + i0,1,(unsigned int)(size() - i0)).move_to(res); + } + } + } + return res; + } + + //! Split image into a list of sub-images, according to a specified splitting value sequence and optionally axis. + /** + \param values Splitting value sequence. + \param axis Axis along which the splitting is performed. Can be '0' to ignore axis. + \param keep_values Tells if the splitting sequence must be kept in the splitted blocs. + **/ + template + CImgList get_split(const CImg& values, const char axis=0, const bool keep_values=true) const { + 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 + const T value = (T)*values; + switch (_axis) { + case 'x' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_width && (*this)(i)==value) ++i; + if (i>i0) { if (keep_values) get_columns(i0,i - 1).move_to(res); i0 = i; } + while (i<_width && (*this)(i)!=value) ++i; + if (i>i0) { get_columns(i0,i - 1).move_to(res); i0 = i; } + } while (i<_width); + } break; + case 'y' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_height && (*this)(0,i)==value) ++i; + if (i>i0) { if (keep_values) get_rows(i0,i - 1).move_to(res); i0 = i; } + while (i<_height && (*this)(0,i)!=value) ++i; + if (i>i0) { get_rows(i0,i - 1).move_to(res); i0 = i; } + } while (i<_height); + } break; + case 'z' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_depth && (*this)(0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_slices(i0,i - 1).move_to(res); i0 = i; } + while (i<_depth && (*this)(0,0,i)!=value) ++i; + if (i>i0) { get_slices(i0,i - 1).move_to(res); i0 = i; } + } while (i<_depth); + } break; + case 'c' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_spectrum && (*this)(0,0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_channels(i0,i - 1).move_to(res); i0 = i; } + while (i<_spectrum && (*this)(0,0,0,i)!=value) ++i; + if (i>i0) { get_channels(i0,i - 1).move_to(res); i0 = i; } + } while (i<_spectrum); + } break; + default : { + const ulongT siz = size(); + ulongT i0 = 0, i = 0; + do { + while (ii0) { 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; + if (i>i1) { + if (i1>i0) get_columns(i0,i1 - 1).move_to(res); + if (keep_values) get_columns(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_width); + if (i0<_width) get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,i)==*values) { + i1 = i; j = 0; + while (i<_height && (*this)(0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_rows(i0,i1 - 1).move_to(res); + if (keep_values) get_rows(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_height); + if (i0<_height) get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,i)==*values) { + i1 = i; j = 0; + while (i<_depth && (*this)(0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_slices(i0,i1 - 1).move_to(res); + if (keep_values) get_slices(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_depth); + if (i0<_depth) get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,0,i)==*values) { + i1 = i; j = 0; + while (i<_spectrum && (*this)(0,0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_channels(i0,i1 - 1).move_to(res); + if (keep_values) get_channels(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_spectrum); + if (i0<_spectrum) get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + ulongT i0 = 0, i1 = 0, i = 0; + const ulongT siz = size(); + do { + if ((*this)[i]==*values) { + i1 = i; j = 0; + while (i=vsiz) j = 0; } + i-=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); + i0 = i; + } else ++i; + } else ++i; + } while (i(_data + i0,1,(unsigned int)(siz - i0)).move_to(res); + } break; + } + } + return res; + } + + //! Append two images along specified axis. + /** + \param img Image to append with instance image. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Append alignment in \c [0,1]. + **/ + template + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,true).insert(img).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \specialization. + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,img,true).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \const. + template + CImg<_cimg_Tt> get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList<_cimg_Tt>(*this,true).insert(img).get_append(axis,align); + } + + //! Append two images along specified axis \specialization. + CImg get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList(*this,img,true).get_append(axis,align); + } + + //@} + //--------------------------------------- + // + //! \name Filtering / Transforms + //@{ + //--------------------------------------- + + //! Correlate image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The correlation of the image instance \p *this by the kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} (*this)(x + i,y + j,z + k)*kernel(i,j,k). + **/ + template + CImg& correlate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_correlate(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + template + 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); + } + + //! 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; + 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>=(cimg_openmp_sizefactor)*32768, + is_outer_parallel = res_size>=(cimg_openmp_sizefactor)*32768; + _cimg_abort_init_omp; + cimg_abort_init; + + if (kernel._width==kernel._height && + ((kernel._depth==1 && kernel._width<=6) || (kernel._depth==kernel._width && kernel._width<=3))) { + + // 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); + + } 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(); + + 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; + } + } + } + } + + 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 cimg_openmp_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); + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0, N = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const Ttfloat _val = (Ttfloat)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); + val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm); + N+=_val*_val; + } + N*=M; + res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0); + } + } _cimg_abort_catch_omp2 + } else { // Classical correlation + cimg_pragma_openmp(parallel for cimg_openmp_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; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img._atXYZ(x + xm,y + ym,z + zm)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img.atXYZ(x + xm,y + ym,z + zm,0,(T)0)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + } + cimg_abort_test; + return res; + } + + //! Convolve image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The result \p res of the convolution of an image \p img by a kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*kernel(i,j,k) + **/ + template + CImg& convolve(const CImg& kernel, const bool boundary_conditions=true, const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_convolve(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + //! Convolve image by a kernel \newinstance. + template + 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); + } + + //! Cumulate image values, optionally along specified axis. + /** + \param axis Cumulation axis. Set it to 0 to cumulate all values globally without taking axes into account. + **/ + CImg& cumulate(const char axis=0) { + switch (cimg::lowercase(axis)) { + case 'x' : + 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; + cimg_forX(*this,x) { cumul+=(Tlong)*ptrd; *(ptrd++) = (T)cumul; } + } + break; + case 'y' : { + const ulongT w = (ulongT)_width; + 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; + cimg_forY(*this,y) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=w; } + } + } break; + case 'z' : { + const ulongT wh = (ulongT)_width*_height; + cimg_pragma_openmp(parallel for 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; + cimg_forZ(*this,z) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=wh; } + } + } break; + case 'c' : { + const ulongT whd = (ulongT)_width*_height*_depth; + cimg_pragma_openmp(parallel for 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 + Tlong cumul = (Tlong)0; + cimg_for(*this,ptrd,T) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; } + } + } + return *this; + } + + //! Cumulate image values, optionally along specified axis \newinstance. + CImg get_cumulate(const char axis=0) const { + return CImg(*this,false).cumulate(axis); + } + + //! Cumulate image values, along specified axes. + /** + \param axes Cumulation axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& cumulate(const char *const axes) { + for (const char *s = axes; *s; ++s) cumulate(*s); + return *this; + } + + //! Cumulate image values, along specified axes \newinstance. + CImg get_cumulate(const char *const axes) const { + return CImg(*this,false).cumulate(axes); + } + + //! Erode image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the erosion in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_erode(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Erode image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel) return *this; + if (!is_real && kernel==0) return CImg(width(),height(),depth(),spectrum(),0); + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2, + mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real erosion + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx1 + xm,my1 + ym,mz1 + zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) - mval); + if (cval=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); + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx1 + xm,my1 + ym,mz1 + zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval=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)) { + 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 + 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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val get_erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).erode(sx,sy,sz); + } + + //! Erode the image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& erode(const unsigned int s) { + return erode(s,s,s); + } + + //! Erode the image by a square structuring element of specified size \newinstance. + CImg get_erode(const unsigned int s) const { + return (+*this).erode(s); + } + + //! Dilate image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the dilation in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_dilate(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Dilate image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel || (!is_real && kernel==0)) return *this; + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx1 = kernel.width()/2, my1 = kernel.height()/2, mz1 = kernel.depth()/2, + mx2 = kernel.width() - mx1 - 1, my2 = kernel.height() - my1 - 1, mz2 = kernel.depth() - mz1 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx2 - xm,my2 - ym,mz2 - zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forYZ(*this,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,0,(T)0) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } else { // Binary dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx2 - xm,my2 - ym,mz2 - zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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,0,(T)0); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + cimg_abort_test; + return res; + } + + //! Dilate image by a rectangular structuring element of specified size. + /** + \param sx Width of the structuring element. + \param sy Height of the structuring element. + \param sz Depth of the structuring element. + **/ + CImg& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { + if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this; + if (sx>1 && _width>1) { // Along X-axis + const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(0,y,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sy>1 && _height>1) { // Along Y-axis + const int L = height(), off = width(), s = (int)sy, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,0,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sz>1 && _depth>1) { // Along Z-axis + const int L = depth(), off = width()*height(), s = (int)sz, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,y,0,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + return *this; + } + + //! Dilate image by a rectangular structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).dilate(sx,sy,sz); + } + + //! Dilate image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& dilate(const unsigned int s) { + return dilate(s,s,s); + } + + //! Dilate image by a square structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int s) const { + return (+*this).dilate(s); + } + + //! Compute watershed transform. + /** + \param priority Priority map. + \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity + in 2D case, and between 6(false)- or 26(true)-connectivity in 3D case. + \note Non-zero values of the instance instance are propagated to zero-valued ones according to + specified the priority map. + **/ + template + CImg& watershed(const CImg& priority, const bool is_high_connectivity=false) { +#define _cimg_watershed_init(cond,X,Y,Z) \ + if (cond && !(*this)(X,Y,Z)) Q._priority_queue_insert(labels,sizeQ,priority(X,Y,Z),X,Y,Z,nb_seeds) + +#define _cimg_watershed_propagate(cond,X,Y,Z) \ + if (cond) { \ + if ((*this)(X,Y,Z)) { \ + ns = labels(X,Y,Z) - 1; xs = seeds(ns,0); ys = seeds(ns,1); zs = seeds(ns,2); \ + d = cimg::sqr((float)x - xs) + cimg::sqr((float)y - ys) + cimg::sqr((float)z - zs); \ + if (d labels(_width,_height,_depth,1,0), seeds(64,3); + CImg::type> Q; + unsigned int sizeQ = 0; + int px, nx, py, ny, pz, nz; + bool is_px, is_nx, is_py, is_ny, is_pz, is_nz; + const bool is_3d = _depth>1; + + // Find seed points and insert them in priority queue. + unsigned int nb_seeds = 0; + const T *ptrs = _data; + cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) { // 3D version + if (nb_seeds>=seeds._width) seeds.resize(2*seeds._width,3,1,1,0); + seeds(nb_seeds,0) = x; seeds(nb_seeds,1) = y; seeds(nb_seeds++,2) = z; + px = x - 1; nx = x + 1; + py = y - 1; ny = y + 1; + pz = z - 1; nz = z + 1; + is_px = px>=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz::inf(); + T label = (T)0; + _cimg_watershed_propagate(is_px,px,y,z); + _cimg_watershed_propagate(is_nx,nx,y,z); + _cimg_watershed_propagate(is_py,x,py,z); + _cimg_watershed_propagate(is_ny,x,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_pz,x,y,pz); + _cimg_watershed_propagate(is_nz,x,y,nz); + } + if (is_high_connectivity) { + _cimg_watershed_propagate(is_px && is_py,px,py,z); + _cimg_watershed_propagate(is_nx && is_py,nx,py,z); + _cimg_watershed_propagate(is_px && is_ny,px,ny,z); + _cimg_watershed_propagate(is_nx && is_ny,nx,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_px && is_pz,px,y,pz); + _cimg_watershed_propagate(is_nx && is_pz,nx,y,pz); + _cimg_watershed_propagate(is_px && is_nz,px,y,nz); + _cimg_watershed_propagate(is_nx && is_nz,nx,y,nz); + _cimg_watershed_propagate(is_py && is_pz,x,py,pz); + _cimg_watershed_propagate(is_ny && is_pz,x,ny,pz); + _cimg_watershed_propagate(is_py && is_nz,x,py,nz); + _cimg_watershed_propagate(is_ny && is_nz,x,ny,nz); + _cimg_watershed_propagate(is_px && is_py && is_pz,px,py,pz); + _cimg_watershed_propagate(is_nx && is_py && is_pz,nx,py,pz); + _cimg_watershed_propagate(is_px && is_ny && is_pz,px,ny,pz); + _cimg_watershed_propagate(is_nx && is_ny && is_pz,nx,ny,pz); + _cimg_watershed_propagate(is_px && is_py && is_nz,px,py,nz); + _cimg_watershed_propagate(is_nx && is_py && is_nz,nx,py,nz); + _cimg_watershed_propagate(is_px && is_ny && is_nz,px,ny,nz); + _cimg_watershed_propagate(is_nx && is_ny && is_nz,nx,ny,nz); + } + } + (*this)(x,y,z) = label; + labels(x,y,z) = ++nmin; + } + return *this; + } + + //! Compute watershed transform \newinstance. + template + CImg get_watershed(const CImg& priority, const bool is_high_connectivity=false) const { + return (+*this).watershed(priority,is_high_connectivity); + } + + // [internal] Insert/Remove items in priority queue, for watershed/distance transforms. + template + bool _priority_queue_insert(CImg& is_queued, unsigned int& siz, const tv value, + const unsigned int x, const unsigned int y, const unsigned int z, + const unsigned int n=1) { + if (is_queued(x,y,z)) return false; + is_queued(x,y,z) = (tq)n; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; + (*this)(siz - 1,1) = (T)x; + (*this)(siz - 1,2) = (T)y; + (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); + cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); + cimg::swap((*this)(pos,3),(*this)(par,3)); + } + return true; + } + + CImg& _priority_queue_remove(unsigned int& siz) { + (*this)(0,0) = (*this)(--siz,0); + (*this)(0,1) = (*this)(siz,1); + (*this)(0,2) = (*this)(siz,2); + (*this)(0,3) = (*this)(siz,3); + const float value = (*this)(0,0); + for (unsigned int pos = 0, left = 0, right = 0; + ((right=2*(pos + 1),(left=right - 1))(*this)(right,0)) { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } else { + cimg::swap((*this)(pos,0),(*this)(right,0)); + cimg::swap((*this)(pos,1),(*this)(right,1)); + cimg::swap((*this)(pos,2),(*this)(right,2)); + cimg::swap((*this)(pos,3),(*this)(right,3)); + pos = right; + } + } else { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } + } + return *this; + } + + //! Apply recursive Deriche filter. + /** + \param sigma Standard deviation of the filter. + \param order Order of the filter. Can be { 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 }. + **/ + CImg& deriche(const float sigma, const unsigned int order=0, const char axis='x', + const bool boundary_conditions=true) { +#define _cimg_deriche_apply \ + CImg Y(N); \ + Tfloat *ptrY = Y._data, yb = 0, yp = 0; \ + T xp = (T)0; \ + if (boundary_conditions) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \ + for (int m = 0; m=0; --n) { \ + const T xc = *(ptrX-=off); \ + const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \ + xa = xn; xn = xc; ya = yn; yn = yc; \ + *ptrX = (T)(*(--ptrY)+yc); \ + } + const char naxis = cimg::lowercase(axis); + const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + if (is_empty() || (nsigma<0.1f && !order)) return *this; + const float + nnsigma = nsigma<0.1f?0.1f:nsigma, + alpha = 1.695f/nnsigma, + ema = (float)std::exp(-alpha), + ema2 = (float)std::exp(-2*alpha), + b1 = -2*ema, + b2 = ema2; + float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0; + switch (order) { + case 0 : { + const float k = (1-ema)*(1-ema)/(1 + 2*alpha*ema-ema2); + a0 = k; + a1 = k*(alpha - 1)*ema; + a2 = k*(alpha + 1)*ema; + a3 = -k*ema2; + } break; + case 1 : { + const float k = -(1-ema)*(1-ema)*(1-ema)/(2*(ema + 1)*ema); + a0 = a3 = 0; + a1 = k*ema; + a2 = -a1; + } break; + case 2 : { + const float + ea = (float)std::exp(-alpha), + k = -(ema2 - 1)/(2*alpha*ema), + kn = (-2*(-1 + 3*ea - 3*ea*ea + ea*ea*ea)/(3*ea + 1 + 3*ea*ea + ea*ea*ea)); + a0 = kn; + a1 = -kn*(1 + k*alpha)*ema; + a2 = kn*(1 - k*alpha)*ema; + a3 = -kn*ema2; + } break; + default : + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified filter order %u " + "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).", + cimg_instance, + order); + } + coefp = (a0 + a1)/(1 + b1 + b2); + coefn = (a2 + a3)/(1 + b1 + b2); + switch (naxis) { + case 'x' : { + const int N = width(); + const ulongT off = 1U; + cimg_pragma_openmp(parallel for 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 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 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 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; } + } + } + return *this; + } + + //! 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 { + return CImg(*this,false).deriche(sigma,order,axis,boundary_conditions); + } + + // [internal] Apply a recursive filter (used by CImg::vanvliet()). + /* + \param ptr the pointer of the data + \param filter the coefficient of the filter in the following order [n,n - 1,n - 2,n - 3]. + \param N size of the data + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive, 2nd derivative, 3rd derivative + \param boundary_conditions Boundary conditions. Can be { 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 double + 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. - 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.) * (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. - 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; + } + } 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; + } + } 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; + } + } break; + } + } + + //! Van Vliet recursive Gaussian filter. + /** + \param sigma standard deviation of the Gaussian filter + \param order the order of the filter 0,1,2,3 + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \note dirichlet boundary condition has a strange behavior + + I.T. Young, L.J. van Vliet, M. van Ginkel, Recursive Gabor filtering. + IEEE Trans. Sig. Proc., vol. 50, pp. 2799-2805, 2002. + + (this is an improvement over Young-Van Vliet, Sig. Proc. 44, 1995) + + Boundary conditions (only for order 0) using Triggs matrix, from + B. Triggs and M. Sdika. Boundary conditions for Young-van Vliet + recursive filtering. IEEE Trans. Signal Processing, + vol. 54, pp. 2365-2367, 2006. + **/ + CImg& vanvliet(const float sigma, const unsigned int order, const char axis='x', + const bool boundary_conditions=true) { + if (is_empty()) return *this; + 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; + const double + nnsigma = nsigma<0.5f?0.5f:nsigma, + m0 = 1.16680, m1 = 1.10783, m2 = 1.40586, + m1sq = m1 * m1, m2sq = m2 * m2, + q = (nnsigma<3.556?-0.2568 + 0.5784*nnsigma + 0.0561*nnsigma*nnsigma:2.5091 + 0.9804*(nnsigma - 3.556)), + qsq = q * q, + scale = (m0 + q) * (m1sq + m2sq + 2 * m1 * q + qsq), + b1 = -q * (2 * m0 * m1 + m1sq + m2sq + (2 * m0 + 4 * m1) * q + 3 * qsq) / scale, + b2 = qsq * (m0 + 2 * m1 + 3 * q) / scale, + b3 = -qsq * q / scale, + B = ( m0 * (m1sq + m2sq) ) / scale; + double filter[4]; + filter[0] = B; filter[1] = -b1; filter[2] = -b2; filter[3] = -b3; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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; + } + + //! 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 { + return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions); + } + + //! Blur image. + /** + \param sigma_x Standard deviation of the blur, along the X-axis. + \param sigma_y Standard deviation of the blur, along the Y-axis. + \param sigma_z Standard deviation of the blur, along the Z-axis. + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \param is_gaussian Tells if the blur uses a gaussian (\c true) or quasi-gaussian (\c false) kernel. + \note + - The blur is computed as a 0-order Deriche filter. This is not a gaussian blur. + - This is a recursive algorithm, not depending on the values of the standard deviations. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) { + if (is_empty()) return *this; + if (is_gaussian) { + if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions); + if (_height>1) vanvliet(sigma_y,0,'y',boundary_conditions); + if (_depth>1) vanvliet(sigma_z,0,'z',boundary_conditions); + } else { + if (_width>1) deriche(sigma_x,0,'x',boundary_conditions); + if (_height>1) deriche(sigma_y,0,'y',boundary_conditions); + if (_depth>1) deriche(sigma_z,0,'z',boundary_conditions); + } + return *this; + } + + //! Blur image \newinstance. + CImg get_blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*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. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) { + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur(nsigma,nsigma,nsigma,boundary_conditions,is_gaussian); + } + + //! Blur image isotropically \newinstance. + CImg get_blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*this,false).blur(sigma,boundary_conditions,is_gaussian); + } + + //! Blur image anisotropically, directed by a field of diffusion tensors. + /** + \param G Field of square roots of diffusion tensors/vectors used to drive the smoothing. + \param amplitude Amplitude of the smoothing. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + template + CImg& blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=1) { + + // Check arguments and init variables + if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6)) + throw CImgArgumentException(_cimg_instance + "blur_anisotropic(): Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).", + cimg_instance, + G._width,G._height,G._depth,G._spectrum,G._data); + if (is_empty() || dl<0) return *this; + const float namplitude = amplitude>=0?amplitude:-amplitude*cimg::max(_width,_height,_depth)/100; + 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; + + 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 + cimg_forZC(*this,z,c) { + cimg_abort_test; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + veloc = (Tfloat)(G(x,y,0,0)*ixx + 2*G(x,y,0,1)*ixy + G(x,y,0,2)*iyy); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + } + if (veloc_max>0) *this+=(velocity*=dl/veloc_max); + } + } else { // LIC-based smoothing + const ulongT whd = (ulongT)_width*_height*_depth; + const float sqrt2amplitude = (float)std::sqrt(2*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.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.f:datmp; + for (float theta = 0; theta<360; (theta+=da2),++N) { + const float + thetar = (float)(theta*cimg::PI/180), + vx = (float)(std::cos(thetar)*std::cos(phir)), + vy = (float)(std::sin(thetar)*std::cos(phir)), + vz = (float)std::sin(phir); + const t + *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2), + *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(0,0,0,3); + cimg_forXYZ(G,xg,yg,zg) { + const t a = *(pa++), b = *(pb++), c = *(pc++), d = *(pd++), e = *(pe++), f = *(pf++); + const float + u = (float)(a*vx + b*vy + c*vz), + v = (float)(b*vx + d*vy + e*vz), + w = (float)(c*vx + e*vy + f*vz), + n = 1e-5f + cimg::hypot(u,v,w), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)(w*dln); + *(pd3++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for 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_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,z,3), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y, + Z = (float)z; + switch (interpolation_type) { + case 0 : { // Nearest neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f), + cz = (int)(Z + 0.5f); + const float + u = (float)W(cx,cy,cz,0), + v = (float)W(cx,cy,cz,1), + w = (float)W(cx,cy,cz,2); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,cz,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,cz,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u = (float)(W._linear_atXYZ(X,Y,Z,0)), + v = (float)(W._linear_atXYZ(X,Y,Z,1)), + w = (float)(W._linear_atXYZ(X,Y,Z,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + default : { // 2nd order Runge Kutta + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)), + v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)), + w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2)), + u = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,0)), + v = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,1)), + w = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + } + Tfloat *ptrd = res.data(x,y,z); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,z,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + } 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); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2); + cimg_forXY(G,xg,yg) { + const t a = *(pa++), b = *(pb++), c = *(pc++); + const float + u = (float)(a*vx + b*vy), + v = (float)(b*vx + c*vy), + n = std::max(1e-5f,cimg::hypot(u,v)), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && _height>=2) + firstprivate(val)) + cimg_forY(*this,y) _cimg_abort_try_omp2 { + cimg_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,0,2), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y; + switch (interpolation_type) { + case 0 : { // Nearest-neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f); + const float + u = (float)W(cx,cy,0,0), + v = (float)W(cx,cy,0,1); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u = (float)(W._linear_atXY(X,Y,0,0)), + v = (float)(W._linear_atXY(X,Y,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + default : { // 2nd-order Runge-kutta interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)), + v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)), + u = (float)(W._linear_atXY(X + u0,Y + v0,0,0)), + v = (float)(W._linear_atXY(X + u0,Y + v0,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } + } + Tfloat *ptrd = res.data(x,y); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,0,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + const Tfloat *ptrs = res._data; + cimg_for(*this,ptrd,T) { + const Tfloat val = *(ptrs++)/N; + *ptrd = valval_max?val_max:(T)val); + } + } + cimg_abort_test; + return *this; + } + + //! Blur image anisotropically, directed by a field of diffusion tensors \newinstance. + template + CImg get_blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way. + /** + \param amplitude Amplitude of the smoothing. + \param sharpness Sharpness. + \param anisotropy Anisotropy. + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + CImg& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) { + const float nalpha = alpha>=0?alpha:-alpha*cimg::max(_width,_height,_depth)/100; + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur_anisotropic(get_diffusion_tensors(sharpness,anisotropy,nalpha,nsigma,interpolation_type!=3), + amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way \newinstance. + CImg get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, + const float da=30, const float gauss_prec=2, + const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec, + interpolation_type,is_fast_approx); + } + + //! Blur image, with the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_x Amount of blur along the X-axis. + \param sigma_y Amount of blur along the Y-axis. + \param sigma_z Amount of blur along the Z-axis. + \param sigma_r Amount of blur along the value axis. + \param sampling_x Amount of downsampling along the X-axis used for the approximation. + Defaults (0) to sigma_x. + \param sampling_y Amount of downsampling along the Y-axis used for the approximation. + Defaults (0) to sigma_y. + \param sampling_z Amount of downsampling along the Z-axis used for the approximation. + Defaults (0) to sigma_z. + \param sampling_r Amount of downsampling along the value axis used for the approximation. + Defaults (0) to sigma_r. + \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006 + (extended for 3D volumetric images). + It is based on the reference implementation http://people.csail.mit.edu/jiawen/software/bilateralFilter.m + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_bilateral(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || (!sigma_x && !sigma_y && !sigma_z)) return *this; + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return blur(sigma_x,sigma_y,sigma_z); + const float + edge_delta = (float)(edge_max - edge_min), + _sigma_x = sigma_x>=0?sigma_x:-sigma_x*_width/100, + _sigma_y = sigma_y>=0?sigma_y:-sigma_y*_height/100, + _sigma_z = sigma_z>=0?sigma_z:-sigma_z*_depth/100, + _sigma_r = sigma_r>=0?sigma_r:-sigma_r*(edge_max - edge_min)/100, + _sampling_x = sampling_x?sampling_x:std::max(_sigma_x,1.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, + derived_sigma_z = _sigma_z / _sampling_z, + derived_sigma_r = _sigma_r / _sampling_r; + const int + padding_x = (int)(2*derived_sigma_x) + 1, + padding_y = (int)(2*derived_sigma_y) + 1, + padding_z = (int)(2*derived_sigma_z) + 1, + padding_r = (int)(2*derived_sigma_r) + 1; + const unsigned int + bx = (unsigned int)((_width - 1)/_sampling_x + 1 + 2*padding_x), + by = (unsigned int)((_height - 1)/_sampling_y + 1 + 2*padding_y), + bz = (unsigned int)((_depth - 1)/_sampling_z + 1 + 2*padding_z), + br = (unsigned int)(edge_delta/_sampling_r + 1 + 2*padding_r); + if (bx>0 || by>0 || bz>0 || br>0) { + const bool is_3d = (_depth>1); + if (is_3d) { // 3D version of the algorithm + CImg bgrid(bx,by,bz,br), bgridw(bx,by,bz,br); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); bgridw.fill(0); + cimg_forXYZ(*this,x,y,z) { + const T val = (*this)(x,y,z,c); + const float edge = (float)_guide(x,y,z); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + Z = (int)cimg::round(z/_sampling_z) + padding_z, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,Z,R)+=(float)val; + bgridw(X,Y,Z,R)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + bgridw.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + + cimg_pragma_openmp(parallel for 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 + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + Z = z/_sampling_z + padding_z, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZC(X,Y,Z,R), bval1 = bgridw._linear_atXYZC(X,Y,Z,R); + (*this)(x,y,z,c) = (T)(bval0/bval1); + } + } + } else { // 2D version of the algorithm + CImg bgrid(bx,by,br,2); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); + cimg_forXY(*this,x,y) { + const T val = (*this)(x,y,c); + const float edge = (float)_guide(x,y); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,R,0)+=(float)val; + bgrid(X,Y,R,1)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,0,true).blur(0,0,derived_sigma_r,false); + + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(size(),4096)) + cimg_forXY(*this,x,y) { + const float edge = (float)_guide(x,y); + const float + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1); + (*this)(x,y,c) = (T)(bval0/bval1); + } + } + } + } + return *this; + } + + //! Blur image, with the joint bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) const { + return CImg(*this,false).blur_bilateral(guide,sigma_x,sigma_y,sigma_z,sigma_r, + sampling_x,sampling_y,sampling_z,sampling_r); + } + + //! Blur image using the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_r Amount of blur along the value axis. + \param sampling_s Amount of downsampling along the XYZ-axes used for the approximation. Defaults to sigma_s. + \param sampling_r Amount of downsampling along the value axis used for the approximation. Defaults to sigma_r. + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) { + const float _sigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100; + return blur_bilateral(guide,_sigma_s,_sigma_s,_sigma_s,sigma_r,sampling_s,sampling_s,sampling_s,sampling_r); + } + + //! Blur image using the bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) const { + return CImg(*this,false).blur_bilateral(guide,sigma_s,sigma_r,sampling_s,sampling_r); + } + + // [internal] Apply a box filter (used by CImg::boxfilter() and CImg::blur_box()). + /* + \param ptr the pointer of the data + \param N size of the data + \param boxsize Size of the box filter (can be subpixel). + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive and 2nd derivative. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + */ + static void _cimg_blur_box_apply(T *ptr, const float boxsize, const int N, const ulongT off, + const int order, const bool boundary_conditions, + const unsigned int nb_iter) { + // Smooth. + if (boxsize>1 && nb_iter) { + const int w2 = (int)(boxsize - 1)/2; + const unsigned int winsize = 2*w2 + 1U; + const double frac = (boxsize - winsize)/2.; + CImg win(winsize); + for (unsigned int iter = 0; iter=N) return boundary_conditions?ptr[(N - 1)*off]:T(); + return ptr[x*off]; + } + + // Apply box filter of order 0,1,2. + /** + \param boxsize Size of the box window (can be subpixel) + \param order the order of the filter 0,1 or 2. + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \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 nb_iter=1) { + if (is_empty() || !boxsize || (boxsize<=1 && !order)) return *this; + const char naxis = cimg::lowercase(axis); + const float nboxsize = boxsize>=0?boxsize:-boxsize* + (naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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); + } + } + return *this; + } + + // 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 nb_iter=1) const { + return CImg(*this,false).boxfilter(boxsize,order,axis,boundary_conditions,nb_iter); + } + + //! Blur image with a box filter. + /** + \param boxsize_x Size of the box window, along the X-axis (can be subpixel). + \param boxsize_y Size of the box window, along the Y-axis (can be subpixel). + \param boxsize_z Size of the box window, along the Z-axis (can be subpixel). + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \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 nb_iter=1) { + if (is_empty()) return *this; + if (_width>1) boxfilter(boxsize_x,0,'x',boundary_conditions,nb_iter); + if (_height>1) boxfilter(boxsize_y,0,'y',boundary_conditions,nb_iter); + if (_depth>1) boxfilter(boxsize_z,0,'z',boundary_conditions,nb_iter); + return *this; + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, + const bool boundary_conditions=true) 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 + \see deriche(), vanvliet(). + **/ + CImg& blur_box(const float boxsize, const bool boundary_conditions=true) { + const float nboxsize = boxsize>=0?boxsize:-boxsize*cimg::max(_width,_height,_depth)/100; + return blur_box(nboxsize,nboxsize,nboxsize,boundary_conditions); + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize, const bool boundary_conditions=true) const { + return CImg(*this,false).blur_box(boxsize,boundary_conditions); + } + + //! Blur image, with the image guided filter. + /** + \param guide Image used to guide the smoothing process. + \param radius Spatial radius. If negative, it is expressed as a percentage of the largest image size. + \param regularization Regularization parameter. + If negative, it is expressed as a percentage of the guide value range. + \note This method implements the filtering algorithm described in: + He, Kaiming; Sun, Jian; Tang, Xiaoou, "Guided Image Filtering," Pattern Analysis and Machine Intelligence, + IEEE Transactions on , vol.35, no.6, pp.1397,1409, June 2013 + **/ + template + CImg& blur_guided(const CImg& guide, const float radius, const float regularization) { + return get_blur_guided(guide,radius,regularization).move_to(*this); + } + + //! Blur image, with the image guided filter \newinstance. + template + CImg get_blur_guided(const CImg& guide, const float radius, const float regularization) const { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_guided(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || !radius) return *this; + const int _radius = radius>=0?(int)radius:(int)(-radius*cimg::max(_width,_height,_depth)/100); + float _regularization = regularization; + if (regularization<0) { + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return *this; + _regularization = -regularization*(edge_max - edge_min)/100; + } + _regularization = std::max(_regularization,0.01f); + const unsigned int psize = (unsigned int)(1 + 2*_radius); + CImg + mean_p = get_blur_box(psize,true), + mean_I = guide.get_blur_box(psize,true).resize(mean_p), + cov_Ip = get_mul(guide).blur_box(psize,true)-=mean_p.get_mul(mean_I), + var_I = guide.get_sqr().blur_box(psize,true)-=mean_I.get_sqr(), + &a = cov_Ip.div(var_I+=_regularization), + &b = mean_p-=a.get_mul(mean_I); + a.blur_box(psize,true); + b.blur_box(psize,true); + return a.mul(guide)+=b; + } + + //! Blur image using patch-based space. + /** + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_p Amount of blur along the value axis. + \param patch_size Size of the 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, + const unsigned int lookup_size=4, const float smoothness=0, const bool is_fast_approx=true) { + if (is_empty() || !patch_size || !lookup_size) return *this; + return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,is_fast_approx).move_to(*this); + } + + //! Blur image using patch-based space \newinstance. + CImg get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, + const unsigned int lookup_size=4, const float smoothness=0, + const bool is_fast_approx=true) const { + +#define _cimg_blur_patch3d_fast(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) \ + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch3d(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \ + alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch2d_fast(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) \ + if (cimg::abs((Tfloat)img(x,y,0,0) - (Tfloat)img(p,q,0,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + +#define _cimg_blur_patch2d(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, \ + alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + + if (is_empty() || !patch_size || !lookup_size) return +*this; + CImg 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 float + nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100, + sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p, + Pnorm = P.size()*sigma_p2; + const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1; + const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size; + cimg::unused(N2,N3); + if (_depth>1) switch (patch_size) { // 3D + case 2 : if (is_fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch3d_fast(3) else _cimg_blur_patch3d(3) break; + default : { + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) + private(P,Q)) + cimg_forXYZ(res,x,y,z) { // Fast + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + if (res._width>=32 && res._height*res._depth>=4) firstprivate(P,Q)) + cimg_forXYZ(res,x,y,z) { // Exact + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { + (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, dz = (float)z - r, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } + } + } else switch (patch_size) { // 2D + case 2 : if (is_fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break; + case 4 : if (is_fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break; + case 5 : if (is_fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break; + case 6 : if (is_fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break; + case 7 : if (is_fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break; + case 8 : if (is_fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break; + case 9 : if (is_fast_approx) _cimg_blur_patch2d_fast(9) else _cimg_blur_patch2d(9) break; + default : { // Fast + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Fast + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) + if ((Tfloat)cimg::abs(img(x,y,0) - (Tfloat)img(p,q,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Exact + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { + (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,0,c) = (Tfloat)((*this)(x,y,c)); + } + } + } + return res; + } + + //! Blur image with the median filter. + /** + \param n Size of the median filter. + \param threshold Threshold used to discard pixels too far from the current pixel value in the median computation. + **/ + CImg& blur_median(const unsigned int n, const float threshold=0) { + if (!n) return *this; + return get_blur_median(n,threshold).move_to(*this); + } + + //! Blur image with the median filter \newinstance. + CImg get_blur_median(const unsigned int n, const float threshold=0) const { + if (is_empty() || n<=1) return +*this; + CImg res(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg::unused(ptrd); + const int hr = (int)n/2, hl = n - hr - 1; + if (res._depth!=1) { // 3D + if (threshold>0) + cimg_pragma_openmp(parallel for 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + const Tfloat val0 = (Tfloat)(*this)(x,y,z,c); + CImg values(n*n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXYZ(*this,nx0,ny0,nz0,nx1,ny1,nz1,p,q,r) + if (cimg::abs((*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 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + res(x,y,z,c) = get_crop(nx0,ny0,nz0,c,nx1,ny1,nz1,c).median(); + } + } else { + if (threshold>0) + 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + const Tfloat val0 = (Tfloat)(*this)(x,y,c); + CImg values(n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXY(*this,nx0,ny0,nx1,ny1,p,q) + if (cimg::abs((*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 { + const int + w1 = width() - 1, h1 = height() - 1, + w2 = width() - 2, h2 = height() - 2, + w3 = width() - 3, h3 = height() - 3, + w4 = width() - 4, h4 = height() - 4; + switch (n) { // Without threshold + case 3 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(9); + cimg_for_in3x3(*this,1,1,w2,h2,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6],I[7],I[8]); + cimg_for_borderXY(*this,x,y,1) + res(x,y,c) = get_crop(std::max(0,x - 1),std::max(0,y - 1),0,c, + std::min(w1,x + 1),std::min(h1,y + 1),0,c).median(); + } + } break; + case 5 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(25); + cimg_for_in5x5(*this,2,2,w3,h3,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4], + I[5],I[6],I[7],I[8],I[9], + I[10],I[11],I[12],I[13],I[14], + I[15],I[16],I[17],I[18],I[19], + I[20],I[21],I[22],I[23],I[24]); + cimg_for_borderXY(*this,x,y,2) + res(x,y,c) = get_crop(std::max(0,x - 2),std::max(0,y - 2),0,c, + std::min(w1,x + 2),std::min(h1,y + 2),0,c).median(); + } + } break; + case 7 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(49); + cimg_for_in7x7(*this,3,3,w4,h4,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6], + I[7],I[8],I[9],I[10],I[11],I[12],I[13], + I[14],I[15],I[16],I[17],I[18],I[19],I[20], + I[21],I[22],I[23],I[24],I[25],I[26],I[27], + I[28],I[29],I[30],I[31],I[32],I[33],I[34], + I[35],I[36],I[37],I[38],I[39],I[40],I[41], + I[42],I[43],I[44],I[45],I[46],I[47],I[48]); + cimg_for_borderXY(*this,x,y,3) + res(x,y,c) = get_crop(std::max(0,x - 3),std::max(0,y - 3),0,c, + std::min(w1,x + 3),std::min(h1,y + 3),0,c).median(); + } + } break; + default : { + 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, + nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + res(x,y,c) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median(); + } + } + } + } + } + return res; + } + + //! Sharpen image. + /** + \param amplitude Sharpening amplitude + \param sharpen_type Select sharpening method. Can be { false=inverse diffusion | true=shock filters }. + \param edge Edge threshold (shock filters only). + \param alpha Gradient smoothness (shock filters only). + \param sigma Tensor smoothness (shock filters only). + **/ + CImg& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) { + if (is_empty()) return *this; + T val_min, val_max = max_min(val_min); + const float nedge = edge/2; + CImg velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum); + + 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 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); + CImg val, vec; + cimg_forX(G,x) { + G.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + if (val[2]<0) val[2] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = vec(0,2); + *(ptrG3++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1] + val[2],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + u = G(x,y,z,0), + v = G(x,y,z,1), + w = G(x,y,z,2), + amp = G(x,y,z,3), + ixx = Incc + Ipcc - 2*Iccc, + ixy = (Innc + Ippc - Inpc - Ipnc)/4, + ixz = (Incn + Ipcp - Incp - Ipcn)/4, + iyy = Icnc + Icpc - 2*Iccc, + iyz = (Icnn + Icpp - Icnp - Icpn)/4, + izz = Iccn + Iccp - 2*Iccc, + ixf = Incc - Iccc, + ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, + iyb = Iccc - Icpc, + izf = Iccn - Iccc, + izb = Iccc - Iccp, + itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else { // 2D + if (sharpen_type) { // Shock filters + CImg 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>=(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); + cimg_forX(G,x) { + G.get_tensor_at(x,y).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat + u = G(x,y,0), + v = G(x,y,1), + amp = G(x,y,2), + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + ixf = Inc - Icc, + ixb = Icc - Ipc, + iyf = Icn - Icc, + iyb = Icc - Icp, + itt = u*u*ixx + v*v*iyy + 2*u*v*ixy, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } + const Tfloat veloc_max = _veloc_max.max(); + if (veloc_max<=0) return *this; + return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this); + } + + //! Sharpen image \newinstance. + CImg get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) const { + return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma); + } + + //! Return image gradient. + /** + \param axes Axes considered for the gradient computation, as a C-string (e.g "xy"). + \param scheme = Numerical scheme used for the gradient computation: + - -1 = Backward finite differences + - 0 = Centered finite differences + - 1 = Forward finite differences + - 2 = Using Sobel kernels + - 3 = Using rotation invariant kernels + - 4 = Using Deriche recusrsive filter. + - 5 = Using Van Vliet recusrsive filter. + **/ + CImgList 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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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.f))), b = (Tfloat)(0.5f*(std::sqrt(2.f) - 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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 res; + for (unsigned int l = 0; axes[l]; ++l) { + const char axis = cimg::lowercase(axes[l]); + switch (axis) { + case 'x' : res.insert(grad[0]); break; + case 'y' : res.insert(grad[1]); break; + case 'z' : res.insert(grad[2]); break; + } + } + grad.assign(); + return res; + } + + //! Return image hessian. + /** + \param axes Axes considered for the hessian computation, as a C-string (e.g "xy"). + **/ + CImgList 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) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + + res.assign(lmax/2,_width,_height,_depth,_spectrum); + if (!cimg::strcasecmp(naxes,def_axes3d)) { // 3D + + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + const ulongT off = (ulongT)c*_width*_height*_depth; + Tfloat + *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off, + *ptrd3 = res[3]._data + off, *ptrd4 = res[4]._data + off, *ptrd5 = res[5]._data + off; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipcc + Incc - 2*Iccc; // Ixx + *(ptrd1++) = (Ippc + Innc - Ipnc - Inpc)/4; // Ixy + *(ptrd2++) = (Ipcp + Incn - Ipcn - Incp)/4; // Ixz + *(ptrd3++) = Icpc + Icnc - 2*Iccc; // Iyy + *(ptrd4++) = (Icpp + Icnn - Icpn - Icnp)/4; // Iyz + *(ptrd5++) = Iccn + Iccp - 2*Iccc; // Izz + } + } + } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // 2D + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; + Tfloat *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipc + Inc - 2*Icc; // Ixx + *(ptrd1++) = (Ipp + Inn - Ipn - Inp)/4; // Ixy + *(ptrd2++) = Icp + Icn - 2*Icc; // Iyy + } + } + } else for (unsigned int l = 0; laxis2) cimg::swap(axis1,axis2); + bool valid_axis = false; + if (axis1=='x' && axis2=='x') { // Ixx + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Ipc + Inc - 2*Icc; + } + } + else if (axis1=='x' && axis2=='y') { // Ixy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipp + Inn - Ipn - Inp)/4; + } + } + else if (axis1=='x' && axis2=='z') { // Ixz + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipcp + Incn - Ipcn - Incp)/4; + } + } + else if (axis1=='y' && axis2=='y') { // Iyy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Iccn + Iccp - 2*Iccc; + } + } + else if (!valid_axis) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + } + return res; + } + + //! Compute image Laplacian. + CImg& laplacian() { + return get_laplacian().move_to(*this); + } + + //! 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>=(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>=(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>=(cimg_openmp_sizefactor)*1048576 && + _height*_depth*_spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res.data(0,0,0,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc - 2*Icc; + } + } + return res; + } + + //! Compute the structure tensor field of an image. + /** + \param is_fwbw_scheme scheme. Can be { false=centered | true=forward-backward } + **/ + CImg& structure_tensors(const bool is_fwbw_scheme=false) { + return get_structure_tensors(is_fwbw_scheme).move_to(*this); + } + + //! Compute the structure tensor field of an image \newinstance. + CImg get_structure_tensors(const bool is_fwbw_scheme=false) const { + if (is_empty()) return *this; + CImg res; + if (_depth>1) { // 3D + res.assign(_width,_height,_depth,6,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ix = (Incc - Ipcc)/2, + iy = (Icnc - Icpc)/2, + iz = (Iccn - Iccp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=ix*iz; + *(ptrd3++)+=iy*iy; + *(ptrd4++)+=iy*iz; + *(ptrd5++)+=iz*iz; + } + } + } else { // Forward/backward finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixf = Incc - Iccc, ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, iyb = Iccc - Icpc, + izf = Iccn - Iccc, izb = Iccc - Iccp; + *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; + *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4; + *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4; + *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2; + *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4; + *(ptrd5++)+=(izf*izf + izb*izb)/2; + } + } + } + } else { // 2D + res.assign(_width,_height,_depth,3,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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 + ix = (Inc - Ipc)/2, + iy = (Icn - Icp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=iy*iy; + } + } + } else { // Forward/backward finite differences (version 2) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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; + } + } + } + } + return res; + } + + //! Compute field of diffusion tensors for edge-preserving smoothing. + /** + \param sharpness Sharpness + \param anisotropy Anisotropy + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param is_sqrt Tells if the square root of the tensor field is computed instead. + **/ + CImg& diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) { + CImg res; + const float + nsharpness = std::max(sharpness,1e-5f), + power1 = (is_sqrt?0.5f:1)*nsharpness, + power2 = power1/(1e-7f + 1 - anisotropy); + blur(alpha).normalize(0,(T)255); + + if (_depth>1) { // 3D + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for 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), + *ptrd3 = res.data(0,y,z,3), *ptrd4 = res.data(0,y,z,4), *ptrd5 = res.data(0,y,z,5); + CImg val(3), vec(3,3); + cimg_forX(*this,x) { + res.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + const float + _l1 = val[2], _l2 = val[1], _l3 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, l3 = _l3>0?_l3:0, + ux = vec(0,0), uy = vec(0,1), uz = vec(0,2), + vx = vec(1,0), vy = vec(1,1), vz = vec(1,2), + wx = vec(2,0), wy = vec(2,1), wz = vec(2,2), + n1 = (float)std::pow(1 + l1 + l2 + l3,-power1), + n2 = (float)std::pow(1 + l1 + l2 + l3,-power2); + *(ptrd0++) = n1*(ux*ux + vx*vx) + n2*wx*wx; + *(ptrd1++) = n1*(ux*uy + vx*vy) + n2*wx*wy; + *(ptrd2++) = n1*(ux*uz + vx*vz) + n2*wx*wz; + *(ptrd3++) = n1*(uy*uy + vy*vy) + n2*wy*wy; + *(ptrd4++) = n1*(uy*uz + vy*vz) + n2*wy*wz; + *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz; + } + } + } else { // for 2D images + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(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); + cimg_forX(*this,x) { + res.get_tensor_at(x,y).symmetric_eigen(val,vec); + const float + _l1 = val[1], _l2 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, + ux = vec(1,0), uy = vec(1,1), + vx = vec(0,0), vy = vec(0,1), + n1 = (float)std::pow(1 + l1 + l2,-power1), + n2 = (float)std::pow(1 + l1 + l2,-power2); + *(ptrd0++) = n1*ux*ux + n2*vx*vx; + *(ptrd1++) = n1*ux*uy + n2*vx*vy; + *(ptrd2++) = n1*uy*uy + n2*vy*vy; + } + } + } + return res.move_to(*this); + } + + //! Compute field of diffusion tensors for edge-preserving smoothing \newinstance. + CImg get_diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) const { + return CImg(*this,false).diffusion_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt); + } + + //! Estimate displacement field between two images. + /** + \param source Reference image. + \param smoothness Smoothness of estimated displacement field. + \param precision Precision required for algorithm convergence. + \param nb_scales Number of scales used to estimate the displacement field. + \param iteration_max Maximum number of iterations allowed for one scale. + \param is_backward If false, match I2(X + U(X)) = I1(X), else match I2(X) = I1(X - U(X)). + \param guide Image used as the initial correspondence estimate for the algorithm. + 'guide' may have a last channel with boolean values (0=false | other=true) that + tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask). + **/ + CImg& 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()) { + return get_displacement(source,smoothness,precision,nb_scales,iteration_max,is_backward,guide). + move_to(*this); + } + + //! Estimate displacement field between two images \newinstance. + CImg get_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()) const { + if (is_empty() || !source) return +*this; + if (!is_sameXYZC(source)) + throw CImgArgumentException(_cimg_instance + "displacement(): Instance and source image (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + source._width,source._height,source._depth,source._spectrum,source._data); + if (precision<0) + throw CImgArgumentException(_cimg_instance + "displacement(): Invalid specified precision %g " + "(should be >=0)", + cimg_instance, + precision); + + const bool is_3d = source._depth>1; + const unsigned int constraint = is_3d?3:2; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum0?nb_scales: + (unsigned int)cimg::round(std::log(mins/8.)/std::log(1.5),1,1); + + 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); + + CImg U, V; + floatT bound = 0; + for (int scale = (int)_nb_scales - 1; scale>=0; --scale) { + const float factor = (float)std::pow(1.5,(double)scale); + const unsigned int + _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1, + _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1, + _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1; + if (sw<5 && sh<5 && (!is_3d || sd<5)) continue; // Skip too small scales + const CImg + I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta, + I2 = (get_resize(I1,2)-=tm)/=tdelta; + if (guide._spectrum>constraint) guide.get_resize(I2._width,I2._height,I2._depth,-100,1).move_to(V); + if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3); + else { + if (guide) + guide.get_shared_channels(0,is_3d?2:1).get_resize(I2._width,I2._height,I2._depth,-100,2).move_to(U); + else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0); + } + + float dt = 2, energy = cimg::type::max(); + const CImgList dI = is_backward?I1.get_gradient():I2.get_gradient(); + cimg_abort_init; + + for (unsigned int iteration = 0; iteration=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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for 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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } + } + } else { // 2D version + if (smoothness>=0) // Isotropic regularization + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } + } + } + const float d_energy = (_energy - energy)/(sw*sh*sd); + if (d_energy<=0 && -d_energy<_precision) break; + if (d_energy>0) dt*=0.5f; + energy = _energy; + } + } + return U; + } + + //! Compute correspondence map between two images, using 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. + \param patch_height Height of the patch used for matching. + \param patch_depth Depth of the patch used for matching. + \param nb_iterations Number of patch-match iterations. + \param nb_randoms Number of randomization attempts (per pixel). + \param occ_penalization Penalization factor in score related patch occurrences. + \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. + **/ + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide,matching_score).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \newinstance. + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization, + guide,true,matching_score); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,guide,occ_penalization,false,CImg::empty()); + } + + template + 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 occ_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 + "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 + "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 + "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, + patch_image._data); + const unsigned int + _constraint = patch_image._depth>1?3:2, + constraint = guide._spectrum>_constraint?_constraint:0; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<_constraint)) + throw CImgArgumentException(_cimg_instance + "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 is_updated(_width,_height,_depth,1,3); + CImg score(_width,_height,_depth); + CImg occ, loop_order; + ulongT rng = (cimg::_rand(),cimg::rng()); + if (occ_penalization!=0) { + occ.assign(patch_image._width,patch_image._height,patch_image._depth,1,0); + loop_order.assign(_width,_height,_depth,_depth>1?3:2); + cimg_forXYZ(loop_order,x,y,z) { + loop_order(x,y,z,0) = x; + loop_order(x,y,z,1) = y; + if (loop_order._spectrum>2) loop_order(x,y,z,2) = z; + } + cimg_forXYZ(loop_order,x,y,z) { // Randomize loop order in case of constraints on patch occurrence + const unsigned int + X = (unsigned int)cimg::round(cimg::rand(loop_order._width - 1.,&rng)), + Y = (unsigned int)cimg::round(cimg::rand(loop_order._height - 1.,&rng)), + Z = loop_order._depth>1?(unsigned int)cimg::round(cimg::rand(loop_order._depth - 1.,&rng)):0U; + cimg::swap(loop_order(x,y,z,0),loop_order(X,Y,Z,0)); + cimg::swap(loop_order(x,y,z,1),loop_order(X,Y,Z,1)); + if (loop_order._spectrum>2) cimg::swap(loop_order(x,y,z,2),loop_order(X,Y,Z,2)); + } + } + 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 (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_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_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter2) z = loop_order(_x,_y,_z,2); else z = _z; + } else { x = _x; y = _y; 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 = map(x - 1,y,z,0); + v = map(x - 1,y,z,1); + w = map(x - 1,y,z,2); + if (u>=cx1 - 1 && u=cy1 && v=cz1 && w0 && (is_updated(x,y - 1,z)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,z,0); + v = map(x,y - 1,z,1); + w = map(x,y - 1,z,2); + if (u>=cx1 && u=cy1 - 1 && v=cz1 && w0 && (is_updated(x,y,z - 1)&cmask)) { // Compare with backward neighbor + u = map(x,y,z - 1,0); + v = map(x,y,z - 1,1); + w = map(x,y,z - 1,2); + if (u>=cx1 && u=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::inf()); + } else cimg_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter0 && (is_updated(x - 1,y)&cmask)) { // Compare with left neighbor + u = map(x - 1,y,0); + v = map(x - 1,y,1); + if (u>=cx1 - 1 && u=cy1 && v0 && (is_updated(x,y - 1)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,0); + v = map(x,y - 1,1); + if (u>=cx1 && u=cy1 - 1 && v=cx1 + 1 && u=cy1 && v=cx1 && u=cy1 + 1 && v& img1, const CImg& img2, const CImg& occ, + const unsigned int psizew, const unsigned int psizeh, + 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 occ_penalization, + const float max_score) { // 3D version + 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 - 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; + for (unsigned int k = 0; kmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + p1+=offy1; p2+=offy2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc,zc)); + } + + static float _matchpatch(const CImg& img1, const CImg& img2, const CImg& occ, + 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 occ_penalization, + const float max_score) { // 2D version + 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 - psizewc, + offx2 = (ulongT)img2._width - psizewc; + float ssd = 0; + for (unsigned int j = 0; jmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc)); + } + + //! Compute Euclidean distance function to a specified value. + /** + \param value Reference value. + \param metric Type of metric. Can be { 0=Chebyshev | 1=Manhattan | 2=Euclidean | 3=Squared-euclidean }. + \note + The distance transform implementation has been submitted by A. Meijster, and implements + the article 'W.H. Hesselink, A. Meijster, J.B.T.M. Roerdink, + "A general algorithm for computing distance transforms in linear time.", + In: Mathematical Morphology and its Applications to Image and Signal Processing, + J. Goutsias, L. Vincent, and D.S. Bloomberg (eds.), Kluwer, 2000, pp. 331-340.' + The submitted code has then been modified to fit CImg coding style and constraints. + **/ + CImg& distance(const T& value, const unsigned int metric=2) { + if (is_empty()) return *this; + if (cimg::type::string()!=cimg::type::string()) // 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 + } + return *this; + } + + //! Compute distance to a specified value \newinstance. + CImg get_distance(const T& value, const unsigned int metric=2) const { + return CImg(*this,false).distance((Tfloat)value,metric); + } + + static longT _distance_sep_edt(const longT i, const longT u, const longT *const g) { + return (u*u - i*i + g[u] - g[i])/(2*(u - i)); + } + + static longT _distance_dist_edt(const longT x, const longT i, const longT *const g) { + return (x - i)*(x - i) + g[i]; + } + + static longT _distance_sep_mdt(const longT i, const longT u, const longT *const g) { + return (u - i<=g[u] - g[i]?999999999:(g[u] - g[i] + u + i)/2); + } + + static longT _distance_dist_mdt(const longT x, const longT i, const longT *const g) { + return (x=0) && f(t[q],s[q],g)>f(t[q],u,g)) { --q; } + if (q<0) { q = 0; s[0] = u; } + else { const longT w = 1 + sep(s[q], u, g); if (w<(longT)len) { ++q; s[q] = u; t[q] = w; }} + } + for (int u = (int)len - 1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan + } + + CImg& _distance_core(longT (*const sep)(const longT, const longT, const longT *const), + longT (*const f)(const longT, const longT, const longT *const)) { + // Check for g++ 4.9.X, as OpenMP seems to crash for this particular function. I have no clues why. +#define cimg_is_gcc49x (__GNUC__==4 && __GNUC_MINOR__==9) + + const ulongT wh = (ulongT)_width*_height; +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) +#endif + cimg_forC(*this,c) { + CImg 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 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_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 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_forY(*this,y) g[y] = (longT)img(x,y,z,0,wh); + _distance_scan(_height,g,sep,f,s,t,dt); + cimg_forY(*this,y) img(x,y,z,0,wh) = (T)dt[y]; + } + } + if (_depth>1) { + g.assign(_depth); dt.assign(_depth); s.assign(_depth); t.assign(_depth); +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for 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_forZ(*this,z) g[z] = (longT)img(x,y,z,0,wh); + _distance_scan(_depth,g,sep,f,s,t,dt); + cimg_forZ(*this,z) img(x,y,z,0,wh) = (T)dt[z]; + } + } + } + return *this; + } + + //! Compute chamfer distance to a specified value, with a custom metric. + /** + \param value Reference value. + \param metric_mask Metric mask. + \note The algorithm code has been initially proposed by A. Meijster, and modified by D. Tschumperlé. + **/ + template + CImg& distance(const T& value, const CImg& metric_mask) { + if (is_empty()) return *this; + bool is_value = false; + cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,0:(T)999999999; + if (!is_value) return fill(cimg::type::max()); + const ulongT wh = (ulongT)_width*_height; + 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 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 y = height() - 1 - dy, ny = height() - 1; y>=0; --y,--ny) { + for (int x = width() - 1 - dx, nx = width() - 1; x>=0; --x,--nx) { + const T dd = img(nx,ny,nz,0,wh) + weight; + if (dd + CImg get_distance(const T& value, const CImg& metric_mask) const { + return CImg(*this,false).distance(value,metric_mask); + } + + //! Compute distance to a specified value, according to a custom metric (use dijkstra algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + \param is_high_connectivity Tells if the algorithm uses low or high connectivity. + \param[out] return_path An image containing the nodes of the minimal path. + **/ + template + CImg& distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) { + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm) \newinstance. + template + CImg::type> + get_distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) const { + if (is_empty()) return return_path.assign(); + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_dijkstra(): image instance and metric map (%u,%u,%u,%u) " + "have incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + typedef typename cimg::superset::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); + + cimg_forC(*this,c) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + CImg path = return_path?return_path.get_shared_channel(c):CImg(); + unsigned int sizeQ = 0; + + // Detect initial seeds. + is_queued.fill(0); + cimg_forXYZ(img,x,y,z) if (img(x,y,z)==value) { + Q._priority_queue_insert(is_queued,sizeQ,0,x,y,z); + res(x,y,z) = 0; + if (path) path(x,y,z) = (to)0; + } + + // Start distance propagation. + while (sizeQ) { + + // Get and remove point with minimal potential from the queue. + const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3); + const td P = (td)-Q(0,0); + Q._priority_queue_remove(sizeQ); + + // Update neighbors. + td npot = 0; + if (x - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x - 1,y,z) + P),x - 1,y,z)) { + res(x - 1,y,z) = npot; if (path) path(x - 1,y,z) = (to)2; + } + if (x + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y - 1,z) + P),x,y - 1,z)) { + res(x,y - 1,z) = npot; if (path) path(x,y - 1,z) = (to)8; + } + if (y + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y,z - 1) + P),x,y,z - 1)) { + res(x,y,z - 1) = npot; if (path) path(x,y,z - 1) = (to)32; + } + if (z + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y - 1,z) + P)),x - 1,y - 1,z)) { + res(x - 1,y - 1,z) = npot; if (path) path(x - 1,y - 1,z) = (to)10; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y - 1,z) + P)),x + 1,y - 1,z)) { + res(x + 1,y - 1,z) = npot; if (path) path(x + 1,y - 1,z) = (to)9; + } + if (x - 1>=0 && y + 1=0) { // Diagonal neighbors on slice z - 1 + if (x - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z - 1) + P)),x - 1,y,z - 1)) { + res(x - 1,y,z - 1) = npot; if (path) path(x - 1,y,z - 1) = (to)34; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z - 1) + P)),x,y - 1,z - 1)) { + res(x,y - 1,z - 1) = npot; if (path) path(x,y - 1,z - 1) = (to)40; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z - 1) + P)), + x - 1,y - 1,z - 1)) { + res(x - 1,y - 1,z - 1) = npot; if (path) path(x - 1,y - 1,z - 1) = (to)42; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z - 1) + P)), + x + 1,y - 1,z - 1)) { + res(x + 1,y - 1,z - 1) = npot; if (path) path(x + 1,y - 1,z - 1) = (to)41; + } + if (x - 1>=0 && y + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z + 1) + P)),x - 1,y,z + 1)) { + res(x - 1,y,z + 1) = npot; if (path) path(x - 1,y,z + 1) = (to)18; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z + 1) + P)),x,y - 1,z + 1)) { + res(x,y - 1,z + 1) = npot; if (path) path(x,y - 1,z + 1) = (to)24; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z + 1) + P)), + x - 1,y - 1,z + 1)) { + res(x - 1,y - 1,z + 1) = npot; if (path) path(x - 1,y - 1,z + 1) = (to)26; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z + 1) + P)), + x + 1,y - 1,z + 1)) { + res(x + 1,y - 1,z + 1) = npot; if (path) path(x + 1,y - 1,z + 1) = (to)25; + } + if (x - 1>=0 && y + 1 + CImg& distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) { + return get_distance_dijkstra(value,metric,is_high_connectivity).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm). \newinstance. + template + CImg get_distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) const { + CImg return_path; + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + **/ + template + CImg& distance_eikonal(const T& value, const CImg& metric) { + return get_distance_eikonal(value,metric).move_to(*this); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + template + CImg get_distance_eikonal(const T& value, const CImg& metric) const { + if (is_empty()) return *this; + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_eikonal(): image instance and metric map (%u,%u,%u,%u) have " + "incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + CImg result(_width,_height,_depth,_spectrum,cimg::type::max()), Q; + 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) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + unsigned int sizeQ = 0; + state.fill(-1); + + // Detect initial seeds. + Tfloat *ptr1 = res._data; char *ptr2 = state._data; + cimg_for(img,ptr0,T) { if (*ptr0==value) { *ptr1 = 0; *ptr2 = 1; } ++ptr1; ++ptr2; } + + // Initialize seeds neighbors. + ptr2 = state._data; + cimg_forXYZ(img,x,y,z) if (*(ptr2++)==1) { + if (x - 1>=0 && state(x - 1,y,z)==-1) { + const Tfloat dist = res(x - 1,y,z) = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x - 1,y,z); + } + if (x + 1=0 && state(x,y - 1,z)==-1) { + const Tfloat dist = res(x,y - 1,z) = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y - 1,z); + } + if (y + 1=0 && state(x,y,z - 1)==-1) { + const Tfloat dist = res(x,y,z - 1) = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z - 1); + } + if (z + 1=0) { + if (x - 1>=0 && state(x - 1,y,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + if (dist=0 && state(x,y - 1,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + if (dist=0 && state(x,y,z - 1)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + if (dist& res, const Tfloat P, + const int x=0, const int y=0, const int z=0) const { + const Tfloat M = (Tfloat)cimg::type::max(); + T T1 = (T)std::min(x - 1>=0?res(x - 1,y,z):M,x + 11) { // 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 + 1T2) cimg::swap(T1,T2); + if (T2>T3) cimg::swap(T2,T3); + if (T1>T2) cimg::swap(T1,T2); + if (P<=0) return (Tfloat)T1; + if (T31) { // 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 + void _eik_priority_queue_insert(CImg& state, unsigned int& siz, const t value, + const unsigned int x, const unsigned int y, const unsigned int z) { + if (state(x,y,z)>0) return; + state(x,y,z) = 0; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; (*this)(siz - 1,1) = (T)x; (*this)(siz - 1,2) = (T)y; (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); cimg::swap((*this)(pos,3),(*this)(par,3)); + } + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE. + /** + \param nb_iterations Number of PDE iterations. + \param band_size Size of the narrow band. + \param time_step Time step of the PDE iterations. + **/ + CImg& distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) { + if (is_empty()) return *this; + CImg velocity(*this,false); + for (unsigned int iteration = 0; iteration1) { // 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)0?(Incc - Iccc):(Iccc - Ipcc), + iy = gy*sgn>0?(Icnc - Iccc):(Iccc - Icpc), + iz = gz*sgn>0?(Iccn - Iccc):(Iccc - Iccp), + ng = 1e-5f + cimg::hypot(gx,gy,gz), + ngx = gx/ng, + ngy = gy/ng, + ngz = gz/ng, + veloc = sgn*(ngx*ix + ngy*iy + ngz*iz - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } else { // 2D version + CImg_3x3(I,Tfloat); + cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) if (band_size<=0 || cimg::abs(Icc)0?(Inc - Icc):(Icc - Ipc), + iy = gy*sgn>0?(Icn - Icc):(Icc - Icp), + ng = std::max((Tfloat)1e-5,cimg::hypot(gx,gy)), + ngx = gx/ng, + ngy = gy/ng, + veloc = sgn*(ngx*ix + ngy*iy - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } + if (veloc_max>0) *this+=(velocity*=time_step/veloc_max); + } + return *this; + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE \newinstance. + CImg get_distance_eikonal(const unsigned int nb_iterations, const float band_size=0, + const float time_step=0.5f) const { + return CImg(*this,false).distance_eikonal(nb_iterations,band_size,time_step); + } + + //! Compute Haar multiscale wavelet transform. + /** + \param axis Axis considered for the transform. + \param invert Set inverse of direct transform. + \param nb_scales Number of scales used for the transform. + **/ + CImg& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(axis,invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg 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.f); + if (nb_scales==1) { + switch (cimg::lowercase(axis)) { // Single scale transform + case 'x' : { + const unsigned int w = _width/2; + if (w) { + if ((w%2) && w!=1) + throw CImgInstanceException(_cimg_instance + "haar(): Sub-image width %u is not even.", + cimg_instance, + w); + + res.assign(_width,_height,_depth,_spectrum); + if (invert) cimg_forYZC(*this,y,z,c) { // Inverse transform along X + for (unsigned int x = 0, xw = w, x2 = 0; x& haar(const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg get_haar(const bool invert=false, const unsigned int nb_scales=1) const { + CImg res; + if (nb_scales==1) { // Single scale transform + if (_width>1) get_haar('x',invert,1).move_to(res); + if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); } + if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_to(res); } + if (res) return res; + } else { // Multi-scale transform + if (invert) { // Inverse transform + res.assign(*this,false); + if (_width>1) { + if (_height>1) { + if (_depth>1) { + unsigned int w = _width, h = _height, d = _depth; + for (unsigned int s = 1; w && h && d && s1) { + unsigned int w = _width, d = _depth; + for (unsigned int s = 1; w && d && s1) { + if (_depth>1) { + unsigned int h = _height, d = _depth; + for (unsigned int s = 1; h && d && s1) { + unsigned int d = _depth; + for (unsigned int s = 1; d && s1) { + if (_height>1) { + if (_depth>1) + for (unsigned int s = 1, w = _width/2, h = _height/2, d = _depth/2; w && h && d && s1) for (unsigned int s = 1, w = _width/2, d = _depth/2; w && d && s1) { + if (_depth>1) + for (unsigned int s = 1, h = _height/2, d = _depth/2; h && d && s1) for (unsigned int s = 1, d = _depth/2; d && s get_FFT(const char axis, const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],axis,is_invert); + return res; + } + + //! Compute n-d Fast Fourier Transform. + /* + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + CImgList get_FFT(const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],is_invert); + return res; + } + + //! Compute 1D Fast Fourier Transform, along a specified axis. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param axis Axis along which the FFT is computed. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + static void FFT(CImg& real, CImg& imag, const char axis, const bool is_invert=false) { + if (!real) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part is empty.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); +#ifdef cimg_use_fftw3 + cimg::mutex(12); + fftw_complex *data_in; + fftw_plan data_plan; + + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the X-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forYZC(real,y,z,c) { + T *ptrr = real.data(0,y,z,c), *ptri = imag.data(0,y,z,c); + double *ptrd = (double*)data_in; + cimg_forX(real,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); } + fftw_execute(data_plan); + const unsigned int fact = real._width; + if (is_invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); } + else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); } + } + } break; + case 'y' : { // Fourier along Y, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Y-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._height), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const unsigned int off = real._width; + cimg_forXZC(real,x,z,c) { + T *ptrr = real.data(x,0,z,c), *ptri = imag.data(x,0,z,c); + double *ptrd = (double*)data_in; + cimg_forY(real,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._height; + if (is_invert) + cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + case 'z' : { // Fourier along Z, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Z-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._depth), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const ulongT off = (ulongT)real._width*real._height; + cimg_forXYC(real,x,y,c) { + T *ptrr = real.data(x,y,0,c), *ptri = imag.data(x,y,0,c); + double *ptrd = (double*)data_in; + cimg_forZ(real,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._depth; + if (is_invert) + cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + default : + throw CImgArgumentException("CImgList<%s>::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } + fftw_destroy_plan(data_plan); + fftw_free(data_in); + cimg::mutex(12,0); +#else + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using built-in functions + const unsigned int N = real._width, N2 = N>>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the X-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forYZC(real,y,z,c) { + cimg::swap(real(i,y,z,c),real(j,y,z,c)); + cimg::swap(imag(i,y,z,c),imag(j,y,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = delta>>1; + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Y-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXZC(real,x,z,c) { + cimg::swap(real(x,i,z,c),real(x,j,z,c)); + cimg::swap(imag(x,i,z,c),imag(x,j,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Z-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXYC(real,x,y,c) { + cimg::swap(real(x,y,i,c),real(x,y,j,c)); + cimg::swap(imag(x,y,i,c),imag(x,y,j,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } +#endif + } + + //! Compute n-d Fast Fourier Transform. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + \param nb_threads Number of parallel threads used for the computation. + Use \c 0 to set this to the number of available cpus. + **/ + static void FFT(CImg& real, CImg& imag, const bool is_invert=false, const unsigned int nb_threads=0) { + if (!real) + throw CImgInstanceException("CImgList<%s>::FFT(): Empty specified real part.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); + +#ifdef cimg_use_fftw3 + cimg::mutex(12); +#ifndef cimg_use_fftw3_singlethread + const unsigned int _nb_threads = nb_threads?nb_threads:cimg::nb_cpus(); + static int fftw_st = fftw_init_threads(); + cimg::unused(fftw_st); + fftw_plan_with_nthreads(_nb_threads); +#else + cimg::unused(nb_threads); +#endif + fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u).", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width* + real._height*real._depth*real._spectrum), + real._width,real._height,real._depth,real._spectrum); + + fftw_plan data_plan; + const ulongT w = (ulongT)real._width, wh = w*real._height, whd = wh*real._depth; + data_plan = fftw_plan_dft_3d(real._width,real._height,real._depth,data_in,data_in, + is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forC(real,c) { + T *ptrr = real.data(0,0,0,c), *ptri = imag.data(0,0,0,c); + double *ptrd = (double*)data_in; + for (unsigned int x = 0; 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); +#endif + } + + //@} + //------------------------------------- + // + //! \name 3D Objects Management + //@{ + //------------------------------------- + + //! Shift 3D object's vertices. + /** + \param tx X-coordinate of the 3D displacement vector. + \param ty Y-coordinate of the 3D displacement vector. + \param tz Z-coordinate of the 3D displacement vector. + **/ + CImg& shift_object3d(const float tx, const float ty=0, const float tz=0) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "shift_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + get_shared_row(0)+=tx; get_shared_row(1)+=ty; get_shared_row(2)+=tz; + return *this; + } + + //! Shift 3D object's vertices \newinstance. + CImg 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. + /** + \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.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2; + return *this; + } + + //! Shift 3D object's vertices, so that it becomes centered \newinstance. + CImg get_shift_object3d() const { + return CImg(*this,false).shift_object3d(); + } + + //! Resize 3D object. + /** + \param sx Width of the 3D object's bounding box. + \param sy Height of the 3D object's bounding box. + \param sz Depth of the 3D object's bounding box. + **/ + CImg& resize_object3d(const float sx, const float sy=-100, const float sz=-100) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + if (xm0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; } + if (ym0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; } + if (zm0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; } + return *this; + } + + //! 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. + CImg resize_object3d() { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz); + if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; } + return *this; + } + + //! Resize 3D object to unit size \newinstance. + CImg get_resize_object3d() const { + return CImg(*this,false).resize_object3d(); + } + + //! Merge two 3D objects together. + /** + \param[in,out] primitives Primitives data of the current 3D object. + \param obj_vertices Vertices data of the additional 3D object. + \param obj_primitives Primitives data of the additional 3D object. + **/ + template + CImg& append_object3d(CImgList& primitives, const CImg& obj_vertices, + const CImgList& obj_primitives) { + if (!obj_vertices || !obj_primitives) return *this; + if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Specified vertice image (%u,%u,%u,%u,%p) is not a " + "set of 3D vertices.", + cimg_instance, + obj_vertices._width,obj_vertices._height, + obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data); + + if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); } + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + const unsigned int P = _width; + append(obj_vertices,'x'); + const unsigned int N = primitives._width; + primitives.insert(obj_primitives); + for (unsigned int i = N; i &p = primitives[i]; + switch (p.size()) { + case 1 : p[0]+=P; break; // Point + case 5 : p[0]+=P; p[1]+=P; break; // Sphere + case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment + case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle + case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle + } + } + return *this; + } + + //! Texturize primitives of a 3D object. + /** + \param[in,out] primitives Primitives data of the 3D object. + \param[in,out] colors Colors data of the 3D object. + \param texture Texture image to map to 3D object. + \param coords Texture-mapping coordinates. + **/ + template + const CImg& texturize_object3d(CImgList& primitives, CImgList& colors, + const CImg& texture, const CImg& coords=CImg::const_empty()) const { + if (is_empty()) return *this; + if (_height!=3) + throw CImgInstanceException(_cimg_instance + "texturize_object3d(): image instance is not a set of 3D points.", + cimg_instance); + if (coords && (coords._width!=_width || coords._height!=2)) + throw CImgArgumentException(_cimg_instance + "texturize_object3d(): Invalid specified texture coordinates (%u,%u,%u,%u,%p).", + cimg_instance, + coords._width,coords._height,coords._depth,coords._spectrum,coords._data); + CImg _coords; + if (!coords) { // If no texture coordinates specified, do a default XY-projection + _coords.assign(_width,2); + float + xmin, xmax = (float)get_shared_row(0).max_min(xmin), + ymin, ymax = (float)get_shared_row(1).max_min(ymin), + dx = xmax>xmin?xmax-xmin:1, + dy = ymax>ymin?ymax-ymin:1; + cimg_forX(*this,p) { + _coords(p,0) = (int)(((*this)(p,0) - xmin)*texture._width/dx); + _coords(p,1) = (int)(((*this)(p,1) - ymin)*texture._height/dy); + } + } else _coords = coords; + + int texture_ind = -1; + cimglist_for(primitives,l) { + CImg &p = primitives[l]; + const unsigned int siz = p.size(); + switch (siz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)p[0]; + const int x0 = _coords(i0,0), y0 = _coords(i0,1); + texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0).move_to(colors[l]); + } break; + case 2 : case 6 : { // Line + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,x0,y0,x1,y1).move_to(p); + } break; + case 3 : case 9 : { // Triangle + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p); + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2], i3 = (unsigned int)p[3]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1), + x3 = _coords(i3,0), y3 = _coords(i3,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,i3,x0,y0,x1,y1,x2,y2,x3,y3).move_to(p); + } break; + } + } + return *this; + } + + //! Generate a 3D elevation of the image instance. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param[out] colors The returned list of the 3D object colors. + \param elevation The input elevation map. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + CImgList colors3d; + const CImg points3d = img.get_elevation3d(faces3d,colors3d,img.get_norm()*0.2); + CImg().display_object3d("Elevation3d",points3d,faces3d,colors3d); + \endcode + \image html ref_elevation3d.jpg + **/ + template + CImg get_elevation3d(CImgList& primitives, CImgList& colors, const CImg& elevation) const { + if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1) + throw CImgArgumentException(_cimg_instance + "get_elevation3d(): Instance and specified elevation (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + elevation._width,elevation._height,elevation._depth, + elevation._spectrum,elevation._data); + if (is_empty()) return *this; + float m, M = (float)max_min(m); + if (M==m) ++M; + colors.assign(); + const unsigned int size_x1 = _width - 1, size_y1 = _height - 1; + for (unsigned int y = 0; y1?((*this)(x,y,1) - m)*255/(M-m):r), + b = (unsigned char)(_spectrum>2?((*this)(x,y,2) - m)*255/(M-m):_spectrum>1?0:r); + CImg::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.f,_height - 1.f,_width,_height); + } + + //! Generate the 3D projection planes of the image instance. + /** + \param[out] primitives Primitives data of the returned 3D object. + \param[out] colors Colors data of the returned 3D object. + \param x0 X-coordinate of the projection point. + \param y0 Y-coordinate of the projection point. + \param z0 Z-coordinate of the projection point. + \param normalize_colors Tells if the created textures have normalized colors. + **/ + template + CImg get_projections3d(CImgList& primitives, CImgList& colors, + const unsigned int x0, const unsigned int y0, const unsigned int z0, + const bool normalize_colors=false) const { + float m = 0, M = 0, delta = 1; + if (normalize_colors) { m = (float)min_max(M); delta = 255/(m==M?1:M-m); } + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + CImg img_xy, img_xz, img_yz; + if (normalize_colors) { + ((get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1)-=m)*=delta).move_to(img_xy); + ((get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_width,_depth,1,-100,-1). + move_to(img_xz); + ((get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_height,_depth,1,-100,-1). + move_to(img_yz); + } else { + get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1).move_to(img_xy); + get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1).move_to(img_xz); + get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).resize(_height,_depth,1,-100,-1).move_to(img_yz); + } + CImg points(12,3,1,1, + 0,_width - 1,_width - 1,0, 0,_width - 1,_width - 1,0, _x0,_x0,_x0,_x0, + 0,0,_height - 1,_height - 1, _y0,_y0,_y0,_y0, 0,_height - 1,_height - 1,0, + _z0,_z0,_z0,_z0, 0,0,_depth - 1,_depth - 1, 0,0,_depth - 1,_depth - 1); + primitives.assign(); + CImg::vector(0,1,2,3,0,0,img_xy._width - 1,0,img_xy._width - 1,img_xy._height - 1,0,img_xy._height - 1). + move_to(primitives); + CImg::vector(4,5,6,7,0,0,img_xz._width - 1,0,img_xz._width - 1,img_xz._height - 1,0,img_xz._height - 1). + move_to(primitives); + CImg::vector(8,9,10,11,0,0,img_yz._width - 1,0,img_yz._width - 1,img_yz._height - 1,0,img_yz._height - 1). + move_to(primitives); + colors.assign(); + img_xy.move_to(colors); + img_xz.move_to(colors); + img_yz.move_to(colors); + return points; + } + + //! Generate a isoline of the image instance as a 3D object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x The number of subdivisions along the X-axis. + \param size_y The number of subdisivions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + const CImg points3d = img.get_isoline3d(faces3d,100); + CImg().display_object3d("Isoline3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isoline3d.jpg + **/ + template + CImg get_isoline3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a scalar image.", + cimg_instance); + if (_depth>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a 2D image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.f,height() - 1.f,width(),height()); + } else { + const _functor2d_float func(*this); + 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. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x Number of subdivisions along the X-axis. + \param size_y Number of subdisivions along the Y-axis. + \param size_z Number of subdisivions along the Z-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img = CImg("reference.jpg").resize(-100,-100,20); + CImgList faces3d; + const CImg points3d = img.get_isosurface3d(faces3d,100); + CImg().display_object3d("Isosurface3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isosurface3d.jpg + **/ + template + CImg get_isosurface3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100, const int size_z=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isosurface3d(): Instance is not a scalar image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.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.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. + /** + \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 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 CImg isosurface3d(CImgList& primitives, const tfunc& func, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int size_x=32, const int size_y=32, const int size_z=32) { + static const unsigned int edges[256] = { + 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 + }; + + static const int triangles[256][16] = { + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 }, + { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 }, + { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 }, + { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 }, + { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 }, + { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 }, + { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, + { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, + { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, + { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 }, + { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, + { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 }, + { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, + { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 }, + { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, + { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 }, + { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, + { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 }, + { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 }, + { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, + { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 }, + { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 }, + { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 }, + { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 }, + { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, + { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 }, + { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 }, + { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, + { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 }, + { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 }, + { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, + { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 }, + { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, + { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, + { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 }, + { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 }, + { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 }, + { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, + { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 }, + { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 }, + { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, + { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 }, + { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, + { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, + { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, + { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 }, + { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 }, + { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, + { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 }, + { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 }, + { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, + { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 }, + { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, + { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 }, + { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, + { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 }, + { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 }, + { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, + { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, + { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 }, + { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, + { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 }, + { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, + { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, + { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, + { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 }, + { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 }, + { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 }, + { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, + { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 }, + { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, + { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 }, + { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, + { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 }, + { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, + { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 }, + { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, + { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, + { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, + { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, + { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 }, + { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, + { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 }, + { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, + { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 }, + { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 }, + { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, + { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 }, + { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, + { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 }, + { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 }, + { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, + { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } + }; + + const unsigned int + _nx = (unsigned int)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)), + _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)), + _nz = (unsigned int)(size_z>=0?size_z:cimg::round((z1-z0)*-size_z/100 + 1)), + nx = _nx?_nx:1, + ny = _ny?_ny:1, + nz = _nz?_nz:1, + nxm1 = nx - 1, + nym1 = ny - 1, + nzm1 = nz - 1; + primitives.assign(); + if (!nxm1 || !nym1 || !nzm1) return CImg(); + 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; + + // Fill the first plane with function values + Y = y0; + cimg_forY(values1,y) { + X = x0; + cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=dx; } + Y+=dy; + } + + // Run Marching Cubes algorithm + Z = z0; nZ = Z + dz; + for (unsigned int zi = 0; zi::vector(Xi,Y,Z).move_to(vertices); + } + if ((edge&2) && indices1(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val1)*dy/(val2-val1); + indices1(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,Z).move_to(vertices); + } + if ((edge&4) && indices1(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val3)*dx/(val2-val3); + indices1(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,Z).move_to(vertices); + } + if ((edge&8) && indices1(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val0)*dy/(val3-val0); + indices1(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,Z).move_to(vertices); + } + if ((edge&16) && indices2(xi,yi,0)<0) { + const float Xi = X + (isovalue-val4)*dx/(val5-val4); + indices2(xi,yi,0) = vertices.width(); + CImg::vector(Xi,Y,nZ).move_to(vertices); + } + if ((edge&32) && indices2(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val5)*dy/(val6-val5); + indices2(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,nZ).move_to(vertices); + } + if ((edge&64) && indices2(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val7)*dx/(val6-val7); + indices2(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,nZ).move_to(vertices); + } + if ((edge&128) && indices2(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val4)*dy/(val7-val4); + indices2(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,nZ).move_to(vertices); + } + if ((edge&256) && indices1(xi,yi,2)<0) { + const float Zi = Z+ (isovalue-val0)*dz/(val4-val0); + indices1(xi,yi,2) = vertices.width(); + CImg::vector(X,Y,Zi).move_to(vertices); + } + if ((edge&512) && indices1(nxi,yi,2)<0) { + const float Zi = Z + (isovalue-val1)*dz/(val5-val1); + indices1(nxi,yi,2) = vertices.width(); + CImg::vector(nX,Y,Zi).move_to(vertices); + } + if ((edge&1024) && indices1(nxi,nyi,2)<0) { + const float Zi = Z + (isovalue-val2)*dz/(val6-val2); + indices1(nxi,nyi,2) = vertices.width(); + CImg::vector(nX,nY,Zi).move_to(vertices); + } + if ((edge&2048) && indices1(xi,nyi,2)<0) { + const float Zi = Z + (isovalue-val3)*dz/(val7-val3); + indices1(xi,nyi,2) = vertices.width(); + CImg::vector(X,nY,Zi).move_to(vertices); + } + + // Create triangles + for (const int *triangle = triangles[configuration]; *triangle!=-1; ) { + const unsigned int + p0 = (unsigned int)*(triangle++), + p1 = (unsigned int)*(triangle++), + p2 = (unsigned int)*(triangle++); + const tf + i0 = (tf)(_isosurface3d_indice(p0,indices1,indices2,xi,yi,nxi,nyi)), + i1 = (tf)(_isosurface3d_indice(p1,indices1,indices2,xi,yi,nxi,nyi)), + i2 = (tf)(_isosurface3d_indice(p2,indices1,indices2,xi,yi,nxi,nyi)); + CImg::vector(i0,i2,i1).move_to(primitives); + } + } + } + } + cimg::swap(values1,values2); + cimg::swap(indices1,indices2); + } + return vertices>'x'; + } + + //! Compute isosurface of a function, as a 3D object \overloading. + template + static CImg isosurface3d(CImgList& primitives, const char *const expression, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int dx=32, const int dy=32, const int dz=32) { + const _functor3d_expr func(expression); + return isosurface3d(primitives,func,isovalue,x0,y0,z0,x1,y1,z1,dx,dy,dz); + } + + template + static int _isosurface3d_indice(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) { + case 0 : return indices1(x,y,0); + case 1 : return indices1(nx,y,1); + case 2 : return indices1(x,ny,0); + case 3 : return indices1(x,y,1); + case 4 : return indices2(x,y,0); + case 5 : return indices2(nx,y,1); + case 6 : return indices2(x,ny,0); + case 7 : return indices2(x,y,1); + case 8 : return indices1(x,y,2); + case 9 : return indices1(nx,y,2); + case 10 : return indices1(nx,ny,2); + case 11 : return indices1(x,ny,2); + } + return 0; + } + + // Define functors for accessing image values (used in previous functions). + struct _functor2d_int { + const CImg& ref; + _functor2d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref((int)x,(int)y); + } + }; + + struct _functor2d_float { + const CImg& ref; + _functor2d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref._linear_atXY(x,y); + } + }; + + struct _functor2d_expr { + _cimg_math_parser *mp; + ~_functor2d_expr() { mp->end(); delete mp; } + _functor2d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y) const { + return (float)(*mp)(x,y,0,0); + } + }; + + struct _functor3d_int { + const CImg& ref; + _functor3d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref((int)x,(int)y,(int)z); + } + }; + + struct _functor3d_float { + const CImg& ref; + _functor3d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref._linear_atXYZ(x,y,z); + } + }; + + struct _functor3d_expr { + _cimg_math_parser *mp; + ~_functor3d_expr() { mp->end(); delete mp; } + _functor3d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z) const { + return (float)(*mp)(x,y,z,0); + } + }; + + struct _functor4d_int { + const CImg& ref; + _functor4d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref((int)x,(int)y,(int)z,c); + } + }; + + //! Generate a 3D box object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the box (dimension along the X-axis). + \param size_y The height of the box (dimension along the Y-axis). + \param size_z The depth of the box (dimension along the Z-axis). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::box3d(faces3d,10,20,30); + CImg().display_object3d("Box3d",points3d,faces3d); + \endcode + \image html ref_box3d.jpg + **/ + template + static CImg box3d(CImgList& primitives, + const float size_x=200, const float size_y=100, const float size_z=100) { + primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5); + return CImg(8,3,1,1, + 0.,size_x,size_x, 0., 0.,size_x,size_x, 0., + 0., 0.,size_y,size_y, 0., 0.,size_y,size_y, + 0., 0., 0., 0.,size_z,size_z,size_z,size_z); + } + + //! Generate a 3D cone. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the cone basis. + \param size_z The cone's height. + \param subdivisions The number of basis angular subdivisions. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cone3d(faces3d,50); + CImg().display_object3d("Cone3d",points3d,faces3d); + \endcode + \image html ref_cone3d.jpg + **/ + template + static CImg cone3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,size_z, + 0.,0.,0.); + 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. + /** + \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). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cylinder3d(faces3d,50); + CImg().display_object3d("Cylinder3d",points3d,faces3d); + \endcode + \image html ref_cylinder3d.jpg + **/ + template + static CImg cylinder3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,0., + 0.,0.,size_z); + 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); + } + return vertices>'x'; + } + + //! Generate a 3D torus. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius1 The large radius. + \param radius2 The small radius. + \param subdivisions1 The number of angular subdivisions for the large radius. + \param subdivisions2 The number of angular subdivisions for the small radius. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::torus3d(faces3d,20,4); + CImg().display_object3d("Torus3d",points3d,faces3d); + \endcode + \image html ref_torus3d.jpg + **/ + template + static CImg torus3d(CImgList& primitives, + const float radius1=100, const float radius2=30, + const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) { + primitives.assign(); + if (!subdivisions1 || !subdivisions2) return CImg(); + 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); + } + } + return vertices>'x'; + } + + //! Generate a 3D XY-plane. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the plane (dimension along the X-axis). + \param size_y The height of the plane (dimensions along the Y-axis). + \param subdivisions_x The number of planar subdivisions along the X-axis. + \param subdivisions_y The number of planar subdivisions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::plane3d(faces3d,100,50); + CImg().display_object3d("Plane3d",points3d,faces3d); + \endcode + \image html ref_plane3d.jpg + **/ + template + static CImg plane3d(CImgList& primitives, + const float size_x=100, const float size_y=100, + const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) { + primitives.assign(); + if (!subdivisions_x || !subdivisions_y) return CImg(); + CImgList vertices; + const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1; + const float fx = (float)size_x/w, fy = (float)size_y/h; + for (unsigned int y = 0; y::vector(fx*x,fy*y,0).move_to(vertices); + for (unsigned int y = 0; y::vector(off1,off4,off3,off2).move_to(primitives); + } + return vertices>'x'; + } + + //! Generate a 3D sphere. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the sphere (dimension along the X-axis). + \param subdivisions The number of recursive subdivisions from an initial icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::sphere3d(faces3d,100,4); + CImg().display_object3d("Sphere3d",points3d,faces3d); + \endcode + \image html ref_sphere3d.jpg + **/ + template + static CImg sphere3d(CImgList& primitives, + const float radius=50, const unsigned int subdivisions=3) { + + // Create initial icosahedron + primitives.assign(); + 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); + // edge - length/2 + float he = (float)a; + + // Recurse subdivisions + for (unsigned int i = 0; i::vector(nx0,ny0,nz0).move_to(vertices); i0 = vertices.width() - 1; } + if (i1<0) { CImg::vector(nx1,ny1,nz1).move_to(vertices); i1 = vertices.width() - 1; } + if (i2<0) { CImg::vector(nx2,ny2,nz2).move_to(vertices); i2 = vertices.width() - 1; } + primitives.remove(0); + CImg::vector(p0,i0,i1).move_to(primitives); + CImg::vector((tf)i0,(tf)p1,(tf)i2).move_to(primitives); + CImg::vector((tf)i1,(tf)i2,(tf)p2).move_to(primitives); + CImg::vector((tf)i1,(tf)i0,(tf)i2).move_to(primitives); + } + } + return (vertices>'x')*=radius; + } + + //! Generate a 3D ellipsoid. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param tensor The tensor which gives the shape and size of the ellipsoid. + \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg tensor = CImg::diagonal(10,7,3), + points3d = CImg::ellipsoid3d(faces3d,tensor,4); + CImg().display_object3d("Ellipsoid3d",points3d,faces3d); + \endcode + \image html ref_ellipsoid3d.jpg + **/ + template + static CImg ellipsoid3d(CImgList& primitives, + const CImg& tensor, const unsigned int subdivisions=3) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImg S, V; + tensor.symmetric_eigen(S,V); + const float orient = + (V(0,1)*V(1,2) - V(0,2)*V(1,1))*V(2,0) + + (V(0,2)*V(1,0) - V(0,0)*V(1,2))*V(2,1) + + (V(0,0)*V(1,1) - V(0,1)*V(1,0))*V(2,2); + if (orient<0) { V(2,0) = -V(2,0); V(2,1) = -V(2,1); V(2,2) = -V(2,2); } + const float l0 = S[0], l1 = S[1], l2 = S[2]; + CImg 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. + /** + \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, + const CImgList& colors, + const to& opacities, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg& object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + 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. + CImg& object3dtoCImg3d(const bool full_check=true) { + return get_object3dtoCImg3d(full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \newinstance. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true) const { + 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).", + cimg_instance,_width,primitives._width,error_message.data()); + CImg res(1,_size_object3dtoCImg3d(primitives,colors,opacities)); + float *ptrd = res._data; + + // Put magick number. + *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f; + *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f; + + // Put number of vertices and primitives. + *(ptrd++) = cimg::uint2float(_width); + *(ptrd++) = cimg::uint2float(primitives._width); + + // Put vertex data. + if (is_empty() || !primitives) return res; + const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2); + cimg_forX(*this,p) { + *(ptrd++) = (float)*(ptrx++); + *(ptrd++) = (float)*(ptry++); + *(ptrd++) = (float)*(ptrz++); + } + + // Put primitive data. + cimglist_for(primitives,p) { + *(ptrd++) = (float)primitives[p].size(); + const tp *ptrp = primitives[p]._data; + cimg_foroff(primitives[p],i) *(ptrd++) = cimg::uint2float((unsigned int)*(ptrp++)); + } + + // Put color/texture data. + const unsigned int csiz = std::min(colors._width,primitives._width); + for (int c = 0; c<(int)csiz; ++c) { + const CImg& color = colors[c]; + const tc *ptrc = color._data; + if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; } + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (color.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImgList& opacities, float *ptrd) const { + cimglist_for(opacities,o) { + const CImg& opacity = opacities[o]; + const to *ptro = opacity._data; + if (opacity.size()==1) *(ptrd++) = (float)*ptro; + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (opacity.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImg& opacities, float *ptrd) const { + const to *ptro = opacities._data; + cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++); + return ptrd; + } + + template + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImgList& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + if (colors[c].is_shared()) siz+=4; + else { const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; } + } + if (colors._width + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImg& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; + } + if (colors._width + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) const { + CImgList opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const bool full_check=true) const { + CImgList colors, opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! 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); + cimglist_for(primitives,p) primitives(p,0) = p; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert CImg3d representation into a 3D object. + /** + \param[out] primitives Primitives data of the 3D object. + \param[out] colors Colors data of the 3D object. + \param[out] opacities Opacities data of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + **/ + template + CImg& CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) { + return get_CImg3dtoobject3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert CImg3d representation into a 3D object \newinstance. + template + CImg get_CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) const { + CImg error_message(1024); + if (!is_CImg3d(full_check,error_message)) + throw CImgInstanceException(_cimg_instance + "CImg3dtoobject3d(): image instance is not a CImg3d (%s).", + cimg_instance,error_message.data()); + const T *ptrs = _data + 6; + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + const CImg points = CImg(ptrs,3,nb_points,1,1,true).get_transpose(); + ptrs+=3*nb_points; + primitives.assign(nb_primitives); + cimglist_for(primitives,p) { + const unsigned int nb_inds = (unsigned int)*(ptrs++); + primitives[p].assign(1,nb_inds); + tp *ptrp = primitives[p]._data; + for (unsigned int i = 0; i + 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 int nx0 = x0>0?x0:0, nx1 = x1=0) { + const tc *col = color; + const ulongT off = whd - dx - 1; + T *ptrd = data(nx0,y); + if (opacity>=1) { // ** Opaque drawing ** + if (brightness==1) { // Brightness==1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)*(col++); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)*(col++); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else if (brightness<1) { // Brightness<1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else { // Brightness>1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } + } else { // ** Transparent drawing ** + if (brightness==1) { // Brightness==1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else if (brightness<=1) { // Brightness<1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*brightness*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else { // Brightness>1 + cimg_forC(*this,c) { + const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*maxval)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } + } + } + return *this; + } + + //! Draw a 3D point. + /** + \param x0 X-coordinate of the point. + \param y0 Y-coordinate of the point. + \param z0 Z-coordinate of the point. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \note + - To set pixel values without clipping needs, you should use the faster CImg::operator()() function. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_point(50,50,color); + \endcode + **/ + template + CImg& draw_point(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_point(): Specified color is (null).", + cimg_instance); + if (x0>=0 && y0>=0 && z0>=0 && x0=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + return *this; + } + + //! Draw a 2D point \simplification. + template + CImg& draw_point(const int x0, const int y0, + const tc *const color, const float opacity=1) { + return draw_point(x0,y0,0,color,opacity); + } + + // Draw a points cloud. + /** + \param points Image of vertices coordinates. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_point(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_point(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),color,opacity); + } break; + default : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),(int)points(i,2),color,opacity); + } + } + return *this; + } + + //! Draw a 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + \note + - Line routine uses Bresenham's algorithm. + - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,color); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; } + } + 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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 2D line, with z-buffering. + /** + \param zbuffer Zbuffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz && pattern&hatch) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 3D line. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + 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.f + nx1 - nx0; + ny0-=(int)((float)nx0*(1.f + ny1 - ny0)/D); + nz0-=(int)((float)nx0*(1.f + nz1 - nz0)/D); + nx0 = 0; + } + if (nx1>=width()) { + const float d = (float)nx1 - width(), D = 1.f + nx1 - nx0; + ny1+=(int)(d*(1.f + ny0 - ny1)/D); + nz1+=(int)(d*(1.f + 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.f + ny1 - ny0; + nx0-=(int)((float)ny0*(1.f + nx1 - nx0)/D); + nz0-=(int)((float)ny0*(1.f + nz1 - nz0)/D); + ny0 = 0; + } + if (ny1>=height()) { + const float d = (float)ny1 - height(), D = 1.f + ny1 - ny0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + nz1+=(int)(d*(1.f + 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.f + nz1 - nz0; + nx0-=(int)((float)nz0*(1.f + nx1 - nx0)/D); + ny0-=(int)((float)nz0*(1.f + ny1 - ny0)/D); + nz0 = 0; + } + if (nz1>=depth()) { + const float d = (float)nz1 - depth(), D = 1.f + nz1 - nz0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + ny1+=(int)(d*(1.f + 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.f); + for (unsigned int t = 0; t<=dmax; ++t) { + if (!(~pattern) || (~pattern && pattern&hatch)) { + T* ptrd = data((unsigned int)x,(unsigned int)y,(unsigned int)z); + const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } + } + } + return *this; + } + + //! Draw a textured 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + \note + - Line routine uses the well known Bresenham's algorithm. + \par Example: + \code + CImg img(100,100,1,3,0), texture("texture256x256.ppm"); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,texture,0,0,255,255); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + 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()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + if (pattern&hatch) { + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(const int x0, const int y0, const float z0, + const int x1, const 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 (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction and z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const 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_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a set of consecutive lines. + /** + \param points Coordinates of vertices, stored as a list of vectors. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If set to true, init hatch motif. + \note + - This function uses several call to the single CImg::draw_line() procedure, + depending on the vectors size in \p points. + **/ + template + 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 : + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i + CImg& draw_arrow(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const float angle=30, const float length=-10, + const unsigned int pattern=~0U) { + if (is_empty()) return *this; + const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v, + deg = (float)(angle*cimg::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.f, + l = (length>=0)?length:-length*(float)std::sqrt(sq)/100; + if (sq>0) { + const float + cl = (float)std::cos(ang - deg), sl = (float)std::sin(ang - deg), + cr = (float)std::cos(ang + deg), sr = (float)std::sin(ang + deg); + const int + xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl), + xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr), + xc = x1 + (int)((l + 1)*(cl + cr))/2, yc = y1 + (int)((l + 1)*(sl + sr))/2; + draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity); + } else draw_point(x0,y0,color,opacity); + return *this; + } + + //! Draw a 2D spline. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + \note + - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points + and corresponding velocity vectors. + - The spline is drawn as a serie of connected segments. The \p precision parameter sets the + average number of pixels in each drawn segment. + - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), + (\p xb,\p yb), (\p x1,\p y1) } where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point + and (\p xa,\p ya), (\p xb,\p yb) are two + \e control points. + The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from + the control points as + \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb). + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,255,255 }; + img.draw_spline(30,30,0,100,90,40,0,-100,color); + \endcode + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const tc *const color, const float opacity=1, + const float precision=0.25, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Specified color is (null).", + cimg_instance); + if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0; + for (float t = 0; t<1; t+=_precision) { + const float t2 = t*t, t3 = t2*t; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t + x0), + ny = (int)(ay*t3 + by*t2 + v0*t + y0); + draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; + } + return draw_line(ox,oy,x1,y1,color,opacity,pattern,false); + } + + //! Draw a 3D spline \overloading. + /** + \note + - Similar to CImg::draw_spline() for a 3D spline in a volumetric image. + **/ + template + 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. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param texture Texture image defining line pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch if \c true, reinit hatch motif. + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const CImg& texture, + const int tx0, const int ty0, const int tx1, const int ty1, + const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_empty()) return *this; + if (is_overlapped(texture)) + return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch); + if (x0==x1 && y0==y1) + return draw_point(x0,y0,texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0),opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0, otx = tx0, oty = ty0; + for (float t1 = 0; t1<1; t1+=_precision) { + const float t2 = t1*t1, t3 = t2*t1; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0), + ny = (int)(ay*t3 + by*t2 + v0*t1 + y0), + ntx = tx0 + (int)((tx1 - tx0)*t1), + nty = ty0 + (int)((ty1 - ty0)*t1); + draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; otx = ntx; oty = nty; + } + return draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false); + } + + //! Draw a set of consecutive splines. + /** + \param points Vertices data. + \param tangents Tangents data. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param is_closed_set Tells if the drawn spline set is closed. + \param precision Precision of the drawing. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + **/ + template + CImg& draw_spline(const CImg& points, const CImg& tangents, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || !tangents || points._width<2 || tangents._width<2) return *this; + bool ninit_hatch = init_hatch; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1); + int ox = x0, oy = y0; + float ou = u0, ov = v0; + for (unsigned int i = 1; i + CImg& draw_spline(const CImg& points, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || points._width<2) return *this; + CImg tangents; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + tangents.assign(points._width,points._height); + cimg_forX(points,p) { + const unsigned int + p0 = is_closed_set?(p + points._width - 1)%points._width:(p?p - 1:0), + p1 = is_closed_set?(p + 1)%points._width:(p + 1=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, + 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); + } + return *this; + } + + //! Draw a filled 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1); + return *this; + } + + //! Draw a outlined 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + draw_line(x0,y0,x1,y1,color,opacity,pattern,true). + draw_line(x1,y1,x2,y2,color,opacity,pattern,false). + draw_line(x2,y2,x0,y0,color,opacity,pattern,false); + return *this; + } + + //! Draw a filled 2D triangle, with z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param z0 Z-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param z1 Z-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param z2 Z-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param brightness Brightness factor. + **/ + template + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + } + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param brightness0 Brightness factor of the first vertex (in [0,2]). + \param brightness1 brightness factor of the second vertex (in [0,2]). + \param brightness2 brightness factor of the third vertex (in [0,2]). + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle, with z-buffering \overloading. + template + 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, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a color-interpolated 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color1 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the first vertex. + \param color2 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the seconf vertex. + \param color3 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the third vertex. + \param opacity Drawing opacity. + **/ + template + 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, + const float opacity=1) { + const unsigned char one = 1; + cimg_forC(*this,c) + get_shared_channel(c).draw_triangle(x0,y0,x1,y1,x2,y2,&one,color1[c],color2[c],color3[c],opacity); + return *this; + } + + //! Draw a textured 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param opacity Drawing opacity. + \param brightness Brightness factor of the drawing (in [0,2]). + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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 opacity=1, + const float brightness=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y, + nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + } + return *this; + } + + //! Draw a 2D textured triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int xleft = xleft0, xright = xright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xright=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval)); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle, with z-buffering. + template + 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, + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; pzl = pzn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + *ptrd = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + const T val = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param brightness0 Brightness factor of the first vertex. + \param brightness1 Brightness factor of the second vertex. + \param brightness2 Brightness factor of the third vertex. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y, + nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction \overloading. + template + 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, + 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, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright - cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc - (dx?dx*(dc/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errc = dx>>1; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + zleft-=xleft*(zright - zleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction and z-buffering \overloading. + template + 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, + 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, + 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 + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y, + nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (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 float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + zleft-=xleft*(zright - zleft)/dx; + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a filled 4D rectangle. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param z0 Z-coordinate of the upper-left rectangle corner. + \param c0 C-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param z1 Z-coordinate of the lower-right rectangle corner. + \param c1 C-coordinate of the lower-right rectangle corner. + \param val Scalar value used to fill the rectangle area. + \param opacity Drawing opacity. + **/ + CImg& draw_rectangle(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const T val, const float opacity=1) { + if (is_empty()) return *this; + const int + nx0 = x0=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.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; 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=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_rectangle(): Specified color is (null).", + cimg_instance); + cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,(T)color[c],opacity); + return *this; + } + + //! Draw an outlined 3D rectangle \overloading. + template + 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. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1) { + return draw_rectangle(x0,y0,0,x1,y1,_depth - 1,color,opacity); + } + + //! Draw a outlined 2D rectangle \overloading. + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true); + if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true); + const int + nx0 = x0 + CImg& draw_polygon(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Specified color is (null).", + cimg_instance); + if (points._width==1) return draw_point((int)points(0,0),(int)points(0,1),color,opacity); + if (points._width==2) return draw_line((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1),color,opacity); + if (points._width==3) return draw_triangle((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1), + (int)points(2,0),(int)points(2,1),color,opacity); + cimg_init_scanline(color,opacity); + int + xmin = 0, ymin = 0, + xmax = points.get_shared_row(0).max_min(xmin), + ymax = points.get_shared_row(1).max_min(ymin); + if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this; + if (ymin==ymax) return draw_line(xmin,ymin,xmax,ymax,color,opacity); + + ymin = std::max(0,ymin); + ymax = std::min(height() - 1,ymax); + CImg Xs(points._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 tn = an; + while (points(tn,1)==y1) (tn+=1)%=points._width; + + if (y0!=y1) { + const int + y2 = (int)points(tn,1), + x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1, + dy = cimg::sign(y01), + tmax = std::max(1,cimg::abs(y01)), + tend = tmax - (dy==cimg::sign(y12)); + unsigned int y = (unsigned int)y0 - ymin; + for (int t = 0; t<=tend; ++t, y+=dy) + if (yn; + n = nn; + nn = an; + } + + cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>=(cimg_openmp_sizefactor)*32)) + 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, + const tc *const color, const float opacity, const unsigned int pattern) { + if (is_empty() || !points || points._width<3) return *this; + bool ninit_hatch = true; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Invalid specified point set.", + cimg_instance); + case 2 : { // 2D version + CImg 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 + 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); + } + + //! Draw a filled 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity=1) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity); + } + + //! Draw an outlined 2D ellipse. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param r1 First radius of the ellipse. + \param r2 Second radius of the ellipse. + \param angle Angle of the first radius. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + 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); + return *this; + } + + //! Draw an outlined 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity, + const unsigned int pattern) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity,pattern); + } + + template + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_ellipse(): Specified color is (null).", + cimg_instance); + if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity); + if (r1==r2 && (float)(int)r1==r1) { + if (pattern) return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity,pattern); + else return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity); + } + cimg_init_scanline(color,opacity); + const float + nr1 = cimg::abs(r1) - 0.5, nr2 = cimg::abs(r2) - 0.5, + nangle = (float)(angle*cimg::PI/180), + u = (float)std::cos(nangle), + v = (float)std::sin(nangle), + rmax = std::max(nr1,nr2), + l1 = (float)std::pow(rmax/(nr1>0?nr1:1e-6),2), + l2 = (float)std::pow(rmax/(nr2>0?nr2:1e-6),2), + a = l1*u*u + l2*v*v, + b = u*v*(l1 - l2), + c = l1*v*v + l2*u*u; + const int + yb = (int)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 + Y = y - y0 + (y0?(float)std::sqrt(delta)/a:0.f, + 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 + 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 (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + cimg_init_scanline(color,opacity); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (y0>=0 && y0=0) { + const int x1 = x0 - x, x2 = x0 + x, y1 = y0 - y, y2 = y0 + y; + if (y1>=0 && y1=0 && y2=0 && y1=0 && y2 + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (!radius) return draw_point(x0,y0,color,opacity); + draw_point(x0 - radius,y0,color,opacity).draw_point(x0 + radius,y0,color,opacity). + draw_point(x0,y0 - radius,color,opacity).draw_point(x0,y0 + radius,color,opacity); + if (radius==1) return *this; + for (int f = 1 - radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x=0) { f+=(ddFy+=2); --y; } + ++x; ++(f+=(ddFx+=2)); + if (x!=y + 1) { + const int x1 = x0 - y, x2 = x0 + y, y1 = y0 - x, y2 = y0 + x, + x3 = x0 - x, x4 = x0 + x, y3 = y0 - y, y4 = y0 + y; + draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity). + draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity); + if (x!=y) + draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity). + draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity); + } + } + return *this; + } + + //! Draw an image. + /** + \param sprite Sprite image. + \param x0 X-coordinate of the sprite position. + \param y0 Y-coordinate of the sprite position. + \param z0 Z-coordinate of the sprite position. + \param c0 C-coordinate of the sprite position. + \param opacity Drawing opacity. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const t + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; x& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const T + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ), + slX = lX*sizeof(T); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; y + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,z0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const CImg& sprite, const float opacity=1) { + return draw_image(0,sprite,opacity); + } + + //! Draw a masked image. + /** + \param sprite Sprite image. + \param mask Mask image. + \param x0 X-coordinate of the sprite position in the image instance. + \param y0 Y-coordinate of the sprite position in the image instance. + \param z0 Z-coordinate of the sprite position in the image instance. + \param c0 C-coordinate of the sprite position in the image instance. + \param mask_max_value Maximum pixel value of the mask image \c mask. + \param opacity Drawing opacity. + \note + - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite. + - Dimensions along x,y and z of \p sprite and \p mask must be the same. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + if (is_empty() || !sprite || !mask) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_max_value); + if (is_overlapped(mask)) return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_max_value); + if (mask._width!=sprite._width || mask._height!=sprite._height || mask._depth!=sprite._depth) + throw CImgArgumentException(_cimg_instance + "draw_image(): Sprite (%u,%u,%u,%u,%p) and mask (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + sprite._width,sprite._height,sprite._depth,sprite._spectrum,sprite._data, + mask._width,mask._height,mask._depth,mask._spectrum,mask._data); + + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const ulongT + coff = (bx?-x0:0) + + (by?-y0*(ulongT)mask.width():0) + + (bz?-z0*(ulongT)mask.width()*mask.height():0) + + (bc?-c0*(ulongT)mask.width()*mask.height()*mask.depth():0), + ssize = (ulongT)mask.width()*mask.height()*mask.depth()*mask.spectrum(); + const ti *ptrs = sprite._data + coff; + const tm *ptrm = mask._data + coff; + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + if (lX>0 && lY>0 && lZ>0 && lC>0) { + T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0); + for (int c = 0; c + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw an image. + template + CImg& draw_image(const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a text string. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Pointer to \c spectrum() consecutive values, defining the foreground drawing color. + \param background_color Pointer to \c spectrum() consecutive values, defining the background drawing color. + \param opacity Drawing opacity. + \param font Font used for drawing text. + **/ + template + CImg& 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent background is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent foreground is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Array of spectrum() values of type \c T, + defining the foreground color (0 means 'transparent'). + \param background_color Array of spectrum() values of type \c T, + defining the background color (0 means 'transparent'). + \param opacity Drawing opacity. + \param font_height Height of the text font (exact match for 13,23,53,103, interpolated otherwise). + **/ + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc1 *const foreground_color, const tc2 *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + 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; + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc *const foreground_color, const int background_color=0, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + cimg::unused(background_color); + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",foreground_color,(const tc*)0,opacity,font_height,tmp._data); + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const int, const tc *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",(tc*)0,background_color,opacity,font_height,tmp._data); + } + + template + CImg& _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, + const bool is_native_font) { + if (!text) return *this; + if (!font) + throw CImgArgumentException(_cimg_instance + "draw_text(): Empty specified font.", + cimg_instance); + + const unsigned int text_length = (unsigned int)std::strlen(text); + if (is_empty()) { + // If needed, pre-compute necessary size of the image + int x = 0, y = 0, w = 0; + unsigned char c = 0; + for (unsigned int i = 0; iw) w = x; x = 0; break; + case '\t' : x+=4*font[' ']._width; break; + default : if (cw) w=x; + y+=font[0]._height; + } + assign(x0 + w,y0 + y,1,is_native_font?1:font[0]._spectrum,(T)0); + } + + int x = x0, y = y0; + for (unsigned int i = 0; i letter = font[c]; + if (letter) { + if (is_native_font && _spectrum>letter._spectrum) letter.resize(-100,-100,1,_spectrum,0,2); + const unsigned int cmin = std::min(_spectrum,letter._spectrum); + if (foreground_color) + for (unsigned int c = 0; c& __draw_text(const char *const text, const bool 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 label, labelmask; + const unsigned char labelcolor = 127; + const unsigned int fsize = 13; + label.draw_text(0,0,"%s",&labelcolor,0,1,fsize,tmp._data); + if (label) { + label.crop(2,0,label.width() - 1,label.height()); + ((labelmask = label)+=label.get_dilate(5)).max(80); + (label*=2).resize(-100,-100,1,3,1); + return draw_image(0,is_down?height() - fsize:0,label,labelmask,1,254); + } + return *this; + } + + //! Draw a 2D vector field. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Drawing opacity. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const t2 *const color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + return draw_quiver(flow,CImg(color,_spectrum,1,1,1,true),opacity,sampling,factor,is_arrow,pattern); + } + + //! Draw a 2D vector field, using a field of colors. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Opacity of the drawing. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const CImg& color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + if (is_empty()) return *this; + if (!flow || flow._spectrum!=2) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid dimensions of specified flow (%u,%u,%u,%u,%p).", + cimg_instance, + flow._width,flow._height,flow._depth,flow._spectrum,flow._data); + if (sampling<=0) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid sampling value %g " + "(should be >0)", + cimg_instance, + sampling); + const bool colorfield = (color._width==flow._width && color._height==flow._height && + color._depth==1 && color._spectrum==_spectrum); + if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,is_arrow,pattern); + float vmax,fact; + if (factor<=0) { + float m, M = (float)flow.get_norm(2).max_min(m); + vmax = (float)std::max(cimg::abs(m),cimg::abs(M)); + if (!vmax) vmax = 1; + fact = -factor; + } else { fact = factor; vmax = 1; } + + for (unsigned int y = sampling/2; y<_height; y+=sampling) + for (unsigned int x = sampling/2; x<_width; x+=sampling) { + const unsigned int X = x*flow._width/_width, Y = y*flow._height/_height; + float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax; + if (is_arrow) { + const int xx = (int)(x + u), yy = (int)(y + v); + if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y)._data,opacity,45,sampling/5.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), + color.get_vector_at(X,Y)._data,opacity,pattern); + else draw_line((int)(x - 0.5*u),(int)(y - 0.5*v),(int)(x + 0.5*u),(int)(y + 0.5*v), + color._data,opacity,pattern); + } + } + return *this; + } + + //! Draw a labeled horizontal axis. + /** + \param values_x Values along the horizontal axis. + \param y Y-coordinate of the horizontal axis in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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 + draw_line(0,y,_width - 1,y,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_x); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _xt = (width() - label.width())/2, + xt = _xt<3?3:_xt + label.width()>=width() - 2?width() - 3 - label.width():_xt; + draw_point(width()/2,y - 1,color,opacity).draw_point(width()/2,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_x[0]=width() - 2?width() - 3 - label.width():_xt; + draw_point(xi,y - 1,color,opacity).draw_point(xi,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw a labeled vertical axis. + /** + \param x X-coordinate of the vertical axis in the image instance. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + if (is_empty()) return *this; + int siz = (int)values_y.size() - 1; + CImg txt(32); + CImg label; + if (siz<=0) { // Degenerated case + draw_line(x,0,x,_height - 1,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_y); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _yt = (height() - label.height())/2, + yt = _yt<0?0:_yt + label.height()>=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,height()/2,color,opacity).draw_point(x + 1,height()/2,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_y[0]=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,yi,color,opacity).draw_point(x + 1,yi,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes. + /** + \param values_x Values along the X-axis. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for the X-axis. + \param pattern_y Drawing pattern for the Y-axis. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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; + if (sizx>=0) { + float ox = (float)*nvalues_x; + for (unsigned int x = sizx?1U:0U; x<_width; ++x) { + const float nx = (float)nvalues_x._linear_atX((float)x*sizx/wm1); + if (nx*ox<=0) { draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero); break; } + ox = nx; + } + } + const CImg nvalues_y(values_y._data,values_y.size(),1,1,1,true); + const int sizy = (int)values_y.size() - 1, hm1 = height() - 1; + if (sizy>0) { + float oy = (float)nvalues_y[0]; + for (unsigned int y = sizy?1U:0U; y<_height; ++y) { + const float ny = (float)nvalues_y._linear_atX((float)y*sizy/hm1); + if (ny*oy<=0) { draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero); break; } + oy = ny; + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes \overloading. + template + CImg& draw_axes(const float x0, const float x1, const float y0, const float y1, + const tc *const color, const float opacity=1, + const int subdivisionx=-60, const int subdivisiony=-60, + const float precisionx=0, const float precisiony=0, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U, + const unsigned int font_height=13) { + if (is_empty()) return *this; + const bool allow_zero = (x0*x1>0) || (y0*y1>0); + const float + dx = cimg::abs(x1 - x0), dy = cimg::abs(y1 - y0), + px = dx<=0?1:precisionx==0?(float)std::pow(10.,(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); + 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); + 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); + return *this; + } + + //! Draw 2D grid. + /** + \param values_x X-coordinates of the vertical lines. + \param values_y Y-coordinates of the horizontal lines. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for vertical lines. + \param pattern_y Drawing pattern for horizontal lines. + **/ + template + CImg& draw_grid(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) { + if (is_empty()) return *this; + if (values_x) cimg_foroff(values_x,x) { + const int xi = (int)values_x[x]; + if (xi>=0 && xi=0 && yi + CImg& draw_grid(const float delta_x, const float delta_y, + const float offsetx, const float offsety, + const bool invertx, const bool inverty, + const tc *const color, const float opacity=1, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) { + if (is_empty()) return *this; + CImg seqx, seqy; + if (delta_x!=0) { + const float dx = delta_x>0?delta_x:_width*-delta_x/100; + const unsigned int nx = (unsigned int)(_width/dx); + seqx = CImg::sequence(1 + nx,0,(unsigned int)(dx*nx)); + if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x) + offsetx,(float)_width); + if (invertx) cimg_foroff(seqx,x) seqx(x) = _width - 1 - seqx(x); + } + if (delta_y!=0) { + const float dy = delta_y>0?delta_y:_height*-delta_y/100; + const unsigned int ny = (unsigned int)(_height/dy); + seqy = CImg::sequence(1 + ny,0,(unsigned int)(dy*ny)); + if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y) + offsety,(float)_height); + if (inverty) cimg_foroff(seqy,y) seqy(y) = _height - 1 - seqy(y); + } + return draw_grid(seqx,seqy,color,opacity,pattern_x,pattern_y); + } + + //! Draw 1D graph. + /** + \param data Image containing the graph values I = f(x). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + + \param plot_type Define the type of the plot: + - 0 = No plot. + - 1 = Plot using segments. + - 2 = Plot using cubic splines. + - 3 = Plot with bars. + \param vertex_type Define the type of points: + - 0 = No points. + - 1 = Point. + - 2 = Straight cross. + - 3 = Diagonal cross. + - 4 = Filled circle. + - 5 = Outlined circle. + - 6 = Square. + - 7 = Diamond. + \param ymin Lower bound of the y-range. + \param ymax Upper bound of the y-range. + \param pattern Drawing pattern. + \note + - if \c ymin==ymax==0, the y-range is computed automatically from the input samples. + **/ + template + CImg& draw_graph(const CImg& data, + const tc *const color, const float opacity=1, + const unsigned int plot_type=1, const int vertex_type=1, + const double ymin=0, const double ymax=0, const unsigned int pattern=~0U) { + if (is_empty() || _height<=1) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_graph(): Specified color is (null).", + cimg_instance); + + // Create shaded colors for displaying bar plots. + CImg color1, color2; + if (plot_type==3) { + color1.assign(_spectrum); color2.assign(_spectrum); + cimg_forC(*this,c) { + color1[c] = (tc)std::min((float)cimg::type::max(),(float)color[c]*1.2f); + color2[c] = (tc)(color[c]*0.4f); + } + } + + // Compute min/max and normalization factors. + const ulongT + siz = data.size(), + _siz1 = siz - (plot_type!=3), + siz1 = _siz1?_siz1:1; + const unsigned int + _width1 = _width - (plot_type!=3), + width1 = _width1?_width1:1; + double m = ymin, M = ymax; + if (ymin==ymax) m = (double)data.max_min(M); + if (m==M) { --m; ++M; } + const float ca = (float)(M-m)/(_height - 1); + bool init_hatch = true; + + // Draw graph edges + switch (plot_type%4) { + case 1 : { // Segments + int oX = 0, oY = (int)((data[0] - m)/ca); + if (siz==1) { + const int Y = (int)((*data - m)/ca); + draw_line(0,Y,width() - 1,Y,color,opacity,pattern); + } else { + const float fx = (float)_width/siz1; + for (ulongT off = 1; off ndata(data._data,siz,1,1,1,true); + int oY = (int)((data[0] - m)/ca); + cimg_forX(*this,x) { + const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-m)/ca); + if (x>0) draw_line(x,oY,x + 1,Y,color,opacity,pattern,init_hatch); + init_hatch = false; + oY = Y; + } + } break; + case 3 : { // Bars + const int Y0 = (int)(-m/ca); + const float fx = (float)_width/siz1; + int oX = 0; + cimg_foroff(data,off) { + const int + X = (int)((off + 1)*fx) - 1, + Y = (int)((data[off] - m)/ca); + draw_rectangle(oX,Y0,X,Y,color,opacity). + draw_line(oX,Y,oX,Y0,color2.data(),opacity). + draw_line(oX,Y0,X,Y0,Y<=Y0?color2.data():color1.data(),opacity). + draw_line(X,Y,X,Y0,color1.data(),opacity). + draw_line(oX,Y,X,Y,Y<=Y0?color1.data():color2.data(),opacity); + oX = X + 1; + } + } break; + default : break; // No edges + } + + // Draw graph points + const unsigned int wb2 = plot_type==3?_width1/(2*siz):0; + const float fx = (float)_width1/siz1; + switch (vertex_type%8) { + case 1 : { // Point + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_point(X,Y,color,opacity); + } + } break; + case 2 : { // Straight Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y,X + 3,Y,color,opacity).draw_line(X,Y - 3,X,Y + 3,color,opacity); + } + } break; + case 3 : { // Diagonal Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y - 3,X + 3,Y + 3,color,opacity).draw_line(X - 3,Y + 3,X + 3,Y - 3,color,opacity); + } + } break; + case 4 : { // Filled Circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity); + } + } break; + case 5 : { // Outlined circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity,0U); + } + } break; + case 6 : { // Square + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_rectangle(X - 3,Y - 3,X + 3,Y + 3,color,opacity,~0U); + } + } break; + case 7 : { // Diamond + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X,Y - 4,X + 4,Y,color,opacity). + draw_line(X + 4,Y,X,Y + 4,color,opacity). + draw_line(X,Y + 4,X - 4,Y,color,opacity). + draw_line(X - 4,Y,X,Y - 4,color,opacity); + } + } break; + default : break; // No points + } + return *this; + } + + bool _draw_fill(const int x, const int y, const int z, + const CImg& ref, const float tolerance2) const { + const T *ptr1 = data(x,y,z), *ptr2 = ref._data; + const unsigned long off = _width*_height*_depth; + float diff = 0; + cimg_forC(*this,c) { diff += cimg::sqr(*ptr1 - *(ptr2++)); ptr1+=off; } + return diff<=tolerance2; + } + + //! Draw filled 3D region with the flood fill algorithm. + /** + \param x0 X-coordinate of the starting point of the region to fill. + \param y0 Y-coordinate of the starting point of the region to fill. + \param z0 Z-coordinate of the starting point of the region to fill. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param[out] region Image that will contain the mask of the filled region mask, as an output. + \param tolerance Tolerance concerning neighborhood values. + \param opacity Opacity of the drawing. + \param is_high_connectivity Tells if 8-connexity must be used. + \return \c region is initialized with the binary mask of the filled region. + **/ + template + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity, + CImg ®ion, + const float tolerance = 0, + const bool is_high_connectivity = false) { +#define _draw_fill_push(x,y,z) if (N>=stack._width) stack.resize(2*N + 1,1,1,3,0); \ + stack[N] = x; stack(N,1) = y; stack(N++,2) = z +#define _draw_fill_pop(x,y,z) x = stack[--N]; y = stack(N,1); z = stack(N,2) +#define _draw_fill_is_inside(x,y,z) !_region(x,y,z) && _draw_fill(x,y,z,ref,tolerance2) + + if (!containsXYZC(x0,y0,z0,0)) return *this; + const float nopacity = cimg::abs((float)opacity), copacity = 1 - std::max((float)opacity,0.f); + const float tolerance2 = cimg::sqr(tolerance); + const CImg ref = get_vector_at(x0,y0,z0); + CImg stack(256,1,1,3); + CImg _region(_width,_height,_depth,1,0); + unsigned int N = 0; + int x, y, z; + + _draw_fill_push(x0,y0,z0); + while (N>0) { + _draw_fill_pop(x,y,z); + if (!_region(x,y,z)) { + const int yp = y - 1, yn = y + 1, zp = z - 1, zn = z + 1; + int xl = x, xr = x; + + // Using these booleans reduces the number of pushes drastically. + bool is_yp = false, is_yn = false, is_zp = false, is_zn = false; + for (int step = -1; step<2; step+=2) { + while (x>=0 && x=0 && _draw_fill_is_inside(x,yp,z)) { + if (!is_yp) { _draw_fill_push(x,yp,z); is_yp = true; } + } else is_yp = false; + if (yn1) { + if (zp>=0 && _draw_fill_is_inside(x,y,zp)) { + if (!is_zp) { _draw_fill_push(x,y,zp); is_zp = true; } + } else is_zp = false; + if (zn=0 && !is_yp) { + if (xp>=0 && _draw_fill_is_inside(xp,yp,z)) { + _draw_fill_push(xp,yp,z); if (step<0) is_yp = true; + } + if (xn0) is_yp = true; + } + } + if (yn=0 && _draw_fill_is_inside(xp,yn,z)) { + _draw_fill_push(xp,yn,z); if (step<0) is_yn = true; + } + if (xn0) is_yn = true; + } + } + if (depth()>1) { + if (zp>=0 && !is_zp) { + if (xp>=0 && _draw_fill_is_inside(xp,y,zp)) { + _draw_fill_push(xp,y,zp); if (step<0) is_zp = true; + } + if (xn0) is_zp = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zp)) { _draw_fill_push(x,yp,zp); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zp)) { _draw_fill_push(xp,yp,zp); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zp)) { _draw_fill_push(xp,yn,zp); } + if (xn=0 && _draw_fill_is_inside(xp,y,zn)) { + _draw_fill_push(xp,y,zn); if (step<0) is_zn = true; + } + if (xn0) is_zn = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zn)) { _draw_fill_push(x,yp,zn); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zn)) { _draw_fill_push(xp,yp,zn); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zn)) { _draw_fill_push(xp,yn,zn); } + if (xn + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,z0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw filled 2D region with the flood fill algorithm \simplification. + template + CImg& draw_fill(const int x0, const int y0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw a random plasma texture. + /** + \param alpha Alpha-parameter. + \param beta Beta-parameter. + \param scale Scale-parameter. + \note Use the mid-point algorithm to render. + **/ + CImg& draw_plasma(const float alpha=1, const float beta=0, const unsigned int scale=8) { + if (is_empty()) return *this; + const int w = width(), h = height(); + const Tfloat m = (Tfloat)cimg::type::min(), M = (Tfloat)cimg::type::max(); + ulongT rng = (cimg::_rand(),cimg::rng()); + cimg_forZC(*this,z,c) { + CImg ref = get_shared_slice(z,c); + for (int delta = 1<1; delta>>=1) { + const int delta2 = delta>>1; + const float r = alpha*delta + beta; + + // Square step. + for (int y0 = 0; y0M?M:val); + } + + // Diamond steps. + for (int y = -delta2; yM?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. + /** + \param x0 X-coordinate of the upper-left pixel. + \param y0 Y-coordinate of the upper-left pixel. + \param x1 X-coordinate of the lower-right pixel. + \param y1 Y-coordinate of the lower-right pixel. + \param colormap Colormap. + \param opacity Drawing opacity. + \param z0r Real part of the upper-left fractal vertex. + \param z0i Imaginary part of the upper-left fractal vertex. + \param z1r Real part of the lower-right fractal vertex. + \param z1i Imaginary part of the lower-right fractal vertex. + \param iteration_max Maximum number of iterations for each estimated point. + \param is_normalized_iteration Tells if iterations are normalized. + \param is_julia_set Tells if the Mandelbrot or Julia set is rendered. + \param param_r Real part of the Julia set parameter. + \param param_i Imaginary part of the Julia set parameter. + \note Fractal rendering is done by the Escape Time Algorithm. + **/ + template + CImg& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1, + const CImg& colormap, const float opacity=1, + const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + if (is_empty()) return *this; + CImg palette; + if (colormap) palette.assign(colormap._data,colormap.size()/colormap._spectrum,1,1,colormap._spectrum,true); + if (palette && palette._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_mandelbrot(): Instance and specified colormap (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.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 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; + const double x = z0r + p*(z1r-z0r)/_width, y = z0i + q*(z1i-z0i)/_height; + double zr, zi, cr, ci; + if (is_julia_set) { zr = x; zi = y; cr = param_r; ci = param_i; } + else { zr = param_r; zi = param_i; cr = x; ci = y; } + for (iteration=1; zr*zr + zi*zi<=4 && iteration<=iteration_max; ++iteration) { + const double temp = zr*zr - zi*zi + cr; + zi = 2*zr*zi + ci; + zr = temp; + } + if (iteration>iteration_max) { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette(0,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(0,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)0; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)((*this)(p,q,0,c)*copacity); + } + } else if (is_normalized_iteration) { + const float + normz = (float)cimg::abs(zr*zr + zi*zi), + niteration = (float)(iteration + 1 - std::log(std::log(normz))/ln2); + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._linear_atX(niteration,c); + else cimg_forC(*this,c) + (*this)(p,q,0,c) = (T)(palette._linear_atX(niteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)niteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(niteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } else { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._atX(iteration,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(iteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)iteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(iteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } + } + return *this; + } + + //! Draw a quadratic Mandelbrot or Julia 2D fractal \overloading. + template + 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, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + return draw_mandelbrot(0,0,_width - 1,_height - 1,colormap,opacity, + z0r,z0i,z1r,z1i,iteration_max,is_normalized_iteration,is_julia_set,param_r,param_i); + } + + //! Draw a 1D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param sigma Standard variation of the gaussian distribution. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float sigma, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forX(*this,x) { + const float dx = (x - xc), val = (float)std::exp(-dx*dx/sigma2); + T *ptrd = data(x,0,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 2D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param yc Y-coordinate of the gaussian center. + \param tensor Covariance matrix (must be 2x2). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + typedef typename CImg::Tfloat tfloat; + 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + float dy = -yc; + cimg_forY(*this,y) { + float dx = -xc; + cimg_forX(*this,x) { + const float val = (float)std::exp(a*dx*dx + b*dx*dy + c*dy*dy); + T *ptrd = data(x,y,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + ++dx; + } + ++dy; + } + return *this; + } + + //! Draw a 2D gaussian function \overloading. + template + 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) { + const double + a = r1*ru*ru + r2*rv*rv, + b = (r1-r2)*ru*rv, + c = r1*rv*rv + r2*ru*ru; + const CImg tensor(2,2,1,1, a,b,b,c); + return draw_gaussian(xc,yc,tensor,color,opacity); + } + + //! 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. + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + typedef typename CImg::Tfloat tfloat; + if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + + const CImg 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forXYZ(*this,x,y,z) { + const float + dx = (x - xc), dy = (y - yc), dz = (z - zc), + val = (float)std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz); + T *ptrd = data(x,y,z,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 3D gaussian function \overloading. + template + 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. + /** + \param x0 X-coordinate of the 3D object position + \param y0 Y-coordinate of the 3D object position + \param z0 Z-coordinate of the 3D object position + \param vertices Image Nx3 describing 3D point coordinates + \param primitives List of P primitives + \param colors List of P color (or textures) + \param opacities Image or list of P opacities + \param render_type d Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud) + \param is_double_sided Tells if object faces have two sides or are oriented. + \param focale length of the focale (0 for parallel projection) + \param lightx X-coordinate of the light + \param lighty Y-coordinate of the light + \param lightz Z-coordinate of the light + \param specular_lightness Amount of specular light. + \param specular_shininess Shininess of the object + \param g_opacity Global opacity of the object. + **/ + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } +#endif + + template + static float __draw_object3d(const CImgList& opacities, const unsigned int n_primitive, CImg& opacity) { + if (n_primitive>=opacities._width || opacities[n_primitive].is_empty()) { opacity.assign(); return 1; } + if (opacities[n_primitive].size()==1) { opacity.assign(); return opacities(n_primitive,0); } + opacity.assign(opacities[n_primitive],true); + return 1.f; + } + + template + static float __draw_object3d(const CImg& opacities, const unsigned int n_primitive, CImg& opacity) { + opacity.assign(); + 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 + CImg& _draw_object3d(void *const pboard, CImg& zbuffer, + const float X, const float Y, const float Z, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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).", + cimg_instance,vertices._width,primitives._width,error_message.data()); +#ifndef cimg_use_board + if (pboard) return *this; +#endif + if (render_type==5) cimg::mutex(10); // Static variable used in this case, breaks thread-safety + + const float + nspec = 1 - (specular_lightness<0.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; + + // Create light texture for phong-like rendering. + CImg light_texture; + if (render_type==5) { + if (colors._width>primitives._width) { + static CImg default_light_texture; + static const tc *lptr = 0; + static tc ref_values[64] = { 0 }; + const CImg& img = colors.back(); + bool is_same_texture = (lptr==img._data); + if (is_same_texture) + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + if (ref_values[r++]!=img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum)) { + is_same_texture = false; break; + } + if (!is_same_texture || default_light_texture._spectrum<_spectrum) { + (default_light_texture.assign(img,false)/=255).resize(-100,-100,1,_spectrum); + lptr = colors.back().data(); + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + ref_values[r++] = img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum); + } + light_texture.assign(default_light_texture,true); + } else { + static CImg default_light_texture; + static float olightx = 0, olighty = 0, olightz = 0, ospecular_shininess = 0; + if (!default_light_texture || + lightx!=olightx || lighty!=olighty || lightz!=olightz || + specular_shininess!=ospecular_shininess || default_light_texture._spectrum<_spectrum) { + default_light_texture.assign(512,512); + const float + dlx = lightx - X, + dly = lighty - Y, + dlz = lightz - Z, + nl = cimg::hypot(dlx,dly,dlz), + nlx = (default_light_texture._width - 1)/2*(1 + dlx/nl), + nly = (default_light_texture._height - 1)/2*(1 + dly/nl), + white[] = { 1 }; + default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.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.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; + } + light_texture.assign(default_light_texture,true); + } + } + + // 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_size(projections.size(),4096)) + cimg_forX(projections,l) { // Perspective projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + const tpfloat projectedz = z + Z + absfocale; + projections(l,1) = Y + absfocale*y/projectedz; + projections(l,0) = X + absfocale*x/projectedz; + } + } else { + cimg_pragma_openmp(parallel for cimg_openmp_if_size(projections.size(),4096)) + cimg_forX(projections,l) { // Parallel projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + if (z visibles(primitives._width,1,1,1,~0U); + CImg zrange(primitives._width); + 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_size(primitives.size(),4096)) + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + switch (primitive.size()) { + case 1 : { // Point + CImg<_to> _opacity; + __draw_object3d(opacities,l,_opacity); + if (l<=colors.width() && (colors[l].size()!=_spectrum || _opacity)) is_forward = false; + const unsigned int i0 = (unsigned int)primitive(0); + const tpfloat z0 = Z + vertices(i0,2); + if (z0>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = z0; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + Xc = 0.5f*((float)vertices(i0,0) + (float)vertices(i1,0)), + Yc = 0.5f*((float)vertices(i0,1) + (float)vertices(i1,1)), + Zc = 0.5f*((float)vertices(i0,2) + (float)vertices(i1,2)), + _zc = Z + Zc, + zc = _zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(i1,0) - vertices(i0,0), + vertices(i1,1) - vertices(i0,1), + vertices(i1,2) - vertices(i0,2))*(absfocale?absfocale/zc:1), + xm = xc - radius, + ym = yc - radius, + xM = xc + radius, + yM = yc + radius; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && _zc>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = _zc; + } + is_forward = false; + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2); + tpfloat xm, xM, ym, yM; + if (x0=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1)/2; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (y0yM) yM = y2; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) { + const tpfloat d = (x1-x0)*(y2-y0) - (x2-x0)*(y1-y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2)/3; + } + } + } break; + case 4 : 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); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2), + x3 = projections(i3,0), y3 = projections(i3,1), z3 = Z + vertices(i3,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (x3xM) xM = x3; + if (y0yM) yM = y2; + if (y3yM) yM = y3; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin && z3>zmin) { + const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2 + z3)/4; + } + } + } break; + default : + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Invalid primitive[%u] with size %u " + "(should have size 1,2,3,4,5,6,9 or 12).", + cimg_instance, + l,primitive.size()); + } + } + + // Force transparent primitives to be drawn last when zbuffer is activated + // (and if object contains no spheres or sprites). + if (is_forward) + cimglist_for(primitives,l) + if (___draw_object3d(opacities,l)!=1) zrange(l) = 2*zmax - zrange(l); + + // Sort only visibles primitives. + unsigned int *p_visibles = visibles._data; + tpfloat *p_zrange = zrange._data; + const tpfloat *ptrz = p_zrange; + cimg_for(visibles,ptr,unsigned int) { + if (*ptr!=~0U) { *(p_visibles++) = *ptr; *(p_zrange++) = *ptrz; } + ++ptrz; + } + const unsigned int nb_visibles = (unsigned int)(p_zrange - zrange._data); + if (!nb_visibles) { + if (render_type==5) cimg::mutex(10,0); + return *this; + } + CImg permutations; + CImg(zrange._data,nb_visibles,1,1,1,true).sort(permutations,is_forward); + + // Compute light properties + CImg lightprops; + switch (render_type) { + case 3 : { // Flat Shading + lightprops.assign(nb_visibles); + 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(); + if (psize==3 || psize==4 || psize==9 || psize==12) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nx = dy1*dz2 - dz1*dy2, + ny = dz1*dx2 - dx1*dz2, + nz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + (x0 + x1 + x2)/3 - lightx, + ly = Y + (y0 + y1 + y2)/3 - lighty, + lz = Z + (z0 + z1 + z2)/3 - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max(cimg::abs(-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } else lightprops[l] = 1; + } + } break; + + case 4 : // Gouraud Shading + case 5 : { // Phong-Shading + CImg vertices_normals(vertices._width,6,1,1,0); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + for (int l = 0; l<(int)nb_visibles; ++l) { + const CImg& primitive = primitives[visibles(l)]; + const unsigned int psize = (unsigned int)primitive.size(); + const bool + triangle_flag = (psize==3) || (psize==9), + quadrangle_flag = (psize==4) || (psize==12); + if (triangle_flag || quadrangle_flag) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2), + i3 = quadrangle_flag?(unsigned int)primitive(3):0; + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nnx = dy1*dz2 - dz1*dy2, + nny = dz1*dx2 - dx1*dz2, + nnz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nnx,nny,nnz), + nx = nnx/norm, + ny = nny/norm, + nz = nnz/norm; + unsigned int ix = 0, iy = 1, iz = 2; + if (is_double_sided && nz>0) { ix = 3; iy = 4; iz = 5; } + vertices_normals(i0,ix)+=nx; vertices_normals(i0,iy)+=ny; vertices_normals(i0,iz)+=nz; + vertices_normals(i1,ix)+=nx; vertices_normals(i1,iy)+=ny; vertices_normals(i1,iz)+=nz; + vertices_normals(i2,ix)+=nx; vertices_normals(i2,iy)+=ny; vertices_normals(i2,iz)+=nz; + if (quadrangle_flag) { + vertices_normals(i3,ix)+=nx; vertices_normals(i3,iy)+=ny; vertices_normals(i3,iz)+=nz; + } + } + } + + if (is_double_sided) cimg_forX(vertices_normals,p) { + const float + nx0 = vertices_normals(p,0), ny0 = vertices_normals(p,1), nz0 = vertices_normals(p,2), + nx1 = vertices_normals(p,3), ny1 = vertices_normals(p,4), nz1 = vertices_normals(p,5), + n0 = nx0*nx0 + ny0*ny0 + nz0*nz0, n1 = nx1*nx1 + ny1*ny1 + nz1*nz1; + if (n1>n0) { + vertices_normals(p,0) = -nx1; + vertices_normals(p,1) = -ny1; + vertices_normals(p,2) = -nz1; + } + } + + if (render_type==4) { + lightprops.assign(vertices._width); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + vertices(l,0) - lightx, + ly = Y + vertices(l,1) - lighty, + lz = Z + vertices(l,2) - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max((-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } + } else { + const unsigned int + lw2 = light_texture._width/2 - 1, + lh2 = light_texture._height/2 - 1; + lightprops.assign(vertices._width,2); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + nnx = nx/norm, + nny = ny/norm; + lightprops(l,0) = lw2*(1 + nnx); + lightprops(l,1) = lh2*(1 + nny); + } + } + } break; + } + + // Draw visible primitives + const CImg default_color(1,_spectrum,1,1,(tc)200); + CImg<_to> _opacity; + + for (unsigned int l = 0; l& primitive = primitives[n_primitive]; + const CImg + &__color = n_primitive(), + _color = (__color && __color.size()!=_spectrum && __color._spectrum<_spectrum)? + __color.get_resize(-100,-100,-100,_spectrum,0):CImg(), + &color = _color?_color:(__color?__color:default_color); + const tc *const pcolor = color._data; + float opacity = __draw_object3d(opacities,n_primitive,_opacity); + if (_opacity.is_empty()) opacity*=g_opacity; + else if (!_opacity.is_shared()) _opacity*=g_opacity; + +#ifdef cimg_use_board + LibBoard::Board &board = *(LibBoard::Board*)pboard; +#endif + + switch (primitive.size()) { + case 1 : { // Colored point or sprite + const unsigned int n0 = (unsigned int)primitive[0]; + const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1); + + if (_opacity.is_empty()) { // Scalar opacity + + if (color.size()==_spectrum) { // Colored point + draw_point(x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height()-(float)y0); + } +#endif + } else { // Sprite + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(color._width*factor), + _sh = (unsigned int)(color._height*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + 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); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } else { // Opacity mask + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(std::max(color._width,_opacity._width)*factor), + _sh = (unsigned int)(std::max(color._height,_opacity._height)*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg(), + &sprite = _sprite?_sprite:color; + const CImg<_to> + _nopacity = (sw!=_opacity._width || sh!=_opacity._height)? + _opacity.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<_to>(), + &nopacity = _nopacity?_nopacity:_opacity; + draw_image(nx0,ny0,sprite,nopacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } break; + case 2 : { // Colored line + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity); + else draw_line(x0,y0,x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 5 : { // Colored sphere + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + is_wireframe = (unsigned int)primitive[2]; + const float + Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)), + Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)), + Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)), + zc = Z + Zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(n1,0) - vertices(n0,0), + vertices(n1,1) - vertices(n0,1), + vertices(n1,2) - vertices(n0,2))*(absfocale?absfocale/zc:1); + switch (render_type) { + case 0 : + draw_point((int)xc,(int)yc,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot(xc,height() - yc); + } +#endif + break; + case 1 : + draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } +#endif + break; + default : + if (is_wireframe) draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); + else draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + if (!is_wireframe) board.fillCircle(xc,height() - yc,radius); + else { + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } + } +#endif + break; + } + } break; + case 6 : { // Textured line + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for line primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + tx0 = (int)primitive[2], ty0 = (int)primitive[3], + tx1 = (int)primitive[4], ty1 = (int)primitive[5], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity); + else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 3 : { // Colored triangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity).draw_point(x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x0,y0,x2,y2,pcolor,opacity). + draw_line(x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)); + else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + } break; + } + } break; + case 4 : { // Colored quadrangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1), + x3 = (int)projections(n3,0), y3 = (int)projections(n3,1), + xc = (x0 + x1 + x2 + x3)/4, yc = (y0 + y1 + y2 + y3)/4; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity). + draw_point(x2,y2,pcolor,opacity).draw_point(x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opacity).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x1,y1,x2,y2,pcolor,opacity). + draw_line(x2,y2,x3,y3,pcolor,opacity).draw_line(x3,y3,x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity); + else + draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity,lightprops(l)); + else + _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)). + _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp),(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop2)/4; + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,xc,yc,zc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,light_texture,lx0,ly0,lx1,ly1,lxc,lyc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + case 9 : { // Textured triangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for triangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + tx0 = (int)primitive[3], ty0 = (int)primitive[4], + tx1 = (int)primitive[5], ty1 = (int)primitive[6], + tx2 = (int)primitive[7], ty2 = (int)primitive[8], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + break; + } + } break; + case 12 : { // Textured quadrangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for quadrangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + tx0 = (int)primitive[4], ty0 = (int)primitive[5], + tx1 = (int)primitive[6], ty1 = (int)primitive[7], + tx2 = (int)primitive[8], ty2 = (int)primitive[9], + tx3 = (int)primitive[10], ty3 = (int)primitive[11], + 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; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity). + draw_point(x3,y3,color.get_vector_at(tx3<=0?0:tx3>=color.width()?color.width() - 1:tx3, + ty3<=0?0:ty3>=color.height()?color.height() - 1:ty3)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop3)/4; + 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); + 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); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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,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); + 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); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + } + } + if (render_type==5) cimg::mutex(10,0); + return *this; + } + + //@} + //--------------------------- + // + //! \name Data Input + //@{ + //--------------------------- + + //! Launch simple interface to select a shape from an image. + /** + \param disp Display window to use. + \param feature_type Type of feature to select. Can be { 0=point | 1=line | 2=rectangle | 3=ellipse }. + \param XYZ Pointer to 3 values X,Y,Z which tells about the projection point coordinates, for volumetric images. + \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 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); + } + + //! 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 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); + } + + //! 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 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); + } + + //! 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 bool exit_on_anykey=false, + const bool is_deep_selection_default=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,XYZ,0,0,0,exit_on_anykey,true,false,is_deep_selection_default); + } + + CImg _select(CImgDisplay &disp, const char *const title, + const unsigned int feature_type, unsigned int *const XYZ, + const int origX, const int origY, const int origZ, + const bool exit_on_anykey, + const bool reset_view3d, + const bool force_display_z_coord, + const bool is_deep_selection_default) const { + if (is_empty()) return CImg(1,feature_type==0?3:6,1,1,-1); + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + } else { + if (title) disp.set_title("%s",title); + disp.move_inside_screen(); + } + + CImg thumb; + if (width()>disp.screen_width() || height()>disp.screen_height()) + get_resize(cimg_fitscreen(width(),height(),depth()),depth(),-100).move_to(thumb); + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0).set_wheel().show_mouse(); + + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + int area = 0, 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), + 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; + + bool is_deep_selection = is_deep_selection_default, + shape_selected = false, text_down = false, visible_cursor = true; + static CImg pose3d; + static bool is_view3d = false, is_axes = true; + if (reset_view3d) { pose3d.assign(); is_view3d = false; } + CImg points3d, opacities3d, sel_opacities3d; + CImgList primitives3d, sel_primitives3d; + CImgList colors3d, sel_colors3d; + CImg visu, visu0, view3d; + CImg text(1024); *text = 0; + + while (!key && !disp.is_closed() && !shape_selected) { + + // Handle mouse motion and selection + int + mx = disp.mouse_x(), + my = disp.mouse_y(); + + const float + mX = mx<0?-1.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 && mX=0 && mX=height()) { area = 2; X = mX; Z = mY - _height; Y = (float)(phase?Y1:Y0); } + if (mY>=0 && mX>=width() && mY=width() && mY>=height()) area = 4; + if (disp.button()) { if (!area_clicked) area_clicked = area; } else area_clicked = 0; + + CImg filename(32); + + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(1); key = 0; } break; + case cimg::keyPAGEDOWN : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(-1); key = 0; } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_axes = !is_axes; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_view3d = !is_view3d; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot...",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + } + + switch (area) { + + case 0 : // When mouse is out of image range + mx = my = -1; X = Y = Z = -1; + break; + + case 1 : case 2 : case 3 : { // When mouse is over the XY,XZ or YZ projections + const unsigned int but = disp.button(); + const bool b1 = (bool)(but&1), b2 = (bool)(but&2), b3 = (bool)(but&4); + + if (b1 && phase==1 && area_clicked==area) { // When selection has been started (1st step) + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } + if (!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 (phase) { + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } else { + if (_depth>1 && (X0!=(int)X || Y0!=(int)Y || Z0!=(int)Z)) visu0.assign(); + X0 = (int)X; Y0 = (int)Y; Z0 = (int)Z; + } + } + if (b3) { // Reset selection + 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 (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() && + !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT()) { + switch (area) { + case 1 : + if (phase) Z = (float)(Z1+=disp.wheel()); else Z = (float)(Z0+=disp.wheel()); + visu0.assign(); break; + case 2 : + if (phase) Y = (float)(Y1+=disp.wheel()); else Y = (float)(Y0+=disp.wheel()); + visu0.assign(); break; + case 3 : + if (phase) X = (float)(X1+=disp.wheel()); else X = (float)(X0+=disp.wheel()); + visu0.assign(); break; + } + disp.set_wheel(); + } else key = ~0U; + } + + if ((phase==0 && b1) || + (phase==1 && !b1) || + (phase==2 && b1)) switch (phase) { // Detect change of phase + case 0 : + if (area==area_clicked) { + X0 = X1 = (int)X; Y0 = Y1 = (int)Y; Z0 = Z1 = (int)Z; area_started = area; ++phase; + } break; + case 1 : + if (area==area_started) { + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++phase; + if (_depth>1) { + if (disp.is_keyCTRLLEFT()) is_deep_selection = !is_deep_selection_default; + if (is_deep_selection) ++phase; + } + } else if (!b1) { X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = 0; visu0.assign(); } + break; + case 2 : ++phase; break; + } + } break; + + case 4 : // When mouse is over the 3D view + if (is_view3d && points3d) { + X3d = mx - width()*disp.width()/(width() + (depth()>1?depth():0)); + Y3d = my - height()*disp.height()/(height() + (depth()>1?depth():0)); + if (oX3d<0) { oX3d = X3d; oY3d = Y3d; } + // Left + right buttons: reset. + if ((disp.button()&3)==3) { pose3d.assign(); view3d.assign(); oX3d = oY3d = X3d = Y3d = -1; } + else if (disp.button()&1 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Left button: rotate + const float + R = 0.45f*std::min(view3d._width,view3d._height), + R2 = R*R, + u0 = (float)(oX3d - view3d.width()/2), + v0 = (float)(oY3d - view3d.height()/2), + u1 = (float)(X3d - view3d.width()/2), + v1 = (float)(Y3d - view3d.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + pose3d.draw_image(CImg::rotation_matrix(u,v,w,-alpha)*pose3d.get_crop(0,0,2,2)); + view3d.assign(); + } else if (disp.button()&2 && pose3d && oY3d!=Y3d) { // Right button: zoom + pose3d(3,2)+=(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 + pose3d(3,0)-=oX3d - X3d; pose3d(3,1)-=oY3d - Y3d; view3d.assign(); + } + oX3d = X3d; oY3d = Y3d; + } + mx = my = -1; X = Y = Z = -1; + break; + } + + if (phase) { + if (!feature_type) shape_selected = phase?true:false; + else { + if (_depth>1) shape_selected = (phase==3)?true:false; + else shape_selected = (phase==2)?true:false; + } + } + + if (X0<0) X0 = 0; + if (X0>=width()) X0 = width() - 1; + if (Y0<0) Y0 = 0; + if (Y0>=height()) Y0 = height() - 1; + if (Z0<0) Z0 = 0; + if (Z0>=depth()) Z0 = depth() - 1; + if (X1<1) X1 = 0; + if (X1>=width()) X1 = width() - 1; + if (Y1<0) Y1 = 0; + if (Y1>=height()) Y1 = height() - 1; + if (Z1<0) Z1 = 0; + if (Z1>=depth()) Z1 = depth() - 1; + + // Draw visualization image on the display + if (mx!=omx || my!=omy || !visu0 || (_depth>1 && !view3d)) { + + if (!visu0) { // Create image of projected planes + if (thumb) thumb._get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + else _get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + visu0.resize(disp); + view3d.assign(); + points3d.assign(); + } + + if (is_view3d && _depth>1 && !view3d) { // Create 3D view for volumetric images + const unsigned int + _x3d = (unsigned int)cimg::round((float)_width*visu0._width/(_width + _depth),1,1), + _y3d = (unsigned int)cimg::round((float)_height*visu0._height/(_height + _depth),1,1), + x3d = _x3d>=visu0._width?visu0._width - 1:_x3d, + y3d = _y3d>=visu0._height?visu0._height - 1:_y3d; + CImg(1,2,1,1,64,128).resize(visu0._width - x3d,visu0._height - y3d,1,visu0._spectrum,3). + move_to(view3d); + if (!points3d) { + get_projections3d(primitives3d,colors3d,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0,true).move_to(points3d); + points3d.append(CImg(8,3,1,1, + 0,_width - 1,_width - 1,0,0,_width - 1,_width - 1,0, + 0,0,_height - 1,_height - 1,0,0,_height - 1,_height - 1, + 0,0,0,0,_depth - 1,_depth - 1,_depth - 1,_depth - 1),'x'); + CImg::vector(12,13).move_to(primitives3d); CImg::vector(13,14).move_to(primitives3d); + CImg::vector(14,15).move_to(primitives3d); CImg::vector(15,12).move_to(primitives3d); + CImg::vector(16,17).move_to(primitives3d); CImg::vector(17,18).move_to(primitives3d); + CImg::vector(18,19).move_to(primitives3d); CImg::vector(19,16).move_to(primitives3d); + CImg::vector(12,16).move_to(primitives3d); CImg::vector(13,17).move_to(primitives3d); + CImg::vector(14,18).move_to(primitives3d); CImg::vector(15,19).move_to(primitives3d); + colors3d.insert(12,CImg::vector(255,255,255)); + opacities3d.assign(primitives3d.width(),1,1,1,0.5f); + if (!phase) { + opacities3d[0] = opacities3d[1] = opacities3d[2] = 0.8f; + sel_primitives3d.assign(); + sel_colors3d.assign(); + sel_opacities3d.assign(); + } else { + if (feature_type==2) { + points3d.append(CImg(8,3,1,1, + X0,X1,X1,X0,X0,X1,X1,X0, + Y0,Y0,Y1,Y1,Y0,Y0,Y1,Y1, + Z0,Z0,Z0,Z0,Z1,Z1,Z1,Z1),'x'); + sel_primitives3d.assign(); + CImg::vector(20,21).move_to(sel_primitives3d); + CImg::vector(21,22).move_to(sel_primitives3d); + CImg::vector(22,23).move_to(sel_primitives3d); + CImg::vector(23,20).move_to(sel_primitives3d); + CImg::vector(24,25).move_to(sel_primitives3d); + CImg::vector(25,26).move_to(sel_primitives3d); + CImg::vector(26,27).move_to(sel_primitives3d); + CImg::vector(27,24).move_to(sel_primitives3d); + CImg::vector(20,24).move_to(sel_primitives3d); + CImg::vector(21,25).move_to(sel_primitives3d); + CImg::vector(22,26).move_to(sel_primitives3d); + CImg::vector(23,27).move_to(sel_primitives3d); + } else { + points3d.append(CImg(2,3,1,1, + X0,X1, + Y0,Y1, + Z0,Z1),'x'); + sel_primitives3d.assign(CImg::vector(20,21)); + } + sel_colors3d.assign(sel_primitives3d._width,CImg::vector(255,255,255)); + sel_opacities3d.assign(sel_primitives3d._width,1,1,1,0.8f); + } + points3d.shift_object3d(-0.5f*(_width - 1),-0.5f*(_height - 1),-0.5f*(_depth - 1)).resize_object3d(); + points3d*=0.75f*std::min(view3d._width,view3d._height); + } + + if (!pose3d) CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose3d); + CImg zbuffer3d(view3d._width,view3d._height,1,1,0); + const CImg rotated_points3d = pose3d.get_crop(0,0,2,2)*points3d; + if (sel_primitives3d) + view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width, + pose3d(3,1) + 0.5f*view3d._height, + pose3d(3,2), + rotated_points3d,sel_primitives3d,sel_colors3d,sel_opacities3d, + 2,true,500,0,0,0,0,0,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,1,zbuffer3d); + visu0.draw_image(x3d,y3d,view3d); + } + visu = visu0; + + if (X<0 || Y<0 || Z<0) { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + else { + if (is_axes) { if (visible_cursor) { disp.hide_mouse(); visible_cursor = false; }} + else { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + const int d = (depth()>1)?depth():0; + int _vX = (int)X, _vY = (int)Y, _vZ = (int)Z; + if (phase>=2) { _vX = X1; _vY = Y1; _vZ = Z1; } + int + w = disp.width(), W = width() + d, + h = disp.height(), H = height() + d, + _xp = (int)(_vX*(float)w/W), xp = _xp + ((int)(_xp*(float)W/w)!=_vX), + _yp = (int)(_vY*(float)h/H), yp = _yp + ((int)(_yp*(float)H/h)!=_vY), + _xn = (int)((_vX + 1.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.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, + zyc = (zyp + zyn)/2, + xf = (int)(X*w/W), + yf = (int)(Y*h/H), + zxf = (int)((Z + width())*w/W), + zyf = (int)((Z + height())*h/H); + + if (is_axes) { // Draw axes + visu.draw_line(0,yf,visu.width() - 1,yf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,yf,visu.width() - 1,yf,background_color,0.7f,0x00FF00FF). + draw_line(xf,0,xf,visu.height() - 1,foreground_color,0.7f,0xFF00FF00). + draw_line(xf,0,xf,visu.height() - 1,background_color,0.7f,0x00FF00FF); + if (_depth>1) + visu.draw_line(zxf,0,zxf,yM,foreground_color,0.7f,0xFF00FF00). + draw_line(zxf,0,zxf,yM,background_color,0.7f,0x00FF00FF). + draw_line(0,zyf,xM,zyf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,zyf,xM,zyf,background_color,0.7f,0x00FF00FF); + } + + // Draw box cursor. + if (xn - xp>=4 && yn - yp>=4) + visu.draw_rectangle(xp,yp,xn,yn,foreground_color,0.2f). + draw_rectangle(xp,yp,xn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,yp,xn,yn,background_color,1,0x55555555); + if (_depth>1) { + if (yn - yp>=4 && zxn - zxp>=4) + visu.draw_rectangle(zxp,yp,zxn,yn,background_color,0.2f). + draw_rectangle(zxp,yp,zxn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(zxp,yp,zxn,yn,background_color,1,0x55555555); + if (xn - xp>=4 && zyn - zyp>=4) + visu.draw_rectangle(xp,zyp,xn,zyn,background_color,0.2f). + draw_rectangle(xp,zyp,xn,zyn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,zyp,xn,zyn,background_color,1,0x55555555); + } + + // Draw selection. + if (phase && (phase!=1 || area_started==area)) { + 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.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.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); + if (d) { + visu.draw_arrow(zxc0,yc0,zxc,yc,background_color,0.9f,30,5,0x55555555). + draw_arrow(zxc0,yc0,zxc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA). + draw_arrow(xc0,zyc0,xc,zyc,background_color,0.9f,30,5,0x55555555). + draw_arrow(xc0,zyc0,xc,zyc,foreground_color,0.9f,30,5,0xAAAAAAAA); + } + } break; + case 2 : { + visu.draw_rectangle(X0=0 && my<13) text_down = true; else if (my>=visu.height() - 13) text_down = false; + if (!feature_type || !phase) { + if (X>=0 && Y>=0 && Z>=0 && X1 || force_display_z_coord) + cimg_snprintf(text,text._width," Point (%d,%d,%d) = [ ",origX + (int)X,origY + (int)Y,origZ + (int)Z); + else cimg_snprintf(text,text._width," Point (%d,%d) = [ ",origX + (int)X,origY + (int)Y); + CImg values = get_vector_at((int)X,(int)Y,(int)Z); + const bool is_large_spectrum = values._height>16; + if (is_large_spectrum) + values.draw_image(0,8,values.get_rows(values._height - 8,values._height - 1)).resize(1,16,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," (...)"); + ctext += std::strlen(ctext); + } + *(ctext++) = ' '; *ctext = 0; + } + std::strcpy(text._data + std::strlen(text),"] "); + } + } else switch (feature_type) { + case 1 : { + const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), + length = cimg::round(cimg::hypot(dX,dY,dZ),0.1); + 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 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 ", + origX + (X01 || force_display_z_coord) + cimg_snprintf(text,text._width," Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d) ", + origX + X0,origY + Y0,origZ + Z0,origX + X1,origY + Y1,origZ + Z1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1),1 + cimg::abs(Z0 - Z1)); + else cimg_snprintf(text,text._width," Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d) ", + origX + X0,origY + Y0,origX + X1,origY + Y1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1)); + } + if (phase || (mx>=0 && my>=0)) visu.__draw_text("%s",text_down,text._data); + } + + disp.display(visu); + } + if (!shape_selected) disp.wait(); + if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); } + omx = mx; omy = my; + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + + // Return result. + CImg res(1,feature_type==0?3:6,1,1,-1); + if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; } + if (shape_selected) { + if (feature_type==2) { + if (is_deep_selection) switch (area_started) { + case 1 : Z0 = 0; Z1 = _depth - 1; break; + case 2 : Y0 = 0; Y1 = _height - 1; break; + case 3 : X0 = 0; X1 = _width - 1; break; + } + if (X0>X1) cimg::swap(X0,X1); + if (Y0>Y1) cimg::swap(Y0,Y1); + if (Z0>Z1) cimg::swap(Z0,Z1); + } + if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1; + switch (feature_type) { + case 1 : case 2 : res[0] = X0; res[1] = Y0; res[2] = Z0; res[3] = X1; res[4] = Y1; res[5] = Z1; break; + case 3 : + res[3] = cimg::abs(X1 - X0); res[4] = cimg::abs(Y1 - Y0); res[5] = cimg::abs(Z1 - Z0); + res[0] = X0; res[1] = Y0; res[2] = Z0; + break; + default : res[0] = X0; res[1] = Y0; res[2] = Z0; + } + } + if (!exit_on_anykey || !(disp.button()&4)) disp.set_button(); + if (!visible_cursor) disp.show_mouse(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + if (key!=~0U) disp.set_key(key); + return res; + } + + // Return a visualizable uchar8 image for display routines. + CImg _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; + if (_depth>1) { + const int mdisp = std::min(disp.screen_width(),disp.screen_height()); + if (depth()>mdisp) { + crop.get_resize(-100,-100,mdisp,-100,0).move_to(img2d); + img2d.projections2d(x,y,z*img2d._depth/_depth); + } else crop.get_projections2d(x,y,z).move_to(img2d); + } else CImg(crop,false).move_to(img2d); + + // Check for inf and NaN values. + if (cimg::type::is_float() && normalization) { + bool is_inf = false, is_nan = false; + cimg_for(img2d,ptr,Tuchar) + if (cimg::type::is_inf(*ptr)) { is_inf = true; break; } + else if (cimg::type::is_nan(*ptr)) { is_nan = true; break; } + if (is_inf || is_nan) { + 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 + 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); + if (is_nan) + cimg_for(img2d,ptr,Tuchar) + 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 + } + } + + switch (normalization) { + case 1 : img2d.normalize((ucharT)0,(ucharT)255); break; + case 2 : { + const float m = disp._min, M = disp._max; + (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.f/(M - m>0?M - m:1); + } break; + } + if (img2d.spectrum()==2) img2d.channels(0,2); + return img2d; + } + + //! Select sub-graph in a graph. + CImg get_select_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "select_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title("CImg<%s>",pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth; + const unsigned int old_normalization = disp.normalization(); + disp.show().set_button().set_wheel()._normalization = 0; + + double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax; + if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; } + if (nymin==nymax) { --nymin; ++nymax; } + if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.; } + + 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 }; + + 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) { + ulongT 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; + CImg message(1024); + CImg_3x3(I,unsigned char); + + for (bool selected = false; !selected && !disp.is_closed() && !okey && !disp.wheel(); ) { + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + const unsigned int key = disp.key(), button = disp.button(); + + // Generate graph representation. + if (!visu0) { + visu0.assign(disp.width(),disp.height(),1,3,220); + const int gdimx = disp.width() - 32, gdimy = disp.height() - 32; + if (gdimx>0 && gdimy>0) { + graph.assign(gdimx,gdimy,1,3,255); + if (siz<32) { + if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0, + false,true,black,0.2f,0x33333333,0x33333333); + } else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333); + cimg_forC(*this,c) + graph.draw_graph(get_shared_channel(c),&colormap(0,c),(plot_type!=3 || _spectrum==1)?1:0.6f, + plot_type,vertex_type,nymax,nymin); + + axes.assign(gdimx,gdimy,1,1,0); + const float + dx = (float)cimg::abs(nxmax - nxmin), dy = (float)cimg::abs(nymax - nymin), + px = (float)std::pow(10.,(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); + + const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0); + axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero); + if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero); + if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero); + if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero); + if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero); + + cimg_for3x3(axes,x,y,0,0,I,unsigned char) + if (Icc) { + if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0; + else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3); + } + else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) + cimg_forC(graph,c) graph(x,y,c) = (unsigned char)((graph(x,y,c) + 511)/3); + + visu0.draw_image(16,16,graph); + visu0.draw_line(15,15,16 + gdimx,15,gray2).draw_line(16 + gdimx,15,16 + gdimx,16 + gdimy,gray2). + draw_line(16 + gdimx,16 + gdimy,15,16 + gdimy,white).draw_line(15,16 + gdimy,15,15,white); + } else graph.assign(); + text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1,13).resize(-100,-100,1,3); + visu0.draw_image((visu0.width() - text.width())/2,visu0.height() - 14,~text); + text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3); + visu0.draw_image(1,(visu0.height() - text.height())/2,~text); + visu.assign(); + } + + // Generate and display current view. + if (!visu) { + visu.assign(visu0); + if (graph && x0>=0 && x1>=0) { + const int + nx0 = x0<=x1?x0:x1, + nx1 = x0<=x1?x1:x0, + ny0 = y0<=y1?y0:y1, + ny1 = y0<=y1?y1:y0, + sx0 = (int)(16 + nx0*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sx1 = (int)(15 + (nx1 + 1)*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sy0 = 16 + ny0, + sy1 = 16 + ny1; + if (y0>=0 && y1>=0) + visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU); + else visu.draw_rectangle(sx0,0,sx1,visu.height() - 17,gray,0.5f). + draw_line(sx0,16,sx0,visu.height() - 17,black,0.5f,0xCCCCCCCCU). + draw_line(sx1,16,sx1,visu.height() - 17,black,0.5f,0xCCCCCCCCU); + } + if (mouse_x>=16 && mouse_y>=16 && mouse_x=7) + cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx, + (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2), + (double)(*this)(x,0,0,_spectrum - 4),(double)(*this)(x,0,0,_spectrum - 3), + (double)(*this)(x,0,0,_spectrum - 1)); + else { + cimg_snprintf(message,message._width,"Value[%u:%g] = ( ",x,cx); + cimg_forC(*this,c) cimg_sprintf(message._data + std::strlen(message),"%g ",(double)(*this)(x,0,0,c)); + cimg_sprintf(message._data + std::strlen(message),")"); + } + if (x0>=0 && x1>=0) { + const unsigned int + nx0 = (unsigned int)(x0<=x1?x0:x1), + nx1 = (unsigned int)(x0<=x1?x1:x0), + ny0 = (unsigned int)(y0<=y1?y0:y1), + ny1 = (unsigned int)(y0<=y1?y1:y0); + const double + cx0 = nxmin + nx0*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cx1 = nxmin + (nx1 + one)*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cy0 = nymax - ny0*(nymax - nymin)/(visu._height - 32), + cy1 = nymax - ny1*(nymax - nymin)/(visu._height - 32); + if (y0>=0 && y1>=0) + cimg_sprintf(message._data + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )", + x0,cx0,cy0,x1 + one,cx1,cy1); + else + cimg_sprintf(message._data + std::strlen(message)," - Range [ %u:%g - %u:%g ]", + x0,cx0,x1 + one,cx1); + } + text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3); + visu.draw_image((visu.width() - text.width())/2,1,~text); + } + visu.display(disp); + } + + // Test keys. + CImg filename(32); + switch (okey = key) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(CImgDisplay::screen_width()/2, + CImgDisplay::screen_height()/2,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving snapshot... ",false).display(disp); + screen.save(filename); + (+screen).__draw_text(" Snapshot '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving instance... ",false).display(disp); + save(filename); + (+screen).__draw_text(" Instance '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + } + + // Handle mouse motion and mouse buttons. + if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) { + visu.assign(); + if (disp.mouse_x()>=0 && disp.mouse_y()>=0) { + const int + mx = (mouse_x - 16)*(int)(siz - one)/(disp.width() - 32), + cx = cimg::cut(mx,0,(int)(siz - 1 - one)), + my = mouse_y - 16, + cy = cimg::cut(my,0,disp.height() - 32); + if (button&1) { + if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; } + } + else if (button&2) { + if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; } + } + else if (obutton) { x1 = x1>=0?cx:-1; y1 = y1>=0?cy:-1; selected = true; } + } else if (!button && obutton) selected = true; + obutton = button; omouse_x = mouse_x; omouse_y = mouse_y; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (visu && visu0) disp.wait(); + if (!exit_on_anykey && okey && okey!=cimg::keyESC && + (okey!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + okey = 0; + } + } + + disp._normalization = old_normalization; + if (x1>=0 && x1(4,1,1,1,x0,y0,x1>=0?x1 + (int)one:-1,y1); + } + + //! Load image from a file. + /** + \param filename Filename, as a C-string. + \note The extension of \c filename defines the file format. If no filename + extension is provided, CImg::get_load() will try to load the file as a .cimg or .cimgz file. + **/ + CImg& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load(): Specified filename is (null).", + cimg_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimg_load_plugin + cimg_load_plugin(filename); +#endif +#ifdef cimg_load_plugin1 + cimg_load_plugin1(filename); +#endif +#ifdef cimg_load_plugin2 + cimg_load_plugin2(filename); +#endif +#ifdef cimg_load_plugin3 + cimg_load_plugin3(filename); +#endif +#ifdef cimg_load_plugin4 + cimg_load_plugin4(filename); +#endif +#ifdef cimg_load_plugin5 + cimg_load_plugin5(filename); +#endif +#ifdef cimg_load_plugin6 + cimg_load_plugin6(filename); +#endif +#ifdef cimg_load_plugin7 + cimg_load_plugin7(filename); +#endif +#ifdef cimg_load_plugin8 + cimg_load_plugin8(filename); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) load_dlm(filename); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) load_jpeg(filename); + else if (!cimg::strcasecmp(ext,"png")) load_png(filename); + else if (!cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"pnm") || + !cimg::strcasecmp(ext,"pbm") || + !cimg::strcasecmp(ext,"pnk")) load_pnm(filename); + else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename); + else if (!cimg::strcasecmp(ext,"cr2") || + !cimg::strcasecmp(ext,"crw") || + !cimg::strcasecmp(ext,"dcr") || + !cimg::strcasecmp(ext,"mrw") || + !cimg::strcasecmp(ext,"nef") || + !cimg::strcasecmp(ext,"orf") || + !cimg::strcasecmp(ext,"pix") || + !cimg::strcasecmp(ext,"ptx") || + !cimg::strcasecmp(ext,"raf") || + !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"dcm") || + !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) load_analyze(filename); + else if (!cimg::strcasecmp(ext,"par") || + !cimg::strcasecmp(ext,"rec")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"mnc")) load_minc2(filename); + else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) return load_cimg(filename); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + + // Image sequences + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded) { + std::FILE *file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to open file '%s'.", + cimg_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + cimg::fclose(file); + is_loaded = true; + try { + if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename); + else if (!cimg::strcasecmp(f_type,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(f_type,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(f_type,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(f_type,"jpg")) load_jpeg(filename); + else if (!cimg::strcasecmp(f_type,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(f_type,"png")) load_png(filename); + else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename); + else if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file with other means. + if (!is_loaded) { + try { + load_other(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image from a file \newinstance. + static CImg get_load(const char *const filename) { + return CImg().load(filename); + } + + //! Load image from an Ascii file. + /** + \param filename Filename, as a C -string. + **/ + CImg& load_ascii(const char *const filename) { + return _load_ascii(0,filename); + } + + //! Load image from an Ascii file \inplace. + static CImg get_load_ascii(const char *const filename) { + return CImg().load_ascii(filename); + } + + //! Load image from an Ascii file \overloading. + CImg& load_ascii(std::FILE *const file) { + return _load_ascii(file,0); + } + + //! Loadimage from an Ascii file \newinstance. + static CImg get_load_ascii(std::FILE *const file) { + return CImg().load_ascii(file); + } + + CImg& _load_ascii(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_ascii(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg line(256); *line = 0; + int err = std::fscanf(nfile,"%255[^\n]",line._data); + unsigned int dx = 0, dy = 1, dz = 1, dc = 1; + cimg_sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc); + err = std::fscanf(nfile,"%*[^0-9.eEinfa+-]"); + if (!dx || !dy || !dz || !dc) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_ascii(): Invalid Ascii header in file '%s', image dimensions are set " + "to (%u,%u,%u,%u).", + cimg_instance, + filename?filename:"(FILE*)",dx,dy,dz,dc); + } + assign(dx,dy,dz,dc); + const ulongT siz = size(); + ulongT off = 0; + double val; + T *ptr = _data; + for (err = 1, off = 0; off& load_dlm(const char *const filename) { + return _load_dlm(0,filename); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(const char *const filename) { + return CImg().load_dlm(filename); + } + + //! Load image from a DLM file \overloading. + CImg& load_dlm(std::FILE *const file) { + return _load_dlm(file,0); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(std::FILE *const file) { + return CImg().load_dlm(file); + } + + CImg& _load_dlm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_dlm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + CImg delimiter(256), tmp(256); *delimiter = *tmp = 0; + unsigned int cdx = 0, dx = 0, dy = 0; + int err = 0; + double val; + assign(256,256,1,1,(T)0); + while ((err = std::fscanf(nfile,"%lf%255[^0-9eEinfa.+-]",&val,delimiter._data))>0) { + if (err>0) (*this)(cdx++,dy) = (T)val; + if (cdx>=_width) resize(3*_width/2,_height,1,1,0); + char c = 0; + if (!cimg_sscanf(delimiter,"%255[^\n]%c",tmp._data,&c) || c=='\n') { + dx = std::max(cdx,dx); + if (++dy>=_height) resize(_width,3*_height/2,1,1,0); + cdx = 0; + } + } + if (cdx && err==1) { dx = cdx; ++dy; } + if (!dx || !dy) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_dlm(): Invalid DLM file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + resize(dx,dy,1,1,0); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a BMP file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_bmp(const char *const filename) { + return _load_bmp(0,filename); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(const char *const filename) { + return CImg().load_bmp(filename); + } + + //! Load image from a BMP file \overloading. + CImg& load_bmp(std::FILE *const file) { + return _load_bmp(file,0); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(std::FILE *const file) { + return CImg().load_bmp(file); + } + + CImg& _load_bmp(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_bmp(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(54); + cimg::fread(header._data,54,nfile); + if (*header!='B' || header[1]!='M') { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_bmp(): Invalid BMP file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read header and pixel buffer + int + file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24), + offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24), + header_size = header[0x0E] + (header[0x0F]<<8) + (header[0x10]<<16) + (header[0x11]<<24), + dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24), + dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24), + compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24), + nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24), + bpp = header[0x1C] + (header[0x1D]<<8); + + if (!file_size || file_size==offset) { + cimg::fseek(nfile,0,SEEK_END); + file_size = (int)cimg::ftell(nfile); + cimg::fseek(nfile,54,SEEK_SET); + } + if (header_size>40) cimg::fseek(nfile,header_size - 40,SEEK_CUR); + + const int + dx_bytes = (bpp==1)?(dx/8 + (dx%8?1:0)):((bpp==4)?(dx/2 + (dx%2)):(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); + + CImg colormap; + if (bpp<16) { if (!nb_colors) nb_colors = 1<0) cimg::fseek(nfile,xoffset,SEEK_CUR); + + CImg buffer; + if (buf_size=2) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0x80, val = 0; + cimg_forX(*this,x) { + if (mask==0x80) val = *(ptrs++); + const unsigned char *col = (unsigned char*)(colormap._data + (val&mask?1:0)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask); + } + ptrs+=align_bytes; + } + } break; + case 4 : { // 16 colors + if (colormap._width>=16) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0xF0, val = 0; + cimg_forX(*this,x) { + if (mask==0xF0) val = *(ptrs++); + const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4)); + const unsigned char *col = (unsigned char*)(colormap._data + color); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask,4); + } + ptrs+=align_bytes; + } + } break; + case 8 : { // 256 colors + if (colormap._width>=256) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char *col = (unsigned char*)(colormap._data + *(ptrs++)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + } + ptrs+=align_bytes; + } + } break; + case 16 : { // 16 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char c1 = *(ptrs++), c2 = *(ptrs++); + const unsigned short col = (unsigned short)(c1|(c2<<8)); + (*this)(x,y,2) = (T)(col&0x1F); + (*this)(x,y,1) = (T)((col>>5)&0x1F); + (*this)(x,y,0) = (T)((col>>10)&0x1F); + } + ptrs+=align_bytes; + } + } break; + case 24 : { // 24 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + } + ptrs+=align_bytes; + } + } break; + case 32 : { // 32 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + ++ptrs; + } + ptrs+=align_bytes; + } + } break; + } + if (dy<0) mirror('y'); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a JPEG file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_jpeg(const char *const filename) { + return _load_jpeg(0,filename); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(const char *const filename) { + return CImg().load_jpeg(filename); + } + + //! Load image from a JPEG file \overloading. + CImg& load_jpeg(std::FILE *const file) { + return _load_jpeg(file,0); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(std::FILE *const file) { + return CImg().load_jpeg(file); + } + + // Custom error handler for libjpeg. +#ifdef cimg_use_jpeg + struct _cimg_error_mgr { + struct jpeg_error_mgr original; + jmp_buf setjmp_buffer; + char message[JMSG_LENGTH_MAX]; + }; + + typedef struct _cimg_error_mgr *_cimg_error_ptr; + + METHODDEF(void) _cimg_jpeg_error_exit(j_common_ptr cinfo) { + _cimg_error_ptr c_err = (_cimg_error_ptr) cinfo->err; // Return control to the setjmp point + (*cinfo->err->format_message)(cinfo,c_err->message); + jpeg_destroy(cinfo); // Clean memory and temp files + longjmp(c_err->setjmp_buffer,1); + } +#endif + + CImg& _load_jpeg(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_jpeg(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_jpeg + if (file) + throw CImgIOException(_cimg_instance + "load_jpeg(): Unable to load data from '(FILE*)' unless libjpeg is enabled.", + cimg_instance); + else return load_other(filename); +#else + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + struct jpeg_decompress_struct cinfo; + struct _cimg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr.original); + jerr.original.error_exit = _cimg_jpeg_error_exit; + if (setjmp(jerr.setjmp_buffer)) { // JPEG error + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_jpeg(): Error message returned by libjpeg: %s.", + cimg_instance,jerr.message); + } + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo,nfile); + jpeg_read_header(&cinfo,TRUE); + jpeg_start_decompress(&cinfo); + + if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) { + if (!file) { + cimg::fclose(nfile); + return load_other(filename); + } else + throw CImgIOException(_cimg_instance + "load_jpeg(): Failed to load JPEG data from file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + } + CImg buffer(cinfo.output_width*cinfo.output_components); + JSAMPROW row_pointer[1]; + try { assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); } + catch (...) { if (!file) cimg::fclose(nfile); throw; } + T *ptr_r = _data, *ptr_g = _data + 1UL*_width*_height, *ptr_b = _data + 2UL*_width*_height, + *ptr_a = _data + 3UL*_width*_height; + while (cinfo.output_scanline + // This is experimental code, not much tested, use with care. + CImg& load_magick(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_magick(): Specified filename is (null).", + cimg_instance); +#ifdef cimg_use_magick + Magick::Image image(filename); + const unsigned int W = image.size().width(), H = image.size().height(); + switch (image.type()) { + case Magick::PaletteMatteType : + case Magick::TrueColorMatteType : + case Magick::ColorSeparationType : { + assign(W,H,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + case Magick::PaletteType : + case Magick::TrueColorType : { + assign(W,H,1,3); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + ++pixels; + } + } break; + case Magick::GrayscaleMatteType : { + assign(W,H,1,2); + T *ptr_r = data(0,0,0,0), *ptr_a = data(0,0,0,1); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + default : { + assign(W,H,1,1); + T *ptr_r = data(0,0,0,0); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + ++pixels; + } + } + } + return *this; +#else + throw CImgIOException(_cimg_instance + "load_magick(): Unable to load file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Load image from a file, using Magick++ library \newinstance. + static CImg get_load_magick(const char *const filename) { + return CImg().load_magick(filename); + } + + //! 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. + **/ + CImg& load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return _load_png(0,filename,bits_per_pixel); + } + + //! Load image from a PNG file \newinstance. + static CImg get_load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return CImg().load_png(filename,bits_per_pixel); + } + + //! 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); + } + + //! 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); + } + + // (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) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_png(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_png + cimg::unused(bits_per_pixel); + if (file) + throw CImgIOException(_cimg_instance + "load_png(): Unable to load data from '(FILE*)' unless libpng is enabled.", + cimg_instance); + + else return load_other(filename); +#else + // Open file and check for PNG validity +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb"); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"rb"); +#endif + unsigned char pngCheck[8] = { 0 }; + cimg::fread(pngCheck,8,(std::FILE*)nfile); + if (png_sig_cmp(pngCheck,0,8)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Invalid PNG file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Setup PNG structures for read + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn); + if (!png_ptr) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'png_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'info_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'end_info' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Error handling callback for png file reading + if (setjmp(png_jmpbuf(png_ptr))) { + if (!file) cimg::fclose((std::FILE*)nfile); + png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Encountered unknown fatal error in libpng for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + png_set_sig_bytes(png_ptr, 8); + + // Get PNG Header Info up to data block + png_read_info(png_ptr,info_ptr); + png_uint_32 W, H; + int bit_depth, color_type, interlace_type; + bool is_gray = false; + png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0); + if (bits_per_pixel) *bits_per_pixel = (unsigned int)bit_depth; + + // Transforms to unify image data + if (color_type==PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + color_type = PNG_COLOR_TYPE_RGB; + bit_depth = 8; + } + if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + is_gray = true; + bit_depth = 8; + } + if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + color_type |= PNG_COLOR_MASK_ALPHA; + } + if (color_type==PNG_COLOR_TYPE_GRAY || color_type==PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + color_type |= PNG_COLOR_MASK_COLOR; + is_gray = true; + } + if (color_type==PNG_COLOR_TYPE_RGB) + png_set_filler(png_ptr,0xffffU,PNG_FILLER_AFTER); + + png_read_update_info(png_ptr,info_ptr); + if (bit_depth!=8 && bit_depth!=16) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Invalid bit depth %u in file '%s'.", + cimg_instance, + bit_depth,nfilename?nfilename:"(FILE*)"); + } + const int byte_depth = bit_depth>>3; + + // Allocate memory for image reading + png_bytep *const imgData = new png_bytep[H]; + for (unsigned int row = 0; row& load_pnm(const char *const filename) { + return _load_pnm(0,filename); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(const char *const filename) { + return CImg().load_pnm(filename); + } + + //! Load image from a PNM file \overloading. + CImg& load_pnm(std::FILE *const file) { + return _load_pnm(file,0); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(std::FILE *const file) { + return CImg().load_pnm(file); + } + + CImg& _load_pnm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pnm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + unsigned int ppm_type, W, H, D = 1, colormax = 255; + CImg item(16384,1,1,1,0); + int err, rval, gval, bval; + const longT cimg_iobuffer = (longT)24*1024*1024; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%u",&ppm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %u %u %u %u",&W,&H,&D,&colormax))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): WIDTH and HEIGHT fields undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (ppm_type!=1 && ppm_type!=4) { + if (err==2 || (err==3 && (ppm_type==5 || ppm_type==7 || ppm_type==8 || ppm_type==9))) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%u",&colormax)!=1) + cimg::warn(_cimg_instance + "load_pnm(): COLORMAX field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else { colormax = D; D = 1; } + } + std::fgetc(nfile); + + switch (ppm_type) { + case 1 : { // 2D b&w Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)(rval?0:255); else break; } + } break; + case 2 : { // 2D grey Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)rval; else break; } + } break; + case 3 : { // 2D color Ascii + assign(W,H,1,3); + T *ptrd = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forXY(*this,x,y) { + if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { + *(ptrd++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval; + } else break; + } + } break; + case 4 : { // 2D b&w binary (support 3D PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + unsigned int w = 0, h = 0, d = 0; + for (longT to_read = (longT)((W/8 + (W%8?1:0))*H*D); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + unsigned char mask = 0, val = 0; + for (ulongT off = (ulongT)raw._width; off || mask; mask>>=1) { + if (!mask) { if (off--) val = *(ptrs++); mask = 128; } + *(ptrd++) = (T)((val&mask)?0:255); + if (++w==W) { w = 0; mask = 0; if (++h==H) { h = 0; if (++d==D) break; }} + } + } + } break; + case 5 : case 7 : { // 2D/3D grey binary (support 3D PINK extension) + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } else { // 16 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } + } break; + case 6 : { // 2D color binary + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { // 16 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } + } break; + case 8 : { // 2D/3D grey binary with int32 integers (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const int *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + case 9 : { // 2D/3D grey binary with float values (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const float *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + default : + assign(); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM type 'P%d' found, but type is not supported.", + cimg_instance, + filename?filename:"(FILE*)",ppm_type); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PFM file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pfm(const char *const filename) { + return _load_pfm(0,filename); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(const char *const filename) { + return CImg().load_pfm(filename); + } + + //! Load image from a PFM file \overloading. + CImg& load_pfm(std::FILE *const file) { + return _load_pfm(file,0); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(std::FILE *const file) { + return CImg().load_pfm(file); + } + + CImg& _load_pfm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pfm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + char pfm_type; + CImg item(16384,1,1,1,0); + int W = 0, H = 0, err = 0; + double scale = 0; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%c",&pfm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): PFM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %d %d",&W,&H))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH and HEIGHT fields are undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else if (W<=0 || H<=0) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH (%d) and HEIGHT (%d) fields are invalid in file '%s'.", + cimg_instance,W,H, + filename?filename:"(FILE*)"); + } + if (err==2) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%lf",&scale)!=1) + cimg::warn(_cimg_instance + "load_pfm(): SCALE field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + std::fgetc(nfile); + const bool is_color = (pfm_type=='F'), is_inverted = (scale>0)!=cimg::endianness(); + if (is_color) { + assign(W,H,1,3,(T)0); + CImg buf(3*W); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forY(*this,y) { + cimg::fread(buf._data,3*W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,3*W); + const float *ptrs = buf._data; + cimg_forX(*this,x) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { + assign(W,H,1,1,(T)0); + CImg buf(W); + T *ptrd = data(0,0,0,0); + cimg_forY(*this,y) { + cimg::fread(buf._data,W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,W); + const float *ptrs = buf._data; + cimg_forX(*this,x) *(ptrd++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return mirror('y'); // Most of the .pfm files are flipped along the y-axis + } + + //! Load image from a RGB file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(0,filename,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(filename,dimw,dimh); + } + + //! Load image from a RGB file \overloading. + CImg& load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(file,0,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(file,dimw,dimh); + } + + CImg& _load_rgb(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgb(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/3UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a RGBA file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(0,filename,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(filename,dimw,dimh); + } + + //! Load image from a RGBA file \overloading. + CImg& load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(file,0,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(file,dimw,dimh); + } + + CImg& _load_rgba(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgba(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,4); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2), + *ptr_a = data(0,0,0,3); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/4UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + *(ptr_a++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a TIFF file. + /** + \param filename Filename, as a C-string. + \param first_frame First frame to read (for multi-pages tiff). + \param last_frame Last frame to read (for multi-pages tiff). + \param step_frame Step value of frame reading. + \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. + - 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 + 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) { + 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.", + cimg_instance, + filename); + return load_other(filename); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimg_instance + "load_tiff(): File '%s' contains %u image(s) while specified frame range is [%u,%u] (step %u).", + cimg_instance, + filename,nb_images,nfirst_frame,nlast_frame,nstep_frame); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + TIFFSetDirectory(tif,0); + CImg frame; + for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) { + frame._load_tiff(tif,l,voxel_size,description); + if (l==nfirst_frame) + assign(frame._width,frame._height,1 + (nlast_frame - nfirst_frame)/nstep_frame,frame._spectrum); + if (frame._width>_width || frame._height>_height || frame._spectrum>_spectrum) + resize(std::max(frame._width,_width), + std::max(frame._height,_height),-100, + std::max(frame._spectrum,_spectrum),0); + draw_image(0,0,(l - nfirst_frame)/nstep_frame,frame); + } + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "load_tiff(): Failed to open file '%s'.", + cimg_instance, + filename); + return *this; +#endif + } + + //! Load image from a TIFF file \newinstance. + static CImg 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); + } + + // (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) { + 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) { + 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) { + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (row = 0; rowny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, 0); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0; rr + void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { + t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (unsigned int vv = 0; vvny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, vv); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0;rr& _load_tiff(TIFF *const tif, const unsigned int directory, + 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; + const char *const filename = TIFFFileName(tif); + const bool is_spp = (bool)TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel); + TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx); + TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny); + TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat); + TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample); + TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo); + if (voxel_size) { + const char *s_description = 0; + float vx = 0, vy = 0, vz = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) { + const char *s_desc = std::strstr(s_description,"VX="); + if (s_desc && cimg_sscanf(s_desc,"VX=%f VY=%f VZ=%f",&vx,&vy,&vz)==3) { // CImg format + voxel_size[0] = vx; voxel_size[1] = vy; voxel_size[2] = vz; + } + s_desc = std::strstr(s_description,"spacing="); + if (s_desc && cimg_sscanf(s_desc,"spacing=%f",&vz)==1) { // Fiji format + voxel_size[2] = vz; + } + } + TIFFGetField(tif,TIFFTAG_XRESOLUTION,voxel_size); + TIFFGetField(tif,TIFFTAG_YRESOLUTION,voxel_size + 1); + voxel_size[0] = 1.f/voxel_size[0]; + voxel_size[1] = 1.f/voxel_size[1]; + } + if (description) { + const char *s_description = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) + CImg::string(s_description).move_to(*description); + } + const unsigned int spectrum = !is_spp || photo>=3?(photo>1?3:1):samplesperpixel; + assign(nx,ny,1,spectrum); + + if ((photo>=3 && sampleformat==1 && + (bitspersample==4 || bitspersample==8) && + (samplesperpixel==1 || samplesperpixel==3 || samplesperpixel==4)) || + (bitspersample==1 && samplesperpixel==1)) { + // Special case for unsigned color images. + uint32 *const raster = (uint32*)_TIFFmalloc(nx*ny*sizeof(uint32)); + if (!raster) { + _TIFFfree(raster); TIFFClose(tif); + throw CImgException(_cimg_instance + "load_tiff(): Failed to allocate memory (%s) for file '%s'.", + cimg_instance, + cimg::strbuffersize(nx*ny*sizeof(uint32)),filename); + } + TIFFReadRGBAImage(tif,nx,ny,raster,0); + switch (spectrum) { + case 1 : + cimg_forXY(*this,x,y) + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + break; + case 3 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 -y) + x]); + } + break; + case 4 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny - 1 - y) + x]); + } + break; + } + _TIFFfree(raster); + } else { // Other cases + uint16 config; + TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config); + if (TIFFIsTiled(tif)) { + uint32 tw = 1, th = 1; + TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw); + TIFFGetField(tif,TIFFTAG_TILELENGTH,&th); + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + } + } else { + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + } + } + } + return *this; + } +#endif + + //! Load image from a MINC2 file. + /** + \param filename Filename, as a C-string. + **/ + // (Original code by Haz-Edine Assemlal). + CImg& load_minc2(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_minc2(): Specified filename is (null).", + cimg_instance); +#ifndef cimg_use_minc2 + return load_other(filename); +#else + minc::minc_1_reader rdr; + rdr.open(filename); + assign(rdr.ndim(1)?rdr.ndim(1):1, + rdr.ndim(2)?rdr.ndim(2):1, + rdr.ndim(3)?rdr.ndim(3):1, + rdr.ndim(4)?rdr.ndim(4):1); + if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_byte(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_int(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_double(); + else + rdr.setup_read_float(); + minc::load_standard_volume(rdr,this->_data); + return *this; +#endif + } + + //! Load image from a MINC2 file \newinstance. + static CImg get_load_minc2(const char *const filename) { + return CImg().load_analyze(filename); + } + + //! Load image from an ANALYZE7.5/NIFTI file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_analyze(const char *const filename, float *const voxel_size=0) { + return _load_analyze(0,filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(const char *const filename, float *const voxel_size=0) { + return CImg().load_analyze(filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \overloading. + CImg& load_analyze(std::FILE *const file, float *const voxel_size=0) { + return _load_analyze(file,0,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(std::FILE *const file, float *const voxel_size=0) { + return CImg().load_analyze(file,voxel_size); + } + + CImg& _load_analyze(std::FILE *const file, const char *const filename, float *const voxel_size=0) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_analyze(): Specified filename is (null).", + cimg_instance); + + std::FILE *nfile_header = 0, *nfile = 0; + if (!file) { + CImg body(1024); + const char *const ext = cimg::split_filename(filename,body); + if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file + nfile_header = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".img"); + nfile = cimg::fopen(body,"rb"); + } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file + nfile = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".hdr"); + nfile_header = cimg::fopen(body,"rb"); + } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file + } else nfile_header = nfile = file; // File is a Niftii file + if (!nfile || !nfile_header) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid Analyze7.5 or NIFTI header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + // Read header. + bool endian = false; + unsigned int header_size; + cimg::fread(&header_size,1,nfile_header); + if (!header_size) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid zero-size header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); } + + unsigned char *const header = new unsigned char[header_size]; + cimg::fread(header + 4,header_size - 4,nfile_header); + if (!file && nfile_header!=nfile) cimg::fclose(nfile_header); + if (endian) { + cimg::invert_endianness((short*)(header + 40),5); + cimg::invert_endianness((short*)(header + 70),1); + cimg::invert_endianness((short*)(header + 72),1); + cimg::invert_endianness((float*)(header + 76),4); + cimg::invert_endianness((float*)(header + 108),1); + cimg::invert_endianness((float*)(header + 112),1); + } + + if (nfile_header==nfile) { + const unsigned int vox_offset = (unsigned int)*(float*)(header + 108); + std::fseek(nfile,vox_offset,SEEK_SET); + } + + unsigned short *dim = (unsigned short*)(header + 40), dimx = 1, dimy = 1, dimz = 1, dimv = 1; + if (!dim[0]) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with zero dimensions.", + cimg_instance, + filename?filename:"(FILE*)"); + + if (dim[0]>4) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with %u dimensions, reading only the 4 first.", + cimg_instance, + filename?filename:"(FILE*)",dim[0]); + + if (dim[0]>=1) dimx = dim[1]; + if (dim[0]>=2) dimy = dim[2]; + if (dim[0]>=3) dimz = dim[3]; + if (dim[0]>=4) dimv = dim[4]; + float scalefactor = *(float*)(header + 112); if (scalefactor==0) scalefactor = 1; + const unsigned short datatype = *(unsigned short*)(header + 70); + if (voxel_size) { + const float *vsize = (float*)(header + 76); + voxel_size[0] = vsize[1]; voxel_size[1] = vsize[2]; voxel_size[2] = vsize[3]; + } + delete[] header; + + // Read pixel data. + assign(dimx,dimy,dimz,dimv); + const size_t pdim = (size_t)dimx*dimy*dimz*dimv; + switch (datatype) { + case 2 : { + unsigned char *const buffer = new unsigned char[pdim]; + cimg::fread(buffer,pdim,nfile); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 4 : { + short *const buffer = new short[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 8 : { + int *const buffer = new int[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 16 : { + float *const buffer = new float[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 64 : { + double *const buffer = new double[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_analyze(): Unable to load datatype %d in file '%s'", + cimg_instance, + datatype,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a .cimg[z] file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(const char *const filename, const char axis='z', const float align=0) { + return CImg().load_cimg(filename,axis,align); + } + + //! Load image from a .cimg[z] file \overloading. + CImg& load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + return CImg().load_cimg(file,axis,align); + } + + //! Load sub-images of a .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Starting frame. + \param n1 Ending frame (~0U for max). + \param x0 X-coordinate of the starting sub-image vertex. + \param y0 Y-coordinate of the starting sub-image vertex. + \param z0 Z-coordinate of the starting sub-image vertex. + \param c0 C-coordinate of the starting sub-image vertex. + \param x1 X-coordinate of the ending sub-image vertex (~0U for max). + \param y1 Y-coordinate of the ending sub-image vertex (~0U for max). + \param z1 Z-coordinate of the ending sub-image vertex (~0U for max). + \param c1 C-coordinate of the ending sub-image vertex (~0U for max). + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load sub-images of a .cimg file \overloading. + CImg& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load image from an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_inr(const char *const filename, float *const voxel_size=0) { + return _load_inr(0,filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(const char *const filename, float *const voxel_size=0) { + return CImg().load_inr(filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \overloading. + CImg& load_inr(std::FILE *const file, float *const voxel_size=0) { + return _load_inr(file,0,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(std::FILE *const file, float *voxel_size=0) { + return CImg().load_inr(file,voxel_size); + } + + static void _load_inr_header(std::FILE *file, int out[8], float *const voxel_size) { + CImg item(1024), tmp1(64), tmp2(64); + *item = *tmp1 = *tmp2 = 0; + out[0] = std::fscanf(file,"%63s",item._data); + out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1; + if (cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0) + throw CImgIOException("CImg<%s>::load_inr(): INRIMAGE-4 header not found.", + pixel_type()); + + while (std::fscanf(file," %63[^\n]%*c",item._data)!=EOF && std::strncmp(item,"##}",3)) { + cimg_sscanf(item," XDIM%*[^0-9]%d",out); + cimg_sscanf(item," YDIM%*[^0-9]%d",out + 1); + cimg_sscanf(item," ZDIM%*[^0-9]%d",out + 2); + cimg_sscanf(item," VDIM%*[^0-9]%d",out + 3); + cimg_sscanf(item," PIXSIZE%*[^0-9]%d",out + 6); + if (voxel_size) { + cimg_sscanf(item," VX%*[^0-9.+-]%f",voxel_size); + cimg_sscanf(item," VY%*[^0-9.+-]%f",voxel_size + 1); + cimg_sscanf(item," VZ%*[^0-9.+-]%f",voxel_size + 2); + } + if (cimg_sscanf(item," CPU%*[ =]%s",tmp1._data)) out[7] = cimg::strncasecmp(tmp1,"sun",3)?0:1; + switch (cimg_sscanf(item," TYPE%*[ =]%s %s",tmp1._data,tmp2._data)) { + case 0 : break; + case 2 : + out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; + std::strncpy(tmp1,tmp2,tmp1._width - 1); // 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 + default : + throw CImgIOException("CImg<%s>::load_inr(): Invalid pixel type '%s' defined in header.", + pixel_type(), + tmp2._data); + } + } + if (out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0) + throw CImgIOException("CImg<%s>::load_inr(): Invalid dimensions (%d,%d,%d,%d) defined in header.", + pixel_type(), + out[0],out[1],out[2],out[3]); + if (out[4]<0 || out[5]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete pixel type defined in header.", + pixel_type()); + if (out[6]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete PIXSIZE field defined in header.", + pixel_type()); + if (out[7]<0) + throw CImgIOException("CImg<%s>::load_inr(): Big/Little Endian coding type undefined in header.", + pixel_type()); + } + + CImg& _load_inr(std::FILE *const file, const char *const filename, float *const voxel_size) { +#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \ + if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \ + Ts *xval, *const val = new Ts[(size_t)fopt[0]*fopt[3]]; \ + cimg_forYZ(*this,y,z) { \ + cimg::fread(val,fopt[0]*fopt[3],nfile); \ + if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \ + xval = val; cimg_forX(*this,x) cimg_forC(*this,c) (*this)(x,y,z,c) = (T)*(xval++); \ + } \ + delete[] val; \ + loaded = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_inr(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + int fopt[8], endian = cimg::endianness()?1:0; + bool loaded = false; + if (voxel_size) voxel_size[0] = voxel_size[1] = voxel_size[2] = 1; + _load_inr_header(nfile,fopt,voxel_size); + assign(fopt[0],fopt[1],fopt[2],fopt[3]); + _cimg_load_inr_case(0,0,8,unsigned char); + _cimg_load_inr_case(0,1,8,char); + _cimg_load_inr_case(0,0,16,unsigned short); + _cimg_load_inr_case(0,1,16,short); + _cimg_load_inr_case(0,0,32,unsigned int); + _cimg_load_inr_case(0,1,32,int); + _cimg_load_inr_case(1,0,32,float); + _cimg_load_inr_case(1,1,32,float); + _cimg_load_inr_case(1,0,64,double); + _cimg_load_inr_case(1,1,64,double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_inr(): Unknown pixel type defined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a EXR file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_exr(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_exr(): Specified filename is (null).", + cimg_instance); +#if defined(cimg_use_openexr) + Imf::RgbaInputFile file(filename); + Imath::Box2i dw = file.dataWindow(); + const int + inwidth = dw.max.x - dw.min.x + 1, + inheight = dw.max.y - dw.min.y + 1; + Imf::Array2D pixels; + pixels.resizeErase(inheight,inwidth); + file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y*inwidth, 1, inwidth); + file.readPixels(dw.min.y, dw.max.y); + assign(inwidth,inheight,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + cimg_forXY(*this,x,y) { + *(ptr_r++) = (T)pixels[y][x].r; + *(ptr_g++) = (T)pixels[y][x].g; + *(ptr_b++) = (T)pixels[y][x].b; + *(ptr_a++) = (T)pixels[y][x].a; + } +#elif defined(cimg_use_tinyexr) + float *res; + const char *err = 0; + int width = 0, height = 0; + const int ret = LoadEXR(&res,&width,&height,filename,&err); + if (ret) throw CImgIOException(_cimg_instance + "load_exr(): Unable to load EXR file '%s'.", + cimg_instance,filename); + CImg(out,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this); + std::free(res); +#else + return load_other(filename); +#endif + return *this; + } + + //! Load image from a EXR file \newinstance. + static CImg get_load_exr(const char *const filename) { + return CImg().load_exr(filename); + } + + //! Load image from a PANDORE-5 file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pandore(const char *const filename) { + return _load_pandore(0,filename); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(const char *const filename) { + return CImg().load_pandore(filename); + } + + //! Load image from a PANDORE-5 file \overloading. + CImg& load_pandore(std::FILE *const file) { + return _load_pandore(file,0); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(std::FILE *const file) { + return CImg().load_pandore(file); + } + + CImg& _load_pandore(std::FILE *const file, const char *const filename) { +#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \ + cimg::fread(dims,nbdim,nfile); \ + if (endian) cimg::invert_endianness(dims,nbdim); \ + assign(nwidth,nheight,ndepth,ndim); \ + const size_t siz = size(); \ + stype *buffer = new stype[siz]; \ + cimg::fread(buffer,siz,nfile); \ + if (endian) cimg::invert_endianness(buffer,siz); \ + T *ptrd = _data; \ + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \ + buffer-=siz; \ + delete[] buffer + +#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \ + if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \ + else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \ + else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \ + else throw CImgIOException(_cimg_instance \ + "load_pandore(): Unknown pixel datatype in file '%s'.", \ + cimg_instance, \ + filename?filename:"(FILE*)"); } + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pandore(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(32); + cimg::fread(header._data,12,nfile); + if (cimg::strncasecmp("PANDORE",header,7)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): PANDORE header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + unsigned int imageid, dims[8] = { 0 }; + int ptbuf[4] = { 0 }; + cimg::fread(&imageid,1,nfile); + const bool endian = imageid>255; + if (endian) cimg::invert_endianness(imageid); + cimg::fread(header._data,20,nfile); + + switch (imageid) { + case 2 : _cimg_load_pandore_case(2,dims[1],1,1,1,unsigned char,unsigned char,unsigned char,1); break; + case 3 : _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break; + case 4 : _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break; + case 5 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,unsigned char,unsigned char,unsigned char,1); break; + case 6 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break; + case 7 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break; + case 8 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,unsigned char,unsigned char,unsigned char,1); break; + case 9 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break; + case 10 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break; + case 11 : { // Region 1D + cimg::fread(dims,3,nfile); + if (endian) cimg::invert_endianness(dims,3); + assign(dims[1],1,1,1); + const unsigned siz = size(); + if (dims[2]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[2]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 12 : { // Region 2D + cimg::fread(dims,4,nfile); + if (endian) cimg::invert_endianness(dims,4); + assign(dims[2],dims[1],1,1); + const size_t siz = size(); + if (dims[3]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[3]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 13 : { // Region 3D + cimg::fread(dims,5,nfile); + if (endian) cimg::invert_endianness(dims,5); + assign(dims[3],dims[2],dims[1],1); + const size_t siz = size(); + if (dims[4]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[4]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 16 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,unsigned char,unsigned char,unsigned char,1); break; + case 17 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break; + case 18 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break; + case 19 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,unsigned char,unsigned char,unsigned char,1); break; + case 20 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break; + case 21 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break; + case 22 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 23 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4); break; + case 24 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 25 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break; + case 26 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 27 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break; + case 28 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 29 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break; + case 30 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned char,unsigned char,unsigned char,1); + break; + case 31 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break; + case 32 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned long,unsigned int,unsigned short,4); + break; + case 33 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break; + case 34 : { // Points 1D + cimg::fread(ptbuf,1,nfile); + if (endian) cimg::invert_endianness(ptbuf,1); + assign(1); (*this)(0) = (T)ptbuf[0]; + } break; + case 35 : { // Points 2D + cimg::fread(ptbuf,2,nfile); + if (endian) cimg::invert_endianness(ptbuf,2); + assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0]; + } break; + case 36 : { // Points 3D + cimg::fread(ptbuf,3,nfile); + if (endian) cimg::invert_endianness(ptbuf,3); + assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0]; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): Unable to load data with ID_type %u in file '%s'.", + cimg_instance, + imageid,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PAR-REC (Philips) file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_parrec(const char *const filename, const char axis='c', const float align=0) { + CImgList list; + list.load_parrec(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a PAR-REC (Philips) file \newinstance. + static CImg get_load_parrec(const char *const filename, const char axis='c', const float align=0) { + return CImg().load_parrec(filename,axis,align); + } + + //! Load image from a raw binary file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the image buffer. + \param size_y Height of the image buffer. + \param size_z Depth of the image buffer. + \param size_c Spectrum of the image buffer. + \param is_multiplexed Tells if the image values are multiplexed along the C-axis. + \param invert_endianness Tells if the endianness of the image buffer must be inverted. + \param offset Starting offset of the read in the specified file. + **/ + CImg& load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(0,filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \overloading. + CImg& load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(file,0,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(file,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + 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 ulongT offset) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename is (null).", + cimg_instance); + if (cimg::is_directory(filename)) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename '%s' is a directory.", + cimg_instance,filename); + + ulongT siz = (ulongT)size_x*size_y*size_z*size_c; + unsigned int + _size_x = size_x, + _size_y = size_y, + _size_z = size_z, + _size_c = size_c; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + if (!siz) { // Retrieve file size + const longT fpos = cimg::ftell(nfile); + if (fpos<0) throw CImgArgumentException(_cimg_instance + "load_raw(): Cannot determine size of input file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + cimg::fseek(nfile,0,SEEK_END); + siz = cimg::ftell(nfile)/sizeof(T); + _size_y = (unsigned int)siz; + _size_x = _size_z = _size_c = 1; + cimg::fseek(nfile,fpos,SEEK_SET); + } + cimg::fseek(nfile,offset,SEEK_SET); + assign(_size_x,_size_y,_size_z,_size_c,0); + if (siz && (!is_multiplexed || size_c==1)) { + cimg::fread(_data,siz,nfile); + if (invert_endianness) cimg::invert_endianness(_data,siz); + } else if (siz) { + CImg buf(1,1,1,_size_c); + cimg_forXYZ(*this,x,y,z) { + cimg::fread(buf._data,_size_c,nfile); + if (invert_endianness) cimg::invert_endianness(buf._data,_size_c); + set_vector_at(buf,x,y,z); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image sequence from a YUV file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the frames. + \param size_y Height of the frames. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param yuv2rgb Tells if the YUV to RGB transform must be applied. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + **/ + CImg& load_yuv(const char *const filename, + 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') { + return get_load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(const char *const filename, + 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') { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb).get_append(axis); + } + + //! Load image sequence from a YUV file \overloading. + CImg& load_yuv(std::FILE *const file, + 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') { + return get_load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(std::FILE *const file, + 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') { + 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. + /** + \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 + CImg& load_off(CImgList& primitives, CImgList& colors, const char *const filename) { + return _load_off(primitives,colors,0,filename); + } + + //! 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. + 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. + template + static CImg get_load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { + return CImg().load_off(primitives,colors,file); + } + + template + CImg& _load_off(CImgList& primitives, CImgList& colors, + std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_off(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0; + CImg line(256); *line = 0; + int err; + + // Skip comments, and read magic string OFF + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): OFF header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Invalid number of vertices or primitives specified in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read points data + assign(nb_points,3); + float X = 0, Y = 0, Z = 0; + cimg_forX(*this,l) { + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Failed to read vertex %u/%u in file '%s'.", + cimg_instance, + l + 1,nb_points,filename?filename:"(FILE*)"); + } + (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z; + } + + // Read primitive data + primitives.assign(); + colors.assign(); + bool stop_flag = false; + while (!stop_flag) { + float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f; + unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0; + *line = 0; + if ((err = std::fscanf(nfile,"%u",&prim))!=1) stop_flag = true; + else { + ++nb_read; + switch (prim) { + case 1 : { + if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 2 : { + if ((err = std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 3 : { + if ((err = std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line._data))<3) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 4 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line._data))<4) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 5 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line._data))<5) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 6 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line._data))<6) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 7 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i4,i3,i1).move_to(primitives); + CImg::vector(i0,i6,i5,i4).move_to(primitives); + CImg::vector(i3,i2,i1).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + case 8 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + CImg::vector(i0,i7,i6,i5).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + default : + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u (%u vertices) from file '%s'.", + cimg_instance, + nb_read,nb_primitives,prim,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } + } + } + if (!file) cimg::fclose(nfile); + if (primitives._width!=nb_primitives) + cimg::warn(_cimg_instance + "load_off(): Only %u/%u primitives read from file '%s'.", + cimg_instance, + primitives._width,nb_primitives,filename?filename:"(FILE*)"); + return *this; + } + + //! Load image sequence from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param axis Alignment axis. + \param align Apending alignment. + **/ + CImg& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return get_load_video(filename,first_frame,last_frame,step_frame,axis,align).move_to(*this); + } + + //! Load image sequence from a video file, using OpenCV library \newinstance. + static CImg get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame).get_append(axis,align); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg'. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return get_load_ffmpeg_external(filename,axis,align).move_to(*this); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg' \newinstance. + static CImg get_load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return CImgList().load_ffmpeg_external(filename).get_append(axis,align); + } + + //! Load gif file, using Imagemagick or GraphicsMagicks's external tools. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return get_load_gif_external(filename,axis,align).move_to(*this); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tool 'convert' \newinstance. + static CImg get_load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return CImgList().load_gif_external(filename).get_append(axis,align); + } + + //! Load image using GraphicsMagick's external tool 'gm'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_graphicsmagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + 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()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_graphicsmagick_external(): Failed to load file '%s' " + "with external command 'gm'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=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::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::graphicsmagick_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using GraphicsMagick's external tool 'gm' \newinstance. + static CImg get_load_graphicsmagick_external(const char *const filename) { + return CImg().load_graphicsmagick_external(filename); + } + + //! Load gzipped image file, using external tool 'gunzip'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimg_instance + "load_gzip_external(): Specified filename is (null).", + cimg_instance); + 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), + *const ext2 = cimg::split_filename(body,0); + + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load gzipped image file, using external tool 'gunzip' \newinstance. + static CImg get_load_gzip_external(const char *const filename) { + return CImg().load_gzip_external(filename); + } + + //! Load image using ImageMagick's external tool 'convert'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_imagemagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_imagemagick_external(): Specified filename is (null).", + cimg_instance); + 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::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_imagemagick_external(): Failed to load file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s%s \"%s\" \"%s\"", + cimg::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::imagemagick_path()); + 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 " + "external command 'magick/convert'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using ImageMagick's external tool 'convert' \newinstance. + static CImg get_load_imagemagick_external(const char *const filename) { + return CImg().load_imagemagick_external(filename); + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_medcon_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_medcon_external(): Specified filename is (null).", + cimg_instance); + 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=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::medcon_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + cimg::split_filename(filename_tmp,body); + + cimg_snprintf(command,command._width,"%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + throw CImgIOException(_cimg_instance + "load_medcon_external(): Failed to load file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + load_analyze(command); + std::remove(command); + cimg::split_filename(command,body); + cimg_snprintf(command,command._width,"%s.img",body._data); + std::remove(command); + return *this; + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon' \newinstance. + static CImg get_load_medcon_external(const char *const filename) { + return CImg().load_medcon_external(filename); + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_dcraw_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_dcraw_external(): Specified filename is (null).", + cimg_instance); + 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 + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\"", + cimg::dcraw_path(),s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_dcraw_external(): Failed to load file '%s' with external command 'dcraw'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.ppm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\" > \"%s\"", + cimg::dcraw_path(),s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::dcraw_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw' \newinstance. + static CImg get_load_dcraw_external(const char *const filename) { + return CImg().load_dcraw_external(filename); + } + + //! Load image from a camera stream, using OpenCV. + /** + \param camera_index Index of the camera to capture images from. + \param skip_frames Number of frames to skip before the capture. + \param release_camera Tells if the camera ressource must be released at the end of the method. + \param capture_width Width of the desired image. + \param capture_height Height of the desired image. + **/ + 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) { +#ifdef cimg_use_opencv + if (camera_index>99) + throw CImgArgumentException(_cimg_instance + "load_camera(): Invalid request for camera #%u " + "(no more than 100 cameras can be managed simultaneously).", + cimg_instance, + camera_index); + static CvCapture *capture[100] = { 0 }; + static unsigned int capture_w[100], capture_h[100]; + if (release_camera) { + cimg::mutex(9); + if (capture[camera_index]) cvReleaseCapture(&(capture[camera_index])); + capture[camera_index] = 0; + capture_w[camera_index] = capture_h[camera_index] = 0; + cimg::mutex(9,0); + return *this; + } + if (!capture[camera_index]) { + cimg::mutex(9); + capture[camera_index] = cvCreateCameraCapture(camera_index); + capture_w[camera_index] = 0; + capture_h[camera_index] = 0; + cimg::mutex(9,0); + if (!capture[camera_index]) { + throw CImgIOException(_cimg_instance + "load_camera(): Failed to initialize camera #%u.", + cimg_instance, + camera_index); + } + } + cimg::mutex(9); + if (capture_width!=capture_w[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_WIDTH,capture_width); + capture_w[camera_index] = capture_width; + } + if (capture_height!=capture_h[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_HEIGHT,capture_height); + capture_h[camera_index] = capture_height; + } + const IplImage *img = 0; + for (unsigned int i = 0; 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++); + } + } + cimg::mutex(9,0); + return *this; +#else + cimg::unused(camera_index,skip_frames,release_camera,capture_width,capture_height); + throw CImgIOException(_cimg_instance + "load_camera(): This function requires the OpenCV library to run " + "(macro 'cimg_use_opencv' must be defined).", + cimg_instance); +#endif + } + + //! Load image from a camera stream, using OpenCV \newinstance. + static CImg 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); + } + + //! Load image using various non-native ways. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_other(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_other(): Specified filename is (null).", + cimg_instance); + + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_magick(filename); } + catch (CImgException&) { + try { load_imagemagick_external(filename); } + catch (CImgException&) { + try { load_graphicsmagick_external(filename); } + catch (CImgException&) { + try { load_cimg(filename); } + catch (CImgException&) { + try { + cimg::fclose(cimg::fopen(filename,"rb")); + } catch (CImgException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to open file '%s'.", + cimg_instance, + filename); + } + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image using various non-native ways \newinstance. + static CImg get_load_other(const char *const filename) { + return CImg().load_other(filename); + } + + //@} + //--------------------------- + // + //! \name Data Output + //@{ + //--------------------------- + + //! Display information about the image data. + /** + \param title Name for the considered image. + \param display_stats Tells to compute and display image statistics. + **/ + const CImg& print(const char *const title=0, const bool display_stats=true) const { + + int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0; + CImg st; + if (!is_empty() && display_stats) { + st = get_stats(); + xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7]; + xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11]; + } + + const ulongT siz = size(), msiz = siz*sizeof(T), siz1 = siz - 1, + mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U, width1 = _width - 1; + + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImg<%s>",pixel_type()); + + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = (%u,%u,%u,%u) [%lu %s], %sdata%s = (%s*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum, + (unsigned long)(mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20))), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) + std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end() - 1),_is_shared?"shared":"non-shared"); + else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared"); + + if (!is_empty()) cimg_foroff(*this,off) { + std::fprintf(cimg::output(),"%g",(double)_data[off]); + if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" "); + if (off==7 && siz>16) { off = siz1 - 8; std::fprintf(cimg::output(),"... "); } + } + if (!is_empty() && display_stats) + std::fprintf(cimg::output(), + " ], %smin%s = %g, %smax%s = %g, %smean%s = %g, %sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), " + "%scoords_max%s = (%u,%u,%u,%u).\n", + cimg::t_bold,cimg::t_normal,st[0], + cimg::t_bold,cimg::t_normal,st[1], + cimg::t_bold,cimg::t_normal,st[2], + cimg::t_bold,cimg::t_normal,std::sqrt(st[3]), + cimg::t_bold,cimg::t_normal,xm,ym,zm,vm, + cimg::t_bold,cimg::t_normal,xM,yM,zM,vM); + else std::fprintf(cimg::output(),"%s].\n",is_empty()?"":" "); + std::fflush(cimg::output()); + return *this; + } + + //! Display image into a CImgDisplay window. + /** + \param disp Display window. + **/ + const CImg& display(CImgDisplay& disp) const { + disp.display(*this); + return *this; + } + + //! Display image into a CImgDisplay window, in an interactive way. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(CImgDisplay &disp, const bool display_info, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + return _display(disp,0,display_info,XYZ,exit_on_anykey,false); + } + + //! Display image into an interactive window. + /** + \param title Window title + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(const char *const title=0, const bool display_info=true, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display(disp,title,display_info,XYZ,exit_on_anykey,false); + } + + const CImg& _display(CImgDisplay &disp, const char *const title, const bool display_info, + unsigned int *const XYZ, const bool exit_on_anykey, + const bool exit_on_simpleclick) const { + unsigned int oldw = 0, oldh = 0, _XYZ[3] = { 0 }, key = 0; + int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1, + old_mouse_x = -1, old_mouse_y = -1; + + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + else disp.set_title("%s",title); + } else if (title) disp.set_title("%s",title); + disp.show().flush(); + + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(dtitle); + + CImg zoom; + for (bool reset_view = true, resize_disp = false, is_first_select = true; !key && !disp.is_closed(); ) { + if (reset_view) { + if (XYZ) { _XYZ[0] = XYZ[0]; _XYZ[1] = XYZ[1]; _XYZ[2] = XYZ[2]; } + else { + _XYZ[0] = (unsigned int)(x0 + x1)/2; + _XYZ[1] = (unsigned int)(y0 + y1)/2; + _XYZ[2] = (unsigned int)(z0 + z1)/2; + } + x0 = 0; y0 = 0; z0 = 0; x1 = width() - 1; y1 = height() - 1; z1 = depth() - 1; + 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) { + if (is_empty()) zoom.assign(1,1,1,1,(T)0); else zoom.assign(); + } else zoom = get_crop(x0,y0,z0,x1,y1,z1); + + const CImg& visu = zoom?zoom:*this; + const unsigned int + dx = 1U + x1 - x0, dy = 1U + y1 - y0, dz = 1U + z1 - z0, + tw = dx + (dz>1?dz:0U), th = dy + (dz>1?dz:0U); + if (!is_empty() && !disp.is_fullscreen() && resize_disp) { + const 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 + 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; + } + oldw = tw; oldh = th; + + bool + go_up = false, go_down = false, go_left = false, go_right = false, + go_inc = false, go_dec = false, go_in = false, go_out = false, + go_in_center = false; + + disp.set_title("%s",dtitle._data); + if (_width>1 && visu._width==1) disp.set_title("%s | x=%u",disp._title,x0); + if (_height>1 && visu._height==1) disp.set_title("%s | y=%u",disp._title,y0); + if (_depth>1 && visu._depth==1) disp.set_title("%s | z=%u",disp._title,z0); + + disp._mouse_x = old_mouse_x; disp._mouse_y = old_mouse_y; + CImg selection = visu._select(disp,0,2,_XYZ,x0,y0,z0,true,is_first_select,_depth>1,true); + old_mouse_x = disp._mouse_x; old_mouse_y = disp._mouse_y; + is_first_select = false; + + if (disp.wheel()) { + if ((disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) && + (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT())) { + go_left = !(go_right = disp.wheel()>0); + } else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) { + go_down = !(go_up = disp.wheel()>0); + } else if (depth()==1 || disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + go_out = !(go_in = disp.wheel()>0); go_in_center = false; + } + disp.set_wheel(); + } + + const int + sx0 = selection(0), sy0 = selection(1), sz0 = selection(2), + sx1 = selection(3), sy1 = selection(4), sz1 = selection(5); + if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) { + x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; + x0+=sx0; y0+=sy0; z0+=sz0; + if ((sx0==sx1 && sy0==sy1) || (_depth>1 && sx0==sx1 && sz0==sz1) || (_depth>1 && sy0==sy1 && sz0==sz1)) { + if (exit_on_simpleclick && (!zoom || is_empty())) break; else reset_view = true; + } + resize_disp = true; + } else switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : key = 0; break; + case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) { + // Special mode: play stack of frames + const unsigned int + w1 = visu._width*disp.width()/(visu._width + (visu._depth>1?visu._depth:0)), + h1 = visu._height*disp.height()/(visu._height + (visu._depth>1?visu._depth:0)); + float frame_timing = 5; + bool is_stopped = false; + disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0; + for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) { + if (disp.is_resized()) disp.resize(false); + if (!timer) { + visu.get_slice((int)_XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),_XYZ[2])); + (++_XYZ[2])%=visu._depth; + } + if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U; + if (disp.wheel()) { frame_timing-=disp.wheel()/3.f; disp.set_wheel(); } + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break; + case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break; + case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true; + (_XYZ[2]+=visu._depth - 2)%=visu._depth; timer = 0; key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false). + toggle_fullscreen().set_key(key,false); key = 0; + } break; + } + frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing); + disp.wait(20); + } + const unsigned int + w2 = (visu._width + (visu._depth>1?visu._depth:0))*disp.width()/visu._width, + h2 = (visu._height + (visu._depth>1?visu._depth:0))*disp.height()/visu._height; + disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(dtitle.data()).set_key().set_button().set_wheel(); + key = 0; + } break; + case cimg::keyHOME : reset_view = resize_disp = true; key = 0; break; + case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break; + case cimg::keyPADSUB : go_out = true; key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break; + case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break; + case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break; + case cimg::keyPAD7 : go_up = go_left = true; key = 0; break; + case cimg::keyPAD9 : go_up = go_right = true; key = 0; break; + case cimg::keyPAD1 : go_down = go_left = true; key = 0; break; + case cimg::keyPAD3 : go_down = go_right = true; key = 0; break; + case cimg::keyPAGEUP : go_inc = true; key = 0; break; + case cimg::keyPAGEDOWN : go_dec = true; key = 0; break; + } + if (go_in) { + const int + mx = go_in_center?disp.width()/2:disp.mouse_x(), + my = go_in_center?disp.height()/2:disp.mouse_y(), + mX = mx*(width() + (depth()>1?depth():0))/disp.width(), + mY = my*(height() + (depth()>1?depth():0))/disp.height(); + int X = (int)_XYZ[0], Y = (int)_XYZ[1], Z = (int)_XYZ[2]; + if (mX=height()) { + X = x0 + mX*(1 + x1 - x0)/width(); Z = z0 + (mY - height())*(1 + z1 - z0)/depth(); + } + if (mX>=width() && mY4) { x0 = X - 3*(X - x0)/4; x1 = X + 3*(x1 - X)/4; } + if (y1 - y0>4) { y0 = Y - 3*(Y - y0)/4; y1 = Y + 3*(y1 - Y)/4; } + if (z1 - z0>4) { z0 = Z - 3*(Z - z0)/4; z1 = Z + 3*(z1 - Z)/4; } + } + if (go_out) { + const int + delta_x = (x1 - x0)/8, delta_y = (y1 - y0)/8, delta_z = (z1 - z0)/8, + ndelta_x = delta_x?delta_x:(_width>1), + ndelta_y = delta_y?delta_y:(_height>1), + ndelta_z = delta_z?delta_z:(_depth>1); + x0-=ndelta_x; y0-=ndelta_y; z0-=ndelta_z; + x1+=ndelta_x; y1+=ndelta_y; z1+=ndelta_z; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=width()) x1 = width() - 1; } + if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; } + if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; } + if (x1>=width()) { x0-=(x1 - width() + 1); x1 = width() - 1; if (x0<0) x0 = 0; } + if (y1>=height()) { y0-=(y1 - height() + 1); y1 = height() - 1; if (y0<0) y0 = 0; } + if (z1>=depth()) { z0-=(z1 - depth() + 1); z1 = depth() - 1; if (z0<0) z0 = 0; } + const float + ratio = (float)(x1-x0)/(y1-y0), + ratiow = (float)disp._width/disp._height, + sub = std::min(cimg::abs(ratio - ratiow),cimg::abs(1/ratio-1/ratiow)); + if (sub>0.01) resize_disp = true; + } + if (go_left) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + } + if (go_right) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x1+ndelta1); + if (y0 - ndelta>=0) { y0-=ndelta; y1-=ndelta; } + else { y1-=y0; y0 = 0; } + } + if (go_down) { + const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1); + if (y1+ndelta1); + if (z0 - ndelta>=0) { z0-=ndelta; z1-=ndelta; } + else { z1-=z0; z0 = 0; } + } + if (go_dec) { + const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1); + if (z1+ndelta + const CImg& display_object3d(CImgDisplay& disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return _display_object3d(disp,0,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + template + const CImg& _display_object3d(CImgDisplay& disp, const char *const title, + 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 float light_x, const float light_y, const float light_z, + const float specular_lightness, const float specular_shininess, + const bool display_axes, float *const pose_matrix, + const bool exit_on_anykey) const { + typedef typename cimg::superset::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); + } 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).", + 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, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3); + if (!title) disp.set_title("CImg<%s> (%u vertices, %u primitives)", + pixel_type(),vertices._width,primitives._width); + } else if (title) disp.set_title("%s",title); + + // Init 3D objects and compute object statistics + CImg + pose, + rotated_vertices(vertices._width,3), + bbox_vertices, rotated_bbox_vertices, + axes_vertices, rotated_axes_vertices, + bbox_opacities, axes_opacities; + CImgList bbox_primitives, axes_primitives; + CImgList reverse_primitives; + CImgList bbox_colors, bbox_colors2, axes_colors; + unsigned int ns_width = 0, ns_height = 0; + int _is_double_sided = (int)is_double_sided; + bool ndisplay_axes = display_axes; + const CImg + background_color(1,1,1,_spectrum,0), + foreground_color(1,1,1,_spectrum,255); + float + Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1, + xm = 0, xM = vertices?vertices.get_shared_row(0).max_min(xm):0, + ym = 0, yM = vertices?vertices.get_shared_row(1).max_min(ym):0, + zm = 0, zM = vertices?vertices.get_shared_row(2).max_min(zm):0; + const float delta = cimg::max(xM - xm,yM - ym,zM - zm); + + rotated_bbox_vertices = bbox_vertices.assign(8,3,1,1, + xm,xM,xM,xm,xm,xM,xM,xm, + ym,ym,yM,yM,ym,ym,yM,yM, + zm,zm,zm,zm,zM,zM,zM,zM); + bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6); + bbox_colors.assign(6,_spectrum,1,1,1,background_color[0]); + bbox_colors2.assign(6,_spectrum,1,1,1,foreground_color[0]); + bbox_opacities.assign(bbox_colors._width,1,1,1,0.3f); + + rotated_axes_vertices = axes_vertices.assign(7,3,1,1, + 0,20,0,0,22,-6,-6, + 0,0,20,0,-6,22,-6, + 0,0,0,20,0,0,22); + axes_opacities.assign(3,1,1,1,1); + axes_colors.assign(3,_spectrum,1,1,1,foreground_color[0]); + axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3); + + // Begin user interaction loop + CImg 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; + int + x0 = 0, y0 = 0, x1 = 0, y1 = 0, + nrender_static = render_static, + nrender_motion = render_motion; + disp.show().flush(); + + while (!disp.is_closed() && !key) { + + // Init object pose + if (init_pose) { + const float + ratio = delta>0?(2.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); + else CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose); + if (pose_matrix) { + CImg pose0(pose_matrix,4,3,1,1,false); + pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0); + pose0(3,3) = pose(3,3) = 1; + (pose0*pose).get_crop(0,0,3,2).move_to(pose); + Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15]; + } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; } + init_pose = false; + redraw = true; + } + + // Rotate and draw 3D object + if (redraw) { + const float + r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0), + r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1), + r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2); + if ((clicked && nrender_motion>=0) || (!clicked && nrender_static>=0)) + cimg_forX(vertices,l) { + const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2); + rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + else cimg_forX(bbox_vertices,l) { + const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2); + rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + + // Draw objects + const bool render_with_zbuffer = !clicked && nrender_static>0; + visu = visu0; + if ((clicked && nrender_motion<0) || (!clicked && nrender_static<0)) + visu.draw_object3d(Xoff + visu._width/2.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.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.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.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 + n = 1e-8f + cimg::hypot(r00,r01,r02), + _r00 = r00/n, _r10 = r10/n, _r20 = r20/n, + _r01 = r01/n, _r11 = r11/n, _r21 = r21/n, + _r02 = r01/n, _r12 = r12/n, _r22 = r22/n, + Xaxes = 25, Yaxes = visu._height - 38.f; + cimg_forX(axes_vertices,l) { + const float + x = axes_vertices(l,0), + y = axes_vertices(l,1), + z = axes_vertices(l,2); + rotated_axes_vertices(l,0) = _r00*x + _r10*y + _r20*z; + rotated_axes_vertices(l,1) = _r01*x + _r11*y + _r21*z; + rotated_axes_vertices(l,2) = _r02*x + _r12*y + _r22*z; + } + axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.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)), + (int)(Yaxes + rotated_axes_vertices(4,1)), + "X",axes_colors[0]._data,0,axes_opacities(0,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(5,0)), + (int)(Yaxes + rotated_axes_vertices(5,1)), + "Y",axes_colors[1]._data,0,axes_opacities(1,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(6,0)), + (int)(Yaxes + rotated_axes_vertices(6,1)), + "Z",axes_colors[2]._data,0,axes_opacities(2,0),13); + } + visu.display(disp); + if (!clicked || nrender_motion==nrender_static) redraw = false; + } + + // Handle user interaction + disp.wait(); + if ((disp.button() || disp.wheel()) && disp.mouse_x()>=0 && disp.mouse_y()>=0) { + redraw = true; + if (!clicked) { x0 = x1 = disp.mouse_x(); y0 = y1 = disp.mouse_y(); if (!disp.wheel()) clicked = true; } + else { x1 = disp.mouse_x(); y1 = disp.mouse_y(); } + if (disp.button()&1) { + const float + R = 0.45f*std::min(disp.width(),disp.height()), + R2 = R*R, + u0 = (float)(x0 - disp.width()/2), + v0 = (float)(y0 - disp.height()/2), + u1 = (float)(x1 - disp.width()/2), + v1 = (float)(y1 - disp.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + (CImg::rotation_matrix(u,v,w,-alpha)*pose).move_to(pose); + x0 = x1; y0 = y1; + } + if (disp.button()&2) { + if (focale>0) Zoff-=(y0 - y1)*focale/400; + else { const float s = std::exp((y0 - y1)/400.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.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)) { + 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); + } + } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; } + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD: if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + if (!ns_width || !ns_height || + ns_width>(unsigned int)disp.screen_width() || ns_height>(unsigned int)disp.screen_height()) { + ns_width = disp.screen_width()*3U/4; + ns_height = disp.screen_height()*3U/4; + } + if (disp.is_fullscreen()) disp.resize(ns_width,ns_height,false); + else { + ns_width = disp._width; ns_height = disp._height; + disp.resize(disp.screen_width(),disp.screen_height(),false); + } + disp.toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyT : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Switch single/double-sided primitives. + if (--_is_double_sided==-2) _is_double_sided = 1; + if (_is_double_sided>=0) reverse_primitives.assign(); + else primitives.get_reverse_object3d().move_to(reverse_primitives); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyZ : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Enable/disable Z-buffer + if (zbuffer) zbuffer.assign(); + else zbuffer.assign(visu0.width(),visu0.height(),1,1,0); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Show/hide 3D axes + ndisplay_axes = !ndisplay_axes; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF1 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to points + nrender_motion = (nrender_static==0 && nrender_motion!=0)?0:-1; nrender_static = 0; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF2 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to lines + nrender_motion = (nrender_static==1 && nrender_motion!=1)?1:-1; nrender_static = 1; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF3 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat + nrender_motion = (nrender_static==2 && nrender_motion!=2)?2:-1; nrender_static = 2; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF4 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat-shaded + nrender_motion = (nrender_static==3 && nrender_motion!=3)?3:-1; nrender_static = 3; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF5 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Set rendering mode to gouraud-shaded. + nrender_motion = (nrender_static==4 && nrender_motion!=4)?4:-1; nrender_static = 4; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF6 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to phong-shaded + nrender_motion = (nrender_static==5 && nrender_motion!=5)?5:-1; nrender_static = 5; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save snapshot + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving snapshot... ",false).display(disp); + visu.save(filename); + (+visu).__draw_text(" Snapshot '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.save_off(reverse_primitives?reverse_primitives:primitives,colors,filename); + (+visu).__draw_text(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities). + save(filename); + (+visu).__draw_text(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving EPS snapshot... ",false).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving SVG snapshot... ",false,13).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; +#endif + } + if (disp.is_resized()) { + disp.resize(false); visu0 = get_resize(disp,1); + if (zbuffer) zbuffer.assign(disp.width(),disp.height()); + redraw = true; + } + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + if (pose_matrix) { + std::memcpy(pose_matrix,pose._data,12*sizeof(float)); + pose_matrix[12] = Xoff; pose_matrix[13] = Yoff; pose_matrix[14] = Zoff; pose_matrix[15] = sprite_scale; + } + disp.set_button().set_key(key); + return *this; + } + + //! Display 1D graph in an interactive window. + /** + \param disp Display window. + \param plot_type Plot type. Can be { 0=points | 1=segments | 2=splines | 3=bars }. + \param vertex_type Vertex type. + \param labelx Title for the horizontal axis, as a C-string. + \param xmin Minimum value along the X-axis. + \param xmax Maximum value along the X-axis. + \param labely Title for the vertical axis, as a C-string. + \param ymin Minimum value along the X-axis. + \param ymax Maximum value along the X-axis. + \param exit_on_anykey Exit function when any key is pressed. + **/ + const CImg& display_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + return _display_graph(disp,0,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + //! Display 1D graph in an interactive window \overloading. + const CImg& display_graph(const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_graph(disp,title,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + const CImg& _display_graph(CImgDisplay &disp, const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "display_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title(title?"%s":"CImg<%s>",title?title:pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth, siz1 = std::max((ulongT)1,siz - 1); + const unsigned int old_normalization = disp.normalization(); + disp.show().flush()._normalization = 0; + + double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax; + if (nxmin==nxmax) { nxmin = 0; nxmax = siz1; } + int x0 = 0, x1 = width()*height()*depth() - 1, key = 0; + + for (bool reset_view = true; !key && !disp.is_closed(); ) { + if (reset_view) { x0 = 0; x1 = width()*height()*depth() - 1; y0 = ymin; y1 = ymax; reset_view = false; } + CImg zoom(x1 - x0 + 1,1,1,spectrum()); + cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg(data(x0,0,0,c),x1 - x0 + 1,1,1,1,true); + if (y0==y1) { y0 = zoom.min_max(y1); const double dy = y1 - y0; y0-=dy/20; y1+=dy/20; } + if (y0==y1) { --y0; ++y1; } + + const CImg selection = zoom.get_select_graph(disp,plot_type,vertex_type, + labelx, + nxmin + x0*(nxmax - nxmin)/siz1, + nxmin + x1*(nxmax - nxmin)/siz1, + labely,y0,y1,true); + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + if (selection[0]>=0) { + if (selection[2]<0) reset_view = true; + else { + x1 = x0 + selection[2]; x0+=selection[0]; + if (selection[1]>=0 && selection[3]>=0) { + y0 = y1 - selection[3]*(y1 - y0)/(disp.height() - 32); + y1-=selection[1]*(y1 - y0)/(disp.height() - 32); + } + } + } else { + bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false; + switch (key = (int)disp.key()) { + case cimg::keyHOME : reset_view = true; key = 0; disp.set_key(); break; + case cimg::keyPADADD : go_in = true; go_out = false; key = 0; disp.set_key(); break; + case cimg::keyPADSUB : go_out = true; go_in = false; key = 0; disp.set_key(); break; + case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; go_right = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; go_left = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; go_down = false; key = 0; disp.set_key(); break; + case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; go_up = false; key = 0; disp.set_key(); break; + case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; disp.set_key(); break; + case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; disp.set_key(); break; + } + if (disp.wheel()) { + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) go_up = !(go_down = disp.wheel()<0); + else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0); + else go_out = !(go_in = disp.wheel()>0); + key = 0; + } + + if (go_in) { + const int + xsiz = x1 - x0, + mx = (mouse_x - 16)*xsiz/(disp.width() - 32), + cx = x0 + cimg::cut(mx,0,xsiz); + if (x1 - x0>4) { + x0 = cx - 7*(cx - x0)/8; x1 = cx + 7*(x1 - cx)/8; + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + const double + ysiz = y1 - y0, + my = (mouse_y - 16)*ysiz/(disp.height() - 32), + cy = y1 - cimg::cut(my,0.,ysiz); + y0 = cy - 7*(cy - y0)/8; y1 = cy + 7*(y1 - cy)/8; + } else y0 = y1 = 0; + } + } + if (go_out) { + if (x0>0 || x1<(int)siz1) { + const int delta_x = (x1 - x0)/8, ndelta_x = delta_x?delta_x:(siz>1); + const double ndelta_y = (y1 - y0)/8; + x0-=ndelta_x; x1+=ndelta_x; + y0-=ndelta_y; y1+=ndelta_y; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz1; } + if (x1>=(int)siz) { x0-=(x1 - siz1); x1 = (int)siz1; if (x0<0) x0 = 0; } + } + } + if (go_left) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + go_left = false; + } + if (go_right) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x1 + ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; } + else { x0+=(siz1 - x1); x1 = (int)siz1; } + go_right = false; + } + if (go_up) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0+=ndelta; y1+=ndelta; + go_up = false; + } + if (go_down) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0-=ndelta; y1-=ndelta; + go_down = false; + } + } + if (!exit_on_anykey && key && key!=(int)cimg::keyESC && + (key!=(int)cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + key = 0; + } + } + disp._normalization = old_normalization; + return *this; + } + + //! Save image as a file. + /** + \param filename Filename, as a C-string. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + \note + - The used file format is defined by the file extension in the filename \p filename. + - Parameter \p number can be used to add a 6-digit number to the filename before saving. + + **/ + const CImg& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save(): Specified filename is (null).", + cimg_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:(number>=0)?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimg_save_plugin + cimg_save_plugin(fn); +#endif +#ifdef cimg_save_plugin1 + cimg_save_plugin1(fn); +#endif +#ifdef cimg_save_plugin2 + cimg_save_plugin2(fn); +#endif +#ifdef cimg_save_plugin3 + cimg_save_plugin3(fn); +#endif +#ifdef cimg_save_plugin4 + cimg_save_plugin4(fn); +#endif +#ifdef cimg_save_plugin5 + cimg_save_plugin5(fn); +#endif +#ifdef cimg_save_plugin6 + cimg_save_plugin6(fn); +#endif +#ifdef cimg_save_plugin7 + cimg_save_plugin7(fn); +#endif +#ifdef cimg_save_plugin8 + cimg_save_plugin8(fn); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) return save_dlm(fn); + else if (!cimg::strcasecmp(ext,"cpp") || + !cimg::strcasecmp(ext,"hpp") || + !cimg::strcasecmp(ext,"h") || + !cimg::strcasecmp(ext,"c")) return save_cpp(fn); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn); + else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn); + else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn); + else if (!cimg::strcasecmp(ext,"png")) return save_png(fn); + else if (!cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn); + else if (!cimg::strcasecmp(ext,"pnk")) return save_pnk(fn); + else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn); + else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) return save_analyze(fn); + else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn); + else if (!cimg::strcasecmp(ext,"mnc")) return save_minc2(fn); + else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn); + else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + + // Image sequences + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); + return save_other(fn); + } + + //! Save image as an Ascii file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_ascii(const char *const filename) const { + return _save_ascii(0,filename); + } + + //! Save image as an Ascii file \overloading. + const CImg& save_ascii(std::FILE *const file) const { + return _save_ascii(file,0); + } + + const CImg& _save_ascii(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_ascii(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + std::fprintf(nfile,"%u %u %u %u\n",_width,_height,_depth,_spectrum); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g ",(double)*(ptrs++)); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .cpp source file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_cpp(const char *const filename) const { + return _save_cpp(0,filename); + } + + //! Save image as a .cpp source file \overloading. + const CImg& save_cpp(std::FILE *const file) const { + return _save_cpp(file,0); + } + + const CImg& _save_cpp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_cpp(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + CImg varname(1024); *varname = 0; + if (filename) cimg_sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname._data); + if (!*varname) cimg_snprintf(varname,varname._width,"unnamed"); + std::fprintf(nfile, + "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n" + "%s data_%s[] = { %s\n ", + varname._data,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname._data, + is_empty()?"};":""); + if (!is_empty()) for (ulongT off = 0, siz = size() - 1; off<=siz; ++off) { + std::fprintf(nfile,cimg::type::format(),cimg::type::format((*this)[off])); + if (off==siz) std::fprintf(nfile," };\n"); + else if (!((off + 1)%16)) std::fprintf(nfile,",\n "); + else std::fprintf(nfile,", "); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a DLM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_dlm(const char *const filename) const { + return _save_dlm(0,filename); + } + + //! Save image as a DLM file \overloading. + const CImg& save_dlm(std::FILE *const file) const { + return _save_dlm(file,0); + } + + const CImg& _save_dlm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_dlm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is volumetric, values along Z will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is multispectral, values along C will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g%s",(double)*(ptrs++),(x==width() - 1)?"":","); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a BMP file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_bmp(const char *const filename) const { + return _save_bmp(0,filename); + } + + //! Save image as a BMP file \overloading. + const CImg& save_bmp(std::FILE *const file) const { + return _save_bmp(file,0); + } + + const CImg& _save_bmp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_bmp(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_bmp(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_bmp(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(54,1,1,1,0); + unsigned char align_buf[4] = { 0 }; + const unsigned int + align = (4 - (3*_width)%4)%4, + buf_size = (3*_width + align)*height(), + file_size = 54 + buf_size; + header[0] = 'B'; header[1] = 'M'; + header[0x02] = file_size&0xFF; + header[0x03] = (file_size>>8)&0xFF; + header[0x04] = (file_size>>16)&0xFF; + header[0x05] = (file_size>>24)&0xFF; + header[0x0A] = 0x36; + header[0x0E] = 0x28; + header[0x12] = _width&0xFF; + header[0x13] = (_width>>8)&0xFF; + header[0x14] = (_width>>16)&0xFF; + header[0x15] = (_width>>24)&0xFF; + header[0x16] = _height&0xFF; + header[0x17] = (_height>>8)&0xFF; + header[0x18] = (_height>>16)&0xFF; + header[0x19] = (_height>>24)&0xFF; + header[0x1A] = 1; + header[0x1B] = 0; + header[0x1C] = 24; + header[0x1D] = 0; + header[0x22] = buf_size&0xFF; + header[0x23] = (buf_size>>8)&0xFF; + header[0x24] = (buf_size>>16)&0xFF; + header[0x25] = (buf_size>>24)&0xFF; + header[0x27] = 0x1; + header[0x2B] = 0x1; + cimg::fwrite(header._data,54,nfile); + + const T + *ptr_r = data(0,_height - 1,0,0), + *ptr_g = (_spectrum>=2)?data(0,_height - 1,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,_height - 1,0,2):0; + + switch (_spectrum) { + case 1 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + const unsigned char val = (unsigned char)*(ptr_r++); + std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; + } + } break; + case 2 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc(0,nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; + } + } break; + default : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc((unsigned char)(*(ptr_b++)),nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; ptr_b-=2*_width; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a JPEG file. + /** + \param filename Filename, as a C-string. + \param quality Image quality (in %) + **/ + const CImg& save_jpeg(const char *const filename, const unsigned int quality=100) const { + return _save_jpeg(0,filename,quality); + } + + //! Save image as a JPEG file \overloading. + const CImg& save_jpeg(std::FILE *const file, const unsigned int quality=100) const { + return _save_jpeg(file,0,quality); + } + + const CImg& _save_jpeg(std::FILE *const file, const char *const filename, const unsigned int quality) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_jpeg(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_jpeg(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + +#ifndef cimg_use_jpeg + if (!file) return save_other(filename,quality); + else throw CImgIOException(_cimg_instance + "save_jpeg(): Unable to save data in '(*FILE)' unless libjpeg is enabled.", + cimg_instance); +#else + unsigned int dimbuf = 0; + J_COLOR_SPACE colortype = JCS_RGB; + + switch (_spectrum) { + case 1 : dimbuf = 1; colortype = JCS_GRAYSCALE; break; + case 2 : dimbuf = 3; colortype = JCS_RGB; break; + case 3 : dimbuf = 3; colortype = JCS_RGB; break; + default : dimbuf = 4; colortype = JCS_CMYK; break; + } + + // Call libjpeg functions + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + jpeg_stdio_dest(&cinfo,nfile); + cinfo.image_width = _width; + cinfo.image_height = _height; + cinfo.input_components = dimbuf; + cinfo.in_color_space = colortype; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE); + jpeg_start_compress(&cinfo,TRUE); + + JSAMPROW row_pointer[1]; + CImg buffer(_width*dimbuf); + + while (cinfo.next_scanline& save_magick(const char *const filename, const unsigned int bytes_per_pixel=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_magick(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_magick + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_magick(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_magick(): Instance is multispectral, only the three first channels will be " + "saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_magick(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + Magick::Image image(Magick::Geometry(_width,_height),"black"); + image.type(Magick::TrueColorType); + image.depth(bytes_per_pixel?(8*bytes_per_pixel):(stmax>=256?16:8)); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = _spectrum>1?data(0,0,0,1):0, + *ptr_b = _spectrum>2?data(0,0,0,2):0; + Magick::PixelPacket *pixels = image.getPixels(0,0,_width,_height); + switch (_spectrum) { + case 1 : // Scalar images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++); + ++pixels; + } + break; + case 2 : // RG images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = 0; ++pixels; + } + break; + default : // RGB images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = (Magick::Quantum)*(ptr_b++); + ++pixels; + } + } + image.syncPixels(); + image.write(filename); + return *this; +#else + cimg::unused(bytes_per_pixel); + throw CImgIOException(_cimg_instance + "save_magick(): Unable to save file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Save image as a PNG file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving, when possible. + **/ + const CImg& save_png(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_png(0,filename,bytes_per_pixel); + } + + //! Save image as a PNG file \overloading. + const CImg& save_png(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_png(file,0,bytes_per_pixel); + } + + const CImg& _save_png(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_png(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + +#ifndef cimg_use_png + cimg::unused(bytes_per_pixel); + if (!file) return save_other(filename); + else throw CImgIOException(_cimg_instance + "save_png(): Unable to save data in '(*FILE)' unless libpng is enabled.", + cimg_instance); +#else + +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb"); + volatile double stmin, stmax = (double)max_min(stmin); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"wb"); + double stmin, stmax = (double)max_min(stmin); +#endif + + if (_depth>1) + cimg::warn(_cimg_instance + "save_png(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>4) + cimg::warn(_cimg_instance + "save_png(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + // Setup PNG structures for write + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, + user_warning_fn); + if (!png_ptr){ + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'png_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr,(png_infopp)0); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'info_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + + const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8); + + int color_type; + switch (spectrum()) { + case 1 : color_type = PNG_COLOR_TYPE_GRAY; break; + case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; + case 3 : color_type = PNG_COLOR_TYPE_RGB; break; + default : color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + const int interlace_type = PNG_INTERLACE_NONE; + const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT; + const int filter_method = PNG_FILTER_TYPE_DEFAULT; + png_set_IHDR(png_ptr,info_ptr,_width,_height,bit_depth,color_type,interlace_type,compression_type,filter_method); + png_write_info(png_ptr,info_ptr); + const int byte_depth = bit_depth>>3; + const int numChan = spectrum()>4?4:spectrum(); + const int pixel_bit_depth_flag = numChan * (bit_depth - 1); + + // Allocate Memory for Image Save and Fill pixel data + png_bytep *const imgData = new png_byte*[_height]; + for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width]; + const T *pC0 = data(0,0,0,0); + switch (pixel_bit_depth_flag) { + case 7 : { // Gray 8-bit + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++); + } + } break; + case 14 : { // Gray w/ Alpha 8-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + } + } + } break; + case 21 : { // RGB 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + } + } + } break; + case 28 : { // RGB x/ Alpha 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y){ + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x){ + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + *(ptrd++) = (unsigned char)*(pC3++); + } + } + } break; + case 15 : { // Gray 16-bit + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++); + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width); + } + } break; + case 30 : { // Gray w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width); + } + } break; + case 45 : { // RGB 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width); + } + } break; + case 60 : { // RGB w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + *(ptrd++) = (unsigned short)*(pC3++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width); + } + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_write_image(png_ptr,imgData); + png_write_end(png_ptr,info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + // Deallocate Image Write Memory + cimg_forY(*this,n) delete[] imgData[n]; + delete[] imgData; + + if (!file) cimg::fclose(nfile); + return *this; +#endif + } + + //! Save image as a PNM file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving. + **/ + const CImg& save_pnm(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(0,filename,bytes_per_pixel); + } + + //! Save image as a PNM file \overloading. + const CImg& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(file,0,bytes_per_pixel); + } + + const CImg& _save_pnm(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_pnm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pnm(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_pnm(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + stmin,stmax,filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const ulongT buf_size = std::min((ulongT)(1024*1024),(ulongT)(_width*_height*(_spectrum==1?1UL:3UL))); + + std::fprintf(nfile,"P%c\n%u %u\n%u\n", + (_spectrum==1?'5':'6'),_width,_height,stmax<256?255:(stmax<4096?4095:65535)); + + switch (_spectrum) { + case 1 : { // Scalar image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PGM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr_r++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Binary PGM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned short)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + } break; + case 2 : { // RG image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = 0; + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } break; + default : { // RGB image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = (unsigned char)*(ptr_b++); + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = (unsigned short)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PNK file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pnk(const char *const filename) const { + return _save_pnk(0,filename); + } + + //! Save image as a PNK file \overloading. + const CImg& save_pnk(std::FILE *const file) const { + return _save_pnk(file,0); + } + + const CImg& _save_pnk(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnk(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_pnk(): Instance is multispectral, only the first channel will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + const ulongT buf_size = std::min((ulongT)1024*1024,(ulongT)_width*_height*_depth); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T *ptr = data(0,0,0,0); + + if (!cimg::type::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 + 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; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else if (!cimg::type::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); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + int *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (int)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Save as P9: Binary float-valued 3D + if (_depth>1) std::fprintf(nfile,"P9\n%u %u %u\n%g\n",_width,_height,_depth,(double)max()); + else std::fprintf(nfile,"P9\n%u %u\n%g\n",_width,_height,(double)max()); + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PFM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pfm(const char *const filename) const { + get_mirror('y')._save_pfm(0,filename); + return *this; + } + + //! Save image as a PFM file \overloading. + const CImg& save_pfm(std::FILE *const file) const { + get_mirror('y')._save_pfm(file,0); + return *this; + } + + const CImg& _save_pfm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pfm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_pfm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pfm(): image instance is multispectral, only the three first channels will be saved " + "in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const unsigned int buf_size = std::min(1024*1024U,_width*_height*(_spectrum==1?1:3)); + + std::fprintf(nfile,"P%c\n%u %u\n1.0\n", + (_spectrum==1?'f':'F'),_width,_height); + + switch (_spectrum) { + case 1 : { // Scalar image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,(ulongT)buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } break; + case 2 : { // RG image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } break; + default : { // RGB image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = (float)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a RGB file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_rgb(const char *const filename) const { + return _save_rgb(0,filename); + } + + //! Save image as a RGB file \overloading. + const CImg& save_rgb(std::FILE *const file) const { + return _save_rgb(file,0); + } + + const CImg& _save_rgb(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgb(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=3) + cimg::warn(_cimg_instance + "save_rgb(): image instance has not exactly 3 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0; + switch (_spectrum) { + case 1 : { // Scalar image + for (ulongT k = 0; k& save_rgba(const char *const filename) const { + return _save_rgba(0,filename); + } + + //! Save image as a RGBA file \overloading. + const CImg& save_rgba(std::FILE *const file) const { + return _save_rgba(file,0); + } + + const CImg& _save_rgba(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgba(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=4) + cimg::warn(_cimg_instance + "save_rgba(): image instance has not exactly 4 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[4*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0, + *ptr4 = _spectrum>3?data(0,0,0,3):0; + switch (_spectrum) { + case 1 : { // Scalar images + for (ulongT k = 0; k{ 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 use_bigtiff Allow to save big tiff files (>4Gb). + \note + - libtiff support is enabled by defining the precompilation + directive \c cimg_use_tif. + - When libtiff is enabled, 2D and 3D (multipage) several + channel per pixel are supported for + char,uchar,short,ushort,float and \c double pixel types. + - If \c cimg_use_tif 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) + throw CImgArgumentException(_cimg_instance + "save_tiff(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_tiff + const bool + _use_bigtiff = use_bigtiff && sizeof(ulongT)>=8 && size()*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + cimg_forZ(*this,z) _save_tiff(tif,z,z,compression_type,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "save_tiff(): Failed to open file '%s' for writing.", + cimg_instance, + filename); + return *this; +#else + cimg::unused(compression_type,voxel_size,description,use_bigtiff); + return save_other(filename); +#endif + } + +#ifdef cimg_use_tiff + +#define _cimg_save_tiff(types,typed,compression_type) if (!std::strcmp(types,pixel_type())) { \ + const typed foo = (typed)0; return _save_tiff(tif,directory,z,foo,compression_type,voxel_size,description); } + + // [internal] Save a plane into a tiff file + template + const CImg& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, const t& pixel_t, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + if (is_empty() || !tif || pixel_t) return *this; + const char *const filename = TIFFFileName(tif); + uint32 rowsperstrip = (uint32)-1; + uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric; + if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB; + else photometric = PHOTOMETRIC_MINISBLACK; + TIFFSetDirectory(tif,directory); + TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,_width); + TIFFSetField(tif,TIFFTAG_IMAGELENGTH,_height); + if (voxel_size) { + const float vx = voxel_size[0], vy = voxel_size[1], vz = voxel_size[2]; + TIFFSetField(tif,TIFFTAG_RESOLUTIONUNIT,RESUNIT_NONE); + TIFFSetField(tif,TIFFTAG_XRESOLUTION,1.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()); + } + if (description) TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,description); + TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT); + TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp); + if (cimg::type::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3); + else if (cimg::type::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1); + else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2); + double valm, valM = max_min(valm); + TIFFSetField(tif,TIFFTAG_SMINSAMPLEVALUE,valm); + TIFFSetField(tif,TIFFTAG_SMAXSAMPLEVALUE,valM); + TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp); + TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG); + TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric); + TIFFSetField(tif,TIFFTAG_COMPRESSION,compression_type==2?COMPRESSION_JPEG: + compression_type==1?COMPRESSION_LZW:COMPRESSION_NONE); + rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip); + TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip); + TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB); + TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg"); + + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + for (unsigned int row = 0; row<_height; row+=rowsperstrip) { + uint32 nrow = (row + rowsperstrip>_height?_height - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif,row,0); + tsize_t i = 0; + for (unsigned int rr = 0; rr& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + _cimg_save_tiff("bool",unsigned char,compression_type); + _cimg_save_tiff("unsigned char",unsigned char,compression_type); + _cimg_save_tiff("char",char,compression_type); + _cimg_save_tiff("unsigned short",unsigned short,compression_type); + _cimg_save_tiff("short",short,compression_type); + _cimg_save_tiff("unsigned int",unsigned int,compression_type); + _cimg_save_tiff("int",int,compression_type); + _cimg_save_tiff("unsigned int64",unsigned int,compression_type); + _cimg_save_tiff("int64",int,compression_type); + _cimg_save_tiff("float",float,compression_type); + _cimg_save_tiff("double",float,compression_type); + const char *const filename = TIFFFileName(tif); + throw CImgInstanceException(_cimg_instance + "save_tiff(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + return *this; + } +#endif + + //! Save image as a MINC2 file. + /** + \param filename Filename, as a C-string. + \param imitate_file If non-zero, reference filename, as a C-string, to borrow header from. + **/ + const CImg& save_minc2(const char *const filename, + const char *const imitate_file=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_minc2(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_minc2 + cimg::unused(imitate_file); + return save_other(filename); +#else + minc::minc_1_writer wtr; + if (imitate_file) + wtr.open(filename, imitate_file); + else { + minc::minc_info di; + if (width()) di.push_back(minc::dim_info(width(),width()*0.5,-1,minc::dim_info::DIM_X)); + if (height()) di.push_back(minc::dim_info(height(),height()*0.5,-1,minc::dim_info::DIM_Y)); + if (depth()) di.push_back(minc::dim_info(depth(),depth()*0.5,-1,minc::dim_info::DIM_Z)); + if (spectrum()) di.push_back(minc::dim_info(spectrum(),spectrum()*0.5,-1,minc::dim_info::DIM_TIME)); + wtr.open(filename,di,1,NC_FLOAT,0); + } + if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_byte(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_int(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_double(); + else + wtr.setup_write_float(); + minc::save_standard_volume(wtr, this->_data); + return *this; +#endif + } + + //! Save image as an ANALYZE7.5 or NIFTI file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 consecutive values that tell about the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_analyze(const char *const filename, const float *const voxel_size=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_analyze(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + std::FILE *file; + CImg hname(1024), iname(1024); + const char *const ext = cimg::split_filename(filename); + short datatype = -1; + if (!*ext) { + cimg_snprintf(hname,hname._width,"%s.hdr",filename); + cimg_snprintf(iname,iname._width,"%s.img",filename); + } + if (!cimg::strncasecmp(ext,"hdr",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(iname._data + std::strlen(iname) - 3,"img"); + } + if (!cimg::strncasecmp(ext,"img",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(hname._data + std::strlen(iname) - 3,"hdr"); + } + if (!cimg::strncasecmp(ext,"nii",3)) { + std::strncpy(hname,filename,hname._width - 1); *iname = 0; + } + + CImg header(*iname?348:352,1,1,1,0); + int *const iheader = (int*)header._data; + *iheader = 348; + std::strcpy(header._data + 4,"CImg"); + std::strcpy(header._data + 14," "); + ((short*)&(header[36]))[0] = 4096; + ((char*)&(header[38]))[0] = 114; + ((short*)&(header[40]))[0] = 4; + ((short*)&(header[40]))[1] = (short)_width; + ((short*)&(header[40]))[2] = (short)_height; + ((short*)&(header[40]))[3] = (short)_depth; + ((short*)&(header[40]))[4] = (short)_spectrum; + if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"unsigned int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16; + if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64; + if (datatype<0) + throw CImgIOException(_cimg_instance + "save_analyze(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename); + + ((short*)&(header[70]))[0] = datatype; + ((short*)&(header[72]))[0] = sizeof(T); + ((float*)&(header[108]))[0] = (float)(*iname?0:header.width()); + ((float*)&(header[112]))[0] = 1; + ((float*)&(header[76]))[0] = 0; + if (voxel_size) { + ((float*)&(header[76]))[1] = voxel_size[0]; + ((float*)&(header[76]))[2] = voxel_size[1]; + ((float*)&(header[76]))[3] = voxel_size[2]; + } else ((float*)&(header[76]))[1] = ((float*)&(header[76]))[2] = ((float*)&(header[76]))[3] = 1; + file = cimg::fopen(hname,"wb"); + cimg::fwrite(header._data,header.width(),file); + if (*iname) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); } + cimg::fwrite(_data,size(),file); + cimg::fclose(file); + return *this; + } + + //! Save image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param is_compressed Tells if the file contains compressed image data. + **/ + const CImg& save_cimg(const char *const filename, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(filename,is_compressed); + return *this; + } + + //! Save image as a .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(file,is_compressed); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Index of the image inside the file. + \param x0 X-coordinate of the sub-image location. + \param y0 Y-coordinate of the sub-image location. + \param z0 Z-coordinate of the sub-image location. + \param c0 C-coordinate of the sub-image location. + **/ + const CImg& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(filename,n0,x0,y0,z0,c0); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(file,n0,x0,y0,z0,c0); + return *this; + } + + //! Save blank image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param dx Width of the image. + \param dy Height of the image. + \param dz Depth of the image. + \param dc Number of channels of the image. + \note + - All pixel values of the saved image are set to \c 0. + - Use this method to save large images without having to instanciate and allocate them. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(filename,1,dx,dy,dz,dc); + } + + //! Save blank image as a .cimg file \overloading. + /** + Same as save_empty_cimg(const char *,unsigned int,unsigned int,unsigned int,unsigned int) + with a file stream argument instead of a filename string. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(file,1,dx,dy,dz,dc); + } + + //! Save image as an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 values specifying the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_inr(const char *const filename, const float *const voxel_size=0) const { + return _save_inr(0,filename,voxel_size); + } + + //! Save image as an INRIMAGE-4 file \overloading. + const CImg& save_inr(std::FILE *const file, const float *const voxel_size=0) const { + return _save_inr(file,0,voxel_size); + } + + const CImg& _save_inr(std::FILE *const file, const char *const filename, const float *const voxel_size) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_inr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + int inrpixsize = -1; + const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { + inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"char")) { + inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { + inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"short")) { + inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { + inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"int")) { + inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"float")) { + inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"double")) { + inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; + } + if (inrpixsize<=0) + throw CImgIOException(_cimg_instance + "save_inr(): Unsupported pixel type '%s' for file '%s'", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(257); + int err = cimg_snprintf(header,header._width,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n", + _width,_height,_depth,_spectrum); + if (voxel_size) err+=cimg_sprintf(header._data + err,"VX=%g\nVY=%g\nVZ=%g\n", + voxel_size[0],voxel_size[1],voxel_size[2]); + err+=cimg_sprintf(header._data + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm"); + std::memset(header._data + err,'\n',252 - err); + std::memcpy(header._data + 252,"##}\n",4); + cimg::fwrite(header._data,256,nfile); + cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) cimg::fwrite(&((*this)(x,y,z,c)),1,nfile); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as an OpenEXR file. + /** + \param filename Filename, as a C-string. + \note The OpenEXR file format is described here. + **/ + const CImg& save_exr(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_exr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_exr(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + +#ifndef cimg_use_openexr + return save_other(filename); +#else + Imf::Rgba *const ptrd0 = new Imf::Rgba[(size_t)_width*_height], *ptrd = ptrd0, rgba; + switch (_spectrum) { + case 1 : { // Grayscale image + for (const T *ptr_r = data(), *const ptr_e = ptr_r + (ulongT)_width*_height; ptr_rPandore file specifications + for more information). + **/ + const CImg& save_pandore(const char *const filename, const unsigned int colorspace=0) const { + return _save_pandore(0,filename,colorspace); + } + + //! Save image as a Pandore-5 file \overloading. + /** + Same as save_pandore(const char *,unsigned int) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_pandore(std::FILE *const file, const unsigned int colorspace=0) const { + return _save_pandore(file,0,colorspace); + } + + unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const { + unsigned int nbdims = 0; + if (id==2 || id==3 || id==4) { + dims[0] = 1; dims[1] = _width; nbdims = 2; + } + if (id==5 || id==6 || id==7) { + dims[0] = 1; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==8 || id==9 || id==10) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + if (id==16 || id==17 || id==18) { + dims[0] = 3; dims[1] = _height; dims[2] = _width; dims[3] = colorspace; nbdims = 4; + } + if (id==19 || id==20 || id==21) { + dims[0] = 3; dims[1] = _depth; dims[2] = _height; dims[3] = _width; dims[4] = colorspace; nbdims = 5; + } + if (id==22 || id==23 || id==25) { + dims[0] = _spectrum; dims[1] = _width; nbdims = 2; + } + if (id==26 || id==27 || id==29) { + dims[0] = _spectrum; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==30 || id==31 || id==33) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + return nbdims; + } + + const CImg& _save_pandore(std::FILE *const file, const char *const filename, + const unsigned int colorspace) const { + +#define __cimg_save_pandore_case(dtype) \ + dtype *buffer = new dtype[size()]; \ + const T *ptrs = _data; \ + cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \ + buffer-=size(); \ + cimg::fwrite(buffer,size(),nfile); \ + delete[] buffer + +#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \ + if (!saved && (sy?(sy==_height):true) && (sz?(sz==_depth):true) && \ + (sv?(sv==_spectrum):true) && !std::strcmp(stype,pixel_type())) { \ + unsigned int *iheader = (unsigned int*)(header + 12); \ + nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \ + cimg::fwrite(header,36,nfile); \ + if (sizeof(unsigned long)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned int)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned short)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \ + __cimg_save_pandore_case(unsigned char); \ + } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \ + if (sizeof(unsigned long)==4) { __cimg_save_pandore_case(unsigned long); } \ + else if (sizeof(unsigned int)==4) { __cimg_save_pandore_case(unsigned int); } \ + else if (sizeof(unsigned short)==4) { __cimg_save_pandore_case(unsigned short); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \ + if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \ + else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pandore(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0, + 0,0,0,0,'C','I','m','g',0,0,0,0,0, + 'N','o',' ','d','a','t','e',0,0,0,0 }; + unsigned int nbdims, dims[5] = { 0 }; + bool saved = false; + _cimg_save_pandore_case(1,1,1,"unsigned char",2); + _cimg_save_pandore_case(1,1,1,"char",3); + _cimg_save_pandore_case(1,1,1,"unsigned short",3); + _cimg_save_pandore_case(1,1,1,"short",3); + _cimg_save_pandore_case(1,1,1,"unsigned int",3); + _cimg_save_pandore_case(1,1,1,"int",3); + _cimg_save_pandore_case(1,1,1,"unsigned int64",3); + _cimg_save_pandore_case(1,1,1,"int64",3); + _cimg_save_pandore_case(1,1,1,"float",4); + _cimg_save_pandore_case(1,1,1,"double",4); + + _cimg_save_pandore_case(0,1,1,"unsigned char",5); + _cimg_save_pandore_case(0,1,1,"char",6); + _cimg_save_pandore_case(0,1,1,"unsigned short",6); + _cimg_save_pandore_case(0,1,1,"short",6); + _cimg_save_pandore_case(0,1,1,"unsigned int",6); + _cimg_save_pandore_case(0,1,1,"int",6); + _cimg_save_pandore_case(0,1,1,"unsigned int64",6); + _cimg_save_pandore_case(0,1,1,"int64",6); + _cimg_save_pandore_case(0,1,1,"float",7); + _cimg_save_pandore_case(0,1,1,"double",7); + + _cimg_save_pandore_case(0,0,1,"unsigned char",8); + _cimg_save_pandore_case(0,0,1,"char",9); + _cimg_save_pandore_case(0,0,1,"unsigned short",9); + _cimg_save_pandore_case(0,0,1,"short",9); + _cimg_save_pandore_case(0,0,1,"unsigned int",9); + _cimg_save_pandore_case(0,0,1,"int",9); + _cimg_save_pandore_case(0,0,1,"unsigned int64",9); + _cimg_save_pandore_case(0,0,1,"int64",9); + _cimg_save_pandore_case(0,0,1,"float",10); + _cimg_save_pandore_case(0,0,1,"double",10); + + _cimg_save_pandore_case(0,1,3,"unsigned char",16); + _cimg_save_pandore_case(0,1,3,"char",17); + _cimg_save_pandore_case(0,1,3,"unsigned short",17); + _cimg_save_pandore_case(0,1,3,"short",17); + _cimg_save_pandore_case(0,1,3,"unsigned int",17); + _cimg_save_pandore_case(0,1,3,"int",17); + _cimg_save_pandore_case(0,1,3,"unsigned int64",17); + _cimg_save_pandore_case(0,1,3,"int64",17); + _cimg_save_pandore_case(0,1,3,"float",18); + _cimg_save_pandore_case(0,1,3,"double",18); + + _cimg_save_pandore_case(0,0,3,"unsigned char",19); + _cimg_save_pandore_case(0,0,3,"char",20); + _cimg_save_pandore_case(0,0,3,"unsigned short",20); + _cimg_save_pandore_case(0,0,3,"short",20); + _cimg_save_pandore_case(0,0,3,"unsigned int",20); + _cimg_save_pandore_case(0,0,3,"int",20); + _cimg_save_pandore_case(0,0,3,"unsigned int64",20); + _cimg_save_pandore_case(0,0,3,"int64",20); + _cimg_save_pandore_case(0,0,3,"float",21); + _cimg_save_pandore_case(0,0,3,"double",21); + + _cimg_save_pandore_case(1,1,0,"unsigned char",22); + _cimg_save_pandore_case(1,1,0,"char",23); + _cimg_save_pandore_case(1,1,0,"unsigned short",23); + _cimg_save_pandore_case(1,1,0,"short",23); + _cimg_save_pandore_case(1,1,0,"unsigned int",23); + _cimg_save_pandore_case(1,1,0,"int",23); + _cimg_save_pandore_case(1,1,0,"unsigned int64",23); + _cimg_save_pandore_case(1,1,0,"int64",23); + _cimg_save_pandore_case(1,1,0,"float",25); + _cimg_save_pandore_case(1,1,0,"double",25); + + _cimg_save_pandore_case(0,1,0,"unsigned char",26); + _cimg_save_pandore_case(0,1,0,"char",27); + _cimg_save_pandore_case(0,1,0,"unsigned short",27); + _cimg_save_pandore_case(0,1,0,"short",27); + _cimg_save_pandore_case(0,1,0,"unsigned int",27); + _cimg_save_pandore_case(0,1,0,"int",27); + _cimg_save_pandore_case(0,1,0,"unsigned int64",27); + _cimg_save_pandore_case(0,1,0,"int64",27); + _cimg_save_pandore_case(0,1,0,"float",29); + _cimg_save_pandore_case(0,1,0,"double",29); + + _cimg_save_pandore_case(0,0,0,"unsigned char",30); + _cimg_save_pandore_case(0,0,0,"char",31); + _cimg_save_pandore_case(0,0,0,"unsigned short",31); + _cimg_save_pandore_case(0,0,0,"short",31); + _cimg_save_pandore_case(0,0,0,"unsigned int",31); + _cimg_save_pandore_case(0,0,0,"int",31); + _cimg_save_pandore_case(0,0,0,"unsigned int64",31); + _cimg_save_pandore_case(0,0,0,"int64",31); + _cimg_save_pandore_case(0,0,0,"float",33); + _cimg_save_pandore_case(0,0,0,"double",33); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a raw data file. + /** + \param filename Filename, as a C-string. + \param is_multiplexed Tells if the image channels are stored in a multiplexed way (\c true) or not (\c false). + \note The .raw format does not store the image dimensions in the output file, + so you have to keep track of them somewhere to be able to read the file correctly afterwards. + **/ + const CImg& save_raw(const char *const filename, const bool is_multiplexed=false) const { + return _save_raw(0,filename,is_multiplexed); + } + + //! Save image as a raw data file \overloading. + /** + Same as save_raw(const char *,bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_raw(std::FILE *const file, const bool is_multiplexed=false) const { + return _save_raw(file,0,is_multiplexed); + } + + const CImg& _save_raw(std::FILE *const file, const char *const filename, const bool is_multiplexed) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_raw(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!is_multiplexed) cimg::fwrite(_data,size(),nfile); + else { + CImg buf(_spectrum); + cimg_forXYZ(*this,x,y,z) { + cimg_forC(*this,c) buf[c] = (*this)(x,y,z,c); + cimg::fwrite(buf._data,_spectrum,nfile); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .yuv video file. + /** + \param filename Filename, as a C-string. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if pixel values of the instance image are RGB-coded (\c true) or YUV-coded (\c false). + \note Each slice of the instance image is considered to be a single frame of the output video file. + **/ + const CImg& save_yuv(const char *const filename, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(filename,chroma_subsampling,is_rgb); + return *this; + } + + //! Save image as a .yuv video file \overloading. + /** + Same as save_yuv(const char*,const unsigned int,const bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(file,chroma_subsampling,is_rgb); + return *this; + } + + //! Save 3D object as an Object File Format (.off) file. + /** + \param filename Filename, as a C-string. + \param primitives List of 3D object primitives. + \param colors List of 3D object colors. + \note + - Instance image contains the vertices data of the 3D object. + - Textured, transparent or sphere-shaped primitives cannot be managed by the .off file format. + Such primitives will be lost or simplified during file saving. + - The .off file format is described here. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + const char *const filename) const { + return _save_off(primitives,colors,0,filename); + } + + //! Save 3D object as an Object File Format (.off) file \overloading. + /** + Same as save_off(const CImgList&,const CImgList&,const char*) const + with a file stream argument instead of a filename string. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file) const { + return _save_off(primitives,colors,file,0); + } + + template + const CImg& _save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_off(): Specified filename is (null).", + cimg_instance); + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "save_off(): Empty instance, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + CImgList opacities; + 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).", + cimg_instance, + filename?filename:"(FILE*)",error_message.data()); + + const CImg default_color(1,3,1,1,200); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + unsigned int supported_primitives = 0; + cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives; + std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width); + cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n", + (float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2))); + cimglist_for(primitives,l) { + const CImg& color = l1?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; + case 2 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 3 : std::fprintf(nfile,"3 %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,2), + (unsigned int)primitives(l,1),r,g,b); break; + case 4 : std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,3), + (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); break; + case 5 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 6 : { + const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3); + const float + rt = color.atXY(xt,yt,0)/255.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.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); + } break; + case 12 : { + const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5); + const float + rt = color.atXY(xt,yt,0)/255.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); + } break; + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save volumetric image as a video, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImg& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { + if (is_empty()) { CImgList().save_video(filename,fps,codec,keep_open); return *this; } + CImgList list; + get_split('z').move_to(list); + list.save_video(filename,fps,codec,keep_open); + return *this; + } + + //! Save volumetric image as a video, using ffmpeg external binary. + /** + \param filename Filename, as a C-string. + \param fps Video framerate. + \param codec Video codec, as a C-string. + \param bitrate Video bitrate. + \note + - Each slice of the instance image is considered to be a single frame of the output video file. + - This method uses \c ffmpeg, an external executable binary provided by + FFmpeg. + It must be installed for the method to succeed. + **/ + const CImg& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImgList list; + get_split('z').move_to(list); + list.save_ffmpeg_external(filename,fps,codec,bitrate); + return *this; + } + + //! Save image using gzip external binary. + /** + \param filename Filename, as a C-string. + \note This method uses \c gzip, an external executable binary provided by + gzip. + It must be installed for the method to succeed. + **/ + const CImg& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_gzip_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimg_instance, + filename); + + else cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using GraphicsMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c gm, an external executable binary provided by + GraphicsMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + +#ifdef cimg_use_png +#define _cimg_sge_ext1 "png" +#define _cimg_sge_ext2 "png" +#else +#define _cimg_sge_ext1 "pgm" +#define _cimg_sge_ext2 "ppm" +#endif + CImg 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=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::graphicsmagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_graphicsmagick_external(): Failed to save file '%s' with external command 'gm'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using ImageMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c convert, an external executable binary provided by + ImageMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_imagemagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick only writes the first image slice.", + cimg_instance,filename); +#ifdef cimg_use_png +#define _cimg_sie_ext1 "png" +#define _cimg_sie_ext2 "png" +#else +#define _cimg_sie_ext1 "pgm" +#define _cimg_sie_ext2 "ppm" +#endif + CImg 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=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::imagemagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_imagemagick_external(): Failed to save file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image as a Dicom file. + /** + \param filename Filename, as a C-string. + \note This method uses \c medcon, an external executable binary provided by + (X)Medcon. + It must be installed for the method to succeed. + **/ + const CImg& save_medcon_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_medcon_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + std::FILE *file; + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand()); + 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::medcon_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command); + std::remove(filename_tmp); + cimg::split_filename(filename_tmp,body); + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.img",body._data); + std::remove(filename_tmp); + + file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s",filename); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimg_instance + "save_medcon_external(): Failed to save file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + std::rename(command,filename); + return *this; + } + + // Save image for non natively supported formats. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note + - The filename extension tells about the desired file format. + - This method tries to save the instance image as a file, using external tools from + ImageMagick or + GraphicsMagick. + At least one of these tool must be installed for the method to succeed. + - It is recommended to use the generic method save(const char*, int) const instead, + as it can handle some file formats natively. + **/ + const CImg& save_other(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_other(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick or GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + + const unsigned int omode = cimg::exception_mode(); + bool is_saved = true; + cimg::exception_mode(0); + try { save_magick(filename); } + catch (CImgException&) { + try { save_imagemagick_external(filename,quality); } + catch (CImgException&) { + try { save_graphicsmagick_external(filename,quality); } + catch (CImgException&) { + is_saved = false; + } + } + } + cimg::exception_mode(omode); + if (!is_saved) + throw CImgIOException(_cimg_instance + "save_other(): Failed to save file '%s'. Format is not natively supported, " + "and no external commands succeeded.", + cimg_instance, + filename); + return *this; + } + + //! Serialize a CImg instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { + return CImgList(*this,true).get_serialize(is_compressed); + } + + // [internal] Return a 40x38 color logo of a 'danger' item. + static CImg _logo40x38() { + CImg res(40,38,1,3); + const unsigned char *ptrs = cimg::logo40x38; + T *ptr1 = res.data(0,0,0,0), *ptr2 = res.data(0,0,0,1), *ptr3 = res.data(0,0,0,2); + for (ulongT off = 0; off<(ulongT)res._width*res._height;) { + const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++); + for (unsigned int l = 0; l structure + # + # + # + #------------------------------------------ + */ + //! Represent a list of images CImg. + template + struct CImgList { + unsigned int _width, _allocated_width; + CImg *_data; + + //! Simple iterator type, to loop through each image of a list. + /** + \note + - 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 + for (CImgList<>::iterator it = list.begin(); it* iterator; + + //! Simple const iterator type, to loop through each image of a \c const list instance. + /** + \note + - The \c CImgList::const_iterator type is defined to be a const CImg*. + - Similar to CImgList::iterator, but for constant list instances. + **/ + typedef const CImg* const_iterator; + + //! Pixel value type. + /** + Refer to the pixels value type of the images in the list. + \note + - The \c CImgList::value_type type of a \c CImgList is defined to be a \c T. + It is then similar to CImg::value_type. + - \c CImgList::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimglist_plugin +#include cimglist_plugin +#endif +#ifdef cimglist_plugin1 +#include cimglist_plugin1 +#endif +#ifdef cimglist_plugin2 +#include cimglist_plugin2 +#endif +#ifdef cimglist_plugin3 +#include cimglist_plugin3 +#endif +#ifdef cimglist_plugin4 +#include cimglist_plugin4 +#endif +#ifdef cimglist_plugin5 +#include cimglist_plugin5 +#endif +#ifdef cimglist_plugin6 +#include cimglist_plugin6 +#endif +#ifdef cimglist_plugin7 +#include cimglist_plugin7 +#endif +#ifdef cimglist_plugin8 +#include cimglist_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + Destroy current list instance. + \note + - Any allocated buffer is deallocated. + - Destroying an empty list does nothing actually. + **/ + ~CImgList() { + delete[] _data; + } + + //! Default constructor. + /** + Construct a new empty list instance. + \note + - An empty list has no pixel data and its dimension width() is set to \c 0, as well as its + image buffer pointer data(). + - An empty list may be reassigned afterwards, with the family of the assign() methods. + In all cases, the type of pixels stays \c T. + **/ + CImgList(): + _width(0),_allocated_width(0),_data(0) {} + + //! Construct list containing empty images. + /** + \param n Number of empty images. + \note Useful when you know by advance the number of images you want to manage, as + it will allocate the right amount of memory for the list, without needs for reallocation + (that may occur when starting from an empty list and inserting several images in it). + **/ + explicit CImgList(const unsigned int n):_width(n) { + if (n) _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + else { _allocated_width = 0; _data = 0; } + } + + //! Construct list containing images of specified size. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \note Pixel values are not initialized and may probably contain garbage. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + } + + //! Construct list containing images of specified size, and initialize pixel values. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val Initialization value for images pixels. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + } + + //! Construct list containing images of specified size, and initialize pixel values from a sequence of integers. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val0 First value of the initializing integers sequence. + \param val1 Second value of the initializing integers sequence. + \warning You must specify at least width*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...): + _width(0),_allocated_width(0),_data(0) { +#define _CImgList_stdarg(t) { \ + assign(n,width,height,depth,spectrum); \ + const ulongT siz = (ulongT)width*height*depth*spectrum, nsiz = siz*n; \ + T *ptrd = _data->_data; \ + va_list ap; \ + va_start(ap,val1); \ + for (ulongT l = 0, s = 0, i = 0; iwidth*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const double val0, const double val1, ...): + _width(0),_allocated_width(0),_data(0) { + _CImgList_stdarg(double); + } + + //! Construct list containing copies of an input image. + /** + \param n Number of images. + \param img Input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of \c img. + **/ + template + CImgList(const unsigned int n, const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + } + + //! Construct list from one image. + /** + \param img Input image to copy in the constructed list. + \param is_shared Tells if the element of the list is a shared or non-shared copy of \c img. + **/ + template + explicit CImgList(const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(1); + _data[0].assign(img,is_shared); + } + + //! Construct list from two images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + } + + //! Construct list from three images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + } + + //! Construct list from four images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + } + + //! Construct list from five images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + } + + //! Construct list from six images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + } + + //! Construct list from seven images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + } + + //! Construct list from eight images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param img8 Eighth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + } + + //! Construct list copy. + /** + \param list Input list to copy. + \note The shared state of each element of the constructed list is kept the same as in \c list. + **/ + template + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + } + + //! Construct list copy \specialization. + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared); + } + + //! Construct list copy, and force the shared state of the list elements. + /** + \param list Input list to copy. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImgList& list, const bool is_shared):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],is_shared); + } + + //! Construct list by reading the content of a file. + /** + \param filename Filename, as a C-string. + **/ + explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) { + assign(filename); + } + + //! Construct list from the content of a display window. + /** + \param disp Display window to get content from. + \note Constructed list contains a single image only. + **/ + explicit CImgList(const CImgDisplay& disp):_width(0),_allocated_width(0),_data(0) { + assign(disp); + } + + //! Return a list with elements being shared copies of images in the list instance. + /** + \note list2 = list1.get_shared() is equivalent to list2.assign(list1,true). + **/ + CImgList get_shared() { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Return a list with elements being shared copies of images in the list instance \const. + const CImgList get_shared() const { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Destructor \inplace. + /** + \see CImgList(). + **/ + CImgList& assign() { + delete[] _data; + _width = _allocated_width = 0; + _data = 0; + return *this; + } + + //! Destructor \inplace. + /** + Equivalent to assign(). + \note Only here for compatibility with STL naming conventions. + **/ + CImgList& clear() { + return assign(); + } + + //! Construct list containing empty images \inplace. + /** + \see CImgList(unsigned int). + **/ + CImgList& assign(const unsigned int n) { + if (!n) return assign(); + if (_allocated_width(n<<2)) { + delete[] _data; + _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + } + _width = n; + return *this; + } + + //! Construct list containing images of specified size \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + return *this; + } + + //! Construct list containing images of specified size, and initialize pixel values \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const T). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of integers \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const int, const int, ...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) { + _CImgList_stdarg(int); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of doubles \inplace. + /** + \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const double,const double,...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, + const double val0, const double val1, ...) { + _CImgList_stdarg(double); + return *this; + } + + //! Construct list containing copies of an input image \inplace. + /** + \see CImgList(unsigned int, const CImg&, bool). + **/ + template + CImgList& assign(const unsigned int n, const CImg& img, const bool is_shared=false) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + return *this; + } + + //! Construct list from one image \inplace. + /** + \see CImgList(const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img, const bool is_shared=false) { + assign(1); + _data[0].assign(img,is_shared); + return *this; + } + + //! Construct list from two images \inplace. + /** + \see CImgList(const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const bool is_shared=false) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + return *this; + } + + //! Construct list from three images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + return *this; + } + + //! Construct list from four images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + return *this; + } + + //! Construct list from five images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + return *this; + } + + //! Construct list from six images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + return *this; + } + + //! Construct list from seven images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + return *this; + } + + //! Construct list from eight images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + return *this; + } + + //! Construct list as a copy of an existing list and force the shared state of the list elements \inplace. + /** + \see CImgList(const CImgList&, bool is_shared). + **/ + template + CImgList& assign(const CImgList& list, const bool is_shared=false) { + cimg::unused(is_shared); + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + return *this; + } + + //! Construct list as a copy of an existing list and force shared state of elements \inplace \specialization. + CImgList& assign(const CImgList& list, const bool is_shared=false) { + if (this==&list) return *this; + CImgList res(list._width); + cimglist_for(res,l) res[l].assign(list[l],is_shared); + return res.move_to(*this); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& assign(const char *const filename) { + return load(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& assign(const CImgDisplay &disp) { + return assign(CImg(disp)); + } + + //! Transfer the content of the list instance to another list. + /** + \param list Destination list. + \note When returning, the current list instance is empty and the initial content of \c list is destroyed. + **/ + template + CImgList& move_to(CImgList& list) { + list.assign(_width); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[l]); + assign(); + return list; + } + + //! Transfer the content of the list instance at a specified position in another list. + /** + \param list Destination list. + \param pos Index of the insertion in the list. + \note When returning, the list instance is empty and the initial content of \c list is preserved + (only images indexes may be modified). + **/ + template + CImgList& move_to(CImgList& list, const unsigned int pos) { + if (is_empty()) return list; + const unsigned int npos = pos>list._width?list._width:pos; + list.insert(_width,npos); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[npos + l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[npos + l]); + assign(); + return list; + } + + //! Swap all fields between two list instances. + /** + \param list List to swap fields with. + \note Can be used to exchange the content of two lists in a fast way. + **/ + CImgList& swap(CImgList& list) { + cimg::swap(_width,list._width,_allocated_width,list._allocated_width); + cimg::swap(_data,list._data); + return list; + } + + //! Return a reference to an empty list. + /** + \note Can be used to define default values in a function taking a CImgList as an argument. + \code + void f(const CImgList& list=CImgList::empty()); + \endcode + **/ + static CImgList& empty() { + static CImgList _empty; + return _empty.assign(); + } + + //! Return a reference to an empty list \const. + static const CImgList& const_empty() { + static const CImgList _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Return a reference to one image element of the list. + /** + \param pos Indice of the image element. + **/ + CImg& operator()(const unsigned int pos) { +#if cimg_verbosity>=3 + if (pos>=_width) { + cimg::warn(_cimglist_instance + "operator(): Invalid image request, at position [%u].", + cimglist_instance, + pos); + return *_data; + } +#endif + return _data[pos]; + } + + //! Return a reference to one image of the list. + /** + \param pos Indice of the image element. + **/ + const CImg& operator()(const unsigned int pos) const { + return const_cast*>(this)->operator()(pos); + } + + //! Return a reference to one pixel value of one image of the list. + /** + \param pos Indice of the image element. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note list(n,x,y,z,c) is equivalent to list[n](x,y,z,c). + **/ + T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + return (*this)[pos](x,y,z,c); + } + + //! Return a reference to one pixel value of one image of the list \const. + const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return (*this)[pos](x,y,z,c); + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + operator CImg*() { + return _data; + } + + //! Return pointer to the first image of the list \const. + operator const CImg*() const { + return _data; + } + + //! Construct list from one image \inplace. + /** + \param img Input image to copy in the constructed list. + \note list = img; is equivalent to list.assign(img);. + **/ + template + CImgList& operator=(const CImg& img) { + return assign(img); + } + + //! Construct list from another list. + /** + \param list Input list to copy. + \note list1 = list2 is equivalent to list1.assign(list2);. + **/ + template + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list from another list \specialization. + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& operator=(const char *const filename) { + return assign(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return a non-shared copy of a list. + /** + \note +list is equivalent to CImgList(list,false). + It forces the copy to have non-shared elements. + **/ + CImgList operator+() const { + return CImgList(*this,false); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end. + /** + \param img Image inserted at the end of the instance copy. + \note Define a convenient way to create temporary lists of images, as in the following code: + \code + (img1,img2,img3,img4).display("My four images"); + \endcode + **/ + template + CImgList& operator,(const CImg& img) { + return insert(img); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end \const. + template + CImgList operator,(const CImg& img) const { + return (+*this).insert(img); + } + + //! Return a copy of the list instance, where all elements of input list \c list have been inserted at the end. + /** + \param list List inserted at the end of the instance copy. + **/ + template + CImgList& operator,(const CImgList& list) { + return insert(list); + } + + //! Return a copy of the list instance, where all elements of input \c list have been inserted at the end \const. + template + CImgList& operator,(const CImgList& list) const { + return (+*this).insert(list); + } + + //! Return image corresponding to the appending of all images of the instance list along specified axis. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \note list>'x' is equivalent to list.get_append('x'). + **/ + CImg operator>(const char axis) const { + return get_append(axis,0); + } + + //! Return list corresponding to the splitting of all images of the instance list along specified axis. + /** + \param axis Axis used for image splitting. + \note list<'x' is equivalent to list.get_split('x'). + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to size() but returns result as a (signed) integer. + **/ + int width() const { + return (int)_width; + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to width() but returns result as an unsigned integer. + **/ + unsigned int size() const { + return _width; + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + CImg *data() { + return _data; + } + + //! Return pointer to the first image of the list \const. + const CImg *data() const { + return _data; + } + + //! Return pointer to the pos-th image of the list. + /** + \param pos Indice of the image element to access. + \note list.data(n); is equivalent to list.data + n;. + **/ +#if cimg_verbosity>=3 + CImg *data(const unsigned int pos) { + if (pos>=size()) + cimg::warn(_cimglist_instance + "data(): Invalid pointer request, at position [%u].", + cimglist_instance, + pos); + return _data + pos; + } + + const CImg *data(const unsigned int l) const { + return const_cast*>(this)->data(l); + } +#else + CImg *data(const unsigned int l) { + return _data + l; + } + + //! Return pointer to the pos-th image of the list \const. + const CImg *data(const unsigned int l) const { + return _data + l; + } +#endif + + //! Return iterator to the first image of the list. + /** + **/ + iterator begin() { + return _data; + } + + //! Return iterator to the first image of the list \const. + const_iterator begin() const { + return _data; + } + + //! Return iterator to one position after the last image of the list. + /** + **/ + iterator end() { + return _data + _width; + } + + //! Return iterator to one position after the last image of the list \const. + const_iterator end() const { + return _data + _width; + } + + //! Return reference to the first image of the list. + /** + **/ + CImg& front() { + return *_data; + } + + //! Return reference to the first image of the list \const. + const CImg& front() const { + return *_data; + } + + //! Return a reference to the last image of the list. + /** + **/ + const CImg& back() const { + return *(_data + _width - 1); + } + + //! Return a reference to the last image of the list \const. + CImg& back() { + return *(_data + _width - 1); + } + + //! Return pos-th image of the list. + /** + \param pos Indice of the image element to access. + **/ + CImg& at(const int pos) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "at(): Empty instance.", + cimglist_instance); + + return _data[cimg::cut(pos,0,width() - 1)]; + } + + //! Access to pixel value with Dirichlet boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZC(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + T _atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZ(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + T _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXY(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + T _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x) \const. + T atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atX(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x) \const. + T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + T _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:(*this)(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Return \c true if list is empty. + /** + **/ + bool is_empty() const { + return (!_data || !_width); + } + + //! Test if number of image elements is equal to specified value. + /** + \param size_n Number of image elements to test. + **/ + bool is_sameN(const unsigned int size_n) const { + return _width==size_n; + } + + //! Test if number of image elements is equal between two images lists. + /** + \param list Input list to compare with. + **/ + template + bool is_sameN(const CImgList& list) const { + return is_sameN(list._width); + } + + // Define useful functions to check list dimensions. + // (cannot be documented because macro-generated). +#define _cimglist_def_is_same1(axis) \ + bool is_same##axis(const unsigned int val) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(val); return res; \ + } \ + bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \ + return is_sameN(n) && is_same##axis(val); \ + } \ + +#define _cimglist_def_is_same2(axis1,axis2) \ + bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2(val1,val2); return res; \ + } \ + bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \ + return is_sameN(n) && is_same##axis1##axis2(val1,val2); \ + } \ + +#define _cimglist_def_is_same3(axis1,axis2,axis3) \ + bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, \ + const unsigned int val3) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2##axis3(val1,val2,val3); \ + return res; \ + } \ + bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, \ + const unsigned int val2, const unsigned int val3) const { \ + return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \ + } \ + +#define _cimglist_def_is_same(axis) \ + template 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; \ + } \ + 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)); \ + } \ + template bool is_sameN##axis(const CImgList& list) const { \ + return (is_sameN(list) && is_same##axis(list)); \ + } + + _cimglist_def_is_same(XY) + _cimglist_def_is_same(XZ) + _cimglist_def_is_same(XC) + _cimglist_def_is_same(YZ) + _cimglist_def_is_same(YC) + _cimglist_def_is_same(XYZ) + _cimglist_def_is_same(XYC) + _cimglist_def_is_same(YZC) + _cimglist_def_is_same(XYZC) + _cimglist_def_is_same1(X) + _cimglist_def_is_same1(Y) + _cimglist_def_is_same1(Z) + _cimglist_def_is_same1(C) + _cimglist_def_is_same2(X,Y) + _cimglist_def_is_same2(X,Z) + _cimglist_def_is_same2(X,C) + _cimglist_def_is_same2(Y,Z) + _cimglist_def_is_same2(Y,C) + _cimglist_def_is_same2(Z,C) + _cimglist_def_is_same3(X,Y,Z) + _cimglist_def_is_same3(X,Y,C) + _cimglist_def_is_same3(X,Z,C) + _cimglist_def_is_same3(Y,Z,C) + + //! Test if dimensions of each image of the list match specified arguments. + /** + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameXYZC(const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + bool res = true; + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_sameXYZC(dx,dy,dz,dc); + return res; + } + + //! Test if list dimensions match specified arguments. + /** + \param n Number of images in the list. + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameNXYZC(const unsigned int n, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + return is_sameN(n) && is_sameXYZC(dx,dy,dz,dc); + } + + //! Test if list contains one particular pixel location. + /** + \param n Index of the image whom checked pixel value belong to. + \param x X-coordinate of the checked pixel value. + \param y Y-coordinate of the checked pixel value. + \param z Z-coordinate of the checked pixel value. + \param c C-coordinate of the checked pixel value. + **/ + bool containsNXYZC(const int n, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width && x>=0 && x<_data[n].width() && y>=0 && y<_data[n].height() && + z>=0 && z<_data[n].depth() && c>=0 && c<_data[n].spectrum(); + } + + //! Test if list contains image with specified indice. + /** + \param n Index of the checked image. + **/ + bool containsN(const int n) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width; + } + + //! Test if one image of the list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \param[out] c C-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z,c). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const { + if (is_empty()) return false; + cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; } + return false; + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z) const { + t c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y) const { + t z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x). + **/ + template + bool contains(const T& pixel, t& n, t& x) const { + t y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \note If true, set coordinates (n). + **/ + template + bool contains(const T& pixel, t& n) const { + t x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + **/ + bool contains(const T& pixel) const { + unsigned int n, x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if the list contains the image 'img'. + /** + \param img Reference to image to test. + \param[out] n Index of image in the list, if test succeeds. + \note If true, returns the position (n) of the image in the list. + **/ + template + bool contains(const CImg& img, t& n) const { + if (is_empty()) return false; + const CImg *const ptr = &img; + cimglist_for(*this,i) if (_data + i==ptr) { n = (t)i; return true; } + return false; + } + + //! Test if the list contains the image img. + /** + \param img Reference to image to test. + **/ + bool contains(const CImg& img) const { + unsigned int n; + return contains(img,n); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + //! Return a reference to the minimum pixel value of the instance list. + /** + **/ + T& min() { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the maximum pixel value of the instance list \const. + const T& max() const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T max_value = *ptr_max; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + T& min_max(t& max_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well \const. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + const T& min_max(t& max_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well. + /** + \param[out] min_val Value of the minimum value found. + **/ + template + T& max_min(t& min_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + if (is_shared) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified shared image " + "CImg<%s>(%u,%u,%u,%u,%p) at position %u (pixel types are different).", + cimglist_instance, + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos); + + CImg *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 + _data = new_data; + *_data = img; + } else { + 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 + 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; + _data[npos] = img; + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \specialization. + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + CImg *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 + _data = new_data; + if (is_shared && img) { + _data->_width = img._width; + _data->_height = img._height; + _data->_depth = img._depth; + _data->_spectrum = img._spectrum; + _data->_is_shared = true; + _data->_data = img._data; + } else *_data = img; + } + else { + if (new_data) { // Insert with re-allocation + if (npos) std::memcpy((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)); + if (is_shared && img) { + new_data[npos]._width = img._width; + new_data[npos]._height = img._height; + new_data[npos]._depth = img._depth; + new_data[npos]._spectrum = img._spectrum; + new_data[npos]._is_shared = true; + new_data[npos]._data = img._data; + } else { + new_data[npos]._width = new_data[npos]._height = new_data[npos]._depth = new_data[npos]._spectrum = 0; + new_data[npos]._data = 0; + new_data[npos] = img; + } + std::memset((void*)_data,0,sizeof(CImg)*(_width - 1)); + delete[] _data; + _data = new_data; + } 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) { + _data[npos]._width = img._width; + _data[npos]._height = img._height; + _data[npos]._depth = img._depth; + _data[npos]._spectrum = img._spectrum; + _data[npos]._is_shared = true; + _data[npos]._data = img._data; + } else { + _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; + _data[npos]._data = 0; + _data[npos] = img; + } + } + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \newinstance. + template + CImgList get_insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(img,pos,is_shared); + } + + //! Insert n empty images img into the current image list, at position \p pos. + /** + \param n Number of empty images to insert. + \param pos Index of the insertion. + **/ + CImgList& insert(const unsigned int n, const unsigned int pos=~0U) { + CImg empty; + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i get_insert(const unsigned int n, const unsigned int pos=~0U) const { + return (+*this).insert(n,pos); + } + + //! Insert \c n copies of the image \c img into the current image list, at position \c pos. + /** + \param n Number of image copies to insert. + \param img Image to insert by copy. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of \c img or not. + **/ + template + CImgList& insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + insert(img,npos,is_shared); + for (unsigned int i = 1; i + CImgList get_insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,img,pos,is_shared); + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos. + /** + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos + l,is_shared); + else insert(CImgList(list),npos,is_shared); + return *this; + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos \newinstance. + template + CImgList get_insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(list,pos,is_shared); + } + + //! Insert n copies of the list \c list at position \c pos of the current list. + /** + \param n Number of list copies to insert. + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i + CImgList get_insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,list,pos,is_shared); + } + + //! Remove all images between from indexes. + /** + \param pos1 Starting index of the removal. + \param pos2 Ending index of the removal. + **/ + CImgList& remove(const unsigned int pos1, const unsigned int pos2) { + const unsigned int + npos1 = pos1=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + else { + if (tpos2>=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + + for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign(); + const unsigned int nb = 1 + npos2 - npos1; + if (!(_width-=nb)) return assign(); + if (_width>(_allocated_width>>2) || _allocated_width<=16) { // Removing items without reallocation + if (npos1!=_width) + std::memmove((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; + 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); + if (npos1!=_width) + std::memcpy((void*)(new_data + npos1),(void*)(_data + npos2 + 1),sizeof(CImg)*(_width - npos1)); + if (_width!=_allocated_width) + std::memset((void*)(new_data + _width),0,sizeof(CImg)*(_allocated_width - _width)); + std::memset((void*)_data,0,sizeof(CImg)*(_width + nb)); + delete[] _data; + _data = new_data; + } + } + return *this; + } + + //! Remove all images between from indexes \newinstance. + CImgList get_remove(const unsigned int pos1, const unsigned int pos2) const { + return (+*this).remove(pos1,pos2); + } + + //! Remove image at index \c pos from the image list. + /** + \param pos Index of the image to remove. + **/ + CImgList& remove(const unsigned int pos) { + return remove(pos,pos); + } + + //! Remove image at index \c pos from the image list \newinstance. + CImgList get_remove(const unsigned int pos) const { + return (+*this).remove(pos); + } + + //! Remove last image. + /** + **/ + CImgList& remove() { + return remove(_width - 1); + } + + //! Remove last image \newinstance. + CImgList get_remove() const { + return (+*this).remove(); + } + + //! Reverse list order. + CImgList& reverse() { + for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width - 1 - l]); + return *this; + } + + //! Reverse list order \newinstance. + CImgList get_reverse() const { + return (+*this).reverse(); + } + + //! Return a sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList& images(const unsigned int pos0, const unsigned int pos1) { + return get_images(pos0,pos1).move_to(*this); + } + + //! Return a sublist \newinstance. + CImgList get_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l]); + return res; + } + + //! Return a shared sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a shared sublist \newinstance. + const CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a single image which is the appending of all images of the current CImgList instance. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg get_append(const char axis, const float align=0) const { + if (is_empty()) return CImg(); + if (_width==1) return +((*this)[0]); + unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0; + CImg res; + switch (cimg::lowercase(axis)) { + case 'x' : { // Along the X-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx+=img._width; + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image(pos, + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._width; + } + } break; + case 'y' : { // Along the Y-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy+=img._height; + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + pos, + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._height; + } + } break; + case 'z' : { // Along the Z-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz+=img._depth; + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + pos, + (int)(align*(dc - img._spectrum)), + img); + pos+=img._depth; + } + } break; + default : { // Along the C-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc+=img._spectrum; + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + pos, + img); + pos+=img._spectrum; + } + } + } + return res; + } + + //! Return a list where each image has been split along the specified axis. + /** + \param axis Axis to split images along. + \param nb Number of spliting parts for each image. + **/ + CImgList& split(const char axis, const int nb=-1) { + return get_split(axis,nb).move_to(*this); + } + + //! Return a list where each image has been split along the specified axis \newinstance. + CImgList get_split(const char axis, const int nb=-1) const { + CImgList res; + cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U); + return res; + } + + //! Insert image at the end of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_back(const CImg& img) { + return insert(img); + } + + //! Insert image at the front of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_front(const CImg& img) { + return insert(img,0); + } + + //! Insert list at the end of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_back(const CImgList& list) { + return insert(list); + } + + //! Insert list at the front of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_front(const CImgList& list) { + return insert(list,0); + } + + //! Remove last image. + /** + **/ + CImgList& pop_back() { + return remove(_width - 1); + } + + //! Remove first image. + /** + **/ + CImgList& pop_front() { + return remove(0); + } + + //! Remove image pointed by iterator. + /** + \param iter Iterator pointing to the image to remove. + **/ + CImgList& erase(const iterator iter) { + return remove(iter - _data); + } + + //@} + //---------------------------------- + // + //! \name Data Input + //@{ + //---------------------------------- + + //! Display a simple interactive interface to select images or sublists. + /** + \param disp Window instance to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(CImgDisplay &disp, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + return _select(disp,0,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + //! Display a simple interactive interface to select images or sublists. + /** + \param title Title of a new window used to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(const char *const title, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + CImg _select(CImgDisplay &disp, const char *const title, const bool feature_type, + const char axis, const float align, const bool exit_on_anykey, + const unsigned int orig, const bool resize_disp, + const bool exit_on_rightbutton, const bool exit_on_wheel) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "select(): Empty instance.", + cimglist_instance); + + // Create image correspondence table and get list dimensions for visualization. + CImgList _indices; + unsigned int max_width = 0, max_height = 0, sum_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + if (h>max_height) max_height = h; + sum_width+=w; sum_height+=h; + if (axis=='x') CImg(w,1,1,1,(unsigned int)l).move_to(_indices); + else CImg(h,1,1,1,(unsigned int)l).move_to(_indices); + } + const CImg indices0 = _indices>'x'; + + // Create display window. + if (!disp) { + if (axis=='x') disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1); + else disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1); + if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else { + if (title) disp.set_title("%s",title); + 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); + } + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0); + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + // Enter event loop. + CImg visu0, visu; + CImg indices; + CImg positions(_width,4,1,1,-1); + int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1; + bool is_clicked = false, is_selected = false, text_down = false, update_display = true; + unsigned int key = 0; + + while (!is_selected && !disp.is_closed() && !key) { + + // Create background image. + if (!visu0) { + visu0.assign(disp._width,disp._height,1,3,0); visu.assign(); + (indices0.get_resize(axis=='x'?visu0._width:visu0._height,1)).move_to(indices); + unsigned int ind = 0; + const CImg onexone(1,1,1,1,(T)0); + if (axis=='x') + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)x0; + positions(ind,1) = positions(ind,3) = (int)(align*(visu0.height() - res.height())); + positions(ind,2)+=res._width; + positions(ind,3)+=res._height - 1; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + else + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)(align*(visu0.width() - res.width())); + positions(ind,1) = positions(ind,3) = (int)y0; + positions(ind,2)+=res._width - 1; + positions(ind,3)+=res._height; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + if (axis=='x') --positions(ind,2); else --positions(ind,3); + update_display = true; + } + + if (!visu || oindice0!=indice0 || oindice1!=indice1) { + if (indice0>=0 && indice1>=0) { + visu.assign(visu0,false); + const int indm = std::min(indice0,indice1), indM = std::max(indice0,indice1); + for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) { + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + background_color,0.2f); + if ((axis=='x' && positions(ind,2) - positions(ind,0)>=8) || + (axis!='x' && positions(ind,3) - positions(ind,1)>=8)) + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + foreground_color,0.9f,0xAAAAAAAA); + } + if (is_clicked) visu.__draw_text(" Images #%u - #%u, Size = %u",text_down, + orig + indm,orig + indM,indM - indm + 1); + else visu.__draw_text(" Image #%u (%u,%u,%u,%u)",text_down, + orig + indice0, + _data[indice0]._width, + _data[indice0]._height, + _data[indice0]._depth, + _data[indice0]._spectrum); + update_display = true; + } else visu.assign(); + } + if (!visu) { visu.assign(visu0,true); update_display = true; } + if (update_display) { visu.display(disp); update_display = false; } + disp.wait(); + + // Manage user events. + const int xm = disp.mouse_x(), ym = disp.mouse_y(); + int indice = -1; + + if (xm>=0) { + indice = (int)indices(axis=='x'?xm:ym); + if (disp.button()&1) { + if (!is_clicked) { is_clicked = true; oindice0 = indice0; indice0 = indice; } + oindice1 = indice1; indice1 = indice; + if (!feature_type) is_selected = true; + } else { + if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; } + else is_selected = true; + } + } else { + if (is_clicked) { + if (!(disp.button()&1)) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + else indice1 = -1; + } else indice0 = indice1 = -1; + } + + if (disp.button()&4) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; indice1 = indice0 = -1; } + if (disp.wheel() && exit_on_wheel) is_selected = true; + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(axis=='x'?sum_width:max_width,axis=='x'?max_height:sum_height,1),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot... ",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false).wait(); key = 0; + } break; + case cimg::keyO : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false).wait(); key = 0; + } break; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (ym>=0 && ym<13) { if (!text_down) { visu.assign(); text_down = true; }} + else if (ym>=visu.height() - 13) { if (text_down) { visu.assign(); text_down = false; }} + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + CImg res(1,2,1,1,-1); + if (is_selected) { + if (feature_type) res.fill(std::min(indice0,indice1),std::max(indice0,indice1)); + else res.fill(indice0); + } + if (!(disp.button()&2)) disp.set_button(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + disp.set_key(key); + return res; + } + + //! Load a list from a file. + /** + \param filename Filename to read data from. + **/ + CImgList& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load(): Specified filename is (null).", + cimglist_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimglist_load_plugin + cimglist_load_plugin(filename); +#endif +#ifdef cimglist_load_plugin1 + cimglist_load_plugin1(filename); +#endif +#ifdef cimglist_load_plugin2 + cimglist_load_plugin2(filename); +#endif +#ifdef cimglist_load_plugin3 + cimglist_load_plugin3(filename); +#endif +#ifdef cimglist_load_plugin4 + cimglist_load_plugin4(filename); +#endif +#ifdef cimglist_load_plugin5 + cimglist_load_plugin5(filename); +#endif +#ifdef cimglist_load_plugin6 + cimglist_load_plugin6(filename); +#endif +#ifdef cimglist_load_plugin7 + cimglist_load_plugin7(filename); +#endif +#ifdef cimglist_load_plugin8 + cimglist_load_plugin8(filename); +#endif + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) load_cimg(filename); + else if (!cimg::strcasecmp(ext,"rec") || + !cimg::strcasecmp(ext,"par")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded && !is_stdin) { + std::FILE *const file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to open file '%s'.", + cimglist_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + 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 is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file as a single image. + if (!is_loaded) { + assign(1); + try { + _data->load(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to recognize format of file '%s'.", + cimglist_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load a list from a file \newinstance. + static CImgList get_load(const char *const filename) { + return CImgList().load(filename); + } + + //! Load a list from a .cimg file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_cimg(const char *const filename) { + return _load_cimg(0,filename); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename) { + return CImgList().load_cimg(filename); + } + + //! Load a list from a .cimg file. + /** + \param file File to read data from. + **/ + CImgList& load_cimg(std::FILE *const file) { + return _load_cimg(file,0); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file) { + return CImgList().load_cimg(file); + } + + CImgList& _load_cimg(std::FILE *const file, const char *const filename) { +#ifdef cimg_use_zlib +#define _cimgz_load_cimg_case(Tss) { \ + Bytef *const cbuf = new Bytef[csiz]; \ + cimg::fread(cbuf,csiz,nfile); \ + raw.assign(W,H,D,C); \ + uLongf destlen = (ulongT)raw.size()*sizeof(Tss); \ + uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \ + delete[] cbuf; \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ +} +#else +#define _cimgz_load_cimg_case(Tss) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Unable to load compressed data from file '%s' unless zlib is enabled.", \ + cimglist_instance, \ + filename?filename:"(FILE*)"); +#endif + +#define _cimg_load_cimg_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; csiz = 0; \ + if ((err = cimg_sscanf(tmp,"%u %u %u %u #%lu",&W,&H,&D,&C,&csiz))<4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'.", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:("(FILE*)")); \ + if (W*H*D*C>0) { \ + CImg 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; \ + } \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + + const ulongT cimg_iobuffer = (ulongT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + unsigned long csiz; + int i, err; + do { + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; + } while (*tmp=='#' && i>=0); + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + assign(N); + _cimg_load_cimg_case("bool",bool); + _cimg_load_cimg_case("unsigned_char",unsigned char); + _cimg_load_cimg_case("uchar",unsigned char); + _cimg_load_cimg_case("char",char); + _cimg_load_cimg_case("unsigned_short",unsigned short); + _cimg_load_cimg_case("ushort",unsigned short); + _cimg_load_cimg_case("short",short); + _cimg_load_cimg_case("unsigned_int",unsigned int); + _cimg_load_cimg_case("uint",unsigned int); + _cimg_load_cimg_case("int",int); + _cimg_load_cimg_case("unsigned_long",ulongT); + _cimg_load_cimg_case("ulong",ulongT); + _cimg_load_cimg_case("long",longT); + _cimg_load_cimg_case("unsigned_int64",uint64T); + _cimg_load_cimg_case("uint64",uint64T); + _cimg_load_cimg_case("int64",int64T); + _cimg_load_cimg_case("float",float); + _cimg_load_cimg_case("double",double); + + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a sublist list from a (non compressed) .cimg file. + /** + \param filename Filename to read data from. + \param n0 Starting index of images to read (~0U for max). + \param n1 Ending index of images to read (~0U for max). + \param x0 Starting X-coordinates of image regions to read. + \param y0 Starting Y-coordinates of image regions to read. + \param z0 Starting Z-coordinates of image regions to read. + \param c0 Starting C-coordinates of image regions to read. + \param x1 Ending X-coordinates of image regions to read (~0U for max). + \param y1 Ending Y-coordinates of image regions to read (~0U for max). + \param z1 Ending Z-coordinates of image regions to read (~0U for max). + \param c1 Ending C-coordinates of image regions to read (~0U for max). + **/ + CImgList& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sublist list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \overloading. + CImgList& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + 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 z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { +#define _cimg_load_cimg_case2(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l<=nn1; ++l) { \ + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; \ + if (cimg_sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:"(FILE*)"); \ + if (W*H*D*C>0) { \ + if (l=W || ny0>=H || nz0>=D || nc0>=C) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const unsigned int \ + _nx1 = nx1==~0U?W - 1:nx1, \ + _ny1 = ny1==~0U?H - 1:ny1, \ + _nz1 = nz1==~0U?D - 1:nz1, \ + _nc1 = nc1==~0U?C - 1:nc1; \ + if (_nx1>=W || _ny1>=H || _nz1>=D || _nc1>=C) \ + throw CImgArgumentException(_cimglist_instance \ + "load_cimg(): Invalid specified coordinates " \ + "[%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " \ + "because image [%u] in file '%s' has size (%u,%u,%u,%u).", \ + cimglist_instance, \ + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,l,filename?filename:"(FILE*)",W,H,D,C); \ + CImg raw(1 + _nx1 - nx0); \ + CImg &img = _data[l - nn0]; \ + img.assign(1 + _nx1 - nx0,1 + _ny1 - ny0,1 + _nz1 - nz0,1 + _nc1 - nc0); \ + T *ptrd = img._data; \ + ulongT skipvb = nc0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int c = 1 + _nc1 - nc0; c; --c) { \ + const ulongT skipzb = nz0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + _nz1 - nz0; z; --z) { \ + const ulongT skipyb = ny0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + _ny1 - ny0; y; --y) { \ + const ulongT skipxb = nx0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + cimg::fread(raw._data,raw._width,nfile); \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); \ + const Tss *ptrs = raw._data; \ + for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \ + const ulongT skipxe = (W - 1 - _nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const ulongT skipye = (H - 1 - _ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const ulongT skipze = (D - 1 - _nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const ulongT skipve = (C - 1 - _nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + unsigned int + nn0 = std::min(n0,n1), nn1 = std::max(n0,n1), + nx0 = std::min(x0,x1), nx1 = std::max(x0,x1), + ny0 = std::min(y0,y1), ny1 = std::max(y0,y1), + nz0 = std::min(z0,z1), nz1 = std::max(z0,z1), + nc0 = std::min(c0,c1), nc1 = std::max(c0,c1); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + nn1 = n1==~0U?N - 1:n1; + if (nn1>=N) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Invalid specified coordinates [%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " + "because file '%s' contains only %u images.", + cimglist_instance, + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,filename?filename:"(FILE*)",N); + assign(1 + nn1 - n0); + _cimg_load_cimg_case2("bool",bool); + _cimg_load_cimg_case2("unsigned_char",unsigned char); + _cimg_load_cimg_case2("uchar",unsigned char); + _cimg_load_cimg_case2("char",char); + _cimg_load_cimg_case2("unsigned_short",unsigned short); + _cimg_load_cimg_case2("ushort",unsigned short); + _cimg_load_cimg_case2("short",short); + _cimg_load_cimg_case2("unsigned_int",unsigned int); + _cimg_load_cimg_case2("uint",unsigned int); + _cimg_load_cimg_case2("int",int); + _cimg_load_cimg_case2("unsigned_long",ulongT); + _cimg_load_cimg_case2("ulong",ulongT); + _cimg_load_cimg_case2("long",longT); + _cimg_load_cimg_case2("unsigned_int64",uint64T); + _cimg_load_cimg_case2("uint64",uint64T); + _cimg_load_cimg_case2("int64",int64T); + _cimg_load_cimg_case2("float",float); + _cimg_load_cimg_case2("double",double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_parrec(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_parrec(): Specified filename is (null).", + cimglist_instance); + + CImg body(1024), filenamepar(1024), filenamerec(1024); + *body = *filenamepar = *filenamerec = 0; + const char *const ext = cimg::split_filename(filename,body); + if (!std::strcmp(ext,"par")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.rec",body._data); + } + if (!std::strcmp(ext,"PAR")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.REC",body._data); + } + if (!std::strcmp(ext,"rec")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.par",body._data); + } + if (!std::strcmp(ext,"REC")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.PAR",body._data); + } + std::FILE *file = cimg::fopen(filenamepar,"r"); + + // Parse header file + CImgList st_slices; + CImgList st_global; + CImg line(256); *line = 0; + int err; + do { err = std::fscanf(file,"%255[^\n]%*c",line._data); } while (err!=EOF && (*line=='#' || *line=='.')); + do { + unsigned int sn,size_x,size_y,pixsize; + float rs,ri,ss; + err = std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&size_x,&size_y,&ri,&rs,&ss); + if (err==7) { + CImg::vector((float)sn,(float)pixsize,(float)size_x,(float)size_y,ri,rs,ss,0).move_to(st_slices); + unsigned int i; for (i = 0; i::vector(size_x,size_y,sn).move_to(st_global); + else { + CImg &vec = st_global[i]; + if (size_x>vec[0]) vec[0] = size_x; + if (size_y>vec[1]) vec[1] = size_y; + vec[2] = sn; + } + st_slices[st_slices._width - 1][7] = (float)i; + } + } while (err==7); + + // Read data + std::FILE *file2 = cimg::fopen(filenamerec,"rb"); + cimglist_for(st_global,l) { + const CImg& vec = st_global[l]; + CImg(vec[0],vec[1],vec[2]).move_to(*this); + } + + cimglist_for(st_slices,l) { + const CImg& vec = st_slices[l]; + const unsigned int + sn = (unsigned int)vec[0] - 1, + pixsize = (unsigned int)vec[1], + size_x = (unsigned int)vec[2], + size_y = (unsigned int)vec[3], + imn = (unsigned int)vec[7]; + const float ri = vec[4], rs = vec[5], ss = vec[6]; + switch (pixsize) { + case 8 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 16 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 32 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + default : + cimg::fclose(file); + cimg::fclose(file2); + throw CImgIOException(_cimglist_instance + "load_parrec(): Unsupported %d-bits pixel type for file '%s'.", + cimglist_instance, + pixsize,filename); + } + } + cimg::fclose(file); + cimg::fclose(file2); + if (!_width) + throw CImgIOException(_cimglist_instance + "load_parrec(): Failed to recognize valid PAR-REC data in file '%s'.", + cimglist_instance, + filename); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file \newinstance. + static CImgList get_load_parrec(const char *const filename) { + return CImgList().load_parrec(filename); + } + + //! Load a list from a YUV image sequence file. + /** + \param filename Filename to read data from. + \param size_x Width of the images. + \param size_y Height of the images. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of first image frame to read. + \param last_frame Index of last image frame to read. + \param step_frame Step applied between each frame. + \param yuv2rgb Apply YUV to RGB transformation during reading. + **/ + CImgList& load_yuv(const char *const filename, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(0,filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from a YUV image sequence file \newinstance. + static CImgList get_load_yuv(const char *const filename, + 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) { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \overloading. + CImgList& load_yuv(std::FILE *const file, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(file,0,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \newinstance. + static CImgList get_load_yuv(std::FILE *const file, + 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) { + return CImgList().load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + CImgList& _load_yuv(std::FILE *const file, const char *const filename, + 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) { + 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'.", + 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_frame YUV(size_x,size_y,1,3), UV(size_x/cfx,size_y/cfy,1,2); + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool stop_flag = false; + int err; + if (nfirst_frame) { + err = cimg::fseek(nfile,(uint64T)nfirst_frame*(YUV._width*YUV._height + 2*UV._width*UV._height),SEEK_CUR); + if (err) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_yuv(): File '%s' doesn't contain frame number %u.", + cimglist_instance, + filename?filename:"(FILE*)",nfirst_frame); + } + } + unsigned int frame; + for (frame = nfirst_frame; !stop_flag && frame<=nlast_frame; frame+=nstep_frame) { + YUV.get_shared_channel(0).fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(YUV._data),1,(size_t)YUV._width*YUV._height,nfile); + if (err!=(int)(YUV._width*YUV._height)) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + UV.fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(UV._data),1,(size_t)UV.size(),nfile); + if (err!=(int)(UV.size())) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + const ucharT *ptrs1 = UV._data, *ptrs2 = UV.data(0,0,0,1); + ucharT *ptrd1 = YUV.data(0,0,0,1), *ptrd2 = YUV.data(0,0,0,2); + const unsigned int wd = YUV._width; + switch (chroma_subsampling) { + case 420 : + cimg_forY(UV,y) { + cimg_forX(UV,x) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd2[wd] = V; *(ptrd2)++ = V; + ptrd2[wd] = V; *(ptrd2)++ = V; + } + ptrd1+=wd; ptrd2+=wd; + } + break; + case 422 : + cimg_forXY(UV,x,y) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + *(ptrd1++) = U; *(ptrd1++) = U; + *(ptrd2++) = V; *(ptrd2++) = V; + } + break; + default : + YUV.draw_image(0,0,0,1,UV); + } + if (yuv2rgb) YUV.YCbCrtoRGB(); + insert(YUV); + if (nstep_frame>1) cimg::fseek(nfile,(uint64T)(nstep_frame - 1)*(size_x*size_y + size_x*size_y/2),SEEK_CUR); + } + } + } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_yuv() : Missing data in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + if (stop_flag && nlast_frame!=~0U && frame!=nlast_frame) + cimg::warn(_cimglist_instance + "load_yuv(): Frame %d not reached since only %u frames were found in file '%s'.", + cimglist_instance, + nlast_frame,frame - 1,filename?filename:"(FILE*)"); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \note If step_frame==0, the current video stream is forced to be released (without any frames read). + **/ + CImgList& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { +#ifndef cimg_use_opencv + if (first_frame || last_frame!=~0U || step_frame>1) + throw CImgArgumentException(_cimglist_instance + "load_video() : File '%s', arguments 'first_frame', 'last_frame' " + "and 'step_frame' can be only set when using OpenCV " + "(-Dcimg_use_opencv must be enabled).", + cimglist_instance,filename); + return load_ffmpeg_external(filename); +#else + static CvCapture *captures[32] = { 0 }; + static CImgList filenames(32); + static CImg positions(32,1,1,1,0); + static int last_used_index = -1; + + // Detect if a video capture already exists for the specified filename. + cimg::mutex(9); + int index = -1; + if (filename) { + if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) { + index = last_used_index; + } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) { + index = l; break; + } + } else index = last_used_index; + cimg::mutex(9,0); + + // Release stream if needed. + if (!step_frame || (index>=0 && positions[index]>first_frame)) { + if (index>=0) { + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; filenames[index].assign(); positions[index] = 0; + if (last_used_index==index) last_used_index = -1; + index = -1; + cimg::mutex(9,0); + } else + if (filename) + cimg::warn(_cimglist_instance + "load_video() : File '%s', no opened video stream associated with filename found.", + cimglist_instance,filename); + else + cimg::warn(_cimglist_instance + "load_video() : No opened video stream found.", + cimglist_instance,filename); + if (!step_frame) return *this; + } + + // Find empty slot for capturing video stream. + if (index<0) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_video(): No already open video reader found. You must specify a " + "non-(null) filename argument for the first call.", + cimglist_instance); + else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); } + if (index<0) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', no video reader slots available. " + "You have to release some of your previously opened videos.", + cimglist_instance,filename); + cimg::mutex(9); + captures[index] = cvCaptureFromFile(filename); + CImg::string(filename).move_to(filenames[index]); + positions[index] = 0; + cimg::mutex(9,0); + if (!captures[index]) { + filenames[index].assign(); + 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::mutex(9); + const unsigned int nb_frames = (unsigned int)std::max(0.,cvGetCaptureProperty(captures[index], + CV_CAP_PROP_FRAME_COUNT)); + cimg::mutex(9,0); + assign(); + + // Skip frames if necessary. + bool go_on = true; + unsigned int &pos = positions[index]; + while (pos frame(src->width,src->height,1,3); + const int step = (int)(src->widthStep - 3*src->width); + const unsigned char* ptrs = (unsigned char*)src->imageData; + T *ptr_r = frame.data(0,0,0,0), *ptr_g = frame.data(0,0,0,1), *ptr_b = frame.data(0,0,0,2); + if (step>0) cimg_forY(frame,y) { + cimg_forX(frame,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); } + ptrs+=step; + } else for (ulongT siz = (ulongT)src->width*src->height; siz; --siz) { + *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); + } + frame.move_to(*this); + ++pos; + + bool skip_failed = false; + for (unsigned int i = 1; i=nb_frames)) { // Close video stream when necessary + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; + filenames[index].assign(); + positions[index] = 0; + index = -1; + cimg::mutex(9,0); + } + + cimg::mutex(9); + last_used_index = index; + cimg::mutex(9,0); + + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', unable to locate frame %u.", + cimglist_instance,filename,first_frame); + return *this; +#endif + } + + //! Load an image from a video file, using OpenCV library \newinstance. + static CImgList get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame); + } + + //! Load an image from a video file using the external tool 'ffmpeg'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_ffmpeg_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + 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=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::ffmpeg_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp2)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + unsigned int i = 1; + for (bool stop_flag = false; !stop_flag; ++i) { + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,i); + CImg img; + try { img.load_pnm(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + cimg::exception_mode(omode); + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_ffmpeg_external(): Failed to open file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + return *this; + } + + //! Load an image from a video file using the external tool 'ffmpeg' \newinstance. + static CImgList get_load_ffmpeg_external(const char *const filename) { + return CImgList().load_ffmpeg_external(filename); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gif_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_gif_external(): Specified filename is (null).", + cimglist_instance); + 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(); } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_gif_external(): Failed to open file '%s'.", + cimglist_instance,filename); + return *this; + } + + CImgList& _load_gif_external(const char *const filename, const bool use_graphicsmagick=false) { + 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()); + 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=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\"", + cimg::imagemagick_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + + // Try to read a single frame gif. + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png",filename_tmp._data); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + else { // Try to read animated gif + unsigned int i = 0; + for (bool stop_flag = false; !stop_flag; ++i) { + if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.%u",filename_tmp._data,i); + else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-%u.png",filename_tmp._data,i); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools \newinstance. + static CImgList get_load_gif_external(const char *const filename) { + return CImgList().load_gif_external(filename); + } + + //! Load a gzipped list, using external tool 'gunzip'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Specified filename is (null).", + cimglist_instance); + 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), + *ext2 = cimg::split_filename(body,0); + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=cimg::std_fopen(filename_tmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Failed to open file '%s'.", + cimglist_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load a gzipped list, using external tool 'gunzip' \newinstance. + static CImgList get_load_gzip_external(const char *const filename) { + 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] 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 + nfirst_frame = first_frame::get_load_tiff(filename)); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimglist_instance + "load_tiff(): Invalid specified frame range is [%u,%u] (step %u) since " + "file '%s' contains %u image(s).", + cimglist_instance, + nfirst_frame,nlast_frame,nstep_frame,filename,nb_images); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + assign(1 + (nlast_frame - nfirst_frame)/nstep_frame); + TIFFSetDirectory(tif,0); + cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimglist_instance + "load_tiff(): Failed to open file '%s'.", + cimglist_instance, + filename); + return *this; +#endif + } + + //! Load a multi-page TIFF file \newinstance. + static CImgList 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); + } + + //@} + //---------------------------------- + // + //! \name Data Output + //@{ + //---------------------------------- + + //! Print information about the list on the standard output. + /** + \param title Label set to the information displayed. + \param display_stats Tells if image statistics must be computed and displayed. + **/ + const CImgList& print(const char *const title=0, const bool display_stats=true) const { + unsigned int msiz = 0; + cimglist_for(*this,l) msiz+=_data[l].size(); + msiz*=sizeof(T); + const unsigned int mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U; + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImgList<%s>",pixel_type()); + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = %u/%u [%u %s], %sdata%s = (CImg<%s>*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_allocated_width, + mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end() - 1)); + else std::fprintf(cimg::output(),".\n"); + + char tmp[16] = { 0 }; + cimglist_for(*this,ll) { + cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll); + std::fprintf(cimg::output()," "); + _data[ll].print(tmp,display_stats); + if (ll==3 && width()>8) { ll = width() - 5; std::fprintf(cimg::output()," ...\n"); } + } + std::fflush(cimg::output()); + return *this; + } + + //! Display the current CImgList instance in an existing CImgDisplay window (by reference). + /** + \param disp Reference to an existing CImgDisplay instance, where the current image list will be displayed. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignmenet. + \note This function displays the list images of the current CImgList instance into an existing + CImgDisplay window. + Images of the list are appended in a single temporarly image for visualization purposes. + The function returns immediately. + **/ + const CImgList& display(CImgDisplay &disp, const char axis='x', const float align=0) const { + disp.display(*this,axis,align); + return *this; + } + + //! Display the current CImgList instance in a new display window. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \param axis Alignment axis for images viewing. + \param align Apending alignment. + \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. + 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, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + bool is_exit = false; + return _display(disp,0,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + //! Display the current CImgList instance in a new display window. + /** + \param title Title of the opening display window. + \param display_info Tells if list information must be written on standard output. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \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. + **/ + const CImgList& display(const char *const title=0, const bool display_info=true, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + CImgDisplay disp; + bool is_exit = false; + return _display(disp,title,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + const CImgList& _display(CImgDisplay &disp, const char *const title, const CImgList *const titles, + const bool display_info, const char axis, const float align, unsigned int *const XYZ, + const bool exit_on_anykey, const unsigned int orig, const bool is_first_call, + bool &is_exit) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "display(): Empty instance.", + cimglist_instance); + if (!disp) { + if (axis=='x') { + unsigned int sum_width = 0, max_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + sum_width+=w; + if (h>max_height) max_height = h; + } + disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:titles?titles->__display()._data:0,1); + } else { + unsigned int max_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + sum_height+=h; + } + disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:titles?titles->__display()._data:0,1); + } + if (!title && !titles) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else if (title) disp.set_title("%s",title); + else if (titles) disp.set_title("%s",titles->__display()._data); + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(disp.title()); + disp.show().flush(); + + if (_width==1) { + const unsigned int dw = disp._width, dh = disp._height; + if (!is_first_call) + disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false); + disp.set_title("%s (%ux%ux%ux%u)", + dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum); + _data[0]._display(disp,0,false,XYZ,exit_on_anykey,!is_first_call); + if (disp.key()) is_exit = true; + disp.resize(cimg_fitscreen(dw,dh,1),false).set_title("%s",dtitle.data()); + } else { + bool disp_resize = !is_first_call; + while (!disp.is_closed() && !is_exit) { + const CImg s = _select(disp,0,true,axis,align,exit_on_anykey,orig,disp_resize,!is_first_call,true); + disp_resize = true; + if (s[0]<0 && !disp.wheel()) { // No selections done + if (disp.button()&2) { disp.flush(); break; } + is_exit = true; + } else if (disp.wheel()) { // Zoom in/out + const int wheel = disp.wheel(); + disp.set_wheel(); + if (!is_first_call && wheel<0) break; + if (wheel>0 && _width>=4) { + const unsigned int + delta = std::max(1U,(unsigned int)cimg::round(0.3*_width)), + ind0 = (unsigned int)std::max(0,s[0] - (int)delta), + ind1 = (unsigned int)std::min(width() - 1,s[0] + (int)delta); + if ((ind0!=0 || ind1!=_width - 1) && ind1 - ind0>=3) { + const CImgList sublist = get_shared_images(ind0,ind1); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(ind0,ind1); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + ind0,false,is_exit); + } + } + } else if (s[0]!=0 || s[1]!=width() - 1) { + const CImgList sublist = get_shared_images(s[0],s[1]); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(s[0],s[1]); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + s[0],false,is_exit); + } + disp.set_title("%s",dtitle.data()); + } + } + return *this; + } + + // [internal] Return string to describe display title. + CImg __display() const { + CImg res, str; + cimglist_for(*this,l) { + CImg::string(_data[l]).move_to(str); + if (l!=width() - 1) { + str.resize(str._width + 1,1,1,1,0); + str[str._width - 2] = ','; + str[str._width - 1] = ' '; + } + res.append(str,'x'); + } + if (!res) return CImg(1,1,1,1,0).move_to(res); + cimg::strellipsize(res,128,false); + if (_width>1) { + const unsigned int l = (unsigned int)std::strlen(res); + if (res._width<=l + 16) res.resize(l + 16,1,1,1,0); + cimg_snprintf(res._data + l,16," (#%u)",_width); + } + return res; + } + + //! Save list into a file. + /** + \param filename Filename to write data to. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + **/ + const CImgList& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save(): Specified filename is (null).", + cimglist_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:number>=0?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimglist_save_plugin + cimglist_save_plugin(fn); +#endif +#ifdef cimglist_save_plugin1 + cimglist_save_plugin1(fn); +#endif +#ifdef cimglist_save_plugin2 + cimglist_save_plugin2(fn); +#endif +#ifdef cimglist_save_plugin3 + cimglist_save_plugin3(fn); +#endif +#ifdef cimglist_save_plugin4 + cimglist_save_plugin4(fn); +#endif +#ifdef cimglist_save_plugin5 + cimglist_save_plugin5(fn); +#endif +#ifdef cimglist_save_plugin6 + cimglist_save_plugin6(fn); +#endif +#ifdef cimglist_save_plugin7 + cimglist_save_plugin7(fn); +#endif +#ifdef cimglist_save_plugin8 + cimglist_save_plugin8(fn); +#endif + if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); +#ifdef cimg_use_tiff + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); +#endif + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + else { + if (_width==1) _data[0].save(fn,-1); + else cimglist_for(*this,l) { _data[l].save(fn,is_stdout?-1:l); if (is_stdout) std::fputc(EOF,cimg::_stdout()); } + } + return *this; + } + + //! Tell if an image list can be saved as one single file. + /** + \param filename Filename, as a C-string. + \return \c true if the file format supports multiple images, \c false otherwise. + **/ + static bool is_saveable(const char *const filename) { + const char *const ext = cimg::split_filename(filename); + if (!cimg::strcasecmp(ext,"cimgz") || +#ifdef cimg_use_tiff + !cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff") || +#endif + !cimg::strcasecmp(ext,"yuv") || + !cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return true; + return false; + } + + //! Save image sequence as a GIF animated file. + /** + \param filename Filename to write data to. + \param fps Number of desired frames per second. + \param nb_loops Number of loops (\c 0 for infinite looping). + **/ + const CImgList& save_gif_external(const char *const filename, const float fps=25, + const unsigned int nb_loops=0) { + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + +#ifdef cimg_use_png +#define _cimg_save_gif_ext "png" +#else +#define _cimg_save_gif_ext "ppm" +#endif + + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_ext,filename_tmp._data); + if ((file=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::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_snprintf(command,command._width,"%s -delay %u -loop %u", + 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()); + CImg::string(command).move_to(filenames); + CImg _command = filenames>'x'; + cimg_for(_command,p,char) if (!*p) *p = ' '; + _command.back() = 0; + + cimg::system(_command); + 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'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for_in(*this,1,filenames._width - 1,l) std::remove(filenames[l]); + return *this; + } + + //! Save list as a YUV image sequence file. + /** + \param filename Filename to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(const char *const filename=0, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(0,filename,chroma_subsampling,is_rgb); + } + + //! Save image sequence into a YUV file. + /** + \param file File to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(file,0,chroma_subsampling,is_rgb); + } + + const CImgList& _save_yuv(std::FILE *const file, const char *const filename, + const unsigned int chroma_subsampling, + const bool is_rgb) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified filename is (null).", + cimglist_instance); + if (chroma_subsampling!=420 && chroma_subsampling!=422 && chroma_subsampling!=444) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified chroma subsampling %u is invalid, for file '%s'.", + cimglist_instance, + chroma_subsampling,filename?filename:"(FILE*)"); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + const unsigned int + cfx = chroma_subsampling==420 || chroma_subsampling==422?2:1, + cfy = chroma_subsampling==420?2:1, + w0 = (*this)[0]._width, h0 = (*this)[0]._height, + width0 = w0 + (w0%cfx), height0 = h0 + (h0%cfy); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + cimglist_for(*this,l) { + const CImg &frame = (*this)[l]; + cimg_forZ(frame,z) { + CImg YUV; + if (sizeof(T)==1 && !is_rgb && + frame._width==width0 && frame._height==height0 && frame._depth==1 && frame._spectrum==3) + YUV.assign((unsigned char*)frame._data,width0,height0,1,3,true); + else { + YUV = frame.get_slice(z); + if (YUV._width!=width0 || YUV._height!=height0) YUV.resize(width0,height0,1,-100,0); + if (YUV._spectrum!=3) YUV.resize(-100,-100,1,3,YUV._spectrum==1?1:0); + if (is_rgb) YUV.RGBtoYCbCr(); + } + if (chroma_subsampling==444) + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height*3,nfile); + else { + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height,nfile); + CImg UV = YUV.get_channels(1,2); + UV.resize(UV._width/cfx,UV._height/cfy,1,2,2); + cimg::fwrite(UV._data,(size_t)UV._width*UV._height*2,nfile); + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param filename Filename to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(const char *const filename, const bool is_compressed=false) const { + return _save_cimg(0,filename,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, const bool is_compressed) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "save_cimg(): Unable to save compressed data in file '%s' unless zlib is enabled, " + "saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); +#endif + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little"; + if (std::strstr(ptype,"unsigned")==ptype) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",_width,ptype + 9,etype); + else std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = siz + siz/100 + 16; + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "save_cimg(): Failed to save compressed data for file '%s', saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); + else { + std::fprintf(nfile," #%lu\n",csiz); + cimg::fwrite(cbuf,csiz,nfile); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + if (failed_to_compress) { // Write in a non-compressed way + std::fputc('\n',nfile); + cimg::fwrite(ref._data,ref.size(),nfile); + } + } else std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param file File to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(std::FILE *file, const bool is_compressed=false) const { + return _save_cimg(file,0,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { +#define _cimg_save_cimg_case(Ts,Tss) \ + if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l0) { \ + if (l=W || y0>=H || z0>=D || c0>=D) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const CImg& img = (*this)[l - n0]; \ + const T *ptrs = img._data; \ + const unsigned int \ + x1 = x0 + img._width - 1, \ + y1 = y0 + img._height - 1, \ + z1 = z0 + img._depth - 1, \ + c1 = c0 + img._spectrum - 1, \ + nx1 = x1>=W?W - 1:x1, \ + ny1 = y1>=H?H - 1:y1, \ + nz1 = z1>=D?D - 1:z1, \ + nc1 = c1>=C?C - 1:c1; \ + CImg raw(1 + nx1 - x0); \ + const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int v = 1 + nc1 - c0; v; --v) { \ + const unsigned int skipzb = z0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + nz1 - z0; z; --z) { \ + const unsigned int skipyb = y0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + ny1 - y0; y; --y) { \ + const unsigned int skipxb = x0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + raw.assign(ptrs, raw._width); \ + ptrs+=img._width; \ + if (endian) cimg::invert_endianness(raw._data,raw._width); \ + cimg::fwrite(raw._data,raw._width,nfile); \ + const unsigned int skipxe = (W - 1 - nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "save_cimg(): Empty instance, for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+"); + bool saved = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + const unsigned int lmax = std::min(N,n0 + _width); + _cimg_save_cimg_case("bool",bool); + _cimg_save_cimg_case("unsigned_char",unsigned char); + _cimg_save_cimg_case("uchar",unsigned char); + _cimg_save_cimg_case("char",char); + _cimg_save_cimg_case("unsigned_short",unsigned short); + _cimg_save_cimg_case("ushort",unsigned short); + _cimg_save_cimg_case("short",short); + _cimg_save_cimg_case("unsigned_int",unsigned int); + _cimg_save_cimg_case("uint",unsigned int); + _cimg_save_cimg_case("int",int); + _cimg_save_cimg_case("unsigned_int64",uint64T); + _cimg_save_cimg_case("uint64",uint64T); + _cimg_save_cimg_case("int64",int64T); + _cimg_save_cimg_case("float",float); + _cimg_save_cimg_case("double",double); + if (!saved) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): Unsupported data type '%s' for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)",str_pixeltype._data); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param filename Filename to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(0,filename,n0,x0,y0,z0,c0); + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param file File to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(file,0,n0,x0,y0,z0,c0); + } + + static void _save_empty_cimg(std::FILE *const file, const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) { + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT siz = (ulongT)dx*dy*dz*dc*sizeof(T); + std::fprintf(nfile,"%u %s\n",nb,pixel_type()); + for (unsigned int i=nb; i; --i) { + std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dc); + for (ulongT off = siz; off; --off) std::fputc(0,nfile); + } + if (!file) cimg::fclose(nfile); + } + + //! Save empty (non-compressed) .cimg file with specified dimensions. + /** + \param filename Filename to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc); + } + + //! Save empty .cimg file with specified dimensions. + /** + \param file File to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(file,0,nb,dx,dy,dz,dc); + } + + //! Save list as a TIFF file. + /** + \param filename Filename to write data to. + \param compression_type Compression mode used to write data. + \param voxel_size Voxel size, to be stored in the filename. + \param description Description, to be stored in the filename. + \param use_bigtiff Allow to save big tiff files (>4Gb). + **/ + const CImgList& save_tiff(const char *const filename, const unsigned int compression_type=0, + const float *const voxel_size=0, const char *const description=0, + const bool use_bigtiff=true) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_tiff(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_tiff + if (_width==1) _data[0].save_tiff(filename,compression_type,voxel_size,description,use_bigtiff); + else cimglist_for(*this,l) { + CImg nfilename(1024); + cimg::number_filename(filename,l,6,nfilename); + _data[l].save_tiff(nfilename,compression_type,voxel_size,description,use_bigtiff); + } +#else + ulongT siz = 0; + cimglist_for(*this,l) siz+=_data[l].size(); + const bool _use_bigtiff = use_bigtiff && sizeof(siz)>=8 && siz*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + for (unsigned int dir = 0, l = 0; l<_width; ++l) { + const CImg& img = (*this)[l]; + cimg_forZ(img,z) img._save_tiff(tif,dir++,z,compression_type,voxel_size,description); + } + TIFFClose(tif); + } else + throw CImgIOException(_cimglist_instance + "save_tiff(): Failed to open stream for file '%s'.", + cimglist_instance, + filename); +#endif + return *this; + } + + //! Save list as a gzipped file, using external tool 'gzip'. + /** + \param filename Filename to write data to. + **/ + const CImgList& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Specified filename is (null).", + cimglist_instance); + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimglist_instance, + filename); + else cimg::fclose(file); + std::remove(filename_tmp); + } else { + CImg nfilename(1024); + cimglist_for(*this,l) { + cimg::number_filename(body,l,6,nfilename); + if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext); + _data[l].save_gzip_external(nfilename); + } + } + return *this; + } + + //! Save image sequence, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImgList& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { +#ifndef cimg_use_opencv + cimg::unused(codec,keep_open); + return save_ffmpeg_external(filename,fps); +#else + static CvVideoWriter *writers[32] = { 0 }; + static CImgList 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; + 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; + 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++); + } + } + cvWriteFrame(writers[index],ipl); + } + cvReleaseImage(&ipl); + cimg::mutex(9,0); + } + + cimg::mutex(9); + if (!keep_open) { + cvReleaseVideoWriter(&writers[index]); + writers[index] = 0; + filenames[index].assign(); + sizes(index,0) = sizes(index,1) = 0; + last_used_index = -1; + } else last_used_index = index; + cimg::mutex(9,0); + + return *this; +#endif + } + + //! Save image sequence, using the external tool 'ffmpeg'. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression. + \param bitrate Output bitrate + **/ + const CImgList& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + const char + *const ext = cimg::split_filename(filename), + *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video"; + + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + cimglist_for(*this,l) if (!_data[l].is_sameXYZ(_data[0])) + throw CImgInstanceException(_cimglist_instance + "save_ffmpeg_external(): Invalid instance dimensions for file '%s'.", + cimglist_instance, + filename); + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data); + if ((file=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.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_snprintf(command,command._width,"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\"", + cimg::ffmpeg_path(), + CImg::string(filename_tmp)._system_strescape().data(), + _codec,bitrate,fps, + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_ffmpeg_external(): Failed to save file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for(*this,l) std::remove(filenames[l]); + return *this; + } + + //! Serialize a CImgList instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "get_serialize(): Unable to compress data unless zlib is enabled, " + "storing them uncompressed.", + cimglist_instance); +#endif + CImgList 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::string(tmpstr,false).move_to(stream); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_snprintf(tmpstr,tmpstr._width,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + CImg::string(tmpstr,false).move_to(stream); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = (ulongT)compressBound(siz); + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "get_serialize(): Failed to save compressed data, saving them uncompressed.", + cimglist_instance); + else { + cimg_snprintf(tmpstr,tmpstr._width," #%lu\n",csiz); + CImg::string(tmpstr,false).move_to(stream); + CImg(cbuf,csiz).move_to(stream); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + 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); + } + } else CImg::string("\n",false).move_to(stream); + } + cimglist_apply(stream,unroll)('y'); + return stream>'y'; + } + + //! Unserialize a CImg serialized buffer into a CImgList list. + template + static CImgList get_unserialize(const CImg& buffer) { +#ifdef cimg_use_zlib +#define _cimgz_unserialize_case(Tss) { \ + Bytef *cbuf = 0; \ + if (sizeof(t)!=1 || cimg::type::string()==cimg::type::string()) { \ + cbuf = new Bytef[csiz]; Bytef *_cbuf = cbuf; \ + for (ulongT i = 0; i::get_unserialize(): Unable to unserialize compressed data " \ + "unless zlib is enabled.", \ + pixel_type()); +#endif + +#define _cimg_unserialize_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l::unserialize(): Invalid specified size (%u,%u,%u,%u) for " \ + "image #%u in serialized buffer.", \ + pixel_type(),W,H,D,C,l); \ + if (W*H*D*C>0) { \ + CImg 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 { \ + 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 (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ + } \ + } \ + loaded = true; \ + } + + if (buffer.is_empty()) + 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(); + bool loaded = false, endian = cimg::endianness(), is_bytef = false; + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + uint64T csiz; + int i, err; + cimg::unused(is_bytef); + do { + j = 0; while ((i=(int)*stream)!='\n' && stream::get_unserialize(): CImg header not found in serialized buffer.", + pixel_type()); + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + res.assign(N); + _cimg_unserialize_case("bool",bool); + _cimg_unserialize_case("unsigned_char",unsigned char); + _cimg_unserialize_case("uchar",unsigned char); + _cimg_unserialize_case("char",char); + _cimg_unserialize_case("unsigned_short",unsigned short); + _cimg_unserialize_case("ushort",unsigned short); + _cimg_unserialize_case("short",short); + _cimg_unserialize_case("unsigned_int",unsigned int); + _cimg_unserialize_case("uint",unsigned int); + _cimg_unserialize_case("int",int); + _cimg_unserialize_case("unsigned_int64",uint64T); + _cimg_unserialize_case("uint64",uint64T); + _cimg_unserialize_case("int64",int64T); + _cimg_unserialize_case("float",float); + _cimg_unserialize_case("double",double); + if (!loaded) + throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unsupported pixel type '%s' defined " + "in serialized buffer.", + pixel_type(),str_pixeltype._data); + return res; + } + + //@} + //---------------------------------- + // + //! \name Others + //@{ + //---------------------------------- + + //! Return a CImg pre-defined font with requested height. + /** + \param font_height Height of the desired font (exact match for 13,23,53,103). + \param is_variable_width Decide if the font has a variable (\c true) or fixed (\c false) width. + **/ + static const CImgList& 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. + CImg &basef = font_base[ind]; + if (!basef) { + basef.assign(256*font_width[ind],font_height[ind]); + + 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). + 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 }; + ind = ~0U; + for (int i = 0; i<16; ++i) + 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 + fonts->assign(); + std::memmove(fonts,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 + } + CImgList &font = fonts[ind]; + + // Render requested font. + if (!font) { + const unsigned int padding_x = requested_height<=64?1U:requested_height<=128?2U:3U; + is_variable_widths[ind] = is_variable_width; + font = basef.get_split('x',256); + 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, + font[0]._height>requested_height?2: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_forXY(letter,x,y) if (letter(x,y)) { if (xxmax) xmax = x; } + if (xmin<=xmax) letter.crop(xmin,0,xmax,letter._height - 1); + } + font[' '].resize(font['f']._width,-100,-100,-100,0); + if (' ' + 256& FFT(const char axis, const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],axis,invert); + return *this; + } + + //! Compute a 1-D Fast Fourier Transform, along specified axis \newinstance. + CImgList get_FFT(const char axis, const bool invert=false) const { + return CImgList(*this,false).FFT(axis,invert); + } + + //! Compute a n-d Fast Fourier Transform. + /** + \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed. + **/ + CImgList& FFT(const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],invert); + return *this; + } + + //! Compute a 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. + /** + **/ + CImgList& reverse_object3d() { + cimglist_for(*this,l) { + CImg& p = _data[l]; + switch (p.size()) { + case 2 : case 3: cimg::swap(p[0],p[1]); break; + case 6 : cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break; + case 9 : cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break; + case 4 : cimg::swap(p[0],p[1],p[2],p[3]); break; + case 12 : cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break; + } + } + return *this; + } + + //! Reverse primitives orientations of a 3D object \newinstance. + CImgList get_reverse_object3d() const { + return (+*this).reverse_object3d(); + } + + //@} + }; // struct CImgList { ... + + /* + #--------------------------------------------- + # + # Completion of previously declared functions + # + #---------------------------------------------- + */ + +namespace cimg { + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + // (throw a CImgIOException when macro 'cimg_use_r' is defined). + inline FILE* _stdin(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdin; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdin(): Reference to 'stdin' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stdout(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdout; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdout(): Reference to 'stdout' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stderr(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stderr; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stderr(): Reference to 'stderr' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + // Open a file (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 + // Try alternative method, with wide-character string. + int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0); + if (err) { + CImg wpath(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(err); + if (MultiByteToWideChar(CP_UTF8,0,mode,-1,wmode,err)) + return _wfopen(wpath,wmode); + } + } + } +#endif + return 0; + } + + //! 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 + _cimg_test_temporary_path("/tmp"); + _cimg_test_temporary_path("/var/tmp"); +#endif + if (!path_found) { + *s_path = 0; + std::strncpy(tmp,filename_tmp,tmp._width - 1); + if ((file=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"); + } + } + 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). +#if !defined(__INTEL_COMPILER) + if (!SHGetSpecialFolderPathA(0,s_path,0x0026,false)) { + const char *const pfPath = std::getenv("PROGRAMFILES"); + if (pfPath) std::strncpy(s_path,pfPath,MAX_PATH - 1); + else std::strcpy(s_path,"C:\\PROGRA~1"); + } +#else + std::strcpy(s_path,"C:\\PROGRA~1"); +#endif + } + cimg::mutex(7,0); + return s_path; + } +#endif + + //! Get/set path to the ImageMagick's \c convert binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c convert binary. + **/ + inline const char* imagemagick_path(const char *const user_path, const bool reinit_path) { + static CImg 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=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 + 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 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 + const char *const pf_path = 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\\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\\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\\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,"%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,"%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,"%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:\\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:\\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:\\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,"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,"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,"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:\\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:\\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:\\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:\\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 + 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); + } + 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; +#if cimg_OS==2 + const char *const pf_path = 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,"./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 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=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 \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=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,"./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 \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=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,"./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); + } + 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=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 \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=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,"./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); + } + cimg::mutex(7,0); + return s_path; + } + + //! Get/set path to the \c curl binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c curl binary. + **/ + inline const char *curl_path(const char *const user_path, const bool reinit_path) { + static CImg s_path; + cimg::mutex(7); + if (reinit_path) s_path.assign(); + if (user_path) { + if (!s_path) s_path.assign(1024); + std::strncpy(s_path,user_path,1023); + } else if (!s_path) { + s_path.assign(1024); + bool path_found = false; + std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + std::strcpy(s_path,".\\curl.exe"); + if ((file=cimg::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; } + } + if (!path_found) std::strcpy(s_path,"curl"); +#endif + winformat_string(s_path); + } + cimg::mutex(7,0); + return s_path; + } + + // [internal] Sorting function, used by cimg::files(). + inline int _sort_files(const void* a, const void* b) { + const CImg &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 +#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); + } + } + } 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); +#endif + + // Sort resulting list by lexicographic order. + if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg),_sort_files); + + return res; + } + + //! Try to guess format from an image file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + \return C-string containing the guessed file format, or \c 0 if nothing has been guessed. + **/ + inline const char *ftype(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::ftype(): Specified filename is (null)."); + static const char + *const _pnm = "pnm", + *const _pfm = "pfm", + *const _bmp = "bmp", + *const _gif = "gif", + *const _jpg = "jpg", + *const _off = "off", + *const _pan = "pan", + *const _png = "png", + *const _tif = "tif", + *const _inr = "inr", + *const _dcm = "dcm"; + const char *f_type = 0; + CImg 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; } + } + f_type = 0; break; + } + } + } 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)."); + + 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); +#endif + + CImg command((unsigned int)std::strlen(url) + 64); + cimg::unused(try_fallback); + + // Try with 'curl' first. + if (timeout) { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,timeout,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } else { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } 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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::std_fopen(filename_local,"rb"))) + throw CImgIOException("cimg::load_network(): Failed to load file '%s' with external commands " + "'wget' or 'curl'.",url); + cimg::fclose(file); + + // Try gunzip it. + cimg_snprintf(command,command._width,"%s.gz",filename_local); + std::rename(filename_local,command); + cimg_snprintf(command,command._width,"%s --quiet \"%s.gz\"", + gunzip_path(),filename_local); + cimg::system(command); + file = 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"); + } + } + 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.), + 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; + } + + // 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 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."); +#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; +#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); + } + + // End of cimg:: namespace +} + + // End of cimg_library:: namespace +} + +//! Short alias name. +namespace cil = cimg_library_suffixed; + +#ifdef _cimg_redefine_False +#define False 0 +#endif +#ifdef _cimg_redefine_True +#define True 1 +#endif +#ifdef _cimg_redefine_min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_PI +#define PI 3.141592653589793238462643383 +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif +// Local Variables: +// mode: c++ +// End: From 102a4eaf4b82d1bc4e03e68e04a75797259b6bb2 Mon Sep 17 00:00:00 2001 From: Grzegorz Cielniak Date: Fri, 1 Feb 2019 15:27:42 +0000 Subject: [PATCH 03/69] Tutorial 2 working draft --- Tutorial 2/CMakeLists.txt | 8 +++++ Tutorial 2/src/Tutorial 2.cpp | 31 ++++++++-------- Tutorial 2/src/kernels/my_kernels_2.cl | 50 +++++++++++++++++++------- 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/Tutorial 2/CMakeLists.txt b/Tutorial 2/CMakeLists.txt index 5cac5fa..cf9d78c 100644 --- a/Tutorial 2/CMakeLists.txt +++ b/Tutorial 2/CMakeLists.txt @@ -16,6 +16,14 @@ set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) +if (NOT MSVC) +find_package(Threads REQUIRED) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + +find_package(X11 REQUIRED) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${X11_LIBRARIES}) +endif (NOT MSVC) + set (SOURCES ../include/Utils.h "src/Tutorial 2.cpp" diff --git a/Tutorial 2/src/Tutorial 2.cpp b/Tutorial 2/src/Tutorial 2.cpp index f64bbd1..d27532a 100644 --- a/Tutorial 2/src/Tutorial 2.cpp +++ b/Tutorial 2/src/Tutorial 2.cpp @@ -38,10 +38,8 @@ int main(int argc, char **argv) { //detect any potential exceptions try { - CImg image_before("test.ppm"); - CImgDisplay main_disp(image_before,"before"); - - std::vector image_after; + CImg image_input("test.ppm"); + CImgDisplay disp_input(image_input,"input"); //a 3x3 convolution mask implementing an averaging filter std::vector convolution_mask = { 1.f / 9, 1.f / 9, 1.f / 9, @@ -61,7 +59,7 @@ int main(int argc, char **argv) { //3.2 Load & build the device code cl::Program::Sources sources; - AddSources(sources, "my_kernels_2.cl"); + AddSources(sources, "kernels/my_kernels_2.cl"); cl::Program program(context, sources); @@ -79,27 +77,32 @@ int main(int argc, char **argv) { //Part 4 - device operations //device - buffers - cl::Buffer dev_image_before(context, CL_MEM_READ_ONLY, image_before.size()); - cl::Buffer dev_image_after(context, CL_MEM_READ_WRITE, image_after.size()); + cl::Buffer dev_image_input(context, CL_MEM_READ_ONLY, image_input.size()); + cl::Buffer dev_image_output(context, CL_MEM_READ_WRITE, image_input.size()); //should be the same as input image //cl::Buffer dev_convolution_mask(context, CL_MEM_READ_ONLY, convolution_mask.size()*sizeof(float)); //4.1 Copy images to device memory - queue.enqueueWriteBuffer(dev_image_before, CL_TRUE, 0, image_before.size(), &image_before[0]); + queue.enqueueWriteBuffer(dev_image_input, CL_TRUE, 0, image_input.size(), &image_input.data()[0]); //queue.enqueueWriteBuffer(dev_convolution_mask, CL_TRUE, 0, convolution_mask.size()*sizeof(float), &convolution_mask[0]); //4.2 Setup and execute the kernel (i.e. device code) cl::Kernel kernel = cl::Kernel(program, "identity"); - kernel.setArg(0, dev_image_before); - kernel.setArg(1, dev_image_after); + kernel.setArg(0, dev_image_input); + kernel.setArg(1, dev_image_output); //kernel.setArg(2, dev_convolution_mask); - queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_before.width()*image_before.height()), cl::NullRange); + queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_input.width()*image_input.height()), cl::NullRange); + vector output_buffer(image_input.size()); //4.3 Copy the result from device to host - queue.enqueueReadBuffer(dev_image_after, CL_TRUE, 0, image_after.size(), &image_after[0]); + queue.enqueueReadBuffer(dev_image_output, CL_TRUE, 0, output_buffer.size(), &output_buffer.data()[0]); + + CImg output_image(output_buffer.data(), output_buffer.width(), output_buffer.height(), output_buffer.depth(), output_buffer.spectrum()); + CImgDisplay disp_output(output_image,"output"); - while (!main_disp.is_closed()) { - main_disp.wait(); + while (!disp_input.is_closed() && !disp_output.is_closed()) { + disp_input.wait(); + disp_output.wait(); } } diff --git a/Tutorial 2/src/kernels/my_kernels_2.cl b/Tutorial 2/src/kernels/my_kernels_2.cl index 02ba7e3..025ebd9 100644 --- a/Tutorial 2/src/kernels/my_kernels_2.cl +++ b/Tutorial 2/src/kernels/my_kernels_2.cl @@ -1,24 +1,48 @@ -//a simple OpenCL kernel which adds two vectors A and B together into a third vector C -kernel void add(global const int* A, global const int* B, global int* C) { +//a simple OpenCL kernel which copies all pixels from A to B +kernel void identity(global const uchar4* A, global uchar4* B) { int id = get_global_id(0); - C[id] = A[id] + B[id]; + B[id] = A[id]; } -//a simple smoothing kernel averaging values in a local window (radius 1) -kernel void avg_filter(global const int* A, global int* B) { - int id = get_global_id(0); - B[id] = (A[id - 1] + A[id] + A[id + 1])/3; +//simple 2D identity kernel +kernel void identity2D(global const uchar4* A, global uchar4* B) { + int x = get_global_id(0); + int y = get_global_id(1); + int width = get_global_size(0); //width in pixels + int id = x + y*width; + B[id] = A[id]; } -//a simple 2D kernel -kernel void add2D(global const int* A, global const int* B, global int* C) { +//2D averaging filter +kernel void avg_filter2D(global const uchar4* A, global uchar4* B) { int x = get_global_id(0); int y = get_global_id(1); - int width = get_global_size(0); - int height = get_global_size(1); + int width = get_global_size(0); //width in pixels int id = x + y*width; - printf("id = %d x = %d y = %d w = %d h = %d\n", id, x, y, width, height); + uint4 result = (uint4)(0);//zero all 4 components - C[id]= A[id]+ B[id]; + for (int i = (x-1); i <= (x+1); i++) + for (int j = (y-1); j <= (y+1); j++) + result += convert_uint4(A[i + j*width]); //convert pixel values to uint4 so the sum can be larger than 255 + + result /= (uint4)(9); //normalise all components (the result is a sum of 9 values) + + B[id] = convert_uchar4(result); //convert back to uchar4 } + +//2D 3x3 convolution kernel +kernel void convolution2D(global const uchar4* A, global uchar4* B, constant float* mask) { + int x = get_global_id(0); + int y = get_global_id(1); + int width = get_global_size(0); //width in pixels + int id = x + y*width; + + float4 result = (float4)(0.0f,0.0f,0.0f,0.0f);//zero all 4 components + + for (int i = (x-1); i <= (x+1); i++) + for (int j = (y-1); j <= (y+1); j++) + result += convert_float4(A[i + j*width])*(float4)(mask[i-(x-1) + (j-(y-1))*3]);//convert pixel and mask values to float4 + + B[id] = convert_uchar4(result); //convert back to uchar4 +} \ No newline at end of file From fd28ebd1179113b1a5d5d26e49c8858f8dc5a6cd Mon Sep 17 00:00:00 2001 From: gcielniak Date: Mon, 4 Feb 2019 12:22:22 +0000 Subject: [PATCH 04/69] Tutorial 2 working --- .gitignore | 3 +- Tutorial 2/CMakeLists.txt | 10 + Tutorial 2/src/Tutorial 2.cpp | 31 +- Tutorial 2/src/kernels/my_kernels_2.cl | 77 +- images/test.ppm | 2412 ++++++ images/test_large.ppm | 9845 ++++++++++++++++++++++++ 6 files changed, 12341 insertions(+), 37 deletions(-) create mode 100644 images/test.ppm create mode 100644 images/test_large.ppm diff --git a/.gitignore b/.gitignore index d163863..7f9a3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build/ \ No newline at end of file +build/ +.vs/ \ No newline at end of file diff --git a/Tutorial 2/CMakeLists.txt b/Tutorial 2/CMakeLists.txt index cf9d78c..6677c9a 100644 --- a/Tutorial 2/CMakeLists.txt +++ b/Tutorial 2/CMakeLists.txt @@ -42,10 +42,20 @@ add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" $/kernels/) +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/images" + $) + #additional command to copy kernels into a working directory allowing debugging directly from VS if (MSVC) add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" kernels) + +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/images" + .) endif(MSVC) \ No newline at end of file diff --git a/Tutorial 2/src/Tutorial 2.cpp b/Tutorial 2/src/Tutorial 2.cpp index d27532a..b5f2044 100644 --- a/Tutorial 2/src/Tutorial 2.cpp +++ b/Tutorial 2/src/Tutorial 2.cpp @@ -21,6 +21,7 @@ void print_help() { std::cerr << " -p : select platform " << std::endl; std::cerr << " -d : select device" << std::endl; std::cerr << " -l : list all platforms and devices" << std::endl; + std::cerr << " -f : input image file (default: test.ppm)" << std::endl; std::cerr << " -h : print this message" << std::endl; } @@ -28,17 +29,21 @@ int main(int argc, char **argv) { //Part 1 - handle command line options such as device selection, verbosity, etc. int platform_id = 0; int device_id = 0; + string image_filename = "test.ppm"; for (int i = 1; i < argc; i++) { if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } - else if (strcmp(argv[i], "-h") == 0) { print_help(); } + else if ((strcmp(argv[i], "-f") == 0) && (i < (argc - 1))) { image_filename = argv[++i]; } + else if (strcmp(argv[i], "-h") == 0) { print_help(); return 0; } } + cimg::exception_mode(0); + //detect any potential exceptions try { - CImg image_input("test.ppm"); + CImg image_input(image_filename.c_str()); CImgDisplay disp_input(image_input,"input"); //a 3x3 convolution mask implementing an averaging filter @@ -79,36 +84,40 @@ int main(int argc, char **argv) { //device - buffers cl::Buffer dev_image_input(context, CL_MEM_READ_ONLY, image_input.size()); cl::Buffer dev_image_output(context, CL_MEM_READ_WRITE, image_input.size()); //should be the same as input image - //cl::Buffer dev_convolution_mask(context, CL_MEM_READ_ONLY, convolution_mask.size()*sizeof(float)); +// cl::Buffer dev_convolution_mask(context, CL_MEM_READ_ONLY, convolution_mask.size()*sizeof(float)); //4.1 Copy images to device memory queue.enqueueWriteBuffer(dev_image_input, CL_TRUE, 0, image_input.size(), &image_input.data()[0]); - //queue.enqueueWriteBuffer(dev_convolution_mask, CL_TRUE, 0, convolution_mask.size()*sizeof(float), &convolution_mask[0]); +// queue.enqueueWriteBuffer(dev_convolution_mask, CL_TRUE, 0, convolution_mask.size()*sizeof(float), &convolution_mask[0]); //4.2 Setup and execute the kernel (i.e. device code) - cl::Kernel kernel = cl::Kernel(program, "identity"); + cl::Kernel kernel = cl::Kernel(program, "identityND"); kernel.setArg(0, dev_image_input); kernel.setArg(1, dev_image_output); - //kernel.setArg(2, dev_convolution_mask); +// kernel.setArg(2, dev_convolution_mask); - queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_input.width()*image_input.height()), cl::NullRange); + queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_input.width(), image_input.height(), image_input.spectrum()), cl::NullRange); vector output_buffer(image_input.size()); //4.3 Copy the result from device to host queue.enqueueReadBuffer(dev_image_output, CL_TRUE, 0, output_buffer.size(), &output_buffer.data()[0]); - CImg output_image(output_buffer.data(), output_buffer.width(), output_buffer.height(), output_buffer.depth(), output_buffer.spectrum()); + CImg output_image(output_buffer.data(), image_input.width(), image_input.height(), image_input.depth(), image_input.spectrum()); CImgDisplay disp_output(output_image,"output"); - while (!disp_input.is_closed() && !disp_output.is_closed()) { - disp_input.wait(); - disp_output.wait(); + while (!disp_input.is_closed() && !disp_output.is_closed() + && !disp_input.is_keyESC() && !disp_output.is_keyESC()) { + disp_input.wait(1); + disp_output.wait(1); } } catch (const cl::Error& err) { std::cerr << "ERROR: " << err.what() << ", " << getErrorString(err.err()) << std::endl; } + catch (CImgException& err) { + std::cerr << "ERROR: " << err.what() << std::endl; + } return 0; } diff --git a/Tutorial 2/src/kernels/my_kernels_2.cl b/Tutorial 2/src/kernels/my_kernels_2.cl index 025ebd9..c29f64b 100644 --- a/Tutorial 2/src/kernels/my_kernels_2.cl +++ b/Tutorial 2/src/kernels/my_kernels_2.cl @@ -1,48 +1,75 @@ //a simple OpenCL kernel which copies all pixels from A to B -kernel void identity(global const uchar4* A, global uchar4* B) { +kernel void identity(global const uchar* A, global uchar* B) { int id = get_global_id(0); B[id] = A[id]; } -//simple 2D identity kernel -kernel void identity2D(global const uchar4* A, global uchar4* B) { - int x = get_global_id(0); - int y = get_global_id(1); - int width = get_global_size(0); //width in pixels - int id = x + y*width; +kernel void filter_r(global const uchar* A, global uchar* B) { + int id = get_global_id(0); + int image_size = get_global_size(0)/3; //each image consists of 3 colour channels + int colour_channel = id / image_size; // 0 - red, 1 - green, 2 - blue + + B[id] = A[id]; +} + +//simple ND identity kernel +kernel void identityND(global const uchar* A, global uchar* B) { + int width = get_global_size(0); //image width in pixels + int height = get_global_size(1); //image height in pixels + int image_size = width*height; //image size in pixels + int channels = get_global_size(2); //number of colour channels: 3 for RGB + + int x = get_global_id(0); //current x coord. + int y = get_global_id(1); //current y coord. + int c = get_global_id(2); //current colour channel + + int id = x + y*width + c*image_size; //global id in 1D space + B[id] = A[id]; } //2D averaging filter -kernel void avg_filter2D(global const uchar4* A, global uchar4* B) { - int x = get_global_id(0); - int y = get_global_id(1); - int width = get_global_size(0); //width in pixels - int id = x + y*width; +kernel void avg_filter2D(global const uchar* A, global uchar* B) { + int width = get_global_size(0); //image width in pixels + int height = get_global_size(1); //image height in pixels + int image_size = width*height; //image size in pixels + int channels = get_global_size(2); //number of colour channels: 3 for RGB + + int x = get_global_id(0); //current x coord. + int y = get_global_id(1); //current y coord. + int c = get_global_id(2); //current colour channel + + int id = x + y*width + c*image_size; //global id in 1D space - uint4 result = (uint4)(0);//zero all 4 components + ushort result = 0; for (int i = (x-1); i <= (x+1); i++) for (int j = (y-1); j <= (y+1); j++) - result += convert_uint4(A[i + j*width]); //convert pixel values to uint4 so the sum can be larger than 255 + result += A[i + j*width + c*image_size]; - result /= (uint4)(9); //normalise all components (the result is a sum of 9 values) + result /= 9; - B[id] = convert_uchar4(result); //convert back to uchar4 + B[id] = (uchar)result; } //2D 3x3 convolution kernel -kernel void convolution2D(global const uchar4* A, global uchar4* B, constant float* mask) { - int x = get_global_id(0); - int y = get_global_id(1); - int width = get_global_size(0); //width in pixels - int id = x + y*width; +kernel void convolution2D(global const uchar* A, global uchar* B, constant float* mask) { + int width = get_global_size(0); //image width in pixels + int height = get_global_size(1); //image height in pixels + int image_size = width*height; //image size in pixels + int channels = get_global_size(2); //number of colour channels: 3 for RGB - float4 result = (float4)(0.0f,0.0f,0.0f,0.0f);//zero all 4 components + int x = get_global_id(0); //current x coord. + int y = get_global_id(1); //current y coord. + int c = get_global_id(2); //current colour channel + + int id = x + y*width + c*image_size; //global id in 1D space + + ushort result = 0; for (int i = (x-1); i <= (x+1); i++) - for (int j = (y-1); j <= (y+1); j++) - result += convert_float4(A[i + j*width])*(float4)(mask[i-(x-1) + (j-(y-1))*3]);//convert pixel and mask values to float4 + for (int j = (y-1); j <= (y+1); j++) + result += A[i + j*width + c*image_size]*mask[i-(x-1) + j-(y-1)]; - B[id] = convert_uchar4(result); //convert back to uchar4 + B[id] = (uchar)result; } \ No newline at end of file diff --git a/images/test.ppm b/images/test.ppm new file mode 100644 index 0000000..7aaf966 --- /dev/null +++ b/images/test.ppm @@ -0,0 +1,2412 @@ +P6 +# Created by IrfanView +640 360 +255 +,t0>v1=w.=t/@v/Bx/@v->t->t,t.Bv-Av/Cx0Dy/Cx0Cx0Aw.Cw-@x-@x,?y+Aw-@y.Ax0C{/Az/Cz/Cx1Dz3Cy3Dz1Ey0D|2E}2E}1E}1D|5F~3G|5Fy6Fz0Dv2Dy1Cx1Cx/Dy0Dy0Dy-@x,@w,@x,Ax.Cz.Bw.Cx/D|0D{2E~4G~4I}4I~6K7K7K:M9N:O>R=Q>Q@SCTGXHZJ^J_OdPfShRgPeRgTgSgPdLaK`K`J_J`J_K`J_I^I^J\GYFZDZBYAX@W@XAX>U?T?T?T>T>S>S?TAVAVBWBWBWDYF[DYDYEZEZFYH[G\G\F[EZEZF[F[G\H\I]FZG[G[G[H\I]J]I]I]I]J^L_KbJcHbJaLaM`KbKaJ`KaJaMcMaNbNdNdOeOdOeQfQfRgSeTgSfSiTiTiShQfQePdPdOcL`M`KaKbMaMaLbNdNdLbKcNdMcOePfNdOeNdOcNbNbKbLcKaJ`H_E`F^H`H`IaJbKcLdPfOeOePfNgOgOgOfRhQgPfOeQgPfNdOeNdLbMcLbLaMaLbMcMcMbJcMbLbLbMbLaNbNaMcKdNfNcNdOeNdNdOeOdOdPfOePfPfRhRhRhRhQgSiOhMgOgShRgRgQgMeNfOgNfNfNfRhQgReSfRfRfReRgQhRhRhRhQgQgSiRhQgRgReRfQgRhPiQhRgSgSiSiRhTjRiRjTjShThSgShThRgRgSgShTjSfThTiRkRjTiUjQhUiShShRgUjRkRlVjSjRkQiTjRiTkTkWlWjVkTkUlTlUjVhTjVkUjVkRjTlTkVlUjUkVkVjUkSlUlVnTmVnVnTpVoToVnVmUnUnVoUkTkVmVmWnUlVlVmUnYlYnWo[rZs[t[t\u\t[s[s[s[sZtZr_t`u_v`x_y_xdxd|c~fgd~c{fkiknlf~f}defgjf~_{`|_z`z_ya|`{^zb|czczdzczd|d{b{e}f~eikmlppttrt{}؂؆斩碲祴Ʝ奶ꬷ릶ꖭ墨侀왨锤ܓޔޗ*;o):p+;q-s/=u-=t.=v.>t->s.@u0@w/?v/?w/@w-=s.=s.=t-=s-t.?u.Cw-Av.Bw0Dx0Ey0Dx3Cy/Bx/Bz/B{0C}1D|0C}0C{3F~2Fz2F{3G|1Ez4D{1Ez/Cz/D{2D|3C{2C{4D|4E}5I}5I~5H}1Fz3F}3G}/Cx.Bx.Bv.Ax-Ay+@w+@w+@w+Aw+@u,Aw,Ax-By1Ey1Ez0Dy0Dy2G{4I~7J~6H6J7J7K8L:MQCVCXEZG\G]J_NbNbPeRgQeQdRePcMcLaLaLaK_I]G\H]H\FYEYCYAYBW@TAU?T>S=RS>S?T?T?TAVAVAVBWBWEZDYBWCXCXCXBWBWBWBWCXCXDYDYF[FZEZEYDXFZFZG[H\GZH[G[I]J^I]I_JaJ`J^K_J^K^I]K^J^J]J_L`LbMcMcOdOdQfQfRgSfSeQfQfShShQfNcNbMaMaJ^L`K_J_KaM`L`KbKaJ`HbJdJdMbMcMcOeMdMcOcOcOcNcNdOeMcMbLbMcKcKcKcJbJcMdOeOeOeQgMfNfNgNfOePfOeOeOeOeOeMcNdMcKaLbMdNbMdLbKbL`McM`LbLbL`L`MaNbLbNdLcNdOeMcMcNdLdMdMdMdOeOeOePfQgQgQgPfNhNgMgNfNfOgOgMeNfNfOgMeNfOgPfPfOfQgRhRhQgQgQgQgQgSiRhRhQgRhRhQgSfRfRhQgSiQgQeSgPfQgRhTjSiRiShRgSgSgThTiQfRgQhQiSgThThShUhTiShShQgQfRgRgTiRgSiQjUiTiTiQgPhRiSkRkTkTjWkWkVlTlRkRjTmSiUjUiXjViUkUkSmSkVlVjWkWkWjWjXjXkTlVoTkTmTlXlVmVmUlWkTkUmVmVmVmVjXjXlUmVmWnYpWqZs[sZrZrYrZrZq\t\t^s`t_v^v`x`y`wd{e|ikmonsuxuwslkorrurojjhijhed{czcyd{e|ed}c|d|hjlmlpnqoqpnsy{Ԃ׉۟홯뜯룲웬睬藨㔣ⓡ);p';p);q*:n-u/?x/@x.?x+?w,?w+?w.Ay.?w/@x2B{0Bz/@w.>u.?v.@u-@y,?w,?x.@y/Bz0C{/B{2Bz1Bz.Bz/B|1D~2E2E3F4G4H|5I}4H}4H}2F{3G}0E}.Cz/Bz.Ay4E}4E}4E}4F{4H}3H}5G3F5H3F|0Dy.Bw,Aw-By,Ax,Ax+@x-Ay-By+Aw+Aw,@w0@v/@w.Bx/By0B{2C|5E}7H~7H8H8I7H6H9L:O=RS@VAXDXGZK]K_M`ObObPcObMcMaMaMaL`J^J^H\H\EXEXEZBW@U>S?T>SSS?T@U@U@U@UAVAVDYBW@TAV@U?T?T>S@UAV@UCXDYBX@WDXCWEYAXBXCYAXDZD[DYEYE[F[F\F[GZFZI]I]I]H\J^J_J^KaJ`KaMcOdOePeQfPfRfThTiRiShShSgQdMaMaK_J^J^I]K_K_J_J^I]I^I_JaIcKbKbKbMcMcMbL`MaNbOcNbNcMdMcMcNdMbLdKbLdKbMbMcMcOePgOePfPfPeOeOeNdOeMcMcPfPfNdNdMcNdNeOdOdMcIaLbKaM`L`LaLbLcLbL`MaObMcNdMcMcNdMcOeKcMfKcNdOeMcOeOeOeOeOfOgNhMfMeMeOfNfNfKfMeLdMeNfOgNfPePfQgQgQgQgQgRhRhRhRhQgPfPfQgQgQgPfQgRiQgQgQgRgRfSgRhRhRhRhQgSgRfRfQeShShTgQgQgQgQhRhSfSgSjUgSfShQfSgTiShRhRgSgShRhShTiSiTgRgRgShTiSgTiUiVjVkTlSmSlSkRjTiThWkWlVlTlQkRiUjVjVjVjWjWiYkUjVmUlWkUnVmWjVmWnWjYlWkWkUlVnXkWjWkWlWpWoUmYpXqZs[sYpXpYrZrZqYr\t\r^t^w_w^v`xbzd{f}jmostwzz~тzvnquxzwsqoopnmnkg}g}g~hhjiinnrrutstqqtsuxy΁ԋ׫ⷼ연鞮꟯젱휫霬衱떤';p';p(s+v-?w,?v+>v+>v,?w/Ay/@x/@x0Bz2C{0Ay.?w.Cx,?w+@w,@w/Bz/Bz0C{/C{1Ay2D{.Az0E~1D~2E2E3G0E|2E~5H4H}5I2H~1F}1F}.Cz/D{/D{0Bz1D|1D{4Dz5D|4D|3C{2B{3G}2G{2F{/Dy.Dx,Bx-By,Ay.Ay,@x-?x+?v,?w-@x0Ay1Ay.Ay0B|1Ay1Ay2Cz4Cz4E~7H8I7I8H9K7L9N:O:P9O;QS?T=R>S>S=PR>S>S>S=R@U@UAVBW@V@V@VCVBV@V?U>V@V?U>Ur->v.?w/Ay/@x1@y2C{2C{2C{2Bz0Ay/Bz/Bz/Bz1Bz1Bz4E}3E}0C{1D}0D~2D3E1F}4F~7G2E~5I5I{3F|2G~0E|/E|.Cz/D|0E~.C}1D|2D|3D|1D|/Bz3D|6F|3Cy1D|0B{0Aw/Bw+@u.Bw.Aw0@x/@y+=y-?x0@x.Ay.Az-Ay0D{.Bz/Ay4D|3D|5F~6G7H8H8H6G6I5K9N9M8O9OS@SBUCVEWHZM^O`M`MbLaM`K^L]K\J[HYDWDXC[BYCX>T=T=Q=R=R;P;P;P;O;N:P:N8O;O9P:Q9O9O;Q8Q7Q:P:P:P:P;Q:PS@U@U?R@T>U?U@V=S=S>T=S=S=ST;T9S:RT>T>U?UAVCWDZG[H]H\I_I_KaJ`LaNcQfTiSiVkUjVkWlVkVkPeNcLbKaH^LaL_L`J`J_H]H\K_J`H^J`I_HaJ_L`MaMbNcMaK_L`NbNbObNcMcLcNeKcMdMcNdNdNdMdObLdMdLdLdLfNdMcMcNdNdMcNdPfPfRhPfOeNbMbMbLbIaJcObLbLcNdOeNdKbLaL`MdNdKaLbLbMcLbMbMcLdLdJeMfOfOeOeNdOdOgKeLfMcOeNcNcNdMdLdLdLdKcLdLeQfPfMfMeMePfPePeMfOgNgMeMeMfMfMeNfPfPfQgQgRfRfPgRdReOgQgRhPfQgPfQgRhSgQfSgRfQfQhRhQgRhRhQeSfPePdQfRgSgThSgSgRgTfSiRgSgRhQfQgQfQgSgSgThThVjUhRiTiWlVkThShTgVjUjTlTlTkUlUmRkSlPiTjSkVjXjVlTlUnVkWjVmWnSmUlWkXkVjVjVjVkSkUlUkVnVnVoXnXn[qZrYp\pXkWnYpXqXqXqZqYqZt]u]u^w`zcyczczh~ijmmkklow~}xvtqssstvxqrsstssqppqusw}||{|{{vvtuqs|΂ӆҌ֘أگ௴వܹ꘭ꜭ욬蚪痧(9o'7m(;p(;p&;p%:o&9n(:p';o';q';s&9t)S@U?TBWDXFYGZJ\J_K`K^I]K\IYGZEYBWCYAYAX@U?T>S=Rv->v.?w.?w->v/?w0?x.?v.?w0Ay2C{1Bz2C{1C{0C{1D|0C}2D1D~2E1G1F}3F~9I8H8I5J~6K5I3E}2E}1D|2F0E~3E|5E}5F~3D|3E}4E}4E}2C{0C{.Ay.Ay.Bw.Bx-Av,@u-Av,Bw+@x-By,By0Bz4B{5D}5F~3F~2E~5H7I6I7I8I7H6H5H6I5J5I6K7N:Q:RS>T>TAVCXDXAVCYG[J]I\GZFYFXDWBV@UBWCX@U?T?T=R=RS@U?TS>S?S@SAVBXDZG]G]H^McPeQgTfQdRhTiVkTiPeOdOcLaKaJaK`LaJ`KaJ_K`L`J^I\H]H^J_K`J`I_J_K`LaLaMbMbMbNdMcLbPdMbNcLcMcMbMbMcNdOeOdPePeOdOcNcOdLaLbNbNcLbMcNdNdNdMcLbNeNdMcMcNdOeNcLbLbMcMcMcLbNcLbLbLbNbLaLbLbKaM`L`M`MbNdMcMcMcNdMcLbMcLbMcLbLbMcMcMdKcHaKcJbJbIbMcLbNdLcOcMdNcOeOdMfMeNfNfLdMeLdMeNfMfNfOePfPeQfNdOfPgNfPfOeQgPfQgQiQgQfSgRfRfRgPfQgSiRhQgSiQfPfOcPePgQfShRfQdSgQiPiPhRhQhQhShSjTgSfSgQeSiSjPiQjVjUiTiShShSgThUiTiRjTkWkUiSjRlTlUkVkWlVmSjSjUjUjVkXlWlUkWlXmXlWkUlUlVmUmUlUlUmUnUnUnTmVnVoUmXoVmWmVnWoXpXpWnXpYr]s`uaucwayayb{e{e|d~f~g~i}g|h|jptutnnnsx{рӃԅ}xupuvuvy|}~ׄڊ܍ݐכܜ۞ݒӊυ|{x{|ςφӋӍՑ֕ږۘۘژۛݖܞܨ墳槴ﰼ뛫蜬#4j#5k#5k$5k&7m&6m$7l%9l'8o):p)9o):n'8o#7o#7o%:q':r':r+u->v.?w.?w.?w/?w-@x-Ay,@x/By0C|0C~/Az0B}0F1E~2E7G6G~5G|7G6H4F5F~5E}3E~4G2G6F~7H6G6G3F~5E}5F4C~3D|2Cz0Bz.By.Az/Cx.Bv-Av+@t+@v,Ax,Ax-Cz2D|4E}3G6I7L7K6I6J6I~8G8I3G}4F~3H~5J5J7L6N8O;P:OR>S?T>TAV@VAVBVEWEWEXEWFYCVBUAVAV@U?T>S?T>S>R>S;P;P7O7N8N9N:O7L7L8M7L9M6L5L6M7L8M6L6L7L9O:N9N9P;P9OSU>S>S@VAWE[F\H^J`McNdQhQfQfSiTiVkUjRgPeNcNcLbKaJ`L_J\K]LaK`LaK`I]J]H\I_I_J`K^L^L^LaK`MbLaLaNbMbKdMbNbNbMbJbLcLbMcMcMcNdPdNcOdNcNcPdNcMbLcOdMbLbKaMcLbMcMcOdMbMbNbLdMcNdLcMcNdNdMbKaMbJdJdKaJaNbLaKaKaMaLaLbKbKaLbNbNcMcLbMcLbNaNaKbLbMcNaMcMcJbKcJcJbIbJbMcNcMcNcMbLbNdNdNdKcMeMeKcLdJbMeMeLdNgMeOePfNfMeQeMeNfMeOfOfMeOgPfMfPgRgPfQfQgPgPgQgQgRhRhRhSiOhRfPfPiPiQjQgRiSiQhOgNfQhOjQjPiThQiRgRfSgUiSkQjThTiSiShShShThTiTgUjTjTjVkShTjTkUkSjViViTnTlVlWlVjWlVkWlXlWkWlWnVpTmVjWkUmWlVkTlUmVoWoVnWoVnVmVmWoVnVmVnXqWoYqXpXpZr[s^u`tbwcxeyg{e~e}ff~g|g|i~morrpqnprzӁՆՋل׀{|}}~z|րـ׃Ԏ؛ۭ䷼佾⺽޹ްؤқԍς΀΃χχЊэҎҒՔؗڗܗߚߚܙܕږ۟㫻믺&6i%4h&2i'3l%6l&7n#4j)9p&7m'8k(8l(8l(8p'6r&9q&:n(Q?T@UAVAVCXAV?TAWBWDVBVBWAV@U?T?R?RS>TCXDXFZF_LbNdOdOeNeRgQfQfQfTiUjShRgOdOdMbLcJaL`J`J_K`K`K`I^J_J^H]K`K`L`J_J`L_L^L_K`K`I`KaKaLbMcKdLbMbLaLaLbJ`KaKaKaMcMcNcNcOdNcLcNbNcNcNcMdLbLbKbLbLbKaLbNbMaMcLbNcNbMbNdNdMcJdLbLbLbLaMaL`K`KaKaLbMaMaIbLbM`NbLcMcMcLbLbLbLbLcLbLbLbLbMcKaLcJbJbLbMbLaMbNdJcLbMcKaLbMcOdLcMeKdKcLdLdMeLdMfPfQgOeQgRgMdMfMeNfNfNfNePfRgOgNfPhPiRgQgRfSfQeQeReReRhQgOeRhQfQhRiTjShRhQgRiQfOgOgPhQiOhPhRfTgRgRfTgRiSiQiQiOkRiUgRgThTiRgTiTiTiUjTiWlVkTiTiShQhSjXlTkWkXlXkWlYnWlXmWlWlWkXlXmXlWlYmTnTlXlWkVmUnWoWoWoUmWnVmWoUmXoXmVnUmWoWoXo[s\t]vaudxcyd|e~h|g{d}d|e|h}i~mnnnommqw{~σԅو֎ܐފ߆ۘ៭㊗؋ކ׎ڝݯܺ۽ںܲظٹ۰֢՗Փҋ҉ԍԑԗ֗יٙٙܘۚܙݛݙܗۗۜߩ壳$4g&5h'4i'4i&4j%3k$3j$4i%5k%:l%:m&:m%9l$8o%8q#7o&9r%8o(;s(:t)9t):r);s);s(:s';s(;t*=q,w,S@UBWAV@U?TAVAVBWAU@U?T>S;Qy+=x.>y.>y.>y/?z0@|0@z2D{3E|4E}0C{1C{4D|4E}2E}1F~1F0E~3H2G1F2D|3E}3F~2F}4E}1F}1F~5J3H1F~1F}1F}0E{/Dz.Cy.Cz0E|2G~2F~5G3F~5F~8I8I6I6I7J6I6I3I2G~0E}0E}3H2G~6L7L8M8M8N8M:O9O;P;P=R=R?T@U?T@UAV?T>S?T>S>S=RS=S>T@VAWCYEZEZF[G[J_K`L_MbMbPbObNcPeNcNdOcOdNcMcLbLaK_J_J_J_I^K`K`J_H]H]I^J_H]J_MbK`J_I_K`K`K`MbLaMbJ`McMbNcKaIbL`J`LbKaJaKcLcMeKbLbLbMcLbMbMbMbLcKbKcLbKaLcJaJbJ`I_KaKaKaMbLaMaLcKaJ`JaJ`KaKaLbLbJ`J`KaKaKaKaL`KbK_MbKaKaLbLbKaLaLaK`J_J`KaKaKaJ`LbJbIaIaIcIbIaIaKcKcMbKaMcPfOeNdNdKbLcNcNbNbNcLdLdPfPfPfOePfNfNfMeNfKcMeNfLePgNfNgOfOePfPfPfPfOfOfPfOePfOeOeQgLdOhPhOhRfRhPfQhPhOgPiQgRfShTjQhQiQjSjUkSiSiTjTjRhSiRhThThThShShShTiShShTiUjUjSgRiSmSlTmSlRlTkTkWmZnYnXlYnXmWlUjUjVkUjUjVkWlWlWlWlWkXmYnYmVkWnXnWoVnWoYpWoVnWoWoYqZr[s\t^u_vdxdyczdzeyeyazbzayd|c{j}l~lk~nmnnruwwy|~ӁԂҐ֟بߩޯ䡭ڨ௷ں׭֥Ԡա֢֦֫Դٻ߳ݨ١ٟޠ۟ۤޯ᷼%2g%2g&3h&3g)4j(5i$4h$6j!2i%5i#4h$5i#8j%9k%9k$:k$8m$8m'8n(7n'6l'7m'8n&9n&9n%:n(v,;w-=x-=y.>z.=y/?z2A|3B~2C{0Bz1C|3D|4E}0D{0B|0C}0D~0F~1F1F3E}6E~3C{1E}6D~3F}2E}3H1F0E~0E{1F|2G1F0E~1F1F|1G}2F5E3F6F8I7H5I5H6I7I7J3H1F}1G~0E}0D|2E}5H4I5K8K8J8M7N7O8N:OS?T=Q?S=T=S>R?T=RR>S?T>T?UBWDZEZF[EZFZF\H]I^J_LaMbNbNaOdOdNdMbOcMbIcKaNbLaLaMaL`J^K`J_K`I^H]J_J_H]I^L`J_L_L_LaMbK`MaL_K`J`JaMaOdMaK`LaJ`J`JaJbLcKbKbMcLcLcK`LcMbMbK`LdLcKcLaK^MaKaKaKaJbKbLbIaK`LaMaKaLaKbKcK`J`JaI`LbKaI_KaMbKbKaLaLbLaMbLbJ`I_JaKaKaJ`I`KaKaJ`KaKaKaKaH`G_HaHaIbIaIaIaKcKaLbMcPfRhNdNdMcMcNdOeNdMcJcLdMdRhSjTiOgSkRjPhNfLdLdMeOgNfNfLeNeQfPfPfOgOgRgQgQgQgPfPfRhPeMfOgOgReRgRhPfSiPhOgOhSiQfQiPhQiShQiSkTjTjSiSiTjTiSjTjSgUfSgShSgShTiRgShSgShTiUjUjSlSnTmSmSlSkTjUnXlXmYkXmXmWlVkWkUjUjUjWkXmXnWkWlVkZlZmXmXlVmVmVnWoVmXmWpWoYqYqXpZr[s\t_s`taudwbwawdwfwdzcyayayeyg|g|hkonkooqrpsuvzz|ч֏ڗܡڬٮޫݱݷعڶٸٺڹܾ޼ݮۧڥܦݨڳ۸זּ엱%2g$1f$1f%2g&3h#2g"2f$4h$4g!2g#3h#4h$5h%5i%5i&6k%6l&5l%5k%6k'6m(8n&8m%9n)8q(9r'9r&:r(8n(:p)9q(8q(8t):r*;s+u,>u->u/@w-?v0Ay1Bz1Bz0Ay0Ay1Bz0B|/C|0C}0D~0F0E~0C{/C{/C{0Bz3D|0C|2E}3F}/D}/D}.C|1F2G1F1F3H2G3G3E3F5G6H7G8I7G3F~3I6J6H2G~2G~2D}0F|/D{0C{2E|3F4G5H6I4J6J8M7L8M8N9N9NS>S>R=R;R;Qz/@w->v/@x/@x/@x/@x/@x->z0B|/C}0C}0B|3C{1By0Ay1Bz/B{1D{.Ay0C{.D}.D}/D}0E~/D~1F0F1F0E~2E~2E2F2F4G2E}3F~2F~2E}3E~6H5F~2F~2G~2G~1F}/D{1E|2D|4E}5G3G4H4I4I5J5J8J7J8L7K9N=R=R=Q=S;Q=S;Q:Q9O8N9N6N7M7M7M7M7M6K8M6K6K8M7K6K6K5K4J3H1F0D0D/C~0D0D0B}/C|/B|/C}0E}/D}/D}1G1F1F4I3H3I5J5J6K5I4K6L5K6L8M8M9N;O;N=P=P=Q=S=R@UAVAV@UAWAWBWEZEZG[E[H]H^K_K_M_M`MaMbLbKaKaKbKaLbLbMeNdLcLaJ_LaMbLaPgQiRhMcKaLaMaL`K`K`K`LaI^J`I_J`K`KaLaK`MbMbLaKbKbKbKbKaMbMbMbKaKbKbKbKbKbKbLbK`J_KaJ`J_K`K`K`K`J_K`J_I^I^J_K`I^K_K`K_L`K`LaK`KaMbLaL`JaKaMcLbLbKaKaKbJaKaJ`I_J`J`I_J`J`KaKaJ`I_I_I_J_K`F_IaLbJ`KaKaLbMcLbLbLbNdMcLbLbMcMcMcNdOeOeQfTiQfLdMePfPeLdMfLdNePePfQgSiTjOeMdMePfOeOeNePfOeOePeNfNfOgNfNfQgQgQfQfPhOgRhNgNfOgQiQiPiShSiSjTiThTjTiSiShTiThSgThRgTiQfShSfThUiUhTgTiTiUiRkUlTjWkVkUnTmTmUmXkXmUnWlVjWkWmWnWmWlXmYnVlXnXnXmWlYnXmWlYnVmWn[oXpYqXpXpYqZr\t\u`t_s_t`x`waucwauav`xawfzg}j~h~klnnoommmopoprtx|̀шӕ՟סמ֢פצٯݾۻڻܽ޿笵%2f&3g%2g%2g$1f%2g$4h#3g#4g"3g 1f#5g#5h%5i#3g&6j#4i&4k(5l(5l&4k%4k%4l$5k&8n%9n$8m%9n'9n(9o'8n'8n'8p(9q*;s+v->v->v->v->v-=w-P=Q>S?U@V@U?T?U?U>U>T@WAWCXF[G[J\I\N_M_L_M`KaMbNcMbMbLbNdLbMcLcNcLaLbLbPePgRhRhOdLcMbMbMbNcLaLaJ_K_K_J`KaLaKaKaMbMbLaL`KcLcLcJbK`MbLaLaJ^K`KaKbKcKcLbKaJ_K`J`KaJ_K`K`K`K`K`J`J_I^K_H]J_K`J_K^L^L^M_LbKcLcKaMbLaJ_KaLbLbKaI_LbKaJ`J`J`J`KaJ`I_G]I_J`KaI_J`J`I_J`J`J_I_J`J`LbJ`LbKbLbKaLbMcLbLbJbKcMcMcMcMcLbPfOfPfNeOdPdNbKcMeOePfOfQgTjTjSiPfOeMdOeOeQgOeNdPfOePfMgKcNfLdNePfPfQgQgQhOgOgOgOgOgOgQiPiShTjSiSiPhPiSjTiUiSiTgTgTgUiRgShShUhSiThUhUiShTiTiRjVjVkVkWkVkWmXnXnUmXlTlWkXkWlWmXnXnWmWlXmZnYmYmXkWlVkXmXmVkWmWmXlWoYqXpYqYqZr[s]u]t^s`t]uaw`vaubw`waxcxfzh|h|j~h~mmnmlhkmnoopqsu{ʀφёԖ֝Ԭ׶ݰسڲݯ觵%2f&3g'4i&2g%3g&3h%5i#3g%4h#3h$5h%5h#6f$4g%5h%5h%5i%4h'3j&3j'3j'5l$3j$3j"8m%9n%:o%8n(8n(9o&6m&6l'8m'8o*;s):q(9q(8t'7s(8s(8t$9r&:q*:q+;t*:s+;s+u.?x*=w,?y+>x+>x*>w*=x-=x-=x-=y.?z-?x0@x.Bz,?w-By.Cz/D{/B|0B}1D.B}/C~0D0D1C}0D~0E~0E~0C}0D}1E0D/E~1E2D1F/D~.D}0F|/E|/E|0E|0E}3G2G4J6I7I4J5J4G~7I6L5J7L6L6K6K:O:O9M7L9N8N8N6K7K5I7L7L3H4G4I5J5I5G6J5J5H4G4G5H2F0D/C~.B}.B.B0B}/A|.B}-C}/C~-A|,@{-A|-C~/C/C0D0F~0E~2G3H5J4I6K5J5J6L6M;P:P:P=PR=R=SS>T>Tv'v*=t+?v+@w*>v,?y-B{.C|.A{.A|-A|.C~/B~/C~0E~1F/D|/B|/B|0C}0C|1D}1D~/D}0D}0E~/D}.C}0E{/Dz/D{2G1F2G~5J4I4I3F4H4I4J4I6J6H5H3H5J7L7L6K6K7L7L6L5M5L2H4G6J4I5K3H5J5G6I4I5I4I3I4G4H1E0D0D/C~/C~/B~.B}-B}.B|/B}-B|-A|-A|,A{/B}/C}.B|-D}0C/C~0E~2G3H4I5J5K5J6K6K8M9M:O:OS>S>T?UBWDYEZG\H\H]J\L]I_I\M^N]J]L_M`K^M`KaMbKbKaLbLbLaMcMbMbOdPfRgNgKdMcMcLdL`MbKbLaK^O_N`K^K`LdKbKaJaMbLaMaKbMaLaL`LbK`J_KaK_H]J_I^J_JaIaK_K\H]I_I_H]K`K`I^J`J`I`J`KaI^H]H]I^K`L`K`K`J_L`K`J_I_K^J_K`J_KbMbK_LaK`J`KbJaI_I_G`I_H^I_I_I_KaI_IaJ`J`J`I_I_I_I_J`JaJaK_J^JaJaJaKaHbHaJbHaHaLaMcMaNdLbMcMcNbNbMbKdJdKdKeLeOfNdPdOcMdMcPfOeOdMbPfOePfNdOeMdMeOeQeOcPfPfPfPfQgQeOeOfOgOgNiPjNhQiPgQhRjOhRiSiTjRhSiRhTjTjSiSiShTiSiSjTjTjUhUhUhUiShShUhThVlXnYmWmVlUnTnTnVlWkWmWmWmWnVlWmXnYoXmYlXlXlXmXmXmXmXmYnVoXqWo[q]s]qZs^q]q]r[t\u^s`s^u`uav_u]uawdyeze{f|i}h|i|j|l}k}k|k~klopolosvy~̂χАԓӔСѪѩӨҳս㻿$1e$3g$4f#3f$5i%4h&3g&2f&4g%3g%3g&2f%3g&2g%2f&4h&2g%2f$1f#1g"1i#2i%4j&6k%6i$5i#4k%6l$5k#4j&7m%7l&7m&7m%6n#4l#4l$5m$5m%6n'8q'8o(9o):p'9n%:o&:o(9o(9q):p):q):r)8t):u(;v(;v(w,Az*>x*?x,Az.C|.C|.@z.A{0B}/B}.A{/B|/B|0C}.C|/B}/@|/B|.D|/D}.C}/D{0E|0E{0E~0E~1F2G2G3G2F2F3G4I4I4I5H5I3H4I4I4I5J5J4I6K5J5K5K4I4I6K5J3H5J4I5H5I6I5H5H4H2G2E2F0D1G1G/D~0C/C~/B}0C/B~.B}1D/B}.B}.B}.B}.B}/D~/C~/E/E~3G3H3H4J6K6K6K7L7L7L7L9N;NS@UCXDYEZF[I\K]M^K\K]L^M]K]J]K^M`K^M`J_I]KbJbMdKcNeLaLbLbNdNdPgPfNdLbMcOdMbOcK`KaK^M`K_LaLbNbLaK`K`LaL`KaJaMaJ_J_K`J^K_K`J_I^I^J_K`I`H_J_J]J_H^I^K_J_K`I^J_K`H]J_J^J^J_H]I^J_J`J`K`J_J_J_K`K`J]K^I^K`K`LaK`J_J_K`MbLaK`I_J_G^J`H^J`J`J`H_I_I_I_J`H^H^I_J`I_KaJ`K_K^J]K^K_J_KbKaJaIaG`KaMcLbLbMcMcMcKbMcMdObLdLcLcMeNdOcNbNbNdMcOeNdNdNdMcMcOdOeOeOeKeMeKdOeMdPfPfQgNfOfNfLdOgMeNhNhPhPiNhPiShRhSiTgRiRhSiTjSiTjSiSiSiTjSiRhSiSiSiUhSiTkUjUiRkSjUkXlXlVlVlUlWmTlTmWlVlWmXkWkWkVlXnWoXnXmXlYnXnYmYnXmZoYnWoWoZo[q]s\r^r^s_s^s^s_t^s`t^s`tbv`t]vbwcxezdyf{h|g|i}i}l~j~j|j~jkpoqoprtw|͂υΉώБԛԣΟͩҹپֺսؽݼ亾%3d#3d$3f!3e"4g%5h'6i)5i'4h'4h&3g&3g%2f'2f&2g%2g&3h&3h%2g#0f"1h"2g&7j&7j#4h$5i#4j#4j$4k"4j&7n%6m&7k&6m%4l#3k!2j"2k$5m%6n$5k$5k$5k&7m'8n&7m&7m%6l%6k%6l'8n'7m(9q):r(8p(9q'9s':s(9q*:r'9p);r):r(:r)w(>w)>w)>w(=v*>x*>x*=w+>y+>y-@{-@z.A{/B|.B{/D}/C~0B}/@{.A|.B{/D}0E}/D|/D|.C|0E~2G2G/E/E.D/D0D2F2G3I3H4I4I3H2G}3H2G~2G~3H3H~3I5H6H3H4I6K5J3H1G3H6H9J5H8H4G2G2F2F2F1E0F/F0G/E~/C~/C/B1B1C/B~.A|0B}-A|.B}0D.B}.B}.D~/E~2F4I4H7I6I6L5J6K7K7K6L8M:O;M=Qw'x+>x+>x+>x-Az.@y.A{,Az,@z,?z-@z,Az-A|.D|.C|.C|/D}0E~/D},C},B~.D-D/B}1E1E/C~2G2G2G1F0E0E~0E{0E|/D}0F0F2E~4F~1F|1F3H2G1F/F2F}4G~8H5H5H4H2H/C1E0F0G0F.E~/C0D.D/E/C.B/B0B/A|/C~/B|.B}/C~.B}-A|/C~0E~/F}2H~4G~6J7K7L5J5J6J8J7L6K8M9K:Nw+@y,?x,?y->y*=w+>y)?x*>w.@z->x,?y+@y+@y-A|-C{.B|.A|.B}.B|.B}.B}/C~.B}.B/C0D.E}.C|0E~0E|0D~/D~0E~0E~1F1F0E~0E0E~1F2G0E~1F2E3F3D}3G}6H}5H2F1D1E0C3F0D0D0D/C~1D1E/C.C.B/B}0C}0B~/D/C/C~.B}/B}1E2F3F1F}4G6I7J5J4I3H6K5J7L6K6K:N;O:O;P;Pw)>x*>z,@{+>z+?{,?{,?z+@z-A|.B}/D}-A|-A|.B}/D}/D}.D}/D}/D}0E~/D}/D}0E}0E~/D~0E~/D}0E~1F0D~/B|1D}0D{0C}2E1E0D0D0D.B}1D.B}/C~.B}1E1E/C/C.B0C-A~0D0D/C~/C~/D~1F2G~4G2G~4H5H7J5J5J5J5J5J6K7L7L9L;N:P;Pw'w)>w*>x)=x,Az,A|.B}-A|,Az-Bz-C{.B|/B|/B|0C}-C|.C{.Cz.D{/D}0E~/D}/D}.C|/D}/D}0C{0C}0D~1C}/D~0D/C~/C~/C~/C~2E1F1E1E/D0D0D0D0D0D/C/C~/D0E~2G1F}2G2G4I5H5H5K5J5K5K5J7L6K8M7M9N;P:O;Pu*w-Bz+@w-Bx-Bx/A{0@|.A{.A{,B{,Bz.Ax/Bz/B|0C}.D{.C|.C|/D}.D|/Bz1D{1C}2D~/C|0E/C~.B}/C~.B}/D2H1F0D~.B}/D/C}/C~.E/B~.B}.D|2G1G0E~2G1H~3G2G3I6H8K8J8J7J5K7L6K8L7L:O;PU?V@UCXBWCXEZEWFZDZFXFYH[G[F[G\G\G\G\H]H]H]H^F\F[F\G]H\I\J]I\J]L`L`LaK_J^K`IbKaLbK`LaLaK`LaLaMdMbMbOdQgOfQhNgOeM_L^L`L`L`K_J`LaK`LaK`K`J_I^K`J_J_J_J_J^I^K_J_J_J_L_M]J^L^N^K_K^M_J]K^L]M^K^L`I^I^J_J_I^I^I^I^H]G\G\G\H_I^I]J]J]J]I\I^I^J_H\J]I[J]L^K^J_J]N^K`LaK_J`J_K`MaK`J`K`J`J`KaK`LbKaJ`KaJ`KaJ`J`J`McLbKaLbLbLcMaNbMcMcMcLcNdMdKcKcLcLdLcOeMcNdOeMcMcOeOeLeNfKcKcMeMeNfOePfOeOeQfQiPiOhPgQgRfOhPiRhThSiPhRiUhThSiTjShQkTkTkTjViSiSiTjUkVlUkWnZnZnYmXkXlXlWkVlYlXlWkVjXmYmYmZnYmYmYmZoYo\p\p]q]t^t]r_s]q^s_v]u^t^tav]u]w]u_savavbwdydydzg{i}i}h}jjlllnnpppppstx}ǀ΅ˋˎɐΉ̀̅˦Թ㜥ٗܙল䥯瓣ߎ掣ꍡꉠ昩㵽旡ԌՎٌٍ׏ٕٓۛߟߤߩݮ୵ܮᱺ稹'5b'5d&5e&3d&1a(3d'5e'4e(4e(4e)4e*5i'4j'4i'4i'4i(5i(5i%2f$2f&2g%1e&2f&1e%2e!2f 1e!2f#3g#2g%1g$1f%2g&0g$1f"1f#1f#/e$0e!2f!2e 0d%0f$1e!1d$1d$0d$1e!1f"1g!1e"/d.e"/f#0e$1f#2e 0e /f /c /f .i%1i%2j"1i!2h 2h2g!2g!2h!2g#4i!4i%4k$3l$3m$3k%4m%4n&3l&5l%8o$7o'7q&5q&7p'8p'7r%8p$7o&:o&:o"8o#7n$7o$8o"7q$9r%:s%:s':t%;t%:s';u&:s'9t&8s';u&x)?w,Ay,>y-@z/?z.@{.A{+@y.@x.C|.D|-A{-B|0B}.A|/D}/D}/C|/B}/B/C~/D|/C}1C}0C}2E/C}/C~.B/C~0D0D0C~2E3E1D~/D}0E2G}2G~2G3H4H6J8J9K9L8J6K8M7K7M:O=Rx)x-?z0Az/Ay,?x-A{.B{-@x,Ax,Ax.Cz/D}0E~.B|-C{-B|.C|/D}0D~0C}1C}/C~/C~/C/C~/D~0D/C~0D2G1G/D~2G1G}2G}5H3G2H4J5J6K7M6L8M7L7M8N9O;PSSBUCVBWCXBWCXCXCYBXDYCXCXDYF[G\H]H]I^H]H]F[F[F[F[G\J\M]L]M_J]J]J^L`K^H^K`K`K`LaK`K`J_K`LaK`K`LaNcOdNeNdOeMeLdMbMbLaK`LaLaLaJ_J]I^J_LaK`L`L`K`J_I_J_J_J_K`I]K_K_L_L`K^O_M]M`J]K^K^J]H\I^J_J_J_I^J_H]I^I^J_H]H]H]H]I^I^J_J^K^G\H]J_I^H]J_H]J_I]I^J_J^I\I^I^J_J_J_J_J_K`KcKbK`K`K`LaLaK`KaJ`KaKaKaKaKaKaJ`J`KaLcMbMaMaL_L`NbNbM`MaNaLcMbKbMcNdMcMcNdNdMcNcOeOdOdLeMeNfNfMeMeQfOeOePfPgNhOePfRhQfPgRhQgRfSfRfRhTjSiTjTjSiTjTkRlRlVlTjTjUkUlVlUlYlXoVlWmXnXmXnXmXnXnXnYnZoXmZoZoZo[nZn[oZnZn[o[o^r^t^t^t^u]s^r^t^tat^t_s]u`u`vbvbvdwexfzh{e|h|j|k{j~j~mooooomprqsvw{~ʄ˓ɦ̥Ξϒ˒̤զӠ؏يߋ抝䊝䄜䂙扝㐣擧㒤יۉ҇ԇԉՉԊ׋ؑ۔ݔܘ۠ߤ৴޶꯹蝵$2c'5f&4c%3d#2c%2d%1e$2f 1d#0d"0b$2a%3c%3b%3b'5g%2f'4h$2f$2f"2e#2e%2e%0e#0e$3g!2f 1d/d0b 2d"1d%1e"0e"0e$0e 0d 1e!2e#/d%0d#0e /c/d.c.e-c-b 0d!1d /a#0d/b!.d .b0d-e-d /f#0e"/d#0g.e /f 2g 1g!2h 1g 1g0f"3h!2f /g!.e#0g$1h /f .e$1h"/e#1h"0h!1j!2j!2j#4k"3l#4l#4l!3k!5k#7l"6j%6m&6o%5m$6n!4l"6n#7p#6o"7p#8q#8q#8q$9r"8q"7p&8o$6n$9p%9p'9q':r(8p):q(:r'9q);t(;t&w-@{-@y.@x,@x-By.Ay.B|/C}-B{-B{.C|-C{/E~/C{1C}1D~.C|-B|.B}1E/C0E/D}/E}0E1F0E~2G3G3F6I6K4J3K5J7L7L7L7M8M9O9P9QS>S?T>SV@U?T@U@T@SBTCUCWCYCXDYBXCZAXDYDYG\H]I^I^I^I[I]G]F[F[G\F[G]J^K]N^I\H[K^K_J^J_J_J^K`J_K`K`K`J_I^J_K`J_K`LaLaMbMbNcNeLcLcNcLaLaK`KaK`J`K`J_J_K_I^J_K_L`I_J_I^L_I^I^K`L_J_K^K_K_N^M^M_L_K^K^K]I^I^I^J_H]H]H]I^H]I^J_I^H]I^J_I^H]I_J]I_I^H]I^I^H]H]I^H]K]J_J^K^I\H^J_I^J_K`J_J_J_KaLaLaJ_J_K`MaKaKaJ`J`LaKaKaKaLbMcKaLbKaMcMaNbM`MaNcK_LbL`NbLbLaMcNdLbMcLbLbMaNbPcNcMdMcMeLdNfMeNfNfPfOePfQgPeNfNfQgPfQgShQgOfSfSfThRhSiTjTjTjSiSiTkRlRlTjTjVkUkWkUkWmWmWmWmWmXnYoXnWnXnXnXnYpYn[pYn[pZo[oZnYmZo\q[p\p\p^s\r]t^t`t^r`s^tat^t_s]v_u`vcvdxdxeyfzg{g|i|k}l}klpooopqpsstwz|ȃʄ͙̉ˮαϢȝʫԲ֥֠ߠ㎡ᄙၙㄛ⑤㆕ъԈևӃЂςЄ҄҅шՋٍّݓߔݘܢߤꟳ!/_#1b$2c"0b#1d$1e"/c!.b#/d".c"/a$2^#1`#1`$2a$2c#1e 0c!1c0c2d /c%2g#1f"0e$0f#1f!1e"2g"1e#1b#1b!1d1c0d"0e!1e 0d!1e$/f#1d"0d#0e#1e!1c"0c!1c/b 0d 0d!1e#0c"0c".b!.b /b-d,d#1e#0d!.b!/d.f"0h0f 1g/e0f 1g0f.d/c /g"/g#0e!.c!/f".f"/d"0d!/f#/f!0i0h!2g 2g 1g"3k!2j!1j!5k!5j!4j#4j"3l#4l"3k"3k$4o!3n"5o!6o"7p#8q#8q#7p"6p"7o#7n#7n#8o#7m$8q$8n&9m(8n(8p(9q):r'9p+;o*:q+:t+:t+;r(:r,?x+>x0@x,Ax-Ax0Bz1Bx.Bz.B|0B}/B|/D}0B|1C{1D|2E|/D{-C{/D1E1E0E~0E~1F/D}0E~2G2F5H9L8N6L7L6J4J7L7K9N7O;P9Q9Q:QS@UCXEYG[EZDYCXCZCYGZH]I^J_H]H]G]I\L]H[H\H]F[H]H]I^I]J]J]H\G\J\K`L^M]L]K_J_K_J_J_I^I^I^H]H]KaI`K`K`MbNcNdOcNcMcMbLaJ`N`L_K^K_J_I_J_K_J_JaJ`L`J_J_K^J^N_J_I_K`I^K_L`K`K_K^L]N_O_J^K^J_H`KaI^I^J_H]H]I^I^I^H]I]I^I^I^I^J]K^J\I^I^J_I^J_I_G]I]J^I]K]J\I_J_J_J_K`J_J_K`I^LaJ_LaJ_J_J`KaJbJ`LbKaKaMbKaI_LbKaLbKaLbLcLbNaOaM`MbMbMdL`LbLbJcMcMcNdMcNdLbNbOcMcNdNcNcKeLdLdNfLeMeNdNdPfQgPfQfMgPgQfQgQgQhPfRhQgSiTjSiSiRhTjUiUjTiTjTjPkRlTlVlWlUkUkWmXnYoXnWnYmXmWnWmXnYoXnZnYnYn\p\nYm\oZn\oZo\q\p[o]p^r_s]s_s_s`s_uat_v`u]v\u`wavdxfygyi{j}i|k}k~kopoprpporvy{ƄɈȗ̘˗̠ɵѿׯϳտ۵ۚؒޕ܁y|Ӂ{ӁՉ҃̓σ|~|̀΂ЄӃцӊ׎ّܒݔݚޙݭܵޫ٣ٟ߶윮!/\!/_"/c!-a"/c#0d 0c/a/a/b /`#1`#1`"/^!1a 0c0c!1d 0d/d/c#0e$1f#0e /d!1e/b!0c!1d#0d#0d!1d 0d/c/c#1e#/c#1e"0d"/c%/e"-c$/f$/e$1f#/e#/d"/c!/c!1e#0e#0d$0d$0d!.b!/c .b/c ,b!-c -a#/c"/e0c!0d 0f/e/d/b/d.e.f.e#/d#0e"0e!0c!2e"0f$0h!.d 1d#0f#0g"/f"0g!0g /g"0i!2i3h"6l 2g2g"2h$4j 2j 1i#4l 4l 3n 3m!5o"7o"5o6o"8q#6p$6n"5m$6n$6o$9m%8n%8q&9q$7r%8o%8n'9p%9q(9q*;p):p&9r&9t(;t(v-@x.Bz.Ay.C|.D}0B}-B{0E|/D}0Cz/E}0D~0B|1D~1F2G1F}3I2G1F2G5J;Q?RSAV?T?T@UDYF[CZBW?T@U?T>SBVEYG[I]E[DZDZC[D\G\I^H]H]I^J\J^J^J]H[GZHZH\H[J]I^H]I]J`J^J\I\L]M]I]L]M]K^J`K`K`J_H]J^H\I^I]K]I\J_LaNcMbLaK`K`LaMaMaK_K`L`J]J\K^J`J_J_L_K_I^I_J_K_K`L`I]L`K_L`L`L`MaJ`K_M`N`K^J]J_H_I^J_L`J^I^H]H]H]I[I^H^G^H]I^I^I^I\M]K]I]H]H]J_J_J_J]I\K]L^I\I\I\H^I^I^I^K`K`J_I_J`LaJ_IaJ`LaMbKaJ`J`KaKaI`KaJ`KaJ`LbKaL`M`L`L`LaLaOdNcLaLaLaKaKbMcJbKcMcMbNaNbMcLbKdKeLcNdLcKdLePeOeOePfOePfQfMgMfNgQhSiPfQgQhPfQgQiSlSiRiSiTjTjTjTjTjSiSjUjUkVlUkVlTjWmWmXnWmXnYnWnWmYoXnYoZpYpZoYnYnZo[pYoZn[o\q\q\p\p^t]s_r_s`r]s_u_u`v_w]v^u_uawdydxeyeyh|i|j}kknopqrrrrtx|~Ȃɋ̖ʠʤ͟ʠ̵ռڵѬϦәь{wssstwyz{~|yxz|}~̀ЁЄ҈Ԋ׍ّܔޔސڟڻ檮ߞޕޓۓᬺ"0^#1a"0d"-b!.b!.b!0c/b/b 0c!0b"0`#1`"0^"0`!1a/c 0c/b.b 0d!1e#0f 1d0d.b /b"/c!1d#/d"/c 0c 0d.b 0d#0d#/c#0d#/c#0d"/c!.c#0d"0d"/d$0d#0d"/c#0e#/e"/d#/c$0e#0e#/d 1d/a/b/b .b .b.b/c.b0d/e0e0d/d/e,b-c.b$0e#0e"/e!1g!0f /d$0e#1e"/d"/d"/e%2j#0g 0g.e!0g 0f!2h 2f/e0f!0g$3k!2j"3k!3k2j 1k"2l!3m2l!5o4m 5o 3m%5m"5m"4m"5m#6k#6m%7p%8p$7r#6o#7o'9q'8m'7n):s):r%8p&9t&9s':r,=u.>v*=u+>v,@x.@x0A|/C}0C}-B{.C|/B|1C}0D~1C}0C}/D}1F3H3I3E}3G3H8M=R@U@U=R9Q5M7M8M8K8M9OS>R>Q@SDVCVAVBWDZCXBWBW?T?TAV@UCXDXFZG[FZFZF[F[G\H\J^J_J]J]K^K]K^J]J]I\GZJ\I\H[H]G\G\J^J^J]M]L]N^K^L\K\J]K_H^I^H]I^G\F\I\H\H\H]J_LaNcLaJ_J_K_K`MaJ_J_I_I^K^J^J^I^J^K`K^L_K_I_I^H^K_K_J^K_K_K`MaL_K^J]K^L^O_N_J]K_J_J_J_L`L`H^I]K^L^I\J^I^H]H]H]I^I^J\K]I\H]I^G\I^I^H^J]I\K^K^J]I\I\I^J_J_I^I^I^I^J_H_I^I^K_LaLaK`J`J`I_I_LbJ`I_J`J`JaKaKaLbMbNcMbNbLbNbMbNbNcMaLbKaLbKcMcLaMbMaNaNcMbMcLdMcNbLcNcMdOdOeOePfPfOePfNgOhOhQgRhPfPfQgQgQgPgSiShTjRhSiTjSiTjSiUkUkTjUkUkTjTjVlWmXnWmWmXnZnXnWnYoXnXnYoYoYnZpZoZo[pYo[p[o[p\q]r_t^s`u]s^t^t]s]t_s`t_v_v`v`vbwdydxdyg{h|k~llmqpqqttstuwzǀɈͤ͘ʪˤ̞ϘΪ֭ϸӶէҥޖtnopqpruvwy{vuxxyzz}΀΄х҉֏ؑؓ۔ܛߡ۝ܘޓݒ۔ݗᛮ헫"0`!/`"/c!.b!.b#0d"/c /b 0c/b"1c$2b$2a"0_!/_"/a/b!1d/b0b!1d#0d"/d!0d/c0d!0c"/c#0d#0d /b 0d!1e"2f"0e#/d#0d!.b"/c#/c#1d!.c!.c#0d"/c$0d#/c"0d#/g"0d#/c#0c"/d#0e"/d#1e"0d 1c.a0b.a/b.b-a0d.d/d/e0e.d/c!/c .c"/d"/d#/d 0f.d .f!0d"0d$0d"/d"/d"/d$1g!.e!/f.f!2f#3f"2f0f/e 0f!0g!0h!1i1h!3j 1h"3j"3k1k!2j!4k!3k 3k$4l 3k!6m!5l#4l$4m%6n&6o$6q#7o#8o#7o%8l%9n%8q%9q%9q&9s&9s':r*v1By/Ax/A|/B{.A{0B}/C|/B{0D~1F~1F2G5J6K6I5H7M9N=R@U>SQBUEXDVGYH[FYEXAVAV@T@UBWAVBWCWEYEXEYDYDYF[F[I]K_K_L]L]I]L]J]H[F\I[H[H[H[H[G[H]H]I^J_J^L\J[J[I\I]K[K[I^G\H]I]I\G\FZG[H\I]G\J_MbMcJ_I_I^K_L`L`K`G^G^H]K_J^K_J^K^J_L`K_J_I]I^I_J^K`L`J^K`K_L`L`L_K^L_L_M_O_M_K^L_J^K`K_K_I]K_K_L^I]H\I]G\I^H]I^H^H\I[J\I^H]H]I^H]G\I^J]J]J]J]J]J\J`J_I^H]H]H]J_I]I`J^J_I^K^J`K`K`I_I`KaKbMbL`I_J`KaKbI_LbNcNcMcMcMbNcMbNcLbMcLbMcLbLbLbNaLbK_NbMcNcMcLbMbNbNcNdNdNdOePfOeOePfOeMgQhSiRhQgPfPfQgPfPfQgPfThUkTjTjSiRhSiTjTjTjTjUkUkUkVlVlVnXmVlWmXnYnXnXnXlXoYoZpZpWqYoZp[p[pZoZo[o\q]r^s^s_u_t^s^t^t^t`t_s`t`v_v_vbwdycxdzfyg{h|llmorqtvxvuwvwx|Ȇ˔ʪά˨͜ΐҊЏәҜԕҍ{pnmonmoqsuxuvtvvwxy{~~̀΃ІӋՐאؓݒݕݒۑܐܐڑڔߕ얭 .]!/a"0a"-`#0e#0d#0d!.a/b0c /a%2b$/b#1`!/_#1c#/d#0d 0c 0c#/c#0d"/d$1f#/e#1f"0c#0d"/c#/c"1d1c!1e#/e#/e"/c#0e!/c"/b#0e#0c"/c"/e#0d"/c$0d$/c"0e#/e"0d!1d!.c"/c#/c$0c"/c"/c /b.a!.c/a 0c/c.b.b.d/e.d0e/e/e.d/b -c#/d"1d/b.f-e 1f"0d%0f%/e#0g#0e#0e"/g$0g#/f".e!0e"2f1h0f 1g!/f"/f 1g3g 0f 1g 1g#3k 3k0h#3k#5m1j#3l 4k5l!5m%5n#4l%6m%6n&6n#6n$6n%8p$9m$8n#8o#8n$9n%9p'9q%8n%8q&:r';s)>v*=v.>w,=u0Ay0By-@x/Bz.A|.@|0A}0C}1F3K4J6K7L4I6K:OS?TR=RAV@U=R?SAUEYCWGZH[HZGZBX?T?U@WAU@V@VEZEZDYDYCYDYDZFZH\I]I]K\J\G[J[I\G\F\FZGZG[GYH[H]I_I\K\K^F]L\JZH[H\H\J\JZJ\F\F[I]J]I\H\G\F\I\I]L`L_K^J^J^K^K^LaK`J_H]H]I]K\I]H\I]J^H^K_K_J_L_J^J]M_M^N_N^M_K_MaL`L`J]L_K^J^K_J]J]M`K_K`K_K_K_J^H]I_H]H^J_H]H]I]I]H]H]K]K^G_I]H]I^H]H]I^I]J]I\J]K_K_H^I_I^I^I^I^K`H^H^K_K]K^J^I_K`K^J_K`LaMcLaK`K`G`J`LbLaMbLaMaLbKaMaNcLbLdLcLbLbMcMcMcLbMaMaL`L`MbK_McMcMcMcKbMcOeNdNdNeOfPfPfPeNhQfRhQgPfOeQgPfPfQgPfQfShUjUjTjRhTjTiUkUkUkVlUkWmYmUkUkToWmWmWmWnYmXnYnZoXnYoYoYoWqYnZo[p\q[p[p[n[p]s]r_tas`sau_t`t`u`s_t`s`t`ubwcxcxdxf{gyf{j}mlmnrqsw{yyyyyzɀˉːʗȢϢқшЄ҄ҊՃuyspnnk~k~j~jnppsrrstvvvyy{|~́΄ЇӊӉ؎َؐیی،،؋Ռّڬ孻阮씭 .`"0^"0^#1b"0`"/c!.c -b.a 0c/a"2c"0^"/_!/^$2`"1`#1d#/d!.b$1e"/d"/d$0f#0e$1f"/d!0e#0e#0e"/c#/c 0c"0d#/e"0e#/c%1e#/c"/e#0d#0e"/d#0e#0c"/c#0c"/c$0f /c/b"1d".c"/c$0d#/c#0d".b/b0b 0d /d0d/c/c/c/c1c/c0b.d.d0c!/b!0d0d/e,b.d.e$1e$/e$0f#0e#0h"/f#0g".e"/j 0f /g"2g 1e!2h1g!0g /f 0f!2k!2j0f 1g!2g!2h"3k!2j!2j"2j!3h 4i4i"5k$4j#4l$5m&6o$6m#5m"8n#7o$7o#8p#8o$9p$9p$9p$9o%:o$:q&8p'9r':r)>r+=s,=u,=u-@x-@x-@x.Ax-C|0E2G2G3I9N8M8M8M:O;PT?RBTBVBVEWEYF[FZCXAV?UBW@U?TAVCXDYCXCYCXBWEXFZFZG[FYI\G[F[FZGZF[G\EZEYHXFYE[G\F[G\G[I]H\H\H\I]H^K\H\H\K[G[EZDZG[G[H\H]EZH\H\J^J^I^K_I]J^J^N`M^K]F\H]G]H]H\G[I]J]H^H^I_I^J]K_J_M]M^M_L_M`L`MaL`M`K^K^LaM^L`K_M`J]M_L_J^M^J_K_J^I^J\J\I^I^I^I`H^I^H`H]I^I^H]J_H]I^G^I]M_L\J^J]K^K_K_K_J]I^I^J_J_K_J_I_K^L_K^J]J^L_K`K`LaMaLaK`K`K`KaK_K^N`LbMbMaKaLbMcLbLbLbMcMcLbLbMcMbLdKaL`MaL`L`MbMcMcNdNaMcMdPdOdRgQeMdMeOfPgQfPfQgQgRhQgRhPgQgQgThTiUjUjTiTiPjTkUkTjVlUkVlVlVnWmWnXoVlWmXnXnUoWoYnXpXp[rXnYpZpZqZp[o[p\q]q[r\q\q]s_tbt_t_u^t`t_s^r_s`tauavdybwcyeygzgzh{j}l~mmpqvxwxw{}ǁȁ̗͊Ν̛Ӑ͐ӔԒ׍چ׉|vxuonkkh}i~h}hlmmosrrttvwyy{|~̀ςЈҊӌ֍ٌڋ׊׍؋אَגؓؕݜ"0` .]"0_"0`#1a#1b!.a -_/a 0d/a"2a#1`#1`#1b"0`#1`#1d"0c"/b"1e$1f#0e#0e0d!1e"/d"/d#0f"/e"/c#0d#0d"/c"/c"/d#0d#0d"/d#0e#0e"/d$1f"/d#0c"/c#0e"/e#0d 0c-a.b/b!/c#0d"/c"/c!.b /b/b 0c 0c"2e.b/c.b/c0b0c2d0c/c 0d"0c!1d!1e/e.d.e /f#/d$0f#0e#0f$1h$1h!.f"/f"/f.d"2e#2f 2f0f0f!0g1f!2i 1i!2j!2j!2i"3j"3j"3i"3j#4k"3k"6l"6j!6k#7l%6l%6m#4l$5m%5m$7o%7p#7o$9p#:p#:p$9p$:q$9p$9q%:q$9p&9q(;s';r)>r-=t,>v.?w,?w-@x,@x.By-By/Dz1F}1F}6J8M9N9L:O:O;Q:OS@UBW@UAVBVCUBVDWFZDXFZDWGZF[EZGYFYFYF[DYDXGXEXDZG\EZEZG]H\FZH\J\I\H]G\H\H\G[EZCXDZFZFZG[EYFZG[G[G[I^I^I^I]H]J^K_L]L^J^J^H[G[H\G[G[I]H\I^I_I`K^K_K_M^L]M^M]O`L`L`L`K_I^I^J_L`K`L`K^K_L^L`K_K_J^J^L`J_J\J]H]H]H]H^J^I^H_I^I^J_I^I^I^H]I\J]L]M^L]L]J\J]K_K_J^J^I^J_J_J_K`H]L_L_N^K`KbKcKaLaJ_J_J_K`J_LaN`M`L_K`LaLaMbKaKaMcLbI`KaLbKaLbNcNcMbNcLbNaNbL`L`MbLcMcLcOdNcMcOdPeQfPeOeOdOeOeOePfRhQgRgQfSfSfRhTiShTiTiTiUjVkSiTjSiUkUkUkVlVlWnWnWnYpUkWmVlXmYnYoYnZqXoYpYpZqYpZq[r\p[p[p[p[o[p]r^r_t`s`s]s^t_s_s`t`tauaucwcxcxcxezf|gzi{k~lnnpsxwxyzćǎ͍͌Γ˞̦ОΗҎԖڏ}|yvvrnljgg~h}i~fhiijmprtvvxxwyz}}̀̓υчҋ֌׋ՌՋՊ֌؍אܑٓܓ"0_!/^!/^#1`$2`#1`"0`!/`"1b 1a!1a$2a#1`$2a"0b$2b$2a$2b$1b#0d 1d#2f%2g#0e 1e#2f#0e$1e$1e"/c#0d#0d$1e"/c#0d#0c"0c#0e$1f$1f#0e#0e%2g#0e"/c#0d"/e"0f#0d!1d.b/c0b 0c"0c"/c!.b"/c /c/c!0c 0c!1d 0e!1e 0d/c0b0c0b1d.b 0d!1d/c.d0f.d.d#1g#1d"/c"/d"/f#0g$0g$1g#/d"/g 0d#3g%3g!1f/e 1i 1i 1i 1i/g!2j"3k!2j"3k#4j#4j#4j#4j$4m 3k 3l!5j#8m#7l#6k%7o$6n#7o%8p$7o%8p$8p$9p#9o%9q$8p%;r%:q%:q(;s+;s+v-?w->v/@x/@x-@x/Bz.D{0E|1F}4H4H7J9L9Lv0>w->v0Ay/@x/Ay/D{/D{2G~4I7J8J9K9L:MU?T@U@VAVAYD[D[BW@U=R?S@U@UATEXCU@S?RASEUDTBT@TBUBUAVBWAVCXCYCZCZCZCXFYEWDYCXDYC[DYFZH\EYFZFZEZFXIXHZEZEYDYCYEYFYDXFWHXF[H[I\FYEZF[G\H\J^J_K^J\J^I]I]H\H\I]J^I^J^I]J^J^J^J^K^N^J_K_N^M^K`J_H^I^L]L^M_M]K`K_N^M^N^L_L`L_K_K_L`J_K_H[H^H]H]J_K^J]L]K^K_L`H]I]H]I^H^I]L^L\M^N_J]J]I\L]L\I]K]J]K_J_I_I_K^M^K^J_J^LaJbJ_K`J`J`J_L`LbK`KaK`KbMaMbMbJaMbMbNcMaKaKaKaMcJ`LbMbNcMcMbNcOdNbMcLbNdMcNbOdOdOdPePeNdNdOeNdOeQhSjQgPfQhQgPfQgSgShUjShTiTiShUjUjVlVlUkVlTjWmXoVnWnWmUkVlXnWmVlYnXnXnYqZqZqYnZo[o\q\q\q[p^r]p^q^q^q_s^t_rau_u^t`v`vauaubwcxcxcxdydydzd{h|jlmmqtw{|ÂÊƏȓǖƖȔΔϕӔҟդԝՑٌwqnk~f~de~d}e}e~f|g{g{g{i~hh|i|kihkqtvuwwxswxz}ςЃЄ҆Ӈ҉Ӊԇԋ׍׎׎הߘꕮ#0`!0^"0_#1`#1`$2b#1b$2c%3b$2a$2a$1a$2a$2a$2a$2a$2a&4b$2c#1d1c 1c%4f%2e%2f$1f&3h&2f&2f&2f&2f&2f$1e$3f'2f#1e"4f#4h#2g%2g%2f$0e$2d$2c$0f$1e$1e$1e#0d#0e$1e$0d&1e%1f%1e".b$0f&0g$3h!2f"3g!3e!2f!2g!2f"3g!1e/c3f 0d/d/d"2e#1d%1f#0e .g .f"1h.e"1d#0d#0c#0d!1h /f"0g$1h$2i!1h 2e!2f 4h 1g 1i0h 0i /i /i 0i 0g 1g 1g!2g3h"3i%6l$5l 3l!4l#5n$6n&8m%9n%8p%8q$9r&;u&:t(;u*=u+;s,=t-=w,=s*=u'v->v->v.?w->v.?w/@x0Ay0@x.Ay0E|1F}2H}6K;N;M;M:K9L:M:M9M8N9O9NT?T>T?T@SATAT?RASBSAU@SBSDTEVAUAVBWAWCVBWAYCZAYAWCVCXCXBVBWBYD[EXEXFYG[H\GWGXGXDXFYDYCXCXBWCXCXCXDXEXFZG[EZG[G[FZFZJ\M]J^J^H\I]HZJ]I]K^L]M]O_I_J_L_J^K]K^J^K_K_J_K_J_L_M^N_K^M]M]M]M^J^J]J]K_J^L`K`KaK_K`K^I^I^J_I^H]I^J]K^J^K^J^I^J^H^H]H]J\J\L]L\L]M^K^I\L]M]L\K]M^L^I^J_K]J]L^N^I]J_K`J`K`K`J_J_L`LaLaMbL_L_L`JaLcJaJ`KaMbLaLaKaNbLaJ`KaKaKbMaNcMcNcNcOcKcLcOdNbOdMdMcOdOcOdOdPdOdPePeOePhQgQgPfSgQgQgQgShTiUjTiUjUjTiUjUjXlVlWmVmWnWnVmVmXoUkUjVlVlVlVkWmXnWmXmZpXoYoYn[p[p[p\qZo\p^q_p^q]p_q`sas`tav_vavavavdvdydycxdycxdycwfyg}i}k~lnrw{LJǍƒƘƞȞȜșʖϕҔ֗סٜҖلuomj}g~fdcafe}f}g|h~h{i~j~j~jgikmrtvvvuvyuyz{~̀ρτІωъ҆ӄ҉Ӎ׍ٍד䘮%0a%2b$2a$2a$2a&4c$2b$2c$2a%3b%3b%3b$2a%3b$2a$2a&4c&4c&4d$2d"2a%3d&4d&4d&4e&3f'4g&4g&3g%4g$5h%6h$5g$5h&4h$6h%5h&6i(5i&3g%2f'4f%3c&4e(6g(6g(6h&4g&2g%3g%3g%3d#1d%3f&3f&4f$1e&2g#3g#4h#4h!3e!3f#4h$5i#5i$6j$7j"6i!2f!3e"4f#2g$4g$2g"1g"2e!1d /g /g#3g#1e$1f#2g"1g!0h".e#/g#2i 1f!2f"2f!2h"3i!2h#2h /e#0g#2h$2i"3h!1g"2i!2h4h 4i#6k$7l"5n#6n$7o$7o$8n#8n$9p%8q&9r':t':t(;u);u,v,=u-=u->v/?w/@x0Ay/@x/@x0Ay1C{.Ay0D{0E}3H6J~9M:N;M9M:M:N9M:M:M;N:M:L;MS?TAU@S@R@TCV?S>R@RASCT@S?TAV@U@U@U@VAX@WAUCVCXCXBYBYBYBWCUDWCXEYGXFVFUGWBWAVDXCWAVAVCX@UAVBWBXEYCXDYFZF[EYEYH\I\I]H\I]H\I]J]L\L\N]N]N^J_J_L`K_L]K^I^J_K_J^K_K_M^M^L_K_L]L\L]L]K_L`J]J_J^K_L\M]K\L]L]I^I^I^K_G_I^I]I\K^J]J^J`I^H\I^H]H^I^H]J\K\K\L]L]M^L]M^M^N^M^I\K_J]I\L]N`L^J^K_K`J_K`I^J_J_K`LaM`M`L_J^K`MaK`MbK`LaLaLaLbMbJ`KaKaKaLbKaLbMcOdNcNcNcNcNcNcQdLbMdOdPeNcOdPeOdPeNeNdPgQfQfQfPfQgPfQgShTiTiUjUjThTiUjVkVkTjVlWnWnWnWnWnYpVmWnVmXoWnVmVnXoYpYpYpYpZpYpZo[o[p\q[o\q\r\r^p_q_r`s`t`u`vavcvbwbwcxbwcxcxcxezezezf{il~pptux|ÇŌēƒ—ġȦʣȜ˙ΛӗӖԗڜޟysnkg}eegfefh~i|h}i~f}h~jjihjmoqstvwssvwyyx{΁΂̊ӌԈЈӈӉҌ֎ؐڛ瘭蘰路墨&3`'3b%3b'5d%3b&4c'5d%3a%3b%3b%3b$2a%3b%3b$2a%3b$2a'5d%3b%3c%3b%3b'5f&4d%3b'5f'5f'5f$5f%5g$6h&8j%8j%7i&7j(:l'7j(8j*6j*8j*8h*8h*8h*8h*8g+9h+9j*8i(8j):l(8i'7g'7h'8i)8i*8j(6f&6i&8j'9j&8j);m(:l&7k'7l'9n%9m&:m%8l$6h%7i&7i&6j%5h%5i'7k&5h$4h$4i#3i!2f%5i$5i$5h&6j#2i /f"1g"3g#5i$6j$4h"3j#4j$4j#3j$3j$2j#3i"5j"7k!5i#8l"6l!5i!5j"6k!5k#7o#7n#6n#8o$:p$9p$8o$9r%7o':t(;u)w/?w/>w/Ay-?w.?x0Az2C{2C{2C{1Bz0D|2G~2G~5H8K9M9L:L9N9M;O:M9M:N9N;M9L;KS>S@U?T@TBTAT@UAV@V@U>S@U>S@U>TAS>Q>R?SBV?S?Q@S@Q=Q?U?T@U@VAV@U@U@T@U@UBWCXBYBYAVAUAUBU@U@UDWDWFWAUBVBWAUBVAWAVAV@UAVBWAWBXBWAVBWCXEYEZEZI]H\G[H\H\H\K[I]J_H_J]J^J^K_L`N_L\I^J^M]N_N_N_K^M]L^K_L`K`K]K\K]K_K_K_J_L_L\L]K\M^M^N^J_J^J_LaKbI^J_J]K^L_L`K`J_I_I_H]I^H]G]J^M]L]L]K]K\M^M]M^L^N_K^K]M_K^L_N_O`J\K`I^I^K`J_J_J_J_K`M_M`L_K^K`LaK`LaLaLaL`LbMbMaKaJaJ`J`LbKaMcMcNcNcOdOdNcOdOdOcMcMdOdNcNcPePePePeOeOeRgQfQfQgPgQgPfQgShTiUjTiShSiTiUjUjVkWkWlXnWnWmWmXnVmWoXoXoWnXoWnXoYpXoYpYpZqZqZpYq[qZo\r^q\q[s]u^s^s^s^sat_t`xavbvbwcxcxdydydyezdyf{f{h|i}m~rsvxyĆĊďŐŗģǩɮϫΩЧӤב}Ƅ͚uqmlh~gfh|i~ff~ii~i~j~hi~jkilloppstwwusuvyzzz}́΃͉ҋӋ҉ԇӈӊԋՇ碳(5b(5b(3d'5d'5d'5d(6e(6e)7f&4c%3b%3b%3b'5d&4c$2a%3b%3b%3b'5d(6e&4c&4e%3d'5c(6g(6g'5f(6g&6g'7j);m)9l'9k*8l(:m(:k,o/=k2@o6Ds2@n0>m0>m3@q3Ar.?p/Aq.Ap/@m/An-?l1>m2?n0=n,?p->r*;o-?q0Bt.@r-=q.?r,=p)v*=u,?v,@u/@v0Aw/Dy/Dx/Cx0Cy0C{/C{1D|3G~5I6K7L9M7M9M8K:M8M:MQ=R;P=R>S=S@S?S=T?TAV@U=R=Q>R=R?T=S>R>R@T?SAU@U>Q>R?T@U>S>T>U?WAU@U@S?S>TAVCXDYBW@UBVDWAUAS@UAVBW@VAVBUEU@UAUBV?T@U?T@U?U@X@W?WBYBYAVEZDXEZFZG[H[G[G\J\L[K[J]J\J]I]I^J^J_L[M^K]I^L\M^M^M^M^M^J^K_K^J_K^L\L]K`J_J_L_M^M_J\K^J_K`K`K_J^KaK`KcKaK`KaM`NaObNdMdKcJ`I^G\H]I^H]K\M^N]M\H\K]K]J]I]K^K^M^M^J]K^O`J^K^L`H^I^I^I^J_J_J_K^L^K^L]J]J`K`LaLaMbK`L^MaLaMbMbLaK`MbLbKaMcMcNcNcMbMbOcOdNcPcNdMcOeMbOdPeOdOdPeOdOeQhRfShRgQgRhRhRgRgUjTiUjShTjUlVjVkUjXmWlWlXmXmWkVkYnXnXnXoYoYpXoWnXpYpYoXoZpXrWrZqZq[p\q\q^s^s^t]r_t^t_t_t`waycwcxcxcwdwcxezf{f{d|g{g}j|knssww}ƍď‘ŏŚæű̱ͲԨФԖzustspmkj|h~ej~h}ggjgjjkklkjnnprqqstvutvxzzyz{͉́̈́ЊҊԈӉӇ҇҅ϰ⫬Ӷퟮꗱ'4a&4b(6d'7e)7f(6e)7f*8g+9h)6f)5e(4c'5d(6e(6e'5d%3b'5d&5d'5d(6e)6e(5e&5e(6h(6g(6g&6g)7h*9j)9j+;m*p,v+@v-@v/@v/@t/Cv1Dw/Ez0Ez0Cy2G}5F|4E|5H6L8L9L;O;P8O9L=M=O9M9J;LSS?T?W?W>VPQ@S>Q=S?R@T@S>Q=R>S=R>S=U>R?T@UAV?U@U?U@UAVAVBVAVAUBTBUCWBVBVBVAVAU@T?T?VAT@U?T?T@U?V=W>W@WAWAVAVAWCVEYBYDXG[F\G[I]J[K[L_M^L^K[L]K\L\K^I^J\L]L\L]L]L]L]M^L]M]L^J_J_J^K^L]K`K_M\L]M^L]L]M^J^K^M^N^K^I^K_K`LaLaMbMbNaNaNaNcLaLaK`J_H]I^H\K\L]L]L\H[L]K^J]I]J]M^L]J]K^J]K^N_L_LaL^J_J_J_I^J_J_J_LaL^J]J]K^I_LaJ_K`L_L_MbLaMbMbLaMbMbLaKaKaNbNcOcNdNeNeLdMeNeLbMcOdPeNcPdPdNfPgMdNeQhPhQfQfOeQgQgPfQhTiShShTiVkUjUjUjVkVlTkVkYnXmUjVkXmVmVnXoUoUnWnVoYmZnXqYpXoYqZq[rZrZq]r]r]q]r]t]r_q`s_u_tavbv`xbwcxcydzezezezf{f{g}h|k~lquuw{ăNjœ”“•¤įȲ˯ӞϚב{vtqprqnj~i}ggj}i~efgk~j~kmklnnqrswsrqttsttxzzzy{}}ˀ΄ͅΈӄτшӇЈΐ՜֣ڠץޞ锰'6b(7d)7f)6e*8g(6e(6e)7f'8f)8f(6e*7f'5d)7f)7f(6e(6e(6e(6e)7f'8f*7g(6e(6g(6e(6f(7g(8h)9i*:k*:k,;m,=o.?q/Ar/@r0@r2Ar4Br6Dt7EtLz;Iw8Fu7Et:Hx=K|:Ly;Lz;L};K|;M~=N~=Or,=r+v,@t.Bw/Cx.C{,?w,?x-?x+@u+?v-=u-=t,>t+>t/Cx-Cw.Bx0Au1Bv2Fy3Gz3I{2Gz3F{4H|5I}6J6J8K:L:N;NO;N:NSR?S?T>R=R=R=T>S>S>S>S>S@U?T?T@U@U?U?T@T@S@SCVCWAUBVAUAUBVAU?U@S>R?T?T?T?T?V?V@U@UBWCX@U@UAVCXCXFZF[F[G\J[M`L^L^L^M^M^I\J]I]J_J\K\K\L]L]L]M^L]K\L]L]J_J^J^K_K_J_J^M]K\M^L]L]M]M^J^K]N^L^J^J_K`K`LaKaM`L`N`M`L`LaL`LaLaI^I^H[J]I\K]L\H\L]L^K^K^J]J]K]J]K^K^L^N^K^K_K_I^I^J_J_I^K`K`MaK^J]K^L_J`LaLaK`M`NaLbMbMbMbK`MbLaLcLcLcNcOdNcOeNeNeMdMdLbLbLbOcPeOdNeNfNeOfOfPgPgQgQfQfQhQhRjQhPfRgShShTiUjTiUjTiVkUjVkVjWlWlVkVkWlXmYnWnWnXoWnVmXmYnXoYp[rZoZoZp\sZuZs_r_s^s]s]r^r_s`u_taubvbxbwdyezezf{ezezh}h}i}llmsw~|yǙȝÔƭʺ̳̟ΐ}wrplnpnj~kii}gf~i~i~h~ghi}j~mmkilprtwzwsqrutuuwyzyz~}~ɂ̃Є·χшІЊҔؚ֘ڗܗ镯&9f*9h*7g)7f*7f*7f)6f)6f'8e'8f&7f'8g)7f+8g)7f)6e(6e*7g'7f'8f&8f(6e'5d(6g)7f,:i-;j->l/@n.?l0>o0>o0@q3Bs6Du8Ev7Es6Ds6Ds;IxLz=JzL}=KzP>Q>P~>P>O?NAO@M>P}r,>s+>t*>s,=u,>v,?x+=u,?w-@x+>w-@x.Aw.Cx.Cx1Ez0Ez0Ez0Ez3G|2G{0Cx1Aw2Bx2Cy1Dy0Dw1Dw2Gy4Fz5Ey4I|6I}5J}5I|7K~6K}8K5J5K6K7K8L9M;M9M:L7K6K6J7J9J;K;K:M:L:O8L9M;N>OS@UAV?T?T;P;P;NSS?T>S?T>S>T?T>S?T@SATBTBVBV@TATBVAU@T>S@R=R=R=R?T?T>T?S?T@UAV@U@U@U@TBWDXFZGZF[G\H[K^L]L^K]L^I^J^K^L^K^M^L]K[L]K\L]L]K\K\K\K\I^K_J^J^I^J^J\L\K\M^L]J[H[K[H[I\I\J^J^I^I_J_I]K^L_M^M`M`K^K`K`K`LaJ_H]H\L\H[J]I\H\K]K]I\J^J^J\M^J^J]M^L]M]I]L_J_I^K`J_J^I]K`L`L`L`K_K_K_MaK_MbMbLaLaNbL`L`MbKaLbMdMdMdLcLaNcPeMeMdMdNdNcMcLbMbOdOdOeNeMgOfOfOfPgOfPgRfPfQfRhRiRjRhRgUjShUjUjUjUjWlUjVkVkVkTiWlWlXmXmXmXmVmYmXlXoXoYnYnYpXp[pZo\pZn[q\t[t^q_r^s_t]r^s`uau`s`scvbwdydyezf{f{ezf{ezgzk~kmootŎxpmsɝŝū˼Բ̨Фwrpk}i~kjj~j}h}g|f|efji}kjjjkmnkknnqsstutrpsutwvy~~|ȁˁ~~ɀ~ʀʄͅψφІϐӗٙՕדߘ따꘰)7f*9h'9g(9g(:h&8f&7e(9f):f):h%6e(7i'9f(9g):h'8f+8h)8g*;i);i'9g+9h+7g,9k,:i/Ly>M|=M|=O|=L{@M|=K}=KzK}O@NAN}@N?N=O|Oz;Qz=Oz?M{=J{;Iz8Hx7Hy6Hy7Fy5Dw4Cu6Fx6I{9K}9J}:I|9Iz7Hy:L|:L|;M~Q=Q;M;P;P:P8M9LR>S;P;P:O;O;O;P;N:O;PR>SR=S=S@U@U>S?T=T=T?T?TAS@SBVBWAVAUDTCSBW?T=R?Q>S=R=R>S>S>T@V?T@U>T?T?T>U>V@UBVFUHYDYFZG[J]L^K]K]M_K\K]L_L\L]L]M_L^J[K\J[K\L]L]K\L]M]L_I^J^I_I]I\J\L]J[IZIZFYDWEXF\G\F[G]E\J\H\K\N^M^O_L^L_M_K_LaLaK`J_J_I\L\L]L]K]I]L^J]J]M]M]M^M]J^H\L\O]M\J]H^J_J`H^I^J_J_K`J_L_L`I]K`L`L`L`I^J_K`K`L`L`MaK`L`JbKbLcKbLcLbMbMcNeLcMdMcMcOdOdNcOcNfMeNeOeNfNeOfOfPgPgQeOdRdQeRgRhQhShShShRgTiUjTiUjUjUiWiUkVkVkWlWlXmXnVoWnYmYmXnXoVoWoXoXpZo[m]p[pZo[p\p\q^t_u^s_t_v`vctaucucvdxdyezezdy_t_tav`vcvdxdxdzdy`ufxyzl}i{fzi{m~}Чҗ˦ݏqnh}g{g{g{j~kh|g{g|h}d}fi~jiji~h~i~kmnnoqnnppqpnmrsttvzŀȀłɃˁɁɀ~ʁ˂΄Ά̆ϊЕӗԗԗѓٙ떬᫶單)7d)6b*8h(9g(9g'8f'8f'8f(9g(9g'8f'8g(9g(9f):h(9h(9e*;g+;i,;j):h):i-:j-;j1?l2Ap2@n3An5Co6Cr6Dr6Ds9Gv9Hw:Hw;Hx>L}=Kx>Ly>Ly=Mx=Kx>L{;Lz:Ky9Iy9Iz;J{M{=N|>P}>O}?O?M~@K{AM}AO~;LxAN{?Mz>NzP{=NzP~>Q~?Q};P9M9K}8I}6F{7G|7H|7H|3G|2G|0Ey0Dy0Dy3Fy2Iz1F{1F{2H|3J|5I|4Hz5I|7H{6G|4H}5H~7G}8I8I8K;L:J~9J~:K;K:K:J6H{6J5I~6J5J3H4I4H5H}4G~5I}6J~5I~3H6I9J:J:K7L6J7J8K:K8M9K9M;O;N:M:M;NR>R>SR>S>S?T>T=S@U?T?T=R=R>S>S?T@T@SCXAUDTDTBSCTCUAT>S=R>S>S?T=R>S?R?T=R=R>T?S?T?T@T@UAWDXEXGXEZE[G\K]J\L^L^L^M]M]M_L^M_K^L^M]K[L\L\K\L]K]K]K]J_J^K_K_H\I[K[J[J[HYFWCXBWDYCXBZCZBZCZCZEXI[I\J]K^J]L]L^I]K`J_J_J_J]K]M^M^L]M^M]M]K[I]M]M]K[K]J]L]M^M_M^K^J^K^I^K\L_I^I^I^K_K_K_K]M^K_K_L_K_H^K`K`J_K`K`L`K_JbLbK`KbKcLbMbNbLeNeLdLbMcOcNcMbOdPeNcOcPbPePePeQfPgQhPfPePeQfQfPeQgQgRfSfSiTiTiUiUjUjUjVkWlVkVkVmWlWlWlWlYnXmXmXoXnWoXnYnYnZn]q\p^o^o]p^q]s^s]s`tau`u`v`weueufyfz^s[p]r]r[p]r_t_tcubv^s^t_scvcwcubsbtbwg{ezdzdyǒуǰӺӁmh|i|eyeyeyi}h|h|i{hyh|i~h}j~ijiik}i}j}k~lnnooopnpmk~jotwvy}ǂɆ̆ˈɊψ˃DŽʂˀ~Ɓ˄̆ͅΑѠўϜӞԽ䖬誷䔯)7d+9f*:f(9h*;i):i(9h):i):h*;i*Mw=Lv:Ir=Lx;IwL{=Ky>My?Ny>My?M{L{?L|@M{>Nz>O|@M|?L|>Kz?M|@N}=N{?My@Nz>Oz=Pz>Q{=Pz=QzNz=N{>O|?L{=L}S=RRS=V?T?T?T>S=R=R=R=R>S>T>SAUAUDTCSCSAU@T?V=R>S=R=R>S>S>S?Q>T?T=S?U?T=R?U?UAWAWDXEYEYG[G[J\I[J\L^J\J\K]K]L^L^L^L^L^J]K[J[J[J[K\K]JZL\J]H\H\J^I\I[JYJ[GZEYBXCXBWAV@UAV@UAV@V?WBXCXEZF[G]I]I]H[I^I^I^J_I_I^L^L]N_O`O_K^J]I\L]J\J]I\H\L]L]N^N\M^K_J^J_H]K^K^J^K`K_L`L`J]N_L]M^L`L`K_LaJ_I^J_K`J_J_J_KbKaKbKbKaMbMbLbMcLbMcLcLcMdMcNcPeOdNcOdNcPeOdPePePePeQfQeQfQfQfQfQfQfShSgUhSgRhUiTjUjUjUjUjVkXmUmXmWlVkVkXmXmYnYnWoWnXpXnZoZn[o\p^n^n_o_o^sau_saraubvaucvevgxfy[oXmXmWlWlZo\q^sdxk_u\p^p^q^q^r]q\o^s`vgyg|f{h|j}k|蛜r~i|h{fyeyeyeyfzfzfzg|h|h|i|i|i|h}f|g}f|h|i}klkmnnpnnookjilpvuy|ƀDžˉˊʇʈȎ̐ɐˇɃȆʅʆɇ̒ͭժ͟ǜʨޔ♪閱*8e*9e*;g*l,j0@l0@l0@l2@m.@l.Al.Al0@m2Am3Am6Co5Co5Dp9Hs=Ku?Ny>Mv>Ku>Kt=KrLy>My?Mz>Ly=JyK{@Lz@Nz?Lz>N{?M|>Kz>Lz>L{=M|=O}NyO{@R|>PzO{=Nz=Nz?P~@Q?P{AO|>My@Oz@O{AO|BR}CR~DR~DRzESzET{GV~GXDVBV@R~>P=O;M}:J|9K}:K~9J}9K~9J~9K~7K~6J}5K|5K|4I{4J|4I|5H|4H{4H{7G|8I6G~8I6I~9KN;L:J~9I~7H|5J~5J2F|2G|2F~0Ez1F|2F}4G|4H}4H|3G|4H|1E{2G~2G3G4G4H6I7H4H2G2G6I7H7H7H3H~6K6I7J6I9M:N:O:M7J:M9LT>S>R>S=R>S;R;Q=R=R>SS@T@UCTDS@T@T?T?T>S=R=R>S=R?T@T?T=S?U?S>R@T?U@U@UAVBVDXDXFYH]H[K\I[K]K]L^J[J[J[J_L\K[J[L[J\J[J[K\J[J[J[J[K\K[I]I]H\I]I[IYHYFXDWBXCVCW@U?T>U?T?T?T@XAXD[CXCZEYEZG\H[F[F[EZH]J_I_I]K^K^K^K^J^J]K^M^GZI]I\I^J^L^L\L\L]K_J^J^H^I]K]L_L`L`K_J^J^N^M^N^L`K_K_L`M`L`K`K`J_J_K`K`J_K`K`J_LaMbMbNbNcLbNeLcNeMdNcOdOcNcPePeOdOdPePeQfPeRgRdQeQfRgQgRhRgQfSiTfTgSiTiUiShVkTiUjUjWlWoWlXmVkXmVlXmYnYnYnXoXoZoYoYnZn[o\p^p_p_o]r_s_scs`ubvdvgwgxhy[pTjTiUjVkWlXn\q\q`ubw\q]q[nXlYmYo[q\q]scxfzgyf|g{h{h~i{iye{fzexcwcxcxdxcweyfzg{g|f~f{fzczdze{d{i|ii~kmllmmollljkjlqrsx{Dž̇ˈɇʈƖ̜ǘƑʘ̑ʌʇ̍˓̡Ȭй٨Щު뫲咯+9f+9e,:f);g,>j,>j,>i.>j1>k0>k2?l2@m4An4Bo6Cp6Cp3Ep1Co3Dq2Cn4Cp7Dr8Fs9Fr9Hs:JtMs=MpLqLy=Kx?Mz=Ly>Iz;IxLz=LxPx>Qz:Q{=O}?P}CP}DR~AP{BPzCQ{BRzCQzCP{CP{CQzETzFT|GXHWEVCVBU?Q=O~;N}:K|:L9L}:L~=O>PSS:QP=QU>S>S>S>TBT@T?U?U?U?R@TATAU@UAUBVCWDXF[JZFZG[I]J[J\L^K\J[K[H\H\J[HZL\L[K\K[K[K[J[IZJ[K\K\K\K\I]I]I]I[JYIZGXGZGYDWDWBWBX?X@WAV?T@U?WAVCWBXCXEZDXEYEZF[DXEYH\H]I_J]K^J]K^J_K^J]M^J\I\J]J^I^J^K]L\J^J^J_K^H^I^I\M^J^K^J^J^K_K^M]L_K`L^M^L`LaK_LbK`K`JbJ`L`LaK`MbK^LaMbLaN`NaM`LaLbNbNcNcOdPcNcOdQfOeOdPePeQePePeRfReQfRgQhQhRgRgTiRgShUjTiTeUkWkTkUjVkVkVnYmWlWlWlUlXmXmYnXmXnYpXoXoYp[pZo[p^r]q_o]s_taubsbubvdxezfv`pWkTkWkTiVkUjYmYnZo[pYmZp[pZoYnYoZn\n^r`ucxe{dyezh{i{jh􋐼hydyezdybwdycxbwbvdwdze{b{b|b|dzdzbxcyd{h|h}kklkkkk~j~j~j~j~hkkoopstwzŃɈˇˈ̘̌ȡɜŗƟ͜ʖɕ˗ɚ̥ʭ䫴ꖨ嘯+9h*8e,:g,:g.k2?l1>k4Bo4An4An7Cp6Dq7Er4Fr5Eq8Gr7Eq5Fp;Ju9Go8GoNs>Nr>LpL{Kx=Iy;GwKy>L{=Jy=Kz:KyKyMx?OzAP{?Ox=Nv>Px=Ox=P{=Q|>P{?O{BP|DQ~CP{DQ{EQ|BQ{CQyDRyDRzDRyFS|HWJYIXGVFUEUATAR~@P=N~O}?P}>P>P?O}=P=O=OS=R=R=R:R;RR@TBSARAQCTARAQ?R>T=S@UCTBRAS@T@T@S?V@VAVAUAV?UCVCWDXH[GYGYJ\IZF[J[IZI\JZJZJ[J\I^H]I^I]K[JZJ[K\J[KZJXIZM\L]K]I]I]I]HYIZI[FZE[FZG[EXCXBWBYAXAX@U@UAV@UBWCWAWAXAXBYCZBZD[CZC[E[FZGZH[G\H]H]I^K^J]I\I\I\I\I^I]I^H]J^J^I]I^I^H^I\L\K`K`L_L^J^J_J^J^N^M^M^N_L]K`J_K`J_K_JaL`LbMbKbJbMaLaMbNcLaMaLaLaNcMbNcNcNcQeOcPePfQgPgQgRhPfRgQfPePeQfQfPiRhRgSgSjQgTiUjSgUkTkUlUlVkWlXmVkVkWlWlWlYmZnZnXmXmXmZoZoYqZq\q[q\q]raqarbsbrbtcweycxdxdvXkUlUlSjTjUkWlWnXoYnZoYmZoZo\qZo[n]o\q`sbwcwfxdwfzg{k~mri}g{dycxcxawbxawbxeyf{f{c{b|b{dzcybxcxdxfzi}i~kkj~j~j}h}i}h|h|j~jilmoorqvyĆɃȄˌ˝˥̜ǔƕá˜ęƛȜʟɪθݢؤꮹ鏩)7d*8e,:g-;h.k/=k1?l1>k2An4Bo4Bo5Bo5Bo6Dq7Er7Fq7Fq9Hs8Gq:Jr=Kr>KpLq=Lo>Mp>Mo=Mn?Mn>MmAPp>MoLyQ~@RAP|BP|BQ|AO{?P|=P}=N|P=O;L8J}8J{7H|5Fz4Ey2Dx3Dz2Bx1Cw/Cx/Dy.Bw0Dy0Dx0Dy1Ez0Dx0By/Ay/Ay/Bz.D{/D{0E|.By2E}3D|2D|2F~2D|2F2G1F1E2D}4D|2C{3D|3D|4E}3E}2E}4G5I~5I~5I3H6J7J;L;M:N;N:K8L6K6L7M9M8L9L8K8K7J6J:N9M8L8L7K8K8K:M:K;J9K7L8M9Nk2@m2@m3@m5An5Bo4Bo4Bo6Dr6Eq:ItMn>Mn=Lo=Kr=MuOvOz>O~=O~=O=O~P>Pk1?l0>k1?l3@m4An3@m4An7Cq7Dq9Gu;Iv=Kv>Jr@NqERtHUsESqDRnCQlCQnCQnCQo@On?No>Mk=Lm>Ln=Lp=Kq=Kr=Ks=Lw:Hv9Gt9Ht8Jt;ItMw;KvMx=Mx;Lw;Lx;Lx:KwPx>Pv@Qu?Ou@Ou?Os>MsOzR}CSCQ~CQ}AP{CR}?P{?Q|?O{>Q|=O|>Pt->t*?t,@u,@u0@v0@v/?u/?u/Ax0@y0Ay1Ay2C{/Bz/Bz.Bz0Ay1Ay0@x1Ay/@x,Az,Az-A{/By1Ay1Bz0Ay.Ay/C{.Ay0C{2C|0F}2G|3G|3G|4H5H6J7L8M7K5H3H4H7I6I8J8K9L;N:N:N8L8L9M7K6J6J7K7L6J5I6H7I6I7I7J7L9O;Pk.k/=j->j0>l1?m4An4An5Cp7Er9GsJqBKqGQqHSqIUqGSnFQnCPkCQmAPm?Ok?Nm=Ll@OpAQq@Op?Np>Lo=JmMx;JuMx=Lu=Px>Nz?Ny=MwMv>Nz=Oz;Ly?Mz@Mz@N{APzCSxHUwFTwETwAPtDQvDTxARwATyES{FT}EQ|DTFTDS~@R}@Q|?P|CQ~BO|AO|CP}AO|?Q|>Q|=P{=Q|:OzP=O=N;M9K{7I{6I{4H|0D{/Cu.Bw->t-=t,>s+?s+?u*?v*>u+?t+?t+?t->v/@x/@x/@x1Bx0Cz.@x0@x.=v/>w.=y/?z.@w)>x*?x-?y/?x/@x/@w.?y/By2Bz1Bz/C{2F|0Fz0F{1Ez4H}5H5H7I7H}9I7L6K3H4I6K7J7K8K9L:M:M:N9M8L8M7K6J6J7H7K4H}7I~5J~6I4G5H6I9L9M;O9N;P;Q:O;Q;P:O:O;N:M

R=Q@Q@RAQBSBSDVBSCSEUCUCUDVCUAU@U@W@VAU@S@TAUAUCWCWI[GZFZHZHZHZI[IZIZI[J\F[J]J\F[DYCXCXCXCVASDWCVEXEWDWDWDVBXCXEWCXCWGZE[EYDWAUAVATAV@UAVCVCXDYDYCXCXDXHXIYGXDXFYDYDYDYDYDZCYEZH\FYHZG[F[I]G[IZH\G[G\H\K]H\H\I]I]L\L\I]J^H]G\G\H\L\H]H\H\H\J^I]I]M^L]N_N_M_N^O_M_O`O`L_L`K_L`MaNcMaOcMcLcOaNbLaNbLbMbNaNbNdQfOdOdNcOcOdPfOdPeQdRfRfSgRfThRhShThTgSiUjUgUhVjShTiUlWlVkWlUhVkVkXlXkYmZo]m[nZoYn[p\s\s^sat_paqat`uaubsYnWlWlVlYn`s]pdw`tWlRgPeQfRgWkTlSkSjRjTlUkVkWl\n]p`tcucvbvbvdwf{i{k~jmoqj}g{e{dzdxcvbwg|gdyh|lnnk}fzh}nllmqmh}i}h}j~k~j~j}j~kklmoooqvz~Ȅʉ̌ʋǓ˕ɛŜȡɨ̫̪ٺ٠֭ݼ㲹۟㪻蛬摩.j/=j1>k1?k3@m4Bo7Er8Gs:Gq=Jo?KpDOoFQnHRnITpGRnDOkCNkCPmBOm@On@OnDSsCRsETtCRr@Pq>MmMv?Ny=Lw>Mv>Mu@OwAPy?Ow?Nu>Mu?MtAOu@OvAPt>Ov?Qw@Ov=Mr>Ms>Mt=Lt?Nz;MxKx>Mz?NzBNxCPvCQuDRuCSuDSuBRu@QwBTz@RxCRyBR{DS}AS~AQ|BQ|BR}=O{@N}AO~BP~@P?Q?P~?P~u.=t-w/>w.=v/=v.;t-=v*w+=w->w.?w.?w->v-@x2C{3C{0D|3E}1G}0F|1E{4H}5H5H5H7G8J7J8J5K8M8M9K;K8J6I9L9L8L8L8L6J8L8L7K6I7K6J7I~7K6I4G3F~4J5J6K6L7L6M~9N9O:O9N:O:O9N9N;O=Q>R=P@RATBT@RASBSDTCUCVCTCVDUBUAU@U@T@TAU@TAU@TBVHZHYHYGZI[I[I[I[HYIZHYJ\IZIZHXF[BWCXDYDYAVAVAVDYCXBWBWAVAUAVBWDYCXFYFYG[FYG[FZAVBVBWBWAWCWEYEYCWCXDYFYIYGXHXEYEYDXDYDYDYEZDZDYG[GZFZH\H\G[I[J[H[GZI]J[K\G\J]J[K\K\J[J\J]I]G[H]I]I\G[I]I]J^J^I]J_M]L]N_N_K_N_N^N_N_N^N`K_N`M`L`MaMaMaNbMaMcLbMaOaPbOcNcMcPeQeQeNcMbOdOdPePeOdPeQeRfRfSgThRgThShThUiWkViViUiUiVkUjWlUjWkWjWkWmWmYlXk\m]nZo[n[p\q]q^s`tbtarbsbt^r\qZmWlVlUkWkWlUiThRfPeQfOcOdNcPeRgQfRgQfThUkUjWjXm\l`qarctdueuduhyexfzi|l~lopmg|e{ezex`vbwcyii~jllmh|jjlh|i~mnpfzezfzh|mkj}klmmppqtswzʅɋ̏ǐƔÚɜƠŞɣʦٯܰ寷尼蓥瑩.i0?i1@l4Bo7Eq7Eq:Ho=Kn>LoBOmFQnFSnGRnFQmDOkDOkFPoBNl?Om@OnAPoCRqBQpAQp?Nm?No=Ll;Hm:GpLt=Mu>Mv;NsMv?Nv>Ls@NvBPu@Mr@Nt>Nr?Lq?Mr?Ms@Ns?Os?NuMq>Ms>Ms>Lt>Mx>Mx;KvKy=Ly>Lx?Kw=Jv?MuCQuDRvBPsARu@QvASy@RwAQwCRyARz?P|?P{AP{CR}AO{@O~AN~@N}>P}>O}@Q@M~;M}v,=u->v/@x.?w.?w->v->v-v->v->v/Bz/Ay5F~3G/D{0E}2F}6I~2E}1D|3E~7H3F~6I5J6K6K6L6I8H7I7J8K7K7J8M6J7K9M9M7K4H~5I~6K6H~7L~6K5H3G~5I~6I~8L7K7L7K8K9K8L7L9N:O;P;P;O=O=R@RASBTBTCVBSBRAUBUASASCUAS@R@T?S@T@TAU@UAUAUCWFXFXGYHYGYGYHZHZFWHYHYHYJZGXGZEYAVAVBWCXCXBWCXBWCY@WAWAW@V@UBWBWDWFXGYH[G\GZGZF[FYHZEZEYEYFZDXDXDYEYFYGXIYHZG[FZEYCXDYEZG\CYEZH\H\H[I\I]H[IYIZHYJ[JYJZI[J[J[J[K\J[L]K\K\J[G\I\H]G\J^K]I]H\K_J^I]L\L]K\M^J_N_M^M^N_N^L`MaO`L_L`NaNbNbMbMaLbLbNaQaPaPcNcMcNcRfQeNcNdOdPdOdOdPeQfRgRfShSgRfRgThTgSgUiThVjVkTiVkTiUjUjWlVi[lYlXlYmYmZm\m]pZo\p]q\q^s`sctcu`sctat[pUkUiTkWnWmWmRhNdNcMcMcLaNbKaMbNcPePePeRgShUjVjZk\m_p`qararctdufvevcwhxiyi|l~k~moldycxdwcwawcyjmkllkli~jjg}g|lnph|g|fzh|i~kkkmnprrvwwz|Ɇɍ̓ȟ˧ɤɦΦɤҟ˲攠߱䓨쎪識뛭杳馹露識1=c1?c/>e.>e-h0?i1@i2Al4Co5Do8Eo9Gl;Jl?LoAOlDQnDSmESmERnEPlFQmCOmCMl>Ll?LlAOoBOoBOoBOoAMo=KmLs=Jt=Lv;Lp;Kl>Lp>Js>Ku=Kt;Ip>Lq@KtBNs?Mp?Ms=Lt@Ms?Mq@Ns?Mq?Mq=MrMt>Mv?Nw?Lv?Nz@My?Ny:Mx;MxMv?Ky@Mx@MxBOyCQwAPw>Pu@Rw>PsBQwAOwAPw?Qy@QyAOyAPy@Oy=NzO}=N|M|9Ky7Iz8I{6Gy4Fy3Ex3Ex0Au0Au/@t.?r/>r*;r+v.?w.>w->u-v,=u.?w-Ay/Bz4E}3D|/C{.Cz2E{4D|3D|1C{2E{4E}2F~3F~3H3H4I4I5G6I5H7I6I8L7J6J5I}5I~5I~6J6J2H}5I~7K8K6J|5I4H}4I}4H}5I~4H~5H6I5H~5H5I7J8K7L9N9OO>N>O@PBRBRASBTASCT?T>T=R@T?S>R>S>R@S?S@TAUCTCTDUGXFYBXBXGYHZGYHZHZGXHZHZGXBVCXBWAV@UAVAVBWAWBWBWDYCXCXBWCXCUAVBXDVCVHXHYIZFZIZHYHYIYHXHYHXEZDZCYCYCXDYG[IZHYGXG[H\EZDYEZF[G\F\EYH\H]J[L\J[H\J[IZJZJZIWIZF[H[H[JZIZJ[IZK\J[K[I\H\I]I]N]N]I]I]H\I]H]L\L]L]L]K^N^O^M^M^N^M`MaMaMbMaMbNaNaO`O`MdLbMaRbPbOcMaNbPdPdPeQeOdPdPdOcPeOdPePeQgQgQgQgQgQfQgQgRgSgUiViUjTiUjUjShUkXk\m]l[mZm[lZn^nZo[p]o\p_s`qbrduewdweucvYnRhShSiUkTjRhPfMbMbKaKaKaLaKaLbMbOdNcNcOdQfSiVi[l_l]p_qasasbsctdudtevfwhxjyj~k~j}lkf{dxizj}eyg|fzf{h|g{jhi~jh|e{f{f{kprqi}h~g}h~ikjloqtuuy{{|ƇːҔ˟为ѬͰҨ٢⤲蟰㕤玨周陨⒥ݠ좳闤ᚬ栳뢹隭飸ᖤٝ㢶른졶꡶騼먷5Fg4Be4Bg1?c2?d0?f.=f.=g,g-?g-?g3@j2?i6Dl7El9GlKm;Im;IpKu=Kr=Kp>Kp?Ko?Ln>Kn?Lp>LqLs?Lv@Mu>Nr=Mq>MtMv=Lv>My?Ny>Mx>Lw>Mx=Lw=Lv=Lx?Ly>Ly?Mz@O|@Oy?OxOt>QuNxN}>L{=Kz;L{N};N{:Lz;Mz=O}=O}q-?q.>r*9q*:r*;q&:o(u);q)v+>v,@x0C{0B{1C|.Ay.Ay/B{/Bz2C{3D|3C|0C{3C{1D{/E{1F}0E|2G5J4I5G3J6J}7L7K7K4H}4H}4H}7K6J6J7J7L8I5J3G|3G|5I~4H}7H~4G~2E3F~4G6H9H7J6I8L9O9N:N=O=N>P?RBRBQASAS>Q>S=S>T>SR?S>R?S@T?S@TATFVFVEWDXCWEXFZHZIZHZI[GZFZHZI[DXDWAV@U>S>S=R@U@U@TAVCXCXDYBWCXCXCYCXDVCVCVEYGXGXHYHYHYHYGXHYHYEYDXDXDZDZDZCXG[FZFZFZG[EZCXEZEZEZDYF[F[H[J[IZJ[K\JYK\J[H\J[H[FZG[G[G[F[G[JZK[J[JZG[H\H\H\G\K^L^M_I^H\H\H\K\L^L^M^N^M^K_N`M^N_L`L`K_OaNaL`MaNeMcOcNbNbP`QaPaObOcOcPdPdRbQbQbPaNcQeQfPeQfPfQfQfRgPfRgQhRgQfQfRgShTjUjTiTiRiQfQfShWjZnZnZnZn^o[o\q\n_oarbrdsctduewgygx^rUiOeNdLbLbMdNbNcJaKcLcLbJ`J_L`KaLbMcLcLaMbPcUfYjYk\n\o\q^saqararctctduevfwizizi|j}l~kdycxl~ml{dxczcybxdwcwezf{g|f{f{f{h}i~lorpkjg}h~hjmprxxux~|˄΋̎ɞη젴醣虜⭴栱푣ԊьҌՑێ׋ҍэ֘薨搡ގԍԚ盳얥ސԓؙ㡴젳윱뚱㨺못9Hh9Gi8Fi7Eg5Cf1Be1Af1?g0=f0>e.h4Aj7Ek8Fk8Fi;Jk?LkCNkALjCOlDPmGRmITnJSnLSoJRpCOmCNlBMk?Nk@Lm@Mm@Ml>Ml=LlLoLqLw>Mv>MwMy?Ny>Mx=Lw=Lw=Kx=Jw>Ly?Mz?Mz@Oz>Nx;MvPx=PwMwr+s,O>P?R@RAQBRAS>RS=S>S>R?UAUASCWEWFXBVCWCYDZCYCZDYFZFYHZFZIZGYCW@U>S>S=R=R>S>S>SAS@UBWBWAVBWCVFXDWDWDWDWEXFYEWGXGXIZHYIZHZHYFXFZDXDXBXCYCXBWBWFYEYEYFZDXCXBWCXCXCXEZEZFXHYHYIZIYJYJ[J\I\I\G[FZG[GZG[GZG[G[G[H[H\H\G[G[H\G\J\M_M_J^H[H\K]K\L]M]O_O_L^M^N_M^M^K_L`L`M^M`LaMaKaLaMaMaNbObOaOaMaMdNcOcOdQaSdRcQbRcQaPePfQgShRgRgQfQfRiRhRiQfQfRgSiUiUjRgOfNeOdRhUjWiXmZn[o[o[m[p]p_p`qbseueuewewhzi{hzZmRfK`LbJ`K`LaMbLaKbJbLcKbJ`I_K`KaMcMbMdLaLaObScWgYjZk[m]o]o`q`q`qarbsbsdufwhygzgzh|l~mg|ezi~i|dwbvaxbwavbwcxbwcxdyf{f{ezf{h}kmoqqj~i~i}klnqrvxvz€Ɓǁǃ̈́ˆʉʋѐӌӒݑ烗y҆እ숦쟪ފû靥␟鑥쓩鎨ዙ؇Նц̈́ʁɅ͇Έ͉ψЉҌӊЍ΋͉ю؏،֐ٍڏ؎ҏӒؒܔᙫ稶줳갾;Ji;Jk:Ij8Gg7Fg5Df2Ad3@f2@e0>c/=a0>c0>c2?g/=f1>i1?h3Ai7Ej9Gi9Hi:Ij>Kj@JhALjEOmIUoMWpJVkFQfHPkKRpCOmCNlBMl?Mk@Mm@Mm@Ll>Nl>Ml=MmLq?Mo>Lo=KnLy=Lw>Nx=LwLyLxMw>Ox;Mwt.?u/@v-Bv-@v/?w-Ax0Ay0Ay/Ay-Ax/D~0E~/D}0E~1D~3D|0F|3G{4H}5I~3G|3G|3G|1Ez2F{2F{3G|5I~4H}4H}6J6J5I~5I~2H~4H}1E{3F3E}1D|3F~4F7K8K6K6N9O9N=N=N?PAS@PDQCQAR=QT>T@UCVEWDVEXEVEXCXBYBXDZCYCZE[FXGWHZGYCWBV>S>SMl?Nl@Mo?LnKo=Mm=KmJw=Jw=JwK{v.?t/@v,Au/@v/@v.?u0Az/Bz-@x-@x-B{0E~.C|/C|/B}2Bz/D{2Fz3G|3G|3G|3G|2F{0Dy2F{3G|5F|4H}5I~6J7K6J7K4H}2G~3I}5I~2E~1H~3E~2F|5J~5J~4I~5J6M9O9O;N;MAQAQDRBPBR@R>P>QT?T>R=TCUEWDVDWEWHYEWEXCXCWCXEXFYGYFXIZEYBW@T=RS>T>T@UAUCVBUDWCVEWFWHYHYFZDYGYEYDWAW@UAVAVAV>S?T?UAUAVCWBWAVCXDYCXDYCXBWCXEXGXHYGXHYHYHYIYHYEXFZDXDYCXCXDYEZH[H\H\GZG[G[G\H\JZM_L^L^K\F\H^L\L]M_N^M]O^N_L]M^L]M_J^J^L`M^OaK`KaPbQbQbMbMbNbNcPcOcPcRdTdRdNdOdOdQeRdTfReQeQeQeQeSgSgSgUeTfUhSgSiShQfPeOcMdMdNeSfWkXk]m[p\p[n[p[r]rbtdshujwixhxizl~nopizYnMaH]H]I^H^I^H]I^H]G\I^I_I`KaLcLcLbKbMdMbMbOdOcUhYjXi\k^mYm^n]n]n^o`qarctgxdvezg|i~i{fzcxavbt_u`uavas`ubwbwcxcycydydzdyf{g|g|kk~nmopoqqoqsssty~Ł{uy}xsooll~lmnqqttttqtuwwxtursxzxwy{υڀ}}xvvsrtqswzxywuxy}‚}Āŀŀ‚łƅȈ΍Ъ諴ϐӑՐӐՕٕڕڗٓטբޣ⣮߯޵⩲ޤ㠮㯽ﯻ;Ik;Hk;Hk9Fh8Fi7Eh3Ce4Cf5De3Bc2@c2@c2@d2@c3Ad4Bg5Ch7Hl9Il;Hk:JlKk?Lm@Mm?Ll?Ll>Jj=JjLo>LoLm>JmIvr,=r*;r(;n(:n):n):m'8m&7o'8m'8k&8l'7i*:m)9m)9l&7m'8n&6m'6o%6n$5k%5l&7k'9k$7j"6m"7n$9p$9n&:o(8q(9o*;r+v.?w->t.>t.?u.?u.?u->t.?u0Bz/Bz-@x.A{/B|.C|,B{/By/Az0Cy/Dy3F{2Ez0Dy2Cy3Cy3Dz3D|1D{2Fz4I~4I~4I~6J5H5H5H4G4G5I4H|5I~6J6J6J7J7I7L6N9P:O:NQSASBTDUCUDWEWCWDXEWCXBVCWCWCYEYGYFXGXEW@U>QS?T?VBWBVBTGWGWCWBUATBUAV@U>S?T?T?TS@S>SBW@UAVCWCZBWBWCXCVDWEXFXGWEWEVEXEXEWDYCXBWAVAVBWBWDXDXFZDYFZH]JZK\J[M]L^K]K]L^J[J[O^M]P_O_O^N_J[L]M^N_K`K_L`LaJ_O`M^O_P`RcL`OaO`MbNaNbOcQePePdRdRdSePcReSfPeQeOdQeRfQeSgSgSgRgSiThTiQdObMcKbLcMdNeQfWkXlZn\p[p[q^r_qasdvewexgyj|k{o}om~opmeyWkK_G\G]HZF\EZF[H]F[H]I^I^I^J`I`IaLbMdNbMbPcReTfTgXiXi[lWjZmZn]o^oaoaqbscubwezf{cxawcxbwbu`s`t`u`u`uat`tbwawbwbucwcueydydyezgyj|n~lmmlnoorqruvwttmlprnmm~k|l~pnpooorqoqnoossssqqoprporrrttrnlijlmmlnprtppmpvywwxzwyz}}ƀDŽʈ͇̉̆ˇ̉̋ώҍҋҎҍѐԐؑؒٗޕߕߒ֚ؒܗܓؖיݡ;Hj;Hk7Fi7Fi6Ef6Ef8Dg6Be5De3Bc3Ad3Ad4Ce5De4Ce6Cg7Ei8Gj:Hk;IlKk?KkKm>Km?Kn=Lm=Ln:Jk>Mnt->v->w,=v.O?P>OAQ@O=O?P>ORS>S>S>U@T@U@TBTDTATAS@S>Q>Q=Q=R=R=R>Q:Q9O9O:Q;Q=R>S>S=S?S?T@U?UAVAVBWAUCVCVCVEWAUAUCXBWDWDXDYCXDXBVCWCWDXCXCVAWCXDXFZI[K[J\L^L^K]M^K[L\N]L]N`OaOaL^J[K\M^M^J_J^J^L`L`N`OaN`OaPbObPbQcNbMaOcNbNbOdReRdQcRdReQeRdTeUgSeTfQfRfQdPgQfRiRhSiThPdK`KbKbLcMdNePeThWkYmZn\p]q_q`qardvhzj|l}m~m}k{gxgyk~mml\pL`F[DYFYEZEZCXEZEZG\G]H]I]I_H_I`JaKcMaMbOcOdPePeSeWhWhXkXlZn\m^o_pbsarduawbwav_u_s_t`sbr`s`s`t`ubuat`t`uaubscuavbubvcxcxcxdyfzi|i~i~ijmopppoprpn~j|j}i}j}mj}g|gzfydwizl|m}n~mnon}j}k|j{hyg{gzgzk~mprpkjg}g{j}i~i~ki|j~g{fzcxcxdydycxdyg|ij}h{gyizf{fyk|gyj{l~kmklqttyyxyz|}ǁȀʃΆЇ͆·χψϊыҌ֍Ԋэыӎ֍ՍҐ֚ݥ㫼:Ff9Fi7Eh6Eg7Eg7Df9Cf8Cd4De4Cd4Be5Ce5Dd7Ff6Ef7Dh8Fi:Hk;Il>Jn?ImMl>Kk=Jj=Jj=Jl>Km=JkIl>Jm=In=Klq,q,v.?w,>u->v-x,>x-?y0Ay0Ay0Bx/Cx.Cx-Bw/Bw0@v1Bx0Aw2Bz0D{0Dy3G|3E{5Ey5H{6I}8L9L8K5I~4I}4J|5J4I6K6K4I5J7L9NN>O>O=N>O=O?PTT=S?S?T@T@U?T@S>R>R=RS>S>T>U@TATATBUBUAVAWCWCWCVCWBWCXEXDXCWCWDYEVCWAVAWBWBWFZFYI\I[L^M^M^K]M^N_L_M_N`L_N`L^L^M]N_J_K_K_J^K`OaOaOaPbPbQcPbPaQcNcNbQcRdPdSdRdSeSeQeReSeTfTfSeTeSgTfQfQeRfQgShSgSgSfNbKaLcLbMcMcMdQeViYmXlZnYm^o`q`qarhymk~dx\pZnZo]qcwmnmauOcF[EZEZDYDYDYDYEYFZG[H\I^H]H]J_J_J_LaLaKcMbMbOdSeTeWhWiYk]n]n]n^naq`qbratat_s`u^s\q_s`p`q_q^pataq`r`sasbtctdtbuauauavbwcxdyezdzdyg|g}hiknjj~i}k{o~l|l}j|g{fyfwgxdwawbtbtbudvfwgxiwhyhyizgxduductbs`ubvauatdwhyj{m~j{dwcvduar_s`t_s`t_s_s]qZoYnXmXlZo\q[p_q`u^r\q]q\p_s`q`q`raucxcxcxe{h{lntpoppqvxxzzƀȀȀƀDŽɆˇˇψΈ͊ΊӉЉЊАԔ؞ݨ繾跿豼9Gd9Ef:Eh8Df8Eh8Ef7Dd8Fe6Eg6De3Ae5Cf4Dd6Eg7Fg6Dh7Eh9Gl;Gm:El>HkIi>Kj>LhAMg@OjBMiBMiBPkBMj@LiALkALlALj?Jh?Jj?Jjq-=q+;n-=p+=o(:m):n(:l(;l&8j%6k%5i&6j"2h!2i!2j"2e$4h!3i!4i$1i%3j"2i#5j"4j$5k$5k$5j$6l%6l&5l*:n'8l'6n'8q'8n(9o):p+;q)=r+;r,y-Ay.@y.Aw/Cx0Cx0Dy/Cx1Bx0Aw0Aw2Cx0Ey1Ez2G|3G{4Fz7G|8H};K9I7I~5I|4I{3I{2G4I5J5K7K7L8N8M8N;O=N>NO>O>O?Q?PS=S>QS@T?SBVAUBVDVDVBVBVEWBT@T@S:P9L9K8J6K6L5I5J5J5J4I4I6K6K6K9N:O9N;P9PS=R?R?U=RHm:Ij;JjKh?Ji@Kk?Jj:Ih=Ijw->v->t+?w->v+N?Q?Q=QS>S=S;R;QR?S@SBT>R=T=SDVCW@T?SCUCUCUAUCSBT?SU?T@UAWFZGWFWGXBVAVBVAWBXDXGZIZK]I[J\L^M]M]L^M]L\O]O`O_QaNaPbN`M_J^M`N_PbQcOaOaP`R`QaRbRbPcQcQcPbNcPcRdQcScReSeSfTfSeTfShRgTfUgUgUgVhUgWiYiXiWjUkMaLaI^JaJ_MbQfSgUiXlWkXl]m\l\lZkXkYkShTiUiWlYlZl[o]p[n^n[lQeF[C[CWBWEWEXDYFZEYF[H\H\J^J^I^I\KaJaKbJaMbQ`ObOcQeUfVgXiZl]l]n^o_o`p]n^o[oZoYqZqZp\o^l]m[n\q_p^p_p`oaqar_r`tar_sau`u`uaubudwavdyeydyf{dydx`v_u`vavatds_q\m^p\r\q\n\mZkZkYjWhVgXgWgWgXgWhVfUdSbVeSdRfRgQfQfQfUiUiViZjXmWj[lYjRgSgSfVfUfSfSfQdQeNcMbM`MaNcNcReQgQfQeShTkSiWkWjXnZp[q\q\s\r_s`uavbvdwhyh|l~l~kmprtvvy}|ăDŽɄɄɈ̉ˉ͋΋͍̋ύϐӜؤީ䮿뱽䵾䳻ᶿ跾嵿근殹殻鮼鬼讼鰾@Lc:Jb:Hd:Ie:Hb9Gb:Ie9Hf:Ih6Eg6Df5Be5De5De4Be4Bf5Cf6Dj6Ci6Di7Ej8Fk8EkJkl-=m.;k0>m/=l/>n0?m0>m1?n1@n1?n.@m-?m-@n->l-?m/Ao0Cp/Ao1Bp4Cq6Dq2Bq2Cq0Bp1Do3Do0An/@n0Ao.?m/=l0>n1?o2@r2@o1@p2@q0>m1?n0>m2@q1@n.>o->q.?r,>p+;n,u+Q?Q>Q;O:P=T=RT=S@S?P@RASBT=Q=TT?T?T@UBWEXGWFWEWBWCXAVDXDYFYIZKZJ\K\J\K\K[L\K]M]L]Q_Q`P`P`NaPcPbOaOaN`OaOaOaO`PcRbQaQaRbTcRdPbPbPcQbRcSbUcScTdUfUeUfTfTfUhUhTeTfUgTfVhWiWiVhUgThTiPdL`I^J`K`LbOdPeSgThVjVjViYlUgRdRfOdPfPgQhRgUiYkXmYn[o^p_n[lNcG]EZBWEYEYDXEZFZG[FZG[H\H\H\I]H\H_I_I^L`MaK_OcOcPdUhVjYk[l]n\m]n[l]m\mZnYmYoXoYoXn[lZmYmWmZo\n_n_n_n^o]n_q\p\p\p`s^tauauasavaubu`tbubt^s]s\qavawbtat[lWgWjYmWjWgUfUfUfTdTcSaSbScSdTeQeReQaPaPaN_LaNbPdMaObPeOcQdScPaPaNaObOcNcOcQeUePdOcPdOcPdMaM`L`LaK`NaMcPePeQfQfShRhUkUkUkXnWmYo[q]r\q^s_t`tcvdwgyhzh|g|j}oqsswz~Àdžʅɇ̇Ȉ͎͊̊̋͌͋И֜ڝܤ榵鞫㪹޻举洼窴⳿嶾嵾淾汻ꮼ讻謼꫼鮼诽걾벾?Nc@Lc>Jd>Ie@Le=Ka:Jb:Je9Hd9Gf9Gf8Ee6Ef6Ee4Dd4Ce3Ae4Be2@e3Af6Di6Dj6Dh9Gj8Fi9Hh9Hg:Ii:Ih;JiJj=Ik;Hj9Hi:Ij:Hk:Gk:Ij9Hi:Ihj.=k-;j/=l/=l-=n->l.?l-?n.?m.>l1?n1>o/=l-=m,=o+=o+=o+;n)9l)9l(;l&9k'8k$5j$5i%5i'7j%5i%6j$4g$4g!2f!2e$5h#2g"/g 0g"1h!1h"1h.e#0g"/f 0f!2h!2h$5k#4j!2h#4l$4l&7l&7k%6j'8l'8n'8n&7m(9o(9p*:r*;r):p+;q-u+?t+>s.Aw0Cx2Cx3Dx5Fz5Fz6Gz5Fz7H}6G|4H{4H{5I|7I}:K9L8L6K8L9J8L~5J~5J~4H}5I~3H~5J4H~6J6J5I|5I|6J~9K;LO>P>P?Q=P=P=Q>Q@Q?R=Q=R=Q;Q;QR?S?R;QSQ=RAUAU?T>S@U@VCWFWGWFWGXEVCXDYG[G[IZJZK[KZK[K]K^K\K[L^L]N^R_S`O_RbRaSdRdOaQ`PaPaN`ScRcRcTdScTdScUeSeRdRdQcRaRbVdVcTdTdScVfVfSdSdSeUfSeTfUgUfVhVhXjTfQcQcK_L`L`I^J^K`MbMcPdSgThUiRfQeSfOePdPeOfPgNeOfQfShUhUkUkWlZn]l_oTgL`J^H\FZDYEYFYEYDXDXDXFZG[H\J[I]H]K[L^J^J_K]J_MaNbPeSgUiYjZk[l[k[l[kZlYlXlWkXmVnWnXlWkVkXmWlZm\k\lZl[m\nZnZo\o[o]nZo^r^s]p^r_q`q_q]o_o^q[o[p]s_tau\oVgUfPeRfPcP`P`MaN_O_Q`N\O_N^N`LaN`M`L`M^L]K[J^K_I]H\I]H_K_K`M^L]M^J_K`M_K_KbMcOcPaP`P`LaK_L`K_M_L`J`M`LcMbMbOdPePeQgSiSiTjWmWnXnXnYoZnYn\q]r^savcucucwdxg}k}opruwz|~|‚ĄƆȇ̊ˉʉˊ̌ύύАԔؕښ䙩㗪۹洸ᨰݩᲽ鳼륰㦲死岽沾魺䮺못骻髼ꪼ鯻诼갽鲽걾粿밾糾벿>Lf@Kd@LeAMfBLc>K^9I^:Kd:Jc:Je;Je9Hc8Ge8Gf6Ed5Dc4Ce3Cc1?b2?c4Af5Cg6Dg6Fg6Eg7Ff8Gf9Hh;JiJf>Ig?Kk>Kk?JgHf>Hf;Gg8Ee9Ff9Fg9Fh8Eg9Fi9Fi7Dg7Eg5Ci6Di5Cg2Bf/Bk/Ai1Al1@k.?h.m->l/?n0@l0Bm0Am.>j/=j/l,=k,=k,>k+=j/=l/P@R?Q@PAS@R@Q=PROM=P>QS@SAUBVAUBVAUEWGYHWGXFWGWEYFYH\G\K]K\K[K[L]M_J\J\L[J\L^M\Q_R`R`O_QaScRdOaN`QbQ`SbTcSdRdTdUeSdUdVdSeSeSeRdQcSeSdTdTcTeTeVeTeTeSeRfUgSeTeVfTfVhXjXjRdPbPaI]H\I]J^J^L_LaMaOcQeRfRfQeRfPcOdNcPdPfOgNeMdNdPePfRgTiUjWkXlZmWjRcMaL_H\EYGWFVEXBWEYDXDXDYIYIYK\J^J\M]L\L]L\L]M_K_MaPdSgVfWhZjWkVkVjWkVjVjUkTlUjUkXlWkVjVkXkYhYjUiUhWlWlYm[mYmYmXmXmYmYmZnYnZl[lXjVjXl[k[m\oYnYo\pWkXkZnWlPeMaL^M^I]K\I\K]H[K[J[K[G\L]L\I]H\I[J[G\K\J\I[I[E\F[G[H\H\H\K\K]I[J^J_L^I^M^L]L\G\I_H_H]KZI^J_L_KbMbMaOaNbPePfSiSiRhTkUlUmXnXnXkVlXm[p\r_s_qat_rbwdye{i{mpsvuywy{}ÀĆʇˉ͊ΌΌϊωч̋ЍՎגؒ᛫ܝޝܓܕڗޒ۔ݡᬹ箼笹誸婷騺穻窺歺筺筻譻论谻ꮼ鰽氼鱾갾챾겼泿巿綿趿綿涾絾湿?If?JeAMf?Id@Mc?L_@Ka@Ld@MdBMeANd=K`:Ib9Id8He6Fb4Cb3Bb2Bb2Ab5Cf4Bf7Dg7Dg7Fg6Ed7Fe8Gd8Gd;Ig;Jg=Jf=Je=Kg?KjAKjALj?Kk;Ig;Jgi.;h-:g,9g,9f+8e*7d*7c+8e)6c+8e+8e+9e*7d*7d+8e*7d(6c(9e):f*;j*;i+k,=j+l.k.=j.@l,=l+k+O;MS@T@UBUDWBVDXGXHYHWIXGZGYIYHXEZF[K]J\K[J[L^I\J^HZJ[KZIZK[K[P\P^UcTbPaRcRdQcOaPbOaQbRdScScScWeUdVfUeRgUgSeRdQcQcRdTeTeSeRcSeSeTfSeTgUfTeUgVhVjVhXjUfN`K_I]J^J\I]J^J^I_K`MaQaOdOdNbL_MbK`MbLbMdMbMcOcLdMcOcOdQfTiUiViSiViReL_L_K_G[FZDXDXDXDXCWCWDXEYEZHXHYJ]N`L^M_M^L\L^J^K_L`NbPdRdUeVfVhUgThRgRgPeSeRgShThUjWkUhUiUhWiWiUhRgSgUiUiUhTiTiSiThUiSgThTgUhUgUeVfUgUgWiYjVjSgSiVhZk[lWjScMbK\J[K[J[IZHZIZIZIZK]J\L^H\G\G\I\HZJ\GYI[FZEZF[DYFZG[F\K]K\K^H]J^H]L]K^K\K\K]K]J\L^J_J^J^K_L`MaL`K_LaO`OcPgPgPgPfTjSkUjVlVlVkTjTiUjZnYo`r_r]s]ravcvfwg{mnqstvwyy|ŀƃǃȅʄɄ˅΄΅΄ʉщՍّݓڗۖݑ׌֌Ռ֌֗۟ߨ㨶樵娵娶娶婸榸⫸諹筻譺箺ꮻ꯼尻篽꯼谼岺ಽ屽屽浼贽贼䵽䶿岿泿벽沾䵾㵾:Gf:Hd=Ie?Ie?Ke@Lb@LbDOeCOeCQdAL_BL_=N_;Ka:Ja7F^7G_3D^5Da5Dc6Dc7Ee7Ed8Ed8Fe8Ge7Fb8Gd:Hf;IeMg?Lh?Jh>Ig=Kh;Ig;Jf:Hf9Fg:Gi9Fh9Fh9Fh8Eg9Fh9Fi:Ff7Fc8Ib9Gd:He8Fc6Db6Cc7Cd8Ae8Af8Ae6Ae3Ad4Be3Ac3Ad5Ch4Bf1Ac.@e/Ag,?e-=f-;g,9f-:f,9f+:f*9e(7c(7c)6c(5b)6c(5b)6c)6c)5c(5c'6c&6b&8d'8f):h'9f):h*;i'9i(9i):i(9g):h(9g*;i,:g.t/@v/@v2Ay4Cx4Dx7G{8H|5Dz2Bw0Cw5Fz5Ey6Fz6H|5J6H~5I~5I~8I7J5I~5I~6J6J|5I|5I|4H{5I|4I~3H}7K~:M:KO=MP?R>Q@R?Q@R?QN?OS=R=S>S@U?UCVCWEYEYEYEWI[I[GYI[J\HYDYG[K]K]K[L]L^J\K]K\JZJZHYHYJZN\N\Q_SaRaRbScQbP`RbRcQcRbRbTdScSfTfTdScRdTfTfTfRdRdRdRdSdRdRdRdSfTeUgVfXfUfVhXjWiZlWiQcK`J^K_K\J[J[J]I\I\I^I]K_K_J_J^J^J]I]J^J_K`K`K`LbKbLbLaLaOdPeQfQeNcNcNbJ]J^I^H\DXFZEYDYCXBWDXCWDXEYGYGXHZK\K]K\L]K^M^L`J_L`J_N`RdRbQcPeQeQePeNdPePePfQfRfSgSgSgSgUeUeRgShQfPeRgQePdRfRgRgRfSgSgSgQeRfSgRfQeUgUfVgUeReMbQeSfTeVgSeOcK_K_K[HZIYIYIZIZGXHZHZHZK]L^HZHYHZGZHZGYFXIZCYEXDYEZCYDZHYIYH[J\I[IZF[I]H[GZI\I[K]J\H^K]K]L]K_M`J`J`L_QbQdOcNcMcOePfRiTjSiShSgRfQfShUjUlZm[l\o\q]s`scvfyj}mopsuuyyy}ÁŁŃȃȁɃʄ˄˅ˆˈ̇ҋӋԍՒؑؑ؍،׍בךݣߣ㧷槶姵䧴㧵䦵䣱শ䫸嬹孻欻筺鯺⳺导诼读尼䮺宼㱽粽豼尼䱽ⱽ氽尽걾갽谽䱽ⱽ氾7Fh9Hg9GgHe?JcALeBNeCOeDPcEPbBPa>M\?M^?M]Ie=Jhf.;f-:e,9e,t/?v2Ax0?w1@w5E{6Ey5Fz4Dw2Cv4Ey4Ey4Ey5Fz4H{3G|4H}4H}5I~6J6J4H}4H}5I}5I~7K7K8K~4I|4I~3I}7J}N?O@P>N=MPOHf>He=HfLfBNh@Kg>Mg?Mh>Lg=Lf?Mg=Ke?Lf>Ic?Jf=Hf;If9Ge9Gg7Fe8Ee5Cb5Bb3Aa4Aa4Ab5Bd6Dd5Ee6Fh6Ce6Bb5Bb4@a4@b4Bd3Ac4Ac4Ac3Ab4@b2?b2@c2@c1?b1>a1>b/@a/=a0>c-=d.;e.;e-:d,9c+9c*9d)8c(7c*9d,8d)6b(5`)6b(5a(6`'6b'7b$6b$5a(5b'5c&5d%6d$6c%7f$7g&8g'8f%6d%6d%6c%7b*7d(7d%6b$6b)8e*7d+8h):h'9g(9g)9g(9f&6c&7f"6d#5c#5c#5b!3b!3d"5d!2c$2c#0b#0`"0a"/a/`"0a!.a".c!.b -a!.b!.b#0d#0c!-a!.a /b"0e/c/c/b!0c/c.b/c!/d".d!-c"-b"0d!1d/b#0e#1e#0f$1f%2f&2h#3g"3f%5i'7k$4h#5i&7k&7k'8n(9o(9o+;r-u2Bu1@v3Ay5Ex5Ey5Ew3Cw4Dx3Dx2Cw2F{1F{1F|5I~3G|4H}5I~4H}5I~5G|6J|6J7K7K5J|3I{2G|3H}7K}=M?L=M?O?O?O>NP=N9M:N9M:M9M:N9N8N;MS?T?S@V=TS>T>RCTCTDTCTCTCUATBUBTDUBUGYGXCXCWHYHZIYJYHYFZJ[I[IZI\J\IYHYJ[I[L\L\I\L[J[IZJZK\M\P^O]P^O^P`PbTaP`OaMaL_N_N_ObP_Q`TbS`TcSfTfWdTeRcSeSgSeSeSeTdYfYgVeSeReRfTfUfVhTfM`I]I]G[I[IZHYGYEYDYAW@WBZEYDYDYCXDVAXDYF[F[GYG[G\H_I\I]K_K^I_I^K_I\H[H[I\J[H\EYFZEYBWBWCXBVAVBVCXFWGXHYHYJYJ]K]K]M_K\L\J[L\K_N`OaN`Q`PbNaPaMbMbMcLbKbMdMcPaNcNbLaNcNbMaMaObLbNbOcNbMbN`O`O`M^L^K`LaLaL`MbMdQcPcNbL`I^J_LaJ`L^O`O`L`J]H\HZGXEWGXHXIYIZJZGZGYGYHZGXGWGWFWFXHYHZHZHZHZD[FYHYGYFXFXHXHXGXFXEWCYGWGXGXHYDZE[F[K\K\K]K\H^I_J`KaLbOeOdReSdUePfNdQfQgQhRfRfRdQeOdQfQfQhSiVjWjYp\q`sexg{j|m~npqtvxy|{|~~Âȅˆ͌ϋЌӉӅ͇͊Ӎ֌ӌяЉ͈ΈΈωьғؗ۝ޟޥਵ⩶㪷䧸䭹㰹ᬹ䫷嫸ꭺ孹㬺㭼ⱼ䳹孻⯺ᱺ௼ᮼᮼᬼ竹諹欻歼譼读讽⯽䰽4Cf4Dd5De6Ef7Df8Eg:Gj9Fg8Ef7FcLg?Mh>LgIe>Iea3A`2A`4?b2>a1?a2?a2?a3@a2@a1Ac0?b1>a0?a/>a0>a1>a-;_-;_-s.>t0@w0Aw1Bv1Bv2Bv3Bw2Dv2Cw2Cy1Cz0Ey1Ez3G|3G|4H}4H}4H}4H}4H}5J~7J9J~5J3H~2G}2G}4H{:L8I}9I};L=NQ?PLe=Kf=JfLf;Id;Id;Hd>Je;Id;Je_1>`2?]2A]4B_2A^3@_2@]1>^2?b/?`/>_.=_.<_.=^-;_.v.>u.?u1Av1Au0@t0At1Av.?u2Bz0Cy0Dy0Dy/Cx2F{3G|2F{2F|4H}5G}8H~9J}5J}4H{3H|2G{4H{7I}8I|9I~P?Q;O:N;O@S>P>P=OTAVEWEWDVFVGVGWHWEXFXEVDUDUDVFXEWCVGYFXGYHZGYEX@VAUAVCWBVBVCWCXFXDWEVFXFWFXEYEYGXHZIYK[O\O]O^M^M`OaQbQcQbPbPaSbSbVdWeTdTeUfYgXfXfVgXfXeTfTeUgWhYfZhYgUeN`J^H\I\I\I]J^G[F[CXCXAVCWCWDWAW>V>U=T>U?S=U=R>T>S=R=RS@U@UAU@SDUFVGXEZIYHZK]K]K]J\K]J[K\J[J[L]M_N`N`M`M_M]N`K^J^I^J^I]I^M_L^L_I\I]I]I]K_I_JaJ^N^M^O_L]IZHYHYIZIZJ\M_M^L^J\I[H[K]I\H[I\JYHZGYGYHZHZIZK]K]JZHWKWJVIWKXIXFXFXEWFXGYGWFWFWFXIZJ\J\K]L^J\K\J[IZH[H[HYFXEWEWGXGXEWHZK\I^J_H]J_L_K\L]K]L^LaNbQdQcRdSeUgUgVkTjTjViSfSfUfTjUjVkVkUjVlXnZpZp[q^sdueyj}i}opsttwyy{~‡ɉ͎ѓ֓ךۚܛߗڗڛܘۘ٠ݒԍˌΊЈЇ̊ΊАђӒӛ٥ࢰާڱ۳ۮߪ㩷䨶㧵⨷檸嬹⮹⮼᰺ᮺ߯⭻୻㭹殹䮸䭹௻᯻⭼௻᰻ᱽ5Df5De4Cd7Eg6Ff8Gi7Ei8Gj8Fi7Fe7Fb:He:Hf:Hd>JeIe;HdLdAMcALb>Kc>Jd;Hc:Hd8F`8Eb7Ca5Ba4B_3A`4A`3@^2@]1?\2@_3@`1?]2@\3A^1@\0?[1A]0?].=\.=Z.s.>t/>u0>u/@v/Aw.Bw/Cx/Ez1Ez2Fy2Fy2Fy3Gy6G|7H~5H{2Gy3Gz3Gy3Gz4Fy6G}8I9HM>N?P?Q>P:MQ?Q@Q>PSAVBVBVDXAUBVAVCWCWDXCWCXFVG[GYIYN\O]M\N_M_N`O_SaRbSaSaUcUcWeWeUfVfVhXfXfXfXfXeWhVhVeVhWfYgYfSbQcI[EYFZGZG[H\H[F]DXBXBYAVAVAVAV@V>U=U?VSS=U?T>S@T@U@UAVAVCW@U@UBUBXAV?T@U@T@T?T@U>S=RS>S>RAS@TEUEZEYDXEXEXK\K]K]K\J[HYIZIZM^N^M_L^L^L^L\L]I\G\J\I]G[J[K]K]L^M^I\H\I]H\G\G\G\L^J]I[K\HZGYIZHYHYJ[I[I[GZGYHZHZJ\K]K]J\JZGYGYHZJ\M^K]J\I[HXKWJWJWKYJXIWGXEYFWDVGYHYFWGXHYL]J[J\J\J\J[K]IZHYI[J\I[GYGYFXFXIYI[I\L^M_MbK`LbN`M_M^L^M^N`OaRcReSeTfUgVgShVjWkWkXj[l\kZpZp[q[q[qZp]s]t]s]raufwh{mnqquwxz|~ÄLJʍ͑ї՜ؙٝܣࡲߠܡݦ壱ڜԚե؝ՒΏώϐєӜ֢ען٠٦ޫٳܳ߭ષ⪷⩷㪸㩷䬹寺᯹᯻᰽⮺௻ⱼ⯻㮺⯻౺஻ޮޮ୼᰽ᵽ䶽3Ba5Dc3Bc6De7Ff7Eh7Dh8Fi7Fe7Ff8Gf8Fg;Gg9Gd:If:Ie8Hd:Jc:If:If9Jc9Jf9Kg:Jf9He9Ge;Id;Hc:He9He9Hf:He;He;Id;Id:He9JfJbBNdEOeEObALc@Kc>I`=IaY0>Z/=[.p,>r->t-;s-;s.=t-t+@t,Av/Dy.Cy1Ex1Fy1Gy1Fx1Ex1Ez3Gz3Gz3Gz3Hy4G{3Ey6G}6H}6G~9E:FS>T?T?T@VAUBVBV@T@TBVBVAUBWEXFWBWH[HYMZP]L\L_J`M_N^SaR`SaScSeRcTdSeTeTfVfXeXfYgZgWgVhVgWhTeQaTbQaL_H\GWDYEZDYDZH[FZEZCXCYAXAWBVAWBWBVAX@U=U=S=S;Q=R=R;PR?T>S@U?T@T=T?V@T>S>RGZ?I^=Lc@Lc=Ia;Ga=G^X.p-=o,>p.@r,=p*;o(=q.t,=s.?u.Av-Cu/Ex1Dv4Fx1Ex0Dy0Dz2F{3Fy1Fx2Hz1Gy2Fy4E{7F7G7I8G9GQ?R>S@T>T>T@UAWAWAUBTBVCWDVEWDXEWFWEVAU@TEWDVEXDVFXFXFXFZCYBV>S=R=R>S>S@U@TBVAU@TAUAUBVFWFXFXDZEXHZIZJYP]M]K]N`O_S`S`RbRaRbSbSeWdVfTfUeUeWfYfXfYgYhWgVgQdI\J[K]GZDXBWAYBWBWDXCYBWBWBWBWAV?UAWCWBWBWCXBW?U>S=R>SU=T>V?V=T>V>S>S=RQ;P;PJc@KaHOdFM^HOaDK^CJ^BI_CK^BK]DM]FP_DPaBK_CH^DI_FL`GNaCI]ELaEM`EM_FM`FM^GN^FO]DO]DP]EM^GN`IQbIQdAK\FO`CL^?JY;FW7DW6DV8BT8BV6AW1?U3>Y.<[+;Z,;]+;\(:Z+;])9Z(8Z&7Y%4V%4V$2W#1X$2Y#1X!1X!0Y!0[!0[!0[ 0[ /[.Y .Z!0[ /Y!0[ -Y ,X,X+X,W+W,X+W+X*W+X,Y+X,Y,Y,Z!.[.Z-Z-Z,Y-\!-^!-\,\!,\!,]-\!0_!/^".`!-_"/`$0c".`".^"0_"1`&4e&4f%3f%3e'5f'5e(6g(5h%5h$5g&6i(8k)9l(;m*:n(8k)9m'8l)9l'8l):m*:m);m*:m)9m(:m*;o*;o);n+p->q/>q.>q->r+>q+=p,=q,=r,>s.?u-=t.?u/@u/Bu0Dw0Dv2Dv2Fx0Dw.Bv0Dx2Gy2Fy3Gz2Fy4G}6E|:I8G}8I}:I}L;I=M=M;M;L=N?P?Q>P?Q?Q>P9M:N:N7M5K|4J|6K}7H6K}7K~7K~7K6J6J6J~:M:J~9J~9J~8I8J;K9H8H8I9K9M9K8K7L7L8L:N:K:K8L7K~9M9N:Q:O9N:O9M8L8L8M9N6K4J~4J~4I~6K7M~7L7L8M9N9N=Q=Q@P?P?Q?S>T>U>T?U@V@V@TBTDVBTDWEVEXBVAUBVAUAUBVAUCWEXFYGYCXBVCW>S=R=R?T?T@T@T@T@TAU@U@T@UBVEWDXBWCUGXH[J]L[N]N]M_M_O_P`Q_S`SaTaRcTcUfUgVgVgUdYfXdYgXhUfUeRbJ\I[FZCXAV?T?T>S@U>S?S@U?T>S>R@T>S?T@U@UAV@UAVAV?TS=SS>S=R=R>S=R=RR;PS@TAUBVDUDTDUFWFVEVEVGWGWFVGWFWDUDUDUCUBWCWCWBVDWHWGXFXEUDUCWCWDXCWCWDXCVDVDVEWFXFXBVCVCTDUDWFXFWEWDWEXGYHZHYF\F[HZHZHZHZE[F\G\HYFXEWGVITHTEVEVFWEVEWCVCVDWEXFXFXHYK]J]L]M^L]M^J\OaNbNcMbPaLaJ\L_K`PdPbOaOaPbM_PbRcQbRdWiViPeNbNcPeUkXmYnUjTiUjYn\mZlYlWlVlWmUkSjVkVlUlUlZq]uavexfxixj{l}qtuwz}ѺӶ֬ؤئܦݥߡܢٞםؚٞٛגא֏ՌЌ͐ΟѥҵڸѲղֵԶ׶ۭۭܰܫ۩ܩܫݭ٬ީ٪ޭ᯷ᱻ⯸߮߰߱ᱽ᯼߱䱻഼ാ޷޷޷3Db2C`4Cc4Cd4Cd6Ef6Ef5De6Ef5De6Ee5Dd6Fd7Hf7He6Gc6Hd9He9He9He9If9Jf7Hd9Ge8Ge9Ic9Gb:Hc>Lf=Kf=Ke9Hc9Gc:Hd:Hf;If;Hc9Gb9GbGY=EY7C[3AY1>X0>X,q1>s/t/?u1Au1Bv1Dv4Fx4Ew1Dw/Dw/Cv1Gy3Gz4H{5G{3E|7H|9I|9I|;M=L=MNP?Q?P?P>N9N9M8L~4K}4J~3I|5I|5I|6K}6J}7K6I6J7K7K8M7K~8J~8I~7H8I9I8H~7H8H~7H~9I9I7J8L7K7K8L;M;K:K6K~7K9M:N:P:O9P9M9M:N:N:N9L9N7L4I~6K9N9N6K8M8M:O9O=PAQAQ>P>R?U=T?U?U>U@VAT@SCUEWDUDTCT@T@UAUAUBV@TAVAUAUCWDWCWCWBW?T=R>S?T?T?T@UBTASBSBTAR@UBSCXBVAUAUDVGZG[I[KZO\K\M_M^N_O]O]Q_R`PbPaQcSdTeQbRcTbTcUdUgPaN]K\GYBVAWAV?T>S?T?T=R>Q=P=P=R;P=O>QS?T>S?T?T>S>R>QS=RS?SAUAU@TBTDUFVFUDUDUDUBTAVATAU@T@T?SDUDVDVBT?SASBRARAS?S@SEWBWDVEWHYIZF[H\I[HZHYI[H[I\HZI[FXHZHWJVIUFWDUFWFXGZEXDXEXHZJ]L_M`NbOeOcQcPbTfUgYkZoWl[pbs[pWlVkYoYo`s]oVhUgSeQbPbRcUfYnZpWlUjPfPgWmYnXmYnVkWlYo\o]lYmYnWnYoZoYlWlVlVkUkVm\qdxhzhylykzo~qruwvz~˯Ϻضש֧ا٪۩ۧݦޤݦަޟؗҒяВϕϝҡПӡҪѺ߾չжԹعڹ۷۶ڵ۱ۮܰ߳װݱۮܭ۰ܳݱ۱߲Ჺ᯺޲ᱺ᰻಺ܳٷܷݶ1Ab1Ba3Aa2Ab4Cd4Cd5De6Ef5Dd5Dc6Ec6Ed6Ed7Gf5Fb8Id6Gc9Gd7Eb9He9Gd9He8Gd9He7Fd9Hc9Gb9Gb:Hc9Gb;Id:Gc:Gb:Hc:HdY-=W.q/?r/@s0@t0@t-Bu.?s0?u.?v->t->t.@v/Bu0Ex2Dw3Ew4Fx5Fz2Fy0Dw0Dw2Fx3Gz5H{3H{5Fz4Ez7H|8I{:K~;K:K;L=N=O>N;L=N;L;L;M:N:M9M9M9M6K5J4H}4Gz3Fy3I{8H}6I|4H|5I~6I5I6J~5K}7M5K}5I|6H8H}7H|4G{4I~4H}7J7G~7H}7H}7I~6K7K7K8J8I7H~8H8K7K7K8L8L9M9M8L:N9M9L:N:O6K6K6K7L6K8L7L8M9N;P;O?Q=PS=R=S=S:RS=R=Q=S;Q;QSN=Q>R?S>S?S>R?S=Q>R>S>PETBTBSAR=R@P>P=R>R?S>R>SBSCRCTCTBSBRAQ?T?S?S>R@RAQ?PARBR@Q=R?Q?O?P>O=R=SARCVEWFUHWGYIZI[HZIZJ[I[J\J\J\I[K]L\L\K[HZHYGXGZJ\O`MaNbM_UfZnZm`r^r]s`s_p`rbtfxi{e{byg|l}if{dyg|e|dtiuar_pYkTfLcNbQf\mar\rZoWlWkXmZm[mXmYnZoYo\pZoZnWmXn[q_qaq]p]oYmXlXn\pbvhzk|l}l~qtvxz}ėʭڵ⩶۪֫ܩܨڦئ٧إ֧֪ةܟգհۯخ۱ܳ߳ⰿᵿിݾټڻٻ׿ܼںڹܶڸض۶ڴ۳۵۶ܳ۳ܴᲺ౼޵ܰݯ۵ܷܳݸݸ.B`2Ca0@`3Ac4Bd4Cd3Ce3Bd5Be5Dd5Dc5Dd5Dc3Ec4Fd4Fd7Hd6Gc8Gd7Ec8Ie7Gd7Gd8Gd8Hb8Hc9Ic:Hb;Id;Id;Id:Jd;Ke:Ic=Ke>Kh>LgBOgEReEReDPaGQcKSeU\oT[kY_o[aoT[gZapdjwqxszv{|v|y~tx~swyyy~nteluahtgkxgjvnp~vxgrvUajOZbLW_JS_IN[FJ\FO_@K\;H\8F[8F[4D\3A\2?[0=\.=[,;Z)8X+8Y+8[(7W(6Z)7[&4Y'4Y%3Y&3X%3X&4Y$2W!1V /V!/Y!1Y!0W .V*V,W-V,V*T+U*W+W*V*V+U,W+W*W+X*V*V*V)U(U)W)W)W +Y*X )[)Y *Z ,[ +\,] /^ .] /^-`.a"0a!/^#0`#1`$3a$2a#1_%3c&4c&4e&6f$4f$6h$6i&9k&8j'9k):m)8k+9m,;o*;n):n):n+;o-=p.>q-=p-p/r/@t.Ar.?s.?s+?r->q.?s.>s0@v1Bv1Bw3Ex4Ey4Dw4Ey5F|2Cy/Cy1Ey3Fy2Ex3I{4H{5Fz6Gz6Hz8I}7H~8I:K;L;L:K:K~;LT?W@W@V@V@TCS@S>T@S@T@T?S@TAU?T@T@T@SBRBSBUAV@V@T=Q>Q?TR?SDTIYJZL\GXI[G[FZFXGYGYHZGZJ[GYHZJZGYK\O[N\M[N\K\K[K]K]J\I[J\K]I[J\J\J\J\GYAU?U@U=T;PQ;O:O:O:O;P:OO@Q>N=RR=Q@P>O?P;N;OLg@MfDOgDPhDPgFReHRfISgISeJRdNVfU\kW^kY_o`esqux||y~w}v{uyvzz~w|ux}~ltremmaklWahQZbJV`GU_CR[BP\@N]=H\X0>Y0>[.q.>p->r):n*;n*;p.T=T?S?S?R=S>S?S?S>R?S@T?S>S?UAU@SCT?S?S>SR>S?U>T=S?U@TDWDTGWHXHYHYHYH[HYGYHZI[IZHZGYGXGXH[HZKZL[L[JZI[JZJ\K]L]L^K]I[GYI[I[H[FYAUAVAW?UP;P;O=N=N;L;L:K;L;L8K~9M8M:N:PR?SCTDUEUGYGXIZHYKYN\L^K`LaOaRcTfUgUgXjYlYk\nbuevdvg}i~l~mnsqool~g{h|g{f|ji~jj~kii~j~h|j}j|fxbt_q_p]nXnWmXnWkXjWkWkWlZm\n\nYl\m[m]o^rbvi~lgyex_u`tcsbrdr^rarasfwj|l~psuwy{{}ƍǗͪݬఽ⳽ݰ೽ݲݴ߳޵඾ߴ߶ݹ޴ݶپܽܽṽ۹ݺݻݺ޸ܷ޸޷ൾ޵ݷݶܷݺ2Ca4Cb3Ba4Cc4Cb5Da5Dc5Dc4Cb5Dc6Ec8Fc7Eb6Eb8Gd8Gd8Gd7Fc8Gd9HeKf?Mg=Lf@MgCNfCOeGRdGRdHScMUhPXkQXkV]oZ`qchv[ap\ct`dvz|}{}w{huu_nvWenVblP^iHVbER_CO\BO^>K^=J^9F]9E_6E[5C]2B\1@[1?\1>[.=Y.=Z-<\,;[+9Z'7X'7Y'6Y'7W&5X(6Z%5Y&6X'4X'5Z%3W$2Y#1X#0Y!.X/W.W,U-V-W+T)T*U)V'R(Q )Q*T*S*T)V)W)X *[ *Z,Z,Z,[ ,[-\!-]!,\ -Z .]!/^-\!/^"0_"0_!/^!/^"0_$2a%3a%3b$3d$2d$4f#2e!2g%6i&6i&6i)8k'7j'7j)9k(9l'7j(8l+8m*7k)9l,t0Av/@t/@u3Ay1Bx0Aw0Aw3Cw2Fy0Ex2Fy3Gz6G|7H~7H4H}4F|6G{6Iz9I}8I}8I7I~7J8I:K:L9K8L7K7K7K8H:J:K8H~7H~9J7H~5I{4H{4H}1Ez1Fz2G}2G1F}3Gz4J|6J6G}7H~3D{1E|3H|3G|3G|2F{4F|5G}8I6G}6H~4H}5I~3G|5F|2G{3G|4H}2F{2F{4H}3G|3F~2G}1G{2G{4H}3G|3G|5I~5J~4I6K5J6K7L7K9M7K8L9M9M9M:NR=Q>T=S=Q>R>R?S?S?S?W>T>U?S?S@T?T>S>T>R?T@WAWAWAW>SAUCTDUDUFVFVEXFZHZGYGYEZEWFXGYGWGYI[H[J[I[HZI[GZGXGYGZHZJ\I[HZGYDXDXDX?T>T>T?V@V=S;R9P8O8M7L8M8M8M:O9N8M8M:O;P:O:N8M8M:N9P9O9M;N:M8M9N7L7L7L8M9O9O9N:P;T:P9N9N;P:R8O7N7L8M:O;P9O7M8M6K7L6K6L8N9N6K6K7L6N6K6K7L6K6K7K6K6K6K6K6K7L6J5J8L9M8L9M9M6M~5I|5I|5J6J~5L}4K}3J}6L~7L7L~6K~7K~6L7L6K9J:K:K;L;L9K:J~9J~8I}9I~7J}7K}7N~8M8N9O:ORBTBTCVDTFWGXJWMZL]MaNbPcUgYkYkZl^q`tbtdvfzhzj|l}l~l~mpuqpqojkkmpmlk~inlm~j}k|j}k}l~izhyfxcwct]pZlXjZl]n_q[q\r]r]s\pYoZo`ve{g|nrplh}evcvbr_tcuexk{n}qsw{{}~ĉʼnƔ͓О֪ڰ߱౼߲ᴿߵ߶߸߸޻߷ߴ߷۾ۼ޼޼ݼ޼ܻܺ޼޺฽߸ߵݸܷݷݷ1@_2A`3B_4C`5Dc4Cb4Bb5Bc7A`5C`7Eb8Fc4Da6Eb7Fc8Fc8Gd9He9He9IfNf?Mg@MgAMh@Ng@PfBPhEPgERdHTeKWhMXiS[nV]p[atcg{ch{^ev^fy^eyfi}vvľýhtx_itYbkT_gPYfHRaERaBO_BO]@M_t,=q->r1?w/@v/@v1Ax0Cy2Fz0Dw2Dx4Ey3Dx4E{6G}3G|4H}5F|7H|:I|7H|7H~4I~5H8I8I9I7L7K7K8M7K;J;J:J8I6J7J7H~6Fz4Gz2G|2F{0Ez0E{2F|4H|3Gz3I}3H}3F}2F{0C{3C{5F{3G|1F{3G|3G{2G|6G}5G}4I~4H}3G|2F{4E{1G|3G|3G|2F{2F{2F{2Ez3E}0F}3F4H3G|2F{3G|4H}3G|5H}4I~3H}4I~5J7L7K8L~8M9M9M9M:N;O:N;O=P;R;RT=Q=Q>R?S?S?T>U?S?S@T@U?U?T?TATAT@VBXAX?U>S@T@SBRBSBVAU@TCWFVFVDV@T@TAUBTEWFYHZIZDXEXCVDTATBSCVCXFXEVCUEWCUBVAT@U=S?T=R=R>S=S;S9P7O7L6K7L7L6K7L7L5J6K8M7L6K5L6L6K8M6K6K7K5K6K7L6K7K6K7L7L8M8N8N9O:Q;N8L7N7N8M6M6L6J8M9M9M7M5K8M6K6K6K5M6M7K5J6J5J6L5J3H4I~2H|5H|3Jz3I|2G5J~5I}5I~4H}4H~5I}4H|4H|5I{5I|7K~5I|4Hy4Ey5Gz3Gz3J{3I{2Hz4J|3I{3I{3I{3I{3Jz1Fy4F{7G{6G{9J~;M:L}8I~7I}8L6K~7K}5K|6L~6L~8K~8N8M:N9M:NKg=KeNg?NhCOgDOfCOgEQkBRhBPdCReIVjJUhJUgMUhPXkRZmV]pV\oV`pV^qY`s[`tej}oqxyy~~źģľƽǿüĤåǨƾƦĢſ{qwxrw}ouxmvvktvqwwnvxkvydnvYdqM[eFS`CP^BO^CL\@L\=J[>K^=I\:FZ7FX3BW3BZ1@Y/@[0?].>[-\.>`.=`+:]-;^,;]+:]+9\)7\)7[(6Z'4X&4W)3Y)1Z)0Y'2Z$1Y!/V".U",T+S+T +V+V-X.Y,Z-[!.]!-^!-^!/] .\ .^ -].\!-]!-],[ .]-\ ,\!-] .^ .`"0`"0_#1`!/b/c 0c0d 1e#1e$1e"2c&3f'4h'4i(5i(6i+8l+7k*9l*;n(9m'8l'9k(9n)9o):p);o+r->s->t->t.?u-Bw/Dx-Au/@t2Aw0Aw0Ez4E{3Ez4E{4F|8G|6G}5H}4H~4J7H~9G9J7K6J7K8L7K:J7H7J9J8J}6G{5F|4Dw5Ez2Fz0Fx0Fz1Gy0Fx1Gy2Hz3G|3E|1C|0Ay3D|3D{2C{3Dz2F{2F}/D|2F{3G|5H}4H}3G|3G|2F{2F{1Ez2F{2F{2F{3H}4F|4E|4D~2F}4F2F|2F{3H}3H}5I~5I}3H}3G|3G|4H~4I~6J7L:I:J7K8L7K7K8M9M:O:P9N;O;Q;Q;Q=SR>R>R?S?S@T@TBWAU@T@TAUAT?U?U@U>T@UAU?R>Q?R?R@T@TAUCSCTCSAT@U?T?T@TCWCVAUBWDWCUCSCSCTAR@U@T?U?T?SAS@T@T?T?T=R=R=Q=R>T;Q8O6N5M7L6K7L6L7L5K4K6K5J5J4I5J5I4I4H3I2G~3H4I5J7K6K4J6K3J5L7L7L6J6K8L6K5J5L5M6L3I5J5J7L6K6K7M7L6K6K5J5J6L5J5J4I4H5J2G~3H2G~1F~1F{2G|2H{2F{2G{3F{0E|0C{1C|0Ey1Ez2F{2Gy1Gy3Fy2Fy3Ey0Dw1Dw0Ew1Fx/Ex1Gy0Ew2Gz3I{0Ew1Gx0Ev/Dv2Fx4Gy7I{5Gz6Hz7I{7K~:K}8J~5I{6J{7Lz4Hz3Iz3J{5K~7L8K7L~8L9O:O@P?UATCUEXFWIXM[M_PbQcUhZk\o`rctewiziygygyhzh|h}k}mnprrrpnoooo~noklpsuqmmk}k}k}k~kllqrsoqrvzµȓēĒΔːĒŘȚʞʢͭϼָѩդѦԨԬٯجׯڵ߻ݻ޺ݾۿܼ۽ݼڸ۷ڷܶں۸ܶݰܰװٱڱ0?^0=]2>_2>]3B^4A_6A^4B]6A_5Db5Ca8Ba7Db7Eb7Eb9Fc:Fc9Gd8Gd9He9Gb;Jf;Je;HdNg?Oh>MgBNfCOgCOgDPhBQfBQcCRdFSfGTgIWgJVhLWjOXmQZnQZnO[lR]pT^rYbu]dx_dybg|flfmhqjrmumvltipnuqxpwxu|pr~}üwox{dov^it_hs^dp_jr\ioXdmR^jIVdCO]AO];J]8I\8HZ8G[6E]5D[3BZ4B]7Ca5A_3@_3@_3@`2?a1@b0?a0=`1>a/;^1;_/;^,:[*7[*8[(6Y&5W&1X&2Y#/W!.W#0Z!1Z"1\ /Z.[ .Z!/\!1]#1]"0\"0]!/^"0_".^!-]!/^ .]-\!,]".^ /^!/_"0_!/^"0^"/c!.c!.b!.b 0d#0d$0d0b#0d#0d$1e%2f%2f'4h(5i(5i(9k%6k%6j$6i&7m&7m#4j':m*s+>u,?u/@v/@v.Aw1Aw1Bx0Aw3Cy3By3Dz1Dy1Ey5G|3D{4E{7G~5I}5J5I~4H}6J5I}4G5H7H}6G{5Fy4Ey3Ez2Cz4Ey3Ew1Fz1Ex1Fy2Fy2Gz2F|/C{.Cz.Ay/Bz1Bz0Ay0Dy0Dy0Dz/D{0E|2F~3F}2E}1Fz2Ez3Dz2Dy1Fz3H|2F{2F{6G}4E{5F}4D~2E}3F~1D{1D|2F{4F|4E{3Dz4E{3F{4H}4H}5I~4H}5I~9J9J8J7K6J}6K}6K5J6J9N9M9M:N;PR@T@T@T@T@TAU?S=Q>S>T>SR=P>R=Q=Q?SAS>R@S=QR>S?S>R?S?TATAU@T>Q=R>R>SR>R?T>S?T@U@U>S=R=S=R8N5L5L6K7L6K7J6I5I5I3H2G~2G~1F~2G2G2G~4G2H~3H3H2G~3H6I}3H~4I3I3G~3H4I4I5J4I5J4I4I4J3L5K4K5J5J7K8N6L7M7K7L7L6K5J4I~3H3H2G~3H3H3H.Cz1F}/D{0Ez0Ez0Dz0Dy1Dz0Ey/By/Bz1Ay0Bw/Cx0Dy0Dv0Dw.Ew/Ew.Dv/Cv.Bu1Dx0Cv.Dv0Fv1Eu/Cv1Dw0Cu0Cu2Ew2Ev2Dv3Eu3Eu4Fw4Fv5Gy2Gx6Hy4Hz5I{4Ix1Hx3I{3Iz4J{4J{6L7L~6M}9M:N;O?Q@SASBTDVFWHWL]N_NbRdUgYk]o`qevgyj{l}j|j{j}f|g|i|m~mnm~m~ppoknnm~ll~oo~oqusqpnmk|ikjkmppqqty}ɦҼ׿ڿݾ’ÓƓŖŚƖƘě˗ğɮӰմٵٸڶѽԼۻ޶ٿۼٽںָڸ۵ڶܹڹܴڱܰݰݯݰ/?^1>^2?_4?_2@\4?\2@[2@[5B^5@^6B_6Da9Db6Da7Eb8Gc;Fd9Fd9Hc9Hc;Id:Jd:JdNh>Nh>Mf?MgANhCNgBNfANe@PdAOcCOeCRgERfFSfHUgJVlLXnJVjKXkMZjN[nP\qQ\qR\sW^uT\rS\pU\pT]oX`sY`vVauV^rW^pY_r[au`e|cjbj}_fy`g{gmmrtx||¹ž»~~tyxmuuoyukvtbnpQ_hET_@Q`=M_v.?w/@w->u/@y0@w-@u/Cw0@x/@x0Ay0Bx0Bw2Ez1Ez2Ez2F{1Fz1F{2E|5E{5Fz5Fz3Dx0E{1Cx1Bw2Bv1Ey1Dw2Ex2E{2Cy0Bw/Dz-D{-@y-@x.Ay-Ay/Cy/Bw-Ax/Bz/Dz/B{0C{/B{2C{2Bz1Bx2Cy3Dz5F|3Dz2Cy4E{3Dz3D{2C~4E|3E}1D|0C{2Dy4E{4Dz4Dz4D{3Dz1F{4I~5I~5I~5I~5I~4I}5Fz6I|5J}5J~5J4J7L8K8L9L8L9MR=Q?S>R>R>R?S?S>RS>SS>S?U?V>T;R=S;P7L6K4K5J5J6K6I6I5H5H3H2G~0E|2G2G2G2G~2F~2H1F}2F}1F}3H5H}5H}5I5H~4I~4H~6J3H}5J4I5J4I2G~4J2K~5L4J5J5J7L6K7L7L7L6K7L8M7L4I~4I~4I4J4I2G~3H1F}1G}3F~2F{3E~2E}0C|1D}/Dx.Ax.By.?u0Aw1Aw/Cw/Bt.Bu-At,Bt+@s+@s-?s/@s1Bt.Bt/Dr/Ds/Bu2Dv0Bt0Bt0Bt1Cr2Dt2Dt2Du2Dr4Fv5Gw2Hv2Hv4Iw4Iy3Iw1Iz3Iy5J|7K}5J|5L|7M~6L};O;O;O=QATBTDUEXFVHYM]M`ObUiWi\n_qbtfwgxizj{i{j|i{h|h|l~l~l~mmmn}q~m}k~k}l~k~m}m|m|p~nnqpqpnnki~imnpqopqv{|~ǺԿžɺïťƬζղѱرسڳ۸߻ֻ߿޼ݽٽ۽ݸ۵ڴ۴ڷڷ۳گڭګڪ٭0?^/>]1>^2>_4>_3>\1@Z1A[3A[6@^6A_3A^3A_4B_4B`3B`6Eb7Eb7E`7E`8Gb6F`8Hb8Hb8Hb8Ic9IcJa>Kb>MbALdAMfAMeAMdBNdDPfEQgGSiHThHUfKXjJYnN[rNZqP[pQ\qP[pP\nR]qR]rR[rV]sT\pT[rV\sU^tV^vW]sX_sY`sZbvXbz\h}_j|cokvo{lxnyozp{q~tw|}~{z{{~~}r{veorU`jMZfGUdBRa=M[>N^@L_Ia_4=_1>_0<^.;]+8\,8[(6[)7\%3Z$3\#1X%3Y%4X$1Y!.W!/Z /\1Y!0[!/[!/\!/\"0^"/\"/\"/\"._!/^!/^ .]!/_/`.a1d2d0c 1a 1a"0a"/c"/d"/d"/d"/d"/c$1d#2f#3g"2f 1d 1g 1h!2h"4g$5i$4h"5i%6j%6j$5l$5k%7o$8m';o)w.=v.?v.>w-Aw.Bw1Aw-Bw.Bw1Bx1Bx2Cz3Dz2Cy3Dy1Bx1Bx3Dy2Cw3Cw2Cx3Dz1Bw1Bx2Ez.By-@y-Ay-?w/Ay/@z0Ay/@x/Ay/Bz/Bz0Ay0Ay.Az/Ax2Cx3Dz3Dz3Dz2Cy3Dz4Cz5E{4E}5D4E}4D|.B|1D~0Ey2F{1Dz0Cy0Ez1Ez1Ez2F{3G|4H}5I~6J~3H}6G|6H{4J|4I~3J~5J~6K7L9L9M8L7J;K;L9MO=M:N;OR;N=Q=Q>Rr/>r.=q+R\4?\1A[1A[0@Z1B\4C]6B^5@\6A]5B]4C`4Da4E_7E`8F`9Ga9Ga9Ga;Ha=Ia=H`=Ia?Kc?Kc@LeAMeANeCOdCNcEPfGRhHSiJUjIVgJWhLZnKXnNXoOWpOWnNXkOZnM[lM[kOZmPXlRZpQYqR[sQYpRZnR\oR]qW_uXcwZez^k~_i~anboboanergthtnyt~wxwwwyvxxyzzzy}}|}w~}fquXglL]eHV_GRbGQeEPbFNaCKaCKb@Mb=Ic=Id:Fb8Fb7E`5C^3@^4>\3@`5@a2=`.=_.;^,:^+:]+9^(7\(6[)5\'5Y&3Z"1Y.W!.W!.[!0\!0Z"1\"0\ .[ /\!/\!.\"0]-\-\-\ .]!0`!/`"1a!0_"1`$0_"0_#1b$2c#0a$2c"0b"0b"/d#0c!0c!1e 2f!3f"2g"3g#4h%6j#5h%6j%6i#4i$5k#4j%5l&9n&:l';n%9l%9m%:o%9n&7m):p':o';q(9n(9o):p)9p':t&9q&:q*r)>r*>s+?t-?t0?v0?v1@x2Cy0Bx/@v1Bx1Bx3Dz3Dx0Dw0Bx0Aw0@x1Bx1Cy/Aw-@x0Ay.@x-@y.@x0Ax/@x/@x0?z0?z/@w/Ay0Bz0By1Bz1Bz3Dy2Cy3Dz5F|6E|3Dz4E|0C{0Bz0C{3D{2Dz/Dx0Dy.Cx-Bx.Bw0C{0Cz1Ez2Cy1Ez2F{4G}4H~4H}5I|5I~4I3J~3I}5J{6K}7K~7K8L5I6J6I7K9I~9K8L:M9M:N:N;O:N:N:N:N9O;P;N>O9L9L7L7L8M6K5J5J8K6K8L7K9M9N:N7K9L:M7L7L6K6L7L6L5K7L6K6K9K9L8K7L6M5L6M5L8M7L6K7L7L8M9N:O:P;P;P7O7P8M7K5I5J4I5J6K6J6K7K~6J3H0H~/F~0C}0E|/D{0E|/Cz/Cz1E|1E}/C{/D{1F}2F|3G|3H|3G|3G|4I}3H3H2G~3H5J4I~3J~4L4I}5I6L6M~5J6K5K6M6K7L9N:O:N9N:O9N8N7M7K3H}3G|1G|1E|1C}1D|0C{0C{/Bx/Cx.Bw.@v/@v/Cx-Au-At.Bw.@t-At.?s/?t/Ar/As->p.@r+@s+?q,@r,As.Bt0Du3Dt3Ct3Et2Dt2Ds4Fs2Fv2Gv5Hv9Ky7L{6Kz6K{7M|9L}9M|:N}P=Q>S?U@U>S?T>R?QCTDUHYM\O`PbUhZn\o^p^qas`rctdufuhujzj{k|j{j|j~j}hzi{k{l}n{m|h{i{gxdyezg{hzj|lkm~m~imljkl}jkk~oqrpsssrvzw|~|~~Ǿ¿ſ¿ƱíʪŲճٲݵ޶ܴܵվݼּ߽Ժڴٰڰװٱ۲٭ڭݪݩߩ+:W,;Y-[0>[/=Z0>[1?Z0>Y/=X0>Y1@[5A]6A]4?Z5B]5C_4D^5F_5D^6E_6D_8F`8F`:H`=H`=Ia>Jb?Kc?Kc@Ld@Me@NfDOgFQfEPfEQgFRhHUjGUeHTeGReJShJRjKQkJRiLSiJUhJWgHTeJTgNUjMUkMUlPXlRYmOZkO\pQ^sUbuXbwXcvYew[gxYgx[h|]i}_k}`m}anfsjvnxr~rtstutuuttssutwwy|~lz|`jtXdnP]gNXfMVdLUdKTeHRcERbCOb?L_AL^AL_?Ja=G`=I`8F^8D_6A]3@\1?[/>[/?\/>\-<[,;[,9[)8X)8Y)8['6Z&4W(5\'6]&6[&5Y&5]$4]$3]#1_"1\$2_!.\-\ .]!/^!/`"0a .^!/_"0a#1`"0_#1`"1`"0_"0_"0`"0a"/c 0c!1d#3f0d"2f"2f!2f"3g"3g#4h$5i$5i#4i#4j$5k$5l&7l$9k$8l%6k&7m'8n'8n'8p):q*:q):p*:q'8n'8n'8n&9r&:q$8o':s(u/=t/>u.>u.?u0@v1Bx0Au/Au.>t.=t/>u1@w1Ax1By2C{2Cz0Ay+?w*>w,>x-=w->v+>x,>x->v/?x/@x/@x/@x1By1Bw2Cy2Dz4E{5Dz2Cy2D|0C{0C{0Bz3D{2Cy/Cx0Dy-Bw/Cy.Bw0Dy1E{1Dy1Dy0Ez2F{2F{4H}3Gz5I|5I~4G|1G|2I|2H{4I}4I~5J6I~5I~4H}3G|3H}5H~6I8L8L8L8L9M9L8L7K7K7J9L9L9L8K8K7J5J4I3H5J2G~2G~3H~6I6I8J6H5H5H5F~4G6H6K6K3H4I6J6I5K5J6I8L7J8I8J5J6K5J6K5I5J5J5J5J7L7L6K8M:O:O:O9O7O6L5J5J5J4I4I4I4J4I3H~2G|2E}1I~1H~/E|/D{/D{.Cz/D{.Cz/D{.Cz.Cz-By-By0C{0Dy3F~0C{2E}3F~0E|1F}0F}3G3H2H~5J~3H4H~5K8K7K}5J7L7L7L6K8M;Q:P:P9N8M7L7L6K6L6I}7I}5I{2G{1E{1E|1D{0C{0Ey/Cx/Cx0Bw1Bx0Bx0Au.Bu-Bu-At/Cv.Bu0Au/Cu0Cv-@s+?r-?r+@r-At.As,At/Cv2Ew2Et2Dt4Fv6Hx7Iv8J{7L{P>P>PAQ?P@S@SBT@S?T>TAUDWHYL\M^PaTfXk[nZm[mYk[m\n^oarbsdtfxh{i|i{j}i|j}i|hzj{j{j{iyi{gydvfxh{e{i{i{i{j}k}k|i~kimnjjjmoopnrrrssyw||y{|~ƸģĥĬƫƸ۵۶ݶݶߴټտջշٱٱٱر۱ڭ۩٩بڧ+:W,;X,;X-Y/=X/>X0>Y3>Z6A]7C^5C^4B]4D^5E^4E_4D^6D^7E_7F^9G_;G_=Ia=Ia=Ia>Jb>Jb?KcKd@MdBNeBOfEQgDQfBP`EOaFN`GNdGOfGNeHNeHOfGRcFRcGRdJRgKSiLThLUjNVkPWiOYmO\rN^uR`rU`sV`rWctWeuXewZfz^i~_h|`l}cpgrisjwkwmyo{p{o{q|r~r~r~p}mzkzmynynzp|q}s~s~ustvvwxy|{y}nu}clu[dlYblW_lS\iNZgKWeGS`IT`JVcITeDObBM^@J^?J_?Jb=H];H^9F^9F`9Fa8Fa8Eb9Eb8Eb5Ba6Cc6Be2Aa0?b0>a.=`/=_-;`*9_(7^)8`*8a(7`&4`%4a%4`$2`#1`!/_#0_#1_#1a$2a#0`#1`"0_$/_#/_#1`#1_!0c 2d 1d%0e"2e#3g"3g0d!2f 1e!2f!2f"3g$5j#5k#4j%6j%6j'8l%7k$4i$5k%6l%6l'8o(9p'9n(9n&7n&7m'8n(9o';q';p%9n&9q';o(u-t,=s.=t/=t/>u/@v0Aw/@x/@u/?w+>v(>w*=w+=x,=w(=w*>x.?v-=u->v->v->v0Ay1By.Aw0Bx2Cy2C{2C{/@x1Bz/@x0Ay3Dz3Dy/Dy0Dw/Cv1Ey0Dw0Dy1Ez1Ez1Ez3G|3G|4G|4H}2Fy4H{3Gz3Gz0Gz1H|1H}1F|3H3H3H~3F{3G|4H}3G|3G|3G|5I~6J6J4H}6F}5F}5H6I5H6I7I5H3F~4F~3F~4G~0E|1F}1F}2G~4I2G~1G}3G4F5F0C}0D}2E}5E}2E}3F~5H4H4I3H4G5G5H5H5H6I5I~7H~5I~4I~6J5I~4H}3H1Gz3I}6J6I6K5J7K8N8L8M8M8M7M8L5J5J5J3H4I4I3H3H2G0F}1E|0F}/F|-Dz.C{.Cz.Bz.Ay-By.Cz.Cz,Ax-By-By-By0Dy0B{.Ay/Bz/Bz/D{.Cz/C{2E|3F~2F|3H}2G3H2H|8L9M7K7K6K6K7L9O;Q9P:P9O8L6K6K6K6K7K8J}5I|4J|5I~4H}2F{0Ez0Ez1Ez1Ez1Dz2Cy2Cw2Cw1Bu1Bu0Dw/Cv.Cu1Cu1Cs.Ds/Ds-Br/Bs/Bt.Bt.Bs0At/Du0Eu0Fu3Et4Fu5Gv7Iw7Ix:Lz;M|=O|@R@R@R?Q~BP}CQ~DRCQ~DQ~BR~AR~ARCSCUCVCWEXH[K\Q`QcRdVi[n[nYkYkXj[l_parbsbsgwgyg{fzfzg{i}izi}i}h|i|hzhzhygyfxgxhzi{hzhyhzi{hzj|j}i~m~nn}kj~lnnooorrsrrwtwyxx{£¢ƠģƩƳӸۺݽۻɼ̳ɾ޽ۼؼؾԷֵڱڲذۭڪبة٨ܨ,;X+:W,;Y,;X-[/=X/=X/=X1?Z0?Y2>Y4?[4@[2@[3A\3A[5C]3A[4B\5D]6D\6E[6E\9E]9E]u*v*;s+u,=t-=x,@y,?w0Ax/@x/@y0Ay/@x/@x0Ay1By2By/Dz/Cw1Cw4Ey1Fx1Ez2F{0Dy3G|2F{3G|5G|6F|5Gz5F{4Ex1Gz/F{0F}0E}0E}0E}0E|1F{2F{3G|3G|3G|1Gz0Ey1Ey2Fz0Dy2Cz3D|4C}4C|4D|3D|/Ay/Bz0D{4E|3D|1D|1C|0E|/D{/D{0E|1F}0E|1E}0E|/Cz0D~/D}.C|1D|4G1E}1D|1D|3E}0E|1F}2G~0E|1G3G4F~4H|5I~5I~4H|3H}3Gz4Gy2G{0E|0E{1Gy5I|5H|6L5J7L7M7K6K7L7N8L8M6K6K4I4I3H4I3G1F|0E|2F~/E|3G1E{1F{1F{0E{1Ez0Cy.Bz/C{.Bz.By-Bx,Ax-By.Cy-Cz.@x.@y,Ax,Ax-Ay.Ay0C|1F}2F|0Ez2G~2G~2G|2G|4I8L7K5J4H~6L~8N9O7M7M7M7M6K6K4J~6K7J3H}4J|6J}8I4H{3G{1F|0Ez3Gy0Dw1Cw1Bw3Dx3Ev0Dv0Dv/Cw1Dw-Bs0Cs1Cr1Cq/Dq/Dp0Fs.Er/Ft0Fu3Hx6Hx5Gv5Ht6Hw8Jw9Lw;Nz=N|;Oz=Q|@Q}ES~IXL[M\LZLZKYIXGTESCTDUDWFZIZN\O]O^O`M^NbQdTgWjVhWiXiZk[l`qarasftkxn|jzgyewi{hyj{h|i}j}i{j}h{jzhzgwizj|hwhvgvhyjzhzhzi{k|k|mm~l~mm~qnpspqttvtvstvxy}™Úš àßı̸ҺܾпŻǻ̾ʰŵսھؽ޿սչضگذج۬٨ڬծۨا*9V*:W*:T*9T-Y0>Y3?Y3@Z5B\3A[3C[3CZ6E\7E\9D\;E];G_Ja>J`?Ka@J`AH_BJ_CK`DJ_ANaBObCN^FMbFNcFLaEMbFNaFQcDQcGTfGSgGReHTgFReGRfGTfHUfHUfKWiNVjRZoU_sT_qV_sWbvWdvWdvYfyXfwZgz[g}\i|[h{[hz[gx[fx[fz[h{\j|^k~_mbocperhthvhwjwnzl{o{o}n|q}q}n|sso}o|q}q}wz|v|u|w~y{~|zyw~w~v}yz{z}~u{szlsho|flzckyah{]dw]dw[bu[asXarT_nOZiISgCNc=Jb9Gb6Da4C`2@b1@b0?b0?e.>d-;g*;f,v,>v-=v-t0@w0Bx1Ex2Fy1Ex1Ez1Ez0Ez1Ez3F{2F{2F{6H~7G{5H{5H{4Hz2F{0F}3G|3G|4F{5Ey2Gz1Ez1Ez0Dz1Dw/Bw0Cy0Dy1Fz0Cy-@x0Ax.Az/C{/Bz0C{.Ax.Ay-@y/Ay.Ay-Ay,Az,Az,@x-@x.Ay.Ay-B|.C}/D}-Az/B|/C|/Bz/Bz/Bz.Ay.Az-Ax-Cz,Ax.Cz-By-By.Ay/Bz0C{3F~2Ez3G|1E|0Dz0D{0E|/D{0E}2G~2G~2G~2G~2G5I~5I|6J|6J~7K}8M7K6K6J7M5K~6L~4J~7J5K6L4J}5J~5I3F|4H}4H{4H{3I}1F{/D{.Cx-By,Aw-By+Au/Dy/Cy/Cy/Cx/Dy.Cy-By-By,By/Cy2Ez2Ey0Dx0Dy2F{2F{0Dx0Cy0Bz1D|3H{4J|2Hz1Gy2Fy4H|2G}5I~6J4I|3I{3H{4Hz3Hy3J{4J|5H{6K}1Gy3Iz5K|4I{5Gz3H{6J|;L~8J{8J|9K|8J{:L|9Ky9Kz=N{=M{;M{9Ky9Ky9My9Oy9Nw;Nw;Nw=MxH_=G]>FZ>G[>F[=G];H`=Ib=G\>F]>G]@G^@H]AI^AL^AL]CO`BPaAN_DQbDQbGQcFTeHUfJTfLVhOWjSZpT_sR]oV]pW_rVbtYfvXev[gzZdyZdz\g{[dx\dvYcu\cw]ez]g{[h{]j{`m~andrdqgtfvixiymzk{n{nzp|nzo|o|zq}kyjxhvdrfosw{u|pwpwszx{yxx~u}t~u~u|w~x{~~v~qwpvkp~fiwbhx_eu_ev^ew^ey]dwY`sW^pV]pSZlOUkMUiHSfBPe>Mbu*:u+;w-=v->v->t.?w.?v.?u0Dx/Cy/Cx/Cx-Bw.Cx1Ez2F{0Ez3F{4E{5F{7G{6G{2Fy4H}5I~4H}7I7Fz4Ey4Fz4H}2F|2Fx3Dx4E{1Dy2Dz/Cx/Bz0Ey/Cx0Dy/Dy1By-Av.@y.Ay,?w-@x-By*?v*?v+>w.Ay.Ay0Bz.Dz,Ay,Ax-By.Bz/Bz1Cz.Ay.Ay/Bz-@x.Ay-@x-Ay.Cy.Cz,Ax.Ay.Ay0C{1D|3Cy0Dy0C|/Bz/C{.Cz/D|1D|/D{1F}1E|2G~1F~5H4H}6J}5I|5J|6J~9L7L7L8N9O8N8N8K~8M:P:Q8M:J6K~6J}7K~7M}7M6K5K4J}0Fy0Ez1Gz3J{4J|3H{2Dx2Cv/Bw/Bx.Az,Bx,Ax-Bx/Dw0Cv/Cv/Bu0Cx-Bw/Cw2Bx1Bx0Dy2Ez3H}3I{0Gx1Fy3Gy3G|3G|3G|4H~3I|2G1F|2Gz3H{4H{4H{7G|3Gz1Gw3I{4Iz4Hy6I{8Jz:L{9Kz8Jy8Jy:Lz:Lz=Nz=Nz@O{DQ}@Q~AS}@S|AT~AU|DV{EVzGY}L[~ScYgVdUbvamdr\lboguesdsgugv`n\jXgTbO`N^P]Q^O^P_N`RaQ`OaQcSdRcRcTeUh[m`n_r_q^pbqapapbpetfugwizk|o~p|n{o|n}m|o~o~l~i{j{m~o}pl{jwjxgwhyiwkxiyizj{j{izk|k{m{mnprtssuutttttvvxy}}œԹʿǴȱƮǰĵźпҷٳղձڭ٩ժթէ֧֨դ֤+7Q*8S+9S,8R,8R,8R-9S-9S-9S/9T.:R-9R.:R+:Q.:S0Y4?X1@V.?T/?V1AX4DY6BX4CX4BW6CW5EZ3BW5AW6BY7DZ6B[6D[6CZ6BX7BY6BX7CV6AT7DU8EV8FW:F_;Ha;FZ>E[>G^@F^CI_CK`AJ]DK^DLaCMaDM`DPaCQaFSdGRgKTgMShNUjLYlOYmQZnR\pT\oV^qYatYctYevXbvYat[bvYavZ`sY`sZatYbu\dy^f{]g{]l|`m|anbqgtdsfufuixnxkzlwlxlxn{lysslyixiwcqbsgpjo{u{ovlsryt{u}xw~t{u|x~yy|~ķxpxkthn|fkzekzbhuckzck|`hw_fw_fx[ev\ctZasY`sY_rW^qP]nIWeCNa?Lb=Ib9F`7D_2A_0@`/>`/=b-;c.;e-:g,9e+9d)7c&7c&7d%3b%3b$2a$2a!1e!1d"2e"3e0d.b 1e1d 0d.e.d1d!1c/c 0f /e!1d"2e"1e#0e#0e%0d$1f!1e!0g 1g 1g0f0h2j 1i0h 1i 3i 3h!2k1i#4j$5k%6k#4l$5m"4l#3i&5l'7m&7n'7p#8l#7l#7l%8m%5k#6l$8m#8l#9l$8o$7n'8l#7o%6n$3l$4m&7p&7m$9m#8m$7m%6l'8n$6k&5o*:s(9r(9o(:n)9p+v->v.>v/@u.Bw.Bw,Av,Av/Cx/Bw0Aw/@v1Bx2Cy2Cy3Gz2Gy2Fy3F|3G|4H}6F|8H|6F{6G{5Fz4Fz6F}5E{3E{3Dz1By-Ay/Bz.Ay0Dx/Cx1Cx2Bx.Bw,@w+?w+@w.Ay-Ay.Cz,Ax-Ay.Ay.Az.Cz.Cz-By,Ax+@w,Ax1B{2C{.Bz/By-@x0Bz0Bz1Ay.@x.Ay.Ay-Ax.Ay.Ay-@x2Bx1Bx0Bw/Cx.Ax.Ay1C{/Ay/Bz0C{1D|1D|-By/E{3F6J4H|4H{5H{7H4I{6K7L8N:P:P9O;O=QU1=V/>W-=S-=T.>U1@W2@X1=U2>V2>W0?U0?T0?T1@V3?X4AX5AZ5BZ5AW5BU6BW6BX6DV8EV7DU7DW9E[;E^u(=s+?t-?u.=u-=s.?u/@w.Bw0Cw.Bw0Dx1Ex/Dw0Dw2Dx6Fz6Hz7G{5F|6G}4E{3Dz4Dz3Dz1Cx.Bz/Bz/Bz0Dy0Dy2E{1Ez/Dx/By-@x-@x.Bx.Cx-Bx.By-Cy/Bz/Bz/Bx/Bz.Ay-@x.Ay/Ay1Bz/Ay1Bz/B|.Ax.Bw.Az/@v.@y-@x-@x,Aw,Ax,@x/By,Bv-Av/@v.@u-Ay+?w.Ax-?x.@x.Ay0Ay2Bz-Ax.Az2D|3D|3G|4H{3Gz3Fz4H{5I|4J|5K}7N9NR?Q@R?Q;P:OT?T>T?UASBT?Q:L}9J}4H{2G|3G}4H|3Dw2Fy0Ew.Dv.Dv.Cx-Bw.Bw-Aw,Az,Az-Bw,Bu.Dv/Dv1Fy1Ex4Dx2Fy1Fz3G}3I{2Hy4Iz5J|6J}5H{9J}8J}8K{6Kz8M|7M{:Kz9Ky9Ky9Lz9KyO{?Q}CR}DS}EU}IWN\P^R`VcVe]lkzs~wxn{iykzmzixctctbrdvexevgwjzm|jzkym}k}m{mswxwwusuuspp~o}l|m}qrrqprrponl~o}o~on}m{k~kzn}m|j|m}m{mzkzl{l{k{m|m~l~nrrstvvwutvxwy{{|ѻǿƿ׾пмӶԵԲկ֪֬ի֮ձԳԲӴյԸмջвӱְөԧ֧֦֤Ԧԡӡԣ+8N,8N,7O-7P.8Q.8Q,8P,8P-8Q.8P-9O.:P.9P.:Q1=U0l-;j+9h(8f+8g)8i%6f$6g$6e#5e!3c#5e"2e0c0c/c/c!1e 0d 0d 0d!1e"/d#/e"0c$0d#0e"2f0d0e/e0g2g/e-c0e0e1g0g0f 1g 1f 1h0g 1i /g!0g"1h#2i!2j!2j"3k!2h!2i"3i#4i%6l&7m$4j"5j#4l"3k#4l!3j!2j"2k!0i#2k"1j#4l$5l&6m#5j#4j%3k$3j&5l%6l%6l$7l#8k$8m&7m'9p'7o&7n&8m'8n$7o&7o'8p$8p&9q&9q&9q'9r%:p&;p*;q.=t-;s0O}>Q~@R~CTEUGW|JZ|JZ|M[|P^Q`Ve]j^nbql{wyx{xtqop~qtuuvvvsvxzzyzyzyxxwvtom~pn}o~ssstrrrqq~pn~pqon~l|l}n}m|m|m|m|l{l{k{n|k{i|k}k~p~qqrsuuwwwwyyzyy{{}ѺκпӼְԱұլӪҫӪӬԮӱװձԳҴѻٱұձӬԩѩզԦզԢӡՠ,7K,9L+8L,8M-9O-8N,8N-9O+:N-:N.;N.Q-=P,>Q.@Q/?Q0>Q1@P/?Q.>P/?R/>S0?R1?T3?W3?U1>S2?S4AV5AV4@V5AS4AR5BT8BT:BUF\>E[AIZ=FW=FX@H\AH[CK_DLaCK_FM`EM`FQcGRfGTfHUfHTgFUhJUiLWkLWjLWiMViNTgOViOUiNViPXkOWjQYlSYlQ[lSZmT\oV]qX`uWbvVauWctXewYfwXeuYfwZgx\h{\i|[h{[i|_lan]j}_l}_l}^l}_l|_l}_l|^k|an`n~bmdnfoipovtzw~u|qxqxnwoupxpys{tzv}v}u}s~ssq~tw}{}z~y{z|x{tzsyouho~el|_fy\cv[buZasZasW^rU\qU[qS[lQYlQYnQYnQVmRXkPViMRdFNaALaAMf:Fa9Gc6Cc6Eg4Cg6Ck4@l4Am0?j.m*8g*8g*8g):h&8h$6f#5g$6h$6g$6g#5g!3e!3e 2d 1e0d/c0d/c 0d#1e#1e"/d"/d"0d!0d!1d!1e0e 1h2g1f0e/d0e/d0f0f/e0f/e/e0g!/f!0g"0h"0g /h0h!1j"1h!2h!2h"0h!3i&6l#4l1i1i!2j!2j 0i 1i 1i-f /h /h"2j"4l"1j!2g#1h"1h#0g#3j$2i#2i"3i$6l&7m&7m%6l&5l$6k%6l&7m%6n&7o$5l'8n&7m&9o&9q&9q$9o%:n'u-t,=s+@s+?r/@t0Au0Au.At1Cy/Dy.Bw0Aw0Dy1Cx1Bx1Bx1Bx/Ax,@x.Bx2Bx/Aw1Bx/?u*>u/Az.@x.?w/@v,@u.Bw.Bx-Bv/Bz-@x.Ay,?w.Ay.Ay.Ay-@x-@x.Ay-@x0@x/Ay/?{1B|/@x-@y+>v+>u0@y-Ay.Ay.Bw0@v/@v/@w/@x.?v/Aw-Bv.Bw,?x.Ay-@x.Az-Aw.Bw0Dy/Cy/Ew0Fy2Iz4J|4J}6L~7M5L~7L}8MPAS@SBRCRBQ@Q@S=QBS?Q@S@RASAUDUFWEWDUCVCUEUAR@P?Q?Q=O;M~8M~;M:M6J}5K}2Hz4J|2Hy1Hy3Iz3J{2Hz3I{3I{3Iz6K|7K}7K}7K~7K~7K~8L~8L}7M|:K|9Ky;M{Q~>P}>P{@P|AR}APzCT~DT~FU}HV}JY}K[}M[|O^SaWeerp|o~wvz{{~ytqrqsuwxyy{tzz||||vsrqsvsssqsp}rp~n|n{n|l|on~j|k|l~l~m|o~m|kzn}m|l|m}n}l|m|l}j|noq~psssvwxxxwvxyz{|}нйҼױӲԲ֭ӬҪԧӧӫҫѮӳ۽λϵɱϳԯԲӭԬҪөէ֤ӢҢҡ.:K-:K-:K-9K,;J-:L,9L.;N+Q.>S.?Q/>Q1>N2>O1?O1?Q/>R-=P0?R1>S2>U3?W4?U3@S3@T5BT4@V4?T5@R7@Q:BS;CU=EW;CU>F]=E\=EZ>EW>HX?HZ@H^@K]BK_DL_CK]DL_CN`BO`CPbCScFRcEQcFSfFSgGSfITfHSeIRdKReKReMSfLTfMTgNUhOViOUhNVgPWiPXkQXkRZpR\qQ\pR_pR_tTatTaqUbsVdtWfvWgxZgzZgx]j|]j}[hxZhyZjzYjy[hxZf|]h}[i{^h}]hz_l|al~dm~gpltryqxqxovlrluktltlukwlwpyt{u|r~q~q}s~wz|~}{z~z|xzvxsvougnahz^fw\cvZatX_qU\oT[nQXlPUnQWoQVlOTkPWkNVhPXjNWlPWmLShJQdHPdCMc;Ia7Ga5E`5Db5Ae3@f2@g2?k4Bn1?k.=i-;j.;j,9g,:i+:j%7h&8h&8h#6e$6f%8e$6f!3d"5f#5g!3e!3e 1e!2f!1e#3g!1e"2f#1f#3g"2f%1f#0e$2f 1d/c1f0e/d.c/d0e/e0f0g0g/f0f.e-d.e 0f%0e.f/g0f0f0f 1g"3i!0g"1h!2k 1j2l 0i 1i"0j0h0h 0h.g/g 1i#4l!2k 1i 0g"0g"0h!0g/f.e /f#2i$3j$3j#2i$3j$4k#4j$5k%6n#4m&7l%6l&7m'7n%8q%7o%8p&:p(;q):r+:t-u/?u/@v0Bx/@v1Bx.Cx/Dx1Bx0Aw1Bx/@u.>w-Au/Aw.Bw0Av->t*=u*>w.>w,=u/>v->t-?u.?v,?t,>v-?x,?w.?w.?w-?w->v-?w-@x.@x-?w+?u,?x)=v*>u->u,>u+=s*;p.?w,>v*>t)>s,=s,=s,=v->v.?x.?t,@u-Av,>t-@u0Cy/Bx-Av.Bw0Ew/Ew2Ex2Gx3Iy5L|4L}6L}7M~7M~:N

P@R@SATARESCS@Q@R@RBSCSAQ?QASARCUDTCWATCUDVFWHXEWCTCUAS@QP>R:N7M6L}9O5M|7L~6K}8L7J{5J|7K}8J|:L8M{6Lz6Kz6L|8Lz8Mz8My:L{P/>O.=Q1?R1>Q1>O2?N0=M1=O/?N1>N2?P2?R1>S2?U3?U2AQ3?O3@Q7@S7>Q9?S9BS:CT:CRFX>FYBG]@H]>J[AK^AL^@M^AL^AL^@M^AN_CN`BO`DOaFOaENaFNaDOaDQbCN_FM`GM`FOcGNaHObHPcJQdIReGPcKThMUhPXkRYlQ[nP^nR]pS`qS`rUarUbsUbtWdwYdxXey\gz[hyZgx[fxYfwXfwWeuYfxYev[dw[cu]dx^ey^hy`j{al|ck~gnirjtjphofohqgphqhqivmwpxs|s}ttuuyy}||{}{|svhn~flbk^fyU^qQZoOYkNVjNTiNSkMTkPWlOVnMTkLVlLTlKShMVhOXmOWlMUhJReDKa@Jak/>h.;h-:i+8h,9h+8e*9h'8g(8i'7j&6h$3d$4d#5c$4d#3d#3f!3e!3e!3g"2f#2f%2f#1e"3e!1f"1e$1f$1f"1f"2f0d0d0f1f1f0e0e0e1f/f0d 0d!1e#1f!/e.g0h0f 1d1e1g 1g0f 1g 0f!1g 1g!1h!2j0h1i/g!1j/h/g/g-f.g1h0h0h 1j 2i 1i 0g!1f!1h$1f#0g /f!0g#2i"1h!0g!0g#1i#4j#4j#4j%6l%6l%6l%6l&7m(9o$7o&9q&9q):s+t.>u.?u.?s-?u,>s.?u1Bw/@v,=r't->s(;s+v*>s*u*;s,v,=u(;s(;s':r':r*;r):o*:q(9q):r(S>RBR@SBUASASBS>Q@RBQCRFRER@Q@SBTBUATCTCUCUCUGUHVGVFUDWCUAS@P>P?QO0P1>P0=N/O0>N1?P0>O1?O3@R3=R2?Q5@P3>P7>Q8@R8AS:CT:DS;DR:CR:CTJ\@K]?J\?I[?J\=K\@J\AI\CK^BK^FM`HNaFQbHPcJQeKReNUhPWjPXkTZmSYmR]oR`rQ`pR`qTarS`rUbuTatVbvVcvWbtYdvWcuWauWbvV`sV`rWasX`sY`s\cuYbt[dvZfv]fx]iyah|ai|bk}blzel~gnen~eqfngofqhsluovpxrzt~x|}z}~Ÿ}kr^exW_qU]rS[sRZnNVlMUlLXkKVjNUlMSkNVmKUiLWlMWmLUlPVqOWnMTiNVjPWlPWiKReBK]>I];F_7D_4B_4Ac2Ad3@c0>d.;e.=h-v,>v,=s,t->s+?t*>s+?t,@u+?s*>u.?w+t+=s':q':r%8p';p);q,=s*=r(s+?t+?t,@u,>x,Aw-Bv0Cv5G{4Fw6I|5I{8N};PSASASBTASAS@R@R@QAQARDSDRBQ@QCTEVCUBUBTDVETHUGUIWIWDVEYCUAS@R?Q>Q=R8N~7N}7M}7M~7M}9N|>P=NR@Q}BRCTDTBT~BS}DT|CVyEUwGWwKZzN]P^R_R_UbXfVbYe[h~]liu~|zxxvtt{yx~óɵ~{xtpm}k{lykzixixhzlzkyjxjyixixkzl{kzkzjykxl{m|m{m|n}m|o}p}ppswvxyxyyxyz{}|~ŽȼżŽóǾȹ˳ͶѻƵήϮѪϫЬԧӧԤէЫөϬҬЮвӳҰЭҭҬЬѫҫѩΩͪЭΫϩЩ/G0>J,:G.:K1:K0O1>N/=L.:K0M2:M4;N3=O0>M1>O0=N1>N1>N1>O1=O/>O0?P0?O0@O0@O4P8@R7?R5?Q6BQ9BS:BS:AQ9BP:EP=DUGX>GX@HZ>GZ>I[K\J`:F`5B]4B`3@c3@c0>a.v+t(=r's)v+;r-;r.s)>s*>s-Aw-Av-Au1Bv3Cx5Gy7I|4Jx7N|8O{=R>S=R=S@S@S@RASAS@R?Q@R>Q?S?QAQCQESDSEVCUATCVFWHUGUGUGUGVFUDUEXEVDUAR?Q>P;P};O}:O|:Lz;M{>P|?Q~@Q=O}?Q?Q~@Q|ATEWGYGXIWKYN]L[L]M\O^Q`Q_TbWdUcWfXfZf_j`mgrhsԮ̬ͭɩī~|{|ytvwxwv{¼~~{ytqm}m}kyiziyixhwixm{jxhwhwhwhwgvhwhwhwixjyl{m|m}l~m~n|r~rtvvwy|~{z}~³¿żþƼɿɺǻĺʻƷƽѽʳͰӫɲ˯ʲ˻зҭЩѦѧѥЧҥҧөͬЭЬЬѮұԮѮЬӪҮѭѬϮЮϯΰΰͮͯа4?F0H2>G4=G3:G2

L.;L0=N/L1>K1=J1>K0=I4=K2M1>M/N.>M2>N1?O1?O3AQ5=Q8?S6AP7>L8>O7?P9AR8AQ7AR7?Q;BU;BT;BQFY;EX:EW9DV9DV8EV:FW:EW:FXd/>c/>g.=i,:g(6e&8d(9f(8e%9g%7e%6d%5e&8f'8f&7e#5c$6d$6d$6h"4f"4g&4f%3b%3`%3b$2a%3b%3b&3b!3a 3c"4d"3e2e3f2g 0d1e 1e 0d!2g"3g"3f 1h 1h 1f#4h"3h$4h"3g"3h$4h%5i#5i$5k&6m':o*;q)9n);n(=r*>s*=r(;q%8m$5k%6l#4j%6i&7l'8m#4m&7m'8n%6l&7m%6m#6l"6k$8o&7m&7m%6l&7m'8n(9o'8n(9o):p(9o):p*;q)u+=u+p*>s)>s'v(=r(=r)=q+;r,=r.>s.>r-S?T@RAPAQARBS@R@R?Q;QAR@R@QAQDQBRBSASCTATCT~EUHWGVDUDUHUEUFWEUHVCUBS?Q?Q?QBSASBSDUGXEVHXIYJYLXL[P\N\S^Q_R`QbSdUdWeYg[iYfXeYf[h_j_kforxȫʪ̫ˮʯͱͱʰʮdz~~}xzyvwwxzÁ}}|xurn~l|l{j{jziyhxhykzl{hwfufuetetetfuhwjwhxjyizjzm}o}qrrtvx|ÿöǽŴˮˮǪɨʩʯȱΨʨ˪ˮά̪̮ͬȶʯˬЧͦУͦҨѨϧѧҬЭҬѫЩѫϭέЬЭЯѮѭҭѭЭѰϱΰαϰα7>I5>G3G5=I3;H3=G2>K2>K2K/=L0=M1=M0=M0=L0=L1>N2?N5>O4>O4=N8>P9AT9@Q7@M7AN8AM9BO8AO9@P:BP8?Q:AT:?S:BR:DS9EV;DU:CT;DU;BV=CVIh7Fe5Cc3Ae3Ah2Ag/Aj.=i/>i*;f*=g)=h(;f':e':e&:e):g,:h,;h);i)9j'8j%7i$6g%4e&4b%5c$5c%2b%2a&3b%3b#4a#4d#5d 5a4d3e 5g 3f"4f!2f 0f"3g#4h!2f!2f!2h#4h"3g"3h$5j#4h$6i%7i%5j&7k%6l'8o):q):p(9o+;r*?t,=s->r,=q,=q*;o):o*;o):n(9m*;q*;q):p*;q*;r+v,r)?r)?r(>p(t)=r';p)r*q)>q*?p+?s+Bt,Cu.Bu,@s-Du.Ew0Fw2Gy4Hz6J|5K|5K|7M}8M|;P}=R~=O~?Q>R@Q?Q?Q?Q?R?QF:>H5=F5>F9?I9?J8?I4>I4=H5=I6>J5?I4?M1>L2@M3@M5>K5>J5P2O1=K1=K2=M5>O1=N3=K3>K2>K2=L4N;AO9AN9@O:BT9ARFY>FY@GZAI]AK]@I[BK]CJ^GPbHQaKSbMTdLUbQXeSWfT[gTZhV[j^cqS[hOXfNYgNZjP[kQYkPWiOXfPWgPWhRYkR[lS[lU\lV]mV[kX_o[bt\br^dq^fp]fp\fr\dt^eu_iwbkyckvfp}en}do{gpdpfpepeqerfshtjulyl{o|mzkygtao]i{XfwYev]dxZ`sX_rZ`sW_pSZlQXhOVgPWdTZgY\mWZlij{cfw^apfhu\^j[^o^duW_pR[lPYkOUhKUiIUhHUhHQfKSfJTiHTgEQfFRhFQgEQgFQfJRgKRgMUjNVkMVkOVkPVlPWiKSgKSgHPgIQhCNg>Je9Gb7Ff7Dh4Dg3Cl4Cm3Dn0Bm.Bl.Ak.Al/Bm.Al-Ak.?k0@m1Bn0Ap.=n+o);k(7h(9g):h'8f&5d&4c(6e&5f#3d%7e%8f"5b"4c"4f 3e!5f"4e"3f#2g!2i#4h#4h"3g"3g%7i#5g#4h#4h%6j'9k'9k&7j&7k'8l):n);p(r->r->r->r*;p*;q,=r/?s-=q*t,=q,=q,?q)=o(>p'=o'=o)@s(=r&;p)s,?t+=r,>r->r-@r->s.?u->t.?s.@t/At1Cv1Cu/Dv0Ew.Bu/Dv0Dw0Fx3Iz3Iz4Jz4Iy6L|6M|7M|7N|;P}S=R?P>PPBH;?F<@I:AG=AGL5>I6>K5=J5L6>K4=J6@L5?K5>K6FY=JZ>IZ@I[EM`FNaFP_JR_MTdNTcNT`QVbWZhY]i]`l\`l_co[^jV]iRYePXeOXcRZgPXcPXcPXePXgPWfQXgRYiT[kV]mY_nZ`o\cs]cs]cr]er]ep`ht^fs_grajudkvfmxkqpvdlxipdqepfpcndpfsfshugtcsan_j|[fx[fx]hzUarScsXdvWatX`rVaqW`qR[lQYjMUiNViQYjS[i[asafzaeylrmo}lnygjs`crcfsX^lQZhOYePWkMUjHSgDQdBMaANaDQdEQgEQhDQgFQhFPfJPfMTgNUhLTiMTjLSgNUhOViMTgMTgLSfJQdHOeDMh@Kgp)=o)=p,>r->r/@t0Au4Ex5Gy3Ew3Ew3Ev4Fx2Ew4Ex2Bt2Bu0@s1At0Au/@t,>r.?s,=s+r,=q,>q+q+?r+?q+?q)=o)q(o(=o)r.?s/@s.@s3Ew1Cu1Ct0Bt/As0At0Au1Cu2Dt2Dt5Gu6Iu4Gv6Hy5Hz6Hx7L{9N{;P};P{:O|8Nz8O|:O|:P{;P}R?Q=R>P{P?PASAR~@Q|CR}AR}CS~AS~BSDSAR~ASBSBTERBSDTGUFVGVHVIXJYHWJYJYKZLYLZM[M[P^P^R`Q_SaUbVbVcVcXeYf\f\g]halaldnhojqhnimnrsz{¼ʾƫӵϯȫʭѱαӸɱӸ͵ɾŴîªû}}}|z{zy}|z||zzy~}|xustqrpn~l|jzkzkyjxjxhyiygwdufvgteviwkxk{l|m}qqqrrvzyyz|~ļèƦĥ¥ĞĝȚȚɚəʛɞʟȠʤͦ˪Χ̨ˣʥ̥ʧͫͫˬ̫̫ΫѪЫάά̬̭ʬʫʫˬ̭̬ʫ̪ά̮ͭͮϯέͬBIKAGJ=FH=GJ?DJAFJ@EI?DJK7=H5;I6I7>I6=G5=G4EW;EV;FXIWKd:Ga:Fd:De6Dd6Dg7Dg7Ej6Em5Fo7Fq5Do5Do4Co/Cn2Ep2Cp.Am/@o/@n0An.>l/>l.?m/An2Ap1?o/=l/=n-;o*;n*;n);l(:i&8f'9g'9g'9f'9i'9i(:k):m);m'8k(:k'9j(:i*r.?q+>q.@t/@s/@t/@u0Au2Cx3Ex5Gy5Hy4Ez4Fy4Gw4Fv8Fv8Fv6Cx7Ey4Cw5Ex3Bv2Au/At0Bt0Au2Bw2Au1Cu0Cu0Bs1@t/@t-?s-At0@u-Bt+@s+>r.?s.?s1Bv4Dx4Dw5Gy4Fv5Gw6Hw3Hw2Fw3Gz3Gy1Ew4Fy7G{6Iz8J|6Hz4Fx4Fv4Fv4Fv1Ds4Fv6Iy6Iy6Gy3Gv5Gw4Fx4Fy6Fy3Eu3Ew1Ex2Dw0Av.?v/@u+?s.Bu/Bv-=r+q.?q.@r,@r+?r*?q+?r0Au/As/As1Cu2Dv1Cu3Fw5Gy6Hz2Dv4Fy4Fw3Ew3Dx4Fw5Fw7Iw:Mx;Ox>Oz>O{P{>RzCU=P{Q~@Q>Q}>Q}AS~BU@T~?R~P@O@Q@Q@Q~?R?R}>Q?QAQ}BTAT@S~@R~ER@SBR~FUCTDUGWHUIWIXHWJXJWJXKY}KXM[N\N\N]O]Q_S^RbR`T`WcVdYfZcYd\f^i]iak`i`hbicklrsyv{|ήЯдƱȼøƼ~~~}}|}{yyz}~~|{zz{wvuvtrsppo~ok{kzkzkxhvgxfwftetercsfuiwkym|l}l}qrtuvw{{xz|~¹úŹŻ¿ĻüƽƮǝĝʙɘȘșɘɛȜɛʠɥ˧ˣɧ˦ʧ̨ʨ̬ʮɭɭʬάΫ˭Ϋʫɭɭɫɫʭ̮ͬʭƮƭȬȬʭͮͮͭάFLOEJNAHK?JK>GJAFJCHNAGMAFM?EI?DI@DI>CJBM9?J7?J9>K9BK:AK=?J8?I3=G4>E8?I6?H8?L9AN:BM:CM:BM;CO;CQ;AL8@MEU=DU?GW>FU?GWn->l0>m/=l/=m0=m2?l1Ap/@n.?m.=n,=o+=o*Qz?QzAT~BUBUCU?Q};N~;M}:L:K~8L|8Iz9Iz8Jz7K{9N};N|=M{Q>P~>Q~>Q>P~>P~@R~?R}@S~AR~ASDSBSCTBSDUFUEUEVFWHWHVHU}HV}IVGU~JVKYKXLZKZKYKZ}LZ~N^N^UaUaXaXbZb\e]f\eZcZdYeanjwpvu{{ǽƱ·~~}}~}{xxwwy}z{zzzzywwuttsrqn~m}n~l|k{lzjxixgugtgtfsescqfthvjym|m|o~qrsuwyyyx{~ĹŻȶĸ¹»ʼǺƺıșșəȘșʚʚʙȜȜɠȤʧͥ˨˩˩ʩ̬ʬȫūɪɫ̭ά˭ɫǬȭɬʬ˫ɭǭɯDZưɯȮʬˮȬɬ̬JORJMQEINCJMELODIMFJQFKQEHOCHLAHK?GJ@EK=CI:AHAL<@J=ALDU=CP=CS@GW@GS=EP>EQ=ER=DS;DT;EV=EU>EW>FW:EU9IU:HU;HV:GU;HV=GW=HYj-@k.Al,An,>l*;i*;i-;j/q-?q->r-?s0@t1@u1At/?t-@u,?t-At/?s1Cu1At1@r0At3?t2?s4@t3Cw3Cv4Dw3Bv2Dt3Eu3Ew4Dx2Dv4Fx4Fx5Gy4Gy3Eu3Eu3Ev5Gy6Hz7Fy6Ey7G{5Fx6Fy:G|:Hx7Gx7Hx8Kz6Jz8Jx8Jy7Iz9Jx:Kz9Hy9JzLz=M|MyOz@RzCSxDSxDRwDRyCQ{DR{DR{ETzGVzIX{JY|HWzDRvESxGS{GS}ES~?Q}=Pz?R|ATBUBUDVCT>O~Q|?R}@R~@R~@S~BUATBUBTAR~BR~BU|FT~ET~FT{FT{FSHUGTFS~GTIUIUGU|FTzFT{FUzGVxIVyLY|O[~R\U^T^XaXcYbZc\hdpfpnsrww}|~~|~~~~~~~~}~|yxvvxyzxxwyxvustrrrqopn~o~l{kzjxhviviwiwguftdrgugujyn}o~o~o~q~q}rq~tttvw{||~ǻʷϾʷúȽɻпŷÛĚșɗȗȗȘɚȘƚƝĜǡƥƧɧʩ˫˪ɪʫʫȬǭʪȬʫȫȬǬɬɭƫȬȫȭǯȳdzñƱƯɬʭȫˬ̫KPSLOSJNSGLPJLRGKOIKPJLRKLRGLPCKNBIM@GK>EI@FL>CL>EL>CK@EM@CL:AI;BJ>DL>CH=DJEQ>DP=DO>FQ>GP?GT>FT>FS;CN=EQ=CR?CT>DW?DUDQ@ER>DP?EO=CNHS=DS?FU=GS>HQ>FQ>FRj+p->r-=q/?r2>r/?r2>s2>s1>r/?s0@t0@s2Bu2Dv2Du0Bu0@t/Bt2Du2Dv2Dv3Ew2Dt3Eu1Dt3Du5Eu5Ew5Ex6Ey4Dw5Fy7Ey8Dx7Fw6Gx6Hw7Iw6Hv7Iw6Hv8IwLz?Ly=Ly;LxN{@OzASyDUyFVyGUzFTwERvDRxESxERxESwESuETuJYxKXxGTvEUuHVyFT{DT|BQ}=P{?R}?R}?R}@S~ATBRBP?P~:L|9K{8J|5G{4Gz5Ez7Iz8J}7L{7M|6M|7M|8Mz9LzQ|>Q|=P{>Q|>Q|@S~?R~ATAT>R|@R{@RzCSzES{DTzERxCQ{DQ|DR~ETESER|ES|ES|DRyCQxCQwDRuBPsCPsERuERvGSuJWzOY{Q\|S^}Wb[f`lfolsnsotpvy|~z~vzszt{wzy|~~}}~~~{yyz{{{|z{{{}~~}zyuussvwwxwvussqrrrqpn~op}n}m|kzkyjxiyiwjxjyfufugvhwkzm|o~op~tttttttvuwwy{xz{||}ɶѼ̾¼ľ˽ƿĖƗȖǖǚɝƝšơťŪƨŨǨǩȩǩǬǮɬǬȬǬƫƬǬǬƭ˭ȮȮɬDZƹȸŸǴIJǭɬȫ˫˪OUXNRTLPQJPPLPQIOQLRTLQTMRULPSJORFJNDIMCHKEIOBELAGLAJM?HJAILEO>DMGN>EO>EL=DN=EO=DMDP>DP?EO>FO@IR?FP@GQAHR@GQ?GQAGVAHU@FR?EQ?ER?FT=DU>FU>ES?EP@ESBFQ@FP?EQ=DP>FQ=FP=EQ>DQ>FQBGTAGT=EPBKSCJTBHTCKUAJSCKUFOYFNXDLWHLXHMXHNZOS_OS_QUaPT`TUcVXe\\fcbjggihiikjkkjlnmjvqozum}ym{wj~l~m{nwysvvwlowgltehqeitdkwciucjtahr[bm[bnZbmX`jW^jWamX_mT^iVakYcoZcp^dr_eselyvwuyeo{akw_kxUdqTcqWbpW^nZap]cqZboZ_m\`jadq^aoW[gQWcPWdQYkSZlSZlOVhNUiJQdIPcGNaGN`LRdQXjNVhLVgMTfOUiLSfNUhNUhMTgLReKQgJQdIOcFNaCM^EM`CK^EM_HPdLRhNUkPWoMYlN[kP\rR[uNVlHPfFNeFNeEMdCKbEMcDKeCJc?Jf@Lf>Kc?Hd>Je?Jf?JfBLi@Ljq.?q0=q-?q/?r.=q0?s->r->r->r->r.?t0Au.?s2Bv1Bw0@t0At1Av2Cv1Cu1Cs0Bt3Bs4Du3Dw4Fx2Bu3Ev4Fx6Gz8Ev9Hy7Gv6Hv6Hw6Hx5Gw6Gu7Gx;HxJzKx=Ly;My;Mx>Kx=Lx>N{Q}=Q{>Oz=P{?P{@Q|CR|BQzDSzFUzFSzFTyHV{HVxGUvFSwDRvDQuDSvESuKXxMYyLWxGTvHTwESwDSyCQ{@P|@Q>Q|=Q{?Q}>P~AR?L};L}9K{9L|9K}2Hy1Gy3Ey5Gy7H{7Iy7Jz4Jy6Kz5Jw8Jx7Iw9Ky:Lz9Jy7Ky6Kw6Kz:N}=P|Q{=P{=P{@Sz>PwBQuCRwCPvCQvBPuBOvAQyCR|BQzCRyCQw@NtAPs@Ns?Mq@MqAMnCPrDQtGTvGVwKYzNZ|OZzP]}P^zR_zUa|Zd^hbkeldnjrnuoumvnvkukvpxp{mzp}uxz{{}|}}|{yvwwvwxzwwy{||{{|zwusprsrwvtssrprqsrpoqq~q~n~m|l{kykzl{l{kzgvguhvhvixixm|n}m}prrtttsvvyzyzwvwvvwy{|~~Ƽ¾ƹƾʲěÔŖǖǖƜáäĥç«ĦéƦŨȨǨīįƯȭɬȫȬŬǬȭȭǯIJƮƮdzƸƽɸȶĴǮǯʫɫɩTX[QXXQWVPUSQUTPTSQSUSTWPTWOUTOUSNQRLQTKNSMOTKNSKNRFJPGNRFMODKOAIPCKQEOSDKRDKUFJRFKQBIQBISBJTAJTDJTCKTELWHOZGNYHOYFMUENVBISEKWCIX?ES?FP?EP?EQDP?FQ>EP@FQCFQBGR@FR>EQ@HR?FR@EQAHSBIUBJUBJUEKWCJUEKWEMXFOYINZGOZGPZLR]QTaPS^NS^MT^PS^QS`SUbTWcWZf[]haaikjlwtlxujwsjvrjxti~xl{l~ilosnv{quvlpwcgrbfqcgraft_dp`do`frceqV_jR_jU^hW_iW_mZaqYbmZbo\do]er^gt^esgjxzyjo{cmybhw_ev[dqXcnZ`lY^kV]hY^j\bo[an[`lcfrbfr[_iZ^jSYiNVhOViMTgKReGMaEL_GMbGMcHOaJPcNSgNVhMUgMTgMTgJQdLSfKRdKReIObIOcIQdHPcEM`EOaDM^FLaIPeMTiMThPWkNYkN[mO\nMYoLXpFQfEMeFLfDKeDMfDLeDKgCLh@Id@Je?Kf?Jf>Hc?Ke?Jf>KfAJfALi>Lg=Lg=Mg;Jij*o,=q,>p,>p-?p,>r->s(q*>r.?q/?s1@t.?s-?s,?r->r/As0Bt1Ct3Bt2Cu2Dv1Cu2Dv3Ew5Gy4Dw7Gw9Hv8Jx8Jx7Iv7Ix7Hy6Hv:Iz8Hw:Jx;Iz;Ix;IxLz>Ly=Lw>Mx>Mx?My>MwP|?O{AU|BR{DS|DR{DRvDRxFTyESyFTwGUyGUxFTwGUxFUwETvGTvIVwLYyLXxLYzJVwEQtDQtESxDSzDS~APz=Nz=Q|PzAQxAPtAOrCRuBQtBQsAQs@Oq@Ns@PuAQuBQvAPuERvDRwERuGTwIVyHUvIVyLYzNZzNZzN[{P]~P]Q^}Q^~N[zN[zN\zR]{T_|Xc\f^i_k`mdpeshtgvhwlvjwhvjxm|suvuvuwzyywqrsrtuvuutwxzy{yxwspn}n}m}nuuuvssrssrrrrtrqpnm}m|m}m{lykykwjwlymylzjykzl|m|o~pqpqqrrtustwxxvwvvxwxxwwyyx|}ö¼ƽřǕƕƙɜéũç¦ħƧǦȧǪŭñİȰǰȮƱȮǮɮµĶŴdzĵŷŹǹȷƵŴưɭȫʪ\`aY__W\\UYXUYXUXW\]]]^_Z\]UZXTWXUVXTVXSTWUV[QUXOSWNQWNRXOSWPRVMPVLQWMQUKRWLSYMRWMPVJQXGMUHOXHOXIPXHOVLRZKRZJP[IPYIOWFOWHOYGMYFLXEKWCKTCIT?FQ?GR>EO?FP?EO@FPCITDJVBHT@FRBHTBHTBHTDJVCKVGOZHOZJP\KO[LQ]IQ]EMXGMYHO[JP\KQ]RUaSU`QV`PW_UX`X[dY\e[^g^`jedmhgkomj{shxmxl|p}o|lmlmlkqszynnorgit_dm^_m_bl`cj_blcgrinuvvrosp^glahqYdnYalYapZbn]dq\eo]fs`ivaguagrfhuhm{ajv_er_er]dpW`kV\h[`lZ_k[_k_an]_k[]i_`l^an[]iW\gKRbJQcHObIPcDL^CK]DJ]DL_FMaEO`HPcKSfHPcJQdJQdIPcHObGOdGPdDMbCJ_DL_FNaFM`JPdIQdHObJPcMTgMVjOWiOViNZlO[qLXnFRhDQgDNgBKeBJfAIfGdAJf?Ie=Id=HdIe>Je@Kf>Jf=Keo,>n/>o.>o-?n.=n*:k*q+q)=q,

Lx=Lw=Lw>Mx?Ny?Ny;MxP{;M|;K}:J|Nr>Ns=Nr?Mr?Ms@Pt@Pt@QuCTuFSuEUtIWvJWwGUtCPpAPoCRsDRuEUvMZ~LZ|KY|N[~N[~N[}N[~MZ|MZ}O[{OZzP[{L[wN[{N[{N[{P\|P]}O\}P]~R^{R]}S_U`VbVcYgYg]k_m^l`oboerfsftfuhvm{qqqn~nssspm}n}po~n|po~rsrtuuwvvuutro~m|k{m|m|twvwwxvsttttrrqrqqn}n}n{o{n{jyixkyjymzm{kyl{l{m|o~p~n}o~qqr}s~r}q}rt~tusttuvvvuvtvvuwz{||~̼ĝŢĜřŗǙșȜĢè¥ĥťŦǤʧɪ®òóijĶûǺĵ÷ļƸŴĶŶǷǷŸȷƷǴŭȮǯDz]``Z_^X][X\]\_^\]\Z\[[]\_a`]^]\^^XYZWYWVWYUWYSWYSWYQTYRUZORXPSXPUYNRVPTXOTXMUXPVYQTYMRWLQWIOVKRXJQVKPVLPVLQXHPXJQYIPWHNVGNWHOYIPZHPZHNWILWBHQBIRAHRBGRDHSDHSCIUDJVEKWDJVCIUFLXGMYGNZIQ\JR]HNZJP\KO[KQ]KT_IQ\IN[HNZKO\NR^PT]VYbUXaX]fW[d[]i\^j[]ibblkjpqnmuqkwsi|uk}ui|sf{ui{ti{uf~wfyg{imvpwwkgjlado]ck^`lcfpbhnfisfhwrtzwzt}~x`jlYbk[cp_fqagtcjvflzgn{`hs_dpdgtchw_fs_epbgs`fr`fr]co[`lY]hUYdVZf]akW[dX\g[]i\^jZ]iKQ^KRcIPcFM`EL_EO`EM^EM_EObCQaDN`HPcHPbFNaDM`BJ]AI\AI^@K_BMaELaEM`GObJPdKQhIRdKSgJRdKTeMUhNViNVjLVkLYqMYpDPfAMdALg>Id;Eb8C_7E_9Ea;Fb=Gd;Fc;FbIg;Jf;Jg;Ji:Ij;Ii;Ji;Ji;Jk;Kk=LmMn=Lmp0Ao1@n2?o2@q/@m-=k-=m,>l->m0>m0=l0>m,>l*>m)=l+n-?o,=n+=m*>n,>q-?p.?q.>r-@r,>q+>p+?q-?q.@q/As/As-?q/As0Bt/Ar0Br1Cs1Dt4Eu7Du9Gu9Hv8Iw7Iy6Hx7Iy5Gw7Ix6Hv9Kw9Jx:HxLy?Ny=Lv>Mv=LvMzFY?GZBI\CJ^EL`FM`FNaHPcIOfIOeJRdJReJReJTgKRfKSgMUkOVpKXpEQiBMf>Jd:Hb6E`6E`4E_6C^4D^3B]6B^8B^7C`9Db:Ec:Eb;Ec9Da;Ec:Fc;Gfm/@n1?n2@o.>m.>l/>m-?m,=k.?m->l-?n-@p-?n+=m,>n.@p.>o.@o/@q/@q/Aq.@p2Ds1Cq2Ep4Cu3Bs1Ar0Ar2Dq2Cr2Bs1Bt2Bs1As0Aq2Ds3Et1Cr/Aq0Bt2Dv4Fw1Dt1Cs3Dt3Eu5Fu7Gu9Fw;Ix9Gv8Jx6Hx5Gx8Jx7Iv7Iw7Ku8Iu;HuLy?Nz>Lv>Mv=Ku>LwKx>KyNw?Mw?OwCOzDP{ESzFTzERwEQwFRxGSzDSxDSuFSuFSuFUvGTvHTuKVvLVzKVwLWwMWvLWuKVuLWuLWvHWsJXuM[yN[yOYzLYzFTrCQvAMx=Lw;Mx:Kw9Mx8Lw9Ky9Jy7Iw8Ku7Jv7Hw6Hr7It9Ju:Hu:Ir9Kq:JnNmCSqXb|lrntekfmoudi\a{YazU^pNYnR\tR[wPYxNYuO[zMXwSXvU[wT[xT[xT]z\dgoU^|U_Q^O\~O\LZ}PY}OY}LX|JYzKX{LYzMYyO[{OZyNYzNY|MYyOYxN[{N[{O\{N[{P]}R_O[}M]~O]N[}N[}M]{Q^~Q^~T`U`Q]}Q_~S`UbYfYi[j`nesduhxjzhxiwhwevgvgvfufufuetbqcrful{n}o~o~n}n}o~o~prrqn}l{kziukyl{o~pqsttssrqttrqqq~q|q{mxmzlzkxlzkylzkyjxkyjxkzmyq|r|q{q|q{p~q|q{m|oynyp{rzo}m|m|t}pr~rpsrswwvwwyy|}ÙƗŘŘǘƛߍġ£äţĤĢ©¿ĺķĶǸƺǺȷƶɯȬȬ˭]a`]b^[`\]_]\^[Z][[]\Z][]_]^`Z\a[Z_Y^_]Z[]UZWUZYVYXVXZXY]WY]TWZTUYVW[VWYOWYOWYOVYNRWLSYLTXLTWKSXHPWMSZPS\PS^ORZQT\ORYORWNSYOS[SW`QU_KRXMRYMRXIPVHPXKQZMQ\NR]JP[MR[LS]LS]LS^KU]LU_NV`MWaMW`OUaOUbNXbQWbPVaQXaOWbS[eSZcX\eY_f]`j^bk^bj\bj\_h[^e[^c\cfcfnjinqnkwuhwhzghigfikkqlnqusu{{~~tpsruvy}tquy}gquchxbiseixdjvciwbhuditbitcgtbfqaepZ]h[]iZ\hZ]jW]g[\gZ\gdairmlusitshwvnvwsijnfgs\`hR[fOWbKScHP`GNaJQcGN`EPb>L]=FY@FY>EY?G[?GZAH]CI^CL]DL^EN_FN`FM_FNaEMaGOeIQdIQdGReGUhJUiISfKViJVkEQh@Ld:Gb9Fa5C\4E]4D^5D_4D^3C]5C^4B]7C^8B_8B^9Cb7Eb8Dc9Dd9Dd9Ee8Ff8Hh8Gh8Gj7Eh7Ej9Hl9Fk8Ej8Fi9Fi8Fi9Hl8Fk9Fk8Fj:Hj:Hk:Hj;Ik9Gj;Iln/?o.?l.>k->j/>m,>l.@n.?m.@n2@n2Bo1Co2Bp3Dr4Ft5Ft2Do3Es5Gu3Dq6Gt7Gu5Ft6Gu4Er2Dt5Fv4Iw7Hv6Et8Fu7Fx7Gu6Fv7Gx9Gv5Gu5Et5Ft3Gr5Gu5Gu4Fv4Fw4Ft5Hw5Fv5Et7Fx6Hv7Iv9IzKx>Ly>Mx=MyOw>Lw=MxMx:Kw:Kw;Jw9Iv9Iv:Jv;Kw?Ov>Nt?Os@PtCQwDRxDRxDSxEQwFRxFSxCQuCRtETuFUvFUvFUvFUvHSvJUuLWxKUuLVtKVtLWuLWuMXvNZxLYvLZvMYvPZxNXvPZ{IVyCQu?Mq?Ns>Lt?MwJ[@HZ@HZ?J[AI[CK^BJ]CJ]CL]EM_CK^CK]CJ^DK^EM`GObGReJTfJUhIViIViKViITgDQd@Le;G`5C]5C^7E_5E^4D^4C^4D^3C]3B\4B]5C^7Da7B`8Dc6Cc9Ce6Ce5Bc5Cd7Gh7Eh6Cg7Eh8Fj6Dj7Dk7Di6Ei6Dj8Dj6Dl8Fl7Fj7Ei7Ek8Fj9Gi9Fk8Di9Gl8Fk8Fl:Hm7Ej9Il8Hl8Gm8Fk9Gm7Fl7Ek7Fp8Gr7Fq7Fp5Do5Bo5Cp4An2Cq2Cq3Bq2Cq1Bn/@l0Bm0Ao0Cp1Dq4Et6Er5Bo7Dq6Fs5Es7Ht6Hu6Gu7Hs6Hv5Gu4Es5Ft7Hv6Gu7Hv6Gu3Ct4Fw4Fu6Gt9Gx9Gx8Gw9Fu7Hw7Hv:Gv8Iw6Gu5Gu4Hs7Hv6Gu5Fu6Gv4Ft5Gw5Fv6Fv7Gx8Iw:Ky;Iz;Ix;Iv;Hu8Iu7Iw6Hw7Iw9Jv7Ju8It;JuMxMx?Mv>Mr>MrLwMx;Nv;Nu=MtKx?Mx?Nw?Nu@Ot?PsBRuCQvCQvESxERwCPtBOsEQtDRtERvFTvFTvFSuFUvFUvGTvKUwKWvMWxLVtKVtMXvMXvMYuPZwOZuOZvS\xTZwT[wS[xPZzJWwFSuDQsCRtAOs?Nt>Mt>Nt>Nt;Ko;Mo?Op?Mo@RpEQnIUqIWsHWkT^qw}mqry{uwlogmah|bi}`h}ah|^ew\au`dych~Y_uW]vW]yU[zMWvMVuMWuMVuNWvQZxRXwSYvQYvQ[yOZyKXzJWyMZ|LZ{NX}NW}LVzHUwHUwKUxKUxJSwITwKTxJTxHUvHSvHSuLWwMXxNYyIVwKXzN[~N\~N[|O\~N[}MXxNYxLWvMVwKXwMZ{P]}O\~P[{OZzR\{O^zO]{RbVfWhYhZi[jZgZi]l`obqbqcrbqbqdsfugvgvetfugviwixkyl{m|n}o~o~o~m|l{jxhtgujxkzm|m}o~n}n~o{n}m|m|l{n}n}n}m|m|kykykyjxjxkyl{kzkylynxmxoznymxnynxmxnymxmxmxjylzmznylzlzn|m|n{m{lzoyq}p~p~prrttruussuuy}~֘›¡Ţ¤ſþſĺ÷ƸĸǵŶıƮʭǫǬ^c]_d^[`Z]b\Z_[Y]\Z\[]_^\^]\^][]ZZ\YW[ZTWXSWVUY\SZYRVVUXZTWWUWVWYXWXZYY]WZ\TX[RVYSXZRWXRVXRVZRW[RX[RW[TW]WZ`UZ`VZ_W[_X[_WZ_Y]`X\_X\_UY_X[_WZ_SV]RUZTV^SV]SV^TV_UX_UY`VYcTYcW[dW\cY\eZ\h[_hW^fZaiYak\cm]dm[blZ`k^`j]`i]_gcagbcihjngjlfflefndglfinehmhgmonqvtpxmoqqtqmoopru|ws|{z|yx~zyzyuv{z}}govagsdgs_doX]hY]i[_kZ^jX[gX\hU[gU[gY\hW[f[_i`clggl}sovvwnjxxsmnp`afTX_PT^JO[FLYCLVDKXEM[FM\IQ^EL[@IXBKZCJZBKZCKZAHXBIYBIYAHZBJ\CJ\FMaHQcIReHRdHQcHReJViKXiJUgEQd?L_Mx=Mx?NyLp=Kp=Kp@NqANv=MvMv>Mu=Ms=Ls>Mt?Nt@Ov@NuAOt@Pv@Ow@NyBN{ANy@NvAOwBPwCRwBPuBPuCSwDSvESvCQtFSuERtERtGTtERuFSuERtFSuFStHUwIVyKUwLXxLWwNYvNYwMZwNYwPZvR[wR^xT^yT[wVZwRYuV[wW^{V^{T[{OZvP]xM[zKYyIVwGTuFSrHUtHVtP\yS]zYdvy|~~~nuzZcptzwxmpmoosiogncm`kal_i~\cxY`uW]tUYtV]tW_wV]wW^{OXvMVvNWtNUuNWvMVuMUuPWvOXxOZzLUxHVxJWyKX{JWzKT|LUzKVyFSvGTwITwJSvIRvHSsIRuJSvGUuFSvFSvITtKVvITtHUvHUwJWzIVxKXzKXzKXzMY|LXxKUvJVvHUuIVvKXwKY|LXxKVvNYwO[xN]|Q`Q`RbTcVeWfVeVeVeXgYh[j\k\k]l`obqbrbrcreufweugvhwjyl{l{kzm|m|l{kziwkvitjuiwjxl{n}m|m|m{m|l{kzkzkzkzjyiyhxkyivhvguiwjxjxjxiwkxmwlwjumxnznzoznznyoznynynznykylzlylykxlymym{ozpyo{q|n|o}o}o}p~q~qttqs~quuvy|“ȴĽ»¶ĵıƪȬȨŪ]`ZZ_YZ_ZY_[W]XW\ZXZZ[]\[][XZZZ[YZ[VY\XW[XUZVVZ]UXZUY\RVYTY\SWXVZXWYYYZ]YZ]W\_WZ^VZ\W[^UY\UZ]TY\SW\TV[WY_W[_U]`U]`T\_W\a[`aZ_b\_cZ^bZ_bZ]a[^cY\cWYaZ]dWZ_WZ`Y\cW[bX^dX]c[_h\`f_bh`ch_bi`ck^cl\em[dn\cl[cj[bi]ekbdkbekcgmaehegljkqjkpcfkdgncflcfleingkpnosusx{wu{swzxyvxwvwxuwuyv|{{~{wnrsp{zqfkk`enegsfhubfo`do^bn\am[_kX\jTZgRXfSYe[_k[_j_bkafpkjl~qwísðxoromhigjim\\`XZ`VZcJOZIQZIPYIMYLQ^NR]IP[JQ]LQ^RVbTZfRWcQVcQXdKS^HQ^IQ^IP\GP^GPaHRcLUfOVhPWjLUhHVeHQb?J[Mx=Lx=Lx=LuKq>Lr@Np>LqMy?Lu?Nv>Mt?Mt?Nu@Nr?Mr?MrCQv?MsBPt@NrBPuCQvBPvDRxCQuBPuDRwCRwCTuDTvESvCRtCSsFSuERtERrFStDQqERrDRqESpFTtFTtKVvJVvMXxMWuKYvMXvPZwTZwUZwR[vS]xTZwTZvS[wT[xV]zX_|Z^~Y_zY`zV]{W`}Za~Z`~Z`|Yaz[c|]d|]bxornoimkqjoiojrkrkserbm`l]j\e{YdyU`xV]uU^tS^vV\vT[xOYwPYxNWvKTsLVtKVsJUuJUsKVxJVxGUwESvDRuGTwJWzLTzJSwHUwESvDRuDQuFRtFPtGPsGOuHQuGRuERtDQtCQrDRsCPpDQtGTvGTvHUwFSuHUwHUwHUvIVvGUsFTsFSuGTuGSuHTsHTsKVvMWwNYyLZ{O[xP[yO]zQa}TcUdSbTcSbTcTcTcVeVeVeVeWf[h\i^k^l`pcseuhwiwmyiyjyixl{kzkzkwjrhrgrhsisgvjxm{mzn|m}l{l{l{jykzjzlzkyixjyjxiwhviwjvhvhvivhukwlxlxkwmymynzmyo{p{mwpxnzmynzozozoznznzlykyn{o}p|q|q}p}q}r~ttwxy{’¾øµĬîʪƩ§¨\_YY^Y[_Z[\XZ\X[^[\^\\_[[]YZ]Z[]YZ\WZ\VY\VV\UU[WWZ[VY\W[]W[^Y]`W[^W\\W]]X\]W[^X\^UZ[YZ^Y\_VZ]UZ]X[`WZ_Y]aZ]bZ^cW_bY_cZ`b^bc^beadh`ag_df`dg_be]be^bd]`c\_e\`d\_d^bf^beaelbgnaenackcelafoafl]bh^clbgn`flbhncindkpcgkdgmdglehmdgikkonmrjnshkqjmsnoulqtmpvmpvptytvzvwxzyy|{{}{}~z~x~~xyy{}}}~}||z{z~|zzyvptphwqc|xkqtkhmpegregs`dm`elabo\^jZ^iU[hRXeW[hY]iX\gZ^i\_gcensos}|qlfmhdolijjdefeiggaabeeghfbiig`ce\`fa`cX\_QW^PV^QU_Z[i\`jX[cX]g^_k\`jX]iS[eSYfRXfV_lU]mS[mRYkOVhMSfHPcDM]>GW;DV;DX8CX5BX3AX6BZ6AY5B[4B\3A[3A\3A[5B\4B]3A\3A]4B_2@^2?`2?a1?a1?b3Ae3Cg1Ah2Aj1@j1@h1Ag2@j0@i-?j.?i3@m2?l0Bm0Bl.@g2Bi3Bi3Bk2Ai3Bl3Cn4Cn5Bo7Dp7Dp7Eq6Ep7Ep6Eo6En7Eq6Eo6Ep7Fs7Fq6Eq6Er6Cq3Dp2Ap1?n5@q4An5Cp4Dp2Ep3Ep2Ep0Cn1Cp2Co4Eq5Dq6Dp4Dp6Cp6Aq5Br5Br5Fs5Gu4Et1Cq3Es3Dr5Ft5Fr3Dr5Cr5Cr6Es3Er5Dr7Dt9Fv9Gv9Gv6Fr6Gr6Gt7Iu5Ht4Gs5Hs6Jt5Is5Gr3Et5Gu5Fu4Gt4Ft6It5Fs8Hv8Iu9Jv7Hu7Ju6It8Hu9Ht8Iu9Iu8Hs;JtLu>Lu=Lt>Ls=Kq=Mq=Mp?Oq@Nq?Mp>Mn?OsJv@Nw?Mr?Mr@Nu?Mr>Lr@Nr?MpAOr>LrAOsANqBPsCQuDQvESyESvDRvFTyDUvEUwDSvCRsDStERtERrFSrERtESrFRqFRoGSqGRpGSqGUsKVwKUvLVuLWuN\wP[xPYwQ[vR]wQ^xS]xT[wS[xRYvUZzW]zW^{X_xX_vW_uX_{Za{[`x\`yZav[cx_fxah{tq~glcm~_j{cjflfmirkrlskvetfrdn^jZe~Xe|UbyT^vS_wT\wS\yOXwMVuLUtJUsJUsJUsJUsITrLWwGSvBPrBPsCQuDStGUzGUxGUxETxDRwCQt@OrBOrDQtFNrFOtDPtCOrDPrCPqCPrDQsCPrBOpDQsERtGQtGSuFTvFSvDQoDSqETtERsDPsFSuFRuFRtERsHStHSqKUsLXsLWsNYtM\vMZwQ`}RaSbQ`~Ra~Qb~RcTbSd~Ra~RaSaQ_~R_UaXbZfZi_m_obqftfsfufuhvhwjykykxhugshsgrdsgrerftiyjxm|n|p~qq~o~n~n~o~n~n|n~kyk{jzmylzjxlxkxlxkwlxjvlxlxlxnznymymylzoznzmynzp{o{o|r|n{p}q|ur~p}p}ruywxw’“¤ŧƧħŦƧad[^c\^a\`b]_a]_a^^_[]`Z[`Z[a[[`Z\a[]a^\a\[`\[`\Z^]\_`\``[__[^`]_`\^_^``[^_Z^`Y]`Y]^\__X]]Z__[`a[^a_ce^bcadf_dg_eh_ee`eecfjcgibfibffcgfbfechfdhfdigfhhegiehladkcelbgi`eichmbfmgirehoehodhmgjrgjtejpeiochncgnfimfipgjpfinjmrnprqrurswmquorvnputuysuvuvyruyqtyuz}w{||~~~~os|kp{hmuuuwwj~glnvuywjnqegrcfq^ag^_f\`i[alYakX^iY]hY]hX\gW\f[^h\_gdehvnlngekdbmiiedc`b`ccamlejgckhemh`kh[zdm~xawu_|f|flibXZbRW]W\ccfikhilko^biY^fYZgZ^iY_k\amY_mT[lOXkKSfJReFM`@FY>GXf1@e1@c0@d0?f,?g,j/@l.?j/Aj.@h1Aj1Aj1@k2Al1@k1@k2Al2Al3Bm3Cm1Bn2Cn4Cn4Cn4Cn2Bm2Co3Dp4Dp2Do1Cn2Co1Bn0@o2@o5Cr4Bo3An3@m3An1Co0Dn0Cn0Dn2Co4Fq6Fr5Cp5Cp7Dq5Cr2Cq4Bq3Es1Cq1Cq1Dr1Cq2Cq4Es5Ft4Es6Ds6Ds5Cq4Br6Dt6Br6Et8Fu7Et5Fr7Eq7Gs6Gs5Gr5Gs4Fr4Fp4Gq5Hs4Gt6Hu8Iu5Ft5Ht5Hs8Ju7Hw9It9Ju8It7Kv6Ju5Hs8It8It8It7Gr=Lw:Ir;Iq=Jr;Ip:In;KpMr?Mq?Lp=Km=Np?Np?Mo>Kn=LlLq@Ns?Mr>Lo?MpAOq@Nq@Nq?Mp@NqBPsBPsDRuCQtBPsDRuCPtDSuDTvCQtCRsDUuDQrERrFSsFRsGSrHSpHSqHRqHQpISqJUsLWwKVuJUsLYuLZtO[vP[vP[vO[uR]xQYuQXsS[xQYySZzU[{V^zT[wV]wW^xY`{[b{Zaw\ax^ey`f|`g{af{uvspzhj|dh|_iy`l}dkfnksowmumslxiwgsgsbn^k_kZgV`{P\wQYwQZvMVuKTsKUsHRpHRqHSqHSqGUrHTuDRtCQtBPr@NsBRrBPsDRtCQtCQtBPvAOq@Nq@NqANqAMpAMrAMs@MqANsANqCPsCPsBOr@NpAMp@NqBNqCQsBRrAPqAOoAPpAPqCQsCRsESuERtERtDSqFRrITqITqITrJUsLVtJYvLYvM[wL[xN]zO]zO^{Sa~Ra~Sa~Q_zP^yP^|R`}Ta~T_U_Sa~Sa~TbVc\f\h^k`nanbpbpcqcsguhvhvgvhrgrftgqgrfrgujxm{m{o}o}o}o|n~n}n}opo~o~n}l}n{n|m{ozlxmylxmylxmymykwnylyrqo{myp{o{n|o|p}q~r}qtt~ttv{slevUk`_xXg_XjSH]J\n[i}i^pm}zæéæğǢƢǥ§¦adXce\ad\ad^]_Y_a[^`Z`b\]b\]bZ_d\^b\^c_^b`_a_]`]^a^_`_]_\`a^_a^_`_^^^]][^a]X][Y]]Z^^[]_Z^`]ac\_a^^d__ebcfabbaeebceceeaedcgecgdfkghmhiohlqjjpkhmhkmjkmjjjjhjjdgmdgmeikdilejnglrhkqhkpjnphlogjpdgnegoehncejikpkpsmpumptilqjmrnrsrtvmosorutuyuvztwzuvuzzzyzzy||w{}z}}|~}~z|}wy}mqxjp|dnxdkvmpv~|wzltlokq}|ijpmnsthlpgloafk\`i\blZ`k\al[_jVZeRVaRW`SUcX\eY^dXZc[`g^`fbbfbbf`_fgceyht\{ae`^^e~\yYfijg^WZhZ]jsprtzoiij_bka`k`bn^`k\`lTZhNWfHQbDL_@I]>F[=CW9AV7?T7?R6?Q7?S5?X4@Y4@X5AY3@Z3A[1AZ1@Z1?Z1?Z3A[2@[4B]3A\4A^5@^5@_6Aa3Ac3Bc2Bd1Ab3Ad2?b0>a2?b0?b1Ad/Ag,=g,=h*;g+=i,=h->i/Ah.@h.@i2Al0?j1@k1@k/>i/>i/>j/>j,=j->j->j.?k0Al/@l/@l0Bm/Am.Al/Al0Am/@m/@n/@n/?m-?k1@m2?l3@n1?m/@l/Bm0Cn2Bn2Co5Dp5Cp4Bo5Cp3Ds0Bo0Ao3Dr2Cq2Dq2Cq2Cq1Bo3Dq2Dq5Ft6Cr6Ds5Cr5Cr6Br6Cs7Fs8Fu8Fu6Et6Dp6Dr8Ft6Dq4Eq7Ht5Fr5Hs5Hs5Hs7Ht6Gs7Gs6Hs7Iu7Ht9Hw:Hu;Ku9Is8Jv8Ju6Hs8It9It:JuLm>Lo>Lo?Mo?No@Mo?Lp?Mo?Mr=LtLp=Kp?Mr@Nt@Ns>Ln>No?Mp@Nq?Mp@Oo@Nq@OqBPqCSsBPsCRsBQsBPsFSvETvESuDSsDQpGRrFQpGSpGRpFQoHRpHRpHQpHQpJSsJUsITrJTsLWtJYsMZtLWsNZtO\tO[tQYuQWsOVsSXvSYzUYyV[{V]zV]yT[wU\xX_xY`yZax[ay[`u]cz_e{bg~cezac|fiil~_dxahzelhpgoiqowrzpxlwjudreueqco`mYfYb~R^zOYyNWtLUtLVuJUsJSrHQpGRpEPnDQpCQpCStDUuDRuDRvCRrBPs@PpCQtAOr>Lr@No>Lo?Lo?Lo>Ko?Jq>Kp=Kp>Io=JmLo>Lo?Mp@Nr@NqAPq@Oo?Mo>Mn?No@Mq@OpBRsDQsCPpDRqFRqITrITsJUsITrJUsIXuLXwKYvKYvM\yN\yO]zO^{O]zP^{P]xP^xP^zR^|U_}S^|T^}Q`}Ra|U_}U`Wa}Yd[e[hZi[i\k^m`ocqftftesgritgsfqhshsgsiujwjxm{n|o}m{m{jyk{l|m{n}p~o}o~o}o|p{q}o{o{o{nzo{nzmzq~lzOaZ\n^x{xyssuz]nQ[sOLdEA_@8U4OeI>T@BXC@Y>wvXji:SBnp ŸccWddYcdYbeZce[bc\ab^cb^bc\cd[bc\_`[_`Zab_^`^_a]]_]^`]aa_ab\^`Zab_`a^bb^`b[^c^^c`ac`^a^_a_ac`ab``bc_a^cfaeh`ce]ggahhajkclndoqfpqhnohikgknigjghkhkmhlkhmmknnlknmlnplpnimqhmokpsjnqjnqimnhlmgkmfiohlnnptlmommpqswmpujmtilrlosmqqptwlpsnrutsxttysvyvuyzzxz{~y{~}{y}mqtkp{ip|joyin{iq{kq}nu}tv~|~|svpsqutunmz|rfjp_bk[_iVXdVWcSTbQS`OR^OT\UXdVUcV\_X[cX[aZZcZYaYZ`Y[a^[`zkomdfgdba_hi|j`b`[\^sked}lupmgishjqcfq^_k\]iQVaEO[BK]?GY=DX8@U5@W5AX5@W4@U4?U5AW4?W2AX4@X4?Z2?Y2@[4B[2?[2>[4?\2@[2@[2@[2@Z1@]2A]3@_5@a3@b3@b2@b3Bc2Bc2Ab0?`0?b/=`1Ab/Ae-@f,=g+j.@g,>d-?f2Ak1@j/>j/>i/i->l,=i/>k1>k.@l->m->l,>k+@j-?j/?k/?k1=m0@n0An2Co0Ao1Bp4Cq4Bq2@m2Bn4Ft0@n0@n0Bo1Ap3@o3Bp3Bq5Cp6Dq6Dq5Dp3Dr4Es4Es6Ds6Cs7Ft6Hs8Fu7Et8Fu5Cs8Fu8Es7Er9Gt9Fs6Fr6Ht6It6Ht6Fs9Ht9Ft9Gt8Gt:Gt9Gv;HuLo=JmLn>Ll?MlBMl@Mm@LnAKnANp@Mo@Ln@Ln?Mp?Mp>LqLs=KoLq@Nu@Ns?Mp>Mn@Nq@Nq?Mp>Lo@NqCNrBNpBQrCRsBQrBQqAOpDRwESvDTuDRrHQpIQpHOoHRpIQqHQpHQpGPoGPoHQpIRqJVtJRrKSsLVuKWuLVtLWuKVrLYsLYtOWtQWtOWuP[uPZwQ[zS[{TY|U[ySZvT[xW^xX_zW^xY`yX_yYax[cz^e|\by\bz_gxbi|^fycj}jquzpwnvs{wt|lwkwdr_pcobm\kXeYc}R`zOYvPXuLUtIUrJVtIRqHRpDOlBQmDNo?Mm@OpCRsFTvCQuBRqAQrBQrBQrCQtAOr>Mn=KnLn=Ln?Np>Mo>Ln?Nm=Lk=Kn>Km?Kn@MnANmAPmDQnHSqHSqITrITrJUsIXtKXuJXvLZvOZwL[vN\xO^yP^yP^{P^xO]xP^yN\xP^zQ^yT_{S_{T_{T`{U_}Rb|Uc~ZdZcWgWfWgXi\j^l_m`oapeqgriuhtgshtiuisjvkxkylzj{jymxmzkzmzo{kzn|n|m{m}p{p|p}r~q}r~q~s~q|stV`ZH]FTlIUmG|hyj~w|{tvObJPgH9O95O32L16Q4)A(0D2EZFMdI=V9kg]|OaIpe}_XqSs]tmsirsnhcdYdcWffZbcVbdVbbYdd]db^cc^cc[ddZddZddZbd^ac^_bZ`b]`b_`b``b^bd^_a\`a\``\^b]_a^``]_`^cd^ef`cd^de_cd^egaik`jl_nl`qqcoobrobusfrqdopblnbmoknokpqkpsmrtnqqqqropronpllnmlnmprolpmjnmpqtprsotqmqrnquostnptqrrstquttrrsnpshmrinrkptptwqswnrunruprvptwswzwvy{{w}}xz~vy~yuwqjoxlo|mq~qwrynwjq|tvz{{txvrvrptrstytodefX[cRVcMP\SU`hioSV_MQ\QTaWXcUWaSX_SW^QTZORZPS[QU\TW^]^be`dkaZu^cfbccecdx^rm}kppntqkdfj[^gWWdPQ_QT_PV`KU_BKV9BO6AQ4?S3@U3@X4?X4>W2?W3>W1?V0?T0>W1>X3B[0>Y1?Z4A\4B_4?]2?\1`2>a/=a1Ad2Bf/Bg2Bk1@j.?j.>h.@g,?g.>f/?f0?h-h,>j->j.?k0>m/?n0Ao1Bn/Bn0An3Ao2@m4@o2Bp1Bp2Ap/Ao3Dr1Ao2Ao4Br6Bp7Cq5Cp5Cp5Dq4Cp5Cp5Cr6Dq5Cp4Er6Dq8Fs7Es6Ds7Et7Fv6Gu8Fs9Fs9Gv7Ft7Ft6Fs7Hw7Gt7Er8Er8Fs9Gt8Dt:Hw:Gu:Ht;Jv:It:It:It;Jv>Lw=Ls=Lp=Ko>Lo?Lo=Jl>Kl@Mo?Ll@LjBMkBKjALj?Ll>Kk?Ln>Nn?No@Mo>Lo?MpJt=Lu>Mt>Lo?Mp@Nq?Mp@NqAMqAMoBOqBQrCPrBQrBQsDRuDRuDRuBPsCRrHRpHRpHQqIRpGQmIRpGPoJPqGPoGQpITrKTsLSsLTrMUtJTsIVuKWtLWsKVqLWtNWsMVqPZvP\vP]xQZxS[zRZxU\{U\xT[wWawW`wW^xW_zU\yV^zV`xW_vW`vZax]cy`f|bjai}gn|v~rzqxszv|mwjufrap^m`k[hXf}Wd}T`{S^yQ[wNYuLWtKVtITqFQoDPnAPoAMm@Nm>Mn?Mo@Or?Mp@No@OpAPqAPrANrAQrAOq?Mp=LnKnLn?T8PhOSjO6N6/I25J@.C2,D0@X>LdGZrPtnyskiyvVnVQnOLjO:U>3Q3TsQN2>Q1>S1>V/>V.=W-:U+;T.=V0>Y1?Y1?[4B]4B]2@[0>[2<[0;[1<\.:[-:].<].<].<].<]-;^/=_.=_/=`/=`1?a0>`0=`/=a.>b/@d/Bh.?f1@g.@g/Ai,>c->e/>h1@i/>g.=f.=f-h);g,=i->j0=m0>k.>k0Am-@k0@l2@k1?l3@n0@m0Am3An1Cr3Cq3Bp4Bq2Cq5Bp6Bo3Bo4Bo6Dq5Cp5Co5Cs5Cp6Dq4Fq6Dp6Dp8Ft7Et8Fu7Gu6Gu7Gt9Gs9Gv9Gv8Fu8Er8Gv8Gt9Fs9Hs8Gs:Hu;Iu:HuLq=Kp=Kn>Kn?Km?Ln@Mo?Ll@Nj@MjAMk?Kj?Ll>Kk@Ml>Ln?Mn@Kn>LmMu>Lq?Mn>Lo?Mp?Mp?LoANp@MoAOpCRsAOpBQsBQtBOrBPsBPsBQrDQpERqHTrHSqIRpKSsISqGSpHRqFRoGRpJUsLUtJUsGRpJRqIUuJUrJUqJVrLWsLWrOZtO[uP\tR^wT^yU\xU\xU\yU\yU]wV_uW_vX^wW\xT[wS[wT^vW_uW]u[`y]c|_g~bi~ah|mtrz~}tzmtkqkugtfrbr`n`k^k[hZfXd~T_{R]yP[wMXsJUrITpGRpFRpDRrCPp@Op?NoANr@Oq@Nq@PqAPqAOqCQsAOrCQtDRuAOrBOs?MpANo?Mo>KoJm?LnMn>Lo?Mp@Nq?Np@Op@Oo>MmAOqCPrCPqCPpDQqGRpGQnHPnFQoHSpITqITrJUsMWvKYtLZuN\wR^wR^xVb|Va{Vb~WcWb~Wc~Tb}UcUa}T`{U`|T_{Va}Wa~XeYeZe\h[i]k^l^maocmcpbpcqdresesiujvlxiwfqgoirjun{q}wn|uttvy{{}myv;M;(;&EX>XqGFd;Qq@Qk?c~Hx]wN]XOdUCZE>T;:L8/C29P?$=(.F7#;-1&(=1,C22I4*?(2L/OeHnhxfy{k|ondzsqwhpqpuwjh_{[yYqVXqRk`m`uq|uyVmNNiHkTc=|T?[-Rn9lLuPf|FaYl?vP`dmk}y]`zN`|OiTp_ldfdWeeVccWbeWaeVbd\ac^_a[`a[ab\bc[`aY^a\_a\^aZ_`Y^^Z]_\]_\[]Z[]Z[[Z\\X_a[``[ab]bc]ac^ab]ab]bd^cd_cd\cd\gg]jjajj`klelleooeoobqqgpphqpgpogpomrrmrsesthtulrrittmsulsuntvmwvlzyo||p||qwwowysvxtvxr{{r~p}or~}r|~s|~txywuvxxx{yzx~{}{}|~|~~{}}~}}z||~|||{{{~~{{wzvwyu}|x|v}}yzwr|rzrwvy~yqzx}vzxvqrpopqijueitijonkkvpiif[^eQU\PT_PT_QT_UWcUYdQUaPR_QS`RTaRS`TVbSWcPUaOQ]SU^VYb[ZbcabyuqzmuvcpizoRwkVwajrpqognlkmhkqllrketlgqkjurrllnY^hLPZKO[JNYJNYBHS=DR7?N5>N3?Q1>S.>R.b/>e,?e->d,;b,=d+>c-?d.=c0?f/>h/>g-l3Ap2@o2Ap5Bp3An3An2@m4Cp4Bo4Bo6Dq5Bp5Dp4Er7Fp6Dr7Er8Fu7Er5Ft5Ft6Gt7Gs8Gu9Gv7Es9Fs8Fv9Ft:Gt8Hr8Gr8Hr:Hs;Ht;Ht;Ht;Ht:Ht;Jv;Ju;JrLn?Lo?Lk@Ml?Lk@Mn>Kk>Kl>Km>Kj=Jm=Jl?Il=JlLp>Lp>Lq?Mp>Lo@Lo@LoBOr@MpCQt>Np@PrAQs@PrCQtCPsCRsAQqDRrERrFSrIRpISrITrHUrGTqDRoGTqGRoHSqITrHSqIRqJVuITqJUqKUrJXsMXsNZtLXtMYsN[uQ[vRYuRYuSZxU[vV\tW_wV]tV\vSXsRWtT[wT[wV]wV\tX^vX`xZby\dy_fzkrfmmsqyntjphqfreqeqcpcn_k_k]iZf}Xd}Vb|U_|R]yOZvMXtMXtLWsJTrFSqDRpCQqBQrAPqAOrAOqAPqAPqDPsFSvDQtDSuESvESvDRuCQtBQrDQsBPsBPsAPq@Pp>Mn=Lk?Kn>Kl=Jl=Km?No?No@Or@PpCQsDSsESsERtDQrGTtDQqERrHRoGRpHSqIQqHToHRnHSqJUsLUtMXsLYtOZwP\wS_yVb|Vb|XcYdZd[f[eYdZe\g\i\h^i]h\h\h`kam_l`ldpeqgrfqhsgvjxjwm{o}q|o|s~vzw{szo{yaordthpj@MB>R>5M6AW:7O3%@)GdB:Q5Ke/#7-!6-.C:)(!1+0%4(6)1F6:L;D_E9M5OcGŞ|~y~txNdYG\ULbU`xb\y`XsWpld~Ud~XXsOPkC`~NgQjMeSf\Cc9n_YwHTmB~TnOq`z_Tq@eOwML\rPvQp>vKm|Mk}Kedy}mXiFp[wZwm]MdF5N3>XE6QD^_Y^_Z`aZ_aX_aW_`]^a]]_X^_Z^^[_`Z_`ZZ\WY\WX]WY\WYZWXZWY\YY\\Y]\^]^\]]]a^_a]ab\cc^_a\aa^aa_cdaaebdfbgh`llannboocooirqkppfqpcqpdpqerrfrrgqskrtjvuj}yl{yl}|j}yl{yn{yl}zl{l~{nl}l}op~q}}prquuuut}~u~~{{||~~{|}zz|~yz~vz~xz|{zy{{wxzvvwzzzw~|~{}|~}|w~{z}}~}~|y~~vy{{kotdjn`flZ`fV_iUZgY^iYYeVXa]\dX\dQXaQWaRTaTVdQVbMV`KT]NU_PS^QT_RU`PT_QU`TX`[[`dddsqhoxvlomiiffkiltlcjn~jon~j|tbfkUWbQS\TV]\Zb^[b\Z`[Z_QQYJNZHMZEJVBGRBITBHWAGV>ER=DSX-;U,:U,:W,:W0:Y0;[.9Y-:[,;\+9\+;]*:\*:\+:\,:^-;`-;a.j/>k/=k0=j2?k1?l0>k1>k/Am1Am2?l0>k0>k0>m2@o1>m1?m4An2?l1Bn0Am1Co1Co5Bp5Co5Do3Dp8Gq7Er7Er7Er3Do5Fs4Ft6Hu9Fs8Es8Fu8Fr9Fs9Gw8Ft7Er3Eq8Gs8Gr7Fq9Gs9Gs9Hs:It7Es9Gt;Ks:Ip:Jq=Mp=Jm;Il=Jk@Jm?IlBKn?Lo>Kn>Ln>Lm>Kn=Jl=Km=Jl>KmKk>Kk?Om>NoAPpBQqBPuCStETuERtHSrFSrFStFSsISrFTqFRoFQqFQoITrITrITrKSuJVsKVtLWtOZxNYuP[wT`zUazXd~Va}Yd\d[gZfZg[g]i]i]i\i^halamam`lbncnepfqgsetdsjxn~n~r~n{zs~}IYMWkTe}V]rKhvWS^R&9*,C.>W8+.?-;Q6K_:atEhtezkZonUhkTigJ`]BWSPf^WueMh`VnfC\Mb~^YtPZuRTkTghQjMUoMcXXqKE^;paxfzeWt>_MFh:Bc;2T)Fe@?\1R/wUU]rEljgaxJg{Tvcw|b}]tUesKpPaD4B6_`\^_[]`Z_aZ_bX^`Z^_Y_bZ\`[]^Z\]XZ[V[ZXY]WY^XZ^Z]^Z[_[[`\Z_\Z^]Y]]Z^^X\\Y^]]_\^_[bc]bc^efaghdehdffbhidhiaijckjcjjglkfpoeqoeppdqrduufxugvwhxwjzxm|wm}ym|yj}xl|o}zk|l|n}m~kmqponqrpoxz~|{|x}|}xz~{zzyyy|uwxtut}z}~vz|||~}}}|~tz{sx}|~x{|orlmrt{zswwogklfmrY`ePV]OT_MR^NR^RU^QU`QUaSWbTVcSWcQWbPYcNW`PT^RVaRVaTXaUY`\\`cfekkhrsn{yfhjd]^ehfjruirj~iniaaabRT]QQ]MRYNR\MQ^OR`LM[JLZJM[IMXDIVAHTAGS@EQ?FT?EU>DT@FTCIYAHZ@FY=EV:GW:HY:DV7AW4@V1?V/=W+;U-:V-8U.9V,8V-7V-8Y,9Z,;\+;\*:\*:\*:\+;]-<_.;a,9_,:_.<`-;_-;a-=c,;d+:d+:d*9b+;d+`,;`-j,=k/k0>j0>l1@l2@m1>k.?k1?l0>k1?m0?n2@m2An3@l0Am1Bn1Am3Bn4Bo3An5Do6Fq7Fq7Gr7Er6Dq7Dp5Dq5Dq9Gt8Fr8Ft8Et8Ev6Fr7Gt5Es5Fr8Fs8Fs8Fr9Hr;Ju9Gs9Hs9Hs8Fs8Ft;Ju:Ir9Ip;Kn:Jl;JmKm>Kn>Km>Lm=Kl?Jm@Jm>Im=KmKmLn>Kn?Mo=Lm=Kp?Os>NrAPtANsAPtAQr?Np?NpCPrCPrDQsBPqAPoAOnCPmCQoDRoFPnFPnHRpFQnHQoHQoLRqKRoIRoGQmESnGRnFPlFQpFQoFQpGQoJUrLUrPWuNWsNVrOWrQWtQXtRXsRYsRYuQXsPWtOTqQXtTZwT\xT[vW^xXayWawUbuWcx[g}[h[fYd~Zg~\h^j]j_j`j^j_i]h[gYcVa}S^zS]yQ]yR]{Q\zO[yL[wLZvMZwLWuJVvLWwGSsETsFUrDQrETvHVxIVxGUvHUwGTvERuEStBQrAPrAQqAPqBQr@PqANpDQqCOoBOpBOoCPr@Op@NrAPsAPrAPqCRsERrERsDRqDRpGRqGSrGSpJSuHSpHRrGQpFQpIRrJSrKTsKUuKVsNYuOYvMXsO[tS^xU`zWb}Xd~Zf[g}[e~ZgZf~[g\h~al_j`lanbk_k_kbmeoeoepfrftcrZlnh}xqp||t|N]SN`M[nUG^=PdAbvKfxVEQ>2B3_{8uNpOk|UhuYVfM6N3mVXsAd~Qh[i\\vQ`xcD\Oij_y^shxctWjaKbP8PH6JG>WFKfAE^F=VC>`?fU^}GaGjP|`Uu;9V+Lj6nJbDLkBEg;]JdjSo^rsnQRp@p[Mp7]RQrH:T?6ND(D9=UH-J6aL}^x]Ga3czMx\z^qWcMCP>XkGsR̝ǕnwԜt]_W^_Y^b[]`X\^Z\^ZZ]X[`XY^Y\^Y]^Y\_Z[_\[a\Z^[]^[Z\XY\YY^ZY^Z[]Z\_]Z]\^a`^b__`^ccabc_cd^efafgdegddfcfhcficghdgifhjijkhmnenogpqgrrhtsgxvissgwvjxulwvnwvjxxkyxl{{n|}o}{n}qpnrqonpswwryz~y{{yzzy{|x}vz}~y}~y}~yyyw}}{{||{|}}xy~wx~vy{}~~{~uxzrtywww{yswwwoqs~pp}{mnppimq`clY^fRU`QU`QU`OS^PT_TXdSXdRVaNU_PXaNW`OWbQVbTXcY[dhgkzus{vlynjvjt~|lppfuuq}k~i|nqib__`TV[UUbTUcMOZKP[NQ]LP\INZLO\JN[FJVBHVAGU>DS@FT?ES?ER@GTCKWDJXBHYCIYDIZ?HX=EW=DV9@S8CT3CU1@T3?Y1=W.:U/;U/:V.9V-8V,:Y*8W+:Y)8Y*9Z*9Z+;],<^+;_+9]+9]+;_,<`-:_*8\,;`+:b*9`+:c-j*;g.;k->k-l1>k.@l0>k0>k1?l3@p3Am2@n2Am1Co1Bn4Co3@m4Bo3An3An4Bo7Dq8Er6Dq6Dr6Ds6Ds6Dr7Eq7Gr9Hs7Fq8Fs6Dq6Dq8Fr9Hs8Ft9Fs7Gq8Hq8Gr:Gs7Eq8Gr8Gs:Hu:It;Jt:Ip;Km:Kl;Km;JkKq=Kp>Nq=Mo?Pq?Mp@Np@Np@Mo@Op@OnANoBPoCRnCPnCPmCQnEPoFOnFNmGOoKRnFOlFQmCRnFPlDOlDNmDOmCNlDOmENmEPmJSrKTrITpKVqLUrMWsNXrOVrQXtQXuOUsJSpMUrQXuOVtPWsTZvR[sR]tQ^sR^vUazUa{S_yR`zUc}Ub|Xc|Zf}]i`k`l^j]j[gYcVa}T_{S]yS_zR^yN\xO]yO^{N]zN\yO]yP]xO[vLZuIWrJWsHVtFUtIXyIXyHWxHWxHWxGUvETuDStDQuBPsAOrBQsBQrBQrANnBOoBOqDQsEQtESvBPsCSsBRrBQrBQrAOsAOrCPrCPoDQpDQsFRoHTrFSqGRpHSpGRoGRqHSrHSpHRnGTrHUpIVrKYtM[uP\wR^xP_yWc}Wc}Xd}ZdYg[h\i`l`l_j`manam]i^k~_kcndlfmgphrivTe\SgVxtyama@NDH[H=Q4AU:;N5AV5G^>YoNkvY?O65F4&7+5(-G3&@.#;%/G0/G1:P7;R8%:#Ib7nL[x>YsCTiGBW;MfHTpLE`8>X33L--C02(:ME%8.';/.$(>72-& 2(2J= 6*,@5#4)+>,2J3?X:Le9]vHOf;F]4Sk@ln^~fTs]{ssaEWBBXJ,B1B]Eta[qOG`@hPd?RUmCgBW^_|;^{AbAajMd•YnHatKvT\dgEt0G;U54I7/%4I@%<6,H:f\Jd9`zJXrEWoBllfuTXdL{sdrXoYsSklyaw]_R\\T\]W]_Y]_]^`^\`[[aZZ_YZ`Z[_Y[_YX^YX]XZ_[]^\[]Z[]Z\^[^`]^_Y_a^_a^^_\]_Zaa^aa_`a_ab_bb`edbeecdfcdfehjihjhikjhjijkflmglnfmohnmhpniooepqestiuumxxpwxmvwkwxlyymxzl~|o}{p}onrtuwvvvwwr~u{|wx{v{{wyxv}{wx||xxyv{{y||y{|~|}~}~|~|}{~{{}wzw{x{uxv{wz|~}pquipuimyorwtupvvqrqlxwozvn{zudgh^ce\`dY^bV[bUYcSWbUYeTWcSWaQXbPWaPU`OV`MV_JS\KS^SWa[^hhgmwtr{vmxnquojqwpnnfhgevqlkhd[\`SS]PS_RU`TVcPS`OR^PT`MP\IO[HNZELXEKWCFVBIW@FUBHVCIWDJXFLXGMXFNWDKVGMZFLZAIX=FW=FVW1=U1=W0;W.e/=d/?c0>d0?d0>c1?f0>b0>c/=e/=d/=c.k.>i0?j0=k/=j2>l2@m1@m1Bn0Am2Co5Cp5Bo4An2@m4Bo4Bo4Bo4Bo4Bq6Ds5Cr7Et8Fr7Fq4Eq6Eq6Dr8Fs7Er7Er;Jt8Go6Gr7Fr7Fq6En8Eq9Er7Ep7Fq9Hq9Ht:Is9Hq9Im:Jn;Jm:Hm=Kl=Jl?Ik?Ii>KkLq=MqMq=Kn=Kn>Lo@Nq?Nq?Mp@Oo?Nl>MkANm@OoBNoEPpENoEOoFOmEOkDOkBQlDNkDOkCNlBNkALjBLkBKjBMkDPmEPnEQmDQmEQmGRnGSnKSpMVsLTtKSsHRpHRpJRoLSpKToMVrPZuOZtQ]wQ]wP\wR]yP\vN\vM[vN]xS^xT`{Ua|Xd~Ye~[g\hWeYeUd~Vb}U`|Ua|R`{O^yP^yO^{O^{O]{O]zP^{P]zO]zN\wN\vKYuJXvHXtJYxJYzIXyHWwFVvFUvETuETuESvDSvESuDRtCTqCRqDSrBRsEQtDStDStCRtDSuBQsDRtDRwETsCRtDQsDPqDTrESoHWmFUmDRlR]uKWmMXpHTnITqLWuKVtKVsKVqMXsNXuM[uQ]wO\wP^yT`zWc}YdYcZd~Yd~]g`k`m`k}amxcqcoal`lbmdlgohn}nrszoyIXPP_SRbYyKYNBPDDTJ>Q@=U:8M4!5%&=*-H(Id=Ka8|qi{OJc4xTuMMc@EZ>;O7(9%'9&+>/'@, 9$5M7.F/($:&>U5I`3Sg6Ng;Ia>:K48L546JB/+--))//$91%;/1G9$;,'9)6N3C\:Jf:^x@jcz[{t|amaj|PTj@gPeW\|WNnD|`{\ybtVuU`~A^z7cjUo{xf@O8G`@wpr]sm`bU_`X``Ybb[_bZ^`\]_Z\_Y\_ZX^WY^X]_Z^`[\`Z\a\_a^``_``^``]_`[``]__]_a\]aZ_aZab[ab]`a]acaeecddbffcfhbgihhjiikhijhjkfllfomennenohnnjpplpqirufrtivvowwnwwkxylyymzyl~}o|p}tprzyvxttstvr}~qxysvwswysvwswxuzzu{{{}}|}}|~{z{}y}~z{z{y{}x||y}vz|rv|tw|x{vz~ty~tyswpu|osxmpwlmuiltdlqcjqejshkpstuvwpxwtgmoefjijngjqlotfhodglbfj\afV[c[_i`cl[`iOX_PV`QT_QU_OW_QWaQXbRYaSZaY[c__cfifkjhyrqyuvxtq{wpvvsffh_`i\`dSXaRVaPT`OS^OR`KP[LR^LQ]OS^OR_NR^JOZHLWFLXCIWCIWEKWHOZINZLP[QU`RVaKQ\EMX@HV@HY>GY;EW;CV;BV7BW8DW6DV7DX7DZ7BY5@W4@S4?U3>V2>W2>W1=X/=W0?Z/=X.=Z-=\+;\-=\/>^,;\-;^/>a0=a/<`2?c2Ac0>b0>a1@c2?b3Ad3Ad1@c2?b1>a0=`0=`/>b.<`/>c.i/>j,>j,;h-;h-;i/=j.=j0j1@k2?k1?l2@m0@l1@l2Al3An3Al1@l2Am4Am3@m5An2@m3An4Bo4Bo4Bo6Dr4Ep5Dp7Dr6Ep8Fr6Do6Ep7Fq6Fr7Er5Eq7Gq5Gr4Ep7Fp9Hq8El7Em7Dl7Fm7Fn9Hq9Hq8Gn8Hm8Hl:Gm9Gl;Ik=JnKmIl;Il;IlLq=Kp>KqMnAOp@On?NmANnDOmDNjANiBPkCOhBMjCNjCLkBLkALjAMjBMlBKlCLkEPmBNjDOjEOlEPoFOlHQmHQqHQpGPoENkFOlFRlGSmISpJTpJUrLXsLWrMWtMXtMWsNYuLXtLWtO[tNXuO\xP^xT_zS_yUb|R`{Tb}VdUc~U`|V`}U`|S_{P_yQ_{R`|Q_zQ_{P^{O_zO^yN\vQ^yQ^yO]xQ]yKZuHWtIXuHWvIWwHVvETtFVuFUvEUtEUtDSqBRnDSoESrFTtGSvGVwFUvCTuDRrBQoETrDSsFUxGVsEUgPbiBVWj~jbtVg{i`qi\l`l{qqzqTbpO\tMYvQ[wR]xS^yR\xR]wR]uR\uVayUa|Xd~Xe}Zc}Zd}\f]h^i]j|S]aVbYfupgvfpiqlsu|ntxGRQHQSISQdom}xdodRaXFXM=SC%!:&8T5LhDKh:PgU;2G2.B08N82L-5M102&2)#5,4J=#7/)'*)$3+&71.-%!0%%9*#7(1@5*A,3M+Ro=mMV}XnWhxZm{^okxGtPbrDrOyi~CgNl@CeCHf=b{RfySWhE\sOOk@Lj?SmBkSMh5eIZt@B[+jSYpDxcudViObzUidmpNn}TuZfnuR`e}[rJsmNFb5YsMsi7O34K3^zZqwipud=P;McNRjIYhIwbZdDzirzhbbU`aV]aX]aY^aY]`Z]^Y\^Y^_ZZ^Y\`Z^`Z_bY`c\_`]aba_`\_^[^^[_`]^`__ba_a]ac]bc[cd_ddbdfcfgcggcijdgicikeijdhjeikhjkgnohnmfoofnofpohppiqplprlrslstkvvlvwl{zm}{n}qsprvwwwtsssrtttr{}oyzsvxtyzv{}v|~z}~{~~}~}|~~z|~z~~|}}y{w|v{~w{uy|uytytw~twsuptyjqxipxflwelwbgr_fqckubiqeksfjtkmvprvoqueinaej`dibfmikplmshkrjmreindhn_diack`dj[ag^bm_cn`do`dl^dnW]iW^iZaj^dgrqm{wqwuqsrqljmmlqhgoaehZ[aZZ`\^cZ]eSWaQU^PT]KS]HP[JQ[KP]GPZHOZMR]KP[OQ]MR_LP]JNYEMWINXNSZPS[MQWOTZOU]PV_NS]QU`IOZCKWCJWAHX?GUS5>U6AX4@W3@W2@V4@X3@Y4AZ1=Z2=]0>].<\0=^1?^2?_1?\5B_5Aa3@`7Cf3Bd3Cd2Ba4Bb6Be4Ad3@c2?b4@c2?d1=c/<`-;^-<_,:]+9[+9^*8`+9_,;`/?c-=b-:d.;d-f/>h/?i.;g/i0?j1@i2Al1?l0>m0>k2@l2Bm3Cn4Cn3Bm2Ak4@k4Cn3Am2@m2An4Cn5Cp6Dq6Cp5Ep7Fr6Dp8Eq4Fm7Fo8Fo7Fo6Fo6Fq8Fs7Er8Gr7Fq5Ep9Gs7En7Fn7El7Dk8Eo8Fp9Hq7Fo7Gl8Hk9Gk9GlIk>Jm;Ik;HjLm?Lj?Li?Mi>LhAOiAMiALiAKiAKi?JhANmANnBNn>Jj>KkALiALjBMjBMkFNkDOlGPoFOnFNmFRoDQnEQlEQlHSoJUqKVrJUqKXrJVrMXtOZvN\wP\wP[vNZtMYtMXtOZuP\uQ]xO]xN[vK[uO_xSa}Va|Wc~Xb~Wb}T`zUb}TazRb{R_yQ`xP_zP^uQ`tS_uS_xT`yR^wS_wS^wO^zP\vMZsIWpIWuFUtGVrESoFRpETpFTpESnFTpFUpHUqGUsFUsFUrFUp_ozVhmL]qKZnOb^lh[wRNlAmN~[a~KSiB_rRshklQeZ_llV_oMXnO[qT]xV^vU\rYfsZbxYawVbvVbyVbyWcyZc~Zf~Ze|]f{YdsJW]=GB8F5buXl|uftfpmtv}zksqiqo:DF9DEAIM9BB:F-+>-6J6;T6C]-^{=_t=j{SAS6YjMQb<_p>^XP[Rbj|<5J,MiPJdDUoLZrOTgJRkF`{IiREa9B[7OhEYtHaL`vOIbBK_BaqRYkRg~d]rScwPpWm~Wn[_oKbxQ]mKM]P7qqq|~f=D8dcXbcY`aZ`a\^`[`b]ab[_aZ_b[_cWbe\`b]^`[`b[[_W]`Z]`]^^\^^[]^[``]_a^`b_^a[cc_de`cdaad`ccacd_ghcfgcegcehbhjfejhhjgjkhkkgmnhmnhlmhopfqpkpqlrrmtumwynyyo||o~~puxrqroproqopqqstuwyyz{||{}|yy|~{~~}z{|yz}y}y}x|uy{quxqtynqwpsznqxnrwiovdktahqcgsbgrchsafq]dkaen_cncgschq_dl`ek\cj^dm_cl`dmdgn`dkbelcfm`cl\`h]aj_cl]ak^bm]am]ak\`iY]hYak]cjehnwuvy}y}x|kkn\^cY\eWZcUYcSYaRXeOT`PUaQU`NU_MU`NUaOUaRV`aeobdmY\dRVaOT]PRVba_rqkacc\]]olfwvlorrcflTX]SW`RV_UXcMQ]HMYEIVDITBHVAGUAHV@HU=ES9DRETc0>b/=a/=b/f.>g.;g.;f/>h.=h/>h0?j/>i0?k/>i0?i/>h1?l1?m1?k1@k1?k2Al2@l3Bm2Aj3@j3Bl4Cn4Co5Cp4Do1Bm4Bn5Co5Do4Cn7Dp7Dq6Fo6Dn8En6El7Fo7Fq7Fq7Fq7Fq6Ep7Fp7Fp8Gq6En8En8El9Gp8Eo8Eo:Gq:Hp9Gl9Gk9Gj:Hk:Hk;Hj;Hj=JlHkLi?Jh=Jh=Ih=Kl=Jl=IlJg?Jh?Jh?Jh?JgALjBMkFOnCLkBNkBMiCNjEPlHSoFQmGRnMWrLXrJVpMXtOYvM\wN[vR^xQ]wP[vP\vO[uNZtKWrLYuN\wO^yL[vO]xR\yQ\wT`yWc{Ye|\ez]ey\ey`fv[ewdkx_er]cs\ds[ap]bo`gr\dm[cmZfscjvdjndlp[doWbnX`sU\oSZlNXgLXhOXkOWmPYpRYpOXqMXqO\rTe`umg}Y~D\A\uPXuBYz@jKRq3Qs8Fa:ZrP|7J0H[INcUxyasfixoZaiV^nXge}|bwfv~jxxZgrWdvVb{Xd{ZgyZgwnyfqtGRU:LB9K8SgI^zKk]dtokxkw]ogFXKC/4:6@B7B:DV@;P<(@/(>68OC+C84L:,A-)C*6L8!5)&!/)*A.Mf:Lh0Pl0dAPq3D`2":5"!9!E^>3L3!8(9P?/'-&&!8)$:.%:+)?04&):+1D/6J5(9&9I73D23G33L66N;-E+2K3+D,"7)-$-A90*"23$"#&())'%-(3..%8I<-?03G29S3Jg5cBWo8mT{k|^~SWu;u:j2p;f4?YoITlGMcGJaFMiH[uQUqG\xJKh;JiBB\==W9F]EW>FZ>J\Ka>I\=H\=K^a0=_.<^.=_1>`5Bd3Ac1?b2@c1?a1?b0@`/?`0?a1@a3@b1?_1@`0@a1?b2?c0>c0=d/=`0>c.=c/=d.=d.=e/>h.=g.=f0?g0?j2Al0?j3Am3@l3@k3@k2Bj4Cl3@j2Bj3@j6Cm4Dn4Do2Cn4Do5Do6Do6Dp6Cm5Bl5Dm6Cm7Dl6Ck7Fo6Ep8Gr7Fo7Fq6Eo6Em8Go9Ht:Gr8En9Gn8Fm8Em8Fm9Go8Ep7Ek8Fj9Gj9Hj:Ij:Gi;Hj;Hj;HkHcOXoNWm>Kg;KeNhANl>Kk;HhJk>Jj@Lk?JhIg=Kh>Ki?KiALjAKi@LjBMiBMiDOkDOkDOkEPlGQmJVpJVqKVrLYtMXtOYvP\vO[uO[uNZtNZsNZrN[uLWsLWsMZtO[vO[uR\wPYvP[tT_s\ewal}hn}lrvz{uzik|fmzglzfjxko{fixbfx_fu]hqQfTSmDQn=Pn>UsDId<>\0W{FMk6Eb7=]7!94M3LdIliM`LVmJYuCrVwPsWmgOdKGb6A]3xnSjO[nabpw[dw\dx]gtXhfFVLScSHXJ7N87O3TnLQm?E`4azPf|aQdZD]H6M;&>0&70=HE.:;/8>-4<-794@8AU@/G34&$7/+?2)>/-F//E/9S;0H2,D.7NA2*/Vs@[|;Su7Ab)=])+G"/G24&3K38R2.E03) 5)'6.%',,-,-%-F8'=/&:2*?39M;4K3*B,2H29M8/A0&7%&@,#>-3H21I4-F*PhK&2.--%$!'%+%),, 0*"5//B>!5.$8,*=.3E.B]8\yDYv?Jc;7J0?Q=o\fCWr4He(Zw5Qn0Vr6Qm*_}:Uq+z<N@U08P>9U<5U5Gb=KiGHfFJhG?Z:IaF8M4DUB9N=;O@:P9XoTpg^pWM[E[jSVgS?P;gxb`sYe{_WhDemevPt^VjG\pSWkNJ`?JcBJbCofXhNes[fu\O]LJYFcw`L`D2F0sh~hjijwW{toye^hRXfQbvaTgSLXSU_bQ]`Q]_V_aWb`\hb[f]\e]be_ficaf_dkdbicbgcegcehafhafgadd^hg^ih_ihbikcij`hj^jkakj`jl^nnbnmdmmfnnfpphpqiqpluumxxkzyozzm~}q}{n|n}q~orpnmmlnnlnlmnswxwvyxz{xzyyxuzz{zy|v~u|}u||vyzvttuppsorqtwtortjnsjmtgjrfkphkpmpvhksehofiqknwikubgnafm]dk\cjY`gV`gYclXbj\_k]`lX_jXaiYal]cn\alXakU\fQYbPYbR[dU\gX]i]`k]fm_eohiquuynnqghnVZcRXbQU_VW^TXaS\gQZcTYdXZd[^eTY_NR[OT`QWaPWbMV^OWaSXeY\hZ[gZ\e]`fiim~xt~pm~n{our|vmjifddhdckW\bMS^NT^SWaPT^NQ^PS]NQ^GMXFMYIMXTW_XW]ZX^][aWV]XW`YX^`_cifjjjmnlnigetplxqlmienlicbacehefj__fVW`QWaMS`LS_IQYEMVFNYEP\@LZ?N]BN]@L_@KaAJa?Jb@Kc=Ibc0=a0>b/=b1>d0>d0>e1>f/i2?l2?k4Ak3@j2Bk3@k3?j4@j4Aj5Ak5Cl5Dm5Do6Co5Bn7Dp5Bk6Dn4Dj6Dl6Bm6Dn5Ek6Ep8Gs7Fq6En7Fo7Fp7Fq9Fr:Dr;Gp8Gm8Eh7Ei9Fk8Ek9Ho:Im:Hk7Eh:Ij9Hi9FhKi?Jg@JhBLj@JhAJiAJi=Jh:Ig9Ig9Ii9Hg:Ig:Ik:Ji8Gg8Hg8Gh8Eg7Eg8Gg9Ij7Gi7Fh7Fh9Gj7Hj8Hj7Fh8Gg7Fh8Hh8Ih8Ii7Gi7Hg8Hf9Gg9Hf9Hh:Hk9Im:Hk8Gi;Kl9Hi:Ih:Hg;Hi>Ii;Hh:If9R3B]7ZtJg]h{g_ocXh^BWCAVB.D.9P:E^A.F-+C-3K-=Z0VpFE_=8O91G9,#"84):92<>+5:-6=)29+563?83G5 :+"<.*!;L?%8)9Q>4E4+D16L<:R?/#%<+Ha?Kl7Mk76S(7S/2L),G,5N:)=U<:Q90#.$(@.("*.*)"#1,'>/2)$6/.#.C23I4.D.4L6'?*&;)5M6"9'"7*3I40H/2K,VsBazQ6!10(& -/&'&*@5%;//)-&)?2*@/6O5FZ9;U*Pl8D_16O/D\B9P5iSSn6Je4:T+C^/Ec.Zw>Uq9Nl/nFxDn;mR2G5;WA>\G=VA>ZAE`G2J48R=0E3QeP=P=/G2E[JdxbGZDD^GI]IN`MI]H7O5d{`YlQN`G\oPhKY.n{Y`pXViMOcA[mGBV68J4:G9CWCPeTF[JPbQ=O:bx]{jr|ajy_.@&rm{lRlC3O%JcHmm[lcESW7GO>N]3CLCTOJYUbsgVdWP[QIUQQ_bSeiO_cK\]]jfcli]g_diddjddjdejchlehicln`orfkm^qpcutewteuuivvjwuhwwkyylzykzymzym}{n}zk|jmnomnjjhknopoqqsuxvwwxv|~{{yvx{zyxvwvurqsq{zozzqz|tu{}tqvrhnpfjnejmgkojlneimcflfimjnrmpsmnqimohlobgjahkagl^dj\bh]bh^bk`en^em[clV]fT\gOXbQVbRXcT^fV^hX_i\cn[cmZclZaj[ai[ajTZaPT_LS]MT_NWaPVaTWbPV_MT_SWaWZ^_ae`dg]`fZ^eX\bQYaT]eR[dR[eS[dX]g\`g^aghfluro{s~vyozqxtonkipnnqoqnmqifmW[cMR^NT`NU^NUaNS_INZGLXCLWCJXFLXIMXKOZRT^RSaSUbNS]RV`VWb[Ze`^hdagebee`eg^doklkkl[[`Z[_ddgediggkbbh\\gaaibbe_cg]bjRYfGQ^EO_DM^BK^CK^BJ^BK^@K]?K^BIc>G_;F_8E_6E^7C^7C[8B]6D`;Hc>If>If:Gd:Ig:If:Je:Kg8Gd:HdJh>Ih>Ig>Ig>Hg>Gf@Hg=Jg;Jf;Jf;JeLj?Jg?IfBMiBMiALh@KgBMiEQlGUpEUpDTnGToMXsNZuMYtMXsNZsN\rP[pO\oP\pS\qR[oQ\sQZqQWnPVqPZrV^pbetwpsum|soxt{}x}~|}}}|}~|pv|DTPrk^wKczRn]xry{rsyuutzXadAPG>OA8L;8R53O2*H.-J01N/OsGKoC*C+*D,7R8-C1PfL/I':T5)C&H_E9R,iFnGLi0E`58S0;V<+D)7P6?Y3aKbHhNWrFB\;7R7/F4(=+6K73M2=U= 7$MhLRnGAZ79R8%>/+$"510F>52*5:%+"*0+:;)523=:3G:4N> =-/G75G;ox%6+-$$7, 7+.,*=.D]4Mk9*GC]?#7 !4%*9%+C*8R90G33'(<0$<,-)*)32%$%63$80'95('1&,D3$9*#6%-B..C0.?-.B.!7*#9-4M5D^>V9TmEOh7A\/,F:Q24N+'C9X//L%Lh0Sq2p<C5H9P99UB.G42G9/B4;S?@VD?ZE0F9.C00E8QjSG[F:M9DYGPhT3I=[n[[o[CX@:Q9`t\B[>J`?{cl[gK6F0UhAfO~PgyI9K.CZC:SBUG2D<$6.,?6TkQNiEk^|UgM*.(Nb)I^tI?Z80D2p^u`s]h|e~zn~xK]S?OO>KNTe`L_WJ[XiwrgulQaXl}tJZRPa[TdaQb[]ng\h_Zb^cljhmkv|su{rxxn{|nyyl}p~yn~yo|lmknmmmljllnptttsttuuxyz{zyzyyxwzzytttsrssqqoop~pr|o~|p{ypyyozzq}|s}|q||txxqxxqxunwulyumxumzxqvujttk{zqzyqyzruwsnpnknngjnfikbfh^bf^dh_bh_dhagl_fh_dh\agW]eTYcPW_NV_MV_KT[LS]LS^MU^LU]MS]OT]KSYMS\SV^Y]^bcfbdfhjlefiefhhknfjo`fk]ag`cjcfkhlnjmnmnovuq{xrxtqppononghkdejcdfegjgglaagZZcPU`OUbJQ]JS_GN[BJVELWBMXCKYELYGMYIO\LO[MP]MQ]JQ\NT`VYfTWcSV_WZaVW^RR\ST^YZdX[dTXbX[e]^dX[_XZ`X[dVXeYZh^`i[YbTUaLR^JR_FO_GPaFNaFM`FM_EL_DL^CK`CI_@F^?E]Je=Ic>Jd:Hb=Jd=Ic>Jd=IcGf;Hd;Id:He;Id:Ic;Jd:Gc=JeKhC^9c}P^vIr^zfqSbT;MCAWGAUD=R@5M52M56T>&<*)B'%F /J+!7%IaM&B)7"4O/C_3fSZzC*EJg6]~?Op53O(,H--H0%?&8T4KeG?Y2:X&Rt>Pm%A[9HaB,C+A\>3M30F2$9.5.%94%#1,)-!0/+542=:4E>)54'14-?6*E/0J;5.$#"(<.0B4/B6(>3,)!'dP6T$3O-0F3$6%0G2%<&7S<=U>.G1)@-$:,#8, 9)&",%$;1'=12+0,+'&"6*3$,B33D10E/)<+-=.->0*>3(>2.F/@X8!-@ .1F-4K.1K)E_9Nk?A]5/G.!:B\5B[08S+F_(34,922C4PgMIdAPjIOfFJ^C~vEUH#52AR/HX-Ma#KC^3D[HKf=:Y*oNfIIk3Tw>hK^~@iKjJwXx\pUz|v\pafyjjyk`laYh`GTPTjcL```lkozqjwhs}muzl}}oqonmmkkkkmpsqutwvxwxxwz|{ywx{{xwwvtrstroqrpqrnn~m|o{o|p~o}p~p|m|l}h|k{m|m}k|jzizk{lo~n|l|n}ylzwosslqqjrqitsmstnsssututussvunppjkodfj_dgabf\`dY_dS[`T\aU[aW\`Y^aY^bX]_X\_X]_]`e`befgdhifjjfppmmmjrqnrqpttrwwuwvuuuvrssvvrwyswysuvqssrsssqqsjkljkmihlbdhbcfabf_`c_^dVXaNS]LQ\CMVBITBJTDLVCLWEMWEMWIPZKPYKPYLQYKOXLQZPU_NR\RW`SX_RW_NR[OS\NU^QU_QVaWZeY\e`bfabg[^cWZ_SVaQU`MQ\IO[FNZEQ[BPZAMZCO^FOaEN_CK[CK[DL^CJ_DL`BI]Jc=Jb>F_;F];EZ:E[8EZ7CX6CY7CY9AX9A\8@^7@`5A]5@^4A`1@_2?_4Ac2?a3@b3Ad4Bf3Ag3Bf5Ci4Bi4Ag4@f4@e4@f4Ae5Ae4Af5Bg5Ag4@g7Ch5Cg5Cf4Bf3Bg3Bg3Af3Bg4Bg4Bg5Cg5Dg5Dg5Dg7Dg9Ch7Bg8Ch:Di9Eg8Eg8Fg9Fg7Gg6Fe7Ge7Fd9Gd;Ed;Fe;Fd;Ec9Fb9Fb;Gc=HdHeIa>JaDMhCKh@Lh@Me=Ka>K^>L];HZ=I_?Le;Jc>X::X5Jd@Qf?Ti)=2+>3,D1)@,6O;!>'@`Y;(?+/*##!5-5.1/!77('?PC7H:-. 8)&;/4()XpU1I,>Z>IeESvI\zIMmF1L5@XL8Q>#7-/B@$56&87)=;8MG:OGATGZqZe}^cqU`pYJ\I:I>?NC8L3\xDZr;Yo8_z=pCl=h8LZjB2=73C9:N>GYM7G?5D;,=/G_CC`?5R2HcBZsNG^=e{g1H;WOXm^U"77S9QsHOq;`B;`,(O!Ce48Y%Hi/Pv6:\&fXZvM{hp}plw`uze{iqk]pag{f`ud[jczx|~sn_vg7QPARZQ`cbvoXngZjg^khpzsmultyk}pstwsttrvuyxxwwxwuw{y|{ytvxuwwsrssrrsqnmmm~|j{k{m}ym{p|p~}mzo~ym~zn}|l}zk{mzl{lyhyiyj}p{j|j}k{izi~ug}vi|vjyui|xl{xl{{o{ypyxnttlqqjllgigffgefhchiajkggigfifdhffghgfefgefggiiefgcfiehhfjiglldmmgoqknnitsoxuoxwrxysxyrxwswwrzyvyxuyytvwqwwsxwuwwtvtqttoopmmnmnoomnnklnikniik`bd__dWY_UY[UV[VW]UV]SU]TX\[]`\]a_ad_acabdcdfccf_acdgheghegiegkfhkgiifighhkhijikkonmrrprpoomonnklmmijmcehadf^`fY]aY_aYabZ^cZ]dVW_XY`VW^UW_ST_NR\LNYKNXKOWKMWNQYNQYQU^PV^SYaOQ[RS_MR[IPZFNYFO\AMYAJYCJ[BGXBHX@FUAHW?GV;DR;DS:BS:BS9?R9@U;AX9?Y8?W7?Y6?Z5A[4?Z4?]4>^5@_4@a3@c4Ad3Ad5Ae4@e3@d3Ad4Bc4Ad5Bd7Bb4Bb6Bd7Ae8Bf8?e7Bf6Bf5Bd4Ad6Be6Bd6Be3Ce2Bc5Df3Bd4Cc6Ed9DeCaEb>Ec=Eb>Fc5(<01K6+D0'C+@a?Ek>2Q,8Z79T;#>.'?.3N5'E,3N5.K-7T3B`6B^1=[,@_66S1)F*"?&1N08R59S6E_AGa;If8Pl?0L,&=,"6+/*,";R5E_9C[:C[-"8-!6,+) 4-!7,4'%.+B/$=&UqL:R5.G,4M21L.A]5+G!PkCoQJb+mIXu4@]'Ro6`@Lm*g>fHb;C^>42>8+940=7.!\rI]y;Wm*DW&Oe9C]#Yo3_t2u@vK4C$5C6GXD7I[1Kh:YuEu`wgyhicwSe~bWmZUhh?NSVeb[h[izii{irk|fymAQXL\eGX_H[^Ma_QbiDW\WijWi^hzpPbUbv`gv]rzk|omqqrv|xuvz{yz~}z{yzx{~yxzyyzvuvrp~mmqp~or~qn|n|o{l|o|q|{mzzl{xkym{p{o{pzpzmzjxeweyf|h{i{jq{ozozm~wivrdtpcpl_jj_hiajkclmaom`mk`lkbjjaih^jhakidihciibklclkcjkdjkfnofqrfrsiusmzwo{zpzzqz{tzyuyyuxxtyyvyxvwwuxyswuqwvrxvsxvstsnproproppopolpomonjkiihhiffeeedbbbbccedefceebddeffdeheehfdgefhigkkjjjinmkqppqoopnmqolvsqtqovsmzwozxpwvszyu}zq{upwtoutpzvpytpvrotqlrpismirmjokiqokpmjqojrojpmiokjpklnijkfhkhfgecdbdd_ecadeeeffgedgfggdddjfihdfeffadg__h[^dYZaY[cVW`XX_VW]SX]SW^SU\QSXNOXKLYILVGJVGJXEHXFHY?DY=DV9BY:BY9AZ:@\9?^7@^8Aa5A_5B`4Bb5Ad5Ac5@a7Ab7@`7A_7A`8Ba9Ba9Bb:Bd8Bb:Ad:Bb8B`7B_9BaD\AH\?FY?H\AG\BH]CI\AG\BG_@GaBIa@I^>H]=H]>H]?G_?H_>J^@K^@L`CLbBJcCJcBJb?Ka>Ib;S;-C6)>3/G52O9/K1A]D =#5V5:X:.G4':.!9*)C-:T=,D4&>,%='/H+Kf&&>'0I/9R0D_7PkA7R0,E/.D64&8M6DV@:R59R23L15N23M2-B/2,32 44++ ("#6H43G3+D,,@4*)).))3E=5'"8,(96)&&:03#1E;$;35/5*4)(;WA(A'&?$.M0;5 !(&,* "")>2#<-"8-)=9.)$86"#);90.+*#702*6-"6./)$9-'<.'=,-B27O8OiN,E*'>((@+.F/-H-Ic?0I(=W/E`.nOkJHd.Mj7@]06R&Tq4e=8U!;Y,@\6H_E*8+9I:K_FNc>4J&BT80C+-D*&>&$;( 5+?&2H*'@>Y-Ql4Hc*Fe [|75Q7S%7S-%A!>Y3?X+D^6*B 0H"Lg9CZ5&,6(&6!2D,I[=7I&=R.,9"2:'9A+5@'BN0drIWl1:SkrvA]eo|6FJ\mz7U(Pl@XvDaK)FQlAiMjhcl|k~t8M>Uh`H\Ni|iNcTYn_cxd[qXNbWJ]cY4YuMgToV|a`tUZjbcrifuhkxgshkven|jozgwo~y}{|}s~o}yxwuo|hu~dr}pm{o{qzq{ssq~nywt}t|u{~z~}y|vyxsx|svwy}v{yw|q{yo{zp{p{q{{ryyswyt{{vwwpzxq~{r|p}q}rsps~o|o|n~whzseyrdsm^tn_ql`tncuqcuo`sp^sp^rl]ql\nk\mi_mj`plcmnbom`qpbppdvsgxtf{ug}yl~znq}n~o}o~r|}s||r|zpxwoyxpzypzo}{o{ynxvmtslrsoqslrqmpqlnnnlmkiijeedgggddceebeefdcdcccfdedddgfggfgffeklgllhlkgrqmvtptqnrojurmyvrwsovsl~{s|n}wkxn}wo{vk|wm|wo{xo|wl|tozsnzsnvrivqhvqkurlxpj{qkypiyrjsoguqjuoivnitlkunjrmjqljoigolhnkfliekjfjkemjengcmidiidghddcdeabb_aa___]``^a^\][\]]X^YX\XXZWVXRPVRRVOQULPVIKTGITCET>ER=CU:BS;BU:@WD_DaC`=B\F_>D\>G[@H\DI]FG^EGWIJYKI[LI^GJ^GG[GGZHIXJLYIKXIIWKKYLKZJI[GGZDHYEI[CIZAHZBJ\CI]AI\AI\>JZ@J_CK`DI_FK`CJ\CL\DL_DJ`CJcAHb=H^>I^@J`@Ha?Ga>Hb=H`?J_@K`>I`F^=H_?H_AH`?F`CJ[EL^HOaFM`IObJP_KQ]MR^JQ]LS^MQ^MN\QTbSUaQU\TU\WX^XY^\]c]]f[]a\[dYZgQVcIP`HOeIPkJQmKRkLSkMSiPWiNTfSUfPSfNRlMSiIQjFPkFPkHPnGQlHSkKVmOVl\bq`bla^oQWfPWjUZpRXkPWjQXjOUkPVoSYpRXnTZoV]rU^sU^sT_rR]oUbtT_sS\sU]sV`v[azVbxXavZauR^rP^pP[lPYlRZmY`o]duZgpSd_[oTGYC6M;EYEQeHVfHHZ@8Q5RlN'?13-[99S5FaCHaCD[>4M2McK$:Ib>7S0.K0/H7+C7/G9%>/3O8$?+2"7U>!>'/$)<7AVGDVC8J93)+":Q@4N2Gc7Hh5Ll95S**G,5 &>,4%,B17R8/I/7P34N,?\28R51I5*A/ 2'4I09P23I,0D/+A,,C.,C+4I9*>51/0.!.?60'*>/+D1+<7+-$'-/,( 6+%80+'+*D*7O:2F7"9-4,"<.-#%9,:T2!;2J,3N2)A,0D5 0.'( !$%%%"5'-D51)!!&42 %#)#"3*"1,.-.,00'),(2*'#6*/''@-1J4 9%.%:)"7$5K5$;#(?,+E(Kh>9V+C_44N*3N+A]7,F!Vu2|Gs9MyJ~SV`\{O~Sdy=lIWp8e?fGnOhLvUY^\wMYWb]cmmzqwslljnxxlekupifuhr[DNS]Xg\WYqt`RQkh[[PS`YeFi^7w6Yxi0l-:>JA]?\.;\)LmC$B#2Q*9W-5M5F_FA[5Fb3cOQn>iYudQj?Rk@A[2Ea8b~S`y]YrR^ySga_wbbxd`viC[OKcQPfX>RUBWV?Y@gQRnLMfK\}JFi+Tq8Tv6Tu1i8mAdQ`zW_tnPa_jynk}l^re\ne[mc]qi[pgH]XL_ing{j~h{i}h{j}qoh}lr]natdy^rZlcuVi`tfz]q^qase{csbrgwbrbrlzdo~pyit}grxpvnu}lzhq^iw_ly`myhvm|q~uiyo|vu|v}|zu{{uzxsyyovtputpssrttsutprnqrloqlqtlutixufusbwueusfwtfzvf}whzi~zj{iknsqqqoonllkn~kljj|hzi}wi~wh}wdxhwrarocnk`libhhdhebkidhgbee`febeebffdihdef`hidmnjlkhqnkwspwsovqqtqlyvqzvrzxq{vn|rzmzk{m{m~ynyo{mzlyl~yjymvm{rkxthxtixtiyti|ui~viwj|tizrlyqkypjzqjzoiyohzqjwpkwojwpkunhtmjrmjsnjsjftihrkgnkfnkfmjghfdfdcedba`_``a^]_Z[]^[_]X\YWZWWYSRVRRVSSWMNSMMTGKVFHV?ER>CUCYCY=BZ;BZCUAW@=W? 732.RmXIfDJcFBZB9M3SiMG`AE\<BPA;P==Q?)@.0I4*F#Pp,1!" #' +=*:L=$%! 23 /.)!'9H@(%**"%'..-%%5.%5/*&"9'/I2$>+(@-!8'&;,';+.A2&:.6$9S28R2Gb?(B$%<0K#Pl9zIWMFFOJF}FzDEJHUNNWTOYT[YP~BJVWVRxHPRSNUQWLIIGHSOURRUT];4=4UUbX?07;QIHcNYgB+H~Cd$=^i5Xy[{i5i)VF3Q3Pk>]:S +B^x>f6Sp0Kh3 4N;:S8KfANhI/H0D]A9S5C^=MiFPm=iS_wZH_N9OB:U=jSNi8-G12M7(E-@_<<[0,H1OWuAZxB8T#Li:]{9e9X~,[{HSkWL`YPaaMb[QgdG[ZQebXngZtja{oF^\Zqv[pxgz_s[mdvd}Xqd}Ri]v^qbvZr\wh_tRgXo^uaxYsPhbzVsWqaz\uf~Xp_ubtdw[o^t_uav]pl}zg|eZqYn]rd}`{f|audx\nbuh|bwgzk}gvhyi{csp}es^m}`nnvpzoy|xt|w~v}~{z{{}{wuuqrpppmkmmkgbczcwe}tbtm_toermdqlaoj^pk`njalibmkfkk`qobtqeroexrj}wqzvmyvkzwn|yn}xo|wo}zp~yozl{h|l{jymyoyo}l{h|i}hzg}tg|tjym|wkyrh{tjxkxi|ug|tjypkwngyph{riypezqh|tk{ti|si}tjzpixpiwnkvmgxojvniwniumjsljqlinkgkhfkgdifcfb`dab]_`_]^a[[YXZVU[UTZQQVQRVLPUJNTIKUDHUAER>DU=BV;AY:@Y:@U:AU:AW9@V8@U8BV;C\C^<@Z=BW=BY?DX?AVABW@DV@CV?DWBVABVBDWCEVEBWFDYDDYEFZJHZJJXLKWLJXKIVLJXKJXJIWGFTGGUHGUGGTHIUKJWIHXJHVHFWJGXKHYIIVKIUJJVJHULJVGGVEFXEGZEI\DJ[DHZFI\FIZEI[EI\FI]HJ\GJZDH[DJ]BI[BJ]CH[CH[DJ_BI]AH[BI\AH]CH\CI\DJ\DI\DJ^CH\AGZCH[FL_HJ^GK^EN^FM\CGZCH\GM\NPaOQ^RT`UUaWVd^cfitg|rwoorneajfcna_ib_ka^j`]ibaj`_g__i`_l`dnW]nOViLRfJPfLSfMTgMTfSXlUZmQYlNTj[bx]ezeobp~nojysdtnbzrWejQWjN\rS[pOZmQ[nR[pQ\rS[sSZqS[pU_wV[qS[pY`uZ`tW`sU`uUax^m\k{UauTatT_rVcs\ixVdh\me\l\ex^WmKG^D9L<>U@7O>%P>HaK9TA6P=%9700!9/=W?6M86N:)>-0A.:Q6@Y6Ie:SoF,E-2L5*C1%:-#61%3A=1C7;M=6M7#;( 7$?V7?[0Mk4qNd_vB2H.\uP?U9.<)%5$)>,;R?/+$4)C\=Jc;@V56G3)%3A<)=6%90*#,>.OeH@W84K3DZF?V@/=1>P?4M90E6'>,6N8!9@Y0C_2+B$TB,@1.&$#+%*#0"$6&-!0 BZ>/H)4G1,B++1%.%  ++;4+$6!/2J-/F&7M)@\#Ut,],^.e7lA{OTSZ\ieeIYQ^lLW[YV[]^QRNPRWa{3|OSVdCXTTXZK}@Dv7r7@E;F%6?>ku+vDCKPCBDWT*LOMdbR\h1P^1n3R]&k-ET;AU;AV9AT;BTCWACX@AVBCYABVBDV@DW?AV>CWACWACXCCXECVECYCDXDHZJI\IIXLKYKJZMKYKHULJWJJXJGUIHVHGTKIWMMYNLXMKXNLYNKYNL[NJYNJYNLZNMZLKWLJXQNZKIVIIWJJYKKXJKZHJYIJYHIWFHXJM\HKYFJVEJZAGXBJZCH]EI]FJ]DI]CH]AIYBIZAFZAE[BI\@GZDFZDFZGJ`BFXIM`IQdLYaKY^XhjVecJU]MTcQXjKO_BCWFGYHJZPR`griidqjohfq_TWYBCSEFZIJ_HM]IL]NP`RUaQT`QR_SVeOVgNUhNTgPWiMTfMThNViSXlVZlY_pr{yxo}j~tkl|QlqCaZ]ouNYjMWpKXpMXnLWkNWkPXkOVlSZp[e|ZfwYetU_rZe{Wfx_qwngvqbv|RbgTebbsf\oYumcyWQf@ZqM0H34K<9P?4L:,A7-D80E5:Q89R<'?/(B47PA%;4$:7-E<3J<;QC*A53I<8N<;R79U.[vQ-E*3M7,I12J9%>3"415-%C,3R9(A/2L78P96E?,59*45,99->71E94M71J8%=*@Y:6O.&@* 6(&7,%""&8-#6%;R>JaO8O<9W*f;R7EQ<@P3H^;PjDNh@Kg9[yGaIhP^}MTuNIbA)@)&9(!1#8H84I42E-;M5(8'7E7O/?L,N_E>R3N]@P^m/C}5HEOXSfMq@`|-Xu%j4n5b{2Wp(f{:CZ"F[(M`.L]*Yj1^q7rCHJZ|+22^aagQ\\YQ/uPf%0e{"J^"Qh3F^&Jg%=Z&;W&:Tk3Hc$r$h2a+Cx0,UCVJM0i{!>;2u1Fd',[rQ2L[q;H[*]r9Xl3WRp'Ur'^z)k>7]4W"Kh/Ml*Lk&R:6R*_:LryDT:AU;BW9BT:ASBUDZ>D\>D]?F\?EXBGYCFWCFYEFXEFUEGUEFXDFXCFWFFWJHWLJVLJXKJWKKVFGWDEWHGYHEVIGWKIWNLYPMZNMZMMXNMZOO[PM[MLYOO[NMXIIUJLUMQZJP[HLXDHSGJUCGSCIUJPYJPZGMYEK\@GY@HZAGZ@IYAG[BFZIP\dp{EPh`mO^oI]gd{zje}ym|h~vezyXlsOdkJW`>DPACU?HPsxffZvXkhsmarcP[]MS^NQ_MT^LN]IM\IM]IN\KP`OSfNUhLTfKTePXjT^nXesZhuZeralv^ovavcy^rs[z|eQpwebxxMmgFddZpt_mzOYlLXoQ_o^lwYgkdspyy^o~oof]te{i|Vsigks{{imjb|q[rNSgJLbAn\QiB@Z=*C6 5/0D?)?44+.D74K;@YB0K7 <-'C35N=,B>/E@+@92H=*A31F71E84I54K0Hb>AZ9,0K:&B35,42*-1*7QA4P>+";+(@.&74!,1$01*,'73/B7*:34G8#9%+D"ZvM9T5)@*0E. 3&"K\Fnd~VhinhuV]tEYnFlXcwRczVYtGjO|[v[w_m[ePiWeW`~Ulasixkrfm`h]mepjpg|rwlshnbp^sayhugsikbe^_Ve_cbTyZWtYQoRigqmoke_ymrdtswjw[w]~fx_uYuY_wH`yKlShns|~{{{Ve>ksl_^YsRmNfM7K&Oi;De'Qw1`?V|8Cj);_'6Y'<^+@c$Wz5Ux3[~:Z|=Tt4Xv/Pp$Ukvj\D>B_Hd`x.s&@jaKESH?D.:?Uf{7EA;;`zAOp4Xw7Hm*1O'E`-^#4U[BU?DWE\?EZ?FZ>DZ?EX@DWAFY>DW?FY?FZEG\GG[HFYEFYBDXDHYBEYEF[DDXECVEFXGGYJIVKIVMJYLJWONXPNYKIWMKVOM[TV]frffu`euhM[aO\eUakO]bSabWe_Yh]]kaYc^PW[DLY>DV?GWAHWBGXL[`_zoC[[@SjCVqATj[s~F^eE\fOfl`ywdqn}Nieb}z`zms{`thk~qQgQbbjheae`kn[pjSceV\hTZgVXiQXdRZdPWcJSaKYkO_rN]pSbsUesQemQhka{}i`|WszVozD^sLgwLjpb8SeA\r8*,>:';9*@51G9&;20G<7RA%@2(D9(A62I;1. 36%995J?I_J:M8/@/3E0IaFE^<8Q07O;$=18/0-11++ ("+>,@%56*=95G>HSc_Ka3Wj>wOLf#nATo!Xl0\}+^2}Ab|(6`Ea l*\7`~]F_SH]c=S[F\aBU]:NVEYaKhjKadAX\EY`I\cGZ_Reh[rnfsfwUqrYu~Phxcwmp~rZytpaw_z|shMkuGbjGgfC^tMg|\xRlOiyVqXrXrUoXrOjTo[tZr_wZrk[x^yXpjWoazWrd}bz[sLe`yZs`yZw\x`]zfZt_z\wQlYsVoUm[tTmUmSl`zSoTnYqVj]sYp`xVpYr]wXoXq[v[u]u]xc~b{]yhimjjc}G_G^^v^tVp[w[uYqToZxWuUq[vVpYuWuXrXq^uWlZpUm\v\x]y`~fgazgb{^y`|fe~g{ewfzfwsq~s~z}~{}tvum|{p|yqyxoz|vvwvwy{uzyuwysx~ywunymvnvmwhten{foxbkwaiw^gu]fsW_mW]lT[eQZgQXiMR_OS_KP`@DX=AWDVACV@CZ=AU>CVCW?CX>FWC`>C^=C_?Ea@Da@F^?D[AG]AG]>EW>EX>CW?BW=DZ2I9"40)+-?9(:,5E27G75F39Q6:T2MfC(@)++!+ -C3,B-8N<@X@_xUybq[pdza3E9.A;3,4%8O:QqKlZn_~qyophxpvmwjm[pZlWo]eSfV[HgZ\QXL^PaNcP]J\JgOhQ[xGeUUtH]~P_YbZUzQOqNSvTEiHVwWWuT]{Z\}YYyQZwTWtTWuWWx[MmY?`JEgHLlIYyVWxRVvSUvTJkKTtSOpNSuRSpS;ZCX>@T;?R>BU;AT<@U<@U9?W<@V=AXBV?EW=BYDa>Eb=Eb=Da>D`?DZBG]@E\?FX?EX=CV?DW=DY>CY>DWADW@FY>HXJVdDM_CO`DSdL^iXipIV_BEWHEZIF[NW^UhiG_g@WZpxklaypUqbnf{]rwqorixvz}dgYyd\}alapfd`w{c{ma{oTqgNlg?\\;`a>\g3M]>UiEbo:RgASjGZlG`fOmk[{m`jWz_kqrvyufejp\xhLjWTtXu}~}~qozzwv|}{wy{rmpjvpKaJRh^bviUh]h}g{qvml~hwrn|as`_yP[uCfWbzUd}OybfiOzfd~Y>V?7I;BYCSkPPiKKcCYrKC^>2M8/L;*B70G>,A70*203323&(*./6("J^H0C1'8,4D86E9CVD3H5?T;;S5Hd>^zQWoLqg{lmzep]q_paiWiU]~NIf=:T9.E5/J6A[DFbCX{Q\LgUiZrgsitedW`X^}SkUeQ^O[|MZ{HTwE\PHj@d9[|RSqEZvHYvF[zKgZY}NPrIVtQ<\;Dc@TxSW{WWzS]}W_}\TuQa~]TpKSnIe`DbJ0L:8U>LkM`{UeWYwNUtRPoM^\ImGTxQ<[7WwRv{l}lrZ]}[gjpM}WzR|S_`ik_]lcb]XqMa|:rIYx8jGlI[v>Zr=gDXNemhonle[[Zhlai^{NUo@T\YXVl7Xs@=Y'Ld5:Q1AZ>^zGtHbz0Ul5dKd1bzF^|FWsELfT0=W(@^'a~L>R-B\2Eb2Rp>C]/Fd6:W*4QOk5f@Ys+bw>GV08H)jBs-|6_,So&=VZr~<\r#a~1D]Qm$f)A2BD3/F0@BR.EZ,C]-Ha.:SX^|#UPse0[z4_{/q1FQKLi[tt*G4r*Ni&Uo,Ml!G7Sd|-z3SRE_w0v/>eA"8AV:Ga,aTNG_QSpUdZl\Fd>of`|T]qc`onRgcZqfWpaG\W^tn_ujXpf>X[TgqSdsPcoWpwUqs\yzXvtQos[yqdom`EbmZvHdpNjvTmwQgyPdCU|AT~L^?T}SlPj~]zeGescvvRoxMgXscs]{}]{g]ya~hjB^h5Pd;Yc<[fQmTqXsSmJd}MeKeJcOiJdSpMlEdxLiVt^{Lh[t^w\xVtYsWrWqYsVsXu`z^u[q]wQkTnQnXv\z]~[{acfqVoIbJc^zc_{Mk`~XrWuYuTo[wd`~Spddeagh]XyfceaXxff_~f]zSqbYyh`|\|WvdmWr_xa}dg_{b~noicc^|]z^z\yb|fgf]wZrhozflYqikfx^qVh}Xf{YeyS`vJTkL[oOZoPYoGQiCMa=E[;EW=EX>FY@H]?I`?Ia;E];B]:A^9Ba:Bb]a?[cF]h@U`EZhCVk;Md>QdNfpMil[yvYxleu]~kivjrip`vhaxiezhdzgh|daw\rsaze{wp{ssn~mt|nxnyuom}wusgrjerg[nW{lbd^`}Zzevjvxkv\i{grbzckQw`kgyio^hTWmGazVRlGhUeQdOPkDBY?E[F,C09QYuI^yOOnANl=OmxSkEeCxQ`hIwVoLbjn]xMzPi^}=R0>W/=V1@Z4C\3E]9Kb@C]5E`53L&EZ5;R,Ie=C^<5M.@Z:5M*3N&OnDZuUE`>Mh>=U2@[2Ib7DZ6AQ93D$gz9So!IhQk*C[Q~,}-MdFb'=WwCbz-QkI#CPd:NUj/.C1F!\v<=VEak1q/4Vx'/G!9SGh!v h5,Uh1o%Th&:%KXu"s@=V\w&yFLd*Sl/`w)5{97 a}.8q%B^%*C9R.L^~>Ih+Wu.Pk!z8><]^|5z=pEf?(H"Kl<:[)Jj.kH@^5SlQVj_[rcczhJ^[TkeUieXmjF[b]mtbs~Uhng|xi{`{xPnm=Z`_|~SnhSqoJiuIew4Ne@XnNgySnyWmz~xrzrm}zaxi|crWxelp}}}fmw~}vmwwys{ycpv~}xq~{qkb{`vgklhgOn}cebIe}HdIcNjNfI_JcLdMcTiZvTqNl?`A^C^zXt\wxutdb\~qfa\z`gba}\zeaZ}OuMsVz[TzQvU{SwWz]Wy[~ZT{]nxcik]Zxe^bc~YtVqUs`~]{a~dklag_fegcfhloahoe`}_}iflsupj\rfzsj}Uj_o_pXk_sVgSc|P_zL\{P`RdRcScM^{K]{O`~M^}M_}MaL_~N`PcSfUhUhRdSePdQfcxZqF]lOiwKfrGdnSs<\m<[oCcu@_oGerA_jC`kIblneEchO>ATBL^Gqj`[a\XMqfc]QkLF`EKbHAUCAR@9N92H16L7E\C>W7MkGDe<@c6De:Ji>Dd6Dd;Cd:Hh?JgD?^??a@DfC@b;LmDY~OdRv\zcfu]jiiPkQnVeObPSyH_PkUgQaPYzLQwCRvGPpFYvMXwMhXfTZwGVxISuB[|J[wG_}OTtILi=Mj@WsEToEOmHQmMf~YLe?MhFQpJ`UVvMQqMIgEKhGUoRVoMXqNOjHPkJSpPNlLPqLCc?d^bXl^}lzegVkZlX}dgOkQxYtRlM_}BUt;]{DOq:Ae2fRJc.jHtSoTYvEPm7Rn8Qi6mKvQF_*Gg+Kn-`>Bc Op.Rr1Tv4Nr/T}3c8[/Z{0cAYsUh3k|Dao6fwOg@J`Rj?Mg>=T0AZ1W0D]1G`2=Z23O*6P)4O&?U/7M+ToLIdF?[88T.He;BY1QgGH^9DY:[o2[UlLeJdOj-4i ?S>WFda|6d2s3:X0tE_y9Xs-c,A@V"lBPp4Zu,@x=MZ>bo4t5Wkk*_|6>S\s02J;Tw5LUk g0q6M3b{(:PSf2bw37_Lk#w@w.LFa!Ic,Zj,If7V:XE.K;aMo76XQq*Pp&c8^,Vz(Q*G;X97S,cHZz=>^*Mq5T},h;Dfg0}>h1[-{C/Vc:)I*G+9R9-H'B^8Pl9]9];Y|CVrIPhQWpYE\UOcbSffPfdG_a\puZonNc\h{rSg[Sk`ZulWtrTqpc|wFbbZs8No:No^zEfzevvdNkOme~_|VyiTwFkNrTyW{NqJnRwOpNoJlSsTvPrRuJnXvbc[jox{g~|nqgZ{Yxf^|c[yhffh`}folmpkchhahlpjrxewz_yWp\tWq]yYsOhMdKcI`KaRfQfOgOgPeSiOiUoWoVoRmRlSlUnSmPiLfTnToB\hA\cUrvFdhKgt>Zi=Zn=Zp=^oHdu=Zi<\i?\ha}^x@]cBY`7LUUinVjj~qwcmPl^RkcRhfMdcAYSVphPjaQhaZ{gd{irxj}unwm{ts{n~yYtpc~wwuk}UhrPdrNim_}xHgePnpSmlLffWosWprUhjbpn[mgivrkwvR`cNafPdhK^bM`hCU`Q]hht}JX_VfiEQ]Q]pclcj}aiuZjmbiqVYi[avUXoIQ`qzgxhuuYjatpjyT]oHeuyx\dQ^zOOlJUuQFeBMnHMjI;X=RrQ=Z=6S;B^@WtI`yRJcFF_BkZcxO_tOUmFUmFLgAKeC?V9MaFJcBewUTdBK^>Je=V;D^A?\9B_:9X8@`;JkAGf9PqAYyL>\4Eg=AgTvDiYbL|`cgt[x`nVkPqV|ebMbSQtBdVZyH[H\HZ}HLnNmGIgFMfFVpOG`AHbFg2YzCSu4Sy4[>Y9nAlDk;}N}Rn~H~UuLarGUW~L{IQ]q5Wk3^oW9?V3D^8D_?P5L[>P`AD]7Ha9E]4BY0E\/:P*>X0XuH("j~;2b{i$t#v @C+EZ=VPj,B[lAHc(Lg)x@HhSp2e-P`u0=EnuAbl'Xv,^v h+;S!xV?V?W}38H84F3?E\j?F^4MKbG`k*(6cq-TmUs >Wy:\d0FKIm"A 8V[v/4R > _AKn"?_/I#+F#0I*Ee86W&2R$A^*Pt/i?A_ f9+No6Pts9o4Uw0De.;W7C]89T'dF3SX~2Hl%Pp0cFTrI[ySYt_AZQBWOF]RGb]:TVF]^Vjej{udzkUl]eoSpgE`\dytu^H4QC>ZDSkLasRYoOAYCZrMOf@MbH=T;8Q3HaBIaCMfFAYcOKk:Km@In:RyEU|EbLw]ca^wVcEmSoYw_Wt@Rq?gWQsA\zJ]{IlUSxCZK[{JZyK\{PJi@i]a|O]xH_yLYtGZsKUsDStDJh8Wp@Sn>fScOrZrUcvJbwLawNZpJPiCUnI[uMeW^{TNkFWpJ]vOfZYrITqFTnGUoHkSpd]qWezSPh?gVUn@E]6XnD^tNWnDawKazP^zQSoFRoC@^5QnFOjDYqJF`8d}NLf5Pl=Kg=[wGYtCJe7ZvBMh3Vm_.Ef/Ur7Hb3`~H`~?gFZ;cCyNs@Lb]_^OHTQMYV\gjnjTov>_l9BS)Vi9]l5D#O_:IZ0NZ/R]3btBL^+J`.Rh>Sm@Md>Lh?Fc8RmCJa7CZ0I_1Ia9D_6E_5A\:Nh<[Qc+x;:O`sCawnoi6Yp9A\'7SUq7Id*Yw-?[$Oj6wNOo|Ee}6Mt?\vq,~=|5-MlOn2|-J`@Y8>C68/G +csFGc\PMKRlmQ_/Ki*Mf$^w%N~?n8QSq*,F $?@]&Uq48R#^zE(?(&A'Ea=Ie:Ca.Or1Qq*]t4_z7o5U"e/2U d9=]0.J$wL6[~;n9Dh+?])Ml/Lk1Kk9LjCCc@bT]NUxX=]O8QP@SRZngH\TcxmSj[Ni`@YY9KPOddMem1Ia0FdH\mfyu[p_tozrpiklIe[LfcQgbfyPlgB]\bzy6QNEdaMmhPrado[~coqlnztvotgpsk?fJdohjsrqpdghl~xvsdeimag^hpxv{gnpsbciisr^a\feqltutsrcf`f`m3RNIgeeqbhopikgorxrqsrgksurwuylwW}h]ngwlzkwv|{}ruovzk~_zWztbyxrZyfb^|aIjIngnnbp_dkrVweLlQqNpGjHjWyPpRqWyUuSqUr^z\}[bW|fbhgfllhc]mg`}\x^edc`[dpgkpjkrrsrwxwmyrvtw||}uljokig_|Yv\yebi{{wnqmTqkg{vmwazckmgffimo~~gupests|~Z`i_irn}bwp`{lftetsjwxhzoywerdz{qyVpcUoiPkgWos9K`4Hb7SC5TE,H@*DA$>9*A<*@8&F9'E>*H?;R?WiSM[FXiR/F6RfNE`:?Z7b~\geE^DVrMdWqhIiDJjFGjELoEQrIWvLTpIj`\{QZ{S_Yg_[R^Yb\dUiXeV]yQLeAlbQkN=T=.B5;M=)dP9R*RmBOj8Uq@WuCgNfMoW^{JiSlUVr?VrAZxGIe>B_8Qm>Ji=Li7Ki-=Wa{2n<}KQirf3`Uq9HWPoEpH~Mn7uX+>U%Yr2=U%/H 9U%C`)@[$>W:SPi'd5az+u+0KQg!n@h+l2BPf}'3JTk<7SvJd{1|IOGDL ?OH:L38V+INi+Mf(Xq-d0d)@]s5q3.I5P*!8=T$Ws5Qp05P&C\A(A4O&Ca3Jk3?_!a4JgZy2r9t6=Rzf*N#G ?`+Ij%Zp/g,(F:V%b?{P^^SblqsgjZ}_ekdoJkaJk_\}ghmgpUwerewpTtifsy}xxhkWy_moxu^gsziqlpmtn}g~^|zSspnfts|wl}uumoxllIgXy`ag{{oVtXsukcaAbCcBaDc>^DcEbFfRnFbSqNiLeNgQlUwSwTwPsWx_e_mdUuc_[afga|aqtk^~UvOpY}Z|`gMqQtQv\[U{Ty`lnboktuiswqsuywupeUtStUv]|go~{fs|ozuoMfbAZVVme~uuopNi[QiXspa]rrpq}qsfhonmkrskrprnjq~guoqvavrZwlasUspQfjPf`_g\{\d\yjjhnjwOfcTlnLbi2BU;H]bo}WilLYcu{agjsjrbhuhoybju`btUUrSXxPUkfj|Y]qdnth~xgs`}hdpH_c?QXZfroydi|jk|ooT]oao{ZnsSii\rdG^HSlLc~[f[o^vvp~`hv]^o[^r\TlW1J>4PI+EC*C@%B>;9#:=(=?';;5OK4SK+JC'A8EXJ4B62@9@ME2I2]xRvvgMgBTmQGYGUmOKgADaCEbEFcGLkMOnJVrG]tIsadYcY^WbYdXi`\Q`RbOXtHZuM\vPPjFOlG?X=5J5*?3/@49M<-B4/C99M?9N;3M4;Y[K[{JWtEIj5Pr:OpVpEXsKVqI[xOPm@ZvHRiA\rHrXboVyO`oNizEj{EqNi|IjOjzGm~LjRiR_vHkSeNYuCTo=ZwAz\^wdLMk:TqC\}LQp>Hg6UwETsF?[>1N/KnHy?{Afw0q?JQzAMrDan9pDPbň{ʼnzG]f7LX1=N+CX.Ud<\b6\ʘʆY*?*0hv&m%Thz&+7-Ol Ws +s!1ABGHOZQl,UMg W)(B5M)^)_;YPp3#C St-?a*I`(b;`#A.Jq5h3{IMk+6V!Ce+_?Jn07X*Ad<7\&Pw9Px:cMY|UD\X0DMJ]bFXXFYYL`_DY_/BN9KSF\`?TXSfb^p`utsqcyb]q\\q_VoY_{f^|iHc\?\[[wv>XQXre\{gHgXY{h]|pGg\TtjRsjLndLq_V{dWu^^\ad<^XEbgJdhNmd[|jOr]]~hjs[}d^kakKga5NQSlrB\\Ge_colnckpxrt\hNkb`{rdrnyUuhEc_\}mZ{iMqeXxmapqxrwovrrvwktY{itUvf^uY~vLloRo{UqzRqq^vatvt}ctrbzeumk̦ihonqwuoutgmjgkWz@_O?1B2CXH:S=IaL>V>JfGXtO]wRk[mYiVfXsgrheVn_dTdRc}Pa|LjXVoJ=U7@Z>5O8%:+*?1-F92G?/C:*?61,.+$7/-D3,I1,M*aU8]1Jf?Mj:Vt=`|Dt[waw_~dpXrZdyHcyI[sF^uEf}LgORm>WuF[vEWtBQkALf:`~M`~L_zKkVRgCQbB\uO[wK_{OSjCEX6EY8Ka6^wD?V*UkA\qDUhY-9V,Ki<:X-@_2Hi;9[/:S3:Y9Jg/z7\9cOZo{oW_^S}:^i.vLQZ9gwMPg+[x1J_(^o9`r>Zh9[j<[m:Zn8Zm6SyOez>Wn6Ph5Nh4Vn4OMz?XK +$9-?*2)8dqJU_8AIkx-D_ft{)E;P L[x"Gf6SPm(h$De$EIGVuxD9Y Ca:?Ba>Fe6=]&Ek%5[@`.Jh=;Z(Ac*/RLj/?\+#DcHWt7;X;\Y3Lp$}F?]!Fh0El(YC)K%3P.<]/Gi<-Q .R'4W5CbJ1KEPgi@USGXZG[YK`a.AK1BOH[]Snc\scizgbyc_uadxcnoI`P\sb[u_^|cQmb1KN@Y\\upCaV`m_hMj^9TNRoh>]\;[`=Z`?a^TwhZ|cnl[}f4RQ;R]9NZOgp;WZ\yqZ|aUxb4SMQohStb^~n8VSSln>W\OglKhc]|lboPo\Y{as|Fc[>VYSrhVxhStkLkhGhbg~MmfHedPllOne\~mfsfnjqkx^~sUviat]~wHgkNjwDanFbnZ{}Wzx[|x`}wn_xZ{r[{Vw}\}plldafhWblqlelvnfkhJi:Z:QkIkAUBSKXBU?XIbEbFcF`D_IhLoKk;W@[DeVvhpQtDfMmCaQnPpHkX}Y~Pvnb}_z`xY~p\ucza{Xz@bQr^CeMo]`ckVz^cZ|_]_]~_gpkOkwQmyYvkgdhkeryu\wrIieJga}XwkUsm=YPPlSjmYz_Su\MjXVoaTlael^|WgagiblYvkRqga}tUpdcdqmm`zf}bjXlZqWlTul[}f\loyTv\QqZGjMKoGdY[vUUsYfhffeiayfasngr|OXhOarfrcmdjSXw`m}_qr`q}O^mOiiZvo`uwIZd`omN]jVmkYzhPragpRr]jmsjzwYgd|ivkwvylxJfUjoUrU@`C@aF:Y?@YCTqS`z^H]O`sdM]R;K?G^N@]N,GA 69$8?!5<(@D16*1+1,BB73,CA";=3LI2H>7L>HVM>J@8N4e[rcLhC>V>H^N1E9:QF2L8=T>?XCNjHPlE_zVi^_|MhTrcnbhVlXc|LjQcyJgRmY\uKiZC\<;R<.F6+@4)=:#84$ .-/,$76)).(*D26T9De=Ij?`WJg;Tp?iMy\bmVf~MjRkSbyHbvJVn?\sHQjIa=XtEZwED`0dM^v?c{RI\CL`EOiC_yQUjFI\BEY9Pf>f|NWlEPc:Wl@Xl?jzOZqEcxNUmCUpEToGJeA9R2WlGUnK[sQSnERnCVrFYuL[nKgw~\TYCjkXdhLafJQ]@fwPiyHrPXl;Uk<[wDVqA\uG_vJUmAe|M]tDUp@Oj?ToELf;D^0Xq:ezHE[0NjOp?dTSsAiYH@DMBI^x}Wke`capA`d3FP-5N%Ge1[uCS2KV;HO;EH:SLEXIIaMW_IPdRK|e`p]y[fb{uTiX[%8;u.`{@Mg1g7,'k@1*o0~+,|%Sp^yo-y+s~;u}*bm>@1@CTs'#B$?'?$#>#2&8#%9"&?#-E%Pg9Ph01JD^"8R?W%F]"c~2s8RQf P\t%Od#cy5j3Az+`sq&{,5{n[u*1B$'4=H.8C`o>GX#]l7Wh.es-CW7~@>Qm/GPk$g-g I'I-Kb~/s.Qu"?`) 8 0L*8W3Lh;\{?Y|5Vy>&E.H*/J.)G(Oo>%D2M$Ml6Gg.a;f/BfZ3&G ^~3^2<[Jm-[G2U,OlG5V)@a6Fe5Jj:?\25S+SpOKgPLcW;MP:MR7JQ0BL=PU?WUBXSOaYSc[Uj]PdWRdT[o]^xeNeTTkX^w`Wse1HP1ESK`f;STSn_Zu`:QSI`b5QT8V^:Xe(CT9W`GgcLj^emMoXWsw;Q\8J\4J\6L\=VX\|kbr7VTC``cuWwcbxC\]HeiC]e=X\HdcCdVdvX|h[kB_YF\dWtqWvrB`\c|b{9XUQoqFcd@^_HheQslMndSsfbrOqdUspXvqg|h~PnqFau@]tLi|NmtMlpPmtSoqeX|zOtyqiY{eaUpzlq\[n]Jnahauii^~fS{rynukxĴߋShA]?ZB\>YLlDeSp;VD_De\|p`am_~a^}PqTs@akPrvMpsDgfPrmUzjiyX{hbogtTxpa`Vy\}]aWxJoyWyPtKo{XyUv|Qu~Fjv^~WyZX{~\|yhTqyXsVrzu^cp{pj{z|A\YQifzpr|cd|pr{hnIn\LkaMgaF]X_xe\vWeb\}cJlbA\`E[]Sfg]skTq\OnTkbeN}`}d`zE`d_OmJ]~]_XfhV}WPuQoWasUYyOeYbWiemjmmf|lg}qcwxrcuKTnkpQ`lPedN`m8JYVrv@_\TnnTnnUtnVpqJ`kTqi\~hNq`YxjY}i\bb`|w\sc\vfY|[^}bLlS0P93Q;FbIVrV:Z@5S@0I:1I:?XDF^GHZQ?PHCXJCYK7KB-I>+FA694:#6>"9?)DC7RP#7;)AC$>;,A<+>3/A1ex_DW;e|`XpSzoo[WuKke:Q0,=5!3-,.()''+))% 3.+#7P@3R8GhJ=`Oj\rDG_2Tn@Nh;YqE[vFZp?e{JNi;Hd5UqBQn>_yI`wEexBZrAYtF`zLYtCUs=Db,>["o:h0Ef)YJ@c/[e'/M \}7y)B396&M=PjDU^NBe\jhwȞ&7M18Q/':9H!?M)M\4K[3I\/BS33C&?P*7K'YnH:I&^kBKX1IP5HHG9;@1-VJ/Use@?,&OC>fZU'WR:I++80|!Xnn(f4HcWsk"6Nk+~)-hQi=\:Y Sq%1L2J :N\k$KQVW]%jt3eq'`j!cl!x+lu-Zz&h=+J3 4".)>';R3Qj>6M(? I_,4H Ri9Wp4Gb(Wr0So#c}/w)Z~5QcK\#Sf(@ZKb m~8ew'ev&t*~&T.c{,CT(z];H%atCj@az5dy7b{.CXj6w8XnU<9v/PY;NZ5JLG]ZAUU^ro\op\ZE`aKfaHec9PX=[aXdAZcRlk9TTMgp=XbDahTsqRrp`xUvoQrle{DdbJfjQnoSqlY~md4Nf7Qs2LjC`qFbpNixaLosRu|VxRt~d[}eX|tv_cgX{`mdkhLq}Qq]xInwSr|xң=[pg[v@[}EcBaGc;VRlpfRr}cRvSv}sr|ZzCahZwyGdfBdcZ~w]ofudokwmtfodnV|nNprHjpXy~YyGgrPoMm~PpLp~IkzMl{QoOo@_lNorPsrJnqDbhEbcNohTrrQorbQplYvxWqr]{}u_}xXssz]JglPr}pxhqgqeq=[NLd[i|tcwjyxxu[x_YwoHba8QX9QQQj_liysukv\xS{I|Dbx<|CMd{QWWxEPwJfYuPXcVyEhZ`Tpkzqphvnwoqpcztlzkzeq~K^aG]ZHZeEUkBXf/JITncKf\AadFbiF_cOnggtKn[Fg]Oq^Z|cqixkfhciS{Y<`G/P>/NA2QB9VBLjS-J92LA+A<0FC4JBBWJ1A?>LLXpc4MA(=:$?<&AA(AD)AD 5:,1-FJ9:$:95-4M8_xZ|ke}Ui\TtNPmFo^_yS2H-I`GIbE7Q9KeNB]@OlOG_DTnMTnNd{Wk\kUx_nW`yHgSe~VVkC]wHiT_zOPlGA[:E^@8O;$:/#$%)*+(>:5, 5-'<.6O87V81S36Z77[2VuEe~SOgALeSlGLd@VqCVpF=S.?R4K\AHY;Mg=[tG_xJ[vKPkBG`;EY;3G,?T96M0>V5:R/3J(G_8Li8Hg0Ql>Ld?Kb?:M6\!Ux,]I;#Hh#q?-'kr m#'O/4E@>Ab?FBMRV5LA!!1=(0C5FtDO=K-KZ?BS2?S0#4'5'*=#:M11I(:M)Pa8=N!p~FkoK44UMEoiOtrDQ=]ZZUJa\GQj%5m+CDb~'Xx&uJ!9(/'=(#90ArAR[v,0I4Tm+Ok#i:0/\xZt,^7T0K!D`:%7?9KT@RT>PRBSSO^^HXXr{Uf[Vg]Tj[\p_PfQ]s\byf)>>Me]a}efgLdUI\[VigZoo+CO(@R4I]3N`9V`7SXE_aHga]~lFf[1MZBYf;Q`5JZSkoJb`RmcZzjMmaKfg;Q\E]f?Ue0HYE^fPnjOneKhcNjeFc_Cb^LjmB[d3M^V_[utA][PmmZdKeoMjlNnhh@]i,Ed6Mo2Jk;Us@]sQo}Z}Kq{Ou}cTxjyWxcc`xryxxqti{f]JmzfbiF`p`{LjuUq~YuXtKmz|nv]}Zz^|ZzZ~Lly^}cjhjiNmhp{duZoNre^n\nOvcUxecodr]ojj]r[}vUwxSquSu|EdrJl}MnEdwJjtQqvwvVxr[z{^zwc|X{rQtoStvYvsya{xNmid{u{wu{usig^j\xgyedbWvdUpd]uilt}uvjjfclfd^A`Q?\RIf`v}zun]fNhLxN[\ZJ][MW[Jb_SPsLx_bHg7Ae9e^e`DgCOrMUsPjbe^zq`}`^yeZljdznuzeloyRfc`yvhw}}jlA\T>TPWtdUsfRubUvh`{{Rmj\zfVvQc_`cB`K2TD?_P1N@)D>3MI,JB6QF/JA+F@(A=%><0ID:OH:LM:II2BA2FE.FC+CB,FF-EJ.3";A+>E((-%K`Kye|Ogo]gXgWhVrdXtJWqHLgOC#/"(?(F`D!:&B_B@]80Q08[4^Ol_c}UEZ>=Q7CU<=Q85I/=P87O5*C)8P42K.06S(]{J@[0G`8RfF8K3EXC;Q69N2;O49L0BT8CU7>O4I\;J`7Wm>awGawJZpFQeE\oOVkITmIUpGZrMVmD\oIm[QdC@L:NRNJSL\iW;J5Fe66O+!62G,3N$Ii%T{6.Rl_?`~M$=- 34BSW9LL:KLaooGUXVf`RcY^pgUeZZm]QhSZp[Zp\Qi[Vq]XvS^XgdVl`WjjK_b$;J%:P3D]0EY8OZ4MU7OV2MVNiebs2OJ2GT=Pb2IWHbeD\_MjeSr`B^USmm-DQBYcF\j.@T5HYA\\ZzoIg`LkhVttJhg1MW%=S2J`9RaBZfKdjha|8T_A[iGcdQnlUsl_rHedDalD^mC]gE_dYyx:W`-Bc/Di7Mo;Us>Yu=XpIezMj|JlKn}NnxSvWxoOnyazluiqpxglyvtut~`{xdqtIgisgygCbpGdsqmnujrtjuxf`Mnu\}g~cvkFj]bmxwhj_eT|dW}pFheBe]S{lYt@`\NnfOta_rCfXPrb^nV{g_q^nX{tOqz=`tIi{LmqmuW|f_lTueEd^EddSjCTh>`wJWoEQjK6O01H-?S:G\C:N4$:RjKQiCJd>NhEIdEFd>NoDGj?dYc|QFa;6L13C38J8:L6>O6Kb@OkGFe;Pq@.LcMKf9Mg>Nh>WnJK_>G^;=R5CU@8K59M;+@.,E32K8,F16"QnRC_:.G'@Y7Ga<>W84G/9K9+A0(>*:Q5EY=FZ<7K/>T6AX5H_7Un@Me8]rH]nLYiOYjG^rNXmFTmITpMcvWQgD`wS5@-FRBZhWZkTTeMM_EQeB\oII`9]yG]xIOl>Vr@WsBgWBc==[>8U0MgAYsLNf>G[8@S1DW9Pa@MX9V^CW^FFJ6EH:DJ9Oc38v#}3Ca t,{5G6PQ'AlLh sD=KEE8A9ROEkbYAfxmpjxHgRr,Ed$+D@[C\ +z9IA9A7MdRe#y4P{3Wh"Fv0q>Kf.Hg!_~ q5c,p7s5aw"`t(<<|3;s$_cEdm*5Tq#Nh On?Tr9#A3Q$;X*C_6gJ,L Vu7Ps7Gj+:W'+C*5Q/YxL.M$=Y0@\89T3_zD_~9bJhU-E&NdVEZIFWTP__Ra\IZPYk_QbY[r`NgQMfQUnVRkUB[FohPjFTmOMaMbrdJ`PVmg%=C.AU'PcF`e7VSB``6PW>W]Vpt8UZ0H[:T`Fbh8T]&=T.DZ0IVHcbSkhhyD]YOoj`yD`aC]iDbmB^gC^cTup;X`"7Z1Cn.Cn3Lm@Xw:SnCZw@YtLiUtSqLi{RrJjyYyZv_{`hb~hyks^xdcYvyca~~ha}j|qe[z~Zz}svk~Wxog\~uk}f{wrqy_ndwjg|n~hzbv\p^lYzcLnU?_NNm[]~__ecph{HlcSroGhcFkbGk_GgZcq^mctLn_WxoKn[ptkfab_jKoiVz~Z|zcbv[}hgpKm^Nmcd|Gh_Fd`KidIk`7UT(EQ:X^@][^yn~~}lc\TZVc[\W\cZ}b[x`\|_ga_\W|Yad^\c^{oaZQrWPtVgge\\|SDhH_dLjV[~_aP`PrOkN[@[D]G\{F]xJdPbKdHuSxOe7}^yj=[@;ZBLoO8YB@^J>YHB_JKlT<[CB^K@\HWuXJiKGeLMlT@bCCb?FcEPjNXkkScjgwbrVgow_nxjxku_i~anavzYqbSoWUrUPnZTpb;WI)D:0IG!>52P>$A8)DB76$>:#<:--/0&9<)8?(8>,A?*@?'A<0EE*>:0&9N?}ihhz`pZ\xLg~TlXt\}grWoY`zNVpH[rJ\vI[vNXrQOcGRgEqfpj|spd]tS^wQmZo\m\auQcyP^xNm]G\7MaW<.F,5Q.Ge8Qo?d}LjSJ]<>V;.@-5E87H97H69L7/B1)83#50 3-!6'$1H<9'$A*,E-1 .E25M4D^C3K7+" 6$:P65I10B-0A*/E,8O0>S1BY3Kc7SiKP;\{2L4KNoOVBC`aD-:E\9LGc.Xpq}C?Q#a%"*J(-0Pf}'`r6[i%EXKe&]@S#dz+fTOa!Ri$6L-D1 0h{G/>rFSl$p?[x&oD8Q/FPb1;%)17=#_l0er2gxet8FU"Ni!$@ ?[+Ec#azAf|F\u;Ri)_u,Pl,L*E=Y!Ea&5L f=PkNhTm,4MVu/[|.4RF]'ZB[3KDZC\h 6c8l78K"DX_v34FTx?_z>H^'z05g}9?@Zx=s7Zn)n)r(6:D5;y7eq&9{:_}7Ml%!;Nh:Lh9)B=Y*Ec0Ab&Fi0Ko1Ji20Kj?Nq(,I_B"*DQo0Gd"3Q3R[|1EkmIFjtQ@`,'?&$>#5Q*Mk7Jg50J'3J2Mc:kE[z9Jf0`Ur6Hb=LaWK[\FUURb^PaV_o_QfQPhQYrYqtpsf~h|wq}yLeU+CI3LWEbdTsh8OUCW_JdbZseZrc6IG->H1AQ/AG8PLR7IdUjrmgqpxcwFf`Ppp>W^KdnB`d=Z[}w{ri#9W+?h5Ll:NqBVw?Ru@YwJe|VsC^ucQp~PpTt]yfl^{ekrmRrbTp~cQlzVrGcpRnxSsu[ytasuxfwmjv\hfoiviCfgSshTvpOokCgbCh]]objRxbZ~nGi[@`TFfSRsXUv]Np[;[JTu^adDcO?]Ja[\P\Z^dTveBaWfvSxhcpw|qtjtVwcZ}gho`_aWeTm\faNr^KoiFh`Df\Tucipvw[{apsqsb~iz~f`gbakajjoz|z}sR|OIqQAdGSuN{eYzOCfMGeQPlYSsZ_^gaomvsj`dUjP}jWzOieOlO|mxfff[E?\LEeP@]J?\K>]KWt\de;XAB^HSnZexx^f~_dJVqnzQ[wYfzev_oYjvx_|hkf]xSk_f_FdH/M>*HC"<7CbK|sDgN8/>\V9UI(C4KgS'C723.@J.8/DF)A?!923JQ5EX8WnFPhF=W::R5AV;@T7AW4>W8:R:,D//E4"9(>W=Gd;oV]}bCU>1C/4D8.?/4E6/@.2H4 7#4('@1*F4!8+-F3.($%!*'.. 0-(%$90,D75'-$(!-C30L8*A3+'0B3=P81C1'7+-?/*=+3K1;O36J/A[5Kc8TfFYfNI\;BW6Ld=cyLmYXkIqoDQCjtgiw]WjHWmCe~LhUNi<>Y,?Y/Ke@[uFQg9UsELhB7QITbZP{yTMG=IJDOPMMLDID9QR27YLk*I1Q Fd"-K/Mp.^scMYLIe$g6@m3y?|P^ (=6LXv!H^3Dt8m?S 0 %1,#' #uxH*1HX+P]3<`q 2TdGVer)[x4@^!&AGc-6PLc(3K^w;Sm'p?Km''B$@,A7L&Ib,Ql'\v/Pk,,EBZ`+Eb#!4+?Ng5AX%Wu8Rm143av!r;1DEZ$CX#Ub0DQau9p?H5Gx%=\q K_Ym(Xl)y>n.@Qm70.c);;[}Tv;4};(FTn8.J4N(.JB_*:]Fg6>])Sp6.K^=?dMk1Km ]y@4L'[w8[5Rw7Ry*T|)Gj+>[!l>d.vExQAX&QlAiRWs>kW;U(C\*^x;mP/L4OqEFc)F_EQ`^L[_KZZIZPXm[I`Msxw{mq`ud\nYnfyu}uk_lfqko_qigGbV5NHYrg9PKFYWAYQXnbus~lp||SmX=XOmtyvuzLfYKdYMhZNl^>YUZnoYlkNf^^xmOjePkfLfd^vtVnpXqTk\r}ijaiw`RrfaWs?_j@]kIgsYvWuxmf}bxsY~vHleQpcWyhLs^^qNpgOol7V^:Uc:Ze6S_-LWKmr?a^Cb_Aaa@`^Jid?]XFcVA`LTt_6VHEdUHiQUzXW|]>aCqjffk\cWw\`eFgK\|\Xz\glclhqpp_Z^V_XfY`Tg`gfqlrh{kxgoaqcsdtigdyutebrZoZmozv~nkvwheXhXq`ramjzb}mgfd`klrtfciaSuMJh@dQz_nVdKbPiVcPoYm\GfDKgTKfRVvSn\TxJJiJIgJEbCVsVy}^|hkv1H??US2J?IdMUsM_|JkKNqA<^B3N?>XL6RI<1BA3PLHTJML@@H>;JE7gtCQm%+J=] Dd$8S!*FPm4a}359@YFR9r_q7iBmFg4Yt#=[On,nCE`!a~6uBAT-'6"-!-8@"~EN_%v/ai"=Kp<{<\m/|&~)jv 7RUw/`=A^3MEY)H\.*>:Q"Om&^~1A^Kh+;V##97M#Ld0:RC[Tm*F`Zx); +).@)'@"84IG_![t*=Zt:~/h},uAh8Tl.Xh6mvLGS([k3@U {932x?[nQ`.Pc&t7f~#9s,7,84RGbf*On<^:\i4JjP?_Ik=3N"nNUu.^7!>7R&vREe/(KFh0'D\~.:X)3L+!7*B&A_3Ln%C`00LYy$k8f;k8[0JIe>9S+=V(sVa}@b~:Ii$aHFb3Xr5wOeQE[IEVSCRRos`yeWqc`{oZshit_xdUkX`sartXr[qsvswq`z\hbsfpalhgjyz{{kkrjsezn|pwo[yWjewoqlg`d\mcmgd\xgsjQpYI`U[niavjYogOfeNec=QZUjiVql^i1Pb,J`5Pe,F\8Td6U`0MY,JW:Xa7U^Ede@aYFiVTub:ZK7VFNnW<_FCgKCjI_cLnLLgKEb@oa{iod[khgaYvYPpUQrV\|cXx^[`JpPPwUNsMhZxcmWmSpSoUhRgVZI[M_WKjIjgtl}rqwkyhp_udgXeQkTgPt]cSqfNoDr^~exdla]~WiatjLoJFgIFdEf`~p~iy_mPjoWlRzaipY\{Na}[WsW^w^]{]_|UQrIQpL@]AA\Hfp^yf^|k`qPob7MM7MH[vf>]BTrOOnIAcMEfR3PG,ED)AA 667RI;YJ6QF*B8JgSSqYIiT*J?-LI%C@/LI3LCA\MGdPHfPTzZ`gVkmO_s_ltqx]vf:VAHg_VvaSpP[wQg^c}\QjNMfO2K>)D<9PE8VF0J>,E>2J@C[V-EC5TI1RC+J<*F;.E?bt\~b}`gdoetXjoSoToRev^eSXsMTlJWkJYlHXqG\xNZtTE`BHeD]xSue|eq]k|``xVsg_yT`yRbzOeOdLcROp@Hh<;T39N8->.6H5AR;;L1J]=M_AMdDCX@2E2(?-+B0'=./A43F9+8-M`AbFa~@Qo1[z<^zH*<&$6)4L,Hc=?V5:U3@]9E^A!":Q>>V?*E1 6*2E9-B6-D6%;,3I31C.!75N-eR?W3NiC;V8 +"$ +2E4%6, *, $!%$%"3'ShI)<"6D6-=/);*/F)TnA!;=V9/H$@X3Mg;\x?Yu:gCb~EjxTVcIgl\otdseTiAjTZxHQmf8e(l6wDf}'s,@XJ?B=;|L]MH0Z/6-=BC/}3So^y%('2r:Vv 443gz47&!26C/Sf)it7z4o&Vk%8N6I$:6L$) +2J+24E]#l>\v3Gc c0E]2HZr:9SF^!_}*e- +) FUdr,n'w#ew}0iv/Sb*,<:gvXglv#z%1;V#Bb 5TMj'Kh&Yt65O/FB\&Pn(Ts&Ut&b+j6)D 4": =U!Zs4c~.D_Ni g>(%8 0$$9:O#8N`y(@Y ]y!1j(Lg H:TF\Ti!ex&t-t,B}*86^1g9g+GIg(8V)G?`=1Q*H1K&+E8Ud:Po/Ur>Dd-0P">_1@a,c;=Wm6.E#!6&.F)Xu>=Po55 A\)o1Vv,2RGe+n?|T0K!B[9jSF\-bzEa}:vQ]}>Fb.`y@Eb+Kf?OiJ:N?Pd_a|n\{lQohJfdNihOicYsiYrde}lHaO\vdWr]\v]nia|]nkpgxmmixworYt\XoYuxOePgzf`ybPiTIcR>ZPJaWSl^F`M@_E@^FPjMTpP\|X]zNuhhajkWm_hm:OSEXcVjpEV_[qrG_e8I^I\kF\fK`bcwsqxNlhZc9Vf:YeFdiGdhGgdLpk6X_;Vg7Rd:Vh1Kf,Ia0Hd)D\7Sg6Qb,F[.I_7Sb2N\5SX>^Y@bQ?_MAaGqmb^`dFdL>_BGkJJkQC^NGcFxwpvxotsfu\}U9YA;ZL>aMDeJT{QT{IZN`RdTFl<`W`UaRqlYSoMc_MmOPp]AaN>\Q\|gjjNrLFkBSzJdXW}JeVh^LpMA`LPnVQqNbaUuVAcBc]VySmhedhgJhMqhrcvbnZeSpbtck[m]xgkZwd\wQZsRXwRj`cWh]UuQ_~eclitbqeqirJcV8OH4OD2M@1K;=YH7WM.MD7TP+CE#9=!57-HB8VL8SK6PH2K?;XE7SE?ZT37"9:+BB)<;2HA=VI;*A=;TL'>:%=<2OE9SD)D39O:th{jhk~afl^mGsVf{av[vYtYiTVrJLgH9T7WoQXlG`wOa~QXvLRmIB\6fXsbk~[tcfzXid\uWmg^wTpcfXb}OKh;D^=9R5=V8=V=0F3/B4P9@R7UjHKb@@Y7/E/3E2+@2 5+*=2%7&/A5!2!CZ4If40K"7S*4O#E_42I'H[C7P.;V5=U6(B& ;5,);9*@2)B/+B35I>$4/1(05P.8S.`{NA^4KlCLm:@[2&AA`9CbRp'Rm&fCc;Qo0Gd%^`{*h8}?F;~+w&H;9O=WbeIBMb39.$#)MhGa1P Pp UrZv#Ed@]Cco73L|-k}#,9(}0Bz~/o/2=AU_|&G)< +Va9Yh?;P+5U ?`9'I6kA80A;6x-3Gn%3Bb:S9Q:X3N7PVw=1P;W'-G%3M(3O Cc%Nl7Gg/Uw<6U.(H4T0>Mq/Jks6)G$>qNMj3Ki)d@Dem4Qx,1O Lg<_|;xPRm5WqI.FNaC9O'\w>a=?^_{=Oj15P'*C'He9fYTo]Id_C_Y9TV0GO>QZ4FL2EHQh`Vn_HaSXtcZu`^watqRmNQkNkepgrsIhTMiYOn\MiXAXJ]seZo_DYK\si=ULQk_=XQF_Z:UM5ODHdUAZJAXAfgGdK\uVh[]zR^x[Xr^ioAYU1BU9J]IZgFZ`9La7EdARh>UaQfkWlgwIeaMhrUnvPnmNllX^@\Y]xr_{jelhmponshuum^|y`|uZwtWxmiWvS{ve_j`Ty[~MtEmt>ci0Od,Ia5Uk9Sl/Ja3Pa@^i=YdD`eEgeA_j0K]D^m4Pb0Jd1Mg"9V0Ia*EV&AK6QW3QR8T[*HN5SU>]WBaXIeTgepbhYzmc|]~Ygh;YAwmwe|jf_b|]yk~iliU~pnovC`J;VNLlZCfMOsTJnKEgGKnMDeL@bHCjAa^HkAfZvzie\>`FDc]9TV2QDRpU?a@[~Y_ZriOrIPuGhghnBcM?[KFaPIcTKlWpk{o\{RKjIXxPdZTtP=[9qi_UndY|X\{\VvTCcBok~xzt~~_}Sj^RtINqErfqm[{``~h^zmj|MoXOpZ>\IFbS(E44MG-GC.ID,GE6RN0LG0IH(@A4JM3KL*DA'B=8OI:OECXK#;2(EA%=A"69 45$56.?A3FE.E>6OC1L=X;OhCdyQe}U[vLVqKNfDj[q_fvVj}ahz[`xWMeI`tUfZiUYsFSoETpBA[44J44F7+?,/F1,D10E57H;2E3:M9RgLHdDAZ?2G48J3*=&)*:2*<1-?1?U<5O+9(;3%;0MhEYwCJf<=Z+0L&)D$+G$D`8*G#3R'?[0@X- 7CT9$2"!(*,!$,C+oZ6M$WnB+2F'J_@3N%A]+Pk4Ha7Id=Ij8Oj9Tp>eFhHhHUr?^rL|`owX^cOS\HFT:L`=J`7;ROdTL\v5CILNN??b]WWZrgtGN0/>!& "Xz)1H12L&6Tq3g3?\&Nl4A^+8S&Dc._~8SL`"\p)f|1CFv0DQMJ>]8TGOW~SiSKL&.eoau#7S-F+F9V!)F#>2 +$=b `5&C Db(KjZ|(:U%; BYe2Jk"=V!'=FY)z<2I,A7L =S1He|,j:@h#^}AXo14M!">*F+F2MKm#1S9ACd l5`u9:y0E-6Ee!K2K9OLt.>]Vx%_m(=[;P3NfQ_/B\:Ie3C^BSaJ^dUihisG`^@Uc>R^Plq6QW:TQZvpPplKf^HdZgo}z{hmr`}Uww`nd~_yjOqdSzpBjjZCfo@alQwX|Qt?dBh8\d*IT6Vb*J[1Jg:Vn-H]:We8Tb8T]A_j1NZ#@O]O5PDJhXJgY?[D{zuni}wea^UmeebsksdrhVvUhcnmDkDcTkXyGKjHNnGkZya[zTFhQ-M;>]LLjTQoT7U<:\C;[H7WDY|bImPe`~lnZlYeUvo[{cA`O{aavtiikk`_tqgl?aGPqWCeN*G;:UF@`Lcfg_f^tmpm_[mfpkedUtVVuZHgOIfSC^NA_K`kStWeiTrTWrWythq_rff[g`4X.idqokm`hJfPEcI\_JmKQpS(F8%?=-DD-BC/JG3NI)B?!9<)?E+337;WR.DA0F<@YGB`O5QI2NI*D>7QL2IE/AC)<=;"<:*@B)C?BcU2L>;J8gly_ub{fbvPn]mgtUlw_qy]xZ{\fwbn^`vROeC[rQ]wS`zRiY]pKYjIWkLLaCK_?VfIj}Xg}Zby]QiG_tQm^f{Rg}Pg{NWmBQkBWsFJd@+A--@01F7)B.1J7*=20B54G41C12H-7P3'<&1D/G]:Og?ZsO6N202K--%93../+%;2&?/1I7B]:1L(9'Ie:.N$5T+8U0IdDBZ>#4!?M7"1& 3('<*4M(lS2IrQaC$@E`5=Y.VnCTi@Kc9?]-Ie3a{MSn9iMWuEM;V Q0)E<\,">G`._z6IlTu u.w,B`=RPt%Nhl/v&?^""@az%?[wFe^"w2?b9WEf C8QIe0xR\.d1cE/H"(?'.F)$?:V1-I(G[{44%4,'=*Li9jB\w3Zn3TJi#yQ4T >_28>])\@;^uTOi=Yv;iD[w4`z4}R-Lw`Hj0qNIf@7LQ.BO1GT4HV0BR,@KYsmOk_D_VFaXIcY;SESkW\uZVqPee\zcRrdEdZIh^OiaF][V5C^0@U>Q^EW_Tih_vkZpm9PVPkfTqhHeRwrdfJjXPl\[wjdk~y{ypomhtmzu|gecq[{wAahMkmhs`xZ|tHid9[`<\iIjnOmqMlvQvU|Ho7Xy^\d|Txz$@[+E_2Md.M]1O^6R]Truc`y;YPo|~zvcpt`bZLlLhhB_KCaOMjY[yd`}hIhX2R?grfjhlKkU[}`]_`\]|V`~Z\}_VvYMkMPoOWzX4U><`DujaI{hZxNi^udgUcYzuNpL5R<:T;@\>slhaNmN:WYL(C>B_T=ZN0JBcsVvf]zcfgjgzxsmqfzjsb~mgW{kk\h[the^CaAsjVzRHmHFjH4T> ;9&:@&9=-EB7RO!>=(@F.3%XS6OJJbTEaL\f :2%:67PJ2KD-GA0CE%::7OF;QE2KB/G:JdJb}^QlM`|V\wM]xOhbG\KPdWH_Z5MF0ED*>>46,GF-KD#::-F@#C IgD3Q233#%9,1A)>N=3?6 BUC$:,"ZtG@[/^{=Rq6fO8R*E\7Lf;kOawKqO^{ARl@Id2Ql>Sn?Nh:Qg;^pDW\EWMKOMAYaBhFV(cu2FVB?Mx9r3CZ9JI1./s&Ig1GDZ4P*I!7X_EYyKFa.,GA]0;X21)9c|MIb2YnG=T-UnBGa63N!vOh9Zq%[z(D>n4a{4Xo-Bb>LjY;~#V?CXV=pV4,g|(C\%9P'"66,GwEMpPo(]{8%C On h&]x(e4\~/,D4N m5r.Wp"o9bvv)9h#Pm(CX vNQe(I[LKSGSm@D_Ql Umn15H ,EOj"Wo@y4Uj Od^q+Xjw:TkRkm%Wd:FHV.@ =WBX)=5F7F0B+I:U- 7Le1$ +.A\5A^)A`3QKj)Cd Hi"Kj!Hi!7W6Rk1Tt#4\s1Rnp5.O +k@s)7Ec"53':%:7LI_DdE\b{)[|;395M @Y()Fe'Ed!<9Xp8-`ui|*=:Mm\{6[p.{,{*Qr1(G@]q:c%STuOo+?^#Ec'Ln#`3Xw9D]1:O)H`:A]0,J;\*Ac,CdC^2" 4M*Rr+a~<-E ]*H.VpJDg2Qn>Kh0Xx?"?(@%8S,So?Nk2Jg+>Z`}88$?Kg5Uw;TwL7TL"5@3FS-AK':F1GLHf^Nk^YtjA\U4MGF_RQmZRn[`yfWqbIf]Ca];VV@\\9QQ:MO8NM2HF2FE7PK9MM.AE.BF1EG.DF:UMBbPGdW/EA.A]V[|mTsf`mzyxxg|fosdg_C]>cWtf,I/LfC?Y=MiKMkNmvLlXMl\CcUSv[NmNFeGIfLOlTRrWRtULkKicngPpSJkKStM`URn=t^rbjV{ibT]}Pmbyo|um[g}wzxeVRnGvWwRPrMTvWPpWHhSFeVA_S;VI[{iFeRKhV:XJ.J:PoX`bjiSuTSvUZ]Y}[geFfLB\HC`FVr]7SE4OFFeZ-H<.HD9UO.HE)@=Nl]boTqYZz[YyYSsTWxWWvSVvLuii]zjsbn`.7-AG-?A0CA5KJ4JCE^MXq]OjL]wT_xSOjLG]J5G?>OK4FD0DA&<:1FF&@;%9>.1(C?#><("du_|obskr\w]tZu_o{c|ct[qWh|Qp]rt`aoPWiPCR?HU@amQTbBRdDQhEYsNLaETcJZkOQhM=O4WjJRjDPkFD`ANgJ`xTYoGVhIJ\@ReDWkMUlO=T;6N4)@+.C53(0&'>1-B6->20@.5G51C//E,@Z8A[/@V0CZ3@Z02M%Je4i>In?Tq5wRPf:-Wn<1*,%6%0L2 +&!7)$70(%0**?:2,1.(>2'C-5R68 (C,6P58$;,9#*D03$#>, "0"-+%+8'#3"*8+K`@;W0*AZ1Jf7Uo?Uj>dtMQJCabE}`\n6btPT<7R#?">(F'7[vDBb.LgA6T,D_2^vN0I$YuCCY.Tj=Hc0A].cIc6?XJb'c=]{*tJ=/DjOK@Zc1Xl/G -I(E3 1;T&Ts3&C =]g?Kg&Uq1~/P`ATQb(HZ#DUSd%Uj&Mb#Zr)bxD8G,<6G8 0B/E)< 0A'7;Z#^}==Y&5L$6Q\yA+ *B 2NgH:W -J=[(8VA^ b5IhG`$7Od/v@d,e,1O$@%A 9Sf"p:.D!F]+*@-C#1K0I}=b Up_x$m<< 3J,@;R_}-Ope$o4k#:`y ^v8D0'-?$ARf.y5w31PBa#Mj#Hfgo(o5W~,3XQo4Y+UpF>Z2Ni7rBTKLaVHaRH`RLcTJbJUnNlbYuV\x^Tl\o{fzuUreTqf\|kY{kJkaJl]HiZOmcTsf[|k]ecePrPVuVYwOc]sqqnjlihgjckOi[XsfWsc\xdijsq^^E_Cb`iei\metkem@`WGde3IW%AMIhi>\_NnxX|_cbOv{>^pA_qHepdsb{hxgpclmvrzSn]Qm[cfgnkgxgq`lSlcHhUiQnur\xugIfSQpf/MIHh_8XI^GIjRXDHeF3P@(=D*54>$@D:=!9=">;'A>,EC860IE3IE$68*55;!68">93MG0HF-?C"6;(9>!49,@A3KDA]LGbKWqTF_CE\IIdS6LD>NN6FHE\SG^R,A;%8:!;9&;B$99*D2JZI}klbojcqQzev[mh|d{eq[nXm~Vu^q_bpXHUCZeSAR=[jVVcNTaGO\BM`DMeDYsRPgLL^BJ_@9P:>P8YoKF_:8R2F]JOdTD\EAW=;N7RcKVfOOfF2+@4&9,7H:.A04G0>R5>X9_{Be9Yr/^x9=V)D_/Xv*]yf~Tnf)Zv$y@Zs(AW2"9#%9+*E/9UA!60))"7/##&,%&21&$ 6-6#9R8*G*$@+0H10(!;+3,)!)WpETo>6Q%OjC'+;/9I6.?'.ELh9rV>X+_}C2K%4N-Fa>8S3Jd?eLYvg3n<\~"}4ARk:h0vi|1DZwf{5=P=K"GX$>OO=O+? CZNb!*=.A6I0@0G*E[Wm.2E.>(C&B.F%; ;3T@\.4.F#)B78S+9W#88QJe#Mn Nm"He$Lh8WTt&Kj%Da'.F4M>U\!7Z( +I`+5K"*C9R@Yb'r'@7R!(CXt2_x-l68Md{6Yu'Njv/Cz8s6B[ u8d}"ZuCe(Tjv4.H Kh#_~)w5Mk 4G_:'5O,&ALj3@]/68P42J,c~R!51GYq/q +-A63'.@&WqF>Y)\yAjH5QNh5hK)A*1C+uNRr2'E%; /M-2P&LiA!9=3JV:PU5IF;RO-AG&:@*?D,?J.>#0?&8?0EE0GD6LJ4HBBWL:PE>SL/D99P=KcLSkQc}^LgNa|cRkYYhd\kkYqkKe_No`[~lZzpavZzqPolOoiQqdRu[ciEeMNkRKgMhlZuaXp[[xd_|j]{jWtdTndRpdJhZNj^Tq[rksnh^nbbReeNf{hmhqw^|jk}`uRrrLl}Sy`ni>aiD_wDdvMn}XyeHg[ssxrmnVz^Xx_Rt_vOmbcuXvc\v[bz\b}Vv_uXmR^OaUm[eTq^ij[MiW1MB7TLA_T+DCA[SJbXMeXAWLJbTPkTJgLPjTAXHD]K@YGAZEc\s]y_uu^|eoYqXrXxS=VH9R?qflac}[f\qdn`vcrcZNi@YsHmfc}c4L@)?B/DK)CE'>?=WR4QIIeYA\KPlTQoWEeOCbO:XI?\MFbQ?[LHcU2LL,@CBXOG_JMgNXrWHeJSoV-D;.FF9RPLk\RmYBbLGfQGeS?\KLiUJhNNlOMmNLnPTtXIhHf_X|SLsAtgZrW&96'=<.EA1I<@^F>]L0;,;'=F6:)GI 9=";:/GC*C@0LK4OFJgY.FC*217!670KH 87#99)>< 8712#69&:9.F?GdTGeMDaJC]J4F92F7&=.)=09H;5F<&7-/B7/@3,?11G3@[3A^(Ie0=Z^|&y:I|Lh>u?_{"i$;4n'y*]x&b/"31$="( *!,,'65+#$;26N;7N82K5%A-!:&21%$;-1%$2XvIA]*b8Mk(\|8eI+#Pl?gId~CeEJh-`}BGd-lLSn8G\7/C,8N;3M)Lh3Yx>Ut:Gd.D_/XuDJe<;O-E[4IY:IXitAHc4Eb&t;v.~Cf?(E,F&NqLKmC.M'?_>>Y61I/1<[4Gg:<],mSHc)]{;_yE`?Z{7Ig'Mk/g<{Bf{*VPm1XKO`j-e*?Kf~+?;*-Jd .E %</E)?#;, *>3I"-D;U)6S(>Y/83/D(C&D +=R1 ,.A +!lGWq(6MJf+I`"GU#7DAQ9LZl/FW %8 ,D2Q#&D%? 4;S,6N 0Gc|.8KCW d}!d},8M/1?6H*@ +ez2au!>;5J 4e{@zF(P,2* #9/I$2L&A\1+D'02!&(B#Hh/Yx3He$#= St0`3Y~"Op!Om%:V-IUp(1F g?_|#p0-N3 2 +Ok7@ZUq7q3v0e~34H%<Rr( 8@TCj3Bx4[wu%0DDFa:Sd{7-Ix()A;ZBc9]J^x @Ut)Om'0NPp,Ei$Op)a;:S"Oi6_yBkT%?C]&{L3)A/(A$Rs;Y{M8>&:G.AE+?>+>?3NL2MI;SO4KH4JI0EB1CF-?I/<*Y_OieZyjY{aFhP:YK\xeekZ|dRo[ZraKgUDaURmgB\XAWSJdYVreIfW]|dnmVzS^VaStfqpquoxjwsDgRAeXhAbiki;Ym>]xAasSt|zwqocLoOZwZuOoeEcaFa^KgYSoVTnQMfHa}Tk[k_d]ZyX]x[C\CYrSh[d_^zjSobUsd@^T*DAQk_;QL6KG4IEE`R[v`E]K@WJ5JCJeX:WGKiP^ZY{Q_T^|Qvem{dlhx9V8cfWuXG`FYqWWrUYvSJiFa~XfZpa}bb}Iu\p\hj22#5A"9=#=A,HH6OPJd[GaTE^O?[K=\M2QE3NF7PJ3ND=XN*C>,BF6KFTlYgdZq]9QCJdNQmP`{g"<:0ILEaUCbREeQGfVB#=C%?@+AD.@C14.JK3OH6MF%?:":@ 381613"::-AD/FF2MJ7NL*CB$;;.FB7RHB]P?[I7S@:TF)D7MeY2I@7NC1G<'@<3,CUD@OA;G>,;1)90-?8*;2:I<-;3.>5,>/-=0;P=%?Nk4s?`~2e:j>zKj=d=mEp@zEyCa&q*5n#y.w,:Pj"Tn:"9!2#6.%=/'&1G9'@1!7*(=11#-!$;/&>)'=+7( /F)XvDLq2Vv9[{7Vt7Tw5Rt72-E-Tu=Pr3^}?cBKf1dMVu=]y>_}?H]3/?+3C32I0Gb5Kh6Mj7jSLh[&Ni.@\Ts)Wq#F{%#z,3{&~'}'DUx!}X,3O>U!=S+YVq7?Tj}.lBUd{.e~(<-y6}HAZ5Oh30\u:l,y/:Pq'n7J[ 2k&IfnF0<:QT$?<=SS7MJ4KI.DB>QM0D;=RG@WGVnYE]I5K<>TB]x^Xu]SmXOec(;M4HX)AO,>P->V0?[+:T6IY>R^Pbb_whVw_?_N>YQ@[PUthVsiMi`B]TAYQF\YJ``BXYMe`GaWNiZKfS[|gEeSA_N[y^be[~_fk]acda]jc|rtoIlVMrcW{psvfcbvBcq0OhVxdlHj[VtYEaHLiSilkmVveDc^Ccc8UT@\TKdUIbOJcNYrV`~aJhQA^MF_ONgRTlYkiWrWa|j9SK3GJ*@?VTB]QOhV>TG2F>=SJ?[P@[SC`PHgKD`EXrWLdJc{^LeIncj[a~T[vQa}bA^FOjSKaP[q\[uX_{\ZyZJhJ[xYY~KkLmPiIjV|qKbHRiW44)DJ$?C12>YNKgVHfV.MC8SL=YS,FC.IE#=;-DE.BD329RJNhWC]GBXL:PDazeNjOA^I9VI'C@>WK>ZL5TI8VJ8SDB[P6TFB_KHfL;XDHcM=ZG:PC6R6,HF7236'0,5&>C*A>F^S3MG&,48.CG3JJ0KE56&:;.DD5PK&C?%A@1KI)D@,F>6SF5QF!:61HC,C=5*5+$0'%4.'9&>[/Yx>Xx5}Q]kGW|=Ff2Tt>Ro9_|>mFYtDSb2i5n(w(}1+|0m8Sv.8S+">*&A';U/MgA-D+4$(=/'<0"5+"6, + 6P5!;1H0)LcDXv@Dd)Uy=Nl5^}AJi.YzUq9TqA?T/>X,CY*^xC:Q(9K66G5+@*QkFD^5B]/SoAE[5VmBTfE=H9)>Ti*Yq&Qc9J +Od*'97J%&:! "&\t@Xl(`p>#/;O BXOg%4Nf)Yt&Tp'Rk:C]t Kfk"c|%az"l cz"9R;TG`F_ !c~64 +5P 1E5=\78W%;X)6S$.K@])4O*/$))2M. :No23 .C"0 Us7Hha4%B/H)!49Q\x,c-QsC_",<EY#f})IFdZz g%{/~2%@!8 9Nl%I>S[q,Of@q*B]%5K]-Xo%@Uq%Oq`$Hl,Ed1L _}3`2Ss)nCgC,b~LF3NN,FF5:-70:%7=/BA8KB@UIIcO:S>8NBL`T>RDJaN`xc_zcOgd(8S-=U8I_?Rb0?X7E^6E[;Mb@R^VjdQmX8UG*C@Yp\SiYSf\Yn`\v^QmQFdO[G;X:@a2>a.RwV[5QJRqa=^I@`R(EC)BC:RQ.GF&B@(@A#8;.C@KcW;XKTHC[R5PF6TK=YM.HA9SI>ZJJhN[y^IkPKjW/J=]|hDfJBbINlPKmJdViOQx9Mq:_@cBcINr@\~PW{O6Y;7UH&C<$<>!5:#9:-DC:TJ(A<)2&4#/(0-CE%?>-1%9< :;77<= ==$AA,HE/MI*EC!8:)/&:@+?D2,NjUtu]yXhbumua~ir[kIizPwhwjteu`v^~d{[z^|`tX}ewaq{^Q\F\gH]kIduTObC?X=C[E;U.=8/>9/;11B59G<6A87A8)3',=.Pk9eDpEwNY{<9Y#_}I>[0:W3B`5B[/Mh7Np9Jk2a?~W]}9nHwGv=i,i&_m*m3Pq.-H'7P4MfD6N,)=,+C50H23"*!$5+&=,4Q)bFD^..E(:U.^|AAd'Wv>He0Po7B^+Ki1_~D?Y2I`@7O)F_;Kf>C]6MfA?P09S)a{JOj8SmCCY<=T7D^@=W8D_8C^3SoEJb<^xHI^7TfPJZH1A/?R=E[;nS[{<[z>\vEC[32J%Fa1mH~Fi)m1u8Vs$vEOl)Yz9Sr98V"F`=;V10L(Fd0Ss9Tu@Cc8Li:XqBIb92L-/F-)&;+0D:$8&9S4,K*5N1;T7;Y9Fe?Ca3Nj>E`5A\19V'@_'Gi$s?`kz.p/^Uwa!Be%Kt<2U*Pv2n?0Mq%ct*[4?V6M 8P '%8/!2!3% -(>)  +% 5%;If-7L   /? DW4RgY4N?YFb/H0M8RFY:K@U-!:Uq.CbTt59(%7!5L"Ni)=W :WMHa. -;AV=Ve~*e%Us#u)-Zw =S]u+B["<Wm&\t#;nC*@ Ob3ZPeSk@Vl ;<6`w!Kt=ay k/GdPiq5tB,JXy1Y|(l@W{-hC7O$;T(Uo?BZ0Tu<<\&= cN2Lf:]|6\x;D]4I_8=S+tG~C&>FU==Q+*BNgETpJ6!9%YpL5O$Pk?Hc6-G!>[&Ji!B>ZXw9UxB9P@,5-=%5@!2@2<0;'3$/$5=8JQ9LN4GM(9E!2C(:-CP(=J*AM 3B+WTEb[8QR1EM2DM7IQ,DJAYYRka^vgMj]0HI9RQ=XHPkX8SH?ZSVxfGhVJia:US4MKboWweVvjRrlDd]A`^A_\7VWCan>[pEesFby^mvhMr~@_n?`lLjg9XVA__Pnd;XLGf_6UU9VZA]_7QS5ML;TRD^Z9TP?UQATU.?E7KO.BD9NJ@WS5PL9SQ.HH1LM)AC1JN'EC@\W6PHB]U1NG1DC=WJJcOZ@IfISvPYKHm=3U,=^4Hm8[FPv;Qw>Jr9MsF"D0+L<2PB4QJ%@=(BA+0!59/5-7)8*6#8<1IH 7:665,0),"41yvkcYtSG]C;J9M_Hrp~]`pOr`wdl^i|Vsas_kUxbMg6Lc7exPu_sx`^dXOVKP[KfoWP[DP_CPaC4M15/FY@i|VaxUE]F&?3 5.);2.>5(<13G:3+?5.D13M5BXD6H92B65F7/?5)62(43-=40@3;H:@JA4@6=H<:Q.qTfKbCnNNn:Da98Q1+C&KdCDa6]{N=\/Ff<7V*Fg5[z>iPlOLh-`|=\{8Xy*Vx$b(ZW{$A^$6P!.F&0$ /*3*$<,&Y,Ea0XtDC]8C]5Aa-Kj6Mi63Q!Kj4Hd64N'B]5:T,2N(;V76Q3C]==R9AS:AU4NjAHb8AZ3E_9?YT1Tp8Us4Vu;Ne9Z.7P*2F]3Qg*Wp2[kD +(&N`66O- DW&/E4JRk80GFW.!3BY-7Q$"6-%9,?Qh%Rd$-D-F5PMf+2-C'AZu/l~5hy*j}.Kc2D ?S -F Qi'Rk)1FBW 1FAZz@;N/F^}3Na .G&#>4P'6S)=9"= !5$"8!,&:%-B'0B$>W'Ea&+1F#BX+/HMj'Ig"3N)$#2$7:U n>Nmf%d'*C)8`s@dz4d|*^z|={1y0f'g&t+q/GaUK?]?saqtdMmEDdEVwOOsHY}RMrEQuG>b3Fj@6Z-Lt6:25,54:#9;!88(CD%WqT+B2/C6UkMatV8O4*B1 6/"40+;53B<'95%41,?3,A3+>8&933C84D78J;8P<6J6AQB.=37I91@5*65 ,((821@95@63<6-8/5G3Nk=Ff5Ff:4P&Ie8Ie:LmE'B&.F.@Z)Lh0?^EhPsu8DeFc"Un8* +)1#-?+0F.Ea:Kh8@Z/?\.@Z4Rn?1K).D,5L5AZV,D`2C[0AZ,2G&Gc3Uw-Ig'He0d:ay49Q'*?#2O)B_;C]<)C&:Z28W.YA+E)6Q23N/0 <cW'A$.F'4J,*? $91IIf05XMnJk\"2O; !>25 !; $#@FaFd:\g0h2>[>^ [%[)c-Tt-Mop0m2{:Jm;[l/$9.+6!(=&. 0 Oh_t=;J)* 0BL]4]tB3A ]t;,AAY+Nf:'<^wIHb/[uD.7Q%"8#5U)"30F0&(8 6IPf&Tl"w7u;\{+Mjax62CK_+C[1K Xm-g6Nh%G] 764(BLm=:.% !2%5H0!82E`0?[!5N"(A1HH^/Pl$c7*%9". )!42K 5U=\Jh,4T J62C AVYu GfFcRpw3[tSli[~r-G^ s,dy%j#No?VK8P\q!EC_BNj Z1JDZPew*u'i6TAc%Ad#lAY|)f<-H".A,+@"Le?5N%23Qk@1#(!(&;S3Od@aqDvFYo$]s3.%2(2G%jM'D!7P29V&wY4Ib{K@Z.La>Gb5+DoCVu&m;@_aCveRkK1F7$62&-#'.4!4625#5=->J#2<*3.:.<#4A4B-:)8 /)70>'5!.;-;$6C0:.8.7+7*:C.CG)<;.@=$84,A=/F@1G=;RDAXEKdMd}gRhXVjaG][Kb^e~liuK`_M^j\l{vovoj{NiS"7>-?J.BF4HM:MT-@K$5E'9E5HLASMQ`PNb\-8"5A$<<9OJ8NN2OK6QP2IP&=KLjqDie2RM=[_0MW[a+CJ1IM3LQ5MT0GR&;F.BP;P[0EO)=E7OO1FJ:NQ1FG/FAIeV>VJ7MEF[S;SO/GG@YUVrbMkWEeN8TF>TQ:PHDcU:ZO4SL&E<9TL6MEM`ZQh[>VJ>XN4MG#85,DB0FD&A:5S@CaB5T6&C.3M6;V6cPw\ya~R`yV?_H8SE:UO-FD1LG0KH.GD0DE-EE1KI-FD0DE2EG";<)AC,FE"<9$8:2FG-AB$89%94D_O8OH-??*?>6SG@aM7VF2OE1JDBaR=U@9M::K96G67G<@NB8E? 00)%%5,@KG2;8*3):Q1Nl@:W4;T28Q0;U3:U0Ea<4N0+B)0E+&<")C(/L09R4,G(2F+#: *B*2M+D^+jO=V$bKA_!Tt)Vx'e*Gib2Ood;!0(5 )5"3H36O,B`4:X-3M#Nl:Pl:;V->Y1W3=Y2:V13M.9P3:S7:T7,A)*B,$=($;'!4"0A32B3.D16H3.C+:S79Q6:S7@[;Ib=*@=T73L3C_>;T1@U:AV;BX;>U9+?'>L-Lg9Li=@[7Jd?QoE>^.\|I5M"$;2J"1O4TKk/Kc,Ea86P/0I)2K,;R6(=!-I'1M)3N(<\3YvB4LIb<1I.'B),F-A[A>Y>5R46R4":$$(D#0L&l]YyG7Q1,E%c|QCV5-xOWw(Xv02$@= , &A48 %IH^'9Q?]7T{Di3b+3Hd{3@X C[&AY Sh-d{D@K4j|HNd-Tl6H`-F]-Pg=AY)Me6Ng3/HZl>DV)9HBT>O>N(= B\-5J3I3J;P& .. ' !^|=Ol">YIe^z.Vn.-Zq7F`\w,Md8N!Yp-5P'(C#1/ @B`5-G'4!!.@(:Q)=T*.GIe&6RnH%?*9N(;U$%&"!6Mf-e/BbC`Edq83LAV%7NE'Fw:o$Uq@f*8Y}@^(1L$Mn/Qt*Ij^{@ #)8+>S7@Y-jI=[rF+LWu?. \lV)B%/E,4EN`y0rL-!!2(eO"A5$AY;G\1Uj;F`+(>/CJc)Ib,nEQp"h3p>jC~]d{evqfnmQgUQhVPgTZp\DZN#68"1;"2; 2>&4(4E+7#4F'8#5;-2.<,8 .;(6E):E'9C-5+7!.8+;D7IO2GH8NJH`V?[M2I?J\OReSXpYbybXmZL`TZmfOk`b~na|bQhRWm[\qkZmmxjxel]tXwo~w&;3->I.=J*;B9KT*;I%4I,@CX^9KI_i_H\NVli )7LT-EK7>=[\%AE+EO.IV>\bEgd4QU6Sa8Sf@]j>[i=Wl-Ea0Ig5Ll2Ig*BhAYKjPuzl1Vt8Xy-Ko@`8Yu+IZ'AP'>N/LO5OT#7B$5D5D5HU6JT-AL/AO-="2B.AN':@,BF+AG4=*CI2MP+HI*CH&>STDZTA\N;RL7KHBVR=WS7QN9UMB^Q>YNGbY>WT.AF:RP7UO2RL&E@+KF,GDF_YTlaA[N9RK,EB=UV$><)BC(?@/JF-I;:V?-K-:Y=B\E[xTqlF_zaY6UC,G@/IG,GD7RN-FC)@@"996NN,EB=SQ3GG(:=%=+EA36$6;.3%8:=SKIfW:VK7IG-BC*E?(;A(1-5 6=1:-13KEJcS?VJ4LEVigNWX8=FGQSTeVujranardxknp_l}foWt\p[dOjQmUkVUoEJ_?[kTHSJ8AC4@<0>:1?9EQGLYJPbGi{YgtT_rL^vOD_?BaEMjS*A3"6.%82(>7 42*>:/C='8230)<57KYNod)d)i"[v?Gv3Jh$*/F+;Y09R10H*1M$Pk?4P&Y)>/BXp4Wr&f}2i8Xp(d}4vE-C CW$IY-\r'71":7Q$2L.I*E.$'2'&.C %;7MSm*B^)F Mn/&?"5c7\{2Ea.#?*(=R ?\w=5QG[z''wF=VZt(EaD`k2?WLd[ww1Rk o!YpQc#bz*p 'w%^x.,B F+11Ed"=T );^qS*C +AWx@x+i+bH-2GB\"Zv-Zy1WnC$ 2!AV3iJHi(5XVu6Jh)%;$!$3$32H1F\2J^#pArX* + +$5)#3()8/*@I\:Pg7YqB(9#Tg:_vB+EFa-d9p9Z|._~7w\b{Re~V_vN\oNRiHMhGMiJLfHB]>HcF?WG'>920-0-3&-$.+%1L`P2H?#0#/'7!,8)4?(1$3:&4:#29+2+4-@D)<=:OH.E:7N=OcPZqVXrSe}]_sZZn[Uk[b|l]yclct`qaf_Wr\Vmb\yk[werpZrT|H_UEW_@S[:MT(8G#/F+>GYZ5E@Yh]\qcczm$0&6H"5C$8B;XZ)DH.IO+FR2KY1JT-FU2H_5Me:Pf7Qf*B\-Bb)>_7Ll.Dh.Du=U@`CeLoVxFi6U{A\~>Yx#?X4N2H'=M/GQ(?F&9D&8I5F9LY/AN/B.C"1G$4E.@O*;E/CI2FK$6C"6A>89'?=&<<*?C 6:-FF%=?3HF/C@*;>3FG*B@";<$:>1404,BA/H@/JB2ME9SI.FF0FF,EC=XP2LG&=;*A=@^L9YCaaTvMHjIAbFLpMOnJVsBv_ZzIyg`W!@*1L:<]AGfC+F*;XBBe?Pv?PpK*F71143+0'BA%?A1457 3//C?2.-,--38/6#580*PgYZja8G@ARO]ogsvnep`gXrdtgpavc~kvcp[r]gPa~Ko[iVeQZvI`sT1?/5B9.:84AB.:;/?:5G;N_RUePWfLTgFTlEb}RPpEPpLJlL*F/-B62DA)>9"43#67,)9J@:J>5F=3D:3D7=J;:I0azD[{/sC-HF^34J%3F,8K)qLNh9+,=/6I-/>,;N4RqAJkB;Y:$>$(@*5 +A,3M4!9%3 !8!64Q3!?!'@+2 5:T7*C'%;!7L)_}J7O*=V1C],@Z'Hd#b0Ga2P:p#fn y,:Ic!>W.>Z45M*5L,1L!Lg4Pn69P+1E*4L/6M40G12F6*;00B2,C-5F16G60!)?+(?+5G3,?1';((:&.E.+C'9V'wN~E}Dh/QZhO+B,(?,.@+@U8W:>U:H`;Mi2D&&J(JfK)E+(C+6O4t_$<MkA4U&Nm;Kh8Ik:Xv@ZpBavDA]&&E=@bNx@g&=V)'A2 Nc*w`r:,%?/J'C:JIh+V;X3S<`]*eKhNqz=)F9YZQ +(="+A/D,;&6* .-/ *@;V&3E%[l>7K7Q9UNh09L&9=Uk9_u-k4c|(p92C 3Efu4AU]v-";'B!/KA^&BaTs2Qp45R' !*7M1..C9QTq/5`;=\C^*Vt5Jm!@c5 '01 /F:Va~*i:3N3MR4K=U B[s(u8;N/d{GSg-_v3n7^{#dy9/ Me:He->["5R"/J"0E'YjM"4!$7-4I1^uFb|?tEc|61%:%NeA+C"1)"#/(e{S^v@Pi9:S)2G*:M0J_3_zF:Yh?iFhKjMi~ObwNczN`xN\sJYoINiHGb?Ea??[@GaECZB=TAG_BRkIVnNTnH\vO`xUYqSSlP8P5ThH[mCf{OZvLVtT8TD8QEG_PUmVjfob|inhAYP)<@3GP%7E.P*3KQ 5?*=L"7B,CK2IP,BI2?'8G);DQR>QL=RN0CE5LJ=YX2KK3FK3KK(CD0II3IGB\Z-FH)?D"4<$6@&2,@E?YS6QM0JJ 8;5KM-AB-@@$<83MG,E?8SDDeM1R94R&:;*=B$9>)AC77-B@.CB*=@*@A%>C"8?-4,@F0GF8TK;VL0OG)B=6QK,JG$=>>WU*E?4MI"64_GVz\PuUBdJ=^I?_J;YDA[ERlIj\c}TgVqa>^@/I<7T:IgC7T34R73T8Bc<-J/2'87;XM82/+$>7(D94QA7Q<9S>C^EHbILgL>WCBZC^tUqcq\qV6G>3*3C4kjolSlJn]jWl]rap\p]zgoWeVj]eWl\kYt_hTgYYtMf|Zeq\/<65AB+8;)98#75&92:LA=OCDWAMdFZwO\LXyFUwHRqDD]AAYE$9.2*'<7**"%$406G<.>1-<10>62C60@.1Ck=w5z9^-nBa6Ki,Ol/Tr'a79Lj@qU_zLVrFDe8/N,.J,.E+4J40$- 0 , 1$8%/";"(?(*+8P16Q.,A),B#Pj?Ea0#;)?;X+Ic6Sq&v0r.u1w#|,p*j0Tw Jiy<\y24QRl;9Q"Ur:gBpP?V(1F'/I+,C)/E4-A1+?50D4&:&0>.$,$:-!5$%8*#;(&jJWk7GY45K.>Z1Ca5SnI;T08O3Nh?[n:%<Tq;}$D}!\w;LMg#fG*H&.H)%:@X3&?%-F-%;#4#KgIFe?NmH  !;"FbM -Da:9S)Oo><]-C`.Da0Mm8B\/Ug?/= ; Mn-8XY(Ip4Z8\7V>S*-C +g9\v%e/+!:.J=Y>^#4R;2K3 <.Rq)1O6Q Mk-H2R Ns>]"$%' 8P!'A8SL>RSj,g~GJ`/7N:N)Nc6@V+F[51A"=Q+8O"^xDNd5=M*bwIAV"i|I[p=(<EW(#3 +'7AR0$ +..BY-`xA < Kc*9K  : 'C4 +EY"]s._u.aw/Pd(]q+o/o/gu3gv6GVIX#D]-8P[u72M*E e$Ga)Je)&<d|>'> r3Ou"> F]r.la^yG` +,+{.}-8C`!(A Qh+i-Je8=G`p/e$Ws%$= G^+l3w0o.+ %7Oj6Wo<:M[q2e|3k4a|3a|;7VdB[x9 8(%%3"I\B)<.,?4Sj=iC;Tv..B0?Z.^{L1H2,=0)9&/Hb|M\z<`@+-!=T5=Z&Mj78Q%G`(qDTNT[]\Y]\[wTeeg{bt[vao\mXxa~bvWcdntbsyhliVqcvfydq`s^{^|XtVr`kWhSfTa}J{]xVoPwZx*@/>%L_:buLfGka^uNG_9b{YMiD_}UmcKeO6KKAU^-AI*;I!,9JYMlYmWpWlR~cx\u^ptctdtiypau_rdwczdym]r[sJ`uJ]lATaGZh:KZ7GW3AR1BQ/?M+?K 0@'8 *<-?+@*F= @.7R*7J/AP-=K$0C&5J-C.A#6F);H'7G-B1AN7KO;RT7QO5NN,@A?SQ?SQ-AC@[V/MJ&?C%8A9MR)CAB_YD$5?*8-8'7D/CG>UQ5QM+FF$%AA!9>#6<(;>-4$8? 6<+2)=A8KN*=B"4= 7<*1)6/7)>?4QI+E?131EH=ZW"<@78/IF5PJ4OD$:37T>TuWCgL8ZB4U=LjT5U@0J;4K;RkOJeD8R1UtJPrCNlS!;0,I3<[9HiC2P12O7@^@C_I1P<2PBAfJ=]G:WC?]FRrS`^cXcVkZlZpWrZiwtxgj][lRul{pc~Ro[{]w_vasdu\zanUsYr[gY`|UdYiXiViUdOWwLNgMJ[LKTP3<>0::&55"23"320?<+=26J;\xWWwNY}PPvAZwL`zQYsR(@+!7**?8)>8(9:)+&(%613G85F$//H)Gd6Da24&<-(:)!1#6B8+A'!9"5&7'F_9Yq/U1Me1FrDg*Sg(_s;uHKl>lLAVBGJX4"5!LfENkBg#m$f!q+Rp$Yp7G\"5Lj7kH%85L4!< 2$<&AXA8R/Bb8IhELdJ7O8;X:0L3$9(B\DB_8Ll;De48V.B^5Pk<2PhP=O.Lb6Qo1Pq.7V'H +3O1Q>e&E A]#h6PtHq#PUo/ )?&>5 6 7 7Q : +9 >"@*I0R?`#Qk%$@ +E #@<[`}7 (>If4%C1O_2&@Sn"Rm,BY%:P'?Q,7F#4E 6H#/@4C&EW4$<8R!Ne2HY17Kg{GMb-GW,1BUi2]r7Tg4fzO$7Rb>% + 6Id+Ni1Yr7jQ`y8>ZB_%q?;R]r.K^i|>Pe"o:_v)[o*Vf&9JGX&Zk+()?%+F +H4R+Gc3-J+E&',C'0H&(@'::O#8QRs(Or-- &:7M$/I uDSs$[|+Pq&Xt*r@Pq>?[%6&8% +.BZv*f7#=m?Mbo*x#mw4m0Wm<_{e&1P]s=_u@+H Lg#00v$b|&?0M/bv=t9=]I(F/F(.@. ->FX2>QrCf/^v'rBLi%[y9Ib8"4!'6(XkH+-%TkAC\#\tBYsD&2%#:%3 0-zPo8yIMWVNXWVvKWyPa~W}[~Z|\|[{X}adg{[\`xSwY~^zY|Zbd\hXacWUsK[[Z}WZZbVeav`\]BU,Ma3wpeWr@?[-jXUoO1F<->;!15'2B$1=NaVPjD@]/4N.@\8Da0Rp7]{L7'AWS(=8m|ijsʲҶݧϤ˨ɰ̟zx{tnj}\kvsmzgwJXmN]sJRkIQhFVh@P_?P]>N[:GW/;N)5I*7K!2D"2C+?!,C/A&:>27.KI.IE)E?,E@%@98OE=YK.J95Q94O;-J:HbT;VH1HD(FB&AA#;=/627!3=/;1=&3!29*>B&8=(>@(D@9VR,EG*2+0.GC&><%8:5IK5OJ87'>A466PJ=[L.F<]x`LlO5U@9XH2O=@\JA^J0J<:SCHaIIcKHdKKjD_U:ZD =04P@8V9FhEFe=EgCZzV8W47Y:@]@JlGXtO^ySWrIYuEbMjTkTsYpV_bt]kXgZi\wdetYlUpWfdqXpZizdjVuax_gTi\fZm^lZo]]|L[zHKgBPaX?GMJN[@IM(315EA-0%(&8.KdI`}UVyDbL[zL[vQ\uKevQMaE:T;&=0"8.%81+,$())1D=C\6;U6%>(2"6"/I/3/F/-F*3(,B%#>!;)EC`3Ge=>],Ts>Fb1YuBQn9Gd/Vr>Gc2Lg;E`/Rl#Xr d*HiTu&TsGg$&D6T#2PB_'4QFh0Hg,C]*If2Vq9Kh.No6Lm6Ha.eN33G1!2"2!2%,=3&6)Ur>Yo2F(e7B1Mp4s&{3bv8EZ$g{EYl;Y/BZ8,EnE|.f%PpXul*\u SXp\tBBk7/Kn6{=GZ *@Id;Lh6D_.Lg>+I+G'C77 0/ (HAe'#A.=(M3Uj72JTn6#<.%<.* 2 6%@0 +2 7 [~.1-JGd'&H:[8 + +-H.P'FPi(Xr$g/g)Zq1Ib/3H$2@"BY-BV+Xm7Um,DZ#[m7/D BSg~1XoJe!:T>WJ_!>RK]"Wk-]q/Si'h<\t-vI*= +5Hbw,' "93N#">Mj3#? Da'/J 0H,%<('=V63P$C\--C9O#1d;,H+?+@'-Vm?2KD`*=]\{44QIjUz}"_ ?  '6$.E[u<)C2G0B EY>V OeRk ,x&[t\wt%r~8Vm3Og&iA$?64Zw$Wn,XqGs"u6,E]u'Bt1:_$3N-'3"PaI';&.I\9;N(_u+y3o.r<]y2Tl-mR0 AX1*>$/@T+lK- ;T$2KF^39P,)@*" DX0B\&+A;Pa~0@W"2DPf93J!(>\t5FJs3LEKTYWUVR[PYX\i~Yb^Z~YadXyQ{RST\\]|VbpkrgptvtnpliffaY_di]vi_ehegdvqotrbRfLGa;\zD]yC6R%Kg7lOjJ_^|SFbKJcMe|U|aw[lR~~bpk\zcxj&#&8E/A`.@e:KuHY}Kc{faWuTiv\nzexoxzupy{orxn}k}pw]m|=L]6ES2AM:IW4EQ8IR4FM;KU+I(:F3FQ 3A2@-92HO&=A5LO.GG.CD+BC)BC&:? 8#99 25*C>/I<-G;)C6[w`;WG&;7.E>8PE0HF&>?67!99&9:(;@ 3;0<+8*4-@E!56$88C\R\AGdLLhNC`=`TJkI?,"=25R?:X><^:Gl8V}D_Kw`y]mTsT[Z[{U{T~YsR}[wTpQmTpZ|fwapT_^aw\s[q\o]yhtbwhn^mYlUkWkYfSkVgRVwHQtFKo?51C50?1:M0Jf.]/Y{1Qp,Id)?]%=Z$9P!:N%?X,Da+^|DA]10I*'@@Z80J$&?!#?#'A+)A.'>+!9%3'?%@X1Kf/TkA'>Sq?Ij6Pt7Ru89Z-ZB\b=(D:T+6P$:T&%AUv@6T%B[13L$?U-9O(Lg95/"QoTSq2]}(a7Nj8,F 1K%*E&-2K3#?#EdF4N39Y;6Q30J-@Y5Oi=.H&axO0Um;,IC`4 #, +H$@1 + +$9W%=]&71M"'C+F6!;KeBb4P9U"!;7R5U(CZ+G`(Sm8Qh7G]-Si9dxA^r@Vj3Yj5HZ.5J$2F#@V#Ui0Uh'Tj.[p<]r=^o4Mdg3h=4JN`-7MSg/Tg/>PWh1]p6Qf&Od#cw3`u4J^#Xi%,/"=%>5 +Jh16U@]. ( 25O-5Q$G^,\s2Tqm>1K 4 ;O!G_G_;Qv7|(7;T _v!YJfHu.EcKh,7Rm#Moi )X}Xu3HlnJ& & %.#")7!JY'x5k,D^zCB]m9q>d|6@^GCNUTKVJPDXSU`}MThTlaaShpfhkilbzV^_VTZVM^[hkmlacckjjkefhefRdz;}QX{RUdbitdTVazPe>rN]iold^nZsah\sbgc{`5LT6Nm>XzA\yXvhdGe+E_,@P1ER+=H#7=!4709(>D2GO#7E2BULYg\ivVgsWhtYjxVfs[lwfwrxwp]r}I_h4FQ6GP?QY1CN:NY7PX1JQCZc?Q^5EU&8F+=K!0A$3B#2@&4)8%/=+9E+:D,=E.BD2HI-CH%;@"7;%<>$>=(A@#6:25&=9>VI'@83N?MjT/I;(=88SJ4RJ(DD#7; 7;,HI+ED28 3<$5>)=@%:<.AE#57%99;QJ1IF'A=*E@9SJ9PM 637KJ6PBIhOQpT7U7B_C\{RMoIAaK7-)B6#?,8Z9ZFaFiSpV[{Ya3Yw0,G Mi./ '<3K!2K!Rm;@`+6T$+D1H)Kf7;X%TqX+Wt8hF0M+/D3,:1!(1O88W96O4#:!'@9S+$9#*>Yq6kG[}8Z{8!:2/(H.MC`)7U*I %29/* 0JHh(#@8.JXu5@_Gd+<[%$CTw8&EC`&Ggx5Hcl/Ghh#3EX,Yt.qy%:i+Sk,J_)9QWs9DZ-:R);R)3D"/C!1BBY14K(EZ39O(5K(4G";O#AV'G\/Kb4=T(Ri6M_-@Pat9Sg3DX2';3M\s#&B c=EiX{!Tw$4=W!.0ID]!Wq.Wt|=Je !5 CYf+7s2p`|h*+=bt,:M!x=LE8DKKLPIHFB;;6;>>;A=><9BBA?;?>DEIKFOTUPX]ZZ`aT_PYVUU_\ZKZc]^daK]&bbcchhnlmgglgqcem{?SzL\qrilhrusoXMZea^QSOqCzPZz0lB[[yHMZacf|UcP^^yWsT{\tqlztaPgxAPWDZ\?T\:RVAZZ4JQ8OVNW9IO1CG3EJ/?F#2>$5@!2<"4<,@G.BJ,AA4JF7MG0E@=SO(>96RI3QL"=>!6968,HJ::5;+4*@A0J@!:2/-*?;-B862PI:UL5LI/CD*X0%>&:.%AGi:0S+9V1Db8=[-Om@8S-$8U(Ed,Kh-Lf8>Y11M"Hg5Jj<5V.8W5;W97Q.Ga6BX6&9;S9(:Ve6Ia*Pi/Qq/Nq$Ps%\|9a>Yw8cF2M"eD2N>Z*;Y)Hb<6O10F+.D&9P.X+6M22&.( +B_%FhVx i'2:'VwDdf;F2DWkAUm>BX+Om(v2Q|'{!+Vv1M|_e)Ibz:]Uo#j/x8k,Sn `z#q0f$f*KkIfZv#;Y `}*b)d/^~3Sp)Xy'Gg(LjL0H5  *@3'B,$@%*@+#;)4M.E^<(<Wt8o=%F+J_9B\)6 .NBb(;Y#&A51R".I!+";08-J)A&(C_(*,4%?Kr,Jo':.KCa[|*g/BfYx$EdYxVwPqQml1{v1-N Xr/pJ!9*$5-* 0C8,!,"[nAWjICF?>==::=8:?@=7BHYSD`W9TP6OS4NU8NX2FQ/CO0DO7JU6GR;PY=P[3ER8IY;MX@QX;NU7JS7HW6HV;M[1BQ/AP$4G$6F+=I7JO:OS3HMQV.AH>UZ0IN$/==&89 16)9;,:9,::1=G)8>-3MG6SD6UF8XI:YJ:ZK3VH,JE=9(C<.HD.DD1BI+4+9B'8@#5?$1/;!1:5HL.BC!382.2MD)B=!;5-H?(@90IG=SI=Z@MkLXrXE_CcYPtJJlICfAbWeN^uPjItPxPnHc{MkXjTlVuU[~ZYq:dIvZc`ekcbknda~gwdx`{gp`jVq]kWqYt\o}Zk{Wa}W[|W\zYQmOMgKhZmYgVeTtXyXoQ`A~^fExPQWYc;r4g'C.z!7,*w"6t.wC;Q+"2,9P;6L5RkBHd1?[*/G'<$<$5!-,' /E*XzPIlABc98X.Fj 30M2*E)6R19S0/E'L`G,A+\kIK[.>U#AZ&;ZKn*Kn1;Z&Nl9Oo4Vt4kF_:Ig37R'4R3 ' +<93V!?860S#$C*D'<6%C0J'):d{;e,Zu+">$>/4J;-H- +04O,H!=/MKft+z3a$NkJf0PVx#\u*K[3DZ/1G;R.>U/2K$1J'1B%1?%4H(2E$7G.&7 '84D%Pb50B%84J&4J'8N'9N$JZ0;K&*;>O.5K%HY.HY-DS(?O#HY/6HEX*Ob4=O$1CF\1@S,2 *=AQ$4DAS%I]#9JKX-9J>P Ob-O`) 6!9&@3O%#=$=;V'.K,I?_, +##(Mg=3NAa!Rt.?9P&2J$D^:)=% -9N!Id)8;U$Tu(y1@[2E(5=V^z(b~-Sl$G^Sk%;M$\o44H k0]xer'}yA@_(-DF6.AAkHZx6i@yIzCKNRQzT^|J|KU[[x=qQzX}^jWla~m{iyu±*@3Sk_>YGXQ;SX:OZ?RZ:MU6JR7LRP\?R[@SZAV\S0CR=QY1CL2DM:MS:MS>RR;OP:MP3DL=QV7SU+DH*RR6JM.?D+8C!/>0<-95GM/CD?RQZpibvp]mlp}qzx~n~awj[mcZjcM_Z;LS4CN.;E'6C.:E*3(3*9!1$7 7"8 117?VQ4MC3PC:ZK6WG;[L9[N4UF8VL6QM.CF1FI2EK 3;08$7?-:/!/,63FJ-AA$8:;PH>YI=SG(>52I?0F?)?<,C68RE%>.RsC\}FnXnSmLf@kFfErPqL_oTv\sTxY~]e]du_xaz[}\agdYekdctZ~cau\s_r]lZq]hn|Wl}Xj~Uc}N^yS[vOe~ViW^vI`zGiSjUe]oPeK]}BjKqIF~(*B**D)1K.'@&0H/7K5)<"7M(E_,Rl2Lh3Gd0Ol8Qn<3O$;U3(C!1M$He1Ur7Bb)6R"6Q((B!8R,7P1(@&'=&4K1,B)1I,8P-A[4.I&C^3Hc3Ea.UnAWq7Tt!Qs Rua)KgCcPo*Qq*Kj!Y{48V/Ll'EDd4|5PkSj!CMt"Kj/s3m0'BJf$r.rh Y| b*_|1OjPi!?T4MC`A\!@]Y*5K/ 5-H;Zh-s4;W i*Zx@^Bef.m+Qo6U3Pb{2AS4ItS*?/A%(; (?#-C%)8,=-<8H("0(8=L0):?P+->/@$1F%3H#@S.3BCT0AQ"O^39IBS';L!BR$R`."91J''<,321L(#?&C BMo49R),D(@@Y'4QJl$9^;W-)A"&!(>)3G*D`!a8:;?DCDADEGEFLLMTOQTQRUXVUZ]\[VW[[^`][Z[WZZ^\V^^__c\c`Z^``[_[][`^fadhijgg_iggasb|Gq?xH{NS{OwL~SoFQS|@VKg9jSr1[x;eCRk-cz6mALM|:sEc@nGuLk@VUxWfJpXu[fR{|lt兕|sn`|~@YY9OTM_fK^`>RW@UXBZXH^\F[\=QX6IW;NX7IQ;OR?TV:SS9QV8MS8LU0AO0BN1DM8LP3GP5HQ=P[?SY@TTASS:KO=OU7LN6OQ0IM7MM2EG/BE8JM8JL5IK.>F)8D/>!0?.>G4EJQdeYkkl~}~qu_qxTfmFX^.?I%4C$1!1-'/?(0B?JT6FF9KH=SLD[PAYK:SG=[M?[LC[RPee3FK7LQ4HN;PT:LS.@E2DI5EP)3$/"3:$9:/.7OB?ZE4K=.C7+A6+A;!81%<0&?30H:8N=@\X31M/*E(2J0!?#:T=5)@);V3-F'2++"9) 5'8&5 -G0. 9%7P62Qh?C_+Lj.Hf*5SD`2?Z16R*+F&%>"1N.Ha?:T)Up?Fb3C`47R,(@$ 8';'2(9%1C-$8##9=V2?X17S(:V&Kf2Lj1Yz4Ed"Gc)Ig&7T1N>\2M9UEa$8S?^Lg&ePm 6;:Bf0Zs%Vn'x@\s#l>Ld&BZ"Pf$jz8<|2,t$bXuo1JeLg=VJ`%Ha'C]$3OGb'Mi1>X"9Rb|8B[J`QgMgQj#Ja+7S%3O&-JEbsS$;)+6Oh&c2q@Z{0=]Kj!$A 0M.H/H0"=&7:;,H: ";Uf8'4@So'v.]s u1r-_{1_vYt+5S$= -Hk8Vsx@4R w*]|b~ z;?`t5p.EeIh!6%C Oi#Yn)I]%Pj%8O(@&84$75N Sp11K=Z$@ZLkw.Mk)H_'=S$/ ->DY!h6Tpaz){:v1z@9<884:6:<8<;?AAD@>C@DCCDCDCGLMPPPOPSPRTQOKZXSZYYZWWLPW[[_]Z`YX`[_b^__^ca^dcdmeigpncgicnifjiihis{lTg:f:vFa|5Ts.mBg7g3skz5?P]u:Tm6rPNj-zUk@tNpMXu@qVjNlǮ٩ѯy‎SigI_]\osF[[G^YJ`XNd^[rjXnnL`gBW[H\^I^]LcaA[[BY\AVZ@S]5FS8JS:MSAUZ7LRDY`8JV5HPH\^ATU:MN>RV=QX9MN6LO?SS7KK4GI.@D0BE7JN4FJ0AH'9C'9B$5B.@H2EG/DC$9:36!49*.&)!35-/-/#)01&<9 50 6/?TJ':6(;;>RQ@SU/AG?PWARYFW[DTW6FFM[]^ooTihRlgUjdOb\Pb]@UMOb]GUT=JK.=>DTT7FI/?DFXZ=PPCWS8KDPfZ_qiMbWBZK=YCGgKEdK-H<+B87!UoOTnHTnH"9'=UEG_BYzMoYsUtTmOjHrOkHiLiLlKdv_vbuZ}ah}InOyV|U\_{VyVd~`hkh{]}dza}^{_~by_zcu^p]nYs\v`iSfQlVeNlTqXlSlSbIeLjKiNcC~MHzE{:u2D?}1v.i$i&v+r&y)z+Tz}CLpUw#`$b"l&7p%e52Us:?_Qq0Ql/Wq8Qp6@_-;].,J#3P'=\+=[+#@#8U77V5,I,7""=!8P5,D-8"5O26O.@];2M0:T5,E'%D$4Q1!;#/F3&>+#=+0'5';%&B+$>.(@0/3I/Ic)Vu47S=Y&5Q!/I%(C/L+7R45N44R10J)D_4?],/I .K&$@$!: &=&%6'$4"&7%.C-4H40G-7N.9T+9U)?Z1Ha4[x@Sr.Li'?X2MGd!]y6Ff"]z7@\$Fc$qCLc1Km(~8~D;[ Uv(,K[v*CYG]Nf)V%Tl3Tj*t>DU`ky-o/Usa{)UrSn Lf Ib =WZ#Ha)AV+9L&';#(" AX5%9%2*:!!0&3+=!BW5-A%>P4(<,A"?T26F%0D!7H&5F!;K,+:5B(%73@(%6-2A(+(*&4% /%1)9+=9J&;K)7D$N&w[d{CXrBRYMo8_]|y,MmaF0Php-54B*463:F@CD64@=<8;>GLFB<8OyGRn,oAq>y<_D\"dz@dz=tQ`z<TYw1d?Zw@[yCh~eүv~at{_uXoxdxuZƃ}cvs]qh[ne[nc_rpOa_]pmWlj_upSfbWjg>QT5GP3EO;NU:MP=PRAUY:LV7IREY]@TU?RQ@TS=PP8KN;NPE\RDYU@TQ9LN0CH:MQ5IJ2FG4HK-AE%7A4GN7KK5KI5JK&:?&9B(9B"490CD0CC/>C/>@.>>3CC2EB,A7PfV/F>!570EC0BC'8;"/7!17)<>.BCEVZ3DFGZYMb_H`^MfcHa^Ga]Jc`Occk~}mhzxmzdvtUel7FI4DHI]S|~zNfKgfjh[zZChERxT+L4%?,napWhMrU]wK4P*C`6Y~JV}CTz?\EiLgJjIuPsL~VuJ`y^{ZvRe}Fd|D|VY_kwjjqjfkikw_u\rZmYv`ePeRmZ[K]NeUbR_yJoY`~LeShU\IcQdQfQkTnTOq:Qr2zGzAp-x* ?Z$D_)BTp)[z(Wu&)B "= Ae`-Qm*6ME9P:Q(6L&.D@WNETl7Ti33P(:(D, (<14=Qc(a|BY Vq&E`]x,NgYsh'=`~!]$y>E_Fa^}/E_"6Ql%-GDb7T#? !<5"= +*F*D)E 7 3-3O,F:O##5 %3#,3K*8Q3*&'&3*$/H,2I.01E)0&5&:"47<)E0H!/J$=*H5V9T%)D-. Qn.A`?]Hc*A[!c2^~2;U?V'Wu1Rn8F],&/FYu2>R*wQTlDmJjHrMe@@=7:;>987?AD@FHJKIOMMRMQPQVVSTVWWWWZYV\\^^^cece`agebbbbddhggdb_ia`UVdhakglfZfc_^\`eg_gffiikgghmtQ:_YZ^y7Xt4Xu2Wr1c~;Qk*Xr3\x3nA\~4Wu3c:|NwDMl6c6Xq/jz>pBRh*SYlCRk4mK|Xb>Mh3^{F\vAYt>avKqRdzEpQoPw_gMpSkVxmTnO\xrRضøŬħˡSgi8LR?RZ:LR=QQRPRR=QP9JI1DE':@%8?&:@"8:,A@.A@0@B8HC0@;->:4JCE]L=WA6OE)=;3IF5II.AD)8A%5=,?E&9=2:/6'56*:9&77&;:G^YNf\H]UXhhano\jh>PNBUSJZ_KWc;ESFS\GWSAVCJ[Gar][mUBS;E[BKlOImK:Z@=[?[xQVqE]yL\yHWuAgR[}JWDSz>_EhKlJrPtQfEc:k>PahcZjkpchpluapxohg~e{d|biUramYgUm[b}OcRkXp]m[bSNkA@_:MgEHe@@_4Ba7Kl?_}M]zFVv@dNTu7h>_.~Dr06?[Del1Z~Ss%=W8R Lm\$r4No<^]|.7V Jlc+U{W{"Mp7Y7UC^+*G*H.I 0K!Db*Fe)B_'Eb14P%;X40O20M4"9&%1%"9)$/$.E22L/9T9 :&3#":&$=(/-##0#-% '<.4%0'$ BY2Zu3Qo(Hd'3O4QMj97S%7T,9W5(E*7$.2J/?X6Da1>Z*D]1 5#1G36&.0.A.$8$,-@-/+/C+AF\7Ne:b/L!:$@8,M820&>-CNb"g{0gp,gq1ct(b~&Mf MhGb=VXt*Rpf~"^v_}^{$6Q =V/J,I)A Qk*Ga7P0H.E41. Tq9Ga+3J-$85MGa+&A,E,A%3 *!7N/&>#$8(9#+(,. 3 9$.A,/*$&7=M)6F!$2/,1"1 '3!$(2'7(?$+<"%2'*+ /3B$0? 2*=#3B&$21ADS),5.r$*60,:?8=??7:9:9A:<>?@EFOIMQLOMNMMR]RYUTUPWZZYW\_a^^]`U\ha`ac`c_b``abc``b]aeg_fjfdinjjhllinlj_hfeXich^ajgJPyAt;g3^z7Ea.Fb-Ie+F`*Zs6kFLh)iEc:[z2Ki+Kh.^z8sD~EIEdE^"=O Tf6rMm~;JXr-_vHOf7Hd.Jg2D`+b}AhpMayHmKrOtUwUiGaE`|IWpD\vIiQa|DpOxSxJmEߠlWkoSik@VVCWX7KM3EH>RP?TPRC7M=6N@KdO?\DB]K=UGBZL5JB/CA)=@$6<&6<*=@'::+::/<:Td[[lbw|z}hvzGbHKeHQiKG]@LeDRjJMk?bOkSlQxXwT^{WuSyZ~Yg^esWyR}NrGYk~gygwg{hhuetTkMh|QbyMjW[uJcOmX]xMVrC[xIPg>jWs_pXjS[vLB^=>\@EcG?[@JdCB_3Kj;Db7Ki2uQiFfHRq4a~?qFa'`%m/Ue/St'Tv+?_@]C`rAX|%m0j)W|#[-Wy(b.p7j-o1i;8]Aa*Sq;#?5RGf*>]$4O&7Q$7P'+D %>$A$/L2$A,6"#<,(B.,!&,&2K3&A*.H8"?, <'(C.*E2%! )"2H:'?2(@0&C,5%$ +CZ65M>X!7UVu8A\+8S$T?U;W ; +b{>/F d3p/=XMh^zne*a}'_|+`5Vw(Po[v$Ke7SUo2Pi#G_!=X=Tc}+f+h4Kg:V \w)(A0G0HC\c{2BU>VEbYx.Xs.Sl$Pg)oA^~2j=_|'l1k17Z d36T&B74 <93N!1 3$AKm*c1Sj-$4 +#6!'7 eu.cx GV +=C[kE[Je"^Lm%Jh#Tpi~$Ynl+Qg0H'B(G4RUn1-G <&= 6 4/%" +57Q"!7$9+CIg&Uv.*+E8L(()*9O02@Z8"5#2 #7!'+(;$ 40 /"-&0@" .3B#&$&*'$(4CS3AO3 # !/!1/.A ->$8J-8K'@M!ES$-: #/,7Q+>]*Eh.@a"8U3Q@[)$:#=Sz$Vw,Ts#]6 !&:ROn-J #;0I(1F&%8o\dy\wMp_i;S?;PE8KG3GC?VN>XK8OC=SG@UIF[P9NI5HG.AA3HH)==+<@-?A3FE0GA9NB?PM;JIQ`\gwqgvnzu`qq=NJ=HGV`aZceBMOFUV"02W`hozFTVWbdN]TsrO[LQ`OL_QLcPPhOQiPLbN8K93D3P_J^|PZ|PdTjVu\xWuXnVfNW@9T-zYlH_~?\}DbtLXv8Ie,@_j5Z~(DgOv#AeNo#=\Rt*Po(IgB`Z}*Oss7Rw"9YA`@`5XWw,Gg>`.K2R;Z'4T#Ko.Vz2Uw,1M01'&?$4!>&5#1$?.02% -% ,+,+F0+F0$;'%?, !7LA1I916("<*&A-1#2,*+.9U*$?-I*H1K!>X.1M"-H%)D%/E]=,E("6;S47Q*2JLj%q[Ji>]Ql$>X2K(C(Xs01I +?Z?XTpTo$3L Qk+Ni#F_+B A`@Zf74Qd+1Jb>Om$Fij6^{*b.e4Jdk4[z$+K *H HfE`(,F+' 9+ 3 +%?Jh's/D] +u%6;9[e";Q'7T[z}*2u(Kc8Q9U*D =TZl Wjt/]w$.A '@#>%A;7Q =AZ)).B' !!% !/ "4,8%A ?_!Ws=*="3/=%!3')2$:#0F+->(-" ("1 /-=6C*#1#2 #0 #2 "-".+9*> +<7E*"/0-?'0 .+<6F'!&:DV(mz=wCYo0(&2 -8*K9Y=\B] #yhLq4XIi:_! .CY%]x$1N$>2L,+B.CnCwQF_-ToEDX;\qLbyJTf7iLZt>Yt?mMe]v5f?\s:`x?}G>8?t?H@5,/,-<2D>G:/7585665;7:==?CFK6IMUPY^YVXVSTS[XTXUU\XU^VWYYWZT\]YV\Zb_Y^Z_^_[^]b_aab^`_\_acgedabcadhbjd_hU^baghg`f^dHMs=g};[u2Ro)jC\|:7TdIUs5Xx7Sr1^~_y?B^+Jf5Xq@mJsSRh=(<:*B65I>?SD0D44L6IaEVnNIaCUnO?WD7OD9NH1BCLLXgfVfe:HJN[^GUVX'7P,@[<6!+D-0L2;&5&#=-*+ +,-C1+!*! 3%3K52K2*F,&C,2 6($9-2!$@-&?3/F= 8-#90  %+6R,'EDa/*C%=*D$3N+0K,55#6/H), _uH8PrCZi*ZwuD )%9+34d{L 6d@h/bI`Mm%Ok*Mj%^~+Ff#7OAX >SVl-G^ 3O5Mc{-XtFz!r!~-v&a~B_Mg :X@^Nl'3Q6P-E5!<A] 3L Mc-O-9 gt/hsQbDV8M1E+F$>,1I!0GIb)- +%8.. 4 5*9-?!) *?9S(/ 'B3*F%?&;(8 2--A,0&$3!0 +*-.$:#42F-.>$,'7!40$-%"%1@-=L6*: =N18H,)7 .#8)>%))&#2/>%4E',:es7Phk: +/' +0.2(Yn8^m>yp?S|Q7]Lk-#@) &:!)CNm9R&<4O-.G#]v>2BA0@>1DAJ\X@OH?LD\g]\ka,:4DSN8IBH[QHUJ=J:R`NeubbsXm|btlcu\_{\LjL8Q=:P@=V>`~WaP<\3KhDXwEPq@QqCHi?MoA[G_PF_G8N?/E0[yRUq>]xE]vEQh?RkEYtK^xN_{Ma{HgO`zMZpMd{TdzQbwRYkM_tTg~YzhlZZwI7O8$;5#87&8T.`@kGrNmC`6nB{SsKX_{:^6Y)Qu&58S(6S-G4#=!-H()C&#;& 6%3BZ7Eb0`|@h;Ru&=]x7<\p2 &)0G/Mf9Yr&_'h6Zpf0Wp(`}32LDaKj'5O8L3FIc5M by6?V_v/Xp'Uj)u;^w_y#p!{!4B_ h&Ni=WQl';VB^8P*F9 F^-DY#!;=YIc$4?\Jg!Kf AZ @\"=Y >\Ca#*F AX$:8Q4L0GLf$:Vb~/D]Yr)>Vb{1Uq"s6n.k0Vt\x!r=_~0+D-'B*D+M.;py15?MYZn'CY\n0DY+E4 / 6,%<@W",'0 0G!+#,+ 6 &3#;)@- +B.A  "#4%&:",>')$-/3 "7#*.-!2):#2 #!   $!+&5#+&5F'Zk?>M!(9N]6"*( +GZ)`o:'6P`(Lc!L\(3!6$,?"!:azOmLWv@wHrGKaEf?]~3oG *#59U&F^+AT 1H(>AZ(5O5M3J*Nf=D^0Qj:vaD^0J^6mNd}GMi6Qk7xVxMf{BAAJKJNQ=gt%l|.BbbX^OZdd_VWQOYHRXVRYTSUP[UXZW]^[\]Xcf^acbefcT^fichfif^]`cldbabbac^[Y[Xbj\]XWD?=>I{-;u9]r1v>R@Ic+oF@\&/H"9O+8M'tIa9nHnH

oNqPKg8?W5QiCGa9a~McIea|F`a3G92H5@W>5O4.H+YL5M?4I?,><*8;*89-<=-=?&67-><*880??0??0@@);9*:9#56"55&9:(>=5LH0B=-=;9KJ4GE$68)<D^9[xJ0H)-C*RmCAX7bv`:O6UoKh[2O/=SB(9->S<;P33F+&?"C^;KiIUsQUrJXsM\yNWtGHi7WtEq_lZo\d}R]}LjUNm;Z{IWyFY|IMjM%=6*'/E2b}QnMeAkI_=]?fETt7bF[w>rMFkk>Pw$Rv+Tt2@`!,HQn8St3Z5Nt+Mo.7T9Zc&@c&-K:T'>W':V$9-0 <#B+:,1' "2%)>/$*B0/ 6&1%1%2J5(@)/%=+$:,0% 5(+ 4&#:!6O.C]<-F/0# "8N4%;!5#:4N(9T'2L#+F#)D%">"4*0:U!A["8JN`35N8P1IE] Yv)Lkg3Jh#Yx2b5C_QjMg!B_d.WrKdEZ;Pr=/(!6 Oi'*F B];T;O*@ 6M+> ++:ES2E 9NUg%>T@Thz(BQCP(9^r0J^5K>W5J 0F9R"$;- -E 8, +;S /F$4 J^& 1 :O'- +&=* ./+*8"  ++%.-?)"!020*"8$#)<),<%*%>B0WX:2; DP)-%!1(&  #13CXe2IX$x9q@e>^|?\y@If-]zG\nCmN]v?Wn?Wl>`uCnHSh/Yl:V[w0gJgFmE^|QyEI6+&)*.42058=?B=@DBEALFLFGPXj!/ +4xJUZUTYTa`h^MLOCKXOWFRXTVR]Z^WYXb`^`a\_Z_e__WU^[_Z`TVW\Z^chebXcd[PZg^eigocKUmPBCTB>R6Mf@VH;SE4H>3CA/>>6EC.?=)<;&981A?.<<5BA1@?*:93CB-==(88)8:/=>1BC2EB:JE3FA2EB:MH+;=);>);<6FE>NJ2AA6IC9OE/D<-B5SfMLaEOgDWtIWrMRmNAZ@:J7BW>D\BShRqjfz_g}YgRsaxo~y}ZmVC\:A];.G/#7)2C8?R>:S::S7HdD<]80M*IcCId?2Q-3R/=_3U|HWwEA`4Db:?`8Da?A^:EbA>a5T{J=d<4V<-I3+?80)WqOUsA\|>Vs9[v?Li/Ig.Om6Ji6Mj9Ro9iDkALs <`+B (G 4X'Be-Ce-WVl#SjPdl*w,c+2M E^ $5 +0 9P $; -6'9 0B+3I;RF[%H^#>WU"'=/AZRs,:TI^>U0J HeOj"d}3Qk(E^$Sjo/]n+]o0Og"Uk"_r5$5 +J`1C 4(<5FHT!1B,? (< #3-C BYYh"'<o>Ws#?S%= :T/J6N")%+ $;?\(&@'Al@Ic#BX:P\t@Oi645Q#Ki0Hb39P$. '6, +$  2"'3$!/. 1!  3L4( 1*'1$ ;>3AIX"JS2E1&-Ye=*  + "0JV.GT(lwHWe#m1J_ Tg)Wl,Ym-JZ(Ttb~R`WYv8oRnNhAXq5yPVOaM_x8`_sH),9&5 Pf:|').-y9pCbDZy>hISn:XlAbxMd{MQf:Ob6[l?vSPf0GY+vK\u9Nf9Nf7rUmF{MMv?<@352-.4><:;> .#.DJ,|P}N[MQT_XNM\BbVz6Q -CCY14I$9I.5E'Mc:Fc3D`*[w;kJhHUr=-G#=V/?Z2Id>SnBcL`zHrVwQh@nFd=wDOgqojsvxvmmZUuVsTz\yV|W{TXsKtAKXJg7a~VKgB=W7E^6F?6I@1F=/D@';:4HD2B?9IE6DB,=<.<=,<<*::-=>+<<%98';9.B=6IB3GA8KD2AB(9<2ED3GD7GE0=;=MF:NC2G?/>9;P>WpTMgHUnM1D.,?,4F4EUDJ[H2B4 .%(9+:J:GZ?`uRn`j~_iaYlXfyfjhkf`x`McQYj`@NHARHN`PLdGOjGca]o]Yn\A\W8VwKDh;<_8+J3.I5&BV=UsAQr@Xx?Fb3;U+E_2Eb1?`.Hh:SqARp9_:U}+Ry*Cf!>Y) 3*A)?(</#?Ae'1O-I!8R*$@ Cb)@Z031J.6 : >-I"1N6Q$;V*#=Eb4;W*'E3Q*,L&.K,0L/";),1%B02 /%  + $%'!(!%=-#:(6"$#,&?'54N-$>%<%$ 50/#8!*A&*B&/H)/N,-J'&?%. 2 # $=".F&Gc17V [ ?R,CZ-5HPi/Vp._x7Uo.jDMf0Ja3RkvY4OYs@sKzU>Y#YtD\xFjQ\r<`w9^s6d{@Xp5tGV|JikmmssxluHfO]}Q[~MiVnXtYuTdD_>[{8PHOiCE_.Ic>SjHZoJA[1d|D~N{ϸͤlc^vTh[hUb|NbzMo`_r^bsf9JFEWP9IBDWLI`K@WE=RE8MC0D<-@<)=<,@?'650?A5E@3B=1??,:90??,<;7HH':9+A?%<9&<91D@?SJ8LC2CC1AB2HD0D?6DD*979MB8MB2E?)85-A;(?0&;1.@56E;3B58I;?P=HYC3D<)80/>4;K9=Q8`v[dxbR`W]o_JcDWvQMnEXxOPpNUlV/D+7Oz>6~?nGRl5Le-Uo7#=7O;Wr@b~Z0.F$:T,Dc5;Z0Ca;0K#B^,Y~7Sz+CgNm/"<$5/D!7L%=T'3G!AZ-5T#*E56A].>Y)! +!7"3#<$;)A -F)D-H:W+1S!&C14*@)$0#"";+-G52% !  ,'##() 6' '#6%:R1*C!6%?(&@'0H.6O2%=!+('<-G%9/L**C)+1"'>*/G-'D$7S(Ut:Ed*,H@[%Ih!s>Yw1EcwA|>`3>^pK5R9T!3O;TOh.'A7)GC`&*E#?)E5KDW%.?"3"87;Q@YVn!h|1ev*l+{1`wVpNjE_8P0?.&+ / 9RX1G1G-C =R9OY n4p3f&:T(A )Ti,5MAU%Zo5Uj'EXL^&,> DZ4F$"#9 Jc(Oc%Ng,%9! $4 +" )<(?% +'8GY6)  " Qa8^j.WgB:K#fx+fr"Ugm1M\o0>+ +/AWm:nHbv9K+XmGgPYo?fzDUQQ:6946==AB@@=@R6PKDEIEUl"WR?TLyJj=oGxPjCrOnKjGqMsIxITT\Zm6h3z;B:7KGRPRTUWZV`TRTOWVVOOUVSR\`[[[ZYVXUV]ZR`\caVYb\f`dZYaAP_`7w.t1p.r8Xr"_{#=w-:;e|.?T]}+Nh77S\z=\x?+<7E%CX/5O"1L4QDb.2F6K!?T&):h~DkAWv2`};Qn0Qn2Sl4Ke9.G%-B%9N/>R1Ga6Gb2F_0Tl=gLE^-Ld4J`/eFLi0Fb+`~2k7nHBY,WoAQl:nPcTn6Rj3Lc.Zq3Xp.f<_foal_mf`WxKZwX\yYZzS@^;LfCeR\yBsNrIm>t@JZe@Eb-?Y,Oe='88.<=*:;4CD?SK4HD2CF+=;)>:4G@5CC):93G?3G<+>4+<2,@9*?3-@/ALfl&z"|+|;K?b{0!5]v4CTv"xDLi8T)9T23N%@]53P-5O11K+Ga7Np3Lq)@eDd(*F,? 1D"4L#4M 2E (A0N4SWv>8W?]!!9 &%3(// 6 !="&A!.I"2N'/J#2N$6S(2M)$' -!#!1( "9,$" &&" &(+!"8*"9+4(-#0/H'/D&';%1%<&!:#!;! ; 4#72J+&@"2(A &=$2 *%;#5O2;\1?`'Rs19W!>Y+D`,;X!Lj.Nm*Ts)Jd{V )@)B8Q @\&>Z+U=R .C =T=R.> / /5&9!5 ,Ea#Qp(LnQtTt*4R+I6Q+BRj33 --I6RC_%.H$?2NGc0?]+&B05PVr1"8 !#&45J!*:6H#/ -:&  ! +"- 4C =J;yJ"-4Gv2SeSf`s'bq~3D>Vk-j~"Zk%ar9.?)609%&#*.9""0bw?BV3E);/?=L">OSi)d}=qLH[-Rd3L`.Rg:vXhD\w>bpNuL{JjyD~NxF~Az1t)r+q%1.|/z5GjH[7TlED\8d^|5`z5~L\j7e:Jj n6Aa!Cn@t>H{E}TdAe|DQd2tPμȽثlponRfR?WF3FC6FB4DA-A>0?>+9:->>BSJ8LD=PJ5HG+A:/D91D6:J?:PF0CA*?>(=<-BC3GC0A;4D=:NA/?5/>:1CB2F@)>4*?8,A77K;MdOD]E?S@BR;FX;B\?0G27N:4L69S86P5?[4Li:5N1.C6,B=+A@#74ShZE\H>U@BZBJbCPd@Qg;Nb>I\>O_GJ_DXqUTnPWqLOhCNgAiBx.o(Bf t(2l']~u?^x8AYx*u)dl>9 Lh5.H!2J,%C#+F)&>#'Aa!Ij.>\&,G0C&/'>7O(5E#1IRs;=`!-N:Z!Ff)Kj0Gd3'> ). 7"9!*J)2N,9!:54%>%)$+'/%2&&.(%# !  !%/ 3$/%% 4(5$ ;*2##6( 6"'?$ 8 0":#20(A'%=#1(A$"<4&<"5P0%=$13K6":#3M12N,Ee)Sq0;X"*D0L&A9U;VF`Xq)`{&r<6SJf0'?,C@X#BZ(L1G>U4 &;*?(7.$;0 =T&%@ 8V0NNn'Gi%86T!3 +6)<1@\- 2CY/Mj)4O+E4#:)B.1 4* 1)H2J*?%9 $/D!' +&7GY. - xT=AeBp08CDNFO)& fv5Zl Ug#VgHWet(l,l,?N`s)Yl K]`q2'1KVGP#% (8o1i.=<0:RJd%b1Yu(Zw([t-Tn%^~*f.b-Vq"y={9N7_zo0}8US/L-D"@T1fNIg/On6Mk3Yu'2C,I]99P'\uGrZE[,Ld5C^-Kh7Ge5Ji7TrAXt?Da"d:i?cEMd8\sH]vG_zF^x@fHRj2Wk8Yn4rHmL`{MsdcRmStSboct[PnA]WEaGF`RHeQB^L@[DC^7Nj:]}Ad>vDu={C`2pBqDSk/lzCjƌÚH_Q2EA6HEDSQ3EE2@A0>@)9:DWQ=QJAUJ?SG>OF9JD;ND6KA1F6.?42G9,C6*A2/E:5M>BYF5L7;T?6L:?N7[nMIe@?W:6O27Q44S3=V6Eb;Jf<-F1*>;&89#66%99EVIby^TnOQlPPiH_vJa|QQeD;I40=52B<+>7)=3;M;EW>eySrBY|p/;ZVv`e!`lx!|3i,j1X| 9"?hG/ 8#(E*#@#2":?Z.Gf/=Z(+H7R!)=):!);!.B$-@!0H Da0Lm0Ml50O9XHe,(H3",5#@( > "?#>)B$&@$4&+$&!&$'$.! 8*  + &(7 9"+ # *8"0(B-*A+,B,"8#%=&.+":%-$>("<&@#,!<"1+D',D'4N0/I16!1/H28!*Zy;B`#2N3N#+F1LBak=Xt-Up(Ok!Lg&2MEa)(=AR'-?1I9Q+FCa%.J?T&2DNe*?X8Q0E 5 + <Kd*Wo*Ibw4`xl/cH 78S?T5I+>$8 2J8Q8I33J(Sm(@X, $@3N 0C0F*;.A "($:Oi3p}"Xfo7ey09D \m1hs+`m,GV|G[h"hs,?U0K +\s,B]~>\s6>P%%25(Ub4$+`m\]y:WrDh4g1k2m*9x7i=Wv8E`.Yn>mHTn3QlA5M&EX9J]9Tj>=S,:Q*kQ6F;O%]t@z[Jd4.B ?N1YkC\qV#_{DPoX2K&4J'5N&9T+%A5N(/CZr=Zx>4I ?M#@V"^u8l=Sn+@^!Lf19O(G^5]vJ3I+2G)=R-MaOE;MBI^MCYF[n[AUJ7L=.>2.A:3F.SsJ)?(6O>BZH.G4*>))<&,A73H<7N=8QA8P<9N;1.>3)510?AIYY;JN.AA.=92B6/B$b5[}"x;=[6T ~:^Kq bu%m%g&m*`}5/LXw20N"=9")G+!?!'A)#8"%96Q''B3K#2L1J%9+2)="9P-'C">*C1K&5O&*E 3/+#9%< $B'&D'&D%*G&"=*E&)@'   ''3$0!+!)' /$-!,9U>:#)##>'*H-3"0E1(>$8'@$)B+ -"1)@,!=%(B, 8")>.6!&<%0H,1K-&@#2#<'+$7%#6&@_"7S# ;Om1Sv&PrTu"FeZ{/B`7R 4Q;W7N1G(<2ME_&:V:X;W6G3DJc!D[#3 Ld2(? =SYq29OQgBSl!Qm!Fb o'Pp"A\:R6;R!0C4K7K@S"Nj6:XRo#Us%1I/6* !53J!4%6=T]z'd3*@BU&2Gj2e~,Xq*Ha'1 '=0D9H0@.E 9 '<3JK`04M(? 9O\ .I.J0 / 4J#=V'&* "9%=#4) 1%<( 1C$% +- (C$<    +" 8L.'= $//?& + (+=!&) DV4!1 q-BWjG^\p+We%AP_o!o~"fp#Q_!(:hw/Ve$IYA9EQ\Sc<}("GN6>\l1t12?:NHZUi(H\#2 7F!) +0=%Og%F\@R H[0s`\zHGb3ZvFEa08Q$D\0iO[x^xBA[3Kc;O)^p@IOak~C[l?WmB4H#J[8FT0O`7fwHTf:k}QarBoPzM^_GHH4@74.5;<}8PgF`Nc+[p1Pj*:U":Q#nPZu=C]+@[.9U,5P(7P(3GE]'zWXt5]uK[z.Yx3Zy73QSq(Ih Ji^,i4d*o'KXaW^YcOUUWPMKQTY]Y`PNS^]kfVVhli]>gMQr"Sv%d.m:g8Wt,Qn%Vr&u@Iv?~D]|(f.Ni^{&RFh=] ?XQi']v2Pj"HbAZPi%h=8RH_Wn"l6[t+n6y7w.g."9 (@3I+9R+2M''A/C!?Z,Mk49P$:H"I\/\r;Sn)b<0MF^1BX1%9=N,*="/B"HY7CY1CY0&<,C73J99M9Z.D`0MjB>X3Gg=Ed<:R:"52&79*68'23.;;/A7ScU:L>E\GJcIFZH(78.<>-<=0@?`Tv'Pti,i,]$_3;[(-7,K)*E(. 1 %=+F 'A/LHc3.G:Q2/3F*+@!,E%.B&) 5"%/'>&7!6 'B)#A&!>!&C'-J+*E&5$<$1"+ 1" :'!:'6$%>12%$#/$+ +3M61H4%B+8X>/K3)H. @&#=/#5K21J*'@$3)B,)C0)F1$=*,&&"+0&=% :(A%:"6"2H3):' #Pm=6U?]&6S$3N"9Wd/Ln^y)TpDc6T.J6TTq8"<1%9%A"<&?=UC[%+=H]g/=RH]&1JOj*8QF`'@"5 )?,C.E1I/E'> +Vn*H^aw#w@;R*C?Y?Y6JZk;Th'3E :U)>X,F^-Yr:\u9^y8qJqGe>vPgD?\!Jd,Jb%QOHBPVFaz/Me xIc1mBYx2`|6Gd%9QF]*>Y"@Z%>V`z56K gR Mf,Jg'Yu72 1DD^+SkB6N1.D#9M)JZ8M`?]rKq]*:F]%Ul4Qh3^vE[xD{_Ge0Ng5bxE9M!BW0Pg8lOPk:BZ<7M<>P@EWG?QCFYKOeRH^KHaIWtOgWaOcKlI|NcD#9:R5I_8Ke4If1Le;Ea,gHJf+\u5fFG+ô\pYWmW?UGAYH=QBRfW9HCAQIBV<:M2NhFNk:To?Rn3BC*6:HZMG_ANfPI[Yg|gkb5BhWz*\~-|Gx9q-F_cXyXx&Uw EgMua&a!o3<[!$:& !=X5";  !43L,">&@$>&>&:%= *=%&5(6,="3$(*1*#<#7 +-2'?$!; ";'>"'?#*C&+D,";&6!'C)!,E8ORn#HfH_Xq%E`Gb)(?,A#:,D"9) 6S(?Y)If21I!4$9 k44c{!E[=T>XB^+8N#y;h31D*Sn0MiF^8PUKa*-D%;>^B\=T-D@V%!7 /F"2J!4L2L9R/E": ?\(Mi25K0D4J.D@V=R7I%7=T0C[,)1 (=!6(>-EBY-(@2ICV%E\`v%J^8NZn,C]=V^r;0<ft6q%seu)k3]o6Ids,>N O`,IZ.Sf*;T4P#Lf=Mi>9Q'Id<;U0=[3?[02IXnAyY/JLh.\y=vPRvNf~BgHvX^z<{PwHR\s2u?[v0azC:P(6G-*;%9J)rB{I`v8`v3fv/rDZl8g{>[k(`q;DS"sPBW1H]7Qb<]nAfwJpRivCpCuFOn7u;UHFIGJ@HDJHYx)\{>Og+_y6?T!DX GZ%@O#0D?X Mj0Ik-Vv::U&ZqChL]|F]|Cfb@Qn0D\#b|AVo1Me24MHd.1J:N9IFW(Ja&d~;Qj"]u$q+Dv,6>1FQQYQR6RUTQOROUf^cTQRMWTOOOc}7Fc#Ff(7[Ki/,HXt?7M1F ,@7L 8J/C?\Rl)Zt&Ur!Mh$j9Xv&r??_Yy9Oo.;X4MH`%4KLe$Mh Ha%Ma)Md&Oh%\w/c|6e{0G`Z{.OlnE >-<'3D(1H(*E ;Q4'7#4>Q(G]'Wp0Ic"Yt6e@8J;M#^z?@])-B%e~VfT3I$6F,%6SiC1B&)6!DV8;N/L^;Xk<`z;Wq6Wn>H`+I_.9M!J_6ZrAkH^.E;O(avM?T/6J*Mg4}\3E+3A=BOK;IJ=NM5HBH_OFZJSkTUtObTX{NLl?Wu@lDyK@Y/4O*Kd:Id1^|ED\1Mb>?Q,\pDewBN\*deOپɺQgMD^?KeIe|jJ]PEVHO`TK]G~XM?~296B|4b6~&=8=1@o*f5bbzC1C*G\>ZpIYrJYsJ]xPI`;=U-=S8=Q=#22&72&<2 40 45#76(:9(:9*;9+=9T27Q0,C'3$  !(1&8$ +-*"%5 $=(%=*5&:&$6"& &&-". ')$7,/$ '7-0""8#+(A3M"C^28T'8lEC[#Yq9Og+:OYv$Kd G[@Y,F @T$-=&5,*;.A :O/#=>V(5S1L3M!So02P5Ud Nkb95U/J:WEb +Qb9#1D#,J8O7OC];W0L;X.F9P(@2IBbLi,4J>X(=AW0H5M)B!<&B9T%5P 4J+!54Q!0L/C,C 6 !4 3G5I7F-<"$ (/ $;4+B.D(?Lb2E\#=QKbF^G`Tm$h;3= ?N!+FYm'g|/Tc,'1+; @Xk~3s~$jv2Zl'9s#~1{/5CJ_at'3Fsw!+ l)EBKLHMHTSr%If+D]$Of&Vj0nE^u7H^%tPb=Hd+A`*B^.+CNf[#On25P8QB\Tp'l<(? :N @W">UOf(C\Vn03JIe%k:1H/*9 ,?%+B&=R7'.*9YH_-G.:O%#5!3# $ $ !7+@& -H2J)1 !> + ?5R 90'EKf)Rtt&j.I[' !7Wl9Jc!]y#Ol!8V.L?X&+>+ 3D_%@]8RLe'1B?V.H/G:Q+(@,D(<!61)% +38O(7Oj-Tl2(? Ph1+A%$6 +9 ! +-/ "8Pi4+=,. +!<2H`,=P=S%7P:T7Q6OG_)]v:Qi&8LBX')=,A-E1$.BWn+Jd /G(A(CQi51 2&:"83E$#7/?+<, !4(G7&  #/C@U8w?BN+ "0 +3Po bjz/%;.f{+u(~$3x-~QM_ -7BLEVH]H9GUzBIANk|/AN }(do $%/u/?Lp~0ex%j~'o&Qf5p l| KWx:GRr/Nd*;3H'3H%Tn@@Z0A[3VsF#:^yK\zJ]{IlR3JNh6[{BqL`Rs+pUHd0Ib6Pj7Ke(wJC`pBUYt-ZVq)Mg4*=&+=!Me4Yq4]s,f|>Ri3dz@Kb*\s;]m-Aw/4R9RTeIUkCTc8^oCcqDjzDwKax7{ItYzJg|9Sm&zO\x7Qm*So/Ge*+G$;1E$C]42L#Z#Rk74N"@Y+6P;Q!2D AU0Qj7B^,0LA_-;T$Rn,b2qAIf\{&`}2`|%n35R#;,C*EKh%*A9Q#5MCU$J[*@W 5GQe,uN*@,A&"; %7#!6"7K/(;!':*:AT.1BF]*-DCY&ax;Ws0:PJd5^wX?TE?SH1C8=N:I\:b;aU,7H- /A(, &@'DPl5Lf#Wq3%< 0LXy"Ww [z[u 2@ AXD_W /F/F,?CU2(<BV*;M#2.9M/G/%8,E 20EBV$'94FZp41F M_/=@S)[u+Me(.F7NMd/Jd"B[#G_&Pe"2I \;R*%8d|KA]"2HHZ)H]%-@I\+Uk*t@v>s7j|,}*HiA:O,Pc:Xg:mLh|Bdw;\r0bx9LLD<>=@P#E\.A]-+B 2Mb>>Y0Nl>Eb4,J[{5O>S DT(4G7EBT!ay.s8J`Zx_{.Wo'e|+Ap':Q@OFGLINGFGACLLHLNLPDLJo:rCCbJh-A_,$CD_35Q!@Z)'=3K$G^/'AQm6B`'.IGb)3OA\e4p;Ol _{.x>,J/I1F/H7Q"1IE_"Pg.Ri(oCC\ ?V$Nd-GY&,=`w?@V(Ul1'>9N.&<D^89T,-Ei~E_yAFY2&72F!AV/La7CX)Yt=rQXtG)9)#" )&#.+6;%36(27 (/'18?KK6DA1,%!  &Sa@Te>GU5cnU΢^ڤصtmqJi5m9r7z9AWt7q.v'/1u,t)Ls'r3^~0BpQf4f}LmmawEmSe~Bk21MCY']rN.A4/F<%:8,;=*7:'49*;:0DCAQN}}Ino2x!g2u9\x,D^*Sj=Vm9[s6a}9e=qNJi.1NHh5)H&E De$m=vFbZaNJCBDFA|:w2EBCLJMPHRTLR[ZRQPMOONSLONKOPNGwCJ~HuDyG{JoAhX!9Q""  &1F+*  ) % +2H 9%?Sq'\z-5M%?5Gc$Ier&}<7E Uo,Hf9W4O0D->#?Q"CW$Ng):T*B %@Ga**F&@036L$3I  %6)D,C1!3) 7Q&5O:TOg/:R+4J9PJa-;L!'6"3 1 52H( M`6G`"{?ZjM`q0|+^v1F 4KTl'-E=V!'8fZv +FA,5# _v r 0w +q$7QfWp*s{,^j5!) -C=| Xfesgtbk1)?Ft}#IX=Q>Q8C?H NY q[h1Xk-+ytxJX` `q~(6i~!J^ ]mXfB_kR`q'[lN\O^>S;O1=N3/F'IaOdv;f|2BVL_!fy5:@F`&@T.Xi@csFuTUqI^x5lA_i/k+KICDCMGOS_j;Qq5Ja+Ba+5R"=W)5Q&1H"+:/> ):(>%5*<>U/7R*?\27S)VrAaD9V 4O!3K!AY/;S Tr3`<\|<%: =O1E"-AAX*?[#?['3MB^):U[v5Rl"Xx#Wr#Xx&w=,I,E =R'D]-6L La1CX'?O$#2 L`*7MR'L`7AU*G\,Mc6J].Kb*8P@R.,@CX08L6L?U$]xAB_)8U+"6AM6!"!*&. $!./,1!*3$.3LWFGT8ZkAYlDR`EJZA>O77A169,OXCIV>-7+.:3UcXcsYp]m}b|iȸəΌwrCyܦјbRs,=\rEFJt3v@j;e2K0~/j+p3@;k)o/6FFisؚŌ`G{5k bBW0H_G6H?*<;"65.<>*9=-A=+?>3B>d|[lLLu_ z-mKo Qxtg!F]-CX.Rm6dBqNnF}WkyVZxO\yLk;xBF}=CGA=@DADHED@ 9;R+/E& "7 /.I 2I*@5I2B  %6)@$5 / 1>' %=j$Bcy;s/;PMib~>*w-Z3F\7-B1I!F`67Q B^%zS}YKh0:U+@X0BX.Si:Xr=c}B_F_&Ur6Ee$Zu6Gi$+D7N#:P#;S^{6F_&BS#L`#}FEZDY g|8`v.M^$^m0Wj?f@Nd8Xj<`qC^wOYrLoErFr=IGw*;,;$2)7+:"2(;BX1>Y-1G##9BZ.?X'1I1H*@J_3[q@`x>g@E`'Qg3tLh>Nj&EFaC]]x)B\cy+by'd|!k*x4y)Bs);@|=?~>p{6E?|5<<~Bv9EILEt1g)B`*F8S&7O*A*?.C $;'?=U/3/A#'<&73H8Q#&=9R(1LAX)Ga$Un)n7j3l3E.0E!7OF_.#9 GX+*;DU3(;4G#8M%7IAV!|M7ROe0!1);7H(,%3 #5/G BX26N/@Y1:S*3H$1I#4F&:/C4E!BT'ax=]v:>R*-:3D$=S*2H!4I"BZ(mH"6 5B*'73F]rlafG  *- ""2.#$(36!, -/8?3?@5@@.::4>B,20CG:_gM9F0-;+(,<*1=-8D2AN3hzZ{xyxxrsjzmtթr1;7Sܽk쫈\tAs?[~3E`$c;`0_)tVu.Ol2mOjGWx-rEzLl?g;sDJEv?UOJOPLEHEDCHDDCE?H?E@BEDILG~A{;@DGJHJGIJMLEBGCABA}BHH~GwBuFvImAqDsFwIpAsCi;i;e7k?fYnDNf@.G)(?"6)C,F%'?")C)61L"d{A+? S Zl'9M=P(9!8$< .E-=3F,>( K\3.|Sh?Yy11P(>2GێԒ|Mҭ[3AyfݳHpx@OsAXx3B_!^|6Tp-[v-g8Qn%Ng#g5j,Ev5f1`~4Mm#Mjk4x7@:a|Fin?c]|Rt>w:g.i2FNMRNn=$?(.=5'83L`CLb>6E<:Q=^yN>Cz;<~9E~<}>|;=>y5x5|8>~7>}8o1|;o4l1r9f3[-[-d2j9oAk>sBp:u>o:uEuAtDvFpFiAqJnDrGi>lFiDhBhCdA_z">55M{0F^AZ!C\!*A53! ( [j9AF#,+-*:Yk%BIWcOc,jyu$-13{-cn NX el$Zd ]gGz'52~ p{6 $6!49N)=T*H^2]vGRm3qFwHc;rN^:0J0E5K,F.D2B/B4J0BUh2ez6Qgj4ez0Kk)~@HaYoBH[/Yl>Ue8rInIsRZsGtEvEKMHGIKINXm+PVp0*; .%6+.PWm'az-Mg Qf)Wn#_x$b|$Rk`v%x1N&Sk4Vr4Ol)s@UjDJb)%7&#/+!),',1,,269:8;4462HHALN>)3!:F0KZA}=n1?x6v0}4@E>@9?<@FAFB:><B}8<{5<~;u0|57:<9<:|:T'Id.;S6 4(9# +* #.  + CV0F]*AW(+E?_UGbXqVm_w6=WHeHi8U% "-A$"&02>$!/LcEe_ 1s.HYWWst+";f$BXi0Ogfr9\!tE[,.\p_t&1Jl1Usy%Xvz `tI` `| ]q=kq mwm}*YdHX!k-_zu#fz0[hNY-8*4DO/#4/D!7N')="52E*,:8JD['@R">W";T#-=,<Yr6Xw*_,b2Nk*1&5&4.< &4'3'2!(5"!3"38Q.3J(7E)7H%=V$>Z$Tl>:O#Vq1d= ,8 ):"4*9/F8O'%79F/04I!/F,A0FH\.:O0H>XTq3$:?O%Ri*|I`~6FY04G#Qc9Re3O[0_gIcjVlu\Mc9Pd=BW5H\5XkBLYA;B<+/0-23457-.-782673@@8FI8GV:9I-?O,Nc/Nf36K%7H(CW,Pe1Ni.Xu65u/fxHbߺʈ顲X2.#u +(,'<\pBh"m:Yt,Us.nCb;Ji#b:Tt.kA`}9Vr,Mj!Un#g4s?e4`.k9Oj%On%o8~Ee3BZlHcNm`6ma~4k5k3dvMe=So5iGi>oAnMlFIq2ZTM\P@QOKBHGJH3<>CBF@;@?HFC;AAz3CCEGF[NFJF@<9BD?=;?AACENHMGPTNFD?C=5CKL@;DAAFAyDwIvHyLxKrGoG]y6gAf@\xZ'2P-H )B&";$"$-,,?)/H"6O"3M$A\&B] 7-F &:'+*  , !&$;8 4L>WC_8F?U 8",(!C9vw6&/8B3A$/ )5FRcs)s&4,@T] IT :@>^cnlrs7Fw>8LprQo^`C8S#=T/:Q*>X,]yBdG_$`{9j<\sA,@/M_,Sl(Ge)&=* ,?;VBZ#25GQe'Yn/3FLco>E^OiS0B@P*:J HW-\n9]p8{Pci=_y/>CAH@DA5EFDG>DDE`!?Y B]+-E3K 6-8SCX)9L#.@, ,A/C;PTf)]p/]s/Pho5^{ k)Dy3CCGGJp=]t5i=\q-Na']m8Tf1Ue3L\*IV"Vh&q;o8Rl&4D6K4FD\)T'Zr7jE" +2$)"0*'3 ,> ):* ,(0B&6I+*: -> 9J$4HK`-Hb(Li-0G( 7FkH4G'7A*9D$R`6Xe=frO`hJ]`FjtY9G*5C&7F#=I/  ##*0$@F/GX.6GM+R`:atEwZ_w5D"}n[˳߰~cNK^6a})0/=C`D`Po'8TVq;He,B\#Pk2Gb'If/8MRj/C\DYZu/Hg!Rm(Ss/_~8]z8?[Hd&Qi$g2TLf&1J{JRFda~3h8Pn!n;yCYw%k4wB~C^+~IXHKQVMDBBUF?A?69CHH;<957;>H?HEEHHSLPSO@ADQOXPIRUDACE=;@KI?:<>HD99;?INKKE<};w9rY#8 +.!4  +(9 *5,# + 48)D!7;X1M1H 8OOe+Me&I^F\8Oax(c~7PAZ2J,CA[ :2P +q)d%f&F2&9$<%,BYt,ZuSM[8.D6I +#3)PM_"]s/I]NG`\s&|Cbv0Oc+aw1xEh7^jFqMVo5AAFHKD?>A?EDQPl8W!B`02P>X,4O#3J -=1-B&"7:J+BV,Jc6C`/A_+>[*Ba0B\)H\)]wA4J!3 3F 5 +=Q"Le+Hb"Ia I\Uk%_u0ay1f~-h+p/w+{1v3l2[y(az)}Ch8Me$K`%O`)GW&K\,N\.Rc1@N i{CQg'j?b{3kAG`,+?'5"8H]50)7 45L'.G?Z(E])>X&Mh5>Z%0I2H9J$6G#1B+=?V$E\'AU$.?;K"CX"Fb'Kd"rH7O7@Z04N&,D *<" .-8"&2!/"4,-< /A%"41D.->%'6'7".!.%8 =R1.C!@U/Ne9Sk8Zp9Xs5*=))8.9&'!*$1%4"/&0=,8G.8J*#4,?%-D5NHa+=]Ff#Ok/DX)0A9H">L16B.=A,JQ4T^:[fDKY9fnbLOF:=-foSn|\zj~qwkanS|vlxb]jHJ\*Pb.Oh.Zs7BU%8F&DR/lyR_wh0<>!:ո㌗\iիBLp3nlj-,!Wz&2OJg-=Y >Z(,ETATE`hA5O9Q =VKg)Pl1=VZq,uBq<_1Le O~Eq9s:l7\x+LkOuFa{2@FIWGMKQNLNLPNC?@HC@FGPIHB;;EAKLK@:=9xAt>|A{?m5n:j4wAt@q>t?|HyABt8t6k4Lg Rl'4N7P.G7!< .!64I+D^1/Wp A[3Q Hb)Rp!3o!3&B[=TQfPk;OVo[y +dj#Idntpe5*q}-;o2AZzHKV%("04%R0G]4=N(\s8{X/ ,:(<".A(UgC9M%D_,\w9kA>T!6LRc.Rh+C\Un.F^!Xq!/E!7M( 5)< 0B$&;/E 1H *B9N(8M",=/@2A8I=V =XPj"Ut(/J4(B0L!@\..E.=8E+"1:I'/@:QKh)=T#4C&"/#.%0!&3%$1#$.;,%3"-8(,:"+;#1'7prDEF}Df9: !~ *b2>XKh*;V&A6Lc}?rMLh)-DIMGEGD?CG@AO@KFHDBARRQNEIEDFLJLMIIFNIMQJMRLVNa&?Q>R6J/A +@SVnB^ 8%;Jg"Z{]&t/{3^zzX,HX,AJ.! +:J$-o>[dm!3jCgz~'#!b|BXm!7Xr /G6No4g~(DVM_TgOdr0d{!o:T:XUv&{czi#3T9x ^z#o${$[uTo +g;T[wVm#x'&&^d+= \m4,= 1A=MQ@D:JQ8D`o]nbwpx)2HYrWn=[f lU&?Ql=2K!-="."1BP6;P(X84 2C=N!<@Fj:Wq18T"1L")B,G0J"+C2F(K_;?T.>U.KeM$FX)8F0?EZEW$Ui2Lg%Ga&C\$Mf,,F'; 1)&;$="8" %2-1+< 1):-):'6#0(8(>0#1"/,;8J"9O (CV4.BIYI.@I+AK,CR*@P#KZ38F1@;E);I+2= PY8]f@anArVnzUrt|efm[]fTlwkbp^BO@$*#+6)(."2;%@A?7;LQMECHEERPDE;E=A98;;<6x/z7j.[w$p9t9q1z9z:|;s6`z(\y+3J +.B.A"9N+3;V)'A2 !#83 +Xt8-C 7 +H'C!:";0J!584K%+X Eba}1)D\;m^xc-<^ +L 4UFkv"t)hHc +|0)% xo,7]sj$q\r;YmJbC[6Na|$h*\|Z|@[)i/.>?T 5M%=#7FT^ FSqzZ_{;q}C{0nt)`{!Uip3CYWm+-F]0VmIq]9N##$.'2%DY3Si=9K%nPQi)Sl2!7%4(57G>UKf"Zw;40H,BRl&rF$7 /?4E9K5KHa Pk%JgiA4GH\&Up)Unp1_y&k2u4v@Ka(nMdwCWcG?B>E:DCCXs13P6Q&+F&$?4N(:R*,A-@,<#2D*->CX42I&9R)Eb0A[0G]4>V,>W'Kg1Yq@D]+a{BMe.+A5F -?Qi05OBX"BW^x44LE^Nd%Tj'ay*u1g+f+u9|DTd(j{BWh3:L.=.?BS&H_#^t0j<@S"EZ%_y>@]%A[#?YKf50)$%8!(!2)  ,.*(7 .@%%5+-<#&24F&1K$1G$%6+8!3D#BT--BEZ'/GSo#Pp,#7+(83G"3N,F.B!/>!7F'8JDT*>N(2B$&)("-'#(.7(1?*;K.6G/.4C(Nc6J`9%% $,$3Ww0g?Ik![z0_}2?ZA[!J`-*:.Jj.Mm*sKOm*Us0>\!Ga)Lc)Rl1;S@T!Vl3>XZz3j?Rj%Ie#Kh%\{-\|.](i-Xzo6n/K:{0CDDEBLH@GJDOIPIMhHC8?G:>CH@BEC9>BFBIBDHMCJMKNOPUNbTUZOQRQSOMKHQOMLO@B@H@ID+C><1CEDGJEF7/4x(r#r&4y,}3o$k%h+a-f6l:[u.Zu/`{6Wr3Zp3`y;Xt9Ic1Og9Qk7Nj7;W#A^&Om-?YRm7/C0D^'2J@Y''A4N6 ) &$=119M!at\6R a,Fe>[B_IeOpY|.8-HJiRm6`|Cm19K0A&91!< '@ +8Qn 3K]"Sh2i+x%s zFar'Jf#8Pl%pu'v#l\w 09'|&]{` =ZZ57,(84Qj(>F]g"Lo2Q*IXwz!_|:6Kf e 0n]|Usl"Qj $9gE_d]} XzZwQok(Q'-<!0DR4\lA4DI\1Jb4Tj8Ib-]v=Sl2mGd>qU5'5+:F]!Fa $<+;7HI],6LXo0.A+96F;K%@S%Oh,Ok)Ol.[v6_w9=Q=Qbz/\t(6Kby.v8GZn,FT"Qc)qA{E>?>O,2E"BW5C]6@Y1.D1=Q,@\*A_(iKCa!_y>G[).C6L0I7N@VBYB]Uo*Rl*Pj'Tl$Zq'd~'f,`{'s7d}.`w8I^$:OVl6,:1?2G9WC_4N%5CU(2H#9F\+Ca$&B .,?('&%3 !0'$//=%*9 .->",>&%2'#1AR2*C-C%6,:#4&7)=;OF] LfkM)&97M'/D$-,8#&3'63@ BP,0A(7$"%(-.$$2#%& #6>0,:#)7&"& ""!'(#)0).2'992761541KLKLNGBC5:;06:):<+BE8?G2MU?)28G)CK/BI$JI%SlzHoTpfgwhbuZKUG;>: (/&+1'AH@,9/)1-?B!G2w7=P#Si8\q9yax?a}>pV|ddyLu[|lln᧰R%FOٟ̤OMP`\uWc-rfl#Qx/D`e | -$)F -G7O-=%: gIIh&\x1Ws0Sq0Gd%A`#Pm1:VXw4Su*Vs)~NB[8UGb-?Y&kEgEVv7;Y\{9Nn+'B%<?T Fd)Jh,,CCX'I`.C_![y0Zw+No#CaqAFgp8Rl-{@y88@n*_z#5KFE1AITLRFPQNE?C6FB96FB;AIGSJPH>;JUPJRDG?=FB?LVOTVQRPOTURMKBOE?F:;949?=:7?>505A:J729O;6577;B9179u-x6v4k2m;d4^z6mBi8e<`}@^{>Ut5Po9Pl4<[1N(C,E'8( *A&=12 +6@\'!<.G#2) !7CU*L^ar'O`((5:G"6L9MJ\(CR 8H$5 '!5 1 &;2J(@A^FgIiD^Sn:T CZ[p![wQpu7t/(<.:_t%#5Xu#E7Um)%6 3[x% 0aw"t:XKj_~`},A^ i#@@b9%E?[ +Da)EY8O/Pk Ii .*!#m^zSn)>|1s.^~@^ u86Q,H^/Pb Rhq>s #*A;ZA[ =T @X ?R.!o1=TbpXi)? .H3 4J8SDa*/G"8A[c(G^0$3 HV$Z(^q!nM\ [k-TO) +O\'1E5L +=RN__rt%9M}J[ EZ !".IX.0>;M*5J +CKe5D`+Lg.kFlG=\ /DY#g~:4PcA3K4D:J:J!#KZ38K!2B6G$5 5H;TNk0E`&Pi.Vo3Qj-^t;I^(7MfQ.Nf3kN;VYp;3JI^6jS/G?Z+*<AS Rk*Vq*7R:SE`Sn$H]Me!Lcg~0j1]|)f7F_Zp,Ma!5H/5HB]Lh"Yw55HEZ0>X'!2B! .*7F(8G%- -=9M)4I")=*9Rd9>Q"CZ)=ULe#l<3 !$* !%-;"-(#/$1>K4$26E)AR/$ &"  )@P:4D<>K9DL+3D>.:L\p?wUrcx^ziݹȮwUḋZY/POpNJ=3,Ո``o ʒ}Uaz7.(RoIem!/\qn@,1k0%B@]-E^+CX([_}2e4Ebf5Xx(\z*]~&a,`,v?~@z2;s1( (@'? #8&?%?>W,,BFZ2>O[k+Zj(Ug.`mE5EGZ'Oa,;K/;O[03BN`-3 Wr46Q /HZz''?/\CSDT&3 ,AU ev@CS f`Z3 ;J|;2o)o,84BN \rz0?T/@&;+A"5N,*CTmI-&2HX>,?[hBnJ0 +'-;#+> $5DS14C *:9N'6L'7O$eNGb)tHvLRq-Yu/[v)d~+#?;T.D(7*?L, +ET*%8*9.>Ri50GCZ$CZtGG_($2!#2&5#&3#  &'/ -5"/9%/:*+9$6E+#  !%!"#&+%!%%($*"&3  !#"&#?G38>'GL:_fKkwOiwN\lA]nEN`4^oA=N!Ybq1so~UP`ODI1 *rx4NVXf-GU<(4$/9!BLai7(1n~$j}:ɢ}٧ɩצ{JHM 7&xda਼/%^˻ҩSm"g*eN^#I] +wv9q,IUCNPWI_NrTx6C_,.C+@Ql1Ml*Ge%6T[w=2L:U&?\+2N:Q$@V Nm-[z8Ge a6yIm>nEOm.]u8^y:If*6R%3P"2L7M#+A6R',G%50?!1*@*CUs0BZ=UWs*Ro(>[Zx0Rp'\{/Tv!Ml u>x5m,d+e-v9s1=LLPAL2QER>IRHIOTLLQNJMLRTQNMLOOCD?;5PRaVZTRQOQRJCADG=@98>>F@v:u;v>s>`{1g9Wy&IfMk+C^#(@ "7"0.& + *>)"0E*?(=4H7L.A Td)v\ Mi0K 9S"=BZ;m "n'm Wv-Rn Ki pt,t?^q,b|K_ +44yu)Ed#<@Y2JEc+IA`'EB] Tq 44"AZKn!,Ng0Cp-@Z$>Ok"Njv2Sk o!61%>,KJkDdA\KeQjs{'B[#@/I.I[y Tp/Od,2J +[v-Yu 8\t.GZXn!*< %0F2F pevQfs:'4F 1C"1F!@W.@V,E\*Zt;wQCd %C@YG^q;wL(?*;1A .BQ1( 2C1B!!/;P Po_}/h,v6CGt7u=u?s:KC]:QnD(E=[wDPTn)D])-BQk=%?7P(=X-Kc8T)7S))=)< /D!Ia0`y=;O5B P1$ 8N'5J"$34C :N)#9;S$;RB]!Sg; .9+3?/'')  ()!13%9C1,8'/9*$'!!#!((""( *0!%/7<(2;!4>"CN1?K,5A!KW9YiEYfAgrJ^hHIT0ds?k}MrWmVxct_wRj:NBN1fq.=6E.%! 5i/@%4B0O](`p(4"*ir8l(^n/4 RN{׶ws~k1G.2>@`TƷƛM4~~Tk"\q@U_ẍv{XZb9O>=C:1K)0P Ii)3N,?%5 +C[ \v@;DG7JMGHH;=MIH=Pg[S_]PFMLKPDEB>;3-1)41<=DKBJCFKHJLRAADLB8ALSJC<96N+C<;@:;:=1w,i(u4q,w/8w1s2v6o4l2q>`}-3L5M2H1 /=&3J")<'>2 . (=/E"5 uAhy1t +2C!71MXr!/Gi!cC]m"Wq:Olni1L&v +Le5Yo 4E ?O+:)-h(2 *:`u%]q!I[=K1=Pi0)9 ': 1_r,8Lex#5jxIR7L>V H:N`k]sq'-AO=P60?"5G,4H)0C " //C3D $3?W*9U#$;-B2F$(@.C!3H"2H 5L(4L#5L?O&9L?W@[@W=PDYF_%Rk3$@R6)AX5Ec0Mj7&?&=5K(!0>@P)[nBE^(Zz8C_'3JCXSg*AVq:A_ ^z,f.f~2Wr+E\Md#b3E`Ia$Sl%f~.^z+Tt(9X Ge$:Q'=2D2J0 #$* +  !%*9"$9*?!8N*3J 0EWm;%71E#Ib3Kb0@Q)5E%,-2E"0GWmA"0"*-&--'::124&>A7$!** '$#+"*+-13=$FQ7S]=afGKV6?F(KU.rQmOJZ,`sH[lBfvSfxO`tDXe;~`tXrx`YmMZlUN\K19/&*VBb]o1#<%6ex':+:/0@/: x?L9B~1@A :+B+C Pj,T?WJe#Nl':WUo1Jf$Db"Gd)-JAZ$,Bk:e1~Jj)x1=n3Ld F0Q8A=BFLJ=3B<:979=ANJJMIPD@DRJKNMFHMJPABJQPULLGM?@A=EEMLPQSD=3C4=]%4Q2 5E!Zo*r3ny3fp7dt7N]%N^(ap;O\+Se1FZ'0DHZ.:M>O!Y?XAX!^~1Pm|/!tRtfq-Mp\}^| Ttz'jHWn#dyj}whvv&`Rl0Qm%C[382J6M";+B h*Qj B*CUsj~&?t63awQ$`vb{ Wo +LeBaTy WxHk dcy 56-H>Wbu#E[Vn&@TXqd sTsd,;Na~F+?KeE,Ut5:\5MW<3C \oh|/<8E0@ K^#97G1>Pf BUcqP|.mzt'J^1vozqv w*qz"UjSg'); 0D'"7#65J"Hb)"5>R"Ja.Wt7U'3JoRH^%f>lDi?c1XyxDSq4#: .4E BR,&7'9I%7H:L"1A:J1DG_&G_ G_!;P\w5Tn0Ga M.E .82?Ul7PgSi#Rj!Ys$h0Zw$Pos2C^Yq-Sm&B^AZ :R>W*F%A4 ,G+E2F#BU.>W-'= +  )+"64K$AY2QjB+D")> H_8@X'". + *95JbzGPh5:L6HK^$f~4C^Mh#7QC\Mg&3)(%> !(4".>)"0.?"3B%+/9!$/)84F!CT(GT-Q\=9D3)&21C&1A#AI879/,,&()%0/,11,'&# + +!%!,%0)5;E.DO7pzXw\j{MnTixRgsQR\AohsN~crZrl}\|hZhDxiq~benWW]O1@8enj^qt"-4GLO$*_j.q3z &+)Iwx+F?Q6& 68+"! ' 7'?V͟Ϭj7v~;[P== /E&8. *ADZ%J])Mh0/G+B2E-=-G1K;Q&1 * $ &3.AU,XA_Xv5+G:W Vt68Q9QAYIe%Ok/;U '< 6H&>,C-A6MI`'Up(g>MIDMQHHGI?<9(75`v Gd9V0>V+/H8Q9R"&?A\$Fe*.M'E4N&HZ+6KSe/CTGQ(DO&Vd3KX"Xh4DW I_!CZ5K10Ph.3cw:;P)>16O5M Gb D_/J6NSo!8R6Wq-DXuUsoOkNoKpAaQubi$9ZKje(QiLcs*3F`s?V Ws}1{:2Tr%;d,8T.;U\w&B]5Yq029X=Z>Tk{ -m:~&h"*7ME_%:5T +<]~5Q{)2Zg0n'gxfq5 /?A&J,6@T5AZ8/E /B"69RXs+1K,??XKo'=^!5"71F&E_4";%:.C:Q'E[-BX'Of0}SRl+8QKhg+6M )?@X$E\,/==M=S&'&68H&0B7L*> H\,S+/G"+ #(4$!/+-):&9"7!2(<Lc8EY8   !.-<""+0>7G?RI`Ys/E\'!6K`..E-3DCS$F\F_D[5KFZ)7G-=$6C]*:]Jl*(I1N%,+ , #0;6!$ & "$,"".+6! %"$-0&"5<*9?%AG.OU<5<.$'"0$0!    J.1:#et`SaI7E/ +!'"""$dx}^lnGVU>G?#}I-)*%!!4Bo +- 9; %!H$+! <-'?|٠h8;*{J}:^?4Io*Pijӧ״ٵܶǦҔڇj]_^ghha`SQNGKET_NBAOQ=V,E`0E]1$-(9)?6Q&5R%1O=XLg(`<4S7 4N"7P:Q!;T>[4O9SPh3E[( 82IMNIITXXQZKUYYWSGDKHMYVLSJL@LTP[d_ZWV\^`Za^UYZKXRYLHZ)Hh5U:X+H8T$$A+H%B+H,GX/>SATAU #T_21<M[.CP$Pa)j}8Lg#D^oA^}.^{'8SNgD\!7?W'.;K(@ 9RRn&q=Z{e~$)Au.G_54;]@f3ULp6[Sw7Xs@7U/J ,FMc"*@9OQg1F,FdfZtGX*@XVt>2K6P Sn=ZB])3S.K5*=;O*Ga2G]4QeA2F3E8IcxFf}ILd'>WRi"p24M )9 :K9LMc0(:0B@R,/4E!%1=1CRg7.D6IWk5Vr0Mf#DZFZ#\q2Og&2L6J5?FT+JX1JZ,Qc1Sh,CY=W@Y=X Yw,!? +#=%;'<*?$ +()+*$6 1.);1..>('%%3"1 0"0/<'"03;-,6 ;K.2D&"* +!* +$1$0(5BT(1CHZ+9N/*;* AL-$ + 0"2%5&63F!5/F(? 8O/E H[-&:5O!%?/N3Y9],O3V,M5R%'=+9(@NF0F.':&(%-$!$-7,/=*,3%$.$. #*!',"%'  '*!?D106 ,6 #$28G'3@!.6#*3 5B&CS/GU1\hFN]:euRo~Yur~fub}nq_xlXiW& ! $!,)1=).;(8?2]j^K[GGWBjwg(>BD_blgx(3=drs.706B F+lfq(L!8f +o( ,3'$5 7@-!"(4t15Q㢳ÚJ-JJG9MEE6C@,@NR<ώм׬݉she^\qbXYR]UWU]RXTgdTS[e_sYq(IZ!@WXl#Ti%Wl0->):CYIb$Fe,< 5Q Ab',H.H6I-?`y?Yt3$?.GAb#\{=!8 &:6K*A&@D^3&?.J?[)0J 1(-=!2#;4 +F*DHe)5R>Y'&=$91I?Z:W0I/E4F/FWo3Nh+8ORk4C\"4L8P>X$7Q#;R&; )>@V Jc#Jk [y'a(m.~#IUNK?GF:49B=>D?GJJBFDNQ\bI*-3\Qm @Z;Uay/Uo(DZ :NCYKe4M Kku+f~!A[r-@WDg * Qi ,GFfDf2WDd@T:Q]|)7T =W51K2O*A +)C(DGdYvvs0;P>Y?YV,E>]/G=] Bf:4"<Rg/+?6O}0{*>h~D\Og Yq6(L^(?Nj^y610I/G +:V :]xVo?W-EZry&Wm(E:PZt z =YIh,4~$kYp+@HHU'aq3^n-&15=bn$;<#\aMX ?F*)CLTd ]h +xuJM1j|n +Tcgyh~ 29^pjydleogmN] '8FZ:1C$-< (>(@Pp+$H 2 $63J.J =Z8R!';/>.?P2':@T'K]0La-DY"2@T)@Uk%e/+A +1@Tf.1G9P",?:L*'+;)<$(TUk#BZ:SZu*Qm(9L3C+97D!CP'FU(`r9Si"t7i81P">5-,/  '")&#2+( )9( ..7J/,B#+%!) 23&AJ7 $  $2%6"2!1$ $3:K%/ 4H/?!"((6* /?.E%@$</G;SF`$F`#Jh+Eh*Nu0Fm"Ju':c9a?e#Lr3=]$EZ1=G8 ($!2 /Y\O8<4/3+%,"&/%"+! ! +  '0;$,6.;,/; ,8P_;ZeAm{T|jl_{jjy^}XxxLW?8B'`jP2?$L]K+Wb\P`_Xgh#.&)%/#",!"- %'$1!EQC;E6UaSSbNRcFES6 @CH #'./5EOT`nu_mrGVVAOG8B4cuAy.i!`Sva~i +>4}IRpNjy '"q " $8I ,0@&"]sn.*B+CMD09[RIRYP]}qZeI[ed`byrZFDBHJICAA>CGAIY~ m3'h}@:L7J"5):@PTi/3M3P.JA\/.G>T%:T&<3H4K-@Kh1[z=Mn.9U 7 +;Q:P#$ /%4 4L/I&;) +((,-3LTq30LDa;T(>.&:d@uJOl,?T+8L"(1'> ,D 0H=U?[!Li.AX(/F5OHf,C_&Wr80GHb(=X!:SFbt64SNXMOKME=DDJIHDDEBLHORKNN:3-65583+-)%/0--/641,/1=AECFDIWYHJLI<9==;32/*7GHKA,4.,7:=82?DD:.#>W3.M/!8 Dc"9V +&#?Ba* +!8 +-G "8+G $A9XFe`5Xx$DbTqeBc ;X:X9R -C )B)A%7!7 )=*  >YId"-F1N:RWsBcf_~Pkc~r#uRp+Le6Q>\6R Fe z'Mh kNh \wMh b}Nh A] +3Yr6L^{kF(>O"#<&BCZ!AY]v'Upx1}AA\H`{II`/Ev15Rp@PhZyxy$xr +/$%&3.)7<%IM*+K:=lr Qvn8>_n$k~2bcwr&MaPdXbmx!YfCT +EX;L 8MVbDX3B##1''/C5M.G8U!-E"6 "7 +Sm0Vs+Ni ?XM\21=" 7?'3>AQ%Si2Wm0IX(0@ 9J?Q8N9M'65G*?&5! +(6($5"6,99E"1@6G3D/B2E6J;QLc"e~2?SXn)`y'Qi$3F+:6D"IW/BN#GW)_q4KbSo%hA &3) %,+   !#!)&"(+1@,-*=#)*,!$&$(!"-#0A*:&6'".-9/>BY!CX& + +'50 +'='B3Q;X$#"(.(,@H>lyu6G=[lfGVVYhc#/%(2%5?6$0# +)(5$$0+6";F17D+=M1>N.KX8 *-,7;7?C:ELGFSR5C>=H@6B62>*HFLha`ZONYGAMHK\Za[]d^XZg`\EBDOP??=AEMALE;[!CEX1@Th/4K=REb(3O;U%4O =Y)(C 70 7 0G#:%:-BZ),H6M!1K@Z*a?Tr:":* +*?;U+7R!:U)*(,' 2' :SGgb7|Nb=;Y!fJ_~9E`oJ>]?XKd+.BJ^$C[!uT0HF_/0LQn4).-F)E&C&C 5N(@'<F^"]y.Yt`{*rFHJJBDDIABCBDHFK8=???BNQEDCABFMOB:?;&)&-;6,'&226+1:AFFD@XG_y!}+7UUtY} :V.F`}*Xz')G 'DTw':X 3MHk!#B>_r1]z8T +)HWy):+-6%< /*B1 !&95O +7Q-@ +)AJb[vz0s)Qs&?To'9TLheFeQgao*6>-9VC_>X2E(8'.2=7C6HEY!:Nl;-@ +Vf5:M(: &6(( &(8*+;) + )#/3A->IZ-AS''3K[(C\_z/GWKZ cx8Xm-1B.=7F!CS&JY/3D_t3,E (>4#:*"  $   ""&.  $* . . #  )/( &&"/-:"$2-<&1"/$(;E&?PC^Sp'Qi3;R$C^&(E#> 8V 1L4N>Y$6WHg*;Z9UEg)6W,I/JMp'j>Zx-Qo+Sk4S_7CR*/03@*"-*35<,3FO;2@*+8$ALK77?4%-#hrm=I=/?Tj2Rr&Pp&IhUt's?boGT6|9;V"'sB46Gjh2+F Sq07O$9=W3K5OKf+Xv4[t82IIc$E`$-E1 +!7G^,Nk2?S;O @[(!: < Fc."9*A%;*B +>V_y&@Wo =VLFNHQQQTRRGIQI9LQMKEFSSSH8:8,>+!$)*)$!-30' $2;:306CFA19992*'+41,,0/.3+;"35A54:;6:4+CPjJh%Ll&*I:-I+D$CDg$4R8P!Ic5!<*A 4&6FZ%:MZm6La'Qd&Q_$Rj Mc!@X6QYr0$>%B+I)E + < :U3K +d~30HFW3I3h.JNj\z!0K,Mn"Ah/L-QWuj[~LnMjY1R;^ +?\Ut-IV+ )<$=/J 5'?5k<]w ;U>=R5T&B G_4K ;W(Fc!6RNi$h}(gx,$, 2 7*C E_!Ocy)"9/G.C +E 5R 2M ,Lf-+)i|?XUq Lg"YxWr7s'#izp|ov^e\h +0[` M@K ox4U]tuxi :D{2?U_ et!ez !v ;&hkBvz2??PI]Tf=OEWWm""( )7/ 7 9 +E%;/HGb(>Y#-A7F$$.(83C8H1D7LXs2(+%'4$3'4  " )"-# +$&)(5#2 *:HV,@PRh*2LXl)HX'&,;O`7)ET19K$O`3CT]r1&>,@*B2M#8 !;+ %   +  (1!&%!!#!""* &-; %1'*!-(2/8CV%.7B14A/1?))6$*$2'4 )6 %31@!;L&>L(7D% 4D DR,ER82=17A8IVLEUEfKmDcGKi +VoIu!nxA#Z'>[#1L4J *%1A!%;X)4 (7-B->"**>!'9Me"Q;O!:J7LKd2"9Mg/A_!0?X*?W"+B#88N)3J#.E=XA\%1H4I59V F_-%:N$+DFa%Li#EbGhJGFIDLP\UGQXLM*4,&,&%')' $)/6487967;48:A3#"#(*!$#+(.6*3*40*%48PIjEbA]4S$@8#>;(F8\6X*I1N6N'5F%2-  4<'Zk2Qa&L]&H[!>P-C 1JC\-FLhY#7#4 + &+- +(A $Kl*D 4 +3F3I7R[B" (,J&? ';Rq)@`g(Tr KhEc)C3L"  /3Hg,G7T8>UYr8M #; ;W?W)&D g.@((Dc5R)@ '>d$.y>2@ cr68H): ,= 14^x"1cv7[o!30E6G0GZ*.IUo jz,FS j.>ZqeWyDcly'lA[# ,*s~hwS]MS $.v~ku $UZ ]m 9my%Ti Xm ftatpi{$dt&Th\m?}% ! 4A'/ DV*0HDb'"? 0J"%58K'*9(0'3.>;M"&'2EF_ "(  " +%& +  ("! 1; %13@+84E1B\* 7( +2 !"  $ '  #$"%'+-8"".&! ,,41*9"Ld7Wq2Fad1Gj:X3O!?/J bz,IaV"lOe4"lxA+2P?9AOAM@9@7@PB>8>BFDFFIPOWV\d^RI>KP[UPSIFC7>EJFD@>AFQOP.F`v2J 2@v-%l/D[H\'Jb!Ul3/FYu>=Y &DOj1`D1LB`,:Z& ?Om8.I'2IG_,3M2H:U!Jb+"4"*06P&(G7M% -*=(B4KMi.G^$$9 3EC[(F[,Jb1.H":L`/3MB`',GC^.4K3H23 9-G3JRn3"; + +":0MB\ &7 4 /IPo+Ts,b7s-|!_?BCB@34@D?IB9@,48130=354-6!'#%"(,% &&#-(!)(!'.:?EB4H@6(7B=5/<@3(%#&,$".0" )((+'1\D_|$\w*OjKh$<] /M@Y)(>]*Kk(8W1L0L03I$C] -F!65G/ ;D%=CDR EX$8N9M>Q2H-Fi|*n$;O>W=Y9U_zo:y /Jc MmFkFj e(c!^~Ut*J6\ +g$j#Hj C^5N /I>\Li&> !7.E& ,1,&8 D`!2 ( / +'?[~U,Od,.J 18L)//G +5UqOl3Pv? / #7I`LhRm!BaEaVps"Pj/H +B,B )s9d$ay$ZpG&:a}+rF-3 H`@U )9 !2#0 K]) 0 >bxNgz(Mc?Q [m+OXk +- + _v''W`>U z<)DZ p(w$DYy"2:U4&4) ox 3D=NWqwnutz3(5B19w +gj;LKafz+69G +@P CVFUTa ;I DYG[Yny !!)!,&55F!Zl?-F Ql0=[\{7(D CX-0@/!,,54?$;K"0@+ !1 ;NF\%Jd"[x/!4! ")"&2-=$1+6'53DK_*Pj$Zu5)FS./80H"Wm6\q6tZ(?4J$&@ ;9,F 6 5)A, +*<# !$() *+!,&. %%"*--5 1;"7<*(+13"*,56&+--2+21: =E";F DU(1H2F"3%6-=,@4F$7 *9 5D:K!8J"7I"=N#,@0C3A")!     + &*)4/9 +8%.8@(5?%(5+;$3".(53?!,69G!"19#&2>I9+7,9F9%0+6 )5!,&18D!;M(:K"ER-0;FU(/>%3 '6 BNCR%DQ-4A*@J=,9Pi4A\@X[vb~#7SSn*)D 4HEUQ9@SiL#L`,2%$.8*3@N]ibR?.5.5A8LCDGB@CJC@7;H@BONNE=VH?JKLOCBIDCEBJKL=57EM??DTiy681-*+0,9259+*# )9@*12#.;A?9>?@4;@8-89>5%##$*4-$,12'#1#/ Zq5Q2PRn,4R/O <4NTp34U;Z4Q5Q#Hc78 AZ&C\"9Q 5K#7 -5=Fdu:J`Wn#JaI]MbG_D[wA.=3I.I Nk1NZxyD`h2Eg>]=Z MoTto)TvSyFi +i No-IKi'c+.R-H+03)C +$(@%B -J2NGc!& +/#@W!_|* +$(6Na&+%>*Wl(h7:UD`X7L$9":.I 6Q*DCa Qo(B.C#"8Li@\ 2Jb}j)4a"F^F\$#:PDZXj>M 3E 8I%y8>f}/[ux-D~([n:Mc'La*AUTp #ny*axC[ ~&^wg[yu'w )o"+1PT[cmtylwdj$r}#{}hfDM$m{ vP^O_ap}ovT^Rd"4Fcw+Qe;O5J DTDT3C9N?U Nd w# !$,(8$7EY&:V'?>X'AYa{<3KWi:$ $%-=K)L\21B!2 3H-E 6PGhZx6 #""$!! ,1A+5;B+>$==S.=VfD"UfB'-'AIa,'>8M.2L&>Z0,F2M+D"3*5%!&)- $,#3 .,!,#)"%($' (*$,-'+)&,*(&%#'%$.-)56/9;/24&9<-./"!&*/!$%*")*) &/,77D>L"N]4+;Wf>Xj8BU"^nBhzSwmm_ps_tsh}GSHFME\^V#,'#/-;%/<'2@&:I'.=@N0%2/@!7G))7(8:I$*:3C8J8J:I#8A0awVj~,$";G+,=5?I,(5!5FMe1Rj.4ORdt.IeQl%3KEXF:Mp,yip:L9ik`\`cYTE:2'7?D;TGIB<=:3@;>90NF48LDEKPFH7N"3 304A/D5Q3L 8O_y35VjLgq)>__ u2Mk^|!_AaSt<`;[;] 8U Eb<6W <5L/ #5Cd 5V 2 +'/E3M5 %+A=W!0:T5Mc6Db !9 $7%' HeB[)>ZT2G 8N%<-D (<AXD^Kf&Pl#b27  $))&  $!""*-)9#25@!+4GU#Hb$0G !3 F]+-;FX2*@2 +6O!3P/K@[1YkT:N4, )A:U),E7P"1KD^+:R;L(-:2>&-:#.9$$0&.//*)+,0"-$+ &.4& &-/)*+% ! "% + +!/9 DM-Xg=gyJduClyIct@_p:^y[zac|Tqrq\z|eoy_alSdr^wn 0GOA"*,2&:@2   &+&.-:( ,)+-8$2A&>N,4C#-:+:->)8-:6D&1=:E!5C8FIY$@Q^\oa_Ng t 6Yl*4<%2<=F3XdeOY[@JKR`]*'5*9;M"2=KT).om+tt0|2XY%dnC/:yN9E09p~DN`Ufy&98c{OdD6BF>DgkbT`jlj\MJ84:B<79GMIMGDQE@8/1297<-2054+4#/BCJADIJLHC?DDGMBCEDHEQLN/?*j{*Tj1,7I/@.@F])5Q5O#4H&4I"Fd27SUr9>\ 3O <[03L $;* +7M(7$?8' "4#:9 2N"";#9;S(- 03E%$=#<7%9+0@0A2I%>":#<.4I"2 %8&6*<2G`0, #5$??Z%Fa2 4H2K#91#9!0 '8)2 +@X'-"3 +3E%;2T>[ +>\-M$?,M Y{'Ut=X =V-Ga!Kg @`Wy$Gc,I&C Po%Fd6S*E&+ .(A8R)"6Tq4=S+$:$0 . +)7R0M + C^$(4K" +$$=G`*.F8T<\>Z +GXs>[[x")3D"&* Yo"/D) ,9 +AL>e3{!(DTjB^Qf$`t#ly,BPR^HPQ_ J]H\L]2>LT&brh|v#ftOZ6A+8*&7+-(; +6J1F'; 2K`$?R;P :M +>V 9M !$!" /=F\--'4* )(+4%%8?"(03@Tk,k?4NHb!SnHf;X##    ',&#+!+.9E">O)%5+8598L;V*A"4 +:O#-BQc35K"; )B1J2JA]$=[Jf'Nh*8R"9#@X(0DI.+1A=C@:ov*`24-$1(??nSQPWTPPGPMU7=L@A76>@>794==BBD@KEB)0&,+32812*24ABGB703)+396>;=NQRIDAJHJFD5.HN=VMg0;U 9L>QMh*=X"2 +0 :X$:VGb)C`%.H3 +1 #: 0(*A0 6+ # ):P3, $ 1&7)/ 4-A#$ $ 0 3!2&@&@-H2N%@-C10/C(Ie3Zv7"7 $61J0G;U,&63E3L :#:. >N*/@,=BW"T7N#25'++0*0&/-70;;88739:AJEF>9CB:<3A:0$9:U$:W 6M%$98Q(C]0=Z#A[&Gj&8W5*G2.?&9K$/BVn!@Y McF\9O=O)>0I&; #: )B $: 3 +!dz73P Ic?nf ]z Yv8)G+L5V:Z3P +3K$8K':l6],'AA`"9%<"7 ) +Cf*H8U1 Ro4B^"@Y$&;Ie%B^ ( -?% +)8+,1IX:$; 9+D [x%i*]}%l:.&%1G +Xs3N Gc1B$@U&)@$= -1Lb"4M,E FbE^Fa.K`}Pj*-AUm.I`2JIbZsRbEUx'Weg+u5If (m Xc `q/CV$;gy*a| g.3LZ0#39Or&ETfc|!0#CRBM ?J OXKV&20@OYhqDL .>(8KW 1C +7G3H HY8M3I "  !%)7/?#0)5'5)*' (,!+8E#CV(Hc$=Z=VJc Rn#SUo*Jf$f{K]lD>K!:E"HU/)6"5C$-; 1> BN03C!3A(-:(#1+7"%3".'1).+2!1;*;H7*6-8S[0>L,=L<,8 .:&4,;'4&3084>=J%5C:G^n7al9KiHc +'$y+>R 9D;FU`E6B5-6+KTJ5@(0=GPX]#U] C8,**&(0$17?CEQSKD37EMAVMQGHGQ>.36?*(.-1,*+R -D4 4I (>AY*Hf-Da(Ga,:U6O$.J 2+A$5) )A :2.I"0&7"3!2. +*C%/5$8###,BV/!1 7 Eb0;W"Vt;A^--F( "0AW$?Z%Ok0Fb)1 +="'A";$1(0)4/ ,A.@#8F_{IVq*DZ!4;R#;UD]!;P;M5K6JXr3b}&}3c}cz?t@$;-/?@5;>900310180?@029AC<;;BC82<47?83AD>:<193CB9=314.1985>GA8:?=DB>A?@DMM2!3B\c=N#DW,F/ %%93L"/Le1;X/P%?%:+#8 %3 Ri?V;RZw43[x3Mm?Lh7S Nj7U *CCY*%8':U0K 0De!< +*@8S/$> 8Pq%Tv%2R2S0Qv>YHiOp"Gc(5VMk8T+?tA!: !5+C%;%5.@1E&+F59W4+E3/M12J"2  -DF`',';#:,G (@2I2Mg!!:)B F`H[(,>YWt$/L  +%+?Ng *7S-Ee{2Wn*ZsUk Ng0K?]5AX -<o1QeCN&JTv{(U=TA^>[4QIe#D^;TRj(Wm)GYNa(Yh3T_,6JBV8J-<-9(51>%0,<'7$    +   )./6V`=`kEa[d6jvCnyEbm0hs4fp4PUQuG[_lBR?O7I5G"/5>_dT_l_O]I6F'AQ3WfMRbO .%/.7(2=+%13?-09%/.BH%7D-3?,#(\N`s 6F%9C+/70=,;&6*85A8D 9D8C4@82Sr]}8\y%fD*=U^2;D/7=G7+3$JQ6TAI5V42'#"& !+3DHTHW]KTVKC8H??9ECB2OEWKTXOAC:9$3;>3103<:CCHFIT=.58;-8/%$->-,+-$/7:377>EBBMSLE443E{^w? Z&C&A4M2K2L!'82C2 +, +'?C`&Fb(Mk0>Z%,G)?$:5 1H-+, #703/G%2 +/$96S#.M'>0H-G+E 3$ %-*2E2K 9 -D%A$A9 +%8+<'?2NMg7* +-(A6,% +# ' +0%</E,AVo3Nh(Rp(HiOh+?U>Y!'> DX#,< 1C,?CV"2Lo0Zr@V4:A329:5*12%,1/,,0134%%2B-1<;7>@2AE?K>GNC02>B3.BB?AGDB<;?8;B?FCB1>NJKOd4 + + 11E(?N4)87H*' + %7  /Eg6CX&717Q7O3H?QLf$1H 7)= )8,:'{/.8@Ti(}5E] +4LIf b{* G\e~%Xt(@;W,,FLk/5 + Ef*G- +#2 Rl07X Ad&=#:Fj Dm9] +Hf& =;Z20L Ea#A>\#E@_"% "4("/AQ"6H%>-K'A1O9W/%< )B =X1,EMfJb!7+7W$:>TUo%-,Lc 5G2; )Vd$5F;N#4w:$/DNLYZf /=anJ2%[j3#N;vq(DC /-!936O2H!42J + 5MRp(Op$A^)F4+ .!%.!**6(;*:0A3H(=-34@8H6F!*80=$6':"2 ,= / $ *  +  + + #$EK6JT607KP3ksLk{EW\e|PdgtG[i2yOKT u}M[b4?HW~RyOyQgcdmu^l{aYgQCR:L]H@K-AJ&GU/CS*Ue9I[(9I8I1A7CEK,LODMYP4D)P_I8D5\i[(4#'/*- +1;C00:*.5%=?;K+5!JT5'7^p/6?#,5'6.<.;4>7@6>7BSKJQIDQA@:<3,-"/+++&DHH9@;BDFFMSTTSE=B@CDMEG:GIGMGOQQKTTKWG>=.By9`z&FUq>\! 9 Z'"< "8  # FR"(3=P&7R?VC]#Pm0Gf%Fc&Ib(;O)<k5Mb0C;4<>,A>\t8T)F"*- .EGY7-9!),% (-?#) !5Pi.7S +@Y.G5H9L+?*-<4J 3E 49J=O+?Sj&>Ps#D%B*D1In,FZBU,C3O ?_;Z*/Fa#4R:U !7 +Np$*2 +&<3Q;Z +*F QuDc%,Mc- 0GLf&C +<&B3O + E[(> 11H0M 9#; 7Qi4*[ +d)=Y E]%$:/@ $0 -@:P086N ax2+D +Yr!Ec>Q+ '8 *<) %!5[v$t 4eu"0A KbDX H]l,+Xn \u!|4Pl (AMEP AM 1?7F BP dq0@I1dsDM@L-= EYv3ES3B]lfv T_4C?K 9G^vLcI^4Q[8EJ%5 .1I/H4IUi%.+=s90? &*$1#+"&'&4!0 '4% /<#1> 1&>(B*H 3K1K )A / + $7.'$ &5#6!+.:8K9S>U#7CAH&&-37$?=6MID4;$/:/:+57E0B3D/>6F2B4C4D,=->3D6E5E*8* );R+> MbAXm!'8 LzPp{O[eBjsWW_AlvQnzQkvHepGxTgox?cyUh]frN`m=ZZTX`~OS[*   +% ?I(5D$' -8ER:=I0]mR8J.p~hQ]Cjz]]oP]nMFU*CP*>L)6D;E@I"JN>XdVHW>PaF]lU>K1TaN8C2.8!18 .54=%3@-GM9?J Rdu%*3iu%$+k}*(4:B%2>0<4>5?9B:E8CAK!7Bv8&q|)d|g|m&`x03@Z^\>OJ/7A"37`L_\U`VSN?4@A8F=?<=@=;4CEJAFD743507+5:44'-%180+#2=C7F?HNFHRJKHYTBDAFEIPNLUUDQPMFJCFOIHNXP7NPNe?VHe+:W!Kg6:U&.K6P"4N"%<$:'B0H6R!=Y+%;2M5Q!3N 0K#*B6N%" 4&,$1J'. +)-@\, 8%;0I""7+@#0#<,F & !9L3' '2@%4+2 &?0 ), +*8%83& ":&%9 ) #'6#1 Zh'gnHR2C?GK4AB<;;,5,++,))988:/040.!4-J1K,K!- 7Kc63M"-;FY/4 !%'2 E]!;R'<0 +?T*-A-@>NAT3E N_+2@/ +33fz0G_Zv Nmy(5QE`'=W0H2M :R0KGa 3S9[#= %"8 +6*G  9[1O %$$C! )(>0C'9 +"5 +4K#8Q5*- "Ol(0Db #: +*C /J'= -: $8I##+E +9V7M!6/:6O.A("9&78H'Vf$R\GP!>E-:&7 / +=(5/<Uf#GQboJY Tc 4@@MFWj}$-F>Wc}>T!5.6,cz8>OZp"4D CV/@13&  +  ($!(!'(5#4#3! !-$4#4"6)@%>"2 0@9H!+5/8%+,*!2) 6?().5:%&.2D+>;-ZWI-0'1 2=/:.>.@*;4B-" // +6E 1?$3 /=;K#);BT&I$S6,4LP?D΀`xZOW=wg[dILT:/99="$,4$@I40?#/6z~gdsWu}}{WtjsOx~XmsMhkQy}euX+9Ta:4?4;;C"6D".>!,9;F2kuTLz=IS}#'-.;6>3@7D;F>QRWLC[ISN+3GOWFH?>40994/241?@EAFSD_M=8L=84B996517?EHEI80,9;@=B4?=DEOOVIHLTNPVSIFHJDED:8:6710)794:@F.4UA7PNm-5P!.I/I /I!8*C,E3I!%=4M%8/F#) '>2N!72' "2,, )!.F&'C!;2&:$*%  "0&4%8' &6! .7:G!;K##3+>9M%#,%+>8O-F!:2*B!/   1 GX,7E5A-;3J@Y*6L;7A*',*/*-:E]&@4Q!D_..I 7 $= b{GRn+Pq,]w= $4Zu5-B2C8L%"9)6H")<=SK`/C7J#6%%9@^_z1D_&>>cLkTqIe<\ +D^?\3O A]2E_*3P -<+C3 3&D*D;.*B7P =W! + +!2-B/;W")$= 4'B On'-N?W'?6D\-0 &+ ( !( *1J:S2D'9 +$A8VD\3K6O +9S,F +:S!$2% #33H ;PE[Vn . %!$,2F7J #/( 2Vn!!42A$Ld+,) ++ ! 6 +'>K`!+A Xp7#/6M)B.E;P +Tk !21A+@ 4H N_.R`S^1C/B$4$'209(6"-)-;#IY0? +'4et!_rNa9K (6HV6B1A +Ylz'cx`qf{!,% %1$38I(:,@!5".  %% &,.( ("#"&#' !0-# .&4.?2B $ +-=!/<,358".-!%$A@;><8]^TV^JY^F!#!&#/#/" *2 &"( $ +"()6"3!1 )9!+"!7D"7D cqGgrHjtDuW148:K20\rED=T% +(2'/$'-.# enV=K4fnRZ_Bwu~vPRHdhb\cX[`WFOF$!"% % & *61=:CAIFO%BO$:G6D!3?-8:C)7@HZf +&7w E/>7=6?I 3<MVFZR=HWLEA-4-1*534-03004:338)TmFf'&;7L'(A5(D+G%?+B(!0( #9/G%1I")E-D(7+ .A+ )(&  ,,E ( + $1! -);.A#"3%9.*>!/8H((5/="3 *:!3 1?!'7':(?@^(#?/H0E!(2  # "1# ):$6)>'?%?-/D$:E]&F]"Sl&l7-CPb--@(9<%}-f#^}d)_(d}*c}#h"]x#PkHa$,E-E*-K6C`$*F!;:YIiGhBbIb'9P@ZH`4G1- &6%7%73M;\MkE_%< +5 -2( !6 + %=.N8O%9  8 -L-#:<[=]! 6 3&;. ++%D ;Sk*Of3$< +$<Eb!>3E / -K&B : $<9 ";))F ):5 /77W8XFaJc1C&'C?Y 2 5%= &=2(288M +"M_+ +BY ( + 5GJ^~X@OTIY:G 2? 9N7L>TNy*~'6Pz)4MW+A %72-9K0? +' .) ( */#'1(/6&!*#.&0-7-7&10=## -0#&!"))!/,)72.TUF"#" ($.& %* DI2=F%DS$=N">P'LZ1tJ{NVyMnyDUa2hyE^_eq:JW^B}+4~&?#"5Y9H0_fOwgLTANITAMEQ>(8HHW em'[e?VlkwrUdXVcPU_ICK7R[EbjU9A"S^;BG2m{kp}k`ulBVCN[;]iA0=-9GP@P  24&.2ANOTBFFO|CI܇vifo)@GMNLKMPOCOLHJGGNE: 5) 7"61. /#=!9 # %?)F&A";%;- 0+ 4 !(0B ' ):+ >M';N2(E5O3H6C$( +% ' # & $7*B)C8 ;5O?W#Ic.-EBY%BZ Jd_z4GbYp8#5 *;-@2G?UC[#Wr$SnTk%\v(b} hVj5>-84>9;30/52&7/+)'50*645)-..+85(36,:818><=@;5==C?78144&%2.;l-f,f,^x(Xq,Tr*Rr,Us0Nn-Lo.Cd'Ca%Eb'Nd#Oi(1J6N6M .G]y2Ww'Uu*St#Rs)Eb"Wv2A_?YV!0H 4 &)!2 &+@Nm"4RTr)IiGcIc-i0'D3J #@Qn1J0N.L/#; 3 +334O*E+6(D .K#C-E :W20G"B #?$112QD`$9O"3L#; + 6xF)< %E4T)F!B +!;.. (- + 6'> .K7V2P3Q/A 2Jk:$2I+F*F.I Ca|3]{Ic0 ) + 2 "9 1N H_*  +%5-;*A %?Ja$ !)= & . + '/= '@ 04G->CM DN 8B '03A FTEUFK.68DJY(:MJ^ +;(:I&/ $1/.= */B0E) *4(32>!*9@%fWeq=nyJu}SJN(WZ)dm|7i|FMS-V\9OY5Ua=Vb6`\Q5F:z$5t |"#*y"&B'?_TSm%%4GN6&,!0#.$(,.U[RFSI'LWF")38(epTOW=NP2rx^vL'0Cm}43D6o_{mws| + "P}戰\t}T`gHfU\lhr u|!<[PUD4O==;?>F:BKC?:==GTONSOZYDHPIO+/@7;5=90:HFAPNYTHLFLJHFMKHEGCFH74;;@?D90658166=D;5?>8A@=D<556=EPOT.t6Oeaw6;V6Q"6P$4O%#=2+ ( 0%$:6J,+'! $ / 1,%62"5!/  $ +!41') & +'@9U(.,E(/H!7' "2) )#.' + 4 +,(8&[l-1D"9'C4G% (3 .#& $8#1'"2.3I#%<5*D@Y&2J5P*D :S ;P"2JVo-Yw0Vr-4L+>*1D5I#4 '< Up/GaF]*@ UmR-~26<7+1;?542/1.0-') +)%06572-9<462)*0q"n(k1Zy$f+r/SyPuZx(_~2Xt-[y0Tn(Vu.Oo,Jj,Ie,Pm47S@^$On/?\!@UDX$9J!=T#1K6TQt*Y!?R6M)="+>!%4+, 3J('%9* +C]#OoGeE`":V&25P%9G^-KKg'.K)#=3Q!,B&9,E,N5-8(B/(> 7-H%= +12C%(A(G3Q.L 6 +$,C%?4+E4$? 2S;!99R*/ ++?1D"( ')B;Z1TGh3O@^b~(Ji&;.AMe&)>Rn$Km:Z 9L* &4?Sj3I_F^(?-A Uj)* +&8/ %*"# ./0 #4Nf4QdT`0)0/:6A-63?:FCO`d$1:)7!3!. $6%3!'7 #0 1?;QtD:I,:8N ,C $7&7 +%9Neo&5KXk)/K!01G7_k5EFRq|6-8 ")?J9FVdHa  #& (&2'25B1@hvBes>ds=Tc/N\*_m9Z`5`gABLLQ%u~IiwNeo9|NuC[e}No{BXd4`XZPHX,uSi_fDgpKM\-`p8DL2CK$vzav:]{I}/|0M9/we(;+%@5C(2:D*@F==K;$SWK68*INCEM?$.AN:.<#DP7&0FR=]kY49$')Z`I&0;E>J>KHMY&P`*ES^l:mxCF'.-A+㣦hUjpbXaSksEc|@_eRyxFvzv͹âԳXAgzk6UhYmQb#ImZibas[_a[TdWgeE]mKUG9AEBBGEIE28@D/5775<8>E=9%A;HHB>=MBJHA0:+!+8'1/?9;=3BJ==C;3BPMDCLLLFDAFGM<@DAFFI=<:431-27<78;:;C18>>DXWp2Tm. 7Lc0.FjH+; *=/GMf(4F3IUk)J` C7-+::@@<=6594<=878=6,-5:04%)49;5302:5963.6'+-,/%v&m*f*g/Vw%\}4Vw/Xy,Pr!_5W}/Z{2]}2Rs'Ts)[{3Rq)Tv/St1Wr8Jf+Ok2Hg,Lj+Zs:J`.L_,=P ;OXq.Kf%.I(C 8R3N&A,H'@T!A[/L/L)A -A2#6"1 !. 0E)9 ;M8P2E$Le)#?1H 6>V +C 8$:k.;X2 &</;Y'!>/M ?^!'L 0O"= +#= :&::T?Y\|>*D(I   8Rm2!! +*"9- !: ).K$@2,9  % .2H2RJf(0@ +mR+(1N 40. +E1O?[$)C "-B8R6J@P''8$>V3K0 FbKa$=A^:V-H[$>[ 8R 7P=W/ 4I!(%DX/E +5L;QDW&<_y701 1 6L' #/B5I*@ 2FXs1vO  &5GAN(5"/)6 )44B7JEU+7 /..A&5#!0@$4,:#7#5 4?;I 4H +:O(<!2,> -E;R y2RfMeRgIYaws +z!:$}$-:4@$9O +H`8LToF^% /7=G-C56<*)38@$LX>*:FP[JJOA:4,/,=:>1<797?7<:B?GBGB768EHD>DKD=C=G4C92DEBAH7@<><0$6FE=8;==D?8;IH@>8C-1,/cz@Y$B^$Li,Pp2.L-H)D1,0D$4N(+J'I3$*1 ## ! ' %# ) +"!0 0% " )8'6.C(B27K)3'?.+<# JY7$!( ;D!" -:!( +"8)>( );@UCZ'> ++<6 +,CKcIe8V1K5Q=Q,DV)1(>("5 3$9)@- (8 %< ,G7S6Pr8Rl`~1:Z5Q.C9L]x&Mn;0FKg#C-F'? 0.J;Y+G6SGf&!-J&@ 8, 8R!C^&Yq=kG+ -G'0  0K&B (.I!7  +  +(4"9V) & *!@.#9' +Ka.(/G.G#6N!%BT*-D6Q -F*C-@:V @\%?A_"1/K4T@\Vs+35N(B+E+!4 ,C //G79Q+Fa$&9 "5 +%7* +, 9;U%? Mc"<$=Yh&DN(5 CU";I 8J0,= +5E.6I*>3B!.* "1"3 *O_)@P&4&5 7IF\I[ERJZ$87QAS"2 + 5/@#:O G_D`f~$cyg|gy 6,r}SfTe j| &$6 CZb".E -D +Kb*? FT#[h9btNhxDM[)R_6GW';L MZ,4@". +YfGL\2^k5gpUinkM[YHTml^i>cvzU;O/?BK?$J1=:??:js1n5e.|D/#-%.07&8@+/72=CL)=I&2>3>9DHO*9?14!]aQ;C&HO(KV&Q^*GS =E9ACLS^;o >C=B2:U}Oj{{lLnyI>ApgmIZXWLWI*>LKHA172;I6,EHTVQLB7-.14<:8=89,<.>::5338G>3A:;JBCADA=<;OAA?@DGAE>B==:8@DPG2>6JCBF5=B>BABCB<>?DJI=K@>1?92'A@5>V:S"0L4Q$+E $=)B01/G%5";,5P(:U+& +0+C% (! " +)/+>* $ ,) +/.& !2,C ,* AW1#81*$  #2,:("/ / & + +-1B(50&7)0H 1L#=,B!! *' #0!7)@5MVm)vB'<4H@U;U8I$4'70 2+AE^#EZ*%L_,(8 +9#5 ); IaEQmz-(6+'&&-)0904(%+66256A483*66:-035;/}1u0s3j2m3f)l+g(j,`&v:Y|!Y{%Z|$St#Rqa,\&Vy"X{%Tv!X{'_1Tr(Pp$BdMq&Hi"BbPo&^~3Mk <[Rk'Yo*[t0Rn,Qk,Ia)0G1INg!>X*A (A"910JOmLl4R7S + +,C(<$80 5M#,D$:(*82G;Q"E\"A[1Hi+]{Yz;Z-M6NH_"KeKfWs*e,@_2RAb$?#: - 6-CY3+F9V &6P@`0NLf, 3N + $::V">2M!? +?X!$: /, I\3  ! "  >R"0F  + +% +#=92 +(!  (<7(>!2*= 3E8O 8S&> .E=Q$7DW.#,B%/ < +2%= 33H$= T-*6 !8 74 1JF])%Ti0Xh"4F iz64F=N +ZjNee|*IaG^?UGW(81D+D /,&=/C%66J8M,AwWp^w`kz:GQ3+7=>!FF.IL0T]6_l=:D6=DJMW+O^(YIS5>8~)A!4J83hoXEQb\%&7TfGRRMPJSRGGHI;RG>GH=;CJGD;7,61AC7:8@>3=>=5:E?F@=GJ@8AA366B>U8M0A*8 8J!DXu3z-p(M7 5GE=80/*.(0.30-=.3850/+/40}*r,i'f*h.k1i,a'b-LnZz-Wy%\)Uxe+d&r3n1d-i1Yv$n2i+f*n2\ b(Y{%c0d~1a|/Yt*KhYw)Su$Rs'KnNp Wr+Yp+i5Tn"Vq*\y5E^ 0L>Y:W< )B,E,&-JCb(E8 9"4 (2DZv!Yx+H/ + +  (1>]"'-; +  +(51L 6W-C  5+Ia"<2->$- +=N S5G$8;P,@GW,99J%8 '? ); ,B EY?RG[M_7K 0G ;T3I(5L-"6)>4K^},Md0>-6'9%,05%. ovA[^.ms`xL~Omqnt}M3;^gCXc<'T\;>G3}yʾ(90H?[uڤotQ]X |0y;uz%)?0?.9(2:F>G (.078? -3>C*2<CM*>FOY,CKHM'AF$:C!O]3BM DM JQ)>C[eBf5%4q nG%"M#?dФխիΕhYae\TMN8BJADF@IGDCGGO6KNF6<8630-&/.--1(-.2763;6::64<9=9A@>;4158=?2(#0+50.9=@>HJKMFCFKGCBC:K=A;>:4B<=AC@EG?M?<;:@HA=?:C?CCA=98*/7:;:;>) +$7& !( "-$ ( +%:2'   .(7,) $6)8" $  +-' +<$ 1!5*>8Q()?"2% "/,;+ %80HJ`2-?,<Uc/5F&7. +- &7 #2 CR':O4Je}8LeUl%]r)BU4G(3-(2|+6/s)n&d~%s3i,u\9U:S*C'H9\1N ; +2Q/N,I7QIiDa$3 +:U +B 55>\=_*K=\%< #?`!  8 @$@ - + +  %/$->!%,? - $7 +7 1K"    '9)< 9S1)>8P=V=V+B2,F.=   & '5 +-F/ ++/0B%; 4N_&,5 &:3J6Qk,Sd.-Xk%wA=5L^/@ 8E"wBSiFX5G JX .Ob D[M^)4U`)HRb3 732F$7 *5 # +?V5K *=@T2B+< /@*;J,)7* $ - 1 (@ 0%9=S)?1F-A 0DM_7I0 ,F/C8K +,==Q*4cmEJ["~IVR]wntsv~Lcj<]QZ*$1"- GS"P^.T`4MZ)?K#8D,NXIIRDco[xWieVg]\i1||k.<4jmK`x=fr .g!ym(@W +Oe Hgh)e~ xIBKIR&MU,@KDM!:CdlGfqQgsSUj.8fx ai| 7?3L`sݎ͢ỳODJ/@2;/59/?AEFFHMCFJE2HN:) )&")! #*'*71''/.+*8-573<7'4@A:88@BD<:C:449?D@=GJEG@FG;>7354CB<;?8;9>;=?61:D6Ka>VA]$=W$(@6)>,> (=4,#4#=(C ,," $ !$  /( #1+/-D$7#2!6 $7.(&4!,8' +'5"/3B%9Ui,J^'(: )@0F*? -C'@Pe#I^BW Id&c.m%2EAz)j<$8 50$*&*012#(-&*&,1 ~"?38u+n'z3l+y4:n*v4z9u2?|0y*w$x)y)m!v*|/q!jp~)ru#_q#aTsc$v,}1v(|0Z}e,^y&|?D}9Y1L9)F(> * +4 7 %&=(C4 /G'A , +" 1H.+- 5M5F@G]d$z@-A_)3M$@ 7*>'*E AaAc2P-E(F 5X:8V'F :/  :U#+H&? 5S[z;2, :,LZuBHY7+ +9O!>78N!  )   6 5 '!8 4*<$A 1I  # ,F 1"8#9+ - * +( ':QOk,'>&=$< : (B4FO`*#2 'Ja+=X+B +2HHZBS 8LWk_oGZ Wk#1E%!3 +#4 BRQ\L[CT GU?M\m'guk{y%ZmJZ/>#.DL* >O)6L H^3H $6$" - 7I!3#&45F 3 //"4*.0*@+>1C*=Kb$0D3"8 6 6K0?j~+Xu.f~"\q;M>ly={TL[(,<6?+34A-4"'2 !. *3;B\eD`rJR_9^iGIT@mqnnouyxſ=NKjb~|6Z{usx4Bdov?ox++-`{]z9S I`\y(X{(5S &=+E5NMiF_G]4@X*Rl2Oj%RiMe3r~ 0t -?sC4A@J$IERw6hbmz5@Z?;USifgbBH:@;<8?I;58BLG?9;;6=G72@A@=/.&(8BM04++&)2/697?9<:662/,70A;9625?D>;CG?@?EC@<;CAB@A;;@AA;897;,A:6:A?=DDEJD?EE?BEIAA5:,>@B9@??9>?.768;2%:,'?3 ! # + "! &  %+ +!/++  )% 2 5$   $& +%2>'#1!/-)+% $651#94M%"6,E[),@?V,#:"6 ),-;&* +_~ Ww:X]z&Tp0C+!82698/183++(*>70"*#2{&:2/{+0=44.2.{#$*zr3s +&'!'*-'%.|!~$*/(.o#n"12u0d~)+121m9PtKh (D ,6 6P/ *=#3,$9 " !( /( "5+ & (>8 ,?%&%6.Cj| x7p-5N +6V1M0Jb~"Mf&%@+K6S4>[-, ?4OKh*9 +1J" +&A. BZ3$: 2 +7R#4,D$1 . 2.J2 , +"-.B! +/0/#.Sv"Pp,J +$>  -( (!9 +*? ,AGa!>D_ `w.K`!AS;J$" 3,F +*=,AQbIVJ`=T 4M 5Q>W_r0IR'Ym6ReMZ3C=\p_p'DWQ_ZnUfHW#2"&4" );-A (CZ3HJE_Xp$Vn'>T!)@:Ta}86S@Ybzl";o08-xF#BL'EM$AFIN,X`;QY2OY.GN#R].HQ$@GFJLULUHQ\c&?2(z{$)MITZV\MP:4O:E5C8ER;UJDJB?-=?A1.2.0F5?6GH995400=+7@8 9109?:7>;@A<8663J::,B;EBF5@:.=099B748(+::@B;B;99<<;Ep6FZ5G,>3 ,@&953 ! +" !4 + 0 + + + +#  "4!  %.) --*# 3$- 1 3+( ./ +$& + + -;+ +' -0$3/ 2(9"/B.B&9#5+@6F!"/2A+ #*(7!0 "29J)*<`u78N F\^u8(;7I*< +4JPe';TD_HcMi%Eci.r5_{&Ws{86&"!%~#&'#)-(1+w81660,53<:3&  '(/"$ -3(+%twyqnm\a++I.K/K;U&'  . #0)?+D1 +  !0$  $6R!0K0 " + !w=`vBYay$Mk +Y{+Jki2Pm;\e4Jj&/%?3Q 4SHj$(I<!:"> &C *H4Q!Nj35 6R=\'?. ';[2 Vd2.= 1 $;)- &$ +' +!6 5 'C.K'>/ +2K3U : !6 " "!1 !2@Y7N $<;ZTs%&!2>\:[%=& #7 +A   +&;+>CZ'$ +$30 $:Qh$!5lua%5HCZZo(CSa;S /G3(CIb&'?Tilw'GV>OEUZjCP BS +F] K\FVCU 4K 0FOe2F )=Wj. BX5M 1F%5/A +=O>S"7./F,@8E "/#&   +"$6&)>+)'?& %9f{)Xi+GPE^4C L]XpTpRkRp6R+=+6CDI!W^;r`Tc:DP!GR&zhQQS|5E77=ptXMT4U]2DH``'Du/LRSY]LGML>/4HE679>IKEHIGAIEFNIG?JLFC46-*+1#233219:;9>589EC710&/7,/31DB7C>H@FB<66=B:5>A5F?;4<<7::AG:@@@KGG9D;8873:=:>8<9=><356.>tv0Zo)?V-D+8N&&=(=3! ++$ +. "$+)    +& 7$;3" +' ($5):!1!734'@0,(;),0  ).'    +"97 3 3":0& ' .%6);6L%'=4L1F 9Q0&8*:CU%FZ/)9!!/) ) ) #7 Rh+8O*? -0C4G.AD]'3K5PDa4NC]"B\H`KgFb\w d}'Lgc{#79@>BDCCE;;H9.'+5'6~!+,#10)59-4/79.263*/233,2%"!%%*)!&+/!#"|+{!92y'{*t'x:3R 4O(E2"9(6G& + '1L1J-C,A%91'  +0,G6' cw4f}^qj|Wlt4MlQo1J C]Kl#Lg)15T"@=[)E#> ,H/K1AZ"Pp#,O-P.I:XB_$- 7P(1J9J' // !("2 &%>$ (=)/L9Mi5 .?.;,5#Ge8\A^ *)' 1 " +/I +3H2E1L3M XI_"cz9;P3I 0*95G):-D 2K)A 7 +&;  + +   +$ %$% +'8&>5'82G .@Pcr@ZqCRIVQem'2KHdc{*(37?7A;G9E>F169<23.16:DF%rVfF4K-H0G&89IOdQjE_%27SYm6$6 *>'A Wt!QiG]^w:ls,js>9dm:gAJ?HHP!AIEO@I ciIeiMTZ.eo1o|bLONBA9JG66?5860/:5A75?>LSI;;=:>?2A:4/8@DBIDODDHGOCKB<74;63-,/32.(-%#8J287&'9&./<.74T4GA37?0*248;=1642,1,$/@GSm<\,G6M#.D/F!8.10'7$7"<6 .))  `.@ &:1+ %!$64 ++?BV0.$;( ,6T' < 8&:*% /" (=5 ' " -$9- + 4&=# +  $ * +4I $( 4H{G(B%<"7*@)@4J$-@ &9$6%4("2(7O9R,E/F7L5IS Kc+=X;T?ZLe C]g2q/^zl0d#;=2 F]/& +$4*8#)8/G)C4 .J/%:1% *@Un<*> l$rw$\s"Le]y1\v&0J 0 2O8U &C )F +;9W5 -I*F)-=\<6[)D%)@ 4O&Kk45Q'81) 7J/ +  ) *F11O%B1R$=     +*>(KOo%"7 +#!* +' 8N"&8#4 ,C Uw#:T#4Nd!&BD\t$jw(+7BF%/2& + 1DCX)= 2%;"9 #: 4K 7"6 +1D +/B/A8HNd$8G.&93HN` w)AV9Pz1*3IX 0B%4 (=2I 3H AU8H4E2C @Q 3 4 #8""  +  # +*($! *A>W#BW(=cz47M EV"PgF\L_BW@S):Wi!$1'"* 08.6 284:387:%( 03:=?@$9;,S]DLU3O\4^iSmptdun8B07B0).AD007BF#Zi'e|-n;bv.Vhcp!x7_vZs?V H_,3C]Qiz g .(C)= /D)@ +>S"(<@V6L2y>}?0E *&6+=,BG_Mdy#7rIc#-UgesAT9>)lw5Ydgq'^ijx,JTLf~8u%IJ061D??<15;8#=3@:30/=A877326./2'392>5246(*03;CE9@7,05235>5&)+-4-7+;8?=AE5;=:<:A@A@CJGG>=A9?:=:84C7/6/&6))&503,/(%35"30.26-/.+(33<,..*/$-5/00/5+328./.<:75+3477634:4=?:4#4,/0/,-0{"Ke Mg)8X/K)A,A)?)B">'( # %6,)?#'  + # Vl4( ,# " AW):Q!4%9%8O&6+G"D'A:4%3+B +DY8(@9R'0NFa10$ 3--+(# 10+B#=1H. /%8 4H)@&=(7H/9M/IF`wB\w'Iah!XrIg `~ u,C<:7/>x+4+=56267,,/&)&&,08.1*)2;---3176-1,,0+%0"%~"x*(x.-%,~!u"x%~)|+s#|*__[~k"20,E0K*A /G%8%' ((;2M9#? 5) + ) #Yp*^v*cQw-PjVrJh6S-G 3O?Y$@ +C .M+H6)D !:7Bh"Pn1$;2NWr=2SIi$?`%'?/ ,B%"B5RGb%(C)< *4   +  +1K07P9 "  + C\$/N"   + * +:%*<%5% .B"8+EBX'0=P  * 0% , +  * )A,*B +t/t~2Ze`k_lFT}P".8H,@3I,C/F.F- + -=E[^q"(:) +* +- 1 +N^2R`&.A <<FK56:,js`IS5JR-DH4RUQfqc.;(+;D+'*9= ).jrJl9WmtCn=Oa?}6Zqm4[s%DZ y-Xn d~B_\wJ`n0AY)+;. 4 %; 5N5Wq)Rm#Fa`w0/G7O +)B 6QH>Yu'*><=dtKCg+/ʎBJ=C&NIJAGID@=4B;@2)2&?72-:7A@78?9B45.,0/%/0(.)$ (++57567(*8/239(1:??<>46&#*$#%'+50-1;6.?34(1-/0"%.#8&#)$,)-(10<75/,*%/06.+.9,A?:5>:*B:2,#(,.-"1/=y@4S?[-5O%)B;T$3L*C-J$3 5#7$ + 1 ;+ #/3/ 0G0J-D.@&6% ->G]&J_/2/'=$>!<+G0P :// 3N 7 +1K+@00 Fd,3R7 !:&<.$ " #' $=(B(BR&Xp9- --?.C4!;6O*8( %#LXhzG\Xoa{N`D\So(A`Xw6Db3P:Z(E Om0Pp&3Q4.D_!;[-> '/+E( +4!6 +"9 * / -H51N$?8K+   & &41@#45K   4 +- ' +  +#+  BZ0=9I(0 *%*> "1!: +%> " $ ( %$9)@Na$GR8FYg%2C*>&:~@AO.A,#9": $=.H3 +Cp =*/@*.   )9/; $5 3C0?R("4 +:,?5H"1 + (.;"4)!1, +$2%+ , + $-"   .%-% +Nb$\j; +"7O LdNg?X LcTog+Ob r2Mi|( BH!0:/516*-6958AB%/0]aH5;BE16;?-ejW2;26PU<<@%U[7IP)R_.ex._n'_p6gx7Xh g|"[pk/>U +\t"cy!avJ^3LaPifyXsOl(+ #L_-+;S 5Q +%@ +:WIf&G]t38;Tp9P Ga\v'l 3>C?B[>8ZcfDɓ(0=91420>?D@BEG?-#-*3/:785;>BB@475527.3125+48.(1$/4,.1*$#3"+7@BH0A<:9+798??<=JDVUTYOEGJJE6379;C47;9+30/3.&;*2..56690(*),+)**$+#,3058=)4/('**7.65+1..8;8.:428-,0(&)/-7175=928?:;;784339;6Y,1N1K5:0#<) $ $&:( %3&</ 5$:&A&?)D-H!92L&!7) ;M$'9*& $:%>0E5P1M(029.|",o"m&q*j b\u+y/e#_w,w*x,o#p'Sp3E)Mf.ATE^)F_$.?.>9N")B!8)B2 . ET9gvF@R'r-g{+z8_u]q)PgC\ ;">Gc ;T5 +#: &<*B Qv&Hd! ,Pk6;R)(B"94I'+(C)D+H-F 2K4G*    +'    "&#70';/    +# ) .  +&+Yi<+#)5* +& +/B! ##3* $#3!CQ 7ER_*{BVf>O+7 (9%4 2H6 -H-E( +$A^| ?-9'= 7J+0D=Q@(IO*_f=FR!_q,Ue"kCi:VgZjl|+av)`t+Mch|"q/J^&sSlA]Ll2+ +CWq0i$:;ULb BVZp%;j#a~11BJFF* y'&t%62D@Qv+13%*#2A2<)/4CELC88-)4/<=E?;7374*+/(4'./42440?-*.0.'&.0"*I0:G;TPC1IB1,'7?6;CE>VUQSYWLG=J98:.*;6:(02*3*5(31*0A30,)(609',,)0$&+.#+)/*116*2&%'(,.'..+*5/32<7+*')3.-4&7)7+4*30410265482.258;91:Jd8TG_.+D0I!>Z1"=%?#<':( $;;* )! ("5%  3J%'B,G!%?+F"+F0L@^(96RE`*6O: %B;W*)  + !   30O7V$)D,D++AD`+)EE\7# 1$82:R:M/E=L5Hh=h7yAj(O:3:CD?@75.:23,?45,.-8+ 94+'/=76-,4--!.=5w%8/;5/62378306.5560}4h$v4q.l.i'g#l&u'o"^\}m(j)c%Rj3F%7 5MLa%Tn-;PF[$+?.F+D0, 8# "3 DN(CV ju(MgWs?Yu"X Gb&;WFb)N4F +&, _w5B\!4 % 38 +s3%73H!7{HR5*HAP%'( 6 ' +K^Yk1P,>(#*( /A-?%7 ) ) + +' ( (.0  ( +#  +E^Z{0N $9 , Vj.UmYrm52J@E9<=:@,1-()v!s%bg*`})Z {7Tv:|(,w}'%-,/&+5@@G465'5(/-:2,+0-*:7)-4/4:/0'24=(<=121&-05@WRQI>NI=;@--$*<8?>LPLQNPHI3=21/-75.0./2.-+,*1,*"/.8,.0*/:6)%76%(!)#4)-18%-,)**'1-8-3111*-+//.9//.058&/0-+/'').'*(3)7$*.22420)&)|.c1H`$2J+C/J""<32(+*C!"=( $ .F(4 8((;W)3O#9T))C4.'=7T%7S%>[-?].)E7N'- $7( +)@+E%=)B$?8T&,I>[*,E <4 &<@]$>]#,H.D)B0K   % "83$=6 /HG^1.D.F3PEb+Gd/#@8O&-BC[.+A)8 " $* !.#5( +!6:R7Q1G&< ?235240-{/}-5y*0y(/y.z2v-v-t+g&y14{/|/u&r+k,^{ ^{%Yv!Idf~;I_$C\^r3D[L]'GY$8J"#8CW. xLTc0f{-pOiSrQkBZEeEc"Je$/IB\C`"8S 8;U%B 0L5SF_-=U" ;5/ 5 4*+ +'"40*&0 'E/M.,C=U $5 ! "4 -> ->!-2 )-"  /!<# + 'T  *. # (=$;.F)> # -=3 +&&,%" %2&%6-Cb{2L^&zD#2D0Mn0:O 4 #79M9M}7&'z":J0?/, +*<>R7H +)5G_w*ALXJZ:O KdIc5N\w_{"Wr%Pg`{DY`dN>A)>?&55?C'DJ'46;98:;?<>tscDR>05=@'8<AD#S\2Xf0KZ"Q^)O]&j~5Pf^k,S0Gsr%Ts tf-.(8Gi}$v,878333-~"4,&%.,mt)w.s0z4r*a`[}w(t#|'&% ,(-,-=;E9;E;6','2$-,,18&.** 3/8D>6+8'/8@IRSPIFUE=JB5I-/(2145:>A60*+3-/3/.%0'+,2/5572+34:66/0/3/;5HaKb+7N"%=0I!8 07K*) !36) ! $4) "82*& AZ/4N!+E)E3$>(@,J2M%.K",L!%A*C6J)2& &)B"<%?6R#.I9U!< <%>';!<7S"&C!8.# .< $ ( +!(B$=-#:3/E0 7N!>W#4C BT4* !  + )* 0.&:*A4M$<,?(9 AV&0C0EG^%Le_|"v(p%{(s"q*{$0.(/*(7~)87/014(.(()(/+85;535,;--./6/6492>63/.}0z,2x-~/x&~/x*w*p&j#p.g H(b~Uon#jh%c+^y$d.`{)^y(HdRn'Rk)Si)Sj+Sd/=P / 8Lg7h|;ex +1O_$er23DG\4G#:>[[Gc#[f4Xq#=X2MBX(4KJ^"'' !. -$ +  , !80   2D!%.C4Ga!-F .E + '=1H8O . (7& "' %  0? "1$-A 0 7 $< ++C$3$  ,6 + !1 (> ,  Lf#[ux(^t;tG>Zs#k9 4e}6*@7V +Khh ;Yn*/?H[@T&6 )=Qe Xq R(  // )>(=, (9(.,/ 2$7 5,)? (= +":#9  !1&8)6&*Ne")4:7?DN*4[k#Ia Lc>SJ`KfQtOnSlPd'glS((FD.JJ4]cFMS/=D491639:AU[/8CHS-5=5:>E:> 180;BPJW&ER_q&[t"?T 9I 0> 5D 7Fk69M +]r|k +et*?S3E o|7I}8;=}'53(u*(# 04-7y$q"3{+~.3.r}-(,'&*./0=/55;7679$68//118,411()-,)56B6+&+;,14C=340.*2*2267GA593>@FD;7FCMJAFB;<8-8#!+,!$-*(/:9-/3327*#,*+67=@2:<=?16I58A@;893BGHF??,7;;5A0,$00288*76.-.1/;916.5969?9*334593=:<5,=@@<4<5>@)A82K#&B1L",)A%?(=$8#<* ,$:,D'>* "0,*A( (%* 6"<$6"755 )?;S /K0D"& 6A'!.6F+D+D-A!! % -(=& % "41F,E4BV&+<:P ;V1L*F oX9Y]|$l.A` Ln!Jf#*F:&@$<!5 "> /L/J$= 8 B](&BJd01!/" /(!8. ' */ #9A[&B`"Nm2#@4KC[5J&> $: 4 ,B>UBYIa&6L %  ' " %1 + %>,&4CV'#::O On'&C2C\%5 ,+A( !5)>;P    $ ')&-2/ $I]1#8/F)?,C1 . /(% + * )9"6#9  8@_tk)FHaRpy3a|,SmF`-G+K <?aToPi%: +7J5H #7*; (; /@ ;N*=&6 +/@ BI*7!'="7):"2 40N3*. 1($#8ToBVj+1. -*/E!(7)7 ThXj#FXUk#:IQb cyi!Rj 43F2EA\ +b Uo0; MQ48:#,.<>'AF'BI ;l#Xk| BH<~356/,40'32,/6/+.2<4y$t5*93"/)0*4 -'3.3-+./"%*-7+)52%2210,4-05)#)+$$/#:3%/))01-&4')$&9-<9=JA51:?=:1.12;05008+%"!58-04=8515+.614/:96.989>9882A394-15>2AB@;1E;6461/4181--35-5?>5PD=<7/6-94286=R?:BBCB?>C;:?D;@AEEKx:Yt/]y7@T#4 %6"7'=,&   0- 86"94M'4N$1M#&A+(%=+ -(C /$!7. *$7328 6 :92*=)+!52 +1 ,B 4"7$>0,% !90,B$( 0" +,&;/ +( /0 3 #9 AW$/G\02D#5$ +#.1C0F'=0K@W-#7) AV2 *1 4 *@&=3)?&< b89V [w%i'l!/$&11<'}#|(..l&-8796*459?V =W Hf3P 3Q4QFb7U6Q+E&>$90 !7 -I8 7T+J/L?[!Uq15PFZ( 2!3- + !)@ : +He*;Y6S'A0N1H$Vj33 .?+ " Ja(9T-I+E%<+#9$5!$ !9G+ 3M% >ZFfD_Nn#Ee=bDePh4  +%') +) !3 7H!:N4L /C!5 "(<!7 2M,2!9#"5#7 $;/ +$ $ ) % "-9 ! "! + +$6KC? '&t)uDa&2KZu>@b;X^y$Qu9S4J%93 +6Wm1AW0-BOe, *@ h&q7$.):L_#)(Kl*SUi" lGOc-u%\*!)/ .. +!vL[f6SiF` Wm&%:2F Vl&Md_yVsz:F`?SH\5Lbv'5>=A:ACH%068B;AAGAH=F9F6B +FNBKFOBKJT(CM 0;GY+Qf0CWRf,?LLX!DQ5FG[Wl+@YkNlQuX|NOr7HGCB<92F577<>9.1(&%/))A-.5:/-6A@@/7,xj +-&$%'$+"!"'$)#'.'++)&#%#. )+,.,919<).-&"-$(&2,7138.3/<=;63**"z%6,5%5*."$)(-8(( &0-(4%6*/543%)!!*)(!.,)11+6-/2+468=943?82<581657449.857?88:8-."(&,12,7C2?EAGD@EAD?B;=89;;k)>UH_&%?5N&$?*,' 0& 21% ;V.+G/! !%0N-G)A!;4% + 3( 20H&A- +(9" +, #++! +),-( &  +&6, 0, .@V-*:'<9M$*@*B'?/B!9Pi-=V>YKc_y'f)B.ox&)0-"'1,;<8/*u+p)75|0C~7~195w/~2|0@5:;0/z$413/0|)|*w*z-v$v$0l"t%fk#{.w-l%k)l*u0m*n&`cbj!f"]!_!j+b&c!w3UpoRm `z*Rp%Up"^z(\u*`t3^t-VkPgmC6Ig1ek%u${J]>Q9Q +$;EbKkGh;YIiCd'D &D6S+G"8+?'@-B#@.I.N[#4*B=W&9 *>"; + 5 !5 #1 +.@ +*#9-  .+= +5D#1- +0(=(  )7$4"/  dv@@ls1VrXr`v]t7jd +=X/5Rr,#?Ojy!be5(9RA.L>U3Ok-E^!7JJ_$d}0+?U;w=bz=5AWFBS&J`$k c+  +(.* ,BQn';P''83L6NE\ DV4D2B +?T9O )EA^6M>P7F8D.7 28 ;@CJ!>F=E1;9?DJ JPU]'IU?KDKJSFOS^*EO$Yc3@S"Oh3Rm5Lf-Vi/Ob(hy7i6dz0n5E:=0#*(*8((---1$)%,~} {" !&*$#%*$+''%%.0+0.1-)'./)+"40(%01,2(/5,=%"!4(%,!,$+'.)+"#.$.7'$& ,)))#|~'*).|$#'&"+*(-&('51()9,500-,5/69:98;85=;>E;4M"+E*A   + 0&!61   % $ "1, - ,;4E 0 3H"":5HKc'Si0?Y/ +CX):Q$,A+C8L Rh7Ka-C_(Yu<2I)'?/I?Z7QPl!OjOku20w/;z%'*,.)&0B430t'{4w1=|2}0s*v+y*15w*}0w+41~,p$x,1s"s)r'|)|(y&|)s$s&y)u(p$k l }.r#x-z.s)o't/q+o'd_h cfln'\~^u1On j%t9f$kM:Xw9o2`~$NiI\k-Zns)+o$Yo=U D\4KUpEx5p77J Xq$\z']{/Uw&<\C`7U,K>[*2M+G7 &;#4.D#: 2N>Z#(?#@;+G$@5Q?R( 4L!<7 +%95F ( ,@!1 /@* +&=Q 9J & *C4Q2,F +D]-F #) %1#7'<&)9O!@_)G4T4+LD`)=T)$ 2L,K&C2Q?Y#0H5N +*'?)BS/ + '1 +/E&=!'>"7 1$'6*5) -@ $ !%7%'   gvAa!NfWs +$g@T 6MAXBA[rz)9\Ms:A^ KkS0!2?Vy/6*>WuI0AV+8~DK^7Xs$PJ] j~-hb|AY +CZ*4Ir$1ESn;bo9 -+%/Je"#6 2&XrRjYp"3I6D;F>O7G 8LPe]r)?PGW2;6;8=>C4;@G BHLR#PW"Ta(Wg+>HO] KXVe1_n@_rAjL_}BfCi@`{5lBtC|EPs4s6g~/_v%m1m}*;2;9<479,3z&y%71253=07B7?*)7897-2,62.125/90,%A#/(/5$"-253*#( !$$%%!(./--2,.300<5<41*6,-1*1)30/+01-//.'/0&"&,!# n&-x)(7(4$0(-((u#f~t*(0'077(,2#('0m{")($,%,*)*..17:4977;C=FBF7=:346>;=:B/AI>B6E@Nh(9V=Y#6&9(C$=.E2';#8'   + 46Q+1*D/J"*B,D(D/! +  )+$ )>4!4 #6 =&C$= D\4 ;' +$+A2G! #&@,+"7) '$7 5. * "5>N,% +( " + -. 30*(;o@BZWr.Uq0?Z ?Y 6 +?*4E*:%70M71-(91DCV#;M;MNf!\{&v4r4[y'_}#j&r&~/ox"%}#~*(*47l&}3:y*{/t,}1y+ks#v)r)o(r(-y&hby,294~-y&w"w%z+s%z)0~.t$y(.2w+50z+x-t)y2u-o-o$n&j ]~e$d!w#"2l+Yr%o2\~x(4s#n#;Ll3_wy-fI\@RCV0F HbKg"[xE]fv)x93MC\2<5Q@[3PA]$5 7 #=)@!70 /-H8S!&A$?#D <\&70M+G&9$+B"- 4$@#9$ '4 9K&BV$%9 %8 ")/B$4& #82M%+D=Y#4P2L"3K$7 (@ < /:W%@%@7W;2 <3 #+B!9 1F#5 ": 4,@!3*;, / $ + +/.!,Xi5<Ul9z>So>SC]vD]Lh6M i&3f`|2Wu%Wy8]}Da +Qk +p Ln@a*IOl`}"Ut5O1m$>AT.=TWo0H?J =MCU@P L]DS8?486=8@:B2:>F9B8?LU*JR(YdDG37<0.(@/;<;7>>8?/4*/23,.2/1+(*,)78)<70-0""*"$061.|'--)!# ),).01134/.($0+#+-95-..00.883;5+*7%)'&%#*#w!&(-02%,),#*!%%7(-*+ %#!++%'-&4)-9!$"$..%.47-;4*,& '/0/9-462C;@8>7<2>>@@EB=;<97>>@;0/3738?B<3?.24=9No"Ke#/,A-2L$?2.$ ,. ('';)-G6*F$<. +H!7     +(;. %950"9%B+ %6 +1( +,  ! - .C-C* +/@#* *>(A0( -*(C!7 2#9.,$% &+( 62 Ka;)G\%BXD]!Cb Nq*:J>O$1EG_Uq%XtLh;Vc$h)Vxs,w,w*~-u#;v$'.3?m#~/x(u#z+w,q&4s%0v(|0x,{-ht({2\ev$y+iy)x)y%w$/}*232-~$|$}(u$63.~22p'v1w6v4m%(3x*6i:iSl_x&9R +e%Xv1!~%9r8k7IGaC` n/=OE[?V$? &B3 *1K*3I%; 5 +1. 6 ( +' +!9 .  +AQ./Wl/0,A1 $4 0+(6/2/30781)5-@R^8A8:@:5862:1020%.4.;7>9C<527,29Wr|>Rm"5KM_-BW',C0E)) % 1&>3O%$>!9-I'</H!,E$ +& &7)3'   2J"#>&=,-? 6!7+"-)! $ " !0"8* ) +,@-H$;+ %:& ! % 03L#* +)?*#61,(" +# 9*A5H$)#43C-A6RCf#1KKa16L/K$?, # , ++>)*(9-0BCX(=Y@[ %6N'    " /!7'@ : < 0D "6. ",-A7* 2+ (+;  1 + +N]9 +4C 7 +/ o)v(#pcw +Mh8X"> j$@VGb"A\Gf9WGfVyTx.2J:W-M 7R:SWnS%%0[q1OoaPhTl cw, F[9Vv8Ud4[p7aw89H?T%'e+j61<( &=6 HcPm'EbTs]y/GMbRgEZlJe|A[m5k}573z&,!*,$&!%+)108t.~3,64+*.)$$%%+,(#'!" $"$0'-,&/0$ .~{y"w..+$ry%*',`~5,w!*t*2s)//& v~ /;:3,-28898/2;B:?36:-0025>)4)%$%)r!}%+#$'+-,52.0$}'cv"1+}"|".$/"!&"6#%*%(&&-91$#2/<8:+5w t%sy | 0,;:)410:=7>17/3>/+..252%68KsVA?C@2/5-.)&+##+ '/2742810343(3Dz-CXo(:K)8%7&8.(  + " !2 3"!$:%<%?1 9T)2 %4% . !1   ++G'D5  ,% 0$  $6 +0J%)@  +C !024P'C[2)$3 +(7!0*<-+ +<>R:N*;!& ! *-$ +10*;(8/DD_%Uu3)E4K&;2H)?(0):&7*:1@#3'8)<0G9Q:S?[.J +t2YzQuttx"poy#|#yxt,+|(.t!z)ks#l{#q{'|'z&st}&0o#y*t }&'}'x#z*+(x"vv|#&z#w$y&y'w(p#q'y5q5p6c-c1h1d#d'g#s*c$Pq9[/M#=GfWrWmp)w$GDdn!az&Us2Zrz$;K`9OXu#HaAVUq)Hk 2Fc 9U 3M OnLi1J 5M;S@UCW2*B,/K$@"8+ +"5 -EC\$F^!9U #:$  (?%+B"756S" -D(@ 1I3 +9L'/C 7." ! +7K%" + !cw8hx#Q%+$sMf ax*oKAVHk#?^1 6N!9Fd!!@ >/Q4Wo ZsIhWxq2l2Ri2L7RFdHd{0E^ Yx+6I$:L8O ,@gzC)/New([q & +4I ~7]zd{)azo{N`z+13GNe 0=P%o:&> +Fc'BC^ +^z"Zyr7Iaj2:L 9H Xh%BPw;Zi%Zh(/:U^GBN;LV:cjMFN1U^:^i=N].Xh7_p7_r4dx2r8k{+|-*4 0" '$ze $)w'z#v%iPqe$]~2v!&zpuux!!!  &**/(&ny|~75;26/((36=9=9;8=A:906-6} (&("&308*1(*(0#1&4*/+Wu[}Jh#6,+<, '6(:"  $1 . 2"(*%:2H 4M"!9!80 !20  4M'90" ,8S&&<' " )$9(E* 3$6(( 1% 3&3' $ " *!2'<(>2F^(2/  ) '! %8"90 +1B@X'2LF`$=[ >Y$.A$:;N)' '+:)9!1 / /?+=/E+A1K/F+A 8Ql.ton"lqtoqnv/y&x%1(--x$s z&q q!(x &*)y%*|)s$w)z*z&'{ld}/q"qp#|+z)w){/p&i#fm$j$g$m.b|(^v(Yr#LgFcXs*Yu)Up$YxkHh Vx#Wy$IfQk"_}'H_WpVr_wLcFE] Zr&9P +@VLbav(LdAV:K'<)<1C E^KgPml+s>Pn(3O 5M9Q:U4M3I)= +(?(B0N4O,)@ 7`u4Pi>VPh+B0J.,!;/I)A9$A0H")&='B!>%? +:S(F/+1 ;& "% + *>/H + 9O%1J0H&!. & +%2B!z>  /% ) # +" '7#r/Zr 9(lSopO_$:;[GfTt 0[sD-Dc&7U=[=b FaOkC_3J(B%8 +/D)?0G @[GbMiLf$Zu0h$Kj]x1Re,4G09O.k#Jd (Fr)p]vB\t*5FCQ*oU"2 !.Tj%UrLe"/C. AW +/F Nkf>^yJexHl{KES([k*bo.r8kx8q|G]g6uRxMrCo}Cjw>fw7l|?88648>?<;<6>@2%/56B@A>8:A82;69621,+*2,-(y!~"'#&&'''''**jr'm0'@); :U#8% -(<1I&/%5#6& +$4 # %8*A$9 !.  $3' % &8,2$  &2@) +D5R6 #9';+ +"<7$;';/C*   (5!*4%9*= #5$ +"( +%3#:5M'A;V'+@(6.# +$$ !5M!5L!&(>.D1H. 35T2O$. !8."<7T1M#8 , +D(? 7N$) +q|@jp2  +/C DT(Qb'|Wglcu Yt_@?XLd.C +Aw!k',+E!? $> Hg%6+E +Lf4G ,@%81 1I*C +8LPf(8Q5QZz$No9X &:k!` 3CGz7|2}0P;7z.419912755:& ") '*!"+&y!,("%*,~ } w*|"y z~$(,)%-%{2((2-++#%.'!xo$m!v%~.fq#5*x%,~%)5.6'$1$)'|+e m*j%j(z+/)%,1821(%",()-&=/,-|( +"'00+(,+,'*.)*"%%(,*0*! ".59H1--1+-/+-54,:459/4318244982>9524>@6<1-2237>:=B9@F>A9>><5<-7,8?87;365:4?8739161051332+-4/22)y!%w"(+#%&*.-)k$Zs#IbWx(5$ + +8M&!8)E%<( &:/ ! -#:0-' ,A%, % /63%# + . 9*F)D"<%( 7M+#6)"  %6#% &9P$)C7N'9N'$:$;(>1J 3 * +"1C. *) 4H') +$3, -A46 =S#K_)=S(9 + (>;S $6'!1 + +' !- +# !&"9-6N  /& +2K #52 "/9R#8S)$A2 &%8&"750L4K-  3 +=! ') *A   % ',>1C$4+B,./@&9Q('=#9 ck.6JO"a~7Va :G"DHW2q((|+xrx0"0ex(;OnPf~& +>$\x9n2HcJn,~>&:,/ +;T #4*A&> Zu3i1&?]^zhh5Jb0I 44$65-,++-+0)&)*nv*r#,+;w%~h+2)'*+"&0|,},4;3/)+"+',")&%(I2(-5<01)*+3.(++*68,2747//2&+*66*376<6;746;8,@7<5-$0<@J?42=4/9>.+2710.*)*7'4:1*--/+-/.0.)7.22- ( !!4~%(n7BBb -H"8'<';'<'A2 0- + .@&+=#( ":-*@$11+$ # + +'$   2/G $?)B4+   +)!  & /' '4! "76+E&;, 1E!&8- +1F 3/24G/ +" +,;' +++!/.@/ #7.G9P#$9 Vk74F. $'4E 7F$4 /' ".&6I\#>Zd0Ee]~_eoqy#ni,lnovtqrlkfnv"|.v,q)w*z(qs"t#_m o{%u#u)x.y2n'v2o2n2l2n1k6`z.f}-g/`}*Zz&`|*`|([y%Yw VtRq KeRm"PgTmPj!LfWw/Mok1Ki1Tn7Ik@_ +@[gy1HYdv1Vm#J`2C +Zl.LY# /0B/D e1Ss9PUl [t"\v>Y>W36O[y/!?Fc +D7Q,G 6N"@X/*<  +O\@GZ3*2'B;R"##6'*D5"1 (%;-    +  7N /#1A&9$,8# +% +70H3F+&$4^k/nz./4ip;5="<1-dzmv%gxl|'f{rg#%r]n8Sc 5Smn&FZRe,&;Wwh6of|*q =-&,E +AU 4O*E:ULeh\w"Mci Wv +QmE\gy1E%9 Wl1E\>W =X"F12I ,D ++(C +>U((?%@ .K(E9S#8 +*=:SD^er2fwAlJ]r;aw5cy3c|0^w$Sqi$p$v(1.,*+4040%42($-)'#+'""#$-)#".4#22240+)&*-'<1-2+.+1&"**(#"#&(&|}%+-/;1214=86 ,,$!','*3*,'}$*72&2tx %v,(*$,){"y#*}+5.*~-/-(sy$~0#/?9!)$#&&*1),+-8+*.1' !{//1)/+*06'2-66=:7=8594573/7.9):>:892?672@=<;?9025@21970:;/5018A3*341.62/.2)..+'~%~*)6+6)--*1.<08;//&&~'t#$2,472b^('D %?!:"71 )A/I!1* + +#"4+&>+ ( ')*" 7 2) ,#,9# + ); , 4$ ) ' -& )$6& /C'$7  $ (&0@ #5"6#;3.D #7#3 +. 6&# * +#3),/%=!8#' 01H &2K ;S)!6 +Xq#k*MpQmu*X~ orptvstqrw"s&}'v"z#w"y%|(v ~*w&p$lq#jm"iw,lppmw,w0t0w1p*u7k1h0f-d0\t*_z.Wr&Wr)^|1\x,[v)Ro!Xu"LmKkEaUt)Tn!OmRl[x%Pp"Vv(HiZ}!m1QuDbJeTn"?UBRQh%Rn\u%FTiz2K]+=/?7HKc!Zt%" "0#?\-5 '<4D "4  !2  6)@2P "@8:, ,- 0 {Os~X-;`p2`j"Xc'AD_l&)]xy?%3I9O 5Md#DX8.H5Q&>$7 #6 965N4M?T(< 4;Pm0l~2uAo6u;s5m+z9o)p(x.{,/y($%'(#({##%v'$)$&-%/,+',&$-*2,5.92<9.+'&#'+ 5,+$/!#"%!&#'*(#%'r&!y'z+}!*t"4)+3+(&&3/}&%*~"1',*{('&v/.%~+1*%#%/))&(-[w ,|xy"'.!2'&, #+ (( **3(*%y z".1-"(4+-704254>9931/98;65..;7E9;61:D766204642=6%23843:<*<7042=?>=518'20-1:6,6=;52++*& *1-74E9<5--0?~1~.s $)&20Me\v.Zw!`*=\73 @[+!<!9)C, , -/*0* + ,+ 0K"3N( 4  , %$ +-(,'?%?7(1  #%6 ) + 7O!0%6 .?2G$@5L#&32E+?/H) % ( 2C3')< , 2H!7 %5)9/ "6/*;9M>V"?X" 5*;+>$.(9J)87E#,>8Qh#TrPg0kho}%xxhkt#t w$v&x%{'w"z%u$t flv qnkklkmkfncjm$w,m*o,q.i.d}-b}+d.Ut#St#Tq$Yt*_x4Sn(]x4Uo,To)Nj Po%CbFe$Nk%Ni Y|#i+Vs Wu&Ws)?ZIk<_8WQmJf3N`w/U /E0G 6/D)0 5. - 0"4/6($!  +#-   %.  ; !? #33* $  $C,M1"50*$4$:"5'3&  9M5L#*?#90/ +*F !7R,;T"%]p/}/ix$Vh +~''3BU i~rlSl Wjcyog t3Uk fIcgpXs }$&FEd{x*t6&=^u c~'k+"]z'):w!_yN_*Nf4NVrgLIew-i#Xzmyopq/h'>6&~Pg">1I- &A&D>87O3\s38DXt&w+0~+4(,w$t1| 6}z$${{'|#"%(|*)++#.520-('1)<:61011/%(""!$%'*#)y /(($#%~lpj."}~k})ku+} .mu!qmt2$.-%485<1~%-'''|-0}1.9-(#$,5)5xy7,&#&'t.%&#'}"~&4'-#+!)*,*.*=/#&y- , 0. #/406+-(;55174?:1584/6-505/>13-.:585(368;?/?./{%752:6)<11,136741;27.,8:36w#;9{2:{(* }%-8*57934627|).ts })r{"~~&n{75QStl)<[*B*F4S*E!:6 1' # &9*# #7"9- /$<5O+&@/ "8"6+"6% ! +(& ! +)."9,!/./:V++ , 0%9 ,7"9 75L$< +F+C%@Q(./$*+- "4';%<,1LJf,/) 75N&"4&7?S%E`(>Z 8M / ( &54E $5!2 -=$2DKd!k%3z+w'lebtlmnmr"it#hr#{)up|)X| Zs"mgnq#r#kw%q"n"n#g p&f h$c_$[z"]~'Xy&Ss"Ts'Po#Mm"Ss)Ml&Li%Ni'Lg%Lj(Mj'Rl'Ig"?[Db Tr0Ng(Kh"Mp7V +Ca8VYs(D[Tq&\Om"Qk!To#4LNc%L^"6R+I +.F-B.Aaz-1L` g(Uwb%GjEbOl!;.M&@(@0LB`$.H&<Ha'E +-M'A :S/L <+#9*C-2/ ">4+IB_7M0*; ]s7*"8,#*, ,C& +"  +  +)4#     + 9R,$5*5'". + &" + ) "C. "0$7 &$tD0%5   +-/B   3K" 'B$A#: IT-Xg# 'FU`qN_ h{/MfYs(f~YqTkI_.Eut \q$8La.F[{ l'X|Dd p*BUF\9PAYv$Ul&Uq co~0Vl"KeZvuv*C`>]-G-rw&qu{$~&gQj_{8OBY 6LAY HbWs ]y(Xq.;OdSl- '??/Sm +Pkd}/i',;1F %--+/+1//"))'()#0|.}&0~$.9,4500(/*6/,+/3/4;.~+),+,-~$~  z!'{ vk Mr +v({'m)|}"ynt$y !hmes!jz%qv#-ft)"o'!p010:1vv&({@+1,6.*&$%"#5+%)"!( {y %#*&yz u&"'&/(+'#3+'*)"!y}#$!070+././249>=87359-+2*30+.(*+5/+,++0%2A.6@<(.1;600{.~4m%z8v,8!&#)gv'2-)31-x&|'{'w%~(#}#x"wjnu{!*p^z2b&(F 9 +;Y4Q*D,G!4"0% 3I.#,%  0:V(3N"$@5  +%5!" ( '9&(")-  +  ( 162 'C4 /( &8 + 1#=0KB`'C^j&t+j$s"pnfv%r r oy%qht!x&u"z"|{lp ghet%ac`n!q"l`__h&`!V`'KmJjJmKo Mq!FhNn%A_Rr/Fd"Kh&Mj&Ba@`FeFdEeDb Mj(Ge"F^!JhGi7TLj!Nn!5O:PQk HgMo@]=URk+Tm/J`%); +Mf,@Y40!5 1I +Xo%Uq;[<\3S ?[:W!?Nl3CZ*CY!1H/K/L)@ 0 # = %C;X+H 7&D.J2M)B#( +307T5,.13H$"iqM$6 =)0})45>?/.*,(.m v*{*s /x%y&},0w'|)z"*%v~%x"jamtvxo#dPv3T Ca!4S*C. *C#' $ ) 6+(>,' 8P''>1I"%?  &*  *$ 2E+.   $ "' ! "2 1 / '5! %5( +3 0I1H2!;0J.D(&2/B51 $;"8#8)?B_0G-)A0& "6#5$2ED\+:SQj-!9 4 +A3 +"6 +8N); *:*;Db!i/Zx^|jmcajeinmolmotstrkou!en$^m$p&s+^_[\QvUy]~\}RvJmRtEfKla3GkLnOp!Lf!Pl*Mj'Pm(Nm#Ee Ed CbAaEdA_EaSm)>WMl%Wv3B_FeSsJ[w"MqMr}=DcD\0J6N_t>,> 8L,B1FZ.(Ohd-VtOl;XGbC_3QXv:4Q"7 ,@9M5M'B.E!6 +C;9V ,I/I&B6 ">1MB[, ,, #?2O5R+F 1M+D/_-7 1 ' $ . 1 $: + ./ 7H#K\*(9#)@.  + +   + , +   "24'  "0(=c +FeF[)> 3M0F2L,D# 0- &4 + \fE   '5 % W_=br,Si$>SJaUlOdey[s^y h:u"GTk2/E`|"Ha 1k~$%-@>S5O Lez1K^-D 8%8 / );Nlt<:V\|PpLh2M4OB^, %I_&Oi(^w"Gc=WVh&/I&; 2PPl!< 8f$s'Svy l h%C\ ^z^{Sps#Wsh%Un8^p\p*%&*,'(1#'}"'$)'$&'4-(-(1".#&.(!%'~"#bdfs!~/Rrd s'}%|!$!%*x}"&wlsuqu-$''!z4/t1%%(+8n.-+')&'${//3/66.F3-)78(6.,$&$)"&#' "#"|#"z!z ~%q~#}1|(2)1x-! +''|"x#fw)|)p}%|$4$u.'%'%)&&') ,"(-%/:4'"1*.2-:165$%/&&)2~%?x$A101*/+ v|:+.w%|!%t;2=/87/44.*'0}(*+-,}(&&(*/1v%67z(12z*y)u&leZ 0is'glmci!eimsf`V}MuNs*0L"<' +/'? +  "-*.+ ,2.D$=+@ ) +!/%6  ( %#*)$7" +" ! + % +   !-!--B5$>&A%A: + < "; $:2 1 / *=)@5&@&<)@ 8I+@&:#6-+E.B&:"L`78O 6 '>1+G(B/H :*< "6KCa3O ]6SKk 3M C[ 6V "7/F.$<WzIh**B+0 , +1! *:-J(B9QRj-:X7I!9D#Vm58O %: J_.0F$:+JaA +   (  )4 #8)    Zu"By.Sh6.($@ 15R;[?[ ;%; +`v/`r2v0.IP1%CR'&4IX;AT% +5J_-~:{+Ng-C +D +>TNgNi c~qLg y%"%cp)$(?*9K +2K-VhN_Jc">T;S3(c.7+CKSk&Rf&Kh]v#,K 3Wv Rn#717L&,?Z1K7O>T/I 9TTsa{ l.Yn(CBYPo :VC?2J. a*p{,q#Ic ?[ +Zy3j`yGaX$2Q+J/KKj'[#,F%@"< ;S"2I'?$<4/H"$ +!6 7,F&< $5;Sp; <$A *E"9( &C#>7+/2'90I 6N Ja!@PK_"0CE\#(=*@ )7    +    +  4 - ) + +"Oe2Xys,%+ +Nf323P$?4Q ; +3F8]h+?HS |>.Db(*-Vm Xo!j5I_CY!Hdy)Hhf^wWmb}az:&5 Kch:Pg(]~v9\xLg>YAX5J8 9XKm m.2HPi=V FdC_`#5"C'=(?,B # 2 +Fa"F]>S6K;S-F Qn9>VNcYs-C[Cb#=Ga IgFA\$?UvZ} u,`}%@\B^: 3]z"Zs:T48gy3J\#./(tt{q~~}s)'3(""$'#*#*!uo%%)d'~ v#}#y()$~!('(#z#026%x#+!$~$'8}!%%~"}#$)1%'($/0.,3'))}&,2.)-}'u%k#k&y.c}~1/4+,y ~&&.+z!{}.%~"+ys .n{%&5)|!%0.$2744>6s)05/,*(2)-y$-?(33&8{/207,-@@./2303'(++&50)'/1(-2%01.31.7?5-"z *+93z4{/|)0,,/{'l1)}z w#'%~ ,z"*{%'#'$#y${$z"w*'tu!}*t$q!mx&w'y)x(r!mgs#sps)g$cbfce h$g%ejf `b^d!_eW}Ls[[YWCi+K1R">7*,# +   &  & )>-E%5+= ( +-<!1"% + + !+.      * #422 #=%A* + * / $  " +,,C5P,E%- *@'>&;) ->(?.,C%= )A-FLg24*G$@>]Dd &7 ,=%5 />L`"D^Ss)Wz')KUr24K 5N#/N ?]4N Da3P On'#@ ,J5 +!;6 7,D%@"8 )6 2 +6#" %83( 9. ";'D0'8T4K&;. (1C" Sm$>[* +  +    +&$<) -=' Zs8=) K^\s*l@g*7MSa,Q,F(D|;+Jf$w &wvXb .j|!9B#"5P_=%=h/1 9L)8K ?S m#Fa Ii [z7XKa5Paz&a{ylRjAYLb:Q Jf ?] +m.l$gQb&.B(@$;:RNiKe LgJiQm0b#Y a{+\w>7 +E>V$3K:=S%;8L1>#+AVs5QKd8S 5M 65O-JNo!_~3Lf},PkIfd#34: &C@^MoNjo,n8L# n"bs%{%*'!|~ pl{!x}}"! )$~#! ) #{$$#'{)!t/'28+@,2757*$3-(""x!%)))} %%&/((((($-/(4).~-05/82//(6&&1#u {&.z(0'%}$%(-,-1/!(q!joos$#.n/v/(3.*,'0)),/'2j}1n2,0-(2/21;,192%.:7;<0:7AB87/6.54717540|*|%&")/|#+0-12'-+$4y0+w'v*z1z06,*)~$5~+9//)x&*)xv.*(}#|%%&#".vwuutsuurjs&q%nr"o%jmaad^gkcbj&X~V}\[X~co(j$m%g#^_h d cU|Hm/T;bHqEkCi7`.O$E)F%?3!   ### "2 % +1(?%6#34H''.?" +) 3* +0  + +%9/      + + #1(8, 1 %?"<(E!=/%7#7$"  +:)D"7 0%;3 -/ + 3-C'=#=0K,F7O ;W 7U@^ 8Y<\>]@_:W*I &A +Ol-HgEhDiJqPzUSwX{W|VWWh!ac a\hi!Rr_ZVzSwf`\g!aj&Wzd&af&]~"Vw_%a#p0h)\|#^(QoWu'Wv'PpXz#RrXs"JfIdOj"Ro#PlUpVpa{(XqVq#HgSnTp MlKmHip1d#6SLgLd";U>W@Y@U9O@V4F55)<-AE^D`.M-L .H , %B 7X @_7N#.I4R!= +)G%@"<,+ +1 )>) + )<$;/G0) +,405- *G["I_Rnc+;S4J#@5Q& 7*3 @[!B[3N'= . &*8Pe3-/KI`: ! +   + ";2  CN,h7|8%1 Mb"z,7Hpo(>I(n7 +BEL%)k|4ey1k} SgDY4H\{*-\r.CTh$AZNi!F[I`8RB_ +>Y>TAUAVIbOk|-;SF^~<-B ,B2H1E Xl,m34JNc*h{Chx@5H 9MOm @[5V?V#f.X|Vti+ ;/J4M+@  .27P+Eb@\0J +Fa/L`y4-E+C 1ONj)PnG` 2@[ay*DTu(j1_{)g%Wq:U !9/ )D + % !6'>?W#>W#.,@"!9,G70I1G#<5 -C*A(>@W),D%@+D1L9V?]$4Q1N>["1P8Z!Ee%7V:X:X<\9Y<\Im%>gBlDpOu!W|!\P{Q|UT{a%]!Y{c'SvcWy]Wy\!Pri%`V{k%X{a]~!Tz_Vxb$a"h)Txb%Zz RtQl[w&KiUw$MoRwJnOoOjJfEbOl\z Ww]{ MkUq KiFiNkPpMmSt$KlQpFaIbBZJg Mi?\F`F\0E ;O+= 9J &7& '-HUv!>\%&A+>Y'5U1N@b6S +C:O2 -F/H1D_*"= !<">/L!6 +  / & +!6-F1 6Q%/-G; Lk,M3L B[:W De8S./G9PPo5  4 +7R!/L5D # + &97I#0%7" ,$# + (   %B 0-3%ER$Xb@ q9:p1}",{6)i|3OZ(ev%MXx l}(s$r%vwKlli l!ZyVrWnJcG]Zq+Uv dZtRg&/..3E Pg"9"8:[)5DbNnHh'0%;/  + -:TGePo'64S-G 1K@X)=I`&1I2I ">U 9+D.E7MQj$"?6JdE] 6N /H +Mhi)>T 3,-Ch=\^}p!eo!w'o|$x#r!o)y"z!uo{{to#pug$#%y!uv"x))-)%!i|(y(+m"IlUw#j1j'5~(| (%v03-).,)##1&++ -y| -.(#~~$&*}(y-j k.r}$s ks *:960$2$60-11/| &ty)) !}*%+(.,50-+-(-~4w(&%&',5')%%*2-2.'141-)41742.v$|**(&%,8*'+*5)&5(R-(}$y&2.0(!'(~,'&+%.&'0|&1w (uy"&*'()~!qv r"ku$ktqns#~{w {'x$vz qnr%gj$]pghbgYgjlk]f!m#ip$kdTzO|OzO|UTSJsLrXQwOxGpFnIqCjMq;` Gj9\ 4X <^0P)K*L'B!=4%?'A 83$7,$ & *' 3.);.+8% ' +#- " +"30&;+,  +   + )%;5$:(=/'?%5$7 (5H.B.E +8P1I4J6M2 +5K=T?T6Ul='>-F(D!9 4K3K,B,F/L+G)E/K)F.J =!? +=[!3O0M8Y+J1PDh%Ek"Go!EnNu%S{$LtX!W T{ZTwYz_"Y|^m&m"o'in*[~k+i)^#i$e!a!d'X}]o'p(g!h&[b$Sw\"_}![x!PqMpSvGlUw#LkRlRm!Rn^|'UrMoFhf.Pic.LpQrNpOrPrNp[!SsUn)Tn%2Q Ik>\-G*E 1F*<8K'8$6 7O:VMo. $Yy@Mm*Pp(8-K Jf"9(  7N0K,F&A,F)D.M-L:W%6N!3I,F5, * #$@+G Jh43Q:R/1KD_ ">Ec_w3!6*4  +" 2@)  Fc-;W*;  + +     +  + + - 0J0zPT)3]n5^s64OsTr h +uCZl +p#u&AQs%lplhKjb}Jf.Qg+;U Ld,GNlw*Uq>V #0Td,:@X&;'#;U:R +H_"&<7N|00{&Je/EBV!%5 #3 +&9 ":N/C'?":v3}C6Q +:U +{?+': $:P!0G+A1J@W6 .D1-GA[A\C_Mc%FZ 3K 'B %> 6%; . : 'B So]t% #Sl3Vp"t'~!y"%))''v!{)q#lquy }#{"z!%|qy#y!%}%wc ir%u{&x(>Ay+)vx'u(m)|)q.|@m.|40w'1))%01+)&%"%/&0',+'#&z/ux y~}z&*'z'p"z(x!r2w)ft$|);//,}#*)~%'#%8(~$,lx"m)*(%{2''-52/8))v/~!)-,))y(x!%)1'(*41:,/11*+(6520w&q{'!~"{ {)+7'$""}7,L~#uxz)*$&#}||!vx%!$y|}t+t{s**o}'qx,oot,tsumi~ !}$z(y$qrphg_p#qqu cfbc^h"beeaabTxh CnJtJrIsBj:f>hBkHoCi:a9_3Z7^3X/N+J+K=3 =&B0L#!83-/  ((  + "--" -! +%!.  +,'%%9#" ' '      #310D."41F!* 12$8%#79J!(9 $5 *83E&9 ,0E&9+;/CI`YoLc 4N0H'@ $> 6 +#9 $: =S"'?-H&C&@$=+E3 4 #>-I-H(D4S3T,K;\ Be!Fk"Kn#Jo$Mr#V|$IqSx^$UzLqg*UzPuY~Z}dccd t3a b&X!\!\ a%UzT|X|cddc#^X}U{SwVx[~Tu[~"Y{FhPn#Ni\u[r$Xr&QkSn KjNmd.;UTrWy!Wz!QrOsQt^o1@b0M2NPk#C`2M +3K9S.D!1 2CBW%Ld+Yw1DdC`Us43Fc*1N2P0O 4Q Ef9T 5N8U%> +/H";7 ("9 )"<2N0L5O 0.A 53 @Z0)E +2H +C!8 -D*EcOk0 +   '@Gc + +   % *7@!    $1-(^z3=~K$+^p#f{ f%i Jeu$#Upgt n~u[qE_\ylOj e}$3M3O (B9R9R=WXzNlPm6BYz!Uh,9S3M H_; 2-B 4"=~0bx\qKcEY' ,%*"6Se8 +RgFa 7QQh-CWn#Mc,'<?Y 71.J 6K%)*C+ +->,=(>.L.L":J^Qh'*BQiQf||u'v riW h^ jemf^ }!{#}uu "&%*&#!xu|!w{n}*}6o4j.w0/~+~*0,3|(z.Gs380./(*-*- .'-$!0+2-4/qsnf+kb&*|"{"20+#|/o2&.-339?0.&w{)u!1&)+".}'v+~!!2-x })},5kt"t#")y&)|#+} ,"wrm,&*(+2+,2')*--*#{#}+{,+{$uvotvy~%%}!/(zsy(##! !($"*!%| }{y~#&|yn ws("z|}s,"zx*!+r{&|&y"tx"+(z ydbcce_ mru!t"igfbf[^Z^V^"VXV~RzW}Ks9a =e9^/N7X5X 6X (6 -9 +?J LZ09K.? +:IFU8K -B)> 3!7 0 9 !8.2!82 + =42 $; < "@ > != ,H&C +-N%I )I6U1Q<`Ml%JkOp%Rv#Uz$Tx _$Z"W{`#Wx^$c'g+^o+^X{\}[|`$S|W~ Vxg.W|^#Y_#a}a ^~Z}a NqXy]{^~e$ZvZ{IfUp"Tme~%\u"Zq%JaWn%JfMiXv"Xs"NkKlGiEeEhGie)?e +InVz Ea7S =['@(? 5**7F .>>UDd@`1O!: +,K?_'DDa FbDaDd Cb0F ET;3LWp51K 4P*F-I!:&:8) 01 +2P,G5S5S+E, &>'B7 4 0  9M/  5L$A $<DaWre|CX *(@ `{ f7JBYTCY0J 2"9#1 +)<-;"1 0 0,B,>2 7 +2KJd&IdRo#b{6_:]7[5Y3W8X3Q,<-=4E%5- / + ,*!5. +8 7 #;!;:3S @ .K0N.M8Y0S8] 6W<[A` DbPp"LlSt$OrOqVyWxSqb"_} \{!]}&^{#h,Zzc%QmQlm3Qm`'Vv[y _z&PpLnUx`}&c~(a%[|\zRtTtYvUxUxZ{UqZwVre~'k([y_| _{%b~,_w*Pl"Wx"Tt[wZxNqUxMql/KpLnSuNsIk)F Pm# ,B$; (>0.6E6OMm&0O)F 1Hg%5O6Q 5P- /(>(E #A?C^[/D#!Tf%Ui*=oJ- +bz5 ! I^']x(A&?6 "ET +Oj:08Sb{&.E6NFb+C7N2I/FD]$/9.J l8- "2 41L:Q7J%< 6L0Rh !~"""|vv#v 'y&y){!"#r{ mt)&,2/xstz$rx~%{*{./0--1=5|"(~%&w#u&m'r%ss-4s0{$y&y w{ '.y,~$,~',+w%q -(u{#v#,&})}+-({"(,/y'.s$jx/k t"u4t,nu#{&*.z*,+$~).4.00)104~)20w$|%2.+&$($**x+.!-+&%~%{+}3hdp#r"k!l&o#3+*{+|,62z)1w$0'w..v&act"ruy(|x~!zj xppk{ goz!^ i`m l~s~!~$w|tmhsupnrwrrngw qp{vrq ce!Y_^]] X` `!S}]KyN{TWQOO~JuNz=g+W F'M 2Z0Y V)@$:%:%@/K5S.JE\*B '@ 84P%;+3 Ma:!31B *#96 1 ,C- +D2/ +/4) +% +BX)8T4Q1P1O!38 +*B;V!$<$'6 )$<2I&     wA;EE& +' (*%VmxBUg8Vt!UoYsg~(Z| `pdyF^ Jbw"dx ]vVj Oe H[9NMd`q!IWhx"m#b}[t6L *BPkVsRn4T)IVp.%$ / +&+&&8/H"5 "5/&? ]|"Hd-H 5N&7ax,o/'', %6He Qq#>W)  + &:2I (B`y7I\Xf%2E $<JbF_ G^4L7OHd17MOfMb$ '0!*G 6%3$ ! +/"; /F8M7G%$(0 %8 .=,>1F [q!iz7!1ugm}#%(}"%u$~({)v${)+~!)}} }'*&%~'+''pu2y+-~&213v!502.{(11|&{&~&:2y ).v!4$,+4t!'2#*&(y$w&r,{%|+u$1nu }$v%|230.(~,.).|*{+{+/},v!3'&$()2056-./+v#}%$,|"$-$ './30610,'y#%0.*0'$0%&.),n"j r*{2q$k!u/q*07F06@{3=?4|,x'q(p&{+z+cj%t(`kt#v$opqtp| joy"niy#kj{ v"y#wyughrgd\_^gu!mmji othnhll^_P{ [TWW}!EpElLrJq@bKodGn"8[>`2V:](I 0O&C 3N:V#1N.O3T,J/L%D)F:1 +/ 481. #"3*! '5-@#1+ &5 .%  +        +    + + +    #88()/)0273;19-8 3=4>(4'64D+=$3". %3#. !0/!0 "32 %=5 2 62/- 1 *(*>7-K-K,G 9U8R(B;3P-K Om(FbQp&Tu#Rtc*Uy e.l3b}+g/f+l0e&b#]|&Ww&Xv&Zx)]z'[y a'^~"TwSx!Z~d-Zz%Vv!_'[XUxTu_'Zy"b~,LhYxNnOoRt`f"i'\~X}IoKqVz#V}f(^ QxT}RyV|U|KpGmQuJlEfQr#+H>U7O)@!: "7  3E!Ic&+C2J +C^&E 7X2K!5 +2'F 4V.P?a A:X4Q1F+ " 2-AF^&=[",J*E+/K&B*F#=& 66 ((C*B" +>R)Vs+ C[/AT($3 $:*E .L9Z8W:!:4 "5  /E"<5Q+F8=V/  XHB +;2Ni]>  -%JH-I +DbC^ !='83E1C'9->S%98V9V'F'A&B3 +' 0G0$>Y2!8 &:Id8Y9S4Q7P6KH_(7Q:U>]>]/N'C"90  $8(1P8+=":O!) *,>PBTd|nr*pjN+){Q!#bp+-IIFeMq Ad +Rwi#c@h7aFh@b>_HgAaCc2RZ#2O=Y"'A-G1N#? *F 9"9'A#>&? +#., ++?$#3#2&* # !  + $  +   +     && "%' ( $ $)+/'0&/)2"-/8", )3"*+1.4/76B3=:D2<5?2:7?6@#0)8 +*:,<*= Kd&Vr,Qn%Kf!Qp(Tq$^z*]s"bz%k0i0]x%QnSu$Cc[+Tza%b-_+Xz![~"]$Z{e)MrQxRxQuZ|!Uw^(OsU|NwU~HpElJnLuU|V|`#V| \(Xz"^&Ol[x%VsZw]z_Y~b&Y}PvQxLuIuS Y ^"V{^!_ XU}S}SLw T~NsEid,1S?Z,I&?E^&/G3J5 +!3) 1KUp/5P /I+C9RA] +"0)A5L 2 Jh#%= %0M3R,F  )9R"!96O"@ ]{<;U!8 #?+G= . +3+G9Q(,    6F)_v//Sq*( #8Zu$D`:T D]5M!9 +/I&A.9P"!:.G(. 5 4P#UeK5A2-|!"Qe?CB(gh.s dl)--9ENY"awsAl~(N_)B\8U0L3Q 5N Vt!Baip`ze(6R:QQh;S/I0+?5PTu%=2 004.'C&?T,AG^B )dTn k#[hkt){=Yb*_l&6 ;P1>.wMEjtC,-#; +C\$'84I >X)A*D 65Wl(Vf.-)ED]-2E* - $.2 +., + & +#!+ &%*K])! $3)   +Vq6{"},w xz#x"w}z| y {$zwqt|(ok{%}%u"z$%%&}"p!gt+o%v'r.h)e$p)iy*jp{&qmiu+n'_dq){+s ~!}~} ut$5{x#(vzu$6b{6**|,().+}#,---.0(-1.5($'%|!+"().~'zt-"0(z&%|!iz-z2t.y,oz"x|~!p!wz(s#,x $&z$}&j*y|$-*x*)&{#{&r :.,@s63-4I028y!x$r"x${+r$,t#o|+1)nx#(uyvogpqmtpl_ pfdb[_OvKrKt?jJtDk[+>g QxT~M{HwIx g%XbOh&[`#[]l(Xbf!^cY_i-`"Z UIpDiKnKlOsCh PvT~Ux"7Y +Rw%6\ Or<_InEiHp_.Pt"JjKn"Nq"9[ CcNq%Dg.R >`:[Po$CbBbEe>]Hf"Hd Li(>[Pm*>^;XEb%So20N3Q3P3O5N!< )D#?+F!;/I%?-* +$2 $"   +)!& ' + &*# !#%)"&,1,0(.$+6?.6-4293:/6$, .9&3 '1 %0 ++6 5@4?8?6>8@>HBO;D=EAIIR OT jn5]f&mu6\j(Xl*i1ID^&%B 6O=V5Q,J =\!2L#;X)D *A1 4P# (=%=:3P"*(A"<-E  2"    22G/D1Jd!SrB\?ZB[?U"64?]7V *'E*C8Q%+ + !1 .4,lC~) +#u+{"'n{p{!`mm/qe q|k,e@@[.E !4#;GdooVr>ZPl()=5M 2K C^B].s;Wo!bz.,,@$2": != +;b|(w2^r.*@?Vm&6LB]4O+F0ix 8+4u2, 7PC_'4C Vn%Kb;B%&  ##>Z*F6.%Jd)4F!'  (Tn2%>*B'> 2 $' +# 2 ! +  +## $0 +% .+  '3!  )<x"-++nlmnx!w!pdj"vrz"y"~'-}&$($}"($&!z!hw+w,l 5t+_y2w,9y).x$pt!mf4[zs(^fcc]yt| wzu&zjfx"}!y#"|+#+,+-|#0(+.~$.x!'v, $$/#&%! ,*-$'w%s s$$z%|$*w%p%>g&;w&-zvyuvy ovw{)u&-')pz(tpn()')'~ ##|#}!-}&r%} 4,(&+,((~*(,u#)uu%*~#z$zz!nux{"momhlifhkj ^_c__"c(IoRy AiHrIrY"a%MtNwJxVL} [_f#T W]i'k$]_bc^Z`f"YR~KwX!W{%PtX~PwOvFoHsIvMuRxTO{\#RrWw%\~)JqLpUr%^{4Mn!?aPs"_1CaMl Ml#EfEeFeQq$@aEeCeRt%[z-Jib6K:HHQ EQ?M&B%B!>)?+C1K:8=Z-G &B :(D8 6 4 +3!3 6NIa()"7 1L.E%>)C# 41 '8$@ &=)?T3O_(A_"<2L*A)CX#!5=X"1R,K"9*7%&!7( +    !-6Kq},+|.B.{-z~fv `sn _uay gA^2fo.Jd~(Tr |5Jg!:$A3R +&A+@ 6 + 9%> ,D/AYg{#r%gx,ZkzGBXDe7Jd4tcs&CK/<.Sfo{&n}" !5=OX/\p,viz@0DQjAV*? + + 2 2O/J'" E\ , " +/cy9/M .M:V2PUq'n)E6(A(C;V%"=";,C7 5R!,8-J'H9\ (C50/ 20&54#;8S&"<#>$:"8*A.G6N.J,K4O9S+(. $!; 7"8 +6LZxWs!'@?W"?>[Tq ^~Dc*A -D0 @O9 LQdy(j\sn"0/*u6[k1xxc|Kh x*DdKm MnJg?YMj+DD^C^8 sB/+ 6'A *=P.5J kJ.?aoVgco)+9:G\Wp "uk}y&o{*$)w'w8YgWj\qNcn$/:Xe:P]-'<7HF1D.D!8 +'%  AU+4O'C ) BX*(6D!%"1Si3C^/(@$=#=(A!< +2 %; +"    +o-q6m"n$z13n{--+=,)x'v&|,x).2{$%%&-*} &|!'!v ~.u$0oy&))(}&x$v$ur m!k*t'yoj!pb kvos"nk|#w}#y|%}m}#{|iluttx"lZmQyo su{"&2~{""y$&0((%z%1|&,%u)!}!)({ |(u#l'~(w |#~"' %))3#|!z#rz%p"j fmtqvtx$z%|*y {*-/r#y.t%|+qr#ou!},sr##zxp/~-w#i] lV~XeP|hbj o'kggcgm$jbg chim!gjhs%m!n"gp(o$ccbl$ag'] ^^Nz g#i+_#XYa#\_&W{_ XPvKvTHv Er +MvSyW KwL{AlZ*V}%KuOrLqW&OwPyGqX%Qy])a.R}%U~%Z*S%W#KzMwS!JrPvJrGoIqQt"Os=h?fLqGmGm;_Iq!3\ 6W?bEj"Cl?j:b@aIm,;^ 1RBg)Dh&5V;Y1R6U.PAh =b#*O2Y;e@h$E < > $E)J'D )B5R<^Bf$=b3W,M6U 5S0N:Y<[Cf!Lm$Ns$7\8\1SGg GgQq"Uv&Oo!Nm!^0DiRx["W|\{'Xt#`|,[w$Urd-^|)f1k7Tu"SsQpX|!X|!`&c&YwZz[y e(j-QtImHmHiJnMtGnOyMq;] Lkd,Y}VzMs SyGn LuKuJtKrFjDg\\ZSXR|\%EiIjSqRqNq]_!g-Z|h*f+d/c.h2Z|#Uye'R}U|[`^RvJkWyWtEeGjHmb&^%SxVyMpUx^Z}Pr2N +4K$< #;*: +0L1O +5B`$'(ESq*(A,I>Z$+ @T9+B!1 $"? = 1,G "+;%1-3 . ) + % '8( !&A*F,/\u"e{5  8*@:UFf^|5=W=W +Oi1 BP6 Tc%Wj: '9Mk@/H&B .Lbe LrJuGqZ'Kp7]?hBl?f;bPv)El4Z9`Ho GkWx*0T 6\d=_?d<]Cc@aGfOp">\Hf=] Ns=cSx&4[ FjUx#RtPrXz$JmOqW}"`"j%aXzo8d-Xs g.W{!b)^%d)Zy"Wx!Km_#HoMr]`!SxZzk*]}MoBfImPuLtMtRvMuPwNoJjBdLmWz Z"["Y$@hNuKrAiMtFlSxZMtYFn Fl OsGgX}\^[`Z~`$g0a%h/Uy_'VzZ}u:XP{[l%SxQr^~%NnTvYu[{TyW}b\"KnTvLmUt\zYwFi'C/J&B ,I33 *@ '> #9C\0JJeF_&'4Q9Q!5Mb!dq)!6 +Un#D[ L_ Lc XlK`i{zw&u.o@Liy%Xp Tl{+l$`wF`G`@Y u'y#?RDTLdH^ ,/D#:$392J*G Kc-Mg5L-'918PTsQe.Ha'5O 4M+A %>9P7Q ;R%>8 7)A 0N/H!?0';1J,)-  & #4    !hhy'q"~**}'t ~)ty!.~#w~(q"m q#~*|{!#& !$),!#&#'"$ !%+&ts"z+~ua +j u$xrv#}%tv!w$|&{%ijjb21o(/x#swz%oj`v*m /w"sqz%*~""k+qopy#ort*&v ~&# "{ l{"~{zqjpy ~ !y{}v!#%! 'v!~~xxsywjt vuw~s #rxm}(og|*fs%ggr&k#g#h&abp%e{&hkkgjv%l"o"lgrm[TVNwb^XXVb]ZX__\#Z ]e"d w/n(m+m'h#c p.b_Xn+a"[\_#g*\!X^$a _ Z\U{\$Kr[X{n4Ika!PwCk +f._$`&T}T[e'S`+Z(]*\,W'Lud6W~*V|&Z~(OuUz!b'V|b(S{\*Pt[~Quf/CiGoOw!CjPq Zw)]}+^-Tw!Z[#^+Y~+LvAl ^$W| HpKsPy"@eEm=hFmV|&FrBnHrBi 2XPz N|FtKyPw!FjJmMsFlIoDlDm=bSu]!X|$CjNsKpNt=cRwRvQuWz]*Fbh5T|FnW}#Rx!PtNqTvb"b#g*v9U{e'_p*aWz[|$a(s=_%d(i-`!Z~X~Ow]QyV|c\Rv[} ]~]Y} BfDgGpOuFk\~+HkLpW|!KlUx%[{#EeQtBcGjCfBf:^ GmV|$DkGjKnCf MlJlInKoBe +T{^ZZX~h%_ _$_%[!YV`#OtSy@kR}R|V~\cg)Rs`faTzLtY}W|UwLoQo[x]y$IdSs@]/L/L,E#; 0F0 1. +*"&B  + +' )  +* ");+AKW?HSWH}:  +!.# +Q\++ +4A! $ %4?MSU&;K(". ,G-L'?(.B $  ++ gw=n)=Zz6c|!J`PbVjXkCV FX\iXjh!e}cwZms%UlIZ1D(:Up#@V 5J 8Rl:IZ*<DWat>O w*e}_v#3EZr[l0[j+0A+<2C!6C(+<! 0?+(?&=/J0G1 ++B +:M& 0F4J#;9P7O _t2*A "72 Po15 *D08R..J,D5-G C['/  *' ! +  3 ' tx%+j{*w%})t s no+~)y&(~(~%{#.&z*}s~~ '}"*)&!$ztz%z$tz"z }${"jk&zxy#oz *'(w-u!y#x!pt |&j,+*v,('|!meit$r&m{(q!2-,*$}wnlqwq&'u!.1,r)tsxt~'q+x{r~#ru jan|!o |% (0}}~(} yw~ {$b unpqn u"w#uut"vx| y0%|x&*{&u#^gr&j_n"ejgiklp!ih(r)nhe^ee#k.Wh%ac!ae$g&cb _[f)i.i/`![Zh`}d&e(]"a&d-b#X~n1l-e$Vc'UzOxVO|UZ`ZX]#\'Pwad%l-e$f&T~\~"b*a(]#^&`$f%` c)g2Sz!k4b+U"My HnVx Ks]~-Onl7d,Wy X{W{!Uy!i5\$Pu`(V}!Z'QxHoUw$a+Vz#MqFiHl Vy^*Fi@fR{IrS|OwPxd/Z(FrMzHo`'[%CqLxKug*^ SW?j +`-Yz%Uye-Ov>fJs?gQs[z!f&IjGhGhV|PvGiU|"Rt]]]]~'MpFkLo?b Nr^ k-Puk%bX{^|Vxy9f%f ZW|Ux#Xy(]~+Xz^^\_QyNvMsT|W\Tyc&YxZ~\d!XzFhW$NrLoZ~&Y|$Y|\ NpTx`&Noc-JkPsLpEgPtUv#CeUx"[{&[{&JnXv"Cc a$]Z~[a"c$] f'`"\VyGm +Hm +MvEp PvLp [d&P{Yp.g"a SvZcjgk!bfdn%Z}Zy_~!XzGgKhIa,H6M+@(>"3 +!1 , 5 8 ":+B5 & < 'B+D4S!5% ( + + . + +  +%) (/H +%7u~?-7s$?4869".&@R IV#DR"IR$+$ &JM*8zo7  +,A :$? ,G)@%   + #5A,TDP[i*C/FC\4?\ KgXp'OeCS@R GZ&66G=QObVjEYl'[sJb +Qd?SG]Smm-Vo+DC] 12)B <[Sr4QFa,D5FRfty3-9gt"s8Jbl4I_/D'> /   1J+D8A3<]n!bz<;eo. Ub6;K(bp9 emIUB[ 9T +Nk#DZ7P,D14Ki#Lj9P6K7M5Q=S"9IHV l}%g{bv Wd6A1ct)*E^

%ETjx>%iz$ dl0kq0055hq0>H"CPhz$b~p|"4:!3;S]<@wv$dq%"!6 *, +  * ! * $ _rBToeje26P7S '?Wm,-B =S]r!K_H]';$76G;M=Q=S6OE\7M F]4M1-=T6M4J. < ;2 * +'= *?k3Nd Ian y/HZ^m7_uIa`vG[+; ET"*:TjWjFY .= .CVe|&F7F3EO[/Pf B_J_,5 7M"+ +2K$*K7 2N=[TnZr~'Vg +-+"-#' 20G&= + +  ##  !10'?Q&%= "9/01   *;/,C .xy$.|(*y"t"s&s&v*u)y-p"r#w'n"q|"|sv wueft y!"(#}~"vuim.y} %y}"%y|vop~&y uv}#|~py%y!y"+v ,q-wr|!|%{%~'hc lry!ns~'v#o },}+rx's$|(.y&|))7s".--z"x|!y x.|$z$y%}-v+a~ao)d#d#q%^}fm!YaVy(j|'x$fm$lnuq##w~&'|)2(2|##z!,z#rs$}%#$s*sspnvkfhy(m#j$f#g$ZZc%\]b!UT~Ov]$KzGp^#Ux]$]%LwMti&\_l(n'j!dl)_Oux;X}t2W{`cg'c'RuIq[ ] n1ZY\h!Y]Xf"h"h"[|b"PuOvZ~W{Pv`Jq [\WR|Z!SyYf'g(g']`PwZ`'QuZzY|Uwm4l3_&W{f%UzZ\Mtd,Xt$i4i2_}&i.\}RtW|PxS}XTzY~ SwUxDoZYQy f#QzJu ] b%` m,HXv]%Roy7f#f(]!Z}e&n-f&WvSpWq?_h&[|Dg +=aY|!Pmp+h%q0d&n,e n)k"it+l x,t)Sz k&W{`h aejccV}cW ci_fgc]l&Z}f ee#d f YaibTz +ZV}_V|EgJpUxi&Ww`UzZ`ZQzOxLtV~S|RyV{TxNuTyQ{PuTwQvSvSr]{$KgEfKl[|i&VwQu NuY~X~NwYU|k'b"a&ckibYaf] gmqpg]b[idepknj` +c`W|TyRwY}Fj Di Mp+I3E$6 );1C2. ( CX/+F=Y"F`*6T-N=]+E,66S#*D0M"/M%C/L,EA^.)A +% +7Q!$ /C)A +2G qa{ 8Xog} y +xcl+ls658@HW.CU9N=V +x%tw(Xb/4B vo:mz4:F#$1"47O$!" .$02+ Z\i:E[3L=Y6S ,C|7Nd-B:N-H7PBU/AF[BS7H/> Me".F 4KRf):Q':"6 ,C >X5"5 , 3,2% +( & ( CY OfCY VkVj cs,@Rt/%9=Q]s+BS3?I[LeZqaw(8LEZPe3C )70?)< 33M @V7L'"*?(?.!? A`:WA[AXx1Wr!(   #1% &."-7&    ! %,A) 2 8J"(9)@,E  +  "  5H3L6g+k#:}/ -' %K[D[2H +E]]wkv#gs DR;PHa +>|@6A%6I /%A9W'(  &- 'BOJY*> 54NjXo MfaxWlMf6P 8O 0G9M5I ,= +&6&7 +)E -E 3#9 (?;I0,,4N&; - 57T"#<   $3FT.#2 =T1H D`8L %5"4Mkt>;O:O -A**1ELaXo(VmI\ ;JRb)"3 &;8Q3K.BZ$`{#Kd"5$*$,6#9 &A +$ALji,*= '5*!.- + # !   + +  ' ,5)0 + 2.B 7 +(@;QO]0 '>-2I'8-=#s,+y'1po"rijnmdfr"a|ky|('}!xsw!|'ihv)$,${x(y ~ | {{ w-! rw%|#x"((.{!#x1)|)|(o|$ng|&upo~.mx&~(r'+)10*$z"{ ${ |*z }(01v%~,w(w',|'psx#q!p})t!uxvx'{ ! "} $~(5u(,wxz#|#%r!{ y&rvx!ssihlqkkv!fljmdfhin#de"|5`j$X|0l"o+Zab#_ b!fdZLzZRyWUX[X\h$_t1s0k(]Ze!j"_\]!Y|e(g%c#e"g"\j#q'n$dgr&k$cj%bm&k,l+]m%o#[Zj*n.e&WY\~n*j(k,]X}e(_!c!VW}SxWT~LuNwS{M{[\p5f&ccbSyU|U}\#]$d*l3s:Usa"Ut^&Z~ SuOor8XyZal%o+m+\~|3v0s3x6j)h!;v+q0e&u5bo(i$|0|/`|9Noe"p'ck#l"p%[zb^`o*s(o$p#lk"q-m$p!jt!mggsu!gx.j"ju$ihiao%_Rwhqi^jjmdi\o&fb\djd\kbk!\W{_PuV{ddaija^Zm#fc\]]^UR{Q{XMwQwUwIm +h#jq#ns"c|-oqme`\ehlfu!fqlX\Z \ T} V|]Sx Y~ ]gig\ ZKrPw JxS V X ]WRzNsUw HlHlCfEc5M0 $6- +$4 Oc0' 5,E-G:U6Q 3T 3P.HNi59X*I+(?(*F%A2N"& +     +4D$>7 +C^Xto)bt"]wQhq}Ui`uUr %#| HT 1%;CZXpmOb31E!>WW` BO# *A!; ?Ln.' ! 6D#dm9Q]$$;/GKc^v%QfJ^0>M_BXMd9N>Q6H0.?*?29T!'B 2N*F+?' /,)8R3L 5 %= )50 (   Dy55$ , "5%$/,@.? nu$~%x%s%_ynmmnngx%ns!t szv'zy't'{"$~"|(uru{y}vu|xt|{u*kqsemz&~&)is"*40y$u"x&ly"uthlky*t u".ry{!z'w'}*y %mw|%qx"*~"v o u&kp"w*z+*.ufpr#ow$|'~#{%&s#!{{urx vu{+gy"mlmorww t!tz kikcgn}(igr#z*w&n#w+o v)2fs+b8h$p*y6m)g'c#n.U~k(fn#aiZ\g!]`eas*[\`i,b%q4`"aj&T{c(Sya$j+f%g*Uyae!n*d^p.Wx6dz2`}m*n*q4f*Zcgdp(k&p.e#g#r,k$c l+q1n-s2_[QvW~d'z>e-a&Tz\$e(V~b"s/e%Wyav0bPuCd @^ +?]`w'v<[s#Uqm0l,\v$Vrk.g+m/q1]} W|q&0ci ]}k z0f"h~#z7`~l(fs+Xym*do'q0u.{2n'w-n)q*u(e|.ow$lfgn'r)q#q$}.n"kk#k#fjcUz e`nv"ao(k$ihUx e[ Y W \U}j!f^ feY kaff^i%dg"[cea`\k!ikp#Pv Pul&_cdb[]U^`V~ k!U~U~_TzRxJrLtPxOwl#gfS _hjigmfkhcoaY _Ntqr Tzagl[cW}W|a_g`ngZYIq ;cR|Mz +Ny Mu +IpY~PwR{]$OpRvRyIoKo2N35PDbJf%5%<7O$A < +]$> 1J.22* ,I,H#A &!1  "(0?05P&>.BNh2Mm*bVq x-<[=^wwp!:5N1hRd)8-B Qj   '3%3CQ6+6  !  !- +$2@#+ +  & " 1#>3 #6--Cmm,x.t(6d{#oxrv")y qz ~'~%y &&!,$&" y~|q|u"zv'zvzsy|~wxlurep~%|"w#*z!)(ysw!onoqegbx&x$l))s,z){'{"tw qxz$nw"qrz%beil n p'{ z%fu!pqx&tyuyt}#uo!p#} n~%'ps~%xvk movs~&x j'v"r$q"e/q!x#l[ m|*nir%o |+mZm&s+v.x3][o*`e"e!dbU{ gk\ fnflhbaad[_^_v9h)j)a~ Y}Sxj$k(z7j(l+m-f'h)d$r,l)m-|2G$6$8 ' 5I8M3H4A , ! +$4!6H` 5( - @U0:T?W$ '5#" ++< +():",. +A  +U&~'=p{v)AZ + + 2Ig+rn~-PZ>]c3;8= wA  #8#5%:&#$H[,1C^s(>OJ_J^;P :O-A'8!/"1 *'8 1D16 2$= $@*F5 & $1 8(DDa-B +  !$B5R2M,2J9 3 !7 4Lj%'C\=[$B +;Y*BYtnw2Ohbu+$5*-B 7IB["6G)1)G[/D+";I*as1G!4+  # ! 5 = )? 0>8O5N'   .& #&>#<    &2%9$< >X'       + 4"549 @^&  ]yy3nu#x)w'/00rdv!i/z%,{%}%$|$!yvxzpwslfjneh_ gjtfy"tu{'W| gZ{t&{!zzyk0iquylt pvso|#m}(z!x~!zww$o~,w*p"4qp#s!w%pu ursjtt-ls%cir%t'x*n0z!{%pu${z|itmrs{qpx#ltwj(rv"(enx$o1t+Cjr pqlkt%qku'p$ouy%ai!ds&hi^dax)s&k"i"l#`KuT{a_X\dd}9o'x(p#]SZXf$\YWYo.`x4`q+f!c`c"d%]~X~m%g!`~}<^u!v;l0q/y5Cv2s+i"u-t,n(y3u0v0p(t.i"u2^v\{X{m.p.]zf%b h$n'q)h#t1Bbz"z>D $E*F;Tp$E`2LDb G` =V^wm++BRgs/`~(Wa6-Bqk}Rg!p<,5 /EWvn(/:.1%Vf28Fu9}2[g+6M4 2 +! &8,8#>Q5J5K4I5HFY#.C $; *,"3 / +$%)24M1M.K*J 0M%> Qj/$?/L5O$ < =_:V5K+0 +A+ +$7+@. Ur!w/Ha&b~$Om%WB\$/5OJcVlLc >SyGET#154I Lgfz>  +P_,n<_p4Qnd).>dv>Ysq*x/#;j)t? +  "<+F2 ) +/ +D4J '6!% ")9!'7 + ! +  +    ( ( +   (9  * ) +( !& $  +]zi,k$s-k$x*s!y&qj|*z#rz'tz |#z#o z|ts|xnpuikc^X T{]W~ U| Os]}p#eg~+u%ala\}m} sssgkn&u'y"xkjiksjjs"`x(z!}#ts}"u(r l"q%p!kr!lqkv!dlnoiwint#ex&dn#x(q"v%y"lt qws)\yxuzf +lY}w!vv!jk~*%sswqx%q#4iq%a`\ pjx-p$w,et(t'v$rqgR| S|`Zn(Q|Wfr*[r*Yy._t)f[WcQ} WTxu4p)w,s'fXg&bg$^\n)Ox X_V|hje[p)h%n-dX`haq,e!u/m(h"k!u(p#k!\v.f al"q'|6b!n'eq$ci)Mnz7l'd#j$m#|2n&fv3j*~@h(x4s,p(l'o-n1p3t1|:s1r7l-j$t(v({2x4x1y-w.v.r0o*m's(mjp!|2o%~4z0z0q)|2u'y&r"m!k#r+|/mo'i!n%q(`]icl#o_~fo#m el)c#h'fv+t'l!|4q'r)m#q(bs$m[a^ad\ s{#okm!k#Ms +blju%kphSz +dz/Zf RwP{cikpy#a t"*w"mk{mf`e_`}&zyts_[ o|!sffggslg mkaUwv%p%ha pwy|(x0yquuugz"zgp oeTz W}ZiU{X{^Ou b\Y~\^!X{X~Z}_"XzZX|`!QuNrMm>]:X(D >XAY5(?(Z 7$!%>Lp' Mjz|K$@!   ;C%~<18LT0H 20AU'+ ) #(' (%#8'>&< Rn,H 6-D$8:Q Uq@\!8  " '<;QHbCb!+Lfg15Pl7$:+D$B +0M 4L&9 ).F1H .A +:Q \p8H*9o)QeCS;N@Uk|D +PhAWQ^,;9,<0* 6 0,(?E]1c^s1$ +,F"1( "^y9 +M]0Ud9 +1H#< +8"-    $+7  + /+4% + 2o'`$`#Rwe$v*v%p"hghqhftwovwq n {j wowuedobm hb[edaNtv*]wj gi~.v$klm^~pwsy!tk] hv #ytqy#fx!rnx v!'z*rksu(s hbkz"qidshclrjsmew#jqv!ij|(h~)spqulzxwkjt|!rqrz#u!wth`o sj|'` nu k}-{(v ie`cm s*i!f$i'l$n mirg[Z`[V~Cj S{Fm`Jp m+Puo%ep$Tx `[ZWP|g6jgcj"Is e$Xg"k'Yl)Vat)q(Zl!j!fg!^g&a\_fgg"dcw*jlqfd^r.]eiq!t+n+i_w$q'o-Unbq([z{1p lk i#u2v7t8]w$m7c!=q2Eo.r1y8w5s/r1l+l%mos'{2u.@v-{1s1\k&q'jm |3r,}['(;Ib%2 2 4* &  /!; 9(E Pi-?1 +#: +%+E$$[m19J FX'71E Ke$IfAY +.FE_OkUo7POb lu&#)rD^Yo"|-:D! - ) + +9O+F%;X"@,E ,@[, " 4,%@[3T_{Vp*B +7RNm"0K 4P Ec-G (A0H1F( /!72J5Ik}7\p+s)Nb9M98E%40Ge})3@JV(  +-G6P : +Wl(uK&,C $> dw? ' 1M,"7 %8"7'A +-A %'(! 0$;,    0* '  +$ 2$8# +,- ^|Tvc&SuYvp*hi!c]YZqlu tpznoqkmwlrkt"ocpifms^ +y%\dq`|z0h x,z%(oy elx wvvums)"vrv{)v{}y~|| ,})ooyzjq ju%ry!b rrxttrrwc )tz"~!oeu!ou y"-x!(t}(plniv"x#snly"ykpebz&psgmj}'Z r"Zsd o`l&`X~XMx AjWm ix'q]eVeZX~Yyg#t*jPu` AejlY Z~ao"dcj`0ltd^Px OxLsV n'eaLv [`dl'f^m#r)WaW~[DrTabh"ac[_ebLoLn _enp`d`|k"hnc~l*m,w0p%^p t#jx*n&t/l/w>G_Ww)Usk4~EEn)r-v0o't)t*s+m'n#|,w(n :Dk%x/q)eu*5p#|2ah x4_Uu"RoC_*HDcs07g-t.82NHh%5VXz*]~.f3x=:[n0Tr#f=qAOd& + ' & 7N :Z@ZCU0Ja "> %A; 1GHY#/  ]d*>J9MXu#C_ >[[s":R F^;R0F E^~9ap7tyz!uZtNhA6JTcavdy#<_xJeqCOf1"9CYXmbq.r2O-I4,D7/M4K* B],8U&D )D- + +Ud;6J8N7R%= f9+C-E-F "?4R.H0I(@*2 +./!7 "8/m{ +Yg%_u br2_sK\+em;;F-AVq%O\AP4H eu;o7_s'0H4%!408 3)>(2! +) ) '(C Ge"9T3+%, 5N!#; ,  +)<'= *!2 +%6 "$ +  " $ +* +  ,1L&:'*?ga#Xrm+]xcf;Ru Ns Qu X{q%hinjktejv%qt sjmpqf`lqz mu [yu)~0v%v(o$z'))w|  vqpny olz p5| &&#$}!!zzw!y"y y%nwy}w{rv!z%vv{%x#klrt|$qwkz"sp| sw!tw#uy",uungpw%s#{,fmoll}$nsx#h\r#cm!ajbopdkw#] leR T KwMz\WQyY fbiepfm%o"w'o"t'q v)n$_gbgcs(r*t&hjh_ ry#h` +mr o"ioLsdan s#},p&p&o"Y z/o$je_cLx Cp +?hZ}[s:OtDjabLsRwWzX|Pu hded|4l#t(fv1r2d Z|j ^dhaYyw1h$n1_x'q@Po!Gbs&??W!#0(H : 0Ld)3"> #9(<Pg7Vk)"42C%.;RBW,= %,53!# ).F1%1Ys!Uhboqs)9Gn)Tn@TVin|#H^ 4O, =V >UAT!& 7 $6  - #4!-#4,7Q )  /# +#1    , 3 .M)Xv7Yw8- 5%;Kn< ,8v${ (7-9RE]7I4<+ 4JSo>Z!6_v((Ytw Tg 9 .'=@V=W LiZj+u L]L]o'bz;)>VYkp!#1 >DNMY&*]n19Gu Zm +j3Yf .@2F ?U(?,2K": +*@ +>T4'  + +(,E&A ,& (!1 5;9T8P ?\%A +/%C !@`|-AX9O&7 +.@AU";M%  %#: %4  (Ke$3M(@4NWpH\ >PbtRfDYC\[t$CX&1K '?AX:L$0)7Up,'3!)*      3/J0+? I^!8N0C#/ 0+F $(! %1:O'*!6$<'3 ,*@    !!( &) )AGac`j [v%0U{MpRse*z-[_+?`!f4J" ,+:   (-K2M2-* 3 )C .  + ! ;/ " 3L"<. utluuw!}rt|#qsp o|'sy*y't{y{ ntvqpqz{v{!vn)wqm|x fu"~&u|$o}%uq!*(z|~l{ f|!yih` kmyuu l{$mv#uz"*z!x!w})mz$)ju#)sr#hjqner}tipgrrpwrtyqn+}(r,^|)eQ|_ghkb`jibYelm w'r"a]fmmSx bQ|gd?iNwjd_Oed h lk`R{_oiqisrz#r ,ront&o!\ k[ pelip,1x&q .~-x.,2?}2{-1{+u-p)<|2y1y.1r)o"y'v!q"v%y%m|-y*rng-}}&z*v|'.{,w$qw qiaf~:Z^ k*Nmv9TsZ}n#`{*y"pm.zs0|#mm;|).ry&0#q%q.u%amedmiz e\ jddjht$Xxv!sZ[\&}grp"hit hsahy&Z)&q0.e-40y"%{"wt&p/&mjfu#|%t},7&v.z4-\ nhlcatt!p"s}#yxyrbtlm#2Yrra|~/>x0\wpd +s2`bmd nV}VKp Sv _dgfW~RuGj 3VVw t0lm!r!ZKqW}Ot>`Out*X;a +,06!&("5_wPk1p|,wYy +\{g h ax^yf`Mq Lodeh_td feu*kIbSlaAcYz$Yt(Lm1K+7T"]3\{B1L#6%C[3Q 5VUsQjd~Nh*C 7PqBd>+WjLefq{3Tfey2L\mr% 24os@Tf)Aey'Vli!Uqo,l!WqZwlzs~0?R"F^SoB] -D ++*$% ( ,9*D,C+@'  +  + % +0 (  39T(E +.K'B :X 6U7UAZ4K$<)5K#2&%/E&=*D!:Ha{%a b1wv3~5%Ouck^X^YV} {,od8ahRy9_b!^)GhGhTvNpJi#<\?Z7R.JAag9U{06VLj!B`;ZRx(EkPr'_~/Us'Kl >V# "'@ +?^4Eg/>['Rh16FB\@^ >^ Hf Ro4QFaJe:RJhLmQi\z|,Sm6Sfevu2t "{&+5E]st} >6Lh!.|%.BZt FcQgd|#ibh!b|m+PkPh_zzPEU".0 & ")": 3  '  .>! 4  + %4I+DZ2Ng9E_$+A'#!>V"Kh)Uq> < $B : .G)C0F%%(;U(,)B 3M0M 2Q 3N 2 "$ 'A$@ )-1 ",2 &:!8-E.F8SKd 9Mdw;EZ!>VEa()D!5/D /"6        2*3 "5*;P)4I!3 5 0J'9+<#  &. ': +4#?1@     +  ! 3D#:O/ jfijp"p!{*}+i|%z!o!]gQtja}!nvu}~{{qvr|u!rzz#aijkptxsqoqqlplyonqssxr"jmkpqsnvtnsu |%okr#q|"s-sow"unrl`u!fmel!kdsprwyjjs`.kav$kdmf.mns#jkY\gnku{,mv$nan&did[Kwfbm|)vvpW]Kxb[Ov T{Mw ]h\ hW Iuc[]iOu +edigh{,n#}+}4|0s'l0-2+0cfmx.}2p'j#l%bk"v)x#)u_ e|&y$t!{'|.n 0e,{#{| tp*+lrFpt"em w([ mxpjlhmp#eg!j g"q/r+TvUwf%__V|QwWQybLsIneNuU{X}`j"`\bu$h+q!v(aeds#n l!m)gm$Yxp%dfx'Z}5q"z(|+0-|(3=m& %.m2(wx'1pic 2o#m Uw [NrV{Y~V}V farg`fv"w$nbku"u7y] ms)y+'l ktx!rv!\1'1as#./,&Wqls -8%?1Tp Uq 5-v0:f9TogToe},m(-w&4$~",ru $)2}&0(x!x#y#pSs +m!s3+F-BCX7MFdW.:U'1M(F9 -+D4/ 5J$,.>.7 +4 ":1J;T?V# %1"6*#>(F4P $;#9 .F@[!!;!%:,(&CLa4   + $!6!5 ) +< $"1 +&=Gd"*IYo7- "7 +% " !5 .H0O=]#:&   # g{&qpo o t#~+ez%xrkkeo!pr|(oz!{}puorz$xpsr^ +qthp ihhgfupsrnc +zo~uv~wwqvsxvuwx#ts&|$w}&t%n!s!ywytoustxrlpz${$~$|&wfruytvfi|+ct#pr npd`Xnnpt x$ggrdccts"_`gIx [R|Y]Px ihbfgehgHxY_fce q*Q{ +\ui` +g\is hPx ^u#`fhw!}%s#u#p!x&q rv%-y#y t"y4s,?hu&j!o!ml'v-;w&=x'\ mr"ob-2y,hk}))'rt1.)ispuul`p!u#ky#u#kqoq#h t/_f!i$h#aW{`#^$Qs\d#_~#GgVz^o]}Z~`!`JoY~Y [gjz!ner"mhTz `oejni]gz*`nlcs%r"fu"1v4qw$,'(&)z&5w (}$+kq"aDt$4u'_Qs{;Vyn%kq&m z*lh_ ip1\y)bt_dl[o%_(y%U~ +jnc c\ *fi|(+)py!~#(F{ x$F^.d^w t%Ws7c~d{!t5WqXtJYrp'B_Mj@\PkYtA^OnWvks|!pt"rvj3w |(]rWl`wd}h!e_Vz1wJH_%)@8N +^y'D`Xs%#Oh +kpznz 7w&#s vxry/]}^Dh e!a\oQuOx\(h:3Ie&5T,N 8W4Q=X+F < !=7YGef9Qp*)='? 2MGb$0SJl#W{0FaKh(%?[t-Vj0.G.1DEaOm4WAb=[@]Yt*WmKe_{~ &Ni OkFX}/qWuZtI`pk\ufLb ^zOv:!;0 +5(B&?(B0 +% 4 +$%>#;0G0K 4 &:,C1( &>5#?,'6'0 7 !<1 ,E3R'+ B[' + /#3%=/F&: )A5MSf6( $( !< 0G&   * , + -  . (<!- $5%,A-A   + 63M*<  )    + +   +j}!"xu r|&$y$z  f v!q}'z&y!{r*mmorqdu%pfsudpopqd]lmlqutwrtw}u{!z{vxqyt`zxpzht#v"rdjncsvvvxu}((p|%u|'rky!ulsfepoq rpYgcjmsigm"X\^ \ ^X}j"t(\m!nr!Wc[i!f`id` g_ [ phZjjwlZ \ i'mtldv wsiehbjjtf y!s~-jmfohonotu%b}mmseddi{*s%mo r!gijq'`8/}/v&|*/y&opb |x|}&k"tsp"s%^bku%}0v*o#s$jdr+k%n&gu+e]g&XvXxb h \xUsWwz!vo TxCf c"V{_bV{baklhy%kV} +cgqV}`quikz!ncs'q0mtqjw(r"y#x-'u+v!0w!*z!tpo#}(2~(*+t&g@y7p&v*;it%o!x-U} y.bqqgr~ w#w,1'Q|x&Fh2Oq%w"(|#5|.~37a}e)s~#pngqq r(k\x#85v*Qk\q"$? 3R;[ .,2R$@7Wx-6PZy.A]@T"'< 3*JHo 7Q1 @V %B +/J]|!}"{)|)TmZ{"BcAa1S +Ef;Z9 *=>Q9UIa#4Ht*OgXp\p#8jRm |&!02I3I JaPl _zr%L^;R SjH$8;S'>yNFbKf Of?V :V Fd,H?Z G^{5)6Me6S}.qp#k}.`mB%5-  +    16U2T(C-$>$?90O)@Y2J3M5P C`-'B(0D): &?5%A6$7, %!$2.CZ2(0J$0E( + !# &.K,F+4 &> 6I.<*8&!5%90 "5/ %,D!7 +)?9P#)>#4Q" +!E\'4L$<& %, +/!6>Q%' %7* " , '8#-G 8S+D(< ,$ +  $6#    +     j}!"qxqz ||u%v!!()$ouy |#nptkfo"gjsupvtsvu"sknz&d k` tb Zf qrp y"vrvpkqikkttnyvpwvx"qztagfu$z'vvuruu{(dy'q^dihlq nsmv"z'r&r"}(ehnu%kdm_Sw lQ}p^ ^XxcVy jng*ap_bfqwv)pla wpligott%v%nmfZ +qhouku#mjpeoc dm|)s&t$k_ d`clwi kokkhejs_&hdvkm^bq$^{)rjot"rg~+u&ax{rj`f(hiw+[{u+w+x)v';s)o#fu&oz+m"eeW p%y,Vw u-o|#`[k+tt%eNs[Ry Qx JtHob"]eYwhz!n\ acmW Zytu v%okeqit!/vyv!},y/x/t$v$(wrq{&.0~$qykf).|!+)w$khd~3b~u+ox!k&u6^[^:t$+w$u!Ei Jf]|X}p!s&:T/H_}\ p25[r"5O Fa \vWsSnm#}yw#Snm(Zxr9Hat2mIc +\so:FaB^2M .E'@Wb},Rrm!Hr,b~ne ~%xx}%| (ju jz,n#e~h/SkQjRl+Ic_vW UmD\ z$Wp D\ 9K AR#^t/Jb)2J$?%70D 0E 2J4R<\-J +F D`^w 0o+p?)7`s:/A$<50 .-& (=*C7-.')+Gf>2JH^&/E4J*C!!;4 E[- 6+. 2,' !*    +6N)=R3  $1/8S!9 +";&&"" +:H# &+ :H"6 -F":7L('=3P7OOj$.H&9H_1H3FDXRmOk;W:Te~*A]b}fcy0%^t+9P>W E_@Z!H^&&: '=31O-G0J5\v(UoKb|;*D5PMjVp!4S]-,7 #;+  $ ' $  " !!(> $3 *0 '2 /( + +1(C!3 +/2$5$ +% +     +  (B": 4I&# /E#/   + %0$C&C ]w<-C)BVx"Xx//A$7K_u.-H4T[~>1 +-.G&"3#5 0JKk Da b%Zv}8v+k!Hds2\vB^Mh]t%=P;L8GGX(<*< Mb\v&OeXq*Qj,3L4LLb!Hca| f#OjSj6{$rr}"m#punm1Bb k/Id'A$;Ib#+F r3Yw*k0Sr)V/1K@_?My3Hs"D[/Ng +Xr :T +-H[uB0Di Eh `z/Wrj~!uytUvm\~ 3 8 +: /* +, !91 +2FB[$%<Nh)7U4P4K+ 5": 7R # #8 +D]+4 2L=T Yo!RiHc9W +J.H%= 'C08UNq2T6RDW -98F&)+D ;XMfQh6Q +5K=P)92E 3K +-CUpv,o+j(I_!5FY,J^*1E1Jm*tp#';3K 7OTi1,AJc/{)=S Zo%]v {}q{wy"!l sZv6QHcD\*C +A@Y(C]{$\!^9W "8_z8?\;XA^)|4O84]u}99n*Kh +If47R 3(NgVpTpMjZ{St] lA]MiVs2Mf+Ie*7T#  + "4  &  1 Gc'9W(D+G &%,C/) !6*&= +Yr*WjLaTq@a;[%@ #BV/1I7TRr )I <0H:O.? +Wj+Ja#?[1OCa5L9MJa]u!BU(8 EV GcE` +C-awm#v?/=@M$/@ ex:(* , $4:S3O2Q2Q3Q 0G#8"7 7Pr)Wu$h8{Av&9Qa8.4Q1)E 5Q0O&@ #  4K&#> 'E 5K'!:% 2R",M$D'E#A-& . 5'C,  20I)-,4H$+=9I#&@  - +    " +4M?W#pLb):T*C+D+DKg'FW0% + * ,[=XWt&Fc?UTmp%Tt\v-D]8LCV"-AYo2T t (5&z&{"(&GgZv*Xr'.G +&!1 +#50_w?4P6 -C&*8F(pN'>+H%<'+:RRj1I$C3Q )F 2.E$=$B.K5Q ; 04J%02LRl,#9/-E r,ldy6"2/0-F-E*A 4E1D $,#9(C :0(#@R8)H4R&+>$-H2  , 1* +,<'  ! %#83K&$05.Kg86    $ +(+ ': 0!4;X !;3M-D>I!AO,_y5Z+B1ITe39H+ $8)?Z:^e(Ww``=`Ut']x+GcC\&6cy04L $:3H"5*&-DWv'SvYx'5O ?Y 9UAV?Y.G 6LCZC_"?W*`u(@[ FaHgOoZwdRtBd Wt"=X=g'v(y(CT@P;PzFLfQk9`s#%7EZ=P EY :O1[y"Cb:V +=V a|'Qg)=Zt'Fc)>5 "$ +B`*gQ& ( +'C!)s8CWn4Sl07JF`(Xu.=T(*AIe+;X (A Nl$;\6V.H 8C]Ha3J(A +"</M-M '@ 0D0DF^`y(Xr%Nm#9]+MCc@[LdLd5O Id$8R&A +EbNoXzc$v'gv/*0D$*'"0%0?U(6:*H>[ 0I+D 53:R#%??O,=*>9+@2'@ .* #6 "7 $; %4 !24M9L-  3 0 +)    5K&,+BR)'3H) +&A3(A% +  +  )*D4S/  % + -A 56QFeg=,:Wi:V% ! ,!       %   +  %   + w&|(~*u{$ss|&cuu{"rs .srdih]l[rcknst*gkkhrky'n r)w+oqcx gt#a o)|&t!y~t{#pr{$jqkdoqsx ox~ u mvumjwv!s(%}$u~u({$nrx"|!uZ~(*sv!vz&fs#akelk^ bap\ fYT{a\[~jo"k"5`mhQv s%{((.glmrdfkeqiv os^ iacx-t"p` +mn` `W} [qyb noXvn ~ utx$+(srkzsntrhoXeTY Y aTY fP~ V i_ _kg_c_tdmq"lr{!{#h+o"gnpkPz s#z!kfp| dlp"c` kauk mwsVyd ahgpvlu'gnenq"lrkbja^yt}.~6;}16t+|10}*-*smjz"x$i.{'k}"^ _r#s|$pu%bkqqv%{*db}q p2z'~+u${&3(,sVsCdZ~Xy#=W6R IkOn-G3J+D /D>O %6$*@. )70 .=0AB_OtSp `|/B^7PF`$:SGa#?W.$:%8 5G6I9J2G+4FRc+1C 9NEa 8T%9Yn%[s*7 +h~!x*E] OjMe"%8)? +/C Kcg%[w#Ha_wUqF` D` /K8S =X2ODcF`4M+D;M2G 6R ,C%;k>:S G_&E$@\y!@ZIm8Y 'E:P?WJbq,k,dx&g~3g&]i!a|#]s+Yn%;OOf+Ga(>)1*E Om)B-C +5H 4H 4D_6':lD=`!'[@W4U9YGhRqPiXm'0F;V2I.I 5T(H3O(C,DOaky"$2+**& +, 2'> !=$A <2O:W.. &'#9./H. %6.3E'   !2/ +!<6 '    5M! (0D(0D(    + +,87Y: +6   1+ +!: +.\o='<(Fa"I_%5J #"0!( + )8"  + %  +        +# } (x%wz t(su} "}&(y~y vrqm)stnma pl'w r!at%~+|&r},n#lu%v)a&fgnrz'o{(vm]g~#}%z lokmhx"jv nnmqivsf_ }$kg%z~!vx s-rl~-sjt"v!}*y'u&k*.o"bo rc] a]akgj!VxDiUydZzo"i f}#q)q#_Vyimhw!m] hb| ` +jf jfkprrndn!v$t$z&j ojyd _[|en|"(f )Xivfyr)|(nvx#n~ jklz)sqk $c m md _a e tytwc fQ|fc bZ~Tsn!hh]zjcaJnoh kFtdh g] prf spphlg[ i znughog |u$bigw!me`ej'elw few-}1=n"|0p(f'r.r-w/r'~4~2s%y#}$tnyx"lliy"po!X ll~#mq"]o{)x#6v'6}2;2u| l}%7#&| x?j!t.EeD_f5Pk'-I4K,GE_%4H5D %;Lc/*3IC\ @U!Rf&z26T ;T)H1NX$<2)@4H`"To'E_ WpId2M +:W [&A8Q-I ,> 5KNo';[7SC^i8=[Eb.H\zeQhMh ;_xFjGid8o,z1A] 1E(> +V5 - /LQu*1O4N#?\#Om\|0/M9TNm,+@(:4O "7F^&s-Xr!Lm&Fc"Lh)B #9($:%@Qp"Us$Hg<[ @]-F8NJ]3I^w!7M.+B@W QkIa2G_w)Ib:S'B?Y5N.D4L 5PI_0I 5PXs.`w7+AAY"'= y2_t59N"4OPk-*>-$ +)> -H%C$C+GB`# 9 =WNf3 4I4L( 8J/ % 'A&? 4 #?7 !8, %   Od2$7((*($ '  EW<  + +      +!  + +4 " 3N5T%B)GKi(>[$&;%* 03M$# "# '5!6 *8" ++/);*# ! $ /(<. "60!.      ' x$ltsnlw~$ru~%)st}#+vlhsw$iw"}$xsw} -pq!ox&/{%+p./5x,lju#lkq{(qnt i``w"ohnmlmnpjmlp^ pgt$pr!cv!`dm"|&it"v$w%0k"t(pr r!u$v&=dx03t!x*_g[ {(e\ |2hv&br(_z"Poeb~n)_{|,-r!x'ikp |%v|#x$gw+U|$a +a p]dgkvzlib}#j(uXdwafu-loz!+{!*mNpjnlq}&i`tgp!iz z~ysr"td}wo |s}mo_ cU]T} +X]knk_~ ilY~ s)mzoZ e zn`jzx rj'a w$`lxuwj&qqtuw rjmjng!Z[q-l%bo{0>s.|9{:f#9j*i-x5q*s(o4v(u*~&}$cs|#x$b}r"orl[}Tu e{&mlgj{$~!u'6|24o%>s#u~!oz"| "t!e Vt@b ;W +7R-J .F4 $6'8:W"6R1'6CXt@s:#=Zr':U EdE]?V?U [x,Hc[w';\5U 7R 83I1E0E0A0+=0C0E,C*D@Y\v1&:8K;Q/E[!8MBVs*\p\r w5Gbd2Rk!m8Wo(?T$9/I d|*H_{E:PB^$=[3P6N3O3/"5 3 +5 .6 1&*"= )!; +.'"; Yu;"830CT=(5": *="))   ) -=*>(? +! & & &92       # .  ++(B7M +6L 11 ,?8R6 5O!G`!Me"3Tk:R5F.@(7"2CXPb9M g(m,m2NeH\MeIdx/3G1/%75O0L+G0,#>2 +1 +'* +2G#;Ma/%^s@FdUt"Dg +<[&E+I /C/CWm)B^.K8k,C_$9+2H=U/E,? 8QHY!/@ +C1C.E:U2K(D +516 5-8C(5G0I.IkS$ 0?V$HU +=2)& ++?-,A* $ & 0$0 +  '=-  '6/ $ %&5 +3    +   .F4B "#*  + '"0( +$!:3N5O &<) 6# (=&  +     9G1  +   +  # +       + nlp|$~&w&u!km]]X} X~[cclV~ +ijy&rh~wsyx!)ru#utx %z{'r#'}t%n|$u}%{"uy$w!x*r t#jjp rb ^ _q!erduolvp}(kl PvR{Z~jgV~ mktcu!{%z%v$r!y)rpih ZcKp Uuu%e{goq"`_dovfkrvdn!~/s%v''{"]cfblvvy*kmheZ ogt z$+#~sfg^ }ytqkhtwlyz$vyq{i rnofl#qxr~ppab jvk }xr} $w}).4ybguea osdmkYv#nohgj{wtgmealV}^ `p_ jhixr vml})'!rd*sirjkY +[Pyh_pmm }.r'z/n'h#Twr+w2i$l#UwZ~a~ `#Tpz2n)n$ej$umnkv'.~.w,w,p#j2y-t(y,y(x)u-{5n&Ku5s-z,y,do$p!2x%dcjr$_Gc8T1\x9>[0K.EPK\%0C +?S&;@U;PCX@T _s7)!3&8*?0I5R9 +6 /H1 4 60 j=ax('Lf"^~'k*>bBeTt&Xy)d32K^z/1N7PXs{8:gD(%1 (%=Y$!?5P-Ed :X6Q5R,J,G-[t93C"/9S/Ii=[ E_,JDc5PCX,%3 8)Zn34?Y\'$, +>K"'92LC\#! +'B5SGW\f"%5 0/ :V-+F&) 7  +% + +    " $1& +   + " -  +$*&@$  %80'# +$5 #7 +*E0>( &>Jd+"8 :Re|P& J[='5  +  +    +    ,!    + +  edqtupsq dn!^h _^}p!t#w$]j!p#kh` nzjiyv$s{#v ] mg$llqz uy'~${"v#{%mroh|/i l!o#dx&Y +l cjv#qhq"jluyYo#~1Z|y1fz.t(jw#sq`2px!rpu mh6g^}h#Mri"r!t q!aa]hW~ n{vXkqpup&vy!qysk^b^ahuvt nb]Ryp(g{0u,s%lsdefewo r^`bqbs~zvxq{lf d +jPu `Zzw'e uk nm^ +s!-krkts|$z!'x#6od[^efl\ih b a|'fz&npy"qmxitbcfc^gd] p dc +f n u+w%@{ !si~&m}!pw^ ^fq&.6-,}*.7ll!V}m&m&gejZ{Jo +[}bn'y,s)mkoz+Ymq#z,7|'ot%s"-m4t*6:Cj)~Wk?Mf#@T(!5$5&?/I%:.CX(4N/&?&> *B 1G.IG`!5N2M +=W8P%= ++ .,EG`"8M-?>S$$;1H#5 +:N=R*?5H%7 ( +/9=LCU)9&%3D 2 (:3E0D N_&+!(@!9 (5/ 0!  2(B#< +3#"K@W/A]Hhe)4T -,M 2UMp$3SNk)8RD_QqFdTv $FMn&=[Zt$A9}1Ti$s>P[r,|P) ,  +-1[w60* + + IY@59V>X+G;Y6-.MJ{0VuHg\x%1I +KkQp>\9W/I!:2J;S1O 4Q/K6O(E +=W:Q+?0G 3MHa]vE_pVheOgUk#9O@TH_C\7M 4(C 8S0H!53F #1!")8'$7 4P'F +%A . + /2GCUOY!`j#Na)$ Yq;*@,')1)>+=$.>0)# / $= 9.4  =1$9 ?V! 4 !.  - '+5N*@   #2^y,Ym fwLc&XcHHQ*Xd,8Fj{?mz:.<n&e% $ $  +%9   +  +  +   + &9         " 6H* +!< +G (  + 24 +11&   &     % +'; +$5 +AK0  *  + + & $&@>S%     +     +"7.J#< 7+ + " + (    +jly(ihq$iRxn(i\o,`p#z-bRwMq j&Sz mYw +^TxSxfTxVz +fgT| jY [W|ete n_ +kb1smw'w+lp)dny+k h%t*fpot*n'ai{+f^i ce?y+3{3j!s&h]DYst,o$w'475/4&$is/Yx#-E 5NJg#?IjHkAb";/+ .?7 +9)-3'  (&7,A1 +,Po)/M +?[%";';\r#9V 8U Tv(E4J- 0C/@ 2> 3Yr+Tm+H^!,DWOp1N *H :X5>Yx30H4KNk1IOiNhSpFc#;|L) ,H&>-  4 +A`%%@" +(@Fh 1O8Q+nO#@ m2Rs!'_(i%m!RvPmKh5R +H $?EeHh:XOn&2O >X(*E"8 +0 5 2H : (>1/D,G#=*E 1N1G F]Xp/:XNk'Hd5P4 :3O3N6M0(*0 +#2 $ +-<, + 1J;X4!90L*? Yd;#6 +8X '=G` We8`m&Ya\e CTw+})Vk0(4/&# + #  0% ++ 4)  +           +  + #/3)/E+F )E*E) 0) !/   $!!0,   #$-## ( 6E$3$  +   +  !62J#    +.G,C,A     +1;) hlp ns$ek"j)WxbVz`[yx)hw/`l'icjdlFmZ~o]U{ p$v*Ry ZQy ^dp pqy'o2r$y&oo!em cj"dbceeh``s#s'V} +Pv +fb[[GnPwo!s(\}n&l&iy%d~7j#Ar%v"jnZmSy_Rwqi x rovhy$pb}%||uoX i"o/Vwt1t%q h[ nnptp_ kmy"e4v(y+es#v|yny#rs|xly)gp|)]p$r#a~v#~"z"ra gom'%"r^{$}mkyrqvqxv!Y u(`hchf ^nnp~|{qlwuwuyyorbi +awxzv.m wnvpqwknsvm zvsshvnvh wryokfhy)nnk/hghqy%_mjw*p#Y_Si&h)Fl YLrb_]eq&u&y*z)oeebx*q(l#k x&4#o}+4u'r'u&3}-q$~35t/x1 +St.Jn5W/O =Y#)A+C&"9Sx,,0K $ !  +$, +@Z1L"7 +-(A 6MLg"9R9Q3M*D222 ,1C- Pg"Pg ,@#6P1C4:PNe%#6 * +.8J#';"5% ).FIa/nUm!uC7x:3R8V!9 0    (   @^*D3S +0@]*B 2Jd0k1j,7R $@:X5P *G6PXu0+A 18 +/ 2 %<3LIe);X2 Um=(B!:Ql"4Ec*F*HOl#/E:W1Q 2K=VCa.I7U0M +.F&> 3 4 /I-B"8 6#9 %6Q4S7P/ $8)? 2F /H=\"A <"= 3 8T",I)G +.   ' 8%A,?)7&5'+6 5O0 )F*J8Qh6f01Rx#q\wx#(m}?U2KPe> ( +2- 0 + + + " $ "$   $*     +   +&&   + +".'=-8P 50$+(C)D)&  + +Pf;0D+#-B  +"     '3 -%+% ) )/=$ * "&2"%7 -,?#7& + +.=%   +     $ ew*Xwjz*l"i#f"`en#iu+lj_h#^~Y}]Qx Tx r%]Y]fOwk]~ o(T^cfecaY|i"gZ a]dkfgfZ~kpcnv&Yl bhu&SdZ fZ bn!hn#jo#aq!phj!~36r np{"s~"h]] eku!sjhbnkl_ "w\fdm"k'k0m+r&d`Yyz,nhhs gr!s$s"iskn%u&muvtr~nwxo-| 2x%-{*f7~+t&7~#|m|p+x&x*ou"t~#wptj lc ^m oHpWMt [_c +~m iwlpyu{z(ok xx~zp _i.dxl x{xvvpj +o p m tqo ivzfo yxlj|nxpz){)ks$w&m!o"l!q t!j]lX V s!_ c`qv%eOzp"W~Sz q)QvNs d,^]Rwa\g}(qr"4q r52v*+*y!v(8w*r!z,x'n-.u%r#8kz.f,z"+$*%'u(\{v0g+5Nb,Bh @c'G5T2'@+' -B.M-G. / *! + 64J ,A$,. +CY!@Y-D  "0+ ++H\#,A "5 Si)9T8S 8J:I+>-B,DAX1B -L_*1J CZ&+)CC@] "; Nf)!1*Qj6)BNpg:^"8SSt Mh#h$iMj$>8`|3:Wn!p&<]5R(DCa'6Ed%_x(_ +*B 40!4(%;7 +A,H-G+D>X=\ Tt&8Q?j2>Y(@Eb6TMiGe+H "9 '>0)E51 :Q"'B/F,&88N"4-B+9#=*": +$*->:S;T&>Ic#/7P*(!5 2 ;8&>'9)3 34 !49T:& +%$&=45KjRu4S UrKc q h]uqdv ;T :Q u+C!( ,0:     (   + 0) +    +   +  ) 1 &  +  #0/E* 0!02 !/ ,>  + !8 #  +  $3#*!0'//%D$7  +% +'#)%  $.  &   + + %1 $=2pq"n#dy%laq$`edhbcZ|\VqOk Pp Jo >dPu[SyT{ZQzb\ |)dMu_cXaOu Ns PrUuabl(W}akpqOuOrVrjagv*^^bZ\e] m`ev'fw"qm~'t$q!pp!ds$rbjpd on``bn[eX +Z R~X rhW} hhlikr!nj%_^}o'SwLmYud~Sv _|4u'2robpyy [~ _[|_hgow o{$kg{$y /~,1r%t+2y-/.%sxqs&#"#"%bcy'pq+olmc hj R}T~ \[croaichay-wuymuzty{#5pz)uz"ss }m p'vff wvstkontoiy{xxjtsx$]cs*Y|j l \p'eW{ lr#X}em!dhd_f +xmu hq]lr&Pud^]Vx W{l.cr%_kir#{-o}&2z%x%5},4l2{,{)ls'qx%jt"qCx*6c}m-u{)"'y~l[bv&{#g)Ac#A +,H,!!'"*"( 5/H1 ;V(5T#*E'>  %$ +, + 0  ) 6G.C 2E.?'7Oc%;U+?O!/ += =R)=($1(D\)3 - #8,&8KMTpTJ[.!8F/"/P_>1M n5^x")@2L>W#&;(C^#=_Y|(Lm 42(K0w(e42L!:Wq/1L 95O%A 8-C6, 6 /F- '+/)17R+)  ! AZ ;X 9S"; *2 *,='3&> ? +".F+>8 4*C;[He- 7/I* +$& )#? 'D 2N ,HWn$F^>QG_LgCX4C(:TZv~,U`'DH=  ' +$  +  #   +      1 $    5 .!      +$ +3 $*': 8 +7G$.-' +3F`K^0($ - 0*0&   # + 0 +/A$:#9 : .A%:#;3 *>#2 + +         +!, +   / $<1 q u$kdz+fj na kiu#] emcu+m s&f Mt Za\ fr!cicdMzZ]_Ov +`Y bVwWum%agr"_sf st"Zk$qv(Uwl!dbXY^gprghs|!w}ov&hav$` \ +f` ggpe mqeYW b^r#aIs[Z h nV U} fw+^Qy ^diglcZ}Pp w4b}]Uw l*{02t#eklsx ^ ncRufm Y gm&8rp~'3-u'w'w*q$x,t+{/*&pdimg xyv6{#Z r'q%r nz*r's-dip tqu$_ tl |m ] +lgigW}qvuw~%h p}!(1n{*w"v"gp j.fqhSzX up!qoY\#x r o yppcZ `i!^i$Z|h dT{j"ex!mz$p!r hs}(*pyr2|*,t#iy+7s"x%r"v(o w-p,o'3ir"p$|.0,q1u!/|+Bp"n"t'}/y%}.}3l0jl3eo#z0{3s%~,p%tts*pw"Y}2Ql&|.\uu.-M-!<!8 5# 0F 7M/7V 'C:80 $ )8 -'- +"% #/$ +&%4'*;0E,(9 /+%8 2F!P_18Jdx9Q,7O.%? +B\AV -#;*=!  + $ +H%=   %$* + +"3%<#  + /&>6-!  =E%*  + +    " ," +2 (D$?pnoq#p!t'noi_gfmg\V~ ZW~ ks$W~ +j`c[V ZMt[^V +hee_gcb]dedcYLprq] +U{ Os UwPk[wCceTz W| g^^YQwgfu pquq^Kq:^3WX~ f[ ge_Dlja` mMpMs V~ jox"j1[ +bha^Ov m%PvPu JpS|d[| ivocie;Vx Y~4v%,t#`^d j nmseet Mv]g%j,Cw+8w(})rx%jq$p!w,p$2u x'nt$grmz &s w$z#kl*rww!|(hfmp q#x|j }qp e*yppxq~!{rqn*0tt1kl{(t%fel!hx)f|#)fo*{ dzunswvzm o piiu*eifp"gn daloqtxw0oj|'q;})ur!{&hlY~Qul0rlx)|-y-=:t(mr#r'6y&~'9~)2{*z&+x%pr |*l}2|0t&p n"4ilz.u+8z.t#|'x$vpyc Qt .N!>7S Sp},1~5Ef#;Z!:.,*G8V$&B"@!=3 4 + ++ 6U2 ,<!  3F6L$5 '1E3I )<7H2D.B /C)!//A,> +#4/$4 / # 6( 8L%H`)&=) ."1) 2K{MBdf)Rsj2uE>V^{+JhQnOn$#;   $3 +!%3?N&^y#9VXy8UEa, <7 :BZOc%$6 &=G/DH_Hdg5#5(31?Si* + + +,"9,+%" + +    !    +  !6)6,N.1M $5L/L +6 #  - / +&8(C6Q#( $' + +4 *F* $@6PKe',A $';! #(97G'7- +1C!"3$   +&#"/&0     + +   -  + 48+*D5Rqy)dq#q gjeha`lleS W]Oy]dj]Sz @eW|CiX|:];^LsGtihHt]da `\X [\cbaX~ +j^ T{ +dRt +ZyLmQu s!onnfX]RveZhfc +`Is El @T @R#1 +0D << 6 +0E8R-G '@)Li$N\FiLlTqB:T Zr.2M(F-HInOs^'\@a =,E )2J"+ $ /D!8YJg*&;D[$H\$A^If!:V#?%B ; ++@Un4%>-D1J,(3 5 +  & +&@/ -0#1.    /,I 'B +?\(.M5 =,I^)H*G&F,N +Gi5 " 4O%/ / + )(   &12 +B['8T:R5 +-CY7Q,E r:-=P'6!/,,@W"7 33H6 '( +& %) /2):, !2%6  +. ! *=Ig&1O6VJn@a8Ug"OtFi7Y3P&@*G-@"&*< + .AV'+'.H6uN. ! 5 +GbXro.4OB^`z,LdF\,G/ ,4RQt`)p0BZ?V0KLj(Nk%@V#4_u9\u)[y.  +  AR) -35P>V 5 / "; +69U+C 0+A^ 9S/#5 ?U#)> !6 +&:3- 4>WGf/G 2 + 9 +If):W%=-%%#8 0FC^- +& .!/;T$< %(!6 +*E    +6P/I7U#> +70M!=!</G-</AFbq$Md@S'7 Pg*>9A +  +  +  +*@&- +"   &" + +       %86 / 0A-;     ** +$ , 8+D0 + ! + *!3 %-I-HA]/D   %9, ++       +    +  #/'2& + . ;,,) )& bm!fZ m W~WQxa`RxZ|gZbg%biXU} gLsx*^ZcbJwbT{WcbaW j^ g^kat"jme m` Z ^X ahZQ{ Rz JrcibDk?ccPw KpUz Z OvHn9a>Nr!"E2Vg$Fi q"S| f] T z.\~hT{ +e^gdaXIte^Vrci_`[Y{2YJnDihS{b:gqz"jctkto^nnhu'h}0/y|%mjv(ov%w!vgmligdT{ +zlqwoqti4Wp jfiz ] s v%lsoqhr]nd xnopq!z(ut~!j p#t(v k,)55z(-~ 1qquvmjiysf lms~}z$q!hk!m"s,m gv#kx&lsnw'^p!ci^n&`Jqcb`m"4y+lhhw'niy&*(=pgs)n x"fy#(vr#1t(7t'+)~!vsrpmmwbs'''s-y"&'i|)~$!y!l}!ehKl y*9Jq _2Q )F :W3T2W5[5W3U#C2 +) '  + & #(C4R,E.G0'9 !5DZ =QKb3M$@&=;M*89S*@ F^$7O(= 9 "= 8' /&  "1+$ ' ) +( , *?%9TdE$;!'St(Lpv=o4 BNo#Uw',N 11N5*(DUq.Vn=2 $6$43H3J/ /O#=i8Ut/+F:W a(UrTr*F ?VF*> c~F , !>[Ps@9Q%; *F +Pq9UlE(BVo(|9l=G^*EU/ + +"20EJc):U;.H%= *&= ,=)@ &<N!3&733Oc6 #@ -K$B1N=Y(D)DHd)+=U!+6I, +- 49S /{O:V1L}5w-v+n!~'*u ~.x'+q y x3+/2x")ui|ukh ._~ ++u!be +hlu k{$2j|h#ys|c2P0P%C 4N9V(D2)K;6 &2 *3 ++H , ( -+A   +"76O4N,H .F 7P,'="5 &9 .GCV?UMl@[6J0C4N0P ?\Fc&>TCV8I 2 +$6 /B+ +1Vl> $4  +) $8) +#Ur&OE["!.F8\k0Nj8 <9 [{.Zz$*F*G .K1PSp= +$ "-A"!5)<" + 9 3n7TmW9t*l25@_Po) 7v3:Vso)Ea=[m7'Ho=Nl,8?[$+9\u,c(!>+BUt8YvPqSuBbGa09K+ !  & + "Ff"9 (3.K"9 3D$7 +$: ": '=&8   %/ +.0G#( ,Qk,C\ %< 2I):Q5 #<4M5/,B$"7" , $<!0 + 3 !<$:/C% . .     ! 1-I3P7P0C#8(?&CB_'@8OKfNd\oUiK\(7i"IZuC=F, + >C7'/$   +-4  +    + ' #  +  +% -"  + +#  + '+ -&1 47"5 *+5 5 #   # -  + + )&6 (<-G(  *?+     !" + +#  ( +.3'B$; 44O"9T"+A#:(& ! " % + h_r%Y^Sx Rxaco"X +croS|`_Oz W[U~aejbe_v-ZX +b]mb\ oiva f g b `gX [ h]Wr!_ fcW onffpg\PvcBj]R YlaRSN~haQxo` nma` kga`[LyfaU gW S~ Q{_]Sw,RxS| Y_Kxedub` a d ynrkqh^b^ bahhc]j]_jo)det%X{ o!n fuaW Mu[biNvcgpdcjU mk[ +tek_[ iz'Y +hty%hi~0p"folr&m%dah#k)[x({+ji A=7r(9[jf]hq0pz)n!`bg!8dNw _\g%i%j$q&gfj}.jr"kk![[Zt-^Lw_U~V~[_v1dw&hT~ aVy^w$pwzz"u&x#,mz%{"~(j}+i-s!u w#x"v0vjz"nn{Ucwzv3jf l-sz#` jw%nf +&m{ qzn vc :]-J'E ,H6%; $@0O-O#A#A*F$" +$ *'2 #" +$# # +;M.12J2J01F':#5+ ;S"%D :2JEb6O%9 +!2 $;Pj0 8'Zn/'+?1C$( 1)?+ *1%' + + +*$ +)> +\|8s3DdVr5Zx*Jl8Y +Di'H = 8X-K =Y)G 96V,K-";2 1 8M*>[Qr+Dea-v55 ';  %4 #4')1 +,Ig$:ZPn- ++&!8 41G*$<6 $4#2)8 14I&-=   & "    +  + ".D'DIg"#?6NvD-D3K3Vk#@Q=N0B":Mg!Vo)-C-Jq=G]!!0@Q",*?)>!, &!<*?!> 'D=U-%9!- --  ' 1 . + +) +!9*  - 0 +. + *7 +$> ..*':"'2HC[3D".  +!   +  $2 1E0':+ , 6 +C "" )9- - & ,<#     +  +      +( 50K+E4 (D=X*0I+":  ! +$ 2yYekcgceV} \^R~ +U PyZYBmUM|^e^` +jXhr \jkR}[ +hie\lq\ ec ZZ _WT~XYblW~ di}'f`\ qW~hb` Z +[kVha +ficO}{*j}(y!ldktspp!dcW fa[ j"$pw"hS} iNw LwcX `^_ linmmb +c a b ` _ b`] v'~(s]|*fpVl$Ttq2Su fq!NtZ~edqufjdd^Oz`fnOv [nY hxjnh cb y||ipoheldhao&o&u+g!u0y2z5j&9w1a#p9Z!}8ck(q.Yzh!p ^Q}O{n)sjl#Mq n&demf]o,l j!`V~lgqpi{-|+im br(hy2]x3Zebe[o"js$V| 9Z| \_oohqyo{ "rxt&tsx!w"tqlmqqg'pq[ +kejiqex}mtrk#{qrv&hZv +y&kq|#rnnxprp+Gk-L-J0* +.-M: 4O)($ !7% +$!4& !<% + ' *B(%;%; 4 0J6 ' ,-9(=Zy3,3Ca!?9R0K(>!8&@+D)=:I&$: -) 3'     +   />#$0$7 +Tq7Lm`(Qu!Df$@ .J [|0*KOu.8X%A <Yw6Sr4;\ <)*%'<>U'*CVu1]$Zw PqKnl1Ljs1`#Db(D +;Q *BJn(Fo@Vw,9sFQh;O!iO>S:R!:Nf37No-Vf+,L;ZXw+% + #EY,&E`.I $< +*B.F-1M-E'B *1 2#  />1, +!%9R8V"BY:V$=7Y%8 +%='>3K+C5 $?7STr/4N2$3J:S%  +  *F) + +   + + #4/0 0J< +89O(= %6 : 4Tk]u%d{'&<!92 :+*G *C#  +  1 JqNv S{ W Zg!X[U~o S[YLxVOzP} R R [ GxaZBkeb`^hatpke V NzeS{f_jw [ _W}\~ Ckk fo2oT}q$p#ca b b\p]iLu^ cgibZ X] +ly'v'lz'|$uuyru$_Z^RjWpk ZhQyhlq"nmb2v%U c\ip#YpgW aQ}lQwQyXqfV|Kql `dT hkW|\V~]&qw%+dqu`v#ceo[Y`br%[ ~!\ +c +eb +gqlkfa zqR|jjXc_n%]]j n#i 9cr0f#g&TsWy#f3Z}!n/o+g!s,w1o%-\ +_bbw&bn#cy*st%}-y,gl#o!shgx#t![ +^v!mgw#{-{.z1?3{6r)`q)]]Yl` +-ax%|'gy%lidmierptrc +ujk{"rc je ^ +y'ql qw"nuZe\ `qh` c }twk w"wtk0ckol)mj |a.f x[w!>!:1"> ,&@ $ +2 =. )  6 ,$ +.-/*G";Rs#Gf6M" _q3Yx>V5K;VJg!>]Ih%2JSn-3Q1K +%"2(=,D.&: + &      ax?6V +21P /P#@FdKk((H>646 +22L)E &. 3";"?%? .EGfHi+K<^ Hf*I @_Ll6-J25Mp$.K Oi*tF5l@9O ++E (A !8N#!9!?`z-Wk62 8 3L. +''2M)G& &>*/$9 +7 6U/H(A 0G*$4Q+#1 0)< +C]!=[##,  6 *K1 '.BF # HX)!' +  +'@%> +    Vi/0C;R Si#4I2=W/16N && 'AVv1.A >N(%, 85( (!" +& +!# ,  + ''%   +" "!&  " +  + % + + ())># ! 2  +  # +   '/ 2 3H;R '@   + # $85 (<3J +!!(0"7 ) (  +@, +&@ 90#>! +  # &% 1Uw[QzNy P{ Z^P} [kh]l [dja[Zm_T Y`V~ +Rxl#]gbiejc^aibx/ahrWljn%Ks{1[ \dnldg]_X] gT}XW^ da ~$g VZ jrmqiisrm_vlh_ `Kx`WX [ qtxjo``X~ X +]jm MxP~ |-j{$gpllRT Z ahbS EmJq +Hs]_`Eo kfmz#nr j ui g o\lX\MvT}w%Ydkq%ckg_ dd` +ejro] hZ +n/cw'{(k"TYcOvf'^~l&Syu-y4g'g"y8e'])d,Rq>z19fQwe!q!p l"l$Wz|*]q#}.pil\q%`Zix't%`oX t*},v!]^v*q(r(z0npl#Zl]bds!{+vdph +ummV \_\] jkmmb`\~%tmumxugh wjrd lTPO~_ +^dh k wowr!yy!vu jm|"qfxudoy"fx&Nn3 )*L@]$-) +";* 1-?! $ ! &; 0B!1'0@>[0NKn'2M+B2H' cvC#.7H1J %7 ,>1I8B_,E&>2L7S"!="8 3&" !    #  +zZ - -E4V.N=\!Wz,Gg&F&C.I9 3OPp+Sp0'D=X;Y*I +*D=Y 8V:Z3Ii(pB&@(B1-G y;|DRl$ +"Sh*&=/sVB^1G!3M39W$;Om{;Cc =]"Ea0 3KPp&'C'&9%9%/A$ +@'B"9 Jc"&,0M;Z";V 8 2 ) :&;."1' + %;,* * . %;'1 +%> 3 + +%#(>$9 2G    :R*%C6S"9 +3??>?K% }GHX1=  +" 4- " +,Nc96J.EM`J`1F + +3MB^Lbm}1"5!:+J?Y/EJ[*  !5   +  ( //" + + +!&   +     ! &"6"#    /*   ++ $ + + + +    + +   +  ' )$; <' )   6( '' . ') 0D('%?1 " /! , +' +8+72UuMpHmJp\~v+obagW}d`cc]^X]T~ \Xq$hb`__][o(T{ gZ +bMxW]Qz h^khd[ v#Kv_JuaW~[V~t!] eFmWHoeU}ib jmlohw$`wZb zmx!e f x mlec]TYFoihomY fYqo_e-_ed[W L{T~ algc V``] hV M~ [`rn"V ggbhrsrx`r[zj lZnuLpkobs%WyppqT|hOv[`\} +x'Yt Y{ a el"eqx*`w(oY UN} Qyl*WxKmd$o&[p&b~4h!l)g+Xwo)9z-2hp'a5o%v1k$l1kfgMsq(p'k si|)q lv'ip LwS|cg[Js h(Zk"hxqz,`Q{ ?gi$]\ +{j e a +}%f te`q(_a[s)\f` dY +abxpwsotuqf ysoqa kUV ] [ekwpiiZ} _hqxqqa +j stVyorbx bU{Tx ;1R%N 5'A!;3 +7 20 5+-H$ !0% + 8!7-DPl(@[%B%!4 +*+:R +8L :O"5 . 8Q>Y+E"; 5 6 1 1M5&8&;"2 +'&,) ! XaE 4N.M <_7*C +-FEc$-KJk%$BQo.'? d;b|:wEKb59 "-BTt)!9 *@'B@^&+, ),"5(>&:1 -=(I_)A\0E   !) -<DQN^FXDR6E[l2*5 &(B6C^#Wt2:V4; 9TKfJa3J4M1D!$2$0)5R8R +K_BW;V-GF\$.BOW> + + ! $ '5&  + "      +. % + +   *>  / %!65 2 7 +5M*    +  + +) '%'*=    + + &( !66* %%, "$;":&"; #!5. "4 3%A!> $A]Mt Nw U~Ttb}!XyWlk_[{^{X|T}[FrJv YhgP h!X^]TR}U|Mq VwLp +Wz X dS~ T hJrIt \ rVKq\ kJxw)b]9_bPv[ +ciaOyalT|c]] tiq_g[gs!Rnl +hnikd ^` ipleq*bigajesmnst!x"u%bbiv!d~*ib\ goeqoex"_ +h[Y _W\ +mZ aodzncf` og kvyz.104(5-,z$rat#b[dk~5lv%o H}05y-dfq%bTCpXz*q s*`q$efu.`zr+x/_zz9]x725ck"a1mX|\~y'lfY{ fQ{ +t&c_.r!nc ez'x$+de UJu V~Jmm2Il +hgs}&j^Wm!W|W X} ubW~kv!f__Il[zfg]=HhdZZ`[^nkpg e +slpbdv%OwcjOulRzef\Qq_PqNoqVz _^ +]thqmhvln`gp&a +Zx0M8YA*K4<8= +O4V">A^8>5 %A01!+'F^$2Q 0I ' --<?WMk.&>'@2K'G[- $7L!7P6 #72L ^<].Q8^.Q9_)NW3;b9 #*3 +!(>/2Vt68%?)I-Q )E!9&^z@Kk'Gk@b>`9Tm)^y>\y;>SLe1d{B85Ge*+ G])s;Fc!"? >[<]Gf\s51@!3,6(;. 1 $<)"+J`2"% #'>@\&-*&?0KGa (D8Y-K'*, zT$'<'>1M%=+ '</ + !#+) *:" (* ,?!#=%+1K3. FV2C& *.2I*3D" +    +  + +  + '!1) %+"3  %   ,$ # "#4 $ +@ 2 +(&9(6U'C9>6SV~Ms Sv]W~` q-v.v%mk#q,Zz[JqO{Ep Is BlKoCkNz DrLv Ky f]R~ ]Ek``MrahV]S} gae\ ^U Y xV_ N~ZT|T Ov\V} Z_` ] p_Tz \y#W}i^ +dhV d +a ^ }vk lhlwtokv%i%>dKj 2_am"u'c9bkZ 3bU[b] W cR~Ow +ZT~`OudcU{v!Y hQzU} Px +Ms p` +M}nkd^hghvsml+rnox#%&*sjz#h4+l-r81.*9m~0r"j.8yoebe![y$&dm#<6i`D]rIt.`l!y&|'`j Yz\|y$mcm*#gdV}cnhaq!l$X tft#iz'eUzWS|Y~Pw`o&ler+pac3Y +djb^]Y kc]UxYwt1Wpp$[vu,d'felw%q$lrnk h` +i_o!^ [ ] W iJqZ~o!BiRw Hl[}VvWv o![{(Wtr'Ss[}zokqzyvy_~ss,*x^{Ro[v$%D,5U"#A <\"0T+P &>. * $;(:,A*B#(7 ! +   +'   )2 +,' +'8]x*&D +;%@Ec!Pp&7V; :Y9Sv+4U>c-O 3Y(L @.OKh5 ' 2!69T"%E &C )D!; +$>#:qA/OIk:]!= 1NFIiZy"C`E`*@Db"B\*"5f8K&B +rR]z2WuyB,$@#? Vt)GbBU/  ET:Rc@!(:!6%:  +  +# *)" +&'>5,H+ -F!7 +"%='A)    + (A 8T15J#&9('?'''<'?81' 3?,  + +%* $'  4!8%#@Zn,M[G].BJ[,/ 1/ )E.#6 +.D,B#;!@*C !"5"-F/G 5L  -7O*4G"5H(9  +&* ) +#4! +    +   4 ( +)"  , +     *  -#*/*A+    +  $  +   +    +-( +.-"   "   *0. ,! +5%>  8 :!@ %C&B!= "? (EVT|r0f"W}`g!x/gkSve$l"b>i7c :da&;bDg5YVhDoGo +Gq\Pz Nx +cy"] R} +^` jMyUHoebV Xgepid ^ [ pa rqebq\kmW~ +`u#fpnwjuc tojg `^skZ +dHrHmUxr&VvZZ]|&r~'dOq ]|y+o!n^knhT +TzjdW +r#Qy +QvPv b X~ 1Sxpq!mgW}][y)m[fNz k^ x wdo` ez~!vn{"Z v"qvo uz%uro~&mhx"}%<~(7,D9d{%9osU\WNu +lk0hs(z&v#06Vm9x+gz)y!wu'd/z&mw py!wvuZx!Dhb Yw2\gZ +Y] +o"V| g#^PxY~X}cWuk!Y|bkvly#ujwuz!inum|,f_l!m#b|bzo&w*fTv p!@qu'Su Y~ mohqjbbgc YpokfY}Qu]Psg`Ovkxvn7a~x~x {(c +ks|{t{# ys'z mrq3>9 ,J,J A'E'?%  % , +'A3 ;I($. + +E7\6& /+ Na,s;;M(;0%?'A0 + !-(.!",7@.3.!7- *' *(! 7OYr< !84!7 F_-65TUt2=]>\=[!%E :\)IIm)/M.22 + !!7-. +" % . 7O!*E ' !+J8U)F=]Ll%%B D_Gd 7QRte"D\0m@[})Ur!l19S +";8P :?[3L!7,. 4":@YYu'4P%1O3J  3 $ ,!  7 ,N; 3O  7H%1 6 $" +!1Qp/Oq3R'.C(+>": +"$=1J7 %A2%   +  1  Nf=,. Ri0B\- & + , -  +%$   &$9 *&1   +!   +$ ( +0 +/ # +&' $&>2 "++  6S"?#<#> 77!? +4 , '-[SX|aLo p,Sw q$aeDgX}KqU| OwBl@k` V~`Pu Y gY\^Ty _B_ k(p!W W +r"U}R~ZV g_ he]p j_ ni vha lVl^ fa b c jkho!WZpshlY ] +U`UbZ^R{S{ +S{ v%W| Kmobj%|'xn_| 'c}(olur# {{#rgckv!0Y o#h}'2up{$w!}%{'q#g_Pv cjHo]q` ok~(guq'rml '|z~zud f %knbS{ p$i0is%~#asx`faaFq El +q ~+2Xx0(x#x&2i;1z-~/~)}s},0q rdx*({'{ 3wktOr x*fgea1x#_[ aLp e!k$Mriv-hkVx cNroLtwvl!|$w!'w~#h euk~(x#fk!y-hb^hlsv}&ybohgu] +oru[ki i fbFncoh jY\w+%(og}tvsv"{tw&n +qPq:]<- -D"-J&$ +" +F & ) ) VfCF[2'E 5X'H' -  + !, 2@+> Mc%0L1I#*-; %*+?S'"1 3G/ & + + " DW8}]Rn {>Of'E`"Nj3.I AZF^*7S"A5V+K13Eb+,K > +7V. 22 4* 2 3- + ! +' 1@2 +*3*&7+!>,J+I4#@ $<) 3%9 ! +IX:&( .+K A]9M-@.@( 9VAf Gg&">#;#6K"" +& &89J#! +.)  *  + 7Q$*@),=)<-C-G@]3R-J(H. #2 (,B, $(*  -!';     +&%7( '         , +2-  +     ! "4  1   #"/ ) * + +$ +  $0 01 +"=(>3 +1&1J'8  &   .& 85 +-E1 . + 7 41+ + +3 $1 -5 (D"?(E"A"> < < 0 ( ++0QwFm ]TySxQvTz _LvRv ]SxMpe!U{X]e"e er+gt&jU~Rx oidz"iz,x5;`Y~Io]FpLr[hEpWV~ `\Hp`cQ{] +om_ gajy"hlsocMt[ _grpr] _` ]ekatmugbPskr rpbearxuy(zs"j +{ppr}__[ceYsl`{mwwtsuoledn^bdk,t~%twlz u&i%{v  nsrozknbY Qw +`xkwn[ +p~#zna g9dFo mZ~z$u ev"s 4~*9u&u(gr({,z$q_bal)z"{!qz"&xq-lw$&xggdeb tcqicbSy hVy Y{syhX~ a vxwml_nymik(|vv(rkopxpn zrp]&ookZp k~ib j jg od d{dd]f #m +|fxm !s +{r z$xviBc8 !'%-B8M '5AR()&@).?#'  +9K"9 Ib0,$: 5C#&;G+:M#9[s8P -J )?Ja>5S +Gb)AY!--#C 5W8["= #!7 /K:V(E*, "  +( 5. 0%) !  '@Y(.N=6+&?5P 8;_Rvj; %#4* #,A9 .H $/-FHf,[|40 +-B' +>\"9V#5R*C +"(D-L-MgE-  +8# " . 8 0 +/ ! ! + / +    0:X + %* )7#3"9 +6.//A Wo;+ +*A  % 1,I4 (7 06F 4L9V.L4-H'9 +$2& &) +"'3 *F4F + + $1  +     +"2% +    +   / ++E.   + "#) +     3/. *&6 + & . +  +     ' + +* /=4E *    #6+'.1 18 2!?, 34 1 *2, 7 /N: +: %@!=%C #A +7: (' &gp.u0j%__p$gar&|1k'l'4]\l%Y|h%fgVwZ~Ru U| +ju"{*6[wdXbf\Xjhgbfq"lVyY{Ntonfjhz&lt"~&|)Wo#\dZ qm^i qquujpihqc jrd}#w#r"cu#h~!}&^ vi yvt{vmrrw!dhxv{q|jpsqlnh rmb| jvkrp_gU} W~ +fquqoy ,e~ }q1"w {{uc ~r~*eerey$zxf%zflw}!ovcN}hlc/wt8m}$z#x"kr(n"s%|)nn_eGjfs,`dhoh[ ^ncg}+iLon&Xwr$jdfy!rbX~ iS{+hk[-on}o#u4z xrmx'}{vyg +~r |u}"wxu!szok k +zp xp ukqdr y~Rxfxx~|q r{vzqt{ |fv g o$>_4Db(E[!"5-6!1A+?Rp&/5 +!8  +D"7 ! +  +:C5-8"!.5C`z%s">Y*E%8 "1 );6Q i0 4Of < 0B./ # %5,- , (- Nc >WJeSlkC/NFm#;a=,J #; ) &.K-'C$B3 (-D% #*;   ) ,-H=]Jl0*)F1P`z9Ja%Vn.Yt,$  +AW'K+#92! .>%4N*HGe)6!21)C12N-JJj,@_!5 +4O!< +3R5 2  +%B`)*F+ /  >L/(/ATq//P/H?Q)+  (8H^2)=.>  +3 ' +0-8U$%  $ /@.*D9P!) " IZ/( +Pf,>\7 +56P/B$.-*< +&#(!5!6 +3A!%( ) '4   # +(.(     +  +  +   + 1 4 3 #3,&;4/$  +    + , &   +    + + + /B'*:#"        + # +(%4205(F33'A5 ,>[*'E6S! ; 26 9 +3: =<8 5-9 7, % +  OsFj Pof%Ko j#^fV l#Rv fU| d`o(UMom+n!k$gcr,kn{(u du)jen!bu(lpgv"ein&`~s*^}dmfkz#{'z0r)q&Rxv.i jahykkzxwrsr%cb _] v[ ckeakiy#i&gmh rr}pm)|$qrr v%lcc\k i vh hnuw#qkh o$due oj !g hPy m"cqbh qsd~'}m 0{wu }}np^ {~"oo,t$a&zo qusf[lph]v)U~_?fZNsfuwvt|"ot3q"_optil{+TuWyw#kstoru$z+Mr\Op Rs ^mk"^zu,dcKr v+[ +sb7e +q w qY} jtwpnr)#wxp}s$~z t {uz|qe p ezs pk p_[gn +r n w}V~wk qnntn jpgvzn`eqz xvfouo sWs*H#@ 0M-F Fay4#CE`Qd8&E[=]!=2 +2Oc}G^qF?T/"9 +@H/!'* );%: 2>p%No 8Q8S(  - +*::S2HSk>Mf -B- , # +!9  +.2@"* +:T)D:T!Ba7W B5YFh.Sv,Ih&2L-H,K;^"@a$6In->d&(J%     $'   "; 6V0&Og*7M $// ,; z7!>Ok*+ Gb":T.D)B6R"&C5U%Hk&*I +Yx780KY{0+G -%8)C$? *F6  + "$ +* +   -D'60C "6&A C^!%0=  +)".=) "3+:541 "7$ 9R*%BEY/;K(' 8P7RC`"->+@ +8W4M9 +5U:Z-#% +. +0D ( !" /.@% (? +)'8$ + ,         +    +    (</J'/ $4-)< ) $ +!% "1'! +       + +!  +/ )=& "/E'    ! * -.)?"= > 5 + &4.4 %B 'E9> +*G6,8626/,/8%@ %# "3FhQuKjWuh#U{n"W~ V~ Ry NsYgU T[Xl,h$r&[i_Zhhbn s#cc_o r%w)8nlv#w$u!y#-{$1y,jncl}(i]~p%^W]u%\ ky"kor pq#ovkxz$~+d|/y)Uv;bb jnhhsnpnnlmZ|pnqgo}+a}2g` +XY V_g[ Xe }!jk~)'|zyn s_ RzW} ^ojl,s-"tv &6f{uy'uly;d-s#Z}nsyqg fn_ _Z}!gw*n%Wc$b%l$e~0+{!j530s-^{ ct}%t#`{q!w'lm%m*lrpa YzevUx VtdaRqib~]zUzigW{V| +sl{"e #k }u_ i_ j twnx y"k ncrqo{~zluz}rl s{r wt +jjh f lh eurl wusia p +s}esw qq f +` c +^]vtj^u gtj ax +MfTm!-Jc$)e t~*k'9O0F,G!9- 12#2-- $1'-$(79! vM) *0 A`$>3F%-C  # ' &$13B$3 '85 +!' ' $"._mR  9Q!(A56S >Jl),Q +"F91 ++ 60N3 %8k6[-%EXw;/   ! +(' +!4Gc- #:If1+: #H[4Nm_&s&v4;S &.D 7/ +' 'Jl1:^Y|+Rs!>`On)&D@c5&! + 0,E4L"  ,6 +   "  ($9$,D"6 -:N&2 +5L$ Qh1+@& +CY)/ +& 0  )9 6  23AP1(6 &@ ,G5 6-F*B, +8O& 62 +#7) +( +(;"7 (3"!113 8L.*@>Z'= 4S"1C,6I-'*7!!   + +      &3     +  .#"  ",!2 1I-B,%-#85&7&  + 5K!    +% +!(  $ # #0)(    + + "  )>(=6L!< &C< "B)F)50O7 7< +%G '3 +!; +; 6 0,($?4,J513XyEk_fbak"W YPy X~jZ TN} [p&x1bn(h!kgp&`g_k!s'ghjs^0oku$jigqv ux%v%r _^l cp!;pp`r!h\ ol{#q^w%bvoyrwz z#{+}.jz)z+Eink[~ j2s ~!z"31y,( *=)-@%/(= +!7'($8&;2&;#   +Wj:$)3*2PB7[:)D #+9T%3 B`"?_1P'E .J.      ( / !. .O$A 1 +4L)#<3`}@'F'D/K.KMr:Ws> '??]!6S9O 9M)0H,! !+M  !1 +!1 3#6&;4 +( '# !9#!0$#8": 4 6O 3%*) 2E(  1I!-+@`1%J;])-JD`'   +   +" % +.'  +    !   + " #. +   1C -:S'  ! $#+"1 $ %-?!*<$$   $ '(=/D2J)F'F4@5R+320)F= +=a823 /3 ,4 3!@#F "DEc)4 ; "?W|RwZn\u0Tz [QyNybhX cf_^`a_g ek#bgq$V +[ZT +gZ b_|+s ^ eWbf^aVvs%mZ~ \kn#hflogZ cdc^Qxav^v%y#ebmg]z".|*lmnhX} ckty"cgv t,~.d~s'v,n!mgrvq=s$/]}X}_u#]ux#Rz\ ^ eOtZ +r"qpa W}|"jkr] +a ] k!m}1[u,2asytr!|{n{sr4ur$c {(hfe^U^Q| d\}3bche] kf [~acnk~.|+1-3D}1j u*geMl#Kk$) @\$Hg((K;aBg#&K4W Rwj22?S!F_(?^ 0LXo;  $ ") &.:["Ln ?d=]2N 3O hF+6U#'+7!'%-+,# (/ "" + + # 1 Lh2 +.G5O & ,&8D`*$9%Ea,2J.<[5R* ,=!+ = 4R4 + + +  "5'+;.C + ,*,(A 1G-$;11<% + - 33N*&F!?2;-G&;"5"  14: .' +/ (* ,  +   + &3  + +    + !!43M' $4*"*9 #"/' *:!'8$)  %% ) # #)1H $:#:,$B!C !> "='C6$F6#? &F0V #F0 6 *2 + %4(I&G 'D <1&B3 Uz CkR|``]icVZV^Zedf[\NtZ~^ \e#Lr +Jt FrQ +_T\a OxT|ddZeeh"a!b!^eHngcp!_m]mnrhjibffgT}Z +,X o$2Y ^l!o u"t ~('tl}'o1rk} n}$ime{"j{*oj2k0y"uj y pr#QwQtihUy db hiPwbc]]l] +w(gcW~ novo~._m"w.e#b"Vy r~$k kq|sys}|lsx-(zfjc},ce Mr +m0Dl Kq[[hX ejplg!a6x%,:v%Ar15l}/e<3U a oSu u(nk}#pi ^ b\nz(|&o| (x!s}moipwFobypcg^cvllus)isqpX +Krd lg f +\vj[ hSye^Mq QtSwZQn{'\Rx y(fxsv i~)}'#x{}ytx~n&n`} On~)pnfv)a\y p#\wSu` +V}?eLse lYw +jvi{mMn +A_;XSn?T1C 0OgY}u#Uns3)4?PEZRo$^pD "%*1 & -"8 7J-$" # /-"42& +.'3& $  + '   +%3$.J"=&?7 *@. > $@.If34ZCg/(G4  # +     6= 1!* 8Z!?\) # 51'E 956U>_/NMMf+2*IDh>e?\':Q* *  + )!;$' +&4 =8 ?R+1 %2 !6@K0 +$.;!") +B %( +"2@%";4 6 3O6Qp-4 ) -  !%Le(*E .F-E4I #>$= #! + %.  +-#5+F 'C +;7"9- 3- ,,' 1 +#B.F  )>! + +% 2+ 9 "B */,B +     (;&* (7 -*A ))6S(F, +!  8)D9   ! +    !7 e V{ MwLu`!WQ}STSN|RFv XMvU|gce$YYVXakcbdc``l`ekaiv)ddk#m#[m$^dfm([u)y-igdljjg_fglgjq t"mjp{$c pu\v&oejowtwlP{Rx cv!Ty k*jfa lb\ mgT| +Ov[ kQw ``q!Mrn'p+`]R|Yb_ b d ff$mjenm +dXa ldnjVy+s$o!{*-u)Sx^#\|cflZ kosf5^~b,M !A4P!7N,+*0 +) *8&B'#/&9* -    // 5 1&<   &=(G)B4UQq"4P&F'B46Q3754G!!3 ,?% 5#=/?! $5C^,3$ $779 :+,F% )B'E !> + $6*">- +,A;T0I75)!  / ,+*1)A/R.K/%  " +  + 5 " ";0 + !, & "0 +!!$ 1 ,9 *#4) + +  '' #:,I'" * +!  +) 5$  ) !  #!  ' " # + + " ) +*!4% +'$  7 *IGe(.!; &E!? 2< >#B @3250v/>u.=t/>u.=t.=t/>u1t/>v->t0Aw0Bw.Bw/@v->t.?u->t->t,=s,=s+t,=s/Aw-Av,@u,@u/Cx.Bw/Cx0Dy.Bw.Bw.Bw/Bx0Aw0Bx-Av/Cx,?w.Ay/Bz-@w.Az-Az+@v-By,Aw,?x+>v.Ay.By/Cy-@x-@x/Bz.Bz0Dx/Cx2F{1Cy5Dz2Cy4E{2Dy0Dy1Dy/Dz1F}4F2E}3F~3F~3F~2E}1D|4E}3D|5G~3G|4H{3G{4Ey9F{6Gz/Dw0Dw1Dw4Cy1Ax1Aw5Dx1Cw/Cx0Dy1Ez0Dy.Bw0Dx.Bw-@x-?w-@x-@x-?w-@x,Ax.Cz/Cy/Cx.Cx/Dy0E{0E}0E|1E|1C{2E}6I5G6I5I}5I~5I~4I~5J9M7K6J7K:N;N;O;O9M9M>R=Q?S=Q=Q@S=PAT?RCUEUIZFXGZI\J^J_K`M`ReShRhShTiShQfRgQfPeQfQfSgSgSfQeObOcKaJ_LaK`J_J_K`K`KaJ_J_K`K`J_LaI^J_I^I^I]J\J]H[FYGZEZEZC[CZAXCZAXAX@WAX@WBYAX>U@W?T@U>S@U@U@U>S?T?S?T>S@U?T@UAVAVAVAVCXBWCXCXBWCXEZDYH]EZDYDYEZEZEZF[F[EZEYGYI\I]H^G\G\I^G\G\EZG\EZG\G\G\G\G\G\J^J]I\G]J^FZG[G[H\H\H\H\H\G[H\H]J]J]L_I^H^H^I^J^I]J^K_K_MaKaJaKdIbIbIbIaMbLbMaMaLaKaMcJ`KaLbH^KaJaIbJbKaNdPcMaNbNcNdMcNdOeOeNdPeOdNdPfPeRgRgRgRgRgSfSfTgSfTgTgTiTiShTiUjShTiShRgQfRgRgRfQeRfPdOcMaNbMaL`MaL`J`KbJbM`MaObLaLbLbNdNdLbNdMcKcLdMdNcPfMcMcPfOePfPfOeOeOeOeOeOeNdObOcNbNbMaLbKbKeIaLbI_J`J`F]F`F_G`G_E]F^G_F^JbIaKcIaKcKcLdKcNgPePfPfOePfPfOeQhOgNfOgPhNfOgOgPhRhRhQgRhQgPfOePfQgPfQgNdMcMcOePfNdOeMcLbMcLbMcKaKbNbNbM`MdKaLaNbNdNcOaLaKdJbNcMcLbMcKaKaMaOdLbL`NbNbNaNbMcMcLeMfNfNdMcOdMbNdPfNdMcMcOeOeNdPfLbPdPePfOeOeNdQgOePfPfQgRhSiRhTjRhSiRhQhQfSfShTjQiNgMfMhOfPhSiSiShQfRhPgRhPfMeOgNfPhPhMePhLdNfMeOgOgSiOeRhPfRfPdThPdQeRfSgRfQeReQgTjQgRhTjRhQgRhRhRhQgQgTjPfSiTjQgSiRhPfRhRiSfRfQdShPfPfSiPfQjOhRjQgShReThShRhRhSiSiSiTjSiTjSkPhQkRkTiSiRhRgThSgViRgRgUiUiThShRgShRgShRgUjSgUkSiRgSfTgTiShRhQkRkSjSkShVkUjUjRiSiUjTiShTiTiShRgShUjUkTmRkSlSlUiUjUkRlRjSkSlQhUlUlRiTkSjUlUlTlWlVkWiWjVlWlSlUkSmTmTlTlVjUjVhVhUjTiVkUjUjWlWlUjSjTjTkSjSjTkUlXlWkThWjViWjYmVjUjSlVmUlTkVmWnXoUlRnTmWpWmWnVoUmSpUmVoQmRnVmUnWnWlUnUnUnWpWpUoTjTlUkUlTkWnUlWnVmUlUlTkUlWoVnWmVpVnXlXlYmZmWoXoXo[sYs[s]vYr\u[u\u^u[sZr\t\t[s\t\tZr\t\sYtZsZs\r_t`s_u`u]u_v`v_u_x_x_x`w`wfzb{e~a}b}efhecb~czd|d}f~ihhijilkjkf}d{e{d}e~aa~ddec~b}gf~e}^{^y_za|_y]z^zbz`xay^y`{_za|_z^y^y`{bzbxczczczcydycybzf}d|e|cyc{c{e}d|e}f~ccggkjjploqpprssvutrrwz}}~ׂ؂ڃއ⏢摣呦甧瘪坮睯랮ꛯ囲꟱꠰鞭䠭㜭⚭断픮랩蛥蘧虧攤ݕܓޓޖᔢߗߖޖ*;n*;p*;q,>s.>u.=t,;r.=s/@s/?t-;s/>u/>u.=t.=t3@x1>x0?w.=t,t0Aw/Aw1Cy1Bx.?u.?u->t.?u->t.?u-=t.=t/>u-=t-t/@v-=t-?t0Dy.Bw.Bw.Bw0Dy/Cx0Dy0Dy/Cx0Dy1Dy0@w0Bx/Cx.Bw,?x.Ay-@x,?x+>y+?w+@w.Bz.A|0C|.Ax0C{1D}2D~1D}0B{0C{0Dy0Dy0Dy1Ez/Bx3Cy2Cy3Dz2Ey2F{1Ez/E}0D{0D|1E}2E}/C{0C{0D|/C{4E}6E5G~3H|3G|5Hz5Fy5Dz5Fz1Ew1Dw0Ex1F{0Ey0Ez0Ez.Cx0Dy.Bw/Cx.Bw.Bv2E|/Az+?w*?v*?v*@w,Ax,Ax*?v+@w.Cy,Av,Av,Av-Bw,Ay/D{.Dz.C{.Az2E~3F}1Fz3G|3G|2F{4H}4H}6J6J7K5I~6I~6I9L8L8M9M9N;O=QQ@S>QBTCSGXEYGZGZG\F[H^J^KaLbMbPeQfPeQfOdOdPeTiShTgTiSgPeQeQcMcMbJ_LaK`J_I^K`K`K`H]J_K`KaH]J_H]H^H]I]I[GZEXEYF[EYDXAXAXBY@WAYS>S=R=S>SS@UCXAVBWBW@UCXCXEZDYDYCXDYCXCXCXDYEZEZEXFWEYEZF[EZDYBWF[CXBWCXCXDYDYEZDYCYFYEYH^H[H\FZFZFZFZFZDXFZG[H\H\H\I]G[H\H\I]H]H\H\H\J^K_J^L`MaKaI_HcHaIaI^JaI`L_K_L`JaJ`KaJ`KbI`JaJaK`JaKbNcM`L`L`NbNeNdMcNdNdPfMbPfNdPfPePeOdQfRgQeReReTgTgReQdRhRgTiShTiTiRgShPeOdOcOcNbNbNbMaNbOcJ^K_NbK^LaLbJbLcMaMaL`MaLbMcMcMcOeOeLaLdJaKcKcOcMcNdOeOeNdOeNdNdOeMcMdNdOcOcNbNbL`OcLbLbNcMbLbKaKaJ`KaKaF`E^F_G_H`KcH`G_IaH`IaIaKcIaMeLdOePfOeNdNdOePfNdOgOgNfNfOgOgNfMdRgQgPfOeOeOeOeOeQgRhQgNdOeOeOeNdOeMcLbLbMcKaKaKaKaNaMaOcLcKbMcNeMcJ`NbMaKdLaMcKbKaMcMcNdLaMbLbMaNbNbNaNcMcLbMcJcOeOeMcNcNcNdNdOePfOeNdOeNdNdNdOfMcNdNdQgNdNdOePfRhPfPfRhQgSiRhTjRhRhQgRhRhPiOgNhLeOiNfOgRhQfPfQfRgShTiPfLdMeMeNfOgMeNfNfNfMeMeMfRgRiSiOdQeSgRfSfSfSfSfRfRfRfQdQgPfSiOeSiQgUkTjRhRhQgQgRhQgSiRhQgQgRhRhPeRfPdSgQeQgRhSiQgOhRjRgQhQhTgSgRgSjTjRhSiPfSiSiTkQkTiTiTiTjSiTiThUhVjSgRgQgUiSgTgPeTiPeRgShSgSfOhSjThShTfTfRiUjTjRkQjOhQiVjRgShUjPhQhUjTiTiRgShQfShRgTiThPjQiQkSkViVkUiQjRlRiQhQhRiRiQhRiSjSjSjUkWkWlVkYjUlVkWlRjWjWlSlTkTkViUhUhRjWkWkVkShUjVkVkRjSkTmUmSkUlTkTlTkUkTmSkTlWjWkWkWkUiSlSjSlSlSjWnTlVmVmTlVlUpWpRpWpWpXpWoVoTnVlVlUnTnUnUnUnVlTlYkTmUkUlUlWnWnWnWnWnUlTlXiXkTmVlUlZmZlZmVnXoWnYp\tYsYrZs[tZsYs]t[s]u\t[sYq[sZr[s[sZrYq[sZtZq[s`t`u`u_u^w_wayaz_xaz_x`wczfxc{d~dhghhhkf~d|gjkqomlkqqsqogijfhfikkilnolohca}c~b~a|a|`|`|`|`{b}da|a|`{`|c{c|dzd{czd{d{e|e|f{b|c|ezc~c{d|c{hf~fdjkkjmmnllpoportssrqsvz{}~Ղ؃؇ދ⒤㛪枰頯䩸欹媻햮ﭸ웨痨㕦㕣ݓݔޒݓޓݖݚ*;o+r1Au/>s.=t.=t/>u-u->t->t-@u-Au->t0Av0Av->s.?u.?u/@u/?u-;s-;s.=t/>u/>u-;r/=t,;r-;t/>u*9p.=t.QSAVAV?T@T?S?T=R>SS>S>R=R>S>SAV@U?T@U@U@U@UBWBWAVBWAVCXDYAVAVDYDYCXDYCXBWCXCXCXCYCYDYCXCXCXEZBWCXCXDYCXBWDYDYDYCXDYEZFYG\F\GZG[G[EYFZEYFZH\G[G[FZF[H\H\H\G[I]I]G[G[I]H\I]J^I]H\I_I_I`HaJ_I`KaK_K_J^K_I\I]K^J^J^J]I]L_J^K^J^I]I]M`MaK_KaMcMcNdMcPfMbQfMcPfQeRgRgRgQfRgSfReReTgRfPgRgRfShShShRgRgRgOeOdNbOcNbOcNbNbL`L`NbK_K_J^J_I_J_LbM`L`K_LaLbKaLbLbKaJaGaHaIbKdKdKcMcNdMcNdMcPfNdPfOeMcLbLbPcOcNbOcOcMaK_McNdMcOeQgNdKaKaLbLaLbMbLaJbKcKcKcKcJbIaJbMeJbMfKdOdPfNdOeNdPfPfPfLgOfOgMeNfNfMeNeOePfPfOePfPfPfPfPfNdPfNdNdOeNdMcNdNdLbKaKaKaLbJ`McMcM`MaLbMbLbKaKaKaNaLaNdLcLaL`LcMcLbLbL`L`L`MaMaMaMaMbMcLbNdNdJbPfPfOeOeOeNdMcNdMcMcOeOdMbMbNcNcNdNdNdOeNdNdQgNdOePfPfPfRhPfPfRhPfPfOfOhOgPgMhMgMgMeOgNgNfOgNgPhNfLeMfPhOgOgMeNfPhNfNfPhLdOgPgPfQgQgNePfPgQgQhQhRhRhOfPfRiPfRhPfRhQgPfPfQgSiRhQgQgPfRhPfRhTjRhRhRhRhPfPdThRfSgQgRhRhPfPiUiPfRhQeQeSgSfQgRhQgRhRhRhSiSiUjSiSiTjTkRiQgRfRfThReRhThSfUiShTiNcShQfQgPjPhRjThShSgRfReThShTiSgUiVkThPeTiTiShOhOgQfPeRgPeShRgTiRgShShSgRkRkOhSgXmVkUiUjPhQhPhPgQhQhTkTkRiRiTkTlVjTiUjUjXmWlVjUjWlSkUlTjSjRiRiSkSkQhWlTiWlTjUiViXlWiUgVkTkTkTkSmSmTiTkVlWjVjVjYmWkXlXkWjViXkUiWlWkXkWkUlVmUoWoUkQlUnSlUoVlUlYlWnUmTnVlUkTlXkWjSlTkVmUlUlVmUlWnWnUmXjVkVjXlXkYmRkUlVmVmUlXoXoZpZsXqYr[tZs\rZrZrZrZrZrZr[s[sZp[rZr\t]t[r\r`t`u^s_sbv^w]u`xax`y^w]w`wc{d{e|f}hgkkjjnkjjooptuvssutsppjimmoqqrrpqpqnmjg~hhffffegfedcb}cxcza{d{f}e|e|f}f}e{e~fazb{d}czc{f~hgjklmjlllqmpqqoopsnqpprvyyy~ׁՅوߋܘߢ岼흯휮휮훯읪ꚫ昨囩䘥㖨㒣ߕ䔡ᒡ):n):o*;q*:p):p);o/?q,=p,

u/>w/>v/>u.@t->s->s0@u/?v/@x0@y/@x.>w/@y/@x.>w0Ax+=r.>t.=s-t.=s,>s-=s.=t-u*9p+;r0Aw->t0@v.@u-Bv0Dx/Cw.Bv.Bw/Cw-Bv0Dx1Fz2Fz.Cv2Ey2Cx.?u2D{.Az/Bz/Bz-@y2E0C}0C}2D|1D1D~0C}0C|2E|2E}4G~3G|1Ez2F{3G|4H}2F{4H}0Cy4Dz2Ez3G|3F}0D|/D{1F}/D|0D|/Bz3C{2C{2C{2C{4E}3D|5D}6G~5G}5I~4H}4I~5J~3G}3G|4H~4G3F5H3F|0Dx0Dz-@x2Fz-Av-Av-Bx-@y/Bz+Ax+@w,Ax,Ax,Ax*?v,Ax-By*?v+@u,Au-Bw,Ax-By+@w,Ay/Dy0Aw0Ez/Dy1Ez1Ez/Cx/Cx/Dx/Cw3H{5J~6K7I7G~6G7I5I4H4H6J5H6I7J6J8K:M:M=P>QQ?RDV@TAVBWAVEZG\DYC[F[G\J^J\NaM_LaPeNcPdShRgRePcSfSfSfQcPcOdLaLaMbK`MaL`J^L`J^J^J^H]G\H]I\I]H\H[GZEXDXDYCX?WBZAWBWAV@UBW?T>S>S>S>SS=R>S>S?T>S?T=R?TBW@UAVAVAV@U@UAVDYCXCXDYCXDYBWBWBWDYBWDYCXCXBWCXCX@UAVBW@UAVAVBWAVBWDYDYDYCXCXH]EZEYCZCYEXDXCWCWBVEYEYFYEXGZG[I]H[FYG[H[GZG[H[I]I]I]H[J_G]J_K`J`I_J_I]I]K_J^K_K_J^MaH\J^J^J^K_J^H\L`K`I`J`KaLbKaLbLbLcLaOdOdNeOfQeQfPeRgRgSgSeRfReTgReShShOeShRgUjRgRgOeOcObMaNbL`NbK_MaJ^K_K_K_L`K_K_L_LbLaMaL`L`L`L`J`J`H_J`JaHbJcJcKdIbMeJ`McLbLbMcMcMcNdNdLbNcQcNbPdPdNbOcObOdNdLcOeOeLbMcMcMcMcMcLbMcNdKdKcMeIaKcMdLdJbIbKcKbQfOeOeNdPfOeOeRhPfPfJdNfNgOiMgNgQfPfOeQgNdNdNdOeNdNdOePfNdNdNdMcMcMcMcMcOeLbMcLbMcLbPeQdNbPfNdMcLbLbMcLbK_LbNaMaM`K_KaKaMbL_MaM`L`MaNbNbPdOcKbMcMcMcLbLbMcNdMcMcMcMcNdNcMcKfLfMgJdKcNfLdLfPfOeOeOeRhQgPfPfQgQgPfRhQgPfPfRhOhMfOhKgMgNhOgLdMeOgOgOgMePhNeNeNfLdOgNfPhLdLdNfNfOgNgOgQeOeOeQgPfNdPfRhQgRhSiQgQgQgPfRhQgRhQgQgRhQgSiQgSiPfRhRhQgRhPfQgRhQgSiPeSfReQfPgRhPfQgQfTjQgRhQgQdRfSgSfPgPfQgOeQgRhSiUkRhQgQgSiSfSfSfRgSgSgSgShSgVjSgThShRgQfRgShPiQiPhThQgUhUiTiUiRhSiThShVgRhUjRgRgRgUiRgShPeShRgRgRgTiShRgRgTiTjOiTlThTiQfShUjThSgTfOhPhQhRjSkSlRlSkSlUiTiVkUjXkVjYkUjWlSmSkQkSlRlQkUmTlPiTjTiTiViVjWiXkUjWlVlSjWnSkTmSlQjVlTkXkWkWkThVjVjWkWkXkWjXkYlVjUiYmTkTkUkWqTlVlTnUnSlSjZlWlVmVmWnVmUmXjXlWkVjTmTkVmWnWnTkVmUmYkVjVkWiYlVjXmWnWnVmTkWnXoWnYpVpWpZsZr\t[sZrZrYrZrYqXsWp\sZq[rYq]uZr\t\t^s`u^t]w^v^v_w^vayaxaxawc{cze|cze}hkhmmppqqswuvyzyuwxyxqnlknonspsqxvtsqpljkkjlokmliigfhc|b{dybxbwczbyf}d|fgd}gd~d{e}e}hjmkmlmnmmrsonprooqprroprvvyx~Ղ؇ڎڝߨ⵽ꬵ윱죱죱휰꛰ꡯꡱ줴렬혩䡱ꞯꚪ暪閦㒣ᗤ⒠*;o*:o':p(;q*:p+w->v-@x,@x,@x+?v+?w*>v+?w,@x.@x/@x-=v.x/>x2B{/Ay0By/@u.=s0>u/=t0?t-?u/@v-=u-?x,?x+>w,>w,>x-@x/B{/Az1D|0B|/Bz/B{0C|0B{2C|1B{0Ay1Bz/Bz1D{.Az/B|1D~2E1D4G2E2E4G2E5G4H~4H}4H}4H}4H}3G|3G|3G|4H}1F{2G|1Ez1Ez1D}/D{.Cz/Cz0C{/Bz-@x0C{3C{4E}2C{4E}3D|5F}5G}4H}5I~5I~5I~3H|3H}2E~3F~4G4G4G2E~0Dy1D}.Bw/Cx.Bw-Aw,By-By+@w,Ax*?v,Ax-By+@w,Ax,Bx-Cz,Bx+@x+@x,Ax,Ax+@w+@w.@w0Aw1Aw/Aw.Cw.Bw.Cw0Dx1E{0C|/B{2D}3C|4D}5F}9J6G}9J6G8I:J:K7G8H9K6J6J;MS>S@U:O=R=RS?T@U>S:O=R=R>SAV@U@U?T?TBWBW?T@U@UEZBWBWAVCXCXCXCXAW@U@UBWAV?T?TBWAV?T?T?TAVAVAVAVAVBWBWF[G\BWDYBYBXBXDWFZCWBVDXEXEYCYAXDZBXDZBXBYE[CYE\D[G\FXDXH[F[F\F\I_H^G]CYF[GZFZFZG[H\I]J^H\I]H\H\J]K^J^K_K_K`I_McKaKaLbLbKaMcNdNcNcNdPfRfOdQfQfPeRgRgSeSeThShRfSiQgRfShTiRgRhRgQdPcMbL`L`MaOcK_K_J^J^K_J^G[K_J`I`LaK_I]K_I]I\K^K_J^I_J_J`IcJcJcJcLdNcMbMbMdLbMcOeMcMbObK_NbL`OcNbOcNbOcObOcObNdMcNdOeNdNdKaLbNeMcKbKdJbIbJbLeKdH`KcNbMcOdMdLbNdNdRhSiOeOeOeOeQfNdPfPePePePfOePfNdNdOeOeMcMcMcLbQgOeQgPfNdMcPfOeMcLbNdMcMcNcNdNdNdPdNdOdLaLbMcLbLbNbL_OcL`MaM`M`McLbLbKbLbObL`K_L`L`MaNbMcNdNdNdLbLbMcMcMcNdOeMcNdPfOeLeMfPgKcIaLdOcNdMcLbOeOePfOeQgNdQgOeRhPfRgRgNfLfMfLeLcNfMeMeNfOgNfPhNfNfNfJeLfLdNfLdMeMeNfNfMeNfPhNfMePeOeNdNdRhQgRhPfPfPfOeRhOePfQgSiRhSiSiSiQgPfQgOeRhPfPfQgRhRhRhTjQgQgPfQgPgQhRiTjQgQgRhSiSiQgRhPfSfQeSgSgRiPfQgRhRhRhTjPfOeSiQhTgRgQeQeRfPdSgSgRhQgSgThSgQfRgRgPeQfShOhOgQfShSgThThThQiTjUhVgSfSfQgVkOdShPeQgRgRgShShQfShRgRgRgShRgRgShNgTiShTiUjSiSiTiTgTfPdShSfTiUhSgUiRfTiShTiSiXlVjTiTiVkSkSkRkUnTmRkSkUmQiRkTiUkThUiWiViVlVkWmTkTkSiSlRkRkTkTkUhWkWkXlVjVjVjSgXkViXkXkYmWjViTlVmVmVlXkUmVlUoWlUkWjWjTmUlUlWnXjWkXlZmXlViWjSkUlUlXoUlWjXlWkXk[mVkZmVjXoXnYpWnVlWm[rWoYrXqZsXr\tZr]uYsXnXqZq\uYq\qYpZqYtYp]u[t]q^sau_t\u_w^v`xayayayaybyczd{e|f}f}jgnklnptotuvyzzz{|~xwsrloosruuzw{trrqrqonpiomkolkjlki}h~e{g}g|gg~e|h~ggjhg}gjgimmmoopotsqqqsuoqnqqtrrquwx|w~уՈ֑٣ݪ޶뵾ꣴ領頰잮Ɪ꠰럯롲졭힫ꚫ靭瞮롱쟯죳Ꙩ떥瓢㗤);q'v/@x/@x)v/Bz*=u+>v-@x-@x.@x/@x/@x0By/Ay1Bz2D|0Ay1Bz0Az0Az.?x,>v/@x->u.@u+@v.Ay/Bz-?w-Ax,Ax,?w-@x,?w.Ay.Ay2E}.Ay0C{/@x0Ay3D{1Bz1C{-@x/Bz.A|0C}0C}1D~2E3F3F3F3F3F3G2G}3H|4I}4H}5I~5I~5I~4H}4G|5H}2F{5I4I0E|0E|.Cz0D{.@y1C{1D|,?w3E}5E}5E}3D|3D|4E|3Dz4Dz3G{1Fz2G}3E~6F7I4G3G4G5I4H|3H|2Fz.Bw0Dy,Av,Av,Av.Cz,Ax-By-By-By-By-By,@v.@x.@y.Ay,Bx+Aw+@w+@w+Ax,Ax-?w0Av/@v0@w0@y/B{.@y-@y/Ay1@x3D|4D|1Bz5F~4E}7H}5F~6G6G7H7H7H8I5F~7G7F6F7J8K8K9NTS=R>S>S?T=R>S=RS=R=R=R?T?T?T>S>S?TAV@U>S>SCX?TDYBW@UAVAT?R@U@U?T?T>S>S?T?T@U>S?T?TAVAV?TAVBW@UBWCXCXAW@VAXBWCWEYCWDXEY@W@VBXAWAWBXAWAWCYBXCYBXDYAXCZDXCZDZCYDZCYE[F\E\DZFYEYG[G[H\H\H\H\H\H\H[I\KaJ_H\I]J^H_I`I_I_McKaLbMcOcPfNdNdPdPePeQfOdPeRfRhTiTiUjTjSiSiUjShRgTiThTgSfPcMbLaMaMaL`I]K_I]I]I]J^J^K_K^J]K_J`J_K_J^J]J^J^G]H_I`J_J`HbIaK`KbHaGaHbKaLbMcLbLbNcLbK`MaL`NbOcOcOcNbNbNbMbMdNdNdMcJ`LbNdNdMbNaMbOdMbNcMcMbOdMbNcLbLbLbLbLbMcMcNdOePfPfPfOePfPfPfPfPfPfMcNdNdOeMcNdMcMcNdNdLbNdPfOePfQgMcMcMcNdPfOeOePfLeNcOeOeMcLbLcHcG_K`LbKaJ`NbK_K_L`MbKbKaKaMcKaKaMbL`MaOcNbNbOdMcMcNdNdLbNdNdNdNdNdMcNdNdMcJaJdLcIeJeLcLdPeOfPfNdNdOeNdNdPfNdPfNdNdOeLfLfOiNiNgNgLfMfLdLdMdPdMfMfOgMeNgKeMeNeNgLdNfNfNfNfMeMeOgNfOfRfPfPfRhPfOeRhPfRhPfRhQgQfSiQgQgQgRhQgPfRhSiPfRhPfQgPfRhPfQgOeRhQgRhPfQgQgRhRhQgRhQgPfQgOfQgQhUgSgSgThQeRhQgPfRhSiSiQgRhQgPfRgReRfThQeQeRfQeSgSiRjUhSgSfRgOeRgRiRhPfRgTjSjSiReRfRfRfSkRhTfTfTgPfRgShQfRgUfUiUiWjShRhShRgQfShRhSfSiShTiThTiRgRgSiTjTgReUgRhSiShRgShRgTiTiUhRiTiUjTiSiViWjUkWlSmUmSlSmQkRlRlSkRjRkUhShRgUjWlWmXlWlVlUlUlTmQjQkSiQhUkViUiUiSkWiViViXlVkXiUhVjWjUkTkTkVmVmSkXkUmVlVoUpVlVmWkXoVmWnWmViXlYlWkXlWkWjXlUmTkYpUlXlXlVjVjViVkUmVmWpVpVpTmUoVoWnYqXqXqYs\t[tYpXoVl[rYoXqWpZsYsYpZqZtYq\tZrZs[r[s^u]v_w_w^v\t\t^v_waybxd{czf}e|iklmnprruuvwxzzyҀӃ؀~{|uropstvwxyx{xstpqppqppqpnnoqmnjjh~ifiijgijkkkkjmprnpttrvuvxwtttwrroqttsuvsvxu{ӅׅՑڤ㯻簷޸饱枮陰쨷﫺젰랮꟯렰족ꡱ뜭ꝭ霬蛫砰렰족꘧蕣(s,=s->u+v.?w->v->v.@x-@x+>v+>v,?w,?w+>v,?w.@x1Ay0Ay1Bz.?w0Ay0Ay1Bz2C{2C{2C{0Ay.>v.?w,>v/Cz.Bw.Ay,>w+?x,Ax*>w,?w0C{/Bz1D|/Bz/Bz1D|0C{0C{0@x1Bz4E}1D|-@x0C|/B|1E~1C}3F3F3F3F2E4F2F~1F}1E|2E}4F4G5I}4H}5I~7K5I~3H}3H}0E}1F}1F}/D{.Cz.Cz0E|0F|0D|0C{/Bz/C{1D|5F~0C{5E}2Cy4E{5G|3Dz6F~6G5F~3D|4E}3E}3E~4F~4H}2F{4H}3G|1Ez1Ez.Cx.Cy,Ay-By-Bx-Bx-By,@x-?x-@x,?w.Ay.@y-?w+>v+Ax,Ax.@x,?w-@x0Ay/@y/@y/Ay-Ay/By0Cz0C{/@x/@x2C{2C{2C{3D{2Cy3D{5F~4E}6G8I9J9J8I9I9I8I7J7L8M6L9N:O;PT?SATBUDWFYH[J\I\J]K^J]K^N`P`N_PbRfObOaMbMbLaMbK`LaNbL`H\K_I]I\I]I]G[G[GZEXEXEWEWFYDYDYBW@U?T>S=R=R>S>S=RS?T=R;P=R=R>S?T@U=R=R@U>TBWAV@U>S>RS?T?T?T>S>S?T>S=R>SAV?T@U@UBWBWBVAW@V@VBX@VAWCVBVCWBV@W?U@V@VBW@VAW@VAWAWBW?W=V@T?UBXAWCYBX@VAWCYCY@VBX@XAXBWCVEYFZEYFZFZFZH\H]I^J_J_I_I]K^L`KaJ`J`J`J`McOdMbNdMcOdPeOdQfPeQfQfQfUjWlUkTjUkUkVkTiWlWlVkTgSfPdQfOdMbMbK`J^K^J^K_I]H\J^I^J`J`K`J_K`H]J`H^H\H\H\I]L_I_I_K`K`KaJ`G`HaI`J_J`McMcNcNcNcK`L`K_L`NbOcNbL`MaOcMbMbNdMbJ`KaNdNdNdMcNdObNdLbLbKaLcNdLbKbLbLbLbLbNdMcNdOeNdOeNdNdNdNeOeOeNdNdOeOeOeMcMcOeOeMcNdLbNdOeNdOeOeMcOePfPfOeNdNdNdMcNeJdLeOdOeLbLbKaJbF^G`LbKaJ`L`L`NbObK`JaKaIaLbMcLbKaM`L`J^MaJ]L`NaNdLbMcKaMcLbLbLbLbLbNdMcMcOeNcLdIdJeLcPcOdOdNcNcPfOeNdNdPfOeNdNdPgLeMfRjLeMhPiNgOcQfMcOeNeOcPePfNfLcLeIeMgLcMeKcNfLdLdNfPhNfLdMeMdPdOdPfQgPfOeSiPfRhOeNdNeNhMfMeQgSiOeRhRhQgPfPfRhPfQgNdQgPgPfRhPfQgQgPfPfQgPfTjQgQgSiQgOePfSfQePdPdPdQeSgRiQgPfQgRhRhQgQgQgOeQgOfPgQdQeQeQePdSgSjQgSfRfQeSgSfThTgQhRhQgRhQgRfSgSgUiQeSfRhQfRhQgQfRgQfRgShShQfQeShThSgTgSfPeRgRgUiTgTgQfPeQgOdQfSgSfReUiReRhRgQfShRgQfShQgThUhTiUjTiUjWjUiTiUjSkTlVmTlRjTlSlSlSkSkRkVjVkWkSkRjSjRkRjUlTkSjSjTkSlSlSjVjVjVkTkSjSkRkXjVjXlWjUlTkUlUnUkTkWjXjWkUlUlSmVpWlWmWjUmTkTkSkVnXlViViWkWkUiWjUlWnSjTlVjVjVjXlUnWnWoVoTmWpWpVoUnVoWpYpXqZs\sVoZpXoXo[rTkVmXqZsXq[tYpZqWtXpZtZr[r\rYp]s]u]u\t[s]u^v_w^v`xc{d{e|f}e|h~ikkmlrsuquxsutvv{~Ӏ~Ӏ|zxttvxttxwwwvtqtuursqsmposqoopupjljkljkkikmmmpurpsqwwxywvyvxywususvutstrurt{~утч֍Ԝܩ岺Ⲽ޽ꚮ홱掠ꢲ롱힮젰雫睭闧⛫坭靭뛩뙨險뚧&;p&;o%9n(t,=s,=s):s,=u->v,=u->v,=u,?v,?w+>u*=u+>v+>v,?x+>w-@x.?w1Bz->v1Bz.?w/Ay.Ay1Bz2C{1Bz1Bz0Ay/Ay.Bz-Aw-@y,?x)@v,Bx,Ay.@x.Ay.Ay.Ay-@x0C{0C{/C{.Bz2Bz3D|1Bz3D|.By0C{0C~0F~/C|1C}1D~1D~2F3E3G1G0E|1F~2E}3F~6I4H4H|5I~5H~3G|2G}3H2G~1F}1F}/D{/D{/D{.Cz.Cz.D{/Cz/By2C{1D{/Ay/Bz3E|4Ez6E{6D|5E~3C{1Bz2C{1Bz0Ay0Bz2F|2G|2H|0Ez1Ez0Dx/Dx,Bw/Ey/Ey-Aw,Ay-Bz,By,By+?w/Bz.Bz+?w-Ay,@x-@w+>v.@x+>v*=u.Ay.Ay1@x1Bz1Ay1Ay/Bz.B{.A}0B|0@x2C{1Ay0Ay0Az4E|2Bw7F}4E~5F~:K5F~7H7H6H8H7G7H~:J:L7K7K8M9N7L:N7M:P:P8N;Q;Q;QS;P>R;P;P;P:O:O:O;P;PR;P;P=RS=R>ST>VTTU;VT@VAW?U?U@V?UAWBTBVDWDWBVCWG[G[G\G\H]I^I]I]I]H\I_G]I_LbJ`LaOdLaLcNdNcNcNcPeQfOdRgUjShVkTjTjTjTkUlVkUjVkUiVhTfReOdNcLaMbK`LaI^MaJ^J^K_J`J_LaK`J_J_MbG\I^H\J^K_J^K^J_G^I_I_J`KaJaG`G`HbIdMbKaJaNcMbNcLaMaM`L`NbNbK_NbPbNaNdLbOfKdLbLbOeKaLaNePdMcNdNcOeLbLbOeNdNdNdNdNdMcNdMcNeNdNcMcPePeNcNcMbPeNcNdNdNdNdMcMcLbMcLbNdNdNdLbOeNdPfPfQgRhPfQgPfNdOeNdJbJdKfJcMbMcLbG`JbIaIaMaJaMbM`NaL_NcLcKbIbKbMcLbMcMcMaMaK_MaLaNaNbLcLbLbMcMcLbNdNdNdMcLbMcMcLbLcLdKfKeMeNdObNcPeMaQgOePfPfNdOeNdOeMcNhMfKdMfMfKcQdNeNdOeNeKeNbLcNdKcMeMeH`MgLdNfMeMeMeJbNfLdLdMeMfNePdOcRfQfPePePfPeRgOePgOeMeNfMdPePeQfTjQfOdPfQfPeRhQgQfOdRgPfNcNdRhOeOeQgRhQgQgQgRhQgQgPfSeQdRfRfQeRfReQfOfPfQgQgPfPfPfRhRhPfPfSiQfPgSfSgTgThRfQhQhQeQeReQdRfReThSfPhSiSiPgQeRfRfQeQdThOdQfShPePePeTiQfRgTeTdUeShThSfRfTgPgQfRgSgRfRgRhSiRfRgTiThSgSfRdTgQeRgShRgSiTiShTjVgTiThTjVkRgTiTiTkSmSlUmSlSlTlRkQkPiOhRjSiThShVjUmTlRkTiSjUlTkXlZmUjUgUiVjWjUhVjQkQkRiQiSlWjVjVjRiTmTmTmUnVlZlVjVjTmXoTmSmSnVlVmXoVmTkTlSkVjUiZlXlXlVjWkViSkTkTlXjWjXkRkTkTnSoSoVnVpUnVoVpXrWqXpXrZq[sVp[rWnWnVmXoYpWqYrYrZsYoXoWsUrWp\sXpZq]sYs]u_w]u_w^u]u`xayayd|awe|e|d{f}khinklqoqpqopoquyz{ҀԀ}{wxuvxuqstttsputsstsrrrnnqtrsstsmolokjlmomlmoqtuvxwxyzwxyzzxwvzvqtsrpusupsuw|σ҃ӄӉӒқ٪ڰٺ߽웭陰禮럯뛫蛫皪噩㜬蝬뜬뛩ꚦ(8n&6m'8n(x+>z*=v*=u*>t*>q*=s-=u/@x->v/@x/@x/@x/@x/@x1@y3B{2C{2C{/@x3D|2Bz4D}1Ay0Ay/Ay.Ay/Bz.Ay.C{/Dz-@x0D|/Bz2Bz3C{1Ay3D|2C{7H0Bz1E}1D{1C|1C~/D}0D0C}4D5G2E1H~1F}2F~5G4D|7I1D|5H7I4H}4H|4H|5H~3E5J1F}1F}/D{0E|-By/D{/D{-By0E}/E/E~.C}.C|/Bz2E}2D}2C|3E}4E}3E}0D|/C{0Bz4D|5E}5F}6G}4Cz3Ez0Ez/B{2C|1@y2Az.?t/@v/Cw+Au-Av.Bv.Bv,@v/@x1Ay.?w0@x-?x+?y+>y-@y.@x0@x-?w,@x,@x/Bz-Az.Ay/By/Bz1E|/C{,@x0Bz0@x4E}3D}1B{3D}4E}6G6G5F~7H8I9I8H8G8I7G8G9L6J6L5M7L:O:O;O8N;Q9O9O:PR@QATASEXCVEYEXDXJ]J\O_O`OaPbNaM`M`LaNcK`L`L^K^L_K]M]HYM]L\IYIYIZCWDWFXBVBZC[CZBXDYCX>S>T=TR;PS=R?T>S@U?T@UAV?RAUAU@T?U?U>T@V?VAW>T?V>T>T?U?T>S=S>T>T?UT>T?U?U?V?U@WCYAWAUCWBWCZE[G\H]H]H^H\I^JaI_I_I_KaKaKaKaMaNcNcOdOdRgShRgTiShWlUjVkTiTiYnXmWlWlVkVkUkShOdNcMbLaKaLbKaH_H^J_L`L_L^L_LaJ_K_J_I^H]H]G[H\I]K_I`J`H^H^I_J`H^I_GaH`J_J^K_L`K`NcLaNcNcLaKaM`L`L`L`J^NbNbMaNbNaPdOdMaNdMdKeLbMcNeJdGcKdOeLbKaPfLbNdNdNdNdMdOdOaPdJdLeLeJdJdKcKdLeLfKdOeOeMcNdNdNdNdNdMcNdLbOeOeNdPfOeQgOeRhQgTjPfOeNdLbMbNcLbLbLbMcLcHbIaIaJcPdMaKbKbKaNdNdNdOeNdNdLbKaLbLbM`MaLaMcKbMcLbMcKaLbKaKaLbMcKaLbMcLaM`McLcJdMdMeMeKfLfMfMfOgOePfOeOePfOeOeOeNcQfPjMfKdKdLfIdPeOeOeNdPcPdNdNdNcNdLdLdLdLdLdMeLdLdLdIaMeLdMdQfPfOfPfLeMfLeMeNfLeOgPfOeQfOdNdMfNgPhOhLdNgOgMePhLeLeMeLeMfMfNgLeRhPfOePfQgPfQgPfSiPeRfRfRfPfPgReTfQeRgOfPhQfQgSiQgQgRhQgPfPfPfPfQgTjShSfTiPgRfSgSfTgRfRePgRhQhQhRiQgRhQgSiRhQhOeQeThRfSeOeRdOeQfQfRgPeTgThSgThQgSfReTjRgRgTfTfUjPhQhSfSgSgRiShPeQfRgPfRgPeOiSgQgTgThTgSgSgThThWkUgVkViRhTjUiUiXlWlXlVjUiShRgSiSgTgUhTjUjVjRlSkWoTlTlUmTkSlSkVnRmRkSlSlRhQiRiTjTkTkViWkXkWkWkTkVlUnUnUoUlVjWkWjVjWmWnXnUoRlTkUlTlXjXlVjUiUjVjWkWkVjXkVmSkRkUmSkTlUlWnUlUoVnVoWnXnXoWnWn[q\r[sXpZq[n\qZnWlWkUmXoWnYqWqWpWpZsVoYp[rZrYq\uXr[t\t\t^v^u\v\v`y`zbwbybyd{bye|g~i}ikgjolpomjomnmsrvy|р~zyvuususttrutstrtstwtspqrqusrqsrrppnoqpqnrouspqtxw||{z||y{{y}xuvtutusvsrvszӀуԅԋՌב֜ڠۦݮݰ޷㷺䴸ᶹܾ顮䛯렰鞫盫뛬ꝭ얬䛪曫阨嗦&7m'7m%8m#9n';p';p(u)y,?y+>v0Bz0@x0Bz/C{/Bz-Ay.By-Ay.Bz1D|1D|/Bz/Bz0Bz2C{4E}5F}4E|4E}6G7H6G6G6G6F:J8I6G7F6G6I5H5J7L7L:O7L8N7M8M9O:PR>S>S@V>TBVDV@RCUFYBUI\FZM^N^K_K^M`L`OdNcL`M_L_K^K^L^K\L]J[J[JZHYEYDWFXFYCYBZCZCZ@WBWBWAV=S?U>SS>TSS>S=R?T@U@U?T?U?S>Q?S@ST>TT=SAVBVBVDXDXF[EZDYEZG]H\H\G]H^F\H^H^J`J`I_J`LbNcMbOdRgVkShTiUjVkTiTiUjVkVkWlTiUjUjWlShPeQfNcNbMcLcLbJ`I_I^K_NdKaKaL`I^J_KaK`J_H]J_I]G[K_K_J`H^H^H^I_J`J`H_G`JaJ_J_LaJ_LaOcLaNcNcMbObMaL`L`L`OcNaNbOcObObOcMbNcNdNcLbLbPfNcOdNcMbLbOeLbNdMcNdNdNdNdMdNdNaOcNcNdNdNcNdOdMcMcLePeNeMcMcLbKbNdNdNdNdOeMcMcOeNdPfOeQgPfSiPfNdMcNdOeNcNcNcMcNdLbM`KaKaHaLcNbMcNbMcKaKaNdNdMcNcMcLbNdKaMcLbL_K_MaLcNdOeLbKaLbLbJ`LbMcMcLbMcLbNcNcMcOdNdIcKcLdJbLdNgMfMeQgNdPfOeOeMcMcNdPfNeLgLfJdKeNgOdMcNdNdMcMbLcNdNdMcLeNfJbKcMeMeMeJbKcKcNfMeKcNePfPfPfPfNfMeOgMeLdMeOgOeNePdOeRiLeMeMeNfOgPhLdNfLdMeMeQiOgNfLdMeNfMeOeQgQgOeQgPfQgPfPgSgRgQgPfQgQdOcPdReQgMfPfQgRhPfPfPfPfPfPfRhQgTjRhPfSfQeReQeSgRfRfSgQeQfQgRhQgTjQgQgQgRhQgRiRgReSgQdPeQfPcPeQfPeQfShRhRfThThSgThSgTgRhQeViReRhSiRhRgSgSgSiRgRgRgQfShQfPeRhPdRdUiThSgUiTgSfUhVjThRgSiSgRjThTiVkVkUkTiShShShTiSgVhViVkVkVkXkSlRjTlTlRjUkXlTkVmTjQkRkRkQkPjSjTkTkTkViUiXjXjWjTlUlQjSlVlTkWkWkViWlVmWnUlUlRlYpSkYkWmXlWkWkVjViUiUiVjViSjTkSlWlTkTkVlUkVpVoVoWpTnVnZoYoYpYpYp[sZrXpWp[p[oZnWlXpWnYpYpWpYrWpYrYrXqXpYqYqVm[tZt\t^v^v\tby`w`z`yczbydzbxczcze|if|ihljoimj~k}j~i}j~nnnux|}рzxwvvuqpostrsssuvy{{wrrrspspuvrtrvsuspnpqrrutxwx{~}|}~{}~~|{zwvwwvqtvprsv{}σՄ҅ӈӏؑ՘ؚע٪߫ߩݩߧ۩۬޲᳹ߴܰپ眫횯랮뛫霫暪暪闧旧'8n(9o&7m'7m(:p'=q'v/>w.@w/@x/@x/@x0Ay0Ay3C{3Bz0Bz/@x0Ay/@x/Ay.Bz.Ay0C{3C{2C{4E}3D|3D|2C{2C{5F~5G2E}2E}3F~1F1G3G3F4F4E2F0E~0E3G~4F~9I6F~5H7J6K4H}5I6J5I~6J1G{3H|2G|0E}1E}/E|.Cz/D{1F}0E|/D}0E~/D}.C{0C{1C{5E}4E}3D|2C{3D|2Bz0C{2D|4E}4E}2C|5F~2C|0Az2D}/Bz.Ay/Bz.Ay.Ay.Bw,Bw+@u+@u,@u,@u,Av,>u/?u.?y+?x,>w,?w,By-Az-?w-@x.@x1Bz0Ay2C{1Ay2C{2C{2Bz2C|/B|2E|2E}1C{2Bz5F~6F5E4E}6G5F~4E}6G9I7F8I5F}5G6F5F5I6H6K5J5J7L6K6L6L4L;R;R;R;RSR=R=R=RS;P?T>S=R=R?T@UAV?T@U@V?T?VR;Q;Q=S;QT;QT>T=S>TAWAW@U@UBWBWBWDYCWFYEYE\H^H^H^H^G]I_J`H^J`LbLaQfPeQfWkTgUgTgTiTiUjTiVkWlVkVkUjTiPePeMbOdMbMaJ`J`J`KaL`K_K_J_K_K`I`J`I_I^K`MbMbI]I]K_I]H^I_J`I_H^I_J`J`KbKcKbK_J_I_I_KaK`MbMbLaLaMbMbOdNcMbQfNcMbNcNcNbOdOdLbKaMcOeNdKaLbMcLbMcMcNdOfNdMcNdNdNdMdMcOdPeNdNeOeNdMcMdMdMcOeNdOdObMbNcNbNdLbMcKaLbNdMcMcNdPfOeQgOeOeOeRhQgOeNdJaJeMeOeMcLbMaMcMcIbKdLbLcNaMcKaKaMcMcNdMcLbLbMcLbMcLbOeLbLcLbLbLbMcMcMcLcMcMcLbKaKaKaLbLbNbLcLbLbNdMbJcLeJbNdOeOdPfPfPfOeMcMcNdMcOfOdOdOeNcLdNcNdMcMcMcNdMdNdMcNdMcJcLdMeLdMfLdKcKcMeLdKcKcKcLdNdOeOeMdJcLeKdKcLdJbNeNdNdNdPfPfKcNfOgNfMeOgMeNfMeQiLdPhNfPhNfMeMfNePePfQgPfOeRhOeNdPeOfQgQgQgOeOcPdRfPgRhMgRgOfPfSiQgSiRhRhRhTjSiPfQgQhTfPdQeQeThPdSgSgSgSgQgSiQgSiRhTjRhPfQgPfPfRiReRhRgPeReOePePeRgQfPeQgThRfRfRfThThRhShRiRiSiSiTjThRgShQfTiTiPeQfShRgRfThTiQgRfRfThRfRhSiSjUhUjWlVjQkUmThTiTiVkUjTiTiTiShTiSgTgViTjUjShTjSmTlSkTlVnQjWkVkRjSkUoRkRkTlSkSjSjPiWjUiVjYlXkVjQhSjQlQjSiUmViWlVkXmUmVmWnWnWnUmYmWmWlVkUiWkVjSkZlYlWkTmSjUpUnUkTkVmVoUoVoTmTmSlVoUnWqUoZrZsYqVnXpVmWmVmVmZlYkVmVmTlWpVpVoVoVoVoTmYqXpXpZrWpZsZr\u\u^w`w_wc{by`xbzc|c{c{c{e}azggijf|e}j~h|h}j~i}g{fzj~msuxz}|zxwvtpqonstuutyvx~ցف~yttsrtrovtvuyussutwvwuzzz~{Ձ؁؁فӂуӃӀ}||vwvvvuuszxxz~ρӅ҅ԉӍڏِږؚڜן۟ݠݟܞۣߤޥާުⴺ㻽ꘪ욯뜰ꛫꙩ䘨㘨䗧(9o&7m&7m(9o(8n*:p):p&7m'v->v-?v.?w.?w->v->v.?w.?w2C{/@x0>x1@y0Ay0Ay0Ay.?w1Ay2C{1Bz4E}1Bz1Bz3D|2C{2C{2D|0D|3F~/Ay0C{1D}2D3F3F2E2D5D3F1F0E}0E}4F~3D|8J3F~6J4G~4I~6K3H}9M4I~4I~2G|2G|2G|2H}0E{2D|1D|1D|0F|0F~0E~0E~/C|2E|/Bz5F~3D|3D|3D|3D|.Ay0D|2E}3D|4E}3D|3D|3C{3E}/Bz.Ay.Ay.Ay0C{/Ax-Av-Av.Bw.Bw,@u-Av,@u+@t,Av-Bv,Av*?v,Bx+@w,Ax,Bx.C{.@x2C{1Bz3D|2D{1C{0By2Cz4F3F2E2E}0D|2E}4D}7F8H8I7G7G6G8I8I7F8I6F7F7G6F5I5I6K6K6K5J5J6L7O7N;R:Q:Q:Q=S>S=R@U?T@U>SAVCXBWBXBXEZCXEXCVGZK]J]J_I^J_K]K^J]I\FYK]GWHYEXFXFYFYCXCXBWBYAXBYAW@UAV?T?T=R>R?T@US=QS=R?T@U?T>S?TAV@T=S:Q:P:P9O?S;NR9O;Q9OT@V@U@T>S@U@UBWBWAVDXDXAUCYDZE[DZF\H^I_J`J`I_KaNdNcRgShUhSeTgReSgPeShShYnVkUjWlTiTiQfPeNcOdMbLaMcKaJ`KaH^I`LbL`J_I^I_H^I_K`J_J_J_J^K_K_I^G^F^G]H^H^I_I_J`H`I`LaLaI^G^J`LbK`MbK`MbLaMbNcLaNcMbOdOdMbNcOeNdNcNbLcOeNdLbLbMcJ`KaMcLbNdMbOcNdMdMcNdNdMcNdPePeOdPePePdOdMbOdQfMcObPcPeMbMbNcLbLbMcNdNdMcNdNdLbNdNdPfOeNdOeNdMcMcLdJdKdKdJdOdNbNbMdNdNcOeMcLcNbMcKaLbLbOeOeOeNdJ`J`LbLbLbNdKaNdNdOeLbKaLbLcMcLaLbLbKaKaLbLaMaL`NaNbLbLcMcOdNcNdOeNdOeOeOeNdNdMcMcNdLbMcMdMcMcNdNcMcMcLbLbMcNdLbLbLbMdKdLdJaJcIdMcMeKcJbJbJbKcLeMdNcMcNdMcNcNdMeMbJdKcLdLdOdPfPfNdOeLeMeOgNfLdNfOgNfMeKcNfLdOgMeLdOgMePhOgQdRiPfOePfNdQgPfOeOeOeQgPfOfQgPfOeLeLdRgRhRhPfPfPfPfRhQgQgSiRhRhRhPfTgSgRfRfQeRfQeRfTiSiQgQgRhPfQgPfPfOeQgRhRhSjRgTiPeObNcPeRgRgPeRgPfTgRfThPdPdSgTgSkRgShPiQjPiQiTgShShTiRgRgRgShPeShThVfUfTeRgTgRhRgRhSiSjRiShOjSkQiShUjTiVkUjUjTiRgShTiShUiUhTjUiUjWlUiRjTlRjSlWjUjUjWkSlSlSlRkNmQjQhTlWiVjUhVlXnWkUlSjUlTjTjSjXjWkVlYnYnXmVoUkVmSjYmWmWlWlXmYmWkVjTkSkVnSjVmTkTnTnWnUlUlToVoTmUnWpVoWpTmUnVnUoVpWoWpVpXmVmVmVmVnUmYpXoUlYqVoUoYrUnYqXpYqWoZrXpZrYs`t`u_sau`y`yc|azd|c{bzbzc{d|bze}bze}e~eh}g~d}j}j|i|g{fzeyi}ipttw|wtrsprmrnorstuvw~~сׂւրz{{ussqpntvxuvvvsvxxy{y}Ձ؂چ݄܇܆܇ڇ׈ՉՈՋ֌ՇӆЄ}~{zwwtuvxz{z}тԆԅъ։Ԏؙِِٓڙܙۜޘڛܚٜۛ۞ܞܞۡߥ۱露ꥷ황虩㙩嚪瘨%6l&7m&7m'8n'8n'8n'8n&8n%9n&:o&:o&:o';p&:o%9n';o&:n(v->v->v.?w.?w.?w->v.?w+S>S>S>T=S>SCXAVCXBWDYCYBWAVDYE\FZH[L^J]J]J]I\GZEXFZGYFXDWEXDXAWBVAVBWBWFZCWCX@U>S@U=R@U=R>SSS?T?T=R?T?T@U@U;PR;RS@U@U@T@T@SDXCVBYCYDZG]F\F\G]H^H^I_McNcOdRgQfRgUgSfSfQdRgRgVkTiVkVkTiUjRgPeMbNcNcNcMbKaKaJ`McI`J`L`NcLaLaI_I_K`J_K`LaJ_J_M`K_H\H\I\I^J`G]H^KaI^K_J_K`LaK`I]I_KaK`LaNcLaMbK`LaOdNcNcMbNcNcMcNdNdLbMbPdMbMbNbNcNcMcNdLbLbMbNbNbNcMcNdMcLbOeNePePePeOdPeNcOdOdMbNcOdNdNcPeMbK`MbLbOcMbOdNdNdLbLbNdNdMcMcOeOeNdNdMcLbNcNdLfMfOcMcNbMbOeOeOeNdNdLbM`LbLbLbMcMcMcMcLbLbMcLbLbMcNdMcMcOeLbMcLbLbNaMaMaMcLbLbKaJ`KbM`L`L`MaNbNaLcMcLbNdNdLbNdMcMcOeOeMcNdMcKaLbMcMcMcLbMcLbKaLbMcKaNdLcNdMcMdKcKcKcIcHbJbLdIaJbKcKcKcIaJbOeNdLbNdMcMcKcOcNbNdJbObOeNdNdPfOdJcOgMeLdOgMeNfMeLdLdNfMeNfMePhLdOgOgMeNfLdShOeOePfOeOeOeRhOeOePfPfPfRhPfNfMeOdQgOeNdQgPfSiPfOeRhTiRhQgRhRhRfQeSgRfRfSgPdThQeOfPfPfQgTjQgQgRhPfPfTjQgShQfQfQeOaPeShQfRfSgRfQfSjSgRfSgSgQeSgReSjPhQhPhRjPhQiQfSgQfSgSgRgTjUkQhThSeSeTfRfRfQfQfQhSiTiSjQiPhQiSjUiVkShVkUjTiTiShShTiRgUhSfTiTiTiUjUiQkUlSkRlVjVkVgTjRkSkQkRkSlRkSjUlWjUiXnVkXlUmTkTkSjSjSkTmWkUjWlXmXmXlWlSmTkUlVkWlZoXmYnVkWkVjSlUlUlWnUlWnUlSlUlUlVmTjSmVoVpTmVoSlUnUnUoUoTnXoTnVpSnWmXoXoVmYpVmVnWmWoVnYqYpYpVpYqYqWoWoXpYqYr]qauau`ucxaubvcw_ybybyc{d|ayc|e~c{e}d}feg}f~ik}hzg|g{i}h|hmmquwuuvsopnppossxwzx|Մփԅق|yxutmpvtvuuuvwxyxz|z|Հ؀؄܆ۉ݈݋݌ېڕܔۀؘݘݎӎ҇Іυz{{{uyy{{{~~ρЅ҃ЄӊыӍ׌ԑוؖۖژݕܗۗۛۗٚۙ۞ݚښ⛪ڠ٦ܬ䮻ꦷꡱ襳궿ﯼ젮隬ꛫꜬ蚪䜬$5k$5k%6l&7m%6l&7m&7m"5k"8l!7k"7k$9n%9n%9m$8m%9o'v->v->v.?w,=u,=u->v,=u.?w.?w.>v.?w.?w+=u.?w->v,=u.?w1Bz1Bz2C{0Ay/@x1Bz0@x/Ay-Ay/Ay.Ay0C{0Cz/B{1D~1D0C~/B}2D~4E0C~1F2G1E~2E}2F~4F}8I7H7H7H8I7H~7I5J~8L7K7K5G3F~2E|7H.Bz1E}1D}3F3F2G/D|5G5E}6G5F~6G4E}4E}4F~1D|4F~2C{8I5F}2D{3D{3D{4D|0Bz.Ay.Ay-@x.Ay0Cz-Aw/Cz/Cw.Bw.Bw.Bw+?t.Bw.Bw-Bw-By+@x+@w,Ax-By,Ax-By.Cz/@y1Bz4E~5D|4D|5E}6G~3F~3E~2E~4F5G6I7K5H5H6I7K6I8I8I7I5G4G7K5I5H3F7I4I5J3H5J6K6L7N8O9P9P;SS?T?U?U?U@U?T@UCXCXCXDXAVCXCXEYI\I\GZH[H[FYEXFYFYDXGZCVBUCVAT@U@UAVBWBWAV@U@U?T?T>SBW?T=R=Q?Q=QT;R=R>S>Q?R@SAV?V?VCYCYBXG]F\H^G]F\I_J`McLcPeRgPeQfReTfQePfRgShUjUjUjUjRgShQfPePeOdOdOdLaJ`KaKaJ`I_K`J`I_J`KaJ_LbJaK`J_K`J_LbK`J^J^I]H\I]F[G]I_I_J`LaK`J`H]K`K`H^KaL`K`I^MbMbMbMbMbLaLaLaNcOdMcLbLbLbMcPeOdMbNcNbMcJ`McLbMcLbLcLbLbLbMcNdPfNdPeNcPePeNcPePeOdNcOdOdNcNdOdOdNcLaKaMcMbNcMbMaKaNdLbLbNdNdMcMcMcMcMcNdMcMcMcOdNdNdNdNdLbLbNdNdNdOeNeNeLbMcMcKaMcNdNdLbNdLbLbMcOeMbKaLaLbKaKaMcNcLaJ`McKaLbLbMcKaLbM`NbM`J^L_M`MaMcLbMcMcLcMcMcNdMcMcOeMcLbLbMcLbLcLbKaLbMcMcLbMcLbMcNaKbMcOeLbKcIaIaJbKcJbIaJbIaJbIaJcMbNcNdLbNdMcMcMcMdOeNdOdKaOeQgOeNdOdOhNfMeNfMeNfNfLdMeMeLdNfLdLdNfLdKcMeNfNfLeOhOeOeOeOePfQfQgNdNdOeOeMfNgNfNfOhPfPfRhNdPfRhPfOeQgQhMfQiSiPfPfQgSiTgSfSgSgSfRfQhRhQgRhRhSiSiSiQgRhRhUkRhQhPeNiQgPdNdOdOdOhPiPhSgRhRhRhQdQdQdSgSjRhPiQiQiPhPhRjSgPjQiOhRjSgRfRiTjSgThShSgThRfSgPfThRhTjQjQiRjQiQjUiVjWlTiShTiTiRgRgTiShSgUiShUjUhTiTiThQjSjVjWlWlViUjTiRjTlVnVmSkTjVkUjWmVkWkTmUmTjSjSjTkVgWjUiUhUjVkXmVkVkYmWlTiWlWlVkXmXmXmXmTlTmWmTkTkVnUlUkVoUmTkVmWnVmTjWnSlVoVoUnVoSlVmUmVoWoXnVlWnVmWnUlVlXoVlTlWpVnXpWoWoXpWoWoWoVn[sYqZrZr\t\uat`u_tbwdydw`y_xc{bzb|bzh{d|f~c}d~d}f}e|hh}ih}g{i|h|lk~orssvstroonomnsryy|~ӃӁ҅ֆك~{zwyusuuwvxwxwxz{}ԁ؅ۇڋڐݒݔݓ՚ןܤݤޤݦޣښ֘ӒѐЍІЁ}}||{{}|ˁ̀ςЃρΆҎ֋ՎҍӏՏґՓՕژܔؙۙۘۘۘۘ۝ޛޕږݛ⡭⣯垯ᡳ妲뭸쭸ﱼ졭훫靭蝭蝭$5k#4j#4j%6l$5k#4j%6l$5k'7n(8n&5l%8l!6k#7m%9m':m(7m'8n*:p):p)9p*:q):q*:q):o&8p%8o%8p%8p#7o%:r';s%8p&9q';r(:r):r*;s+S=R=R>T=R?T?T?T?T@UAW?VAVBWBWAUDVFYDWEXFYFYFYEXFYFYDWDWBUBUBVAWAV@U?T@UAV?T=R>S?T?T@U=R?T>S?R@S=R;PS>SS>S=RS:N8N:P;Q9O:PVQ>T?VAW@VAWCYD[F\E[H^I_I_I_LbJ`McOeQgQfRgQfPePfRgUjTiUjUjUjUjShRgRgPeOdNcNcOdOdMcJ`J`KaI_K`L^L_K]K]J\J^LaJ_LaJ_LaLaLaJ^I]I]H\J^I]G\J`J`J`J_J_J`L`L^J\J]L^K_NcLaJ_MbMbK`K`LaK`NcNcNcMbLbMcKdMbMcNcLbNcLaNcNdK`LaLbMcMcMcMcNdNdNdMcMcOeNdQfOdPeNcOdNcNcOdOdNcPeOdNcOdMbNcLbNdOdOdLaLaLbKaJ`LbNdNdLbMcMcNdMcMdPdOdMcMcLbLbNbNbMbLbMcMcNdOeNdL`McLbLbOeNdMcNdJ`KaKaMcMcLcIcJdJcKaLbI`J`MbLaLaLaMbKaKaJ`LbM`K_OcMdKbKbLbJ`KaMcMcMaObNbMcNdNdLbLbLbMcMcLbMcNaNaNaLbJ`NdKaLbMcNbOcMdKaMcKbIbKcJbKcJbKcJbIaIaKcIaLdNdNdNdNdKaLbMcOeLbMcMcMcNdOePfLbNdNfJbNfOgNfMeMeLdKcLdLdKcJbMeOgMeNfLdOgNfPhNfNePfPfPfQgOfNfNfMfOfPfLdMeNfNfMfOfQfQfPfQfOdRgQgRgOeQgNfLdKdRgQgQgQgPfPgRdRfQgQgRhPfPfRhPfRhQgTjRhQgUkPfSiQgSjQiPhRfPfPeShQiPhPhQjRjTjSiNeRiPgSiSiQgPiPhOgPhPhNgQhPjPjQhQiPiPiTgShSiRiSgSgRfSgRfSgSgThUkQiSkPhRjUhUjRfVlRfUjShShShTiShTjUiThShTiSfRhTiUiSlSkUiUjUjShQfRgTlRkQjTmThSkWkUjXiWiVlUmTnSkUlUkXlWmWlUiXlYnVkWlWlWlWlWlXmWkUkYnWlZoXlXqXpSkTmTkWjViWlTnVnXlRjTlSjWnVmUlWpUnXqWpWoXoWoVnWoVnUnWnVmWnVnWmVoWpWpUmVmWoTlZrYqWoXpZrYqYqYqWoXpYqZr[t\u]w`tav`u`ubwcw`za{dxcxh|e~e}f~f}gfie{i}i~f{i~jj}i}mmortuqpqosonqnsvw{~рԂցӂҊڋۇօ~}wvvw{||~zxzy||ր|Ӏ؁փԇאّטڞۥ᪱߭ವݴ޳޴ݴ౳ܬئԠќіБЋ͈́̀~}ˀ̀͂΂͇̆΃ЉъЊыЍԎѐԒ֒Ԕזؖږڙݗޘޙߘߚᙧޘܕٗۘܕݕۗܝ⣱椵髸誶뜬蝭"3j#5k#4k#4k$5l$5l$5l$5k$5k$5k&7l'8m&6k'9k"8i%8l&7r(9p'8o'8n(9p(9l'8k'8m(9n&7n#7o#7n#7n$8o&;q$8m(;t*v+R=Q?S>S?T?U>S?TBWAU@V@U@UCX@UEWDWFXEWCVDVDWCUEXFYH[BUBUCVBTBUBW@UAVAV>S@U@U?T=S@U=S@U>S=P?Q>Q=S;PSS=S>S=P;O:N;Q;P8M=SS=T=U?U@W@VBYCZE[G\E[I_H^H^KaLbOeNdNdOePgSiQgRgQfShTiShRgWlXmVkVkTiRgShQfPeOdOdNcMbNbLbLbLbI_K`M`L_I\K]L^L^NbK`K`LaLaJ_J_LaJ_I]L_H\FZK_I_G]H^I`JbJ_K`K]M_P`O_K_J`LaK`I^K`NbK`LaK`MbK`MbMbMcMcJdNbLaNbNaObNcLaK`JcIcIcMbLbKaKaMcMcLbMcMcMcLcOdPdOdMbOdNcNcMbMbOdOcOdNcOdMbLbLbLbNcNbMaNdI`OeLbKaLbLbLbLbMcMcLbMcNbMcNbKbOcMbNbMbJeNdMdNdLcMdLcKeNcMcNdMcNdLbOdMcJ`McMcKaKbLfKeJcLaKbK`NbMbNbLaMbK`J`McLbKaNbK_MbKaLbKaKaLbKbKaNaNbObNbNeMcLbMcMcMcMcLbMcMcNaNaNbLbMcMcJ`MaPcK_ObNaNeNdOeMcKcKcJbKdKbKbJdJcJcIaJcMcLbMcNdNdMcMcMcM`LcLbMcLbNdLbNdMcNdJcKcJbLdMdLdKcLdLdLdKcKcLdKcKcMeKcNfPhKdLeLeNdOeOeQgPgMeMeMdPdRfLfNfMeMeJbNfMfNfLeMeLeKdLeOgQgNfNfPhQiRgRhShOfPfRhNeOfRhOfRiQgPfRiPfPfQgNeSiQgQgRhQgTjQfMgPfPdRgQeMgOgQiOgOhOiOhSiPfSiTjSiQgRfSgOgNfMeNfRgRiNiNiQlPhPhOgQjTiOhRiQhQeRfRfSgSgUiVjTjSlQiQiTgSgTjSjSiUiQfShShQfShShRfUiVhTjSgTiVkUjUjThUiTiVkVkVkShTiVjVkXlUiVjRjTiTgViWiTiSmToRlVnTnXlVkWlVlUiVkXmWlUjXmXmWlXmXkWjVkVkYmTnUnUmXqSlWjXlWlWmVjSmZlYmZmUhRkSkUmRlVoWnWnUmVnVnWoXpXpVnVlVmVmVmWoWoVnUlTmXnUnYqWpVnWoVnVnXpWoWoZrWo[sZrZr\t\t`s_s`tavdycxcxeyfzh|f|e~e}f~d~d~ff~e}bze|g{h|i~h~m~noorposqpoqkpronsx|}΀ԀՁӆՅԌ؉؅نڄځׂ؄ۀ~~كރ~~~ׁل܃كقԉ׍ٖڟާ௷߼߽ݺݴش۬֨ԧڟ֘ՒҋԇυуςσЃχ҈ЇЇ̊ъҋЏҏӎӍӓד֔הڕٝݖږږݘޚݜݛۛݞޝޖؘٔۛߞ᢮妸韯&5i$4h%5h"2f#2g'4k'3k%3j#5k&8m&7n$5o$5l$5k)9p(8n&7l%6l(9l):n'8k'8l(8l(9m'8o):s'8q'5r'7r&9o%8m(u+v->v,=u-=w->x,=u->v,=u1Bz/@x0@x0Ay0@x1Bz/@x.Bz.A|/A{.B{-B{0G~0D~,?y1C~/B|2B}2B}2B|2Cy5E|4F}6G~6G4E}6G7H7H7H6H2F~2E}5G6G6G6F~5G1E}3H2H1H1G2F2E}5I3F~8K8G6G7H9K3F~1E}4G3F~5H7G~5F}2E|.Bz0D}/B}0F.C}2F/Ay.Bz.Dz0E|0E|0E}.D{.C{-Cz,Ax,Ax,Ay-Bz-By,Ax-By/D{-By.Cz/D{.D{/D|2G}3H|5H}8I~3H|5I~6I;K5H8J7J7J5I~6J5I~5I~5J~6J2G{6J4D}3F6I2F}3G5H4G5I4I4I7L7L8L8L8N6L9M:O:O8M9N:P:Q:QR>P>P@S@U>S@TAV@U@UCX@UDYAWBWAV?U?UAWAVAVCXEXEXBWAWAWBWCX@UAV@U?T?T>R@R@S@S;P=R=RSR>Q=R8M:N9L:M:M9M;P:P:M;N9M9N8O7M8N7M6K7J9M;Q8M9NQ=Q=P>S;P=R>Tt+u(u,=u->v.?w->u/@x.?w1By1Ax0Az/B}.@z.B|,Bz.A{/A|/A{.B{/C}1D~2A|1A|2B~1A}1B|4E|1Bz4E}4E}5F~5F~5F~5F~5F~2E}2E}3E}4E}7H7H6F~3G1F3H2H/G0E~2E|4G~3F}3F}3F~7I6F~7I3F~2E}4G2E}2E}3E~6F~5F~1D{2E~2E.D|0E~1F/D~2E/D{0E|/D{/D{/D{/D{0E|.Cz-By.Cz.Cz,Ax-By-By/D{/D{.Cz0E|0E|2G~4I5J}4G{5G|3G|4H|4G}8H7H6I5H8L6J~5I}5I~7L6J~6J~4H|7J5H3F~4G4H2H~2G~4F~3G3H4I4I5J7L5J8M9O9N7L;P:O:O9O8O9O;Q;P=Q=P;N=Q?U?T?T@UAVBW@UAVCXBWAVBW@U@UAVBWBWBVCUATBUAVAVBW@U@UAVAV@U=R>R?R>Q=R;PT@V?U>T=S?UEZCXDYG[G[H\G]K_J`McMcPeOdNdNeNeNePeQeRgQfQfPePePeQfShUjShTiQfRgRgQfPeNcNcOdMbMcMcLbJ`LbK`J_J_K`K`J_K`LaLaLaK`J_J_H]J_J_K`J`H]J_K`J_I^K`LaMbJ_J_J_J_J_M`M^L_M`L_K`I^J_K_J`J`J`LbJ`KaKaMcNdMcMbMbLbKbMbMaKaK`K`MbMcKaJ`J`J`KaLbJ`LbJ`LbKaMcNdLcLbKaPdOdMbMbMbLcNcLaMbMbMbMbNcMbNbMdNeLaMcMcMcKbKcKbLbKaJ`J`I_LbKbMbLbKaK_JaMcMcMcNcNcMaNaLcLbNdMcNdNdMcKaJcKcKaLbJ`LbKaKaKbKaMbMbJ_McKbKaJ`LbJ`KaMcKbMbK_M_MbJbJaLbOdNbNbMaOcLcMcMcMcKaLbMcKaKaKaJ`LbKaLbMcLbLbLbKaLbKaLbLbMcNdJ`KaKaNcJbIaLdJbKcKcJbLcLcLbKaLbNdLbNdKcIbLbLbMcMcLbLbKaMcKaMcMcNdLcKcLdMeMeKeMeLdKcMeKcLdMeJbKcLdNfMeNfQgQgQgPeQgQgRhSiPfNdLdNfNfNfMeMeNfNfNfMeMeNfKcNdQgQgRhNfOgOgOgNfPhPhQiRgPfRhSiRiQdQeSfQfQeRfPdPdQeQeRfRhRhQgPfOeOePfPfQhQgShNgNfPjQiUjQeSiQgSiPfRhSiRhQgQhNfPhQiOgOgPhQiQiQiPiQiSfRfRhTfSgQgRgOeUhThSgRiRhTjSjQjPhPhPhRkPkPiShSgTgRhRhTfShShTiTiShUjUjTiTiRgTiTiShShTiTiVkVkVkTiUjShShVkShQhRiRiUlXkVjVmSjTlVjVjXmUlWlXmXmYnXmWlWlWlYnVkXmXmVkWlUjUjXmWlWlXmWlWlXmYnVlUoUmTlVnWlXmWlUkXlUoUmTlVnVnWoVnXpVnUmTkWnVmWmWmWoWoUmWoWoXnXoXlUnWoVnWoWoWoVnWoYpWoYq\t\t]u]u\t`u`udydxexa{cze{d~d|h|g|f{f{e}b{d|c{d}f|e}j}h|i~jkmmnonprnpklkprtwzy||΂у҄׆Ԇ׉֊؏ݒ␟܉ۋߍ߈݌ܖߞ᰽㐜Ֆᔢ⌚ي؋׌ْܘݣ᪵޳ܷ۵ֿܿݺص׶׻ۿ߽ܹڵحץס؜ԛՕӒՏԌЎӏӒ֐Ԗו֙יؙם؝ڛכ؜ۜۚٝݝݙۘڛݚۛܜݙۙۙܚޛݝޢ⥲୸ⷿ찵૵륳젯#3f#3f#4h(7l'4i(5i'4i%3i"2j#3k#3j$2j%3j%4k$5l$4k&5k%6l&9l&;n%:l$9k%:m%:l$8l$8n%9o%8p&9q'9r$6n#6l&8q&9r&9p&9q':t(;v(v+{-y-@z+>x-@z,Az-A{,?y-Az0B|2A|2B}0@{2B}2B}0@|2A|3B~3D|3D|3D}6G3D}8I5F~4E}0D|2E}4G6G7H5F~4E}3G3I3H1F2G1F2G3E5G1D4F4G1D3F2E}2E}4G2E}1D|2E}4H2F~2E}3F3F0E~2G2G1F0F~0E{0E|1F}0E|3H1F}0E|/D{/D{-By-By,Ax.Cz-By/D{.Cz.Cz1F}0E|3I3H4G6H4F4G4G7I8I8H5I5H7J5H7J5H7I7I7J3F4G4G3F~3G4J2G~3H4F3F3H2G~3H6K8L7M4M7M9N8M9N:O:O;OS?T?T@U=RDYAVAVAV@UAV?T?T?TBWBWBX@U@UEZBW@UAV@U?T>S=R?T=R:PST?U>TCYCYFZDYF\FZFZK_JaJaKaOeMcOdPdPdPdPdPcPePeRgPfOeQfPeQfPeRgQfOdShPePeOdPeOdNdOcPdNcMaJaI_J`K`K`LaJ_J_J_K`J_K`J_LaJ_I^I^H]H]G\I^G\J_J_I^K`I^I^I^J_LaLaLaLaK`H]J`I^J\L^J`K`K`J_J`I_JaLbI_I_J`KaLbKaLbNdNcNcMbKaLbK`K`LaLbKaJ`LbLbLbJ`KaJ`LbMcKaKaLbJaMcLbMcLbLaMbMcLbLbOcNcNcNcMbNcLaMbMdMdMdLcLaLaLcKbLcMdKcNcJ`LbKaMbKaL`J_KaLbKaKaLaMbLaMbMaLcLbMcLbLbLbKaLbIcHaK`KaMcKaLbKaKbLbLaMbMbJ^KbLbLbMcKaKaJ`J`KaJaL_KaKdLaI_LbM`L`M`MaLbLbLbLbKaMcLbMcLbKbLcKaLbKbKaLcLbKaLbLbLbJ`KaKaKaLbKaLbKcKcLdJbIaIaIaJ`NcJ`KaMcNdNdMbIbJbLcMbNdLbMcLbOeNdMcLbNdLbNdMcJcLdLdLdLdMeJbJbMeMeMeMeLdKcLdMeMeOgOcOfQfOdOePfRhRhQgQgMfMeMeNfLdNfMeLdNfNfNfMfNfQePfPfPeNfLdLdMePhNfNfPiQgOeQgPfPfPfPfNeQeRfQeQeSgQeRfQeOfQgQgQgRhRhPfQgQgQgOhPhRjOgOgOhUiSiSiSiSiSiRhRhRhPeQiOgOgPhQiOgNgPiQhThSiRiSiSgShShReQfSgSjThViRiShSiSiRkPhOgRjNjRiUhTiUiSgRhTiUiTiTiRgTiShUjUjTiTiQfRgTiUjTiVkUjShTiTiUjVkVkShRhPhRhRlRkTlTmWmUlTkTjWnUmXmWkWlWlXmXmZoWlYnYnWlXmWlWlWlVkUjTiUjUjTiVkVkWlVkWlVkRjTlSjWlVkVkXmVkWlYlUnUnVpVoWoWpSkXpYqXoVmWnVmWnUmVnVnYqXoVoXpYmXqVnVnVnWoVnXpXpWpZsZr[sYq\tZr^u`r`tbw_y`yaxbzd|d~e|e~h}g}dxb|czayc{bzbzc|bxczf~j|i}j~k}ononooolmnmqsuuwzy~΂ҁЀЀфхӉ֊ۋىنՈىڌܑۙޣ⨳᮹稴穴襱矩⚧ޓ؎ْۖܢ৲ݳݼڿ׾ؿٸٲ٭ק֣ԝӘ՘֗֕ԕԗ՛ך֟ڝآڡ١ա֢֢٣ڟڝؘٚ؜ܛܝ۞ޝݟޝڜڠޢߣߩݱ߹飴좶%6i&6i$3g&2g$1f(5j'4i(4i)5j(5j'5l%5l%4k#2h$4g%4i$4k%6k&7l$8k&:m':n%9l';m$8j%9m$8n$8m$7p#6o"8o%9q&9q$7o#6n%9q'9q)9r):q)8r)9u(8s(:p):r(9q(9q&9q';s&9q&9q)w,x*=w,?y+>x*>x*?x,>y/>y1@{1@{0@{0@{1A|1A|/?z1A}0@}2C|2Cz4E}2Cz4Ex3D{3D|3D|5G1D|/Bz3E}5F~5F~6G4E}2E|3F~1F2G2G2G3H.D}2G2G0F0F0E~1F3F}3F~2E}4G1D|2E}3F~1D|3F~0C{1E}/E2G2G4I0E1F~2G~0E|1F}2G~1F}1F}3H/D{.Cz.Cz-By/D{-By.Cz/D{.Cz/D{/D{3H4H5H5H4F~5H3F~6H7H8I9I5G7J5H6I4G8K5H5H4G4G7J5H3F}2G~2G~1F}1F}3F1F3H3H2G~6K4H5J7L7L8M7L9N8M:O:O9N:O9N:O9MS>S;O:P;Q9O9O:P9O9O:P9OTBXCYAWAWBWDYDYF[G\H\H\H\MaMaMbNcOcNaNaOcNbOcPeNdMbQcQeQgQfOdPePeOdOdPeNcNcNcOdNcOcNdMdMcKaJ`I_LcMaI^J_K`J_K`I^I^I^J_LaJ_I^J_I^J_H]I^G\J_J_H]K`I^I^K`K`J_I^J_J_K`I^K`KaJ_J`K`K`K`J_K`MbK`K`K`I`LbKaMcMcOcLaLaOdLaKaLcHaLaL`I`McKaJ`J`J`K`J_J`J`J`LbKaKaLaLbKaMcMcMcJ`LcKaKcLaMbNcNcMbMbMbJbLcLcLcKbNfLcKbLcKbKbJaI`KbKbJ`J`J`J`I_I_J`KaLbKaJ`LaK`K`MbKaMcLbLbNdKaKaKaIbKaJ`KaLbLbLbKbJaMbLaLaLcLbKaLbKaLbKaI_J`KaLaL`K`GbJ`KaLbK_K`McLbKbKaKaKaKaNdKaMbM`M`M`L`MaL`L_K_I_I_KaI_J`LbLbKaMcKaLbLbMcHaJbKcIaIbJcHbKdMbJ`LbKaLbKdKcJbJbMcMcLbLbLbNdMcMcMcMcNdMcNdLaIbJbKcKcKdJdNgKdLeLeLeKdKcKcLdOgNfOdOeQfPeRhQgOeQgRhOgMeNfLdNfOgMePhPhMeMeNfMeOgMfRfQeRgNfOgPhNfLdMeUjQfNePfQgPfQgSiRhPfPdPcSfSfPdReSgUhRhQgPfOeOePfQgOeNgOhMePgOgPhOgPhQeTjQgPfPfOeQgTiTiMhPgRjQiQiOgOgSfSgTjSiQgRhTjRiRhSiNgQjTiTjRhSjTjQgRhSiRgRkQiOfQiQiTiRhTgTgQfRhThPeSiTiShTiRgTiTiShTiShTiShShTiVkWlVkTiUjRgTiShShSjQhQkRkSkTlRkSmSmRlTiSjRjUlYmYnYnXmXmYnWlXmZoXmYnWlWlVkWlVkUjXmWlUjWlXmXmTiUjWlWkYmXmXmVkYnYnXmWlYmWkYmYmYmYmXmUnVnUlUlVmWnUlWoWoWoUmYqWoVnZnVnVnVnWoWoXpWoUmWoZrZr[sZr\t[s]u[t^w]u`xb{b{b{c|cye}ayfze|g}d}azd|bzbzbze}c{gd|e}gh|j~mk~nmnmommnooqtrwvy}}|~ρт҇Շׅԇڅև։֍ؖ؟ݧܨݭⰹ樰৯ᬷ맰嘦ېӕ֡ߦٳۼ׼ٻܳ٭֣ѡ֛ԛ՛֙Ӝל֝՞נ֣۠ӧקԫت٪٣٣ٟؠٛל۟ޠۜ۝ڠޠۡާ૱᫵۳๿ힲ%4g'3h%2g&3h&3h%2g%2g'4i&3h(5k(4l'4k$3k%4k$4h"2g$4j$5k%7k#8k';n#7j&:m#7j$8k$8k&:m%9o'y.>y0@{.>y0@{0@{1A|0@|0@|/?z3B}2C{1Bz2C|4E}5F~5F~0C{0C{1D|3E}4E}5F~5F~3E}3F~2F~2G1F1F0E~0E~0E~2G3H4I1F3H1E~3F~3F~2E}1E}2E}3F~0E|2F5F~/C{3G~/E|2G1F3H4I6K2G2G2G}2G~0E|0E|1F}1F}/D|.Cz/D{-By-By/D{0E|.Cz0E|3H2G~1E}4F5H4F~3F~3F~4F~5F~7H9K:J6H7J5H4G6I8K7J5H5H7J4I2G3H~2G3H/D{1G~2G1F}0E|6K2G~3H6K6K8M6K8M7L9N8M9N9N9N8M8M:O:O:O;PSAVBW?T>S?T@UBWAUAV?T?T@U@U?T?T>S?T>S>S>S=R=RT?U?UAW@VDZE[DXDYEZDYG\FZI\G[I]J_I^NcMaM_MaLaNdMcMbNbPbObOcNdOdPeMbPeOdOdOdNcOdNcOdOdNcLbNdLbLbK`NcK`J_K`H]J_J_K`J_K`I^K`J_K`K`I^J_H]I^I^I^J_J_I^K`I^J_K`J_LaLaLaMbJ_I^J_J_J_K`J_J_K`K`K`MbK`K`K`LcKaKaMcLbLaNcNcNcLaKbHcLdL`H]KaI_I_LbLbJ`JaJbLdKcNeKbNeKcIcLaKaLbMcJ`NdLbKbLbNcLaMbLaOdKaKcMdJaMdLcMdLbLaLdJbLdLcJaI`JaI`KaI_KaKaKaI_J`KaJ`LbKaMbMbMbLaMbKaNdLbI`K`J_J`IbK`J`J`McLbKaLbMcMaKaKbKaKaJ`LbLbKaLbKaLbJ`LaK^L`LbLbJ`K^K_ObKaKaJ`LbLbKaLbKaLbK`M`M`L`NaK_MaK_K_KaKaKaLbLbLbKaKaI_LbLbKaKaJcIaH`H`JcIbJcHaJdIaMaMbIaLdIaMeIaNcKaKaMcNdKaMcPfNdOeNdNdMcOdLbIbIbKcMbObMaObNbNbPdOcKdKcLdKcOfPePfPfPePfNdNdPfOfOgLdMeOgMeOgOgMeLdLdMeMeNfLdMeLdNhPdNgNfOgOgOgNgOeQgOeOePfPfQgOeQgOeNdPgNdPgPfQgQgMdOeOeOePfNdNdRhQgLdKcQiOkPgOgOgPgRfQgQgRhPfPfRhQhNiPgMePhNfQjOfTjPeSgRiRhTjTiShTiRgNgQiPhQjTiUkVlSiRhSiRhSiRhVkQjQkRjThTiShRgShQgShVjUhShUjShShUjRgRgRgTiShUjShShTiUjTiUjTiVkTiUjThQiSlSmSlSlRlWnSmTmQjRlTkSjUlUlZnWl\qYnZoXmXnXmYnXmZoWlWlWlUjVkUjTiVkWlWlVkWlTiVkTiVkVkUjXmXmWlVkWlXmUjWmYnZoXmZoYmWkSlYoVmVmVlYqVnTlVnWoWoXpYnWoWoWoUmVnWoWoWoYqYq[sYq[s\t\t[sZs]v]v_xcwdxezfyb{`yb{fyg|dxeydz`xd|_we}ay`xe}d{e|f}lklml~mlmnpommpitrstww{wxz}΁~΁Ђ׃Մԃք҈ԐՖؠأګ઱߫ߩݪ㬱䠮ݗӝة䬴ݱսۼռڳ֪էӠҠԞ֞מ֠؟أؠ֥֦ӪԬԱٵ۵ݭ۩ץ٦ݣܞ؟ޞߞڟܟݝڠڧੵ߯䳼㶺잱&2h%2g&3h$1f$1f&3h&3h$1f%2g(5j'4i&4h&7j%5h$4h&5j$3j!2h$7j%;m$9l"7j#8k"6i#7j$8k';n&:l$9j$;n'r';p';o&:n&:o(r+>q->t*;q):r):s):r)9u)9v*;v';u(y-=x-=x.>y.>y-=x-=x,z.>{.>{-=x0@z3D|3D|6G~1Bz2C{3D|1C{/Bz1D|/C{1Bz5F~2C{3E}4G/Cz0E|1G0F~0F1F1F2G3H2G0E~1F2F1D{2C{5E}6F~4F~2E}1E}4F}5F~4F~4G1E|2G}1F}6J3H2G2G0E~0E~0E|1F}1F}0E|0E{0E{/Dz/D{.Cy.Cz/Dz0E{0E|1F}1F}2G~3G~2E|4F~5G~3G~3G~5F~6G6G7H9J7I6I6I5H6I6I7J7J6I5H5I4I4I3H~2G~1F}2G~1F~.Cy1F}2G~2H2H~4J7L7L6K8M8M9N7M9N7L7L7L:O:O9N8M8M;P:O:O=R>S;P=R?T@U@U@U@UAU>T?U@V@VAU>S=R>S?T@U?T?T>S>S=RS=R=R=R?T>T?U=S?U@VAWCYCXEZG\DYDYEZF[GZG\I^I^J_H]I]K_LaLaLaNcObPcNaPcPcNaPfQfNcNcOeMbOdNcPeMbMbMcLbNbLbLbI_NcK`I^K`J_K`K`J_J_J_H]K`I^K`K`K`K`H]I^G\G\I^H]H]K`H]H]I^LaMbLaI^K`K`I_I^J`LaK`LaLaJ_LaK`MbMcMbLaMbL`I_LbMcNcNcMbNcKaLaJaJaK`NdJ`J`McLbKaMcJbKbLcKbKbKbNeMdKaNdKaMcNdKaMcKaKaMcNcLaLaLaLaMcKbLcLcKbJaKbMaMbK`L`LaJbKcLcKbJaKaI_I_H^K`LbLbLbK`KaLbMaLaLaLaMaLbLbMaLaI`KbMcJ`K`KaKaJ`J`McLbLbNdKaLbH^I_I_KaKaLbLbJ`J`J`J`JaL`KbLbKaL_M`MdKaMcLbKaKaLbLbLbJ`I`KaKbJaKbJaKaI`KaKaKaJ`J`J`J`J`KaJ`KaKaLbLbIaH`IaHaI`JcJcIbJcJbHaF_JbJbJbKcIbMcMcKaKaKaOeQgQgOeOeNdOeOeNdNdLaMbNdNdMdMcMdOeKaLbNdMeLdKcNfNfPeQgNdPdOfQgPfQgOeMgQiOgNfLdOgNfLdJbMeNfNfMePhOgMePhNgNfLdOgLeNeQgPfNdPfRhNdRhPeQfOePfQgPfNdRgPfOeOeOeRhPfOeOePfQgMcQhNgNfOgNePhOgOhOhQeRhQgQgPfPfRhQgQiQiNfQiQiQhRgQfSgSgRhRgUkPhPiOhQiRjQiRjSkTiSiTjUkTjRhTjUkUkQgUjRfTgSjRhRhRiTgViUfSgUiQgShTiRgShTiTiRgTiRgRgTiTiRgShTiUjUjShQfQfTjPiUmRkSlTmUlUmPiTmTmTmUlSjUlSjUnTnXmXmYnXl[mWjXmYnWlXmWlWlTiTiVkUjUjWlUjTiUjUjUjWlYnWlWlVkVkXmYnXmWlWlWlXnXmXmWlWlXmVjTlXoYpYoVnWoWoUmSkYrYqXnXpWoVnXpWoYqXpXpYqYqZrYq[s\t\t\uatau]qbvcxcxbwdxb{fzfzdydydyfyey_wc|b{ayayayc{d|`wd|g{gzj|j|mkk|l~mmmmnomoprrstvvvvxxw}|{}~~҃ҊՒٗכڝܦ৭ߧܫݰ⪳ߥܫ᯸⮸޲ټּظگ׮լ֦Ӧ֥ԥզե֫ح٬ײ׶ܺ޿㹽ݯۨקܢ١ۣۡݟ٠ۢݥަݭܳ㸼乼ṿ޽휴%2h&3h&3h%2g&3h%2g&3h'3h)3j(5j(5j'6j%5i$4i&6j"3h!2h"3h$4h'7k%5i%5i&6j#6i$8k#7j';n#7j';n&9l$:k$:l#8m$8l$9n&8n'7m(:p(8n'6m'6l'5l(8n%6l'8n&7m&7m%:n$9n';p%8n';p';p';n(v.?w,y-=x/?z-=x.>y/?z-=y/?y0@{0Az0Bx0Az1A}2C}3D{3D|1Bz0C{1D|1D|2C{4E}5F~5F~1D|2E}1D|/A|2D~0E~0E~0F1F0E~0E~1F2G2G1D|4G6H2C{4E}3C{1E}3F}5E}4E}4F~3F2F~0E|3I1F2G0E~1F1F~0E|/D{0E|2G3H2G1F/D~0E~/D~0E0E}1F|0E|0E|1F}0F~4G4F5E2E4G4G4E}9J9J8I6H5I5H6I7J7J5H7J7J7J5H5J4I0E|1F}1F}3H/D}0E|0E|0D|2E}2E}4G6J5J5J6K6K8L7J9L8J7L7L7L9N8O8N8N:N;P9NS=R;P?T=R>S@U?T=R=QAT@U>T=T>T?T?T?TS>S=R:OS=R=R?S>U>T@VAWBYBXCYCYEZF[EZF[F[F[EYF[H\I^I^H]I^K`J_K`K`MbNcNcNcOaNbPcOdOdNcPeLbOeMbNcOdNcNcKaI`JeJbKaNcLaLaI^LaK`LaJ_LaNcJ_K`MbJ_K`J_J_K`H]H]I^H]I^J_I^J_H]H]H]J_LaNcK`I^K_L_L_L`K`MbMbMbK`LaLaNcL_LbLaK`MbJ`LbKaLaNcNcOdLaMbJ`K`K`MbH^KaI_McJ`I_JaJaLcLcKbJaLcKbMbNdK`KaMcKaKbI_KaMcMbMbLaLbMbJ`MdLcKbLcLcLcLaK`J`I_L`NbMbJaJaLcLbI`LcI`KbKbKbMcIbKaLaLaLaMbLaMbKbMbMbMbK`KbLcIaK`KaKaI_J`J`I_J`LbJ`McI_J`KaKaLbMbKbLbKaJ`LbLbM`KbKaLcM`MaLbKaLbKaJ`J`J`J`J`J`LbKaJ`J`LbJ`I_KaJ`KaKaKaKaJ`J`J`KaKaJ`LbKaJ`G_G_H`IaIaF^JcIbHaIaIaJbJbIaJbLdLcLaLbKaLbNdMcNdRhRhPfPfMcOeMcPfMcMcKaMcOeOeOeOeNdNdMdJbKcNfMeLdNeSiRjQjSiSiRhNeNfQiTlRjPhOgPhNfPhLdLdNfLdOgKcMePhOgMeOgOgNfMeLcPeQgRePfPfPfPfQgMfQiNfPfQfRhPfPfQgQgQgOeOeOeQgPfRhPfOeLeNfOgPhNfQhTgPcQgQgRhQgPfPfQgTiPhPhNfOgOgNfSiTjQfShUkRjNgPhPhQiTjOhQiRjTlUjTjSiUkUkSiSiSiRhSiUjTiTkTjSiTjSiSgSgUfUfThQfTiShUjQgShQfRgTiShRgRgRgRgRgRgShTiUjUjWlThThTlSlUmRmSnUmTmTlSlQjUmSjSjUlTmTlUjZoVkWl[mYmVlYnVkXlWlWlVkVkWlXmUkUjVkShVkVkUjWlXmVkYnYnYnWlTiVkVkUkWmZlZnZmWlVkWlWlXoUlTkVmWpVnXpTlXqVmVlYmWpVnVnXpYqYqXpYqXpZrYq[sZr\tZr]u_s\q_t_t`v`vdxdxbvdybwbwbxdwdwdxf{eycwazbzayazaxg{eyi}hzh{ji}kjkpoqmngnpoqpssrsstvuxyyy||z|хӌڑږܗܚڢܧݨڬ۲⫯ܬެޮݴݼؿھ޹ֹض״ٱٯױڲձַ۸۷ۺۿ޼ݴ۪թۦܥڢ٤ܢܦޥ۪ٮ۸⹽乼꘯&3h%2g%2g&3h'4i'4i$1f%2e(4h(5j&3h'5j$4h#3g$6h&6k!2i#4i#4h$5i 1e$5i#4h#5i"7j"7i#8k$9j$9j!6h$8j$9l%:n$8m$8m#6l%7l'6m(5l(7n'6m(7n*9p%7m&7m)9p(:p&9n&8m%:n$9m%:n';p(=q)y,y-z.>z.=y.=z1@}3B3B~3B}2Cz0Ay/Ay/C{0D|0C{3D|4E{3Dy1D|/C{/Bz.B{0C}/B|0C}1C~1D}/D}2G/D}2G/D}2E2D{4E}6E~4D|3C{2D|0E{2C}6C6E~1Cy3G|3F~1D}4F4H1F/D}1F/D}/D}0E{/D{1F}0E~2G2G1F0E~1F~1F1F2G}1F}2F}2H~2G~3E3E4E3F3E7G7H7H7H8I7H5I5H2E}4G5H6H7I6I6J7K2H0E~1F}3I1G}1F}2G1G}0D{2D}1D|3F~4G3G3J2G~4J5K7K8K7J7I:M8M7M7N6N7N7N9M:O:O;PS>SR?S>R=R=SS;P;PS>R@U@U@T@V>U=S@W?UDWBYCZEZDYCXF[CXFZFZFZEZE\G^I^G\J_J_K_J_LaJ_LaMbLbL_MaNcOdLbOdKaOgNdMbNcNbNcMcMdHbKcLaNdMaMbLaLaNbLaNbLaI]J_I^K`J_J_K`LaLaI^I^H]I_J_J_LaJ_I^J_H]K`L`I^LaJ^L^L_M`L`KaLaMbMbLaLaK`NaL^K]K`J_J_H_KaI`L`MaMaNcLaNbL`J`LaLaKaKaH^I`KaLbKcKbLcKbMdJaJaMdKcLcKcLcLcKbNbKaLbLbNcMbOcMaMbK_LdKbMdJaKcKbK`LbL^L_LaL`L`JaJ_J_K`J_JcKcJaLbL`J`KaKaH^J_K`LaLaK`I_KaK`K`LbKbKcMdKaJ`K`J`LbJ`J`I`KaKaI_LbKaH^J`LcNaKbKaMcKbKbI`LbMcKbK`M`MaLcNdKaJ`KaJ`J`JaJ`KaJ`LbKaI_KaKaH^LbJ`KaKaJ`I_KaKaJ`KaJ`J`LbH^H^HaF_G_G_HaHaGaJcHbH_JbIaIaIaH`HaLcI^LbMcLbLbNdMcMcPfPfNdMcOeNdLbMcMcOeMcNdOeMcNdMcNdLcKdKcJcKdMeOeQgRgRkUjTjTjQgRjPhTlRiRiQhPhLdMeLdNfLdMeLdNfOgNfMeOgNfMeMeMfMfQfPeSgQgQgPfQgQgNgOgNeRgPgOeQgRhQgPfRgOeQgOeRhRhQgRgOeMfMeMeNfOgOgReQfRfRhRhPfPfRhQhSiPiOgOgPhQiQiShQfPeQeNhPhRjRjQiSkThPiSkOgPiTjTjSiVlRhQgSiTjSiTiViTiRiSiSiSiSjThRfUfUfSgShShRgThRfTiRgUjShQfRgRgTjSgThShUjUjTiRgShWlUkRkSnSmRmVnSkSmRnTlTmSkTkSjTkTmWoXlWkYmXmWiWkWlWmWlYoYnVjXlUjWkUiVjUjVkUjUkUjZlVkXnXoWlXmUjXlXmYnVkXmZlZl[lYmZoZoXmXmXnSkVnWoTmVnWoXpVlVlVnZnXqXoXpWoXpYqXpYqWoYqYqZrZr\t[s]v`t`tau`tcvatcwcwavawbwbxdvdwewfycye{bvf{bzayayd{dyf{i~e{g{g|f~hkjnlmlmjmqopopqqmppquutvv|yz{ӂԉՉ֋ؒܗܜءר׬ڬܨجܨܮݲޭֳڹݼۼܼܽݿݽۼݻ۽ỿݸ౶ܫةܩݪަ٩ݪܪٰ׳ٵ۷ߺ좱隲%2g%2g%2g&3h%2g#0e'4i%2g'4j'4i$2g#5h#3g"2e$4i#5h%5g$4h"3g!2g#4k#4h#4h"6i"5h%5i&6j&7l'7k"2g&6j&6k&6l%5k(6m(5m%5l&8m#5k%6l'6m(8n'8n(9o(9o$7m"7l(;q*6q)9r*:s(8p&7p&9r(;r'9q):o(9o):p(:p*:r+8r(7o)7r)9t&6q(8r+v):r,=s,=v.>y,>w.?v0Ay/@v-@u(S?TS=Q>S>S:P;QT>S@UAV?T@U@TDXBWCWDYDYBWBWBWDYBWEZE[EZI]H]G\G\H^G]G]I^LaI^MbK`MaK^M`NaM`QbLaNbOcPcMbNbLcLbLbLbKbIcIbKaNdKaKcKcJcJcIcIbKcJbKbK_LaMbNcK`LaK`J_L_L`K`K_J^J_K`K`K`JaH`IaJ_K_M_L_O`M`K`LaMbMbK`K`K`J`K`KaJ_J_K`L`L`K`I`I`LbMcLbI_KaJ`KbMbMbLaK_NcMaMaMbMfJaMdLcKbNeKcMbMcLbKaKaKaKaKaMaNcNcNdLdJbJbMbKaK`KaMdNaL`K`J_MbLaJ`JaI`K_J_K`G\K`J_J^L`MbLaJ^K_LaLaK`K`J_J_J^K^L^LaK`K`L`MaK_KcJbJbJbMaMaJ^K`KaJ`LbI_McI`MaK`KaLaLaLaMaMbJ`KaMcKaLbKbLbLbLbKaNdMcKaLaL_J]J^J_KaJ`J`H^I_J`H^McG]I_I_J`I_I_LbJ`KaKaJ`J`KaG\NcI]K`K`KaMbLbJbIaIaIaLdJbIaJ_LbKaNdLbLbMcLbNdPfNdMcNdMcMcLbLbKaOeLbMcNdMcMcMcNdLbNcQgNcNcMcLaQgNdQgRhRhRhUkTjSkTlPiPkNiLgNiOgNfNfLeNfKcMeKcOgNfOgNfMeOgNfPdRhQgOeOfRhPfPfRhPeLdNgNjLiMgLgNhOjMhLgMfRgRhPfRhPfPfPfPeOgNfMeLdMeNfPiMfNgOhQjRgQgQgQfPePhRjOgPhQiPhLdMfOhPiNfPhPhPhSkRjSlQjQjRkTiTjQgRhRhVlTjSiTjTjTjUkTjSiSiVlSiTjViSgShThUhTjSgRfMfQiRfRgRgShPeRgRhUgPiOiUhTiShTjTiUjShUjVjVkVkWkVjVnRkQjToSkSkVnTkUlTkVlUnVoTlUnVoWmWjXlYlWlTmSlVpWkRlRlSlWlVjYkWjYkXnVkVkXlWlYnVkVoVkVkXmZoWlYnWmXmZoVkYnXmXmXlXlZnZnTlWnVnXqVnUnYmWpXpWoXpWoYqZrXpXpYqZr[sZrZr\t\t\u^v]v]v`u_udwbvbyctgydvbubvbubudvdvav`y]wc{`xb{g{h}g|f{h~g}h|mmm~oonkmooppooosnrppqsprsxuwzx~~ՀԄ׈׌ؔؗؠ٤ڧ٬ܪ५ۨߧޥݧ٧ذڸٿؿ޻ܷܴݴޯܬث׬ج٩֩׭ڱշڵ$1f%2g$1f$1f$1f#0e$1f%2g%2g%2g%1f!2f"2f"2f#3g$4h#3g#2f!2f"3g"3f#4h"3g#4h%5i#4h"3g$4h(7k$4g%6k%6l$5k&7m%4k%4k$3j&5l$6k%7m'5l&5l(7n'8n&7m'8n%7m'9o'6p(9q):s'8q'8q':s':s%8o&7m):p'8n(9o'8o&8p(9q):r(8t'7s'8r(:q):r):r,=u*;r->v):s-=x,v,=u,=u+u,=t,>u-?v.?v.?v.?w1Bz/@x1Bz/@x/@x0Ay0Ay0Ay1Bz1Bz0Ay1Bz0@z1B}-A{0C}0B}/B|1D~/C}0F~0F/D}/D}/Bz0D|0D|/Bz.Bz0C{/Bz1D|3D|3D|0C{1D|1D|2E}2E}1E}/E~-B{/D}.C|.C|.C|2G0E~1F2G1F2G0E~2G3H1F2G3H3G3F2E3F2E5H3F5H4H5G7G7G7G8I7H5F~2E}4G3H2H6J6I7I4H1E|2H1F}4H3E~1E}1F}1F}/D{/E|1E|0Bz2E}1D|2F~3G5H4G5H5H6H6J4I4I3H7L6K8M8M8M6L7M8M7L8M8L9N8M:O:O=R=R?T=R?T=R?T>S;Q>TQ>QS=R>S?T@UBWAVBW@UBWBWBWAVBWCXDYDYDYEZCXF[G\H]J_I^H^H^H^I^K`K`K`J_L_M`M`NaNaM`N`M`NaMbNcMcLbNdKaKbJcLaKaLbJ`McKaKaJbGaHbJcKbLbNcK`NcMbMbK`K`K`J_I^IaJbLcMbMbKaLaL`JbLbJ_I^KaK^L`M`I^LaLaLaJ_LaLaJ_J_K`J_LaK_G]I`KaKaLbJ`KaKaLbJ`LbKbMbLaJ_LaMbLbLcNcMeLcKbKbKbNdNbMcKaLaMbJ_J`LbMcLaNcLaLcMdMdLcJaKaKaKaJaI_J_J_J_J_J_J_JaI`J_K`K`K`K`LaLaLaJ_J_K`J_J_LaK`J_K`K`K_K]J\K]J_J_I_K`I_IaMaL`J^J_LaJ_KaJ`J`KaKaJ`KaK`KaKaJaNcOdKaMbLbJ`KaKaKaKaNdMcLbKaKaKaLbMcMbL_L_J^J`I_I_J`J`J`I_KaKaJ`J`J`I_I_I_I_KaKaKaJ`I_G]J`H^I_J`J`K`I`HaHaIaIaIaJbJbLbLbKaLbKaKaLbKaNdKaNdLbLbNdMcLbNdNdNdLbLbLbNdNdNdLbLbMcLbMcOeMcNdLbNdPfNdQgQgTjShRkRjNgOhMhLfOfOgNfMeSgOhNfMeOgLdMeNfNfMeLeMeQePgPfQgQgTjPfPfQgPfMeLdJeMeMdNfOgOfOfMeOgQfQgQgOePfPfOeQgOgOgNfNfOgNfOfOfMiNfOfQfRhRhRfSjMgPiOgQiOgOgNfMfPhPhNfNfOgPhPhQiPhQiTkPhUjSiTjTjUkUkRhSiSjTjUkUkUkUkSiSiTjSiUhUiSgVjUiTgQfRfRgShRgTiTiPeTiRgRgSeSgTiShUiWjVjVkUkTiUjUjTiUjVjSlSkVnTkTlSkSlTlSjTlRiVmUmTlTlVnUmUnWlXlXlWlSlTlTlVjRlViUiUjWkXlWkYlVlVmVkXmWlYnUjWkXmXmYnWlWlZoYnXmWlVkZoWlWlYnYnWlXmYnVmVnWoXpZnWlWpWoXpVnWoXpZrXpZr[s[s[sZr\t^v\t]v]u\t_w`t`ubxaucwcxat`sbubucvcvbtbvbv_wby_xazh{i~g|cxf{g|f{jk|lnnqoknpoooopqnlqqpqrqqruvxuy{{̀~ԅԆԏӔ֜֠էڤڦ٢٣ڝןץڥت٬شܼ߻޹޶۵ڲڰ׳ٲ۴ܴ޳ٷ߻ݾ%2g#0e$1f%2g#0e#0e$1f%2g$1f$1f$2f#3g$4h$4h!1e$4h$4g"3g 1e!1e"2g"3g#4h$5i$5i#4h"3g$4h#3g"2g!3i$5k$5k#4j&4k(7n'6m%3j%4k&8m'5l&5l&6l'8n%6l%7m'7m&7m'8m'8n'8n'8n&8m%9m&:o(;p*9p*:p'7n'9n'8o'8p'8q&8o&7n(:q&7o'8p(9q(9q+y.>y.?w/@x.?w.?w/@x/@x.?w0Ay/@x.?w/@x1Bz0Ax1Bz->y,@y.B|.B|0C}.A{2E2D~1C}0B}3C{4D|3D|1Ay1Bz0Ay1Bz3C{1Cz.By1D|/Bz/Az0C{0C{/Cz/D}0E~.C|.C|/D}0E~0E~0E~1F0E~0E~3H2G2G2G3H2G/E}1F3F2E3F3F2E2E3F5H5H3G~1D|3G5H3F~3F~2E}3E~4F~4F~5H3F~8I4F~2D|3G2G~1F}0F|2G~1F}1F}/D{/D{0E|0E|2E}2C{4D|5E}4F~4G4G4F5G6I2G5J5J6K5J6K6K6J7J8J8K6L7L7L7L7L:O9N>S>S>S>S=R>S>S>S>S:Q;QS=R?T=R?T@UAV@U@UBW@UAVAVBWCXAVBWBWF[EZF[F[G\G[E[D[G^I^I^I_K`M_K^K^MaK^ObN`NbLbLbMbLaJ`LbKaLaLcIaMcKaKaKaMcKaJ`MdPfMdLcMdKbLaJ_NcI^LaMbMbK`K`MbMdQhOgPgQgPdNcKaJbJ_K`K`NbL_M_MaJ_K`LaMbMbK`K`LaLaJ_I_KaI`I_H^I_H_KaJ`LcLbLcI_J`LaMbMbMbNcLaKaLcIaJaLcKbKbKbKbMaKcMaMbMbMbK`KaMcLaKcLcKbKbKbLcLcJaLcJaKbLbLaK_J_K`J_KaI`I`J_K`J_J_LaJ_K`LaK`J_K`J_J_K`LaK`J_I^H]I_J_J_K`K`LaJ_H]J_I^K`K`I_K`OcK_LbL`KaKaI_J`I_I_KbMbLaLaNcK`KaJ`J`LbMcLbLbMcMcMcLbKaKaKaJ`KaLbJaJ`I_I_I_I_I_KaJ`J`LbKaI_J`I_J`I_KaKaKaJ`KaJ`I_J`J`I_I_KaJ`I^LaMaIaG_IaJbKaKaKaLbJ`KaKaKaNdKaPfKaMcKaLbKaLbMcMcMcMcNdMcLbMcLbMcNdNdKaOeLbMcNdPfPfPfQgPfShTiShSgPeKcNfNfMfMeSfQeOdLfMeOgLdMeKdLeLdQfOdOeQgQgQgSiSiTjRhPfOeNdLdNfLdNfLeMeMeMfLeMeMePePfQgNdOeNdNdPeNgOgNfNfNfQiNfOgLdOgNgRgPfPfQgQfQeSgOhQiOgNfPiRePhNfOgMeNfPhQiSkPhPhQiPhTjQgTjSiTjTjTjUlShShTjTjUkSiTjRhRhRhUhUhThThSgThTiShRgShShTiShQfRgShShSfTiShUiThUiUhUgUgTjRfTiUjVkUiSlSkTmSlSlRjWjVjVjXmVoVnSkTlUmVnVnUnXkYkYmVkUnTlSmXlWlTiTjVlVkVkXnWnYnXnXlWlWlXmYnYnVkXlYnVkYnXmWlZoWlXmZoXmWlWlWlXmYnYnUmWpXqWo[o[oXpWoXpXpYqYqYqWoXpZrYqZr]u[s[s]u`t_s_s_s`v^savav`wavbwbufxcvcuatbwcway`x`xdwdxfyf{g|i~iih}jllmmnpoopopnnpknnoqoronrrsutuuy|~у҆эՒӘ֠נ֟ՠםם֢ۡף٧ܦ۪ڰݵٿ޿ۺۺڸ۹ڴعܻߺݻ߾蹽%2g%2g%2g#0e%2g$1f&3h&3h%2g%2g&3h%3h#5i&6j"2f$4i$4g$5i$4h 4f 4f"3g!2f#3h"3g!2f$5i%5i#4g"2g"1i&7m%4k%4k%4k$3j%5l&6m%4k&5l'6m(7n'6m%4k$5k%6l#4j'8n'8n&8m%6l%6m%9n';p';p';p&;o&;p$7l)9o(9n&7l&7m'8o%6n):r%6n(9q'8p(9q+x-u/@x+v/@x.?w.?w.?w/@x/@x,Q=OS=R=R>S@U@U@U@UAV@UAVAV@WAX@UBWCXCXEZDYCWEZF\CYF\G\G\I^FZL^J]L_K^L]M_M`K_N`OaNaKaK`OeLbKaKaK`LbKaJ`McLaKaLbNdLcMeNeOfLdLcMbMbJ_J_LaJ`MbMbLaOdPhSjQiRiSkQiPhLcKbKaMbMbNcL`M`LaLaLaI^LaJ_K`LaJ_H]I^K`I`H^J`I_MbMaK`LaJ_L`K`MbK`MbMbLaMbI^McKbLcJaLcLcJaKbLaL`K`MbLaMbMbLaMbK`J`JcKbJaLcKbJaJaKbLcLcLcLcLcKbJ`MbLaJ_J`KbJ`J_K`J_K`K`I^K`K`K`LaK`J_K`J_J_J_I^J_J_I^I^K`H]K`K`I^K`K`J_LaK`L_M_K^K]K^K`MbLbLbLbMbMcMbNbLaMbK`J_J_J`LbKaMcMcMcMcLbKaJ`J`J`KaMcJ`J`J`KaJ`J`I_I_J`I_KaJ`H^J`H^J`J`I_J`KaKaKaJ`I_KaI_J`I_H^I_I_I_J`I_H^F_F_G_LaKaLbI_H^KaJ`KaLbJ`LbKaNdKaMcNdLbMcKaNdNdNdLbJ`NdLbMbMcJdNdLbLbNdMcPfNdNdNdPfOeQgRiShPePdMfMeKdMePeOeQfMbLeKcLdOgMfOePeQfPfOePfPfRhRhRhTjVlRhPfPfOeOdMeNeQfQfPeQfPfOeOeQgNdPfOePfPfPfPfPfMeMeMeMeNfNfNfOgOgNfShPfQgRhRhOfQgPfRhNgOgPhPhTgNfNfOgNfPhOgOgPhRjQiQiQiShRhSiTjSiQgRiSgTjThUhQjTiSiUkSiUhUhTkSjUiThThSgSgUhQgShShTiRgQfRgShShReSgUiUiViTeVjRfUiRhVkShShUjSiSkSkVjVjVjVjTjYnTiWkTmUnVnTmTmTmTmUmXkVkZmWmTlVmXkXkWkXlYmWkXoXnXnWmVlVlYoVkXmYnXmWlWmVlZqWmYoYnVlYnXmVkWlZoYnYnWlWlYnYnYmSkVnXlXmZpWoYqXpYqYqXpYqXpYqYqYq[s[s[s\t]v`t`u`u^s^s`u^waz_u`wav`u`vbw^tdz`v^u_x_w^wdyfyg{g}i}lh|i}jf{mkkonllpnomnjkknkkopoporkqqruxxyˀЂχӉӓ՗֖Ԣ٠֢أסԤզԤԦ֨تخڰٻܾ߼޿筳䩳$1e$1e%2f$1f&3h&3h$1f$1f$1f%2g$1f%3h#4h%5i#3g$4f"3e"4h"3g"2g"2g!3g"4f#4g!2g$4i(7l#3g#3g$4g&6i$5j"4j$3k#3j'6m)5l&4k&6m'5l%2i&6m%4k%4k$4k$6l%6l%5k(9o'7m&8n%:o%8m$8m%9n$8m&;p$9n':p'8n(9o'8n&7l'8o&7p'8p(9q):r(9q):r,=u):r,=u):s*:u)9t*:u,v->v->v,=u.?w.?w.?w->v/@x+z.?y.A{-@z.A{/B|/B|.A|0B}0?|1@|.>z.>z0@|/?z/@{-@z1D~/B|1D{.Ay/Bz1D|0C{1Cz0C{.Dy0E|-By-By2G/D}1F0E~/E~0D}1C}1C}1D}0C~2E1D0B~2E2E1D3G0E~1F2H1G1G1G1H2E}3F~3F~3F~2E}4F~4D|3D|2D|1D|3F~1C|4H1F}1F}0F}/F|0E{/D{/D{0D{/D{/D{0E{2C{3D|4D|6H2E}3F2G3H2H3H2G3H2G4J4H6I6I7J7J6I7J6I6I9L8K8N;PQ=S=R>S>S@UAUAVAVCX?TCW@V?U?U>T>T@V?V?V>TCYBX@VAWBXAXEZG\G\I^J\J]I\K^M`N_N`L`L`L_M`OcLbKaKaMcMdLcMcKbLbLaLaKaNdMcMbJ`MbLbLbMcNcNcMbK`K`LbNdKaMbQeMfOeSiSiVlPfOeLbKcKbMbK`LaMbLaOcLaMbNcK`J_LaK`K`LaJ_K`J_I`J`J`K`NcNbL`LaL`LaMbMbLaLaNcK`K`LaLcKbLcLcLcJaJaJbL`LaLaNcK`LaNcLaI^J_J_J_JaLcKbJaJaI`LcJaLdLcKbJbIaJ_K`LaJ_H`KbLaI^K`J_LaJ_LaJ_K`LaLaLaK`K`J_J_J_J_H]K`J_H]J_J_K`K`K`K`J_L`L^K^L_J]M`M`K^LbJbKcKbKcMeLdK`LaMbLaI^J_KbJ`KaLbLbMcLbLbJ`I_KaJ`KaKaKaKaLbKaKaKaKaKaH^KaKaLbI_J`I_H^I_J`H^J`KaKaKaJ`I_J`I_J`KaH^J`KaI_I_KaKaK`KaI_I_J`I_KaJ`NdKaLbMcKaLbLbLbJ`KaLbMcLbMcMcMcMcLbJ`LbJcKcKbMbMcLbMcMcLbNdLbMcLbRhRhOfOeQgQiLeMdNcPePfPdNcMcKcKcLdLdPdOePfMcNdOeNdRhTjRhTjSiUkRhPfPfPfNeJbJcOdNdNdNdOdRhQgNdQgOeQgNdNdOePfOeLfOfLdMeMeMeMeLdMfMeOeOeOeRhRhQgPfOeRhPhNfMeOgOiOgQiNfNfOgPhOgPhPhQiPhRjShSiTjRhSiTjQgSiRfPjOhQiRiUkRhThThSgRhTkUhThRfUiSgRfThSgTiTiShRgRgRgTiUhSiVhUiThWgTiUiUiTiRgShUjShSgRlQiUjUkUkUjVkUjVkXmUiWkYnWmYnXnXnXnVnWjVkVjVnTlUkWkWkVjXkXmWnXnXnWmWmTjXn[nVkWmVlYoYm[nWkZmYmXlYkYkWlVkVkVkXmYnWlWlWlYnYnTkUlZmWlXmVoWoXp[sXpXpXpXpXpXpYqZr\t[s]u]u`t`t^s^s_tav]w]vavaw_ubwbw`uavav`xbxay_xdwcyfzfzh|i}eyh}lh|h~i}nmlpnnmoknfklkkokpqmpopoqrrssw}{ЂЃωӑԕ֕՘Ӟӡפԭ׮ٴޮکҩ԰ٱܩԮҼ޿쯶殹&3g%2g(5h(5i%2h$1f&3h&3h$1f$1f#1f&2g$2g#4g#3g#3g%5h#3g$5i!2f 1f"2f$6h$7h$6g$6g%5h&6h$4i!1e%5j&6j(9l/b'6k%2j&3j)6m+7n(5l&3j)4k(6m$3j'5l&4l%5k#5k$8m"8l#7l$8m$8m$8m&:o':o)8o'7m'8n*;q&7m'8n(9o&7o&7p'8q'8p'8p+v+v,>v,>v,=u.?w,=u+y,?y+>x,?y/B|,?y*=w,@z/A{.=x/?z0@{0@{/?z.>y->y-B{-@z/A{0C{.Bz.Ay/Bz.Ay-@x/Bz,By-By/D{,Ax2G}3H/D}/C}0B}1D~0C~0C~2F0E0E1E0D2F0C~0E~0E~0E~0E~/D}1F0E~0F0E~2E}1D|2E|4G~3G~2D|4D|1D{/E{.By0Bz2D|2F}1G}2F|1E}/D~0D~0F}/F{0D{/E|/E{1E|1E}/C{0C{0C{/D{0E~2G3G3H2G2G3H5J4G5H7J7I7I5H6I4G7J8K7K7M7L7L8M7L:O:O9N9N:O:O;P8M;PS=R?T=S>U?U>S?T;Q>T>T?UAV?U?U?U>T>U>T?UCYAXDZEYI]EZF[H[GZJ]J]H[L^N_L\O`L^L`J]M`K^LbMbLaOdLbLcNcMbNcNcLbMcMcNcMcMcMcLcLbMbNcLaLaLaMcKaKaOeRhRgSiSiQgOePfPfNdMcLbNcLaMbNcMbNcNcMbLaMbMbLaI^J]K^K_J`K`K_JaKaLbJ`JaJaJ`KaKaKbKbNbK`MbLaLaMaKcKbLcLcKbLcJaKbK`J_LaMbLaMbK`J_I^K`K`LaJ_LaKcLdMdLdLcLbL`LaK_L`K`I^J_J_LaI`I`J_J_J_J_K`K`LaLaK`J_K`K`I^K`LaK`I^K`H]K`J^I^I^I^I^K`J_H]J_J`K]K]L_K^M`M`M`L`McKbLcKbKbL`LaNcK`LaMbK`I^J`KaKaKaLbLbLbJ`H^KaJ`LbLbJ`H^KaJ`I_KaI_KaI_J`I_I_I_I_I_F\G]J`KaI_J`KaKaI_J`J`J`J`I_I_J`I_I_H_KaI_I_I_I_I_KaKaKaJaKaI_J`KaKbLbI`KaLbI_LbLbLbMcLbMcLbLbNdJbIaJbJcLbOeMcNdNdNdMcLbLbMcOeNdPfPgOeOePePeOdNdOeNbNbMbKdKcLdPdMdPfRhQgOeQgPgRhTjTjTjSiSiRhOeOeOeOdPePePfNdPfOeQgQgOeOeKaNdPfQgPfNdOeQfMfNfJbMeMeNfLdLdQfNdPfRhQgPfPfQgRhTjQgPiNfPhPhNfOgNfPhQiMePhPhOgPhOgPhOgQgRhSiWmTjRhTjSjOhOgQiQiQjSiTkUhVjWkSiRhTgTgSfTgTgSgViThQfRgShTiShTiTiVhSjSjSfThShSgUiTgTiShShTiUjShUiSlVjWkVkVkUjWlWlVjWnVlXnWmWnWnXnYnTmVnYmWlRjUmWkWkYlXkXkUlWmVlXnWm[qYoUkVmVkZnWkXlZn[oXl[oZnYmXlXjXmVkXmWlUjXmYnXmVkSiTlXoVmWoXmXmXpWoYqXpXpXpZrZrXpYqYq[s[s[s[s^u[t\t^s^s_t^s_r^wawaxauav`uauavbv`x^t_wexdybwdxg{eyi}h}k|h~kjf~llklmmmll~llgkkmmonoonopqprqtuxx}}̓ЅЈщђՖ֖כ՛Ҩ׳ڸ޶ٳٴ۶ٽẺⴹޮծѴڿﱹ學&3g%2f%2f%2f%2g%2g&3h#1e&3h&3h&3h'4i&6j$4h"2f#4h%5i#3g%5i!3g$6h&8k&6i$3g#6i#4h$4g&6i$4g$4g%5h%5i$4h)9m$3g(5j&3i&3j'4k%2i%2i'4k)7n&5l%4k"1h%2i#5k#9m#7l%8n%9n$8m$8m%9n%8n'8n(9o'8n):p&8n&8n&8m&7m'8m'8m'8n'8p):r+u(:t(=v-?v,v,=u,=u.?x/>z,=x)>x+>x,?y+>x,?y-@z+>x+>x,?y+>x-@z-=x.>y-=x,y,@z,@y,>v0Ay.@x/Bz-@x+>w-Ax-By/D{,Ax0E|/D{0E}-C|1E/B|/B|3F1D-A|0D1E.B}0D/C~/C~/C~/A{0B}1D/E}0E~1F0E~.C|0C~/B|.A|1D~1C~0C~0C~1E/D~0E1F1D3E3G0E~1F/D}0D}0D~.D|.F{0D{/D{/D{/D{1F}/D{/D{/D}0E~3G1E1F3H4I4I6J6I8K5I2G6L5I5H4G2E}7J7K7L6K6K6K6K7L8M8M6K7L6K9N:O9N9N=Q:O8M8M7L7L9N8M8M8P7N8M6K6L7K5I5J5J:O9N4I4I3H5H3H5J4I4I4I6J5G4F7I6I5K6K6K5I6I4G5H4G5H5H5H2F1E2F2F.B}.B}/C~.B}.B~+?|-A/C0C~-@{/B}.A|.B}-A|.C~/E.A|/C~/C~+?z)=x/C~-A|-A|-A|0D~.B}/C}/C}0D~0D~1E/E}1F0E~2G3H4I4I2G3H5J3I6K8M7L5J4I4I5J6L6L8M8N;PS;QT>T?T?U=UAUAVAV?TCWBVBWF[DYCXF[G\H\I\I\J]H[H[K]N_L]M^K[PaM^K_L_K^LbJ`LaMbMbJ_MbMbNcMcKaLbNcNcLbLbLbLaK`OcObPeNbNdKaKaNdRhRhRhRhSiRhOeMcLbOeMbNcNcMbNcMbLaMbNcMcNcLaK`I^LaL_J\K^J`J_K_JaLcKaLbLbLbKaLbLbI`K`MbK`I^J`LdLcKbJaKbLcKbKbJ_J_L`OcLaMbK`J_J_I^K_K`K`J_MbL`K`K`J^LaK`LaJ_H]I^J_LaJ_I^KaH_J`I^K`LaLaK`K`J_K`J_J_LaK`I^K`K`J_H]LaJ_G^G^J`LaK`I^K`K`LaI^J_J`J_M`M`M`L_J_LaOdJbKcMaJ_J_J_LaI^I^J_LaLaJbKbKbJ`H^J`KaKaJ`LbJ`KaJ`MaLaL`LbLbMcJ`J`KaI_I_J_J`J`J`H^I_J`I_I_H^J`KaJ`KaKaI_I_J`J`I_J`L_L_J_J`J`I_J`I_I_KaJaK_L`M`K_MaK_L`K_JaKaLbMcLbMcMcLbMcLcLbKaH`IaJbKbJ`McMcLbLbLbKaMcLbLbMcOeOeOcQdPfLeLdMeJcLdKdKdJcLdKcLcPeNcMcNdReReQeSfSiRhSiQgRhRhOeNdOeRhQgPfMcNdOePfMcNdOeNdOePfPfMcOePfPfOeNeMfNfMeLdNfMeMeOfQfOeQgQgPfSiQgRhRhPgNgNfPhNfNeOgOgMeNfOgNfNfNfPhQiQiQiPhShTjSiSiRhRhRhSiSkRjPhRjQkRhTjThUiThRhTjSjRhRiRhTkShTiTiTkTjThShTiRgQgThTjUjRgTjTgTgRhShTkUiUiUiUiUjUiUiViVjVkUjUjWkVlVlYoWmXnXnXnXnVlVmTnUmVkSkSkTlVmYnVmVlVlXnYoWnXnVlUkYoVlYoZmXlWkYmXlZnWkWkYmZnZnZoVlVkZoXmWlXmYnXmYnZnWnVmWmVoYmWmVnYq[sXpYqZrZrXpWoZr[sZrZrZrZr\uYt[s_s`t`s^t^s[u`t_t_tavau^w^w_wbx_x_xbvdydydzdyg{g|i{hzj|h}i}fi}i~jnllklj|k}ljkknlmmnonoposrqutvwx~̂ЅЊԌӑԑ֖כؤװ۲صشٴ۴ڰװٺ฽ଲϮо߿߿޻޻⻿$1e&3g'4h%2g(4j)5j&2g'3g'2g%3g&3h%2g&4h%5i#3g&3i$1g$5i#3g$2g$3h#2e$4g%5f#6e$6f$4g"2e&6i%5h&6i#4f'6h"2e%5h&3g'4j&3j(4j'3j'4k(5l(4k&4k&5k%4k%3j#3j#8m"7l'u,>u.?v->u,?v*>x)x+>x+>x*=w)w(;u*=w*y-u,Ax.Cz,Bx+@w0E|/B|.@z1C}/B}/B}/B~.C~.B}.B}/B}/C~0D0D0D}1C}3F2F.C|0E~.C|0E~2F2D~/B|0D}0E~0G1F1C}1F.E~1G0D~2D~1D~1D~2F0E~/D~/D}0E~-D{/G}0G}/D{0F}/F|.F|/D{1F}-Bz2G4I2H3H3H5J4I5J5G6G7J2I5J6K4J5G~6I7J5H6K6K5J6K7M6L4J6K5J6K4I7L7L;P:O8M:M7I8J7L8M:O8N8O;N6J6K7L8L7J6J5I5K7L5K8M6K2F3G3H5J2G5J4J3H6I5H4G6I5J5J3H2G6H5G3F3F5H5H3F4H2F0D.B}0D~0D.B}/C~/C0D/C.B~1D~.A|/B}.B}.A}.B},C|-B}/C~/C~/C~.B},@{.B}-A|-A~,C.D.B0D/C0D/C0C0E}2G0E~1F~0E~1F3H4I3H6K5J4I4I4I6K7K6K5J6K7M6L6M8O:O:Q:O:OQ=PS?TT=U;V>UCWBWBW@UAVBWEZF[E[G\I^EZH\H[G[H\J\I\L^L\M^M^M_O`N^N^MaK^N`M`M_M`LaJ_K`MbLbLcLcLaMaMaMcMcKaJaMbMaNaOdLaLcJaMcJ`PgPfRhTjQgPfPfPdQeMcKbMbNcNbOdMbMbLaMbOcNcMbMbLaK^N_J_L_L^J`K_JaKbK`KaKaLbJ`I_J`JaMbMbK`LaKaKbJ`LbLdMeMeLcLcL`LaKaIaK`MbMbJ_K`KbJaK]J_K`I^MbMbLaJ_K_L`MaK_J_J_K`I_K`LaI^H_H_H]J_J_MbI^J_K`K`I^J_MaJ_K_K`I^L`K_I^I^F^I`H`I^I^G\I^I^I^J_H]KaJ`J_L^K_J_LaL`I_LcL_J_LaJ_K`K`K`LaK`K`MaI`JaJbJ`KaMcLbJ`J`I`H_K`I^MbMaL`J`KaI_J`H^K`J_K`IaJ`J`I_I_J`KaG]H^J`J`KaLbMcI_K`I_I_I_J`K`H[K^K^I`I_J`KaI_I_KaKaNaK^L_I\K_K_K_K^I_J`KaKaLbJ`McJ`LbNcLaIaJbJbIaH`LbLbKaMcMcKaLcNdLbOeLbNdNdNaQdNcLdLeLeJcKdLdLdKcLcLcJbNdPfPeOcPeQeRfRePfSjPfOePfNdOeNdOeOeOeQgNdNdOeOfLbPfQgOeQgOeOeNdPfNdNdPeNdLeLdMeLfNgNgLeOeOePfQgOeOeOeMcOeQgPfMfMfLgNhJeLeOgNfOgPhPgPgQhOfOgRiPhSjShSiSiQgSiTjSiQjPiQjQjPhQjShShWiViThQhUkRhRhSiRhRhThRgShTjSiSiRgRgRfThUjSkRkThQfTiSiQgUjTgUiVjUiSgThUjUjUiVjVkUjWlWmUkYoUlXnXnUkWmWmVkVnTnUlUkVnUmRkXnWiUmVlWmXnWmWmXlXmXnXnXnVl[nZnZmZnXkXmYmYmYmXlXlZnYoWlXmVkXmYnYnYnVlSnUlVmWmVnWoZmWqYqWoYrYqZr[tYqZrXpXq[sYs\u\u[sZtZsbv`s`tat]s_s^s_uavav\w^w_w_w_v^vewdycxezezdzeygzj|j{k|h|i~f~jg|jklli|k~j~lkiikmnnnpqsoonrttsutxw~πЂт͈ҊӍӑԐٕ֠ա̨ϯҬӫӧӤϥЯշ۴ְп㺾᷻⵹%2f$1e$1e'3f&3i#5h"4f%2h'1k'1i%3g%2g%3h#4f$4g"2e#4g"1d"2d#6h$6h$3f$3g$5g#5h$4g#3f$4g#3f#3f"3g&4i(3j'2g%2h&3j&3j$2j"1l$3k'3j&2i%2i%5m#2l#2l#1l$4k&7k'7m&6l)9o'9o%9n$8m&9n'7m'8n&7m&7m(:o)9o'5l&5m'8n'8n%6l(9n&7o(9q%7o"5m'8p'8p'8p&7o(:q(9q):q(9p%9p$9p$7o'7o)9p(9n(9o(:o'x(;u(;u*=w);v)>w(>w'=u)=v(;w':u*:v+;w);r+v-?v*;s*;s/@x->v+?w+>v-@x*>u-By+@w)>u,Ay->z-A{.D}-B{-C|/C}/A{/B|/B}/B|-A|.B}.D~-A|/C~.A}0D/C-B|0E~0E~0E~/E|/Cz.A{/B|/B|0B|1D~1D~1D~1C~1E|0C~1C~1F/F}/E}/E~.D|1F}1F0E~/D}-B|/D}1F}/D{0Dy0Dz0E|.Cz2G~2G1F~3H3H~2G}6K4I3I5K4J3I2F3F3F4H2G5J3I~6K4I3I6I6H5G7I6H4G3H5J5J4I6K8M7L8M5K7M7L6K7L8M8L8M7L6K6N1J4O1I3I3H6J5I4H5I4H5J6L4J5J4I6K8L5H3F5H6I4J4I4I5K3H6K3I3G4G5H7J2G1E1E0D0D0D1E.B}.B}.B}.C|/B~/B,A|.B}-A|0D,A|-B}-@{-A|,B{.B}.B}-A|.B}-A|,A{-C|.A|0D~.B|0D~-B|,D|,B{.D~0C/C~0C/B}.D|0E~2G2G3H3H5J5J4I5J6M4K4I6K8M6K7K6K8M7M8L:MQ=R;P=Q=RS?T?T?UBWAVCXBWCXF[EZGZFZF[G]J^H[J\L]I\I^J_J]I\M^N^N\M]K^L_L_M`M`M_L_I\M`MbK`MbMaMaLdJaKaJaJ`McLaMaMaMbMbNbNbOdOcMbNbQfOeQgRhQgPhMfKdLcLbMcLbMdKcLcMaI_MbNcJcJaK`LaK_K]N_M]PaL_M_K_J_LaMdMdJaLbLbJ`KaJ_LcMaJ_NcMbLaIbLcMbLaL`MaK`K`MbMbLaK`LaK`MbK`K_J_I_I^I^K`I^H]J_I_KcKbIaI_J_K`L_I\H]I^I^H`K`I^H]I^I^K`LaK`I^H]K`J`IaIaIaHaIbI_JaJ`K_J^K_F[I^H]J_K`K`J_MaL^L^LaLaNcLaK`LaLaK_LaMbH]I^K`K_K^J_K`MbJ_K_K`J`LcLcK`K`LaLaK`K_MbL`I_LbJ`J`J`I_I_K`I`FaF`I_I_H^I_H^J`G]KaH^H^KaI_I_I_JaGaJ`I_J`G]I`McJ`I_J`J`G]J`I_I_LbI_JaKaJaJaJ^K_J^J]KaKaKaKaLbLbJ`KaG`KcH`IaJbJbH`JbIbI`K`LbKaNdLbLaNdNdLbMcMcMcLcNcMbPdOcNbNcJbMdKcIeJfLfKeLgJcLfKdOfOeNeNeQdQePcOcOfNcLbOePfOeNdNdPdPdMaNdNdPfOePfOeOeNdOeNdPgJcNfKcNfMfReOcPePdNbPfOeQgPfQgOeOeQgRhPfRePdPfPdPeMeNfPhPhPgNiNiMhPkPjMhPhPhQiRfTiTjShPjPiQiQhTjRhUkSiTjRhRhRiRhSiQgUkRhTjTjTjTjRhUkShThThUjSiSjRhTjVlSiSiShUhUhTfWiTiThThRhUjRhShShVhUgVgTiVkWmVlXmZnWmVlXnVlVlTlVnSnSmUoUmTlXlWiXlWmXnUkXnXnWmYpXnYoUkYoYoYoXnWmZpXnYlWkYmWkYmYmXlYnXmXmYnVkWlWlXmYnZnXmZoWoXpXqWpVnXpYq]q[q]s\p_tZs\s\p]r]q]q\q]s[r^u\u\s]r^t`t`s]v`s`u^savdx_s]v]u_w_xbvdycxfzexe}e|d{i~i}i}h|i|hzk}h|l}k}j|nj~l{j{k}lk~njjlrpnmpknpnoutwx}}˂̆҉҉яӏӔԒӔҙџҨ֩ҧϪӫզҩӭҷصվ帾߽#0d%2f%1f#2d$6h&5j$4h'6k(4k'4h'4i&3h%2g'2f%3g$5h#4f#3f#2f$3f$3f#4f!2e"3f$3g#3f"3f"3f#4g#4f$4h%4i'1g'1g'4i&3k$1i%2i"1h"1h"2i&5l%4k#1h$3j%4l$3l%6k&7k'8m'9p'8n%8m$9n$9n%8m&6l$5k$5k&7m&7m&7m$5k#4j'8n%6l%6l'8o):r&7o$5m$5m$5m$5m$5m&7o&7o'8p):r'9q$8p':r&7o'8p(9n%6l(9o%7m'v+v)t'x*>x,Bz-B{-B{-B|.@z-@{-@{-A}-A|,@{.E/B~/C~.B}-D{.D|0E~0F~1G2G/D}0E~1C}.A{0C}0C}/B|/B|/B|/B{0Cz0C}0C}0B}/B}1C}1C}0D}/D{/D}.C|/D}/D}.C|/D{/D{0Dz0D|/D{0E|1F~0E~1F1F1F~1F}4I3H4H3H~3H4I3H2F4H5I6K3H7L1F~3H6K6L6J5H6I8K4H3H4I4I5J6K6K7L8M6K5J5J6K5J6K6K6K7K5K6M6M8M5K3I2G3G3F6J8L6L3H5J5J4I3H3H3H5H6I6H5I4I4J6J6H4G3I3H5J5G2F2I1E1E2F0D2E1D1D1E/C~0D/C~.B}.C}.B}/C~.B}.B}-A|0C~0C~1C/C~,@{,@{-A|.B},@{+?z.A|/C~-A|.B}/C~.A|.C}-D~/D~0C0D0C0E1F1F0E~3H4I3H4I4I5J6K6J6J6K5J5J7L6K7L8M8M:O8M:O:O9O:O;N=P=P;N;O>S=R=R=R>S=R?T=R?T?T=S>UAVAVBWF[DYEZF[G\H^G^I^I]GZH[N_L]M^H]K]J]J\L]L^M]N_J\I\M`L_K^M`M`M`L_M_I_MbLaMcJaI`LaMcMcKaKaLbKaJaMcLbLbLcLbNdMcOeNdOeQgQfNfLfLfKeMbLbLbMdLdMdLaMbLaNbMaMbLaKaJ_M`M^P`P`K]L_K`K`K`LdMdKcJaKaJ`KaKbKcLaMbK`MaMbKaKaMbK`LaLaLaLaK`LaJ_K`J_J_K`K`J`K`I^G\H]I^LaJ_J_J_I`I`IaIaI^J_K]H[J_H]LbF]H`H]J_I^LaJ_J_J_J_H]K`J_K`J^K_I^J^J_J_NbJ_H]I^H]H]I^G\I^I^K`LaJ_J`J`J_J_J_I^K`K`K`J_K`I^I^I^MaJ\K^I^J_I^J_J_LaKaKcMbJ_J_LaK`LaH]JaJ`McJ`LbKaH_J`H^I^I^I^H^J`I_H^H^H^G]LbH^J`KaJ`J`J`I_J`KaI_J`KaI_G]H^I_H^H^J`J`J`I_KaLbJ`J`JaLaM`I]I]K_IaI_I`KaH_H_JaJaIaIaIbJbIaH`H`IaH`I`J_LbLbLbMcLaMcNdLbLbLbMcLbNeMbK`L`McNbMaJdKdMdKbJbKcMdMeNfLdPfNdNdObNbOcNbPdMcLbOeNdNdPfNdPfNcMbLaMdOeOeOeNdPfPfOeNdNdNdOdNdNeKcLdNgQeQeMcPePgOePfMcPfPfOePfRhQgPeRfPdNgOhNfOgNfNfOfNiOiOiQkNhNiQiRjNfOhNgMfSjPiNgNfQiTjRhRhUkTjRhRhSiSiSiRhSiTjTjSiSiSiTjShQgTjTjRhTjRhTjSiSiTjTjUkViThUjTgUiThVjUjTiShTiRgVjUjTjTiTiWmXnXnYmXmUkWmWmWlTnUmTkUoSnUlTlWlUlVjUkXnWmYoUkVmVlUlVlUkTjVlXnXnWmZoWmWnYlYmYmXlYmYlXnWlWlVkZoXmWlXmVkYnZoZoUnXpYqYqXpWoYr\q\r^t^r\rYr\t_s]s]s]r^s^rZu\u\u\v_t^t`s_s_t`u_t`u_t`u_tau\u_w_xbwdycxezezcwe|dze|kfzi}ezi{j}jk{l~ni|j}kk~j}k}j|kkoqnpqoplmnotruuwx~́ς΃ЇΎԑՏϕԕїМТЩѬӪեЩӬհԺڿۿ׿޽⻿礵$1e%2f%1e%2g%6h"2e$4g$4i"3g"3g&5i'3i(5j%2e&3f&1f&3g&3g$6h$5h$5h$4f'2g%3g#4g$4g'2g'3g&2f$2f$6i&4i'1g'2h%2g%2f$1f#1h"2j#3j"0g$3j#2i#2i#2j%3k$2j)7n&7k&7k$5j%6m$4j$4k%5l&7m&7m$5k#4j%6l%6l&7m&7m&7m%6l%6l&7m%7m'8q$5m"3k$5m#4l$5m#4l%6n$5m%6n%6n$6n&7o):q(9q'8o):p):p):p*;q&9n$9n&:o&:o';p'w*>w*y+>x)>x)>w(=w*>w+>x)y*>w*?x.C{+Bz-C{.D|.C|-Az-@z/A|/B|0C|0B}0B|/A}0B}.A{-@z.A{0C}/B|/B|0C}0C}.D}.B{0B}/B~.A|-A|-A{1C}.D}.C|/D}-B{/D}.C|.Cz1F}/D|0E{/D{/D{0E{1F1F/D}2G1F3H3H~4I4I3H2H3G1E2F2F5H3G3I5K5J4I4I4H5H5H4G~4H2G2G~5J5J4I5J5J4I4I6K4I5J5J6K6K7L7L5J6L4L3L6L5I4I3H3H4J6K6K5J5J2G5J5J6K4I5H5H5H6I4I6H6I3F6H5I3I3G4H1F4G1E1E2F/C~.D~/F0F0G2G/C~0D0D/C~-A|0D/C~.A|/B|/C}.A{.B|.C~0C~0C~0B}.A|.A|-A|/C~1E,@{.B}/C~,@{/B}/C~.D~/C~/D~.E~.D0F/E~2G3H3H3H4I3H3H6K4I6K6K5J5J6K7L6K6K8M8M8M7L7L9N:O:MS>S>S?TBWAVBWDYCXDYEZEZF\I[K^I\J\O`L]J[J\J]J^J^J]L]M^M^L]I\M`M`M`ObK^J]L_M`L`I^LaJ_G]JbOeKbLcMdKbKbKcMdOeLbMcMcLbKaOeMcOePfPfQgQgQgPfOdNcLbMcMcKcNdPeMbK`McNcMaK`NcLaI^N`M^L_K^L^K`MbMbLaNbMbMaK`K`K`K`L`KbMbK`LaKaJaJaKaOcK`J_J_J_J_LaJ`H]J_J`KaLaLaI^K`I^H]H]J_K`J_K`KaIaIaJaJaJ_I^J^K^J`G\H_G^I`G\J_J_J_K`J_LaH]G\I^K`J_LaJ_H]J_K`I^J`J_I_J_K`G\J_J_J_K`I^K`LaJ_J_K`K`K`K`J_J_K`K`K`K`K`K`J_J]K_I_I^J_I^J_K`LaLcL`I^K`K`I^I^J_K`J_MaLaMbNbK`J`J`F]H^J`I^H^I_KaJ`G]LbJ`KaI_I`H^H^H^J`J`J`J`H^J`KaJ`G]I_I_H^I_H^I_I_I_H^KaKaKaJ_K_J^K_I]H\K^K^M`L`L`L_I_LaMbKaLbHaIaH`H`HaJaLbLbMcMcKbJaMcOeNdKaMcNdMcNcNbNbLbMcNdMdNbNbKeJbKcKdMeIbLeLdNeNeOeMaNbNbOcObNdNdLbNdOeNdOeNdPfOeMdMcMcNdNdMcNdPfPfNdNdNdOeNeKeMdNfMeKcLeOcRfNcOdQgOeOeOeOeQgPfPfPdMgNgOgOgLdOgNfNfMfMhMgLfMgOiPhOfPgQiOfQiQhQfRhPgPiTjRhRiTgRiRhSiQgTjSiTjTjTjSiTjVlSiRhTjSiSiSiSiTjSiRhSiRhSiSiTjSiTkThVjSgSiSjTjTkUiVkUjUiRkSkViThTjVlXnXoYkXkVmWmVlWmSlSlTlWoTlUmTlTmYlWmXoVlVlXnWnXlXkWkVjXlVmXnWmYoYoVoZoXoZmXlXlYmXlXlXnXlYnYnXmYnVk[pYnXmYnZoUnWoXpVo[qZo]r[q^t\r]s]t^q^r^s\q`s^r^s]r^s`t_s_t]r^t`t`t^s_t_t`ucxav_t`t^x^v^xcxbwcxdybwezdyfzg~g|h{g{k~h{j~i}h}l}k}ml~h{j}l}iikkmopooroplpprruvvy~Ɂ΂τσχωҋ͑ҒґӔԘҠҧҥͣѣШѮֳָ׾ڽֹԺԼԿ۾߼ؽ幽꧶$1f$0e#2f#3f$4g$4g#3f"2d$4h&6j#3g%4h&3g%2f&3g&4h&4h&3g(4h%1e&2f&2g%2g%2f&2g&2g%2f%3g%3g%1f&2h&3h%2g&3h$1f%2g$1f%2g$0e#1f#2l"1h"1h%3k$3i'9k&8k$5i$5i"3g"3g%6i#4k#5j$5k%6l$5k%6l#4j#4j%6l&7m$5k&7m&7m%6l&7m%5k&5m#5m"3k#4l$5m"3k$5m$5m#4l%6n%6n'7o&6o&7p%6m'8n'8n%6l(9o'8n(9o'9o$9m&;p%9n%8n(8n&7m):o(9n&7l+w)>w+@y*@y)>w*>w)>w+@y,Az+?x,Az,Az,B{/C|.A{-@z-@z-@z/B}/B}/C~.A{/B|0C}.A{/B|0C}0C}0B}/C|.D|.D}/B~0C~/A|3B~2A~.?{/C|.D}/D}/D}.C}.C|.C{0E{0E|0E|0E|/D{/D}/D}/D}/D}2G1F1F1F0E~0D~2E2F3G1E1D2F1E3G2F4H2G2G4I5J4H6H6I5I5J3H3H4I3H4I3H4I5J5J3H5J4I5J4I5J4I2G~5I6L3I6J3H4I5J5J4I7L5J2G4I4I5J3H2G5I6I6I6J5H8J6J5H5H4H3F5I1G3I1F1E1E1E2F/C~1F1G1G0F0F/E}.D~1D.B}/C~0D/B}0C}0B2D/A0C-A|0B}2E0C~0C~0C~/B}-A|.B}.B},@{/C~/C~.B}.B}0D.B}/C~/E/E~0F/D}1F3H4I4I4I3G4H6K5K6K5J7L8M4I6K7L6K8M6K6K6K7L7L8MS=R=R@U@U?T@UG\EZCXF[DYG\EYH[H[J\M^J[L]M^K\M]M]M^N_L]L]K]J^I\L_K^J]K^L`L_J^L_NaJ]K`H]J^LaHaIbIbKdNeLcMdMdLdLaKaMbKaKaMcLbPfMcNdPfPfOeNdMcNdLbMcLdNeNdMaMbNcPcMcKaL`K`K`M_K^L_PcL^KaKaMbLaMdNbK`MbK`LaLaMbI^K`K`LaK`MbIaJaKbMbKaJ_K`J_J_K`J_L^L^L_J\L^J`J`K`J_J_K`I^I^J_J_K`J^J^H_G^J]K`I]J\J_K`H]I^I^K`J_K`J_J_K`K`J_I^J_H]K`I^H]H]H^H^J_K]K]K]J_I^I^F[J_H]J_J_H]H]L`LaLaH]J_K`I^LaI^I^J_MbJ_K`MaI\L_K]J]I_L^LbLaJ_K`LaLaK`J_K`K`K`LaLaMbK`K`LaK`K_K`K`I^J_G^H^I_J`H_H^I`I^J_K`J_J`I`I_I_I_G]I_H^H^I_J`F\I_F\I_I_I_KaI_I_J`KaJ`KaI_J`K^K_L_L`I]I]K_J^K_J^K_KbI_KaKaK`NcK`KcG`G^K_KbMcKaNdLbJ`J`LbOeMcLbNdLbIbKcMdMcMcNbOcMbLbOdNcLbMcNdPeOeNcPdPdMaOcOcNbNbNdMcNdMcOeOeNdMcLbOePfNdMcMcOeLcObNeNdNdPfNdNdMdJdJeMdKcLdLdLdMfMfKcPfQgQgQgQgRhPgKdPiMePhLdNfLdPhNfMeMeNfOjNiPjQhPgPjOiLhMhOkOgTjSiShThTiRhTiTgRiRhTjQgSiRhSiRhRhTjSiSiTjTjTjSiTjSiSiVlSiTjRhSiRhSiSiTiRhRhVhThQiSiSiUkTkUiShVjQjRjQjSlWkUlViXkWnVmWmVlUkWmWmWmWkXkSmTlVnTmRkXlVmUkVlVlWkVjXlUiXlYmViTkWmWmUnXpXnYoXnXlYmWkZnZpZpWmXmYnZoWlXmWk[pZoXmZnYnWqWpWoYnZpZqYo\r]s[qZp\s^t^tau^r`t^r^s^u^r_t^s`u^s`s_s^s^s_t`taubwav`t\t_wcwbwcxezdyezcxdyg|ezh|f{f{f|h}h|i}kjkj~h|i|l~ih}i}kgkpppoqqorqprqstuwx|{̄ЃΆωч̊͋ϑґӘ՝Ԡ̡˝ʟˤϬӸڽ۽սֻֽػ׾ܺۻڼپٽ⹾ⷼ㹼빿﫺짻兩$3c%4d$2d!1e#3f%4g#5h1d!2g$6k%5i#3f&6i'3g*7k'4h(3g(5i(5i(5i&3g%2f&3g&2f%2f%3g&3g&2f'2f&3g%2h%2g'4i&3h#0e&3h$1f$1f$1f"0f"1i#2i"1i"1g(8l$6j%6j#4h&7k$5i$5i%6j#4k"3i#4j$5k$6l#4j"3i$5k(9o%6l%6l&7l%6j'8o%7l(5m'6m%3m$4k"4l#4l!2j"4k"3k$5m$5m$5m'8q%6n"3i$5k&7m&7m%6l&8n'8n(9o&7m'8n%5k&6l(8o&7m%6l%6l%6l(9o%6l):p&7m(9o$6l(9p*;r*;r*;r(9p'7o)9q(9q%9s&9t&:t(;t(;r):q):r+:r'8p*;s*;s):r):r'9q%9q(;t)w)>w*=w+?x+@y)>w'w+>x*=w+>w.Az,?z,?z,?z/B}-@z-@z-@z/B|.A{/B|/B|.B}/D}.D}.B~1D1C~1@|/>z0@}.A{0B|-B{.C|.C|1F0E/D|0E{0E{.C{-B}/D}/D}.C|2G1F2G1F2G1F0G/F/E-D~/E1F1D/C~2F0D5J3I2H3H4I4I1F5I6I4I5J6K3H4I2G~3H4I2G~3H1F}2G~5J3H4I4I3H2H~7K5H6H4H4J5J4I5J6K6K4I5J4I1G3H2G1F4H5H5H8J9I6I2E|8H8H4G5H4G2H4I0D2E4H4H4H2E1E1G1G0F/E~1G/E/F0F0D/C~.B}/C~/@0A1C1B2E/B|.A|0C~/B}.A|.A|/B},@{.B}-A|/C~0D1E.B}/C~-A|-A|-C}/E/E~/F0E~1F4I4I3G4G5H7I7J5J6K6K5J5J6K6K6L8K9L6L8M6K8M:O8N9OS?TAVAVCXDXEZBWCXE[F\G\FYI\GZH[L[L]N^L]J[L]K\L]O`K]I^H\K^L_M`L`M`O_M]OaK^NaL`J`J_I^I]KaHaHbG`KaJaKbJaNeKbKbMdNcLbLbMcNdOePfPfLbMcOeNdLbNcK`LcMdLbNbQeObMaQdNaJ`K`NbJ]QdPcMaMbK`K`NcKaKbOdMbLaNcK`J_J_MbI^K`NdIaKcLcLdJ`MaL`J_J_K`J_LbK_MaK^K^J]J]L_L^J`LaI^G\K`K`LaK`J_K`J_H_KbI`K`H]H^H^I^K`LaI^J_K`I_K`K`K`I^H]I^I^I^K`J_I^H]J_K]I\L_I\K^G^F[J_G\G\H]I^H]J_K`H^LaG\I^H]J_J_K`K`LaK`J_LaJ_I^L_J]L_L_L_K^J]J_LaK`J_J_LaJ_K`J_K`K`J_LaI^J_K`K`K`J_J_K`K`J^J_L`I^K`L`K`J_I^I^K`I]KaI_I_J`I_F\J`I_KaH^H^J`KaI_J`I_I_J`I_J`J`KaJ`I_I`I_J`JaG[K_I]J^I]I]K_K_K^I]I`I_J`LbJ`JaHbJbJ`KaMcKaKaJ`J`KaKaMcMcKaLbMcKeKbMcLbOeMcMaQdMdMcNdLbLbLbPfPfNeObNbPcObObOcNbOePfNdMcLbNdMcOePfPfNdNdNdOeNdOeOePfLbOeNdOeOcKcJdJeKfLdMeOgOgKcLdNfOePfPfNdNdQgOeLeKcNfNfLdOgPhNfLdMePhNfOgOfMfLeNhMgMgMgPgQhSjShRgPiOhPiShSiRiSiRhRhSiRhRhTjRhTjSiUkRhTjSiRhTjSiTjSiSiTjRhRhSiRhRhRiNiSiTiSiSiTjSiVlSiUkUkRhUkRkRjRkRjVkUlXkXlVlWmUkXnVlUkWmUkXmUjVjTmUlTlWpVnWmVlVlVlXkVjVjWkWkUiYmXkWnWmUoWpZoXnZpZlXlYmXmYoZpWmVjZnWlZo[n[rVjZn[pXmZoZoZo\o]s[qZp[q\r^t[q[qZp\s`s_s`t]q`t`t_u_v^r`u_s^t^s`s_u]rbv]v]ucvav`v]xcv`vcxcx`udybwdyezdyeyd{g~f|h|g}i{i}i}j~klki|jkkkfjknoooqqopnrrusuvxx{~{́˅͇ψʇˋԑӕӚҠТ͢ѥѩаҶָ׼ӻռںջֿۻۼۻ׾ټٿػڹ߲߳߯汹㵽趽䲽괿ﰺ勺亮$2c&4e%4e"2c#3d"2f"4f1c 2d#5g%6h&6i$4g*:l(4i(5i(4h&3g'4h(5i(5i'4h(4h&3g%2f%2f'4h&2f'2f%2f&3h&3h%2g%2g'4i'4i&3h$1f$1f"0f"0h"2g#3f#3g'6j%5i'7k(7k$5i!2f"3g%6i#4j#4j"4i"2i$3k$3j"3j$5k%6l%6m&7n%6m&6j$5i%6l&4l%4k%4k#2k#1j!0i!0i$3k!3k!2j$5m%6n#4k$5k%6l#4j#4j"3i$5k'6n'7l'8m&7m%6l&7m%7m&6l&7m%6l$5k$5k%6l%6l&7m&8m(8m(6n'6p'9q(9r(9q(9r'8q(9q(9q(8q*;r*;s&8p'8p'8p(9q)8q&8p):r(:r*;s*;s*;s+;s*;t';u':t':t)=w)>w&;t'w(>v*z*=x*=x,?z+>y-@z,?y-@z-@z.A{/B|/B|-@z-A{.D|.C|.A{/B}/B}.B|-@{/C}.A}.@{.A{.A{/D}/D}/D|0E~.C}/D}/D}.C|.C|.C|.C|1F1F3H2G1F.D}-C}.D~-C}.D-C~-C}/D2E0D0D1E0D2F3H2G3H2G2H3H5J1F1F2G1F|2G}1F}1F}1F}4I2G~1F}2G~2G~3H2G}5J6I4G4G5H5H3F1F}1F|4I4I5J5J5J3H2F0F1G1F5I4G6H:J7I7K7J8H6G4G4H2H1F2E0D2F2F/C0D1F.E/E/E/E.D0F0G0E~/C~/C~0D0D0D/C/D0C0B2C/A/@~0C~.A|.A|/B}0C~/B}-B}-A|/C~.B}/C~.B}.B}/C~/D~/F.E~/F1E~2G1F3H4I3I6I7J7J5H8K7L6K5J6K5J5J6K6J7J6I8M5J7L9N:O:O:M:M=Q=Rw*?x*>x&;t&;t(=v';t)=w*>x)y*=x+>y+>z,?y*=w,?y+>x+>x+>x,?y,?y-Ay/Az/B|0C|0C~-?z-@y-@z/A|)S@TATEW@U@VCXEZCXEZE[H[FYE[F[E[F[H]H[I[J]L]K\L]L]IZJ[L^M^J^I\J]N`M^L]N_L]K_L_K^I]I^I^I^KaI`IaKbH_JaH`JaI`L`LaI^K_K`NcLaLaNdKaJ`J`LaMaLaMbMeKbKbMdNbNcNbNaP`PaOcNcNaObM`ObObOaM`KaK`I^J_J_K`K`J_J_K`J_LaLaJ_LaIaKbLcMdKcNbLdNdMbMbMbJ_L_ObN_M^N`K^J]L_J]K]I^I^K`J_LaI^I^LaJ_J_K`I`I`J_J_J_H\I^K`LaJ_LaM`J^K`K`K`K`J_I^I^H^JaMaI^H]K_K^I^I]M^I\I\H]H]J_H]J_G\L^J]GZI\L^K^I^K`H]J_I^I^LaI^I^LaJ_J_L_J]K^J]K^J]K]K_J`K`K`K`J_K`I^J_K`J_K]L]K`JaIaJaJ_I^I^H^J_H]H]I^H]I^K`K`I^J_H]I^LaLaJ_K`I^I_F\H^H^J`H^I_G]J`I_H^J`I_J`I_J`J`I_I_H^J`KaI_I_H_J`I`JaKbJ`J`I`K_J^K`KaJ`I_J`HbIbG`K_LaKbKaLbKaKaKaJ`KaJ`LbJ`McN`LaMcKaKaKaKaMcMcLbMcNdMcNdLbNdNdNdNdMcMcLbLcLbNdLbOeMcOeMcNdNdNdNdPfMcOeNdOeOePdOcOcKeLeNfMeKcLdKcLdJbLdKcKcLdKdMdLdOeNdNdQgMcPfNdNbOcOdLfMfMeMeNfNfNfNfLdMeOhKhOgMgPgMeQfRhSiPhOhOgPgOgQjPhVkSiRhSiRhSiSiTjPfUkSiTjTjSjSiRhTjSiSiTjVlTiRhRhSjTjRhTjTjTjSiSiSjPkQkQkVlVlTjTjUjUlTjTkUkVlVlWmWmUkUkXnXnWmWmVlWmWmWlWlTmUmWlWnUlXnXnWmWmWmUlVmTkXkYlXlYmWkXoWnYoWmYoXlYmYmWkWkYmWmWmYnXnZpYoZpZp[qYoZnYn\qZnZp[q\oZn[o\r\r_uZo]r\r\r]t_r^r]q^r_v^u]v^v\t\t^v\v`tbv`u\t^v]u]uavcv`ubw`ubw_tezcxdydydycxhe{e{g}h~ih~i}h|h|kj~mj}k}jji~lmgolmpnpossrrrutuxwzx{}|ʀ́˄͆ΌюѕΙϟͦӭֱٵٷػ׻չԻؾ۽ۿశި⠫㟯砮嫹谾派渼߱%3d&4e$2c$4f#4c!3b"4d0a%7i 3d$6h$4g&7j$5h%6i$3f&2f'4h)5i&3g(5i(5i(5i&3g'4h'4h'4h&3g'4h&3g&3h%2g&3h%2g%2g%2g%2g#0e$1g$1e'3g'2h$2g%5i$5i%6k$2l%4k"4h"3g#4h!2f#4h&5h%4i$3h$4h&6j&6j$4h&5i%4h$4h"3g 5g#4i%5l$3j#2i#2h#2j"1j#1k"0i"1i 0i$4l"3k!2k$5m"4i"3i#4j 0f$3j%3j"1h$4i&6j'7k&7k#4k$5l&6l'5k&5l$5l$5k&7m%6l#3i(6m%4k%5l'6m%4k%4k'7n%7l&8n&8m#4i&7l'8n&7m(:o%8n%5n%4m'6o'6o)8q*8r)8q(6o'9q*;r(9r&9t(:u':u&9s%:s$:s%;s'x)x+>x-@z.A{,?w,?z,?y*>x+Bz,B{*@x+@z,@{/@|,>y-?y,@z,Bz,A{,@z.D|.D}+@y0E~-B{-B{/D}/D}.C}0E~.C{.D|.E},B|,B}+B~.B/D-D/D},@{1E/C~1E1E0D/C~0D~1E1F2H1F3G2F0E~0E~1F.C|0E~/D}/Dz1F|1F}/Dz/D|.C|/D}1G/E~1F1D~3F4F~4G}0F|0E{0E}0E1F4I2G1F1F0F/E~0E~2G~2G}3F}2E|7G|6H}5H4F5H4G5I3I3H1E0D0D2F0D/D/F0F1H1G0G/F/E~1D2F1E0D,D}.F0G.E0C.B/C/C/C/C0A/A0C~.A|-A|0C~/B|0C}/B}/C~-A|0D/C~-B},A|/C~/C~0D~1F~/E~/D}2G0H}2G~3H5H~6K~6K~5I~8L6K6K5J4I4I5J5J6K7J6J6L6K6K7L8M7L9L9L;O:P:Oy-@z(@x(>w)?w,Az.@{.A{.A{,?y-@z-Az-B{,Az,Ay-B|.B~.C|/D|-B{/D}-A}-A|.A|-A|.B|.B}/B}/C~/C~.B}.B}0D0D/C~/C~.B~/C.B}/C1D1G/E~/E~.C|.C|0E~2G1F~0C{0C~/D}/D}0E3H/D}/D}1E~0E~3H2H1F1F1F.C}2G0E0E~2G1F2G1F0E~2G1F1F3G2E3F4F}4E}4E~4F}4I}7I~7G|4G~5H3F2G2D1E1E0D1D/B~4G2E1E2E1E/B}/C~2F0D~/C~0C0D1D1E1E/C.B~.B-B.C~0C}1C}/B}1C.A|.B|0D0D/C-A|/C~.B}.B}/B}0C~.B}/C~1E2F2G4E2F3G~3D}3F~5G6H7J7J6H5J4I4I5J2G~3H6K6L5J5J7L7L6K7L6K5J9MS>S@U>T@UBWCY?UAXAWD\C[DYDYDYEZHYFYH[J[HYH\GZK]M^K\N`M^M^M]M^L]P`M^L]M^N`K^I\L_H[I[H^H]I^H]H]J^H]I^I_I^L_K]J]J]J]J`J_K`K`LcJbJaK`J_LaLaJ_MbNbMbNbMaNaMaNbO`PaN`LaL_M`PcNaM`NaL_J_I^K`H]K`J_G\LaI^I^I^I^H]K`I`I`LcLcKbPgKbNeNeNeNfMeK_LbLaK`L`K^J]L_J]K^M_L`KaI^I^K`H]I^K`K`LaK`J^J_LaJ_K`I^J_MbK`J_J_K`J_K`J_J_K`K_I^I^J`J^K^I]J^I\K_L`K_K_K^LaI^J_G\I^K_J^K_I]I_J_H^J_H]K`K`J_G\H]J_I^I^H`K`I_J\L^L\M]N_J]J^J`J_J_G\J_J_I^J_J_H]H]K`F\J`H_I`JaKbH_G_H`H_I^H]H^I_H^J_I^J_I_LaH]K`J_G\J_H^I`I`I_J_H^H^H^I_K`J_I^K`J^K`L`K`H^H^I^L`LaLbK`JaK`H^I_KaJ`KaJ`LbJ`J`KaKaLbLaK`KaG`H`NcKaJ`J`J`LbJ`J`J`J`LbKaMcKaLcKaKbJ`KaMcLbKaMcMcKaMcOeNdMcMcKaNdOeLbKaL`NcMaJeJbMbK`McKaNdLaMcMcNdOeNdQgOeOePeNcPeLeMeLdMdNaJcLdMeKcKcLdLdKcMcPfNbMaOdNcNbOcNeQgMcOfNgOhNgLdMeNfLdNfMeOgLdNfNeMdLcPeQgOfPgQgQgShPgShRhQfSgSgSiSiRhSiTgTfTgTgTjTjSjOkPjUjRhUlSgRiUkQgShVhThUiTkTkQjRjRkSkPkSmUjUjShTjRhWhSfVjVjUiSjVlWmVlWmVlWmWmVlUkVlUkVlWmUkWmVkVkYkXkXkXkYlVlWlVlXnWmWmTkWjXlXkShWkZnVkYmXkVjYmWkWkYmWkWkZm[oXl\oXoUoTlVoUnYm\rZp\oYpZp[o\p\p\o\s[q]s[r[p_s[p^r^s]r]r]r_s^uYr^v[s\t\t^v]v]v]v]ubt_s`tavawbucveybucvdxdweyezcxdydze{h~f}g{gjh~i}lj~g{lkkkkllmlmonoootrqsrttttuvvx~ɀ΀ЄЋҒЗΚϛҜԞסҥի׫ճٶںܼܿۻ۽޺۳ղֲկӮҮ֭᦯杧㘫閩꘮ꢰ괵ܭد޵⵹߸໾ܾ߾޸޸߸຿﫸&4d&4e$2c%3d$4e#2f"2f!3e#6e$5h$4g&4h'4h&3g'4h'5i$5h&6i(7j)5i)6j)6j)6j(5i'5i'4h'5i'4i&4h%2g%3h%2g%2g'4i'4i%2g$1f&3h&3h%2g%2g%2g$1f$1f'3i&4g$4h$4g#3g#4h#4h!2f"3g"3g"4f"4f"3g"2f#3g#3g$4h%5i#3g#4h!2h"3h!3h"3i#1i"1h#2i 1g"3k!1i!3k2j2i 0e"3i"2i$1j!1h"3j!1i 1h!1h$1j&3i'4i&7j%6h&6i&6j$5i$6l$6k%5j$5k$6l"5j"3h$5l%3j$1h%1l+7p'5k$4k&5l&5l#1h&5l&5l$3j%4k$2j$4j$3l$3n%5o$7m%6n&7o%6n'8p&7o'7o%7o%7o%8p$7o%7p$7q$7q%8r%9s$9r%;s&;t&8r':t':t';t':t(8s*8t'8t&:t&9t%8s':t*=w':t(:t(;t'=u%:r)x,>x+>x+>w+>x+>x,>y.y+>y*=w)=w+@y,=x(;u*=w+>x-?y,;v-=y*=w*>w*?x(=v'y,@{,Ay,Az,@z.B}.B},@{1E.B}-A|-A|-A|.B}-A|-A|.B}.B}/C~-A|.B|/C}.B~0D/C~/C~,C{-D|.C}.B~0F~/D}0E}.Cz/E~2G0E~.C|/D}1F1F/D}0E~0E~0E~1F1F.C|0E~0E~1F/D}0E~2G1F0E~0E~.C|1F1F0D}2D~4F2E1D~0D{4F2D|2H{4H~5G4G2E1F2D/C~/C~2F1E/C~1E1E0D.C~1E2F/C~/C~.B}0D/C~2F0D0D0D-A~.B0E/D.B.C.B|0C~/C|0B~0B0D.B/C0D/C~/C~/C~-A|.A|.B}0D0E0E~1G2E3G0F|1F}3E~5H5H6I8K7J5K6K4I5J4I4I6K6K5J5J7L7L7L6K6K8L8L9M=P:L9N;P:O;PS>SBWAVCXAWBYBXC[E[EYEYDZDYEZGYFYGZGZI\H\J]N_K\M^M^N_L]L]N_PaN_N_N_M^I\I\J]L`I^G\G\G\H]I^I^H]I^J_J_H]M_J]I\J]K]I^K`K`J`I^I^J_L^J`J_LaLaMbLaLaNbNbNbL`O_O_N_O`L`M`L_K^M`M_J^J_K`I^J_I^I^H]K`I^J_J_J_J_K`H]J_KbKbMdLcRiNePgNeOfNeOfNcL`K_K`J_NaK^K^J]J]J]I]I_J_I^K`K`J_J_J_I^J_K`K`J_K`K`J_J_J_K`K`K`LaJ_J_LaMbK`J_J_JaK_K_J^I^I^J]J^J_M_I^I^J_I^I^K`K`J_J_K]I_K`K`J_J_J_I^I^J_H]J_H]I_H_H]K_L_J^I\K_I\I\G[I^I^I^H]J_H]H]I^H]H]H]I^G\H]HaI`JaH_H_IaHaH^I^J_I]F]H]J_J_I^H]I^H]I^I^J_I^H]G^H`G^F]I_H_H^H^K`I^I^I^J_K`H]LaH^I^K`I^J`LbJ`JaLaLbKaH^I_I_I_KaJ`LbJ`J`J`KaI_J_GaH`K`J`J`LbLbLbJ`J`J`KaJ`KaKaKaLbKaKaLbKaLbJ`KaLbKaKaKaLbLbMcLbNdNdMcMcKaLcNdNdNcLbMdNfLbMbMcJdNcOeMcOeMcNdOeQgPfMcKdKcMeMeLdMfMeKcKcKcMeJbKdKdNcOeNdNdOcMbNcOdOePfOeNcMfMeMeNfMeMeMePhMeNfNfNfNfKcOfRgRhPfRhQgRhPfQgPfQfUgQeRgShRiRhTjTjRiRiTgSiQgSiRgShSiQhUhThThTkSiTjUkTgUiSiSiVkSlQiSmPiPkVkUkSjTjTjSjTgThUiSgSjUkUkWmVlXnWmWmWmUkVlXnWmVlVlWmWmWmVlXkXkWkWkYmVnWmWmYoYoZpZmWkWk[oXlWkWkXk[oWkWkXlWkWkWkYmYmZnYm[oYpZpWpXo[oYo[qZq[o[oXoYo[oZnZqZp]sZp\r\s^s]r^u^u\t]t^u^u_u_u\u\t\t]u^v^v]u^v]u^w^vatbv`ubwbwbxcxdzbwbwavf{ezeydzf|e|i|ezc}g}ilj~j~kj~lnj~onmnmnnnpopprpqsttvtwuwz|~ˀ́̓ω͑їћҠϞЖΖϕϗћЛϢҦҪӳۺⷺذӨЩѭίӭԮֲڭܨ࠭㙨䕧蒦敪眫޳廾⬱֥Ԡ֧ܧۥٰݲ޴ܹ޷ݼỿݼ߽۾߿⼿෻Ṽ㲷ݲ߷廿창$2d&4f$2d%3d$2c!2d#3f#4f!3c#3e%5h(5i&3g%2f'4h'3g'3g&3g(5i(5i(5i*7k)6j)6j)5i)5i)5i(4j(4j&3h&2g&2g%2g&3h%2g&3h$1f%1f'3h&2g&3h%2g$1f$1f$1f'5j$4h$4h&6h$5i 1e 1e!2f!2f!2f"3g$4h$4h%5i$4h%6i%6i"2f#3h"3i 1g!2h!1g#1h%4k"1h$3l 0j0i1h0e1f!3h%5k!0f!1e 2e!2e 1e 1e!2e$3f'3h%2g&2h$3g$5i$5h%5j$3k&5k%5j(7o$6k!2h"3i"2i!/g"0h%1k%1i&2h+8o%3j%5l%5l&5l&4k#3j#3j#3j#2j"1k#2j!1j"3k$5m'8p$5m%6n&7o'8p&8p$7p"6n#6n%8q%8r%8r$7q$7r'9s&8r&8s%:s%;t&;u%;t%9s(7s*:t(:q)9q*:q(:s&9t&:t':t(;u&9t);u&9s(;v(;v':t&9q*=u);u);v(;u(;v)x(;u':t,y*=w)=v)?x&;t'v,Az)>w*>y)=x,@{-A|+?z,@{*>y+?z-A|-A{-A|-A|+?z+?z-A|+?z,@{-A|0D.B~/D~.B|.A}-A|-A}/C}/D}1F/D}.C|/D}/D}/C|0E~/D}0E~/D}0E~0E~0E~.C|0E~/D}1F0E~/D~1F/D}0E~.C|0E~0E~/D}/D}/D}1G0F~1E~1C}0B|/B|2F}0C{0C|0C|0C}4G4F1E3F0D1E0D0D1E0D0D0D/C~0D0D/C~1E/C~/C~0D/C~0D1E1E1E/C.B.C.B.C.B~0B~1C-@~-A~1E0D1E/C/C~.B}.B}/B~/C0D2G0E~1F1G~4F}4G~3H1G}4I5H5H7J8K8K5K5J4I4I5J5J5J5J6K5J6K7L7L7L6K6K:N:N;M;O;P;P:O;PSAU@SBWBWBX@WFYCXCZC[EYEYEZF\EZH[I\F\H[J\M]L]M^M^M^K\L]K\O`O`N_L]K]I\I\K^GZH]H]H]G\H]G\G\H]H]H]H]H\H[I\I\J]L_K^J]H]J]J^I\I\K^L_K`MbK`LaMbL`MaNbMbL_PaO`O`N`I^K^L_MaMbK`J`J_LaH]I^I^J_I^K`I^I^I^J_I^J_K`MaMdJaMdNeNeOfPgPgPgPgNfOeNdLcNbK`L_I\J]J]J]I\K^J]H]I^J_K`K`J_H]I^J_I^H]I^LaJ_K`MbJ_K`K`J_LaLaI^K`G\I]MaL`K^I\I^H]I^J_H]I]J\M`J`J_K`J_K`K`I_O`L^J^H^K`I^J_K`J_I^J_J_G\G\I]J_H`H`K`K^J]J]K^K^J]J\J_J_H]H]J_H]G\H]H]F[G\I^H]H]J_J^I^J^H]H]I]J^J_I^H]H^I_J_LaJ_J_I^J_H]I^I^J_I^J^I^K`K_I^J_J_J^I^I^J_I^J_LaI^J_H^J_J_I^K_LaLaK`I^I^J_G^H^I_H^G]H^LbKaKaJ`J`J`J`GbGaKaJ`J`J`KaJ`J`KaKaKaI_I_J`J`J`LbKaLbKaLbMcNdMcKaMcMcKaMcLbMcLbLbLbNdJ`McLbMcMcObNaNaLcMcMbJdNdOeLbNdMcOePfOeOeNcKdKcLdKcKcLdLdLeMfLeJcLeNbNbNdMcOeNdNdMcLbMdMcNdNdPeMeOgLdKcMeNfNfMeLdKcNfLdMeMeNfOdPfPfQgPfPfPfRhPfRhPgOfSjQiThShTiSiSiRiRiRhVlRhSiRhRiSgReThUiTkTjRhUkRiViTjUkSiTlSlTmRkUiRhUkSiTjRhSiRiUiRfThSjSiUkWmWmWmWmUkUkWmWmVlVlVlYoVlVlVlXnVmWmZlZnXlXlXoWmXnUkXnYoWmWkWkXkWkYpXoXoYpWnYkXlZnZnYmZmYnYm[oXlYmZp[qXoZp[q[n]q\q[q\r]pZq\r[q]s\r]s\r^q_s_r_r^q_s_r_s^u`v_t\u\t^v]u]u`w\t]u_w_w[t]wavavcxbwbwcxbwezcxezeze{e{e|h{i}i}ihij~lj~li}lkmomlnmnqpnooprquotursvvw{~ɀ˂̃ΆБϖҜӠӞӘΓ͔АяҏґґД͙̤լկ٪ФͣͧҫԱذԲٲݱ㬸硰♬㖨䒦䑥蓨䝪඿쵻䟤әڛۛ۞ܞ٦ާݩܪڰݸ仿嶹㶸䱶ⱴ⮶㶹㵺᷼鯼&4c&4b%3a"0`'4g%4e"4d$4f"2c&3f'3i'4h&3g'4h'4h&3g'4h'4h&3g(5i'4h)6j)6j)7k%6h'8l(9l#4h&7j#4h#4g$4h&3h%2g%2g(5j#0e$3f#4h%4i&2g&3h$1f$1f#0e%2g%4h"2e"3f"4f#4h"3g"3g"3g!2f#4h&5i%5i$4h$3g&2h%3g"3f!2f 1h 2h!2h#2i"1h"1h"1h%4k!0f!0f!/f .f#1i -f!/h.d 0d!0d0d/c"2f#3g!2f#3g%2g$1f&3h'3h$2g"3g$5i&3j'4h#3h!0h"4j 1g!2h 1h!/g ,f".h#/h'4j(5l(4k%1h(5l&3j'3j'4k%2i$4j#2i /h$2k"3k"3k#4l$5m#4l$5m'8p$4l%5k&8p"6n 4k#6o%8r!4o$7p%8o%7o$9p"8o$9o$:p";o#6m'8o&7o%6o(9r(9q'8p*:q);s&9t(;u&9p&9q&9q(;s%9q&9q':s':r(;t(;v':t(;t&:q&;s);u*:t*;r):r*;s(;s':q);t)z+?z*>z*@z+@y*>y)z+@y*?w*?y/B~-A|-A|.B|.C{/C~,@|,@{-A|/C~-A|-@{.C|-B{/D}.C|.D},C|/D}/D}-C|0E~/D}/D}.D}0E~.C|/D}0E~.C{/D{/D~/D~.C|1F1F/D}/D}1F1F0E~0E~/E~.D|-C|/D}2C1D~4G~0C{/B{0C}0C~1E1D~1D0D1E2F/C~1E/C~.B}.B}0D1E-A|.B}.B}0D/D~/C~1E3G0D2F2F/C/C0D/C0D.B0D0D.B.B/C1E-A~/C0D~0D.C|0E}/D}/D}1F1F~2G~5H4G2H3H~3H~3G~4G4G6I6I5K6K6K6K4I6K6K6K6K5J5J7L7L6K7L8L9K8J;N9O;P:O;P:O;P;P;P;PS?TAUAV@UAVAUAVBWDZC[D[DYAVCXF[DYDZI]I[I\H[L[J[H[J\M^N_M^N_M^L]O`L]J]J\K^H^J^G\G\H]G\G\I^G\G\G\G\J_H[H[J]H[J]J]J]K^N^M^L\M]L^J]K^I^K`KaK`M_K_NbOcObN_O`N_N_O`K_K^L`MbLaNcJ_K`K`F[J_J_I^J_J_J_J_J_K`LaJ_K`I`LcKbLcNfMdMdSjOfPgNeMdOgMdLcMbK_M`L_M`K^J]K^I\J^H]H]J_I^J_J_LaI^I^J_J_H]J_H]J_I^I^I^I^K`J_J_J_I^L_J^L`L`I_J_H]I^I^I^LaH]I^K`K`I^LaJ_J`L`N^K^K^K_I_H]L^J^I_I_I^I^H^J_G]H]IaI^K_I\K^K^I\H[I\I\H^H]I^H]J_J_J_J_J_J_H]G\J_I^H]I^K_H]I^I^J_J_I^H]I]G]F\J_I^J_K`I^J_J_G\I^J_K`K`I^I^I^J_J_I^K`H]I^J_J_I^J_J_H]K`H]I^K`K`K`J`I_K`H]MbL`H^I_J`F\H_I_H^KbJ`JaLbKaKaI_KaI_I_I_I_KaJaJaJaKaI_I_LbJ`J`KaLbJ`I_LbMcKaKaMcKaKaLbMcLbMcKaKaNdKaKaMcLbMbM`J^MaNbNaMcHbKcLaMcMcLbMcMcOeOdNeJcLdLdLdKcLdH`LdOeMaQdOcNbPeKaNdMcOeOeMcOeMcOeMcMcPfMcJbMeKcOgNfMeNfMeNfNfNfMeOgMeNeRhNdPfQgPfQgPfQgPfPfQgPfQgPjPjNhPiRfQgSiSiSiQgTiTjTjSjVhSiVhUhSiSiTiTjSiTjUkUkSjRmQjRkThSjUkUkUkTjRhTjTjRgSgThSjUkTjTjUkTjUkVlVlUkUkTjXnVlWmUkXnXnWmWmTjVmYmWkUiXlWnXnVlXnWmWl[nVjXlYlYoXnXnXnXnYoYlXlZnYmXpZmYm\pZn\pZm]pZp[q[p]p\p\p]p[pZq\r[q\r^t\r[q\q\p_s^r_s_s^r_r_s^t^r_t`s\t^v`x\t^y]w`v]u^v_w`x`tbw`ucxcxavcxbwcxcxcxdzf|h|g{g{g{jg~ljj~kkj~j~lnkllnmqnnnnpopqprstvuuxw}ʁʄ̊ҔҖҝҜћія΍͊ΌӆЄЇэ̓ϜӞӝΛ̞ͦԬڬٰ۴ٵ௶祱ᜧܘ┧⑦⒥璧葦埱ﷻ檱ߙ֔ۓܔܗۜݜܡޟۣڪޱ߷⹽߷۴ٹ຾㳶ܴ೸岴欵䭳᭵᯶㴼帻$2a&4c$1`)4c%3a%3a%3a$3c%5f)7g'5f&3g'4h&3g'4h&3g'4i'4i'4h(5i'4h(5i)6j)7k&7k&6j%6j'8l#4h&7k"2f$3g&3h#0e%2g(5j&3h%2f#3g$4h$4h&2h$1f%2h%2g%2g$1f'3i#3f#3f#4h"3g0d 1e 1e"2f&5i%5i$3g&3h&3h%3g!2f"1f"0h /f /f!0g"1h#2i#2i%2i /f"2j,d$4i"2e/b!0c#1f"2f!1e!1e#3g!1e"2f"3g$3f%2g&3h"/d%2g&3h'3i'3h'2h%0f%3g$4i!/g2g0f 1g.f%/l"-g$0i'4j&3j+8o$1h%1h'0i'2j%2i%2i"1h!0h"1k"1j 2i!3k$6n"3k#4m"4l$5m$6n$6k$5k%6m'7p"3k!5m#6n!3m%5m"7o"5m$8o#9o#9o"8o!7n&6n'8p'8o'8m&7m&7o&7p(9q)9q(:q(;s':r':r$7o(8p+;r'7n);t%9p%8p)w&;t(=v&:s%9t';u%:s'w(=v(>v(=v,Az*?x,Bz+@y'=u*?x+?y*=y,@{+>z*>x,Az/B|,@|,@{.B}-A|.A}.B}-B{.C|,A{/D~-B|.B{-B{.B|0C}0C}0B|0B|1D~-C{/D}/D}/D~-B{-By/Dz/D{/D}1F/D}0E~2G/D}/D}/D}0E~/D}-B{/D}/D}-B{1F~/Bz/Bz/Bz1D~1D~1E0C}1D}/E~0C0D0D/C~0D/C~-A|.B}.B}/C~0D/C~2F0C0D0D0D1E1E/C~0E0E/C/C/C/C0D0D/C.B0D-A1E/C~0D0D0D.D}0E~2G4I2G|1F}2G~3H1F1F4I4I5G4G5H6J6K5J6K5J4I6K6K6K6K5J6K7L6K7L8M7M7M7M9N;P;P;P:O;P:O=R=RS>S>RAT?TBW@UBW@U@UCYC[AYD[F[DYDYF[F[FZJ\GZI\G[H\H[K^M^N_L]J[N_L]N_L^H\G]G\J_H]G\I^G\H]G\G\I^G\G\I^H\HZI\I\I\H[G[H^H\L\L]M^K\K\I]I\KaL`K]M`M`MaL`PdObPaL]N^N_L`L^K^L_NcK`NcH]LaMbI^J_J_J_K`MbJ_K`I^J_J_J_LaJbJbMeMcMcMdQhNeRiQhOfMdOfNeOgOdMbL_L_I\K^M`L_L_L_H^J_H]K`H]K`J_I^J_I^I^J_J_LaH]J_K`I^I^J_K`I^H]K_J^I^K_J^J_J_I^K`I^J_I^J_K`J_H]J_I^I^J`K`M_K^L_J]J\L^L_K_K]I\J\J\J\I\J]K_J^J_J_L^K^J]K^I\J]I]H^H]J_J_I^I^J_I^J_J_I^J_J_H]G\H]H]I^I^G\I^H]H]J_I_G_G^J_I^J_J_I^I^J_I^H]J_I^J_K`G\J_I^J_J_G\H]K`K`I^J_H]J_J_I^K`K`K`I^I]K^I\K`I^J_I^J`J`I_J`I^LaJ^J_K_J_J_K^JaKaKaH^J`J`I_J_L`L_LaJ`I_I_J`KaJ`J`McJ`LbJ`J`KaKaKaKaLbKaMcMcLbMcLbKaMcMcLbKaMdMaMaMaMaOcLbMcJbMcLbOeKaNdNdMcNdOeJbMeIaLdLdKcKcJbMaPfOeNeOeMcMcMcMcMcNdMcMcMcMcMcPfMcPfKcLdMeNfNfLdMeJbMeLdMeNfMeMeNgRfOeOePfOePfPfQgPfRhTjOeSiPjPiPiRkQjRhRhSiRhOhPhRgSiTjSgVkVhRgTjTjPjRhTjSiSiUkTjTjRmRkSiTjVkRiUkSiRhUkRhXnThVjTkUkTjVlTjTjVlUkWmUkVlVlUkXnUkXnWmXnWnWmXkXkYmXlUiZnVnWlVlWmXnWmWkXlWkYnXoXnXnXnUkWnXlXlXlYmXoXl[o\o\oZk\qZo[qZp^r\p\p]q^r[o[r[q]s]s_u^t]s\r^q_s_s_s^r_r]t^u`vau^s^s]v\t^v^u\v]w]y_v_w_x`tbwcxcxcxbwbwbwbwavdycxe{czi|j~h|i~hg~j}kki}lj~j~kkmmnmnonnonqpoossqsrxww{~|ɀʄ̂ȎіԗГϑΑϐӉτ̅΂˄΂˄ʉ˒ϙҝל՛Ԥاڬڨةدۭᤪ☢ܔ㓥擦萦鏨ꓧ墳誴⯵ᠦ֖֎ڏےݓݓޔܙܙܛߜݞܥީݮݮگۮ۱ݰߴ㱷䭴୴߬䰶ᬵ߰㷿喙(6e#1`&4c%4e%3d$2a%3b&4b(5d&4f&3f(5i(4h(5i)6j(7h'6e(7h'4i'4h'4h+8l(5k)5j)5i)5i)6j+7k(4h*6j'3h'3i%2h&3h%2g%2g$2g#4h"2f$3h#4f%4g%1e$1d#0d(5i&3g#1e$5h$4g!2f 2f$5i!2f%4h#3g$4h#3g$3g'3h&3h'3h#1f 1f"0i /g /g /f!0g#2j#2j"0h%1g&3h0c 0d!1e"0e%1f%3g!1e"2f 0d/c!1e!1e#3g"1f%2g%2g#0e$1e$1e%2g%2g&4g"3f"2e"2e.c-e/f/e0h.i 0i .f$1h'4k#1h)6m'2k&2j$1h%2i%3j!0g!0g"1h /f!2i%3m$3m#1k"1g"1i%4l#1h"1h%4k#5j"4j"3l#3k"5l"5m&6n&6n$5m#6n"5n2k 6l%7p&7o'8p'8p%6k&7n&7o$5m&6p&7q&7o&8p%8p$7o'7n'8m&7m'8n&6m(:s%9q#6n*:r):r*;t)9t(7q(9q(9q%7o%8p&9q%9p&;q$9p$9p$9p%:p$9o$:p)?w)w'w)>w)>w(=v'S>S=S>T@U@UCXDXBWDYF[DYCXBWEZCXEZGZH[HZH[H\FZJZK[K\L\K\K\K\J]G]G\G\G\G\G\G\G\H]F[F[EZF[H]H]K_I\I[I[H[F\F[F[G[J\L^K\K]I]I\H[L^J]K^K^J_NcMaNbMaN_O_N_LaL_L^L_I_MaK`LaLaLaI^I^K`J_K`K`LaLaK`J_LaK`J_MbKcNdNbMaNcPeOgOeQhOfPgQhRhOgOfLdNcM`K^J]K^J]I]J_L`J^J`K`K`MbJ_K`I^J_J_H]K`J_I^J_J_J_K`K`MbMbJ_J_G\H]K^K]K^J_I^J_I^J_J_K`K`K`J_K`K`I^I^K`L_N^N_K^K^K^J]K^N^K^K]I\I\I\J]J]I]I`J_M_K_L_L_J]J]J]J]H]I^K`J_J_H]J_K`H]J_I^J_H]K`H]J_H]H]G\F[G\H]H]H]G]G_I_H\H]J`I_I_I_J_G]G\H]J_I^J_J_J_I^I^J_J_J`I^H]H^J_I_J_H]I_I^K`K`K`G]N`K]K`K`K`H]I_I_KaKbJ_I^NcK`K`LaI^KaKaH^J`MaJaJ`J`K_J^J^K_K_J`KaKaJ`KaJ`J`KaI_KaJ`NdMcLbLbI_KaMcJ`McLbKaKaKaKaLbKaLbNbNaNbNaOcLdLbMbNdNdNdNdNdNdLbLbNcLaIbLdMeMeKcLdLdKdLeOeNdOeMcMcMcNdNdMcPfNdMcNdMcNdNdOdLdKcMeMeLdLdMeNfLdMeOgMeOgOgNgPePfPfOePfQgOeOePfRgTiRgRgRkQjOhNiNhPeQgQgOgNfNfOgRfSjTgSiSfThSjPhPjTjTjSjUiUkUkSiUjShUkVkQkThUkUkTjSiSiUkUhUiSjTjTjTjWmWmTjUkVlVlTjVlUkVlWnYoYoYnYlYlXlXlWkWkXlWkWkUmVlXnWnWlXlWkWkXlWnUlVlYoZp\o[oZnYmZn[n]qZnXlYnYnZn[nXoZo^p\p]q^r^r]r^t[q`v`v_u^t[r\o]q_s^r\p_r\t]s_u^t_u^t^sat]w\t^u\w\w^t\t\uau`u`uavavcxbwcxcxcxdycxczd{eyfzh|i}h|j}j~i}ki}j~lki}lmmkoompmponoqpqpqwuuwuz{}ɀʂ̃ˇNj͒͐͏ΐύχ̈́̀σʂŃćœ͘ԛלל؛٠ܣަޥۥܤڧޤ䝥㒠瑣摤蓥鐤ꌤ琤㜮⧲ߪ۝֑ԕܐڑۑېڒۑڒאؖۙڜݟݥާݦݨܧݬ߭߯᯵᮳ଶஶۯ߯ⱺ⵾鮹譺調'5c&4d(6h%3d%3d%3d&4c%3b$3a(5e*5h)4h(5i&3g'5i(4g)4f*6h(6j)6j'4h'4h&3h)6j)6i)6j(5i(5i(5i)6j)6j(5i$1e&3g&3g&3g%4g$4g#3e"4e"2e#3g%1e$1e%2f&3g#0d&3g%1e%3f 0b"2f&5i"1e$4h"2f$4h!1e!1f$1f#0e%2g%1g$2f"3f"2e$4g"3f 0d!1d#3f"0d!-c#1e 0d"2f"2f#2f$0f#3g!2f!1e"2f"2f 1d$4h#4h"1e$0d$1f$1g"/g$1g#0e#0e"1d/`!1d$4g#4g"2f"1h!/g /g.i /h 0h!0h"0g&2g)6j#1f&3i&3j$0g!.e$3j"1h-d#2j"1k!0h#2h#2i"2i$2h&2f$0g"1i$3j#2i/g#4l"3k#4l#3k#4l#4l#4l%5m&6n%7o"5n"5m$4l%5m&7n%6k%5l%5n$5m$5n&6r&7q'8p(8p&6n&7o'8n$5k&7m%6l(8o%6n$5m):r&7o'7p$4o$4o'7q'8p'9q%9q%8p#7o$9p$9p"7o#6o%8p%8p&8p%9r':t)w(=v)=w+=w*=v+>x,@z/D}.C|)>w.Cy-Bz-Bz,Ax,Ax.Ay.B|0B|0A{1D~.A{.A{.A{.C|-B{-B{,Az.@x/Az.Ay1C|1D-?y0B}0D}.D{-B|.C}.C|/D},Az-B{/D}0E~.D{.Cz.Ay1D|1D}1D~1C}1D~0C}1C~.C|0D0D0D0C~.B}.B}.B}.B}.B}.B}/C~1F3H2G1F0E}.B{1E1D,A|/E.B0D.B}.B}/C}-C}/D.B/C}.B}/C~.B|-C{2G2G0G1F0E~1F0E~2G~1G}2H3G2G2G2G2G~5I6H5G:L7I8K9K8K8K7I8K5J7L7L7L7L6K8M8M7L6K:O:OS;PU?UBWBW@UCXDYAVBWDYEZEZEYFYEXH[GZEZEZFYH\H[EXH[K_H[H[G\I^H]G\G\G\G\H]H]H]H]F[H]G\H]I^I^G\G]EZF[G\G[H\G]G\H[H\H[J]J]K^J]I\K^L^L`K_MaL_O_McL`L`K^I^LaI^IaKbJaLaMbMbLaK`MbMbMbMbK`LaK`MbLaLaMbLcJaMbMbLaNcPfQiQhOfNePgOgNgQgNeObM_K^J]K^NbK_K_K`L_MaI^K`LaJ_MbLaJ_I^LaNcI^K`J_J_J_K`K`J_K`J_K`I^H]K`I^K`J_I^J_I^I^I^J`J`K`I^J_J_J_K`J_K_N^K]I\J]J]I]M]N_K`J^K_J]L_L_I\J]J]J]L_J]M^J^K^L_LaJ^I^H]H]I^J_I^I^K`K`G\I^J_H]I^H]J_I^G\H]H]H]G\G\G\H]I`G^I^I^H\J\J\J]I^K^J\I\J^J_I^I^J_J_H]H]H]K]L_H[J]J]K]K^J]K^I^K`I^H]K]O_L]K`J_LaMbK`I_J`J`LaJ_K`J_MbLaK`L`J`J_K`K`KaKaKaJaLaKaLaLaKaJ`J`KaJ`KaKaKaJ`KaI_KaJ`H^KaJ`J`LbLbMcMcMcKaLbLbKaMcMcNdLcM`L`MaNeNdMbLbNdNdLbLbNdNdLbMcMdJcLdJbLdJbKdKcLdKcNcOeOeMcNdLbOeOdOeNdMcMcNdNdPfRhNdLdLdNfLdMeKcKcMeNfLdJbNfOgNfPhNcRhPfOeOePfNdPfQgRhPjPjOhOhOhNhQfQfRhRhQfOhPgQiOgQjQgSiUjSiRiQgQiRjTjSjUiUhTjTjTjSiUkTjSiQkRkVjSiTjTjTjShWkViSjSiRhRhTjSiVlVlVlUkVlUkUkXoZlZmYnXmXnZnYmWlXlYmVjXlWkXkVmXoXkXlXlWkYmUiVjWkWlYnZmXkZnYmYmYmYmYmXlZnZnXlYm[q[qYp\pZnZn\p\p^r]t\r^t\r_u\r^q`t\p^r_s_r]t^t`u^t_u_t^t_u_ubv\v_w\v\w^v]v_s_t`v`u`uavcxavdybwcxezdyczgzg{i}i}i}h|j~j~j~j~kj~kklmlklnnnroqoqoonoqptsvxw||}̀ʂ̇ˊˌ̎Ǎʒ͍ΎЅ̃}̀Ȉ˔ˤӱ߫ߝ֙؛ۚۚݡ㥰⧴㦳䣮䙧ޕߒ㑤鐣董鐣뎢錟劢卢៮ߞבԎ֍ԏَڐڍٌԐڐڑۑڗؘٗۜܠڣިॲުݪ߭ᱶ᭵٪ۯ⯸హ䲾즶立$5d'4e&4f'5g'5g'5e'5b'6d&4c&3c)4h(4i(4h%2g(5j)6i(4f'3f(5i)6k(5i)6k)5k)6k)6j(5h(5i'4h(5i'4h)6j(5i$1e*7k$1e'4h$2f"3f!2d"4e$5g$3f$1e&4h&3g$2f"/c%3g&4h'3g!0d"2f#2f"2f!/d&3h"2f$2g'2h&4i%2g$1f$1f&3h$4h"3g$5h#2f"3g"3g!2f#2f&2g"0e!1e!2e"3f!0d$/c"2e!2f"2f 0d!0e%2g!2e0d#2f%1f"/d%2h#0g#0f#0e#0f$2e"2c0b!1d$2g$1f1c1c#2i*c.f.e"3h0b$4h$0f#2g0h /g /f"1h"1h.e-d.g!0i /f!0g /e%1h#0c$1e&5i"1i!0g$2i#3j$5l 1i#3l!1j"4l!2k#4m"4l"3l"3l%6n#5m!4m#6o%5n$6o"4m$6m%6n#5n%5q$4p%6p$6m'9q&7m#5j'8m#5h'8n%6l'8p(8p&7o);r&5o'7s&6q%5o'8p'8p'9q%9q&9p%8p$7o&7o)8p)8q)9r,9r*;t%7r%9r#8q$9r$:s$:t#7s$9r!7o$9r$9r'u*>u):v);v)=v'V?V=T@U>SBWBWAVCXBWBWF[EYEXEVDXDYEZDYEXDWGZFYFYGZGZG\DYF[EZF[H]G\EZG\F[G\H]H]G\H]I^G\I^I^H]H]G\F[E[F_F]H]I^I[H[H[I\I\I\H[J^I]K`L`MaJ^KaK^K_J]H^K_MaKaIaKcJ`K`LaK`K`LaK`K`K`LaJ_LaLaLaMbMaMcNdOcMbNcNcOdNbQfNfOfQhQhPgMfLePfQeOaM`M`M_MaM`MaK_K`J^J\NdI^K`J_LaMbK`J_KaKaJ`J^MbJ_H]H]H]K`J_J_I^J_K`I^K_K`J_I^J^K_J^J_L^H^I^K`J_J_K`K_LaM\M^K`L`L`O^N^L_J^K_J^L^L_M_M`J]M`L_K]M\N^N^I\L_M`K`J_K`J_I^LaK`J_I^J_H]I^H]H]I^G\J_I^G\H]G\G\H]H]G\G]I`H^J_H]H]K]J^J^I]J]J\GZJ]J_J_I^H]J_J_H]I^I[J\I[J\K]J]L^M_L^I^J_I^K_K]N^N^J`J_LaK`I^L`K`H^I^I^J_K`LaK`LaK`K`L`LaK_J_I`I`I`L`K`LaK^LbMcJ`J`KaKaI_KaKaJ`KaJ`KaLbJ`KaKaI_McKaLbKaKaKaKaLbLbKaKaLcMdNbNaMaKaNcMcKbNdNdMdMcLcNePfKaOeIaKdKcLdMcMeKcJbLdOeOeMcNdNdNdOeOeLbMdMcLbPeOeMcPeMfNfNfOgKcJbKcLdNfLdPhMeNfNgMdQfOeOePfPfPfPfRhOeQfPiOhQjOhPgQfQgPfRgPfRhOiNiPiQiRiThRhRfSiSjOiQiOhSiTfUiSfThSjRhSiShQgTjPkQkRmVkUkUkTkTkUgThSjSiTjUkTjUkSiVlUkWmUkVlVmXnYmYn]rYnZoWjVjZlWlWkXlXlWkWkUjVmXk[nXlYmYmXlXlVjYnWlYoXmZmYl\pYmXlZnXlYmYmZmZrYmXnXn]p[n_s]q[o^r^u^t\raw\r_r_s_r^q]q_r]t_uaw]w\t`u]s^t_ubx]s^s]v]w^v\t^vav^scvcvawdxdyavdxezeyeyd{gyfyi}i}g{kj~h|g{i~kkklmklnnmoonooqmnqqqppsvtvw{}}ͅЂ̊͊ˏ̑Ȕ̒̑΍Έ̈́͂˅Γӣָ汶ߣܚےٕޓܖߝऱ祮랪攦⍟拡挣鋢鉡猟鎢艡卡ߪ춾ꪱܟגՎ׋Ԍٌ؋׊؊ؐ׏؏גڑۓޗݚᚭߡᣯ᢫ۨާڬޮ௷ݬ߯᭵⯹㯹誺'4a&4a'5d&4b&4b%3e&4e&2c'3c&2b)5d'3c(4f(6f&4d'5f)5g*6g*6h'3d)4f)5f*3h)6j'4i)6k(5j'4h(5i'4h'4h'4h*7k'4h&3g'4h&2g#1e#3f$2g'3i'1h$0d&1e&2f%1e'2f&2f#/c$3f%5h0d#4h!2f!2f#3g"2f$5h"2f%1f%1g%2g%1g#2f"2f$0f'3h&0g'2h#/d#2f 1e /d$1f".c$0e#/d#1f!3g!1e"2f!1e 0d!0d#0e$0e$0f!/c0c"2d$0d$1e".b%0c%2e$1f!2h!0f-f"2h!3e!0d#.d/d0h/d .e%0h$0f"/d#0e$1f$1e!2e!1d/b /f!0f1d.c /f!0g-f#/k&3k#0h&3k"0h"2i"2i1g!2h#3i3g3g1e0f#4i 1f"3i"3h!2h$5h#5k5i"5i%7m%5j&3l$3m'5n$2k$3k&4m&5n&5n&4m&5o%3n%3j'5m'5m(9n%7o#7o&9q'7q%5q%5p%5p'7q&8o(9q'8p'7p(9t%9p&9q%8p$7p&9p%:n&:n&9o#9o"7m"7n#7o&8o%9o$:q#8q"7q"7p&;t$9r'x*=w*?x*@y+Az,Az-?y-@z,?z.@{0?{/>y/@{/C}/A|-B{+Ay,@y,>w0C{.Cz-B{.D}0F-@z-@{-C{-Az.A{0C~-@{.A{0C}.D}-C|.D|/D|1C}/B}0C0B.B0D.B}/C},Az-B{0E~1D~0C}2D~2D~3E3F.C|-A}0D-A|/B.B.B/C~/C~0D/C~0C~0C~1D1D2E~2D~5G/A|0E/C|0E}1F/D}2G}1F}1F}3H3H0E~1F3H5I4G8K7J7J8K9L9L9L7J6I6J6K8M8M8M6K7M6L7M:N;P=R=R;PS=R;P;PS@U?TAVBVBWBWEZBWCYCXCYEZDYCXE[EZDZEYGZGYI[GZEXDZF[F[DYBWDYF[GZHZI^G\G\J_H]I^G\G\H]F[F[F[G\G[G[G[EZF[G]G\I\J\N^K^J]O_O_M^K_M_N`L_MaK_L`MaJ_I_K_L`I^MaL`NbNbK`K`MbK`J_MbMbMbK`K`LaMbK`LaMbMbK`LaNdMfMeRiQhOfPgMdQgMdLdOfNdNbN`L_LbMbK`K_MaLaL`I]H]K`LaK`K`I^K`J_M_M`J`LaJ_LaK`LbIaH`K`K_J_J`H]J_J`H]J_IaH`E^H^I^J_J_J_I^J_K`G]J_J]K^L`K_J_J_I^I^K`J^L`K`J^K`L`K^K^I\J]J^J]J^K^I\J^K_K_G\G\I^I^H]H]H]I^G\I^I^F[G\K`J_G\I^G\H]I^I^H]G\G\H]I^J_J_J_F\I^H\K_MaJ^I]K_I^I^I^J_I^H]H]I_I^I^I_G\H^J_J_I^K`J`H`I`I^I_K]L^J_K`K`K`K`I^J_L`LaI^K`LaK`J_NcMbLdJbI`I^I`MaLaK`J`McKaJ`LbKaJ`J`J`J`KaKaJ`KaKaJ`I_H^KaI_KaKaJ`J`KaLbMcMcMcMcLbKbNaOcL`MaMaMaNbMaNaNaNbObOcPcObOcObObLcNdNcMdJbKcHcLgJbKcJbLbNdLbNaMbOcMbNaMbOcNcOeNdOdKfMeMeLdMeMeJbMeMeLdNfMeNfLdKcKdMfQgOePfOeOeNdOeQgQfLfOhNiRjMhPeOePfMgMePiPgMfPiOgPhPiQgSjRhSiTjShTiRhRhQhSiRiRhRhTjQkOgOhTiShVkTkTgViWiViTjTkRhVlTjVlWmTjUkVlWmVlVlWmXlXlYoZoYnXmWlYnZoXnWjWkXlWkXlYm[oXlXnXmYnXnXkWkXjZlXmYmXlYoYoWoYoZmZnZnZnYmXlZm\p\pZn[o\p^s^t]p]q[o\q]s^t^t^t]t_r]q[r]t\r_r]t]s_t]t`t`tbx_u_u`v^w]u`u`tau`t`tbv_tbvcybxcze{dzawe{dyd{f}f|g}g~g}e}jm{n}i{j{i{mknlmmnnmnolqnoopooqquuxuyz~~~ˁ̅ʋ̜̎̓ȟΙ͒ˎ΋ͅdžȐ̥ڴߺ޺⩮ܛۖۋڎ␟ݗ✫癩喥獟䋞玡쉞膞䈠戠抜䇠ዡڦ踿쯱ߢؖ֎Ҍҍ׊؇׉؊֋֎ڏ؋ڌڏېٖܖܚޜߞ⠭⡭ࢭݦଶ䮳ݯݬ۪ஹ꫻(6e'5d'5d(6e'5e&4e%3f%2d%1a%1`'2c'4g%3d'6g&4e'4d&1a(3d)4e&1b'3c)4d)6f*5j%2g(5h&3h%2g&3h(5j(4i'4h(5i(5i'4h(5i(5i%2f%1e%1e$1e$1e&1e&1e&2f$0d%1e%1e%1e$2f"3g 1e 1e0d!2f"2f!1e#2g$4h"3g"1e'3h$0e%2g&3h&3h$0e$/e$0e"/d$0f#0f"1f$1f#0e"/d"/d$0f!2f!1e!1e"3f!2f .c$0f$0e&2g#2f!1e 1d#2f$0e$1e%2g%1e!.e/f#4g$4g$4h 1e"1e$0e.c.d-c.e!.e#0f$1f$2g$3g%2g!2e!1e!0h /f /g"/f!-c.e!0g/i /g$0i#0g%2i&3j#2i!1g 2g!2h!2h 1g2g2i!1h!2h!2h!2h 1g!2g"3f"3g!1g"4i$4j#1i#3k#2k$3l#2l"1j#2k$3l#2k#3l%3m'2k&4k%4k#3j%7m#6n#5m$5m&7o'8p&6r&6q%6q%6n&7o&7o%6p&6q%8p$7o"5m"5n%9o$8m%9n%;o#8o$9p#8n$7o#6n#6n$6n"6m#9q#7q"7q$9s$9r$9r$9r$9s&9s':t%:s't(=t)>u*>y+>y,?y,?y)v)?w*?x-?z+>x+>x,?y,?y,?y,@y-@z-@z.Az.@z-@z-@z-Ax-@x.Bz.D}-B{-B{,Az.C|.C}/C}/A|0C}0B}/A|0B}0E~0F.C|-B{/A|0C~.B|.C}-A|.B}.C}0E~0E~/D}0C}0C}1D~/B|/B|0C}0C}0D.B}/C~1E.B/C.B}/C~0D0D1E1D1C~1C~2E2E3F3E3E3F/D}/D}/D}2G1F}1F}1F}3H~2H3H2G5J4I3I3H~4I8J8J8K8K9K9L8J9M6K6K6K8M6K5K7M8N8O9NS=R>SBW@VCYBXAVCXCWBWCXCXCXDYDYCXCXEZE[EZCXDZEZDYEZEZDYDYBWEYE[F\G\G\I^H]H]I^G\H]H]F[EZG\G\G\G\F[G\F[F[H]H[K\M^J]N]M^M^L]I]J]L_K^K]K_L`K_KbKaH_K`K_NaMaMaJ^LaK`K`MbMbJ_K`LaLaLaK`K`MbLaNcNcLaLaLaMbNcOfPgMdNeNeOfMdMdNeNbK`LaOcLaMbMbL`MaK^L_L`K`LaK`LaI^J_K`L`J]K_J_K`J_I^J_K_H_J_J`L`L`L`I^K`LaK`I^H_I`J^J_J_I^J_I^I^J_K`J_J`J_K^K`MaK_L`LaL`M]I^KaK_K_L`J]J]J]K^K^J]J^K^J]M`J^I]L`I_G\J_K`J_H]H]I^I^H]I^I^H]H]H]I^H]I^G\G\F[H]I^G\G\I^H]I^I^H]H]I]K_I]K^K_FZH^J_I^I^I^I^H]H]J_F[I^I^J_I^K`J_J_I^I^K`J_K`M]L^I^I^I^I^I^I^I^I^I^MbLaLaK`J_K`LbKbKbKbJ`K`K`LaI_JaJaLbKbMcKaLbI_McJ`J`H^LbKaJ`J`J`McLbJ`KaJ`J`J`KaJ`KaMcKaKaKaMaMaOcL`K_L`NbMaMaMaNbMaMbMaMaMaMaNbOcQeMdMcOeMcKcMeJbG`JcKcMbNdOeOeMdMcMcNdNdMcOeOdNdOeOdKeKcLeMeKcNfKcMeMeNfMeNfKcMeNfLdNdQgQgOePfPfOePfPgPiOgLfNeRgQgOeOePfNgOhPgPhQiOgPgTiSiRiTkTkSjRhSiRhSiSiRiRhTjTjTjPjOhPiTiTjUkVmWmUjSjUjUkUkTjUkUkUkVlSiTjVlWmWmVlWmXlXl[pZoYnZnZnXmWlZoYmWkThWkVjWkYlYmZoXmYmXm\qZmYlZlXlYmUjYpYnXn[nZn\pYmYm\pYm[oZnYmZo]q\pZo_r\p\p\q^u_u^t\r_u_u^t_u_u]s`s^r]t_uaw^t_s`t^u`v^t_u]v\t_t_u_tawawaxavawe{axczbxczczd{e|f|f|h~ji}g|i}g|k|k~i{j|l~l~m~nmlopnnnpnqooqrpqqsssxwxz}}Ȃ˄ˍϐ˕˜ΝˤПЖϓЍΈʋː˦ܳᲸٱޡٔڎ،܋ኛߊ፟ጟߓ呣䎢㌟匟臙懝慝⇟㈟㎡揤掦ߢ浺믳㧬ޛۑԊӋ׊׊׊׊׉ӎ׉؋׌ً،֒ٓܙߘښޜޟܡܦߧީߪݪ᫸祴稶靈뜱砵잱'5c(6d(6d(6d'5d'5c&4b'5c&5d%3d#2c'5e%3d$2c$2d$2c%3`%3`$3_%3^'4`(5a(5`'4e%2g'5h'6f%3d%3c'5e(6h'3h'5i'4h)6j)6j&3g%2f$1e%2f$1e%2f&3g$1e&2f$/c&2f$0d%1f%2g"2f$5i"3g.b"2f!0d0d!3g"2f#3g$4h$4h"2f#3h&3h&4i"-c&0g$0f$1f"/d"0e/c!.c"/d#0e#0e$1g!1e!1d#/d#0e%1f#1f"3g 1e0d-c.d-e /g!1h /g 0g /g"2h 1d 0d!1e$4h"2f!1e%1f+`/e.e -d%0h#0f"/d0d"2f#4g"1f.f"1g!0g /g"0g$1i"1h .e-d,e#0i$1g$1h$2i!/g"2h0f/e0f 2g2f1f0f!2h0f!2h!2h 1g 1e!2f 1g"2h"1h"2i%3j#0g.d/f"1j!0i$3m"1k$2k%2h$1h$2i%5l%4k#3k"3l&6n%6n$5m#4l"2n"3m#4l!2j#4l$5m$5m$5m"6n#6n#6n%8p#6m$8l%9n#7m"6n#6n"5m#7o#7o#6n$7o#6n 6p"7o#8o!6l%:p#8o&;t$8p$7q%9s$:s#8q%:s%:s%:s&;t$9r%9s$9r#9q%9r&8s#6p':t&:s(:t(;t$:s&;t$9r%:s&;s&v(:u(7t(:u(;u(;u)w,?y.A{,?y*=y,>y)S>SS?T=R?U@TDVDWCWCXCXCXDYBWBWDYCXDYCXCXDYDXDXEZDYCXCXCXDYDYEZDYEZF[H]H]F[G\J_J_G\G\G\H]F[G\G\G\F[H]EZG\G\G\J]I]J]M]M^M^L]N_M^J]K^J]J]J^J^K_L`L_K_I^I^J_NaM`LbLaK`NcMbJ_LaLaJ_J_J_LaLaLaMbMbK`J_MbMbMbMbPdQeOeNfOfMdMeNeOfMeLdLdMdMbMaLaMbLaLaJ_H^J_LaK`MbLaLaJ`J^K]K]I_J_I^I^I^J_K`K_J_L`K_NbK_J_J_K`I_I`J_H]I_J_I^I^J_K`H]K_L^J]K`L^L^L`NbL`H\L`N`N^O_M]L]K^J]K^J]I\J]K^K^I\H[I\J_I^F\LaJ_H]I^I^I^H]I^J_H]G\K`I^H]I^J_I^H]H]I^I^G\G\G\H]I^H]I^I^I^J_K_K\K]K^H^H]G[H]I^J_I^H]I^I^I^H]I^H]J_J_J`G]H]I^J^I^I^I[I\I\J_J_I^J_K`K`I^I^I^J_J_J_J_K`K`K`KbKbKbJbI^J_LaLaLaK_LaK`LaMaMbJ_J`LbKaH^I_KaJ`J`J`KaI_LbKaKaJ`J`KaJ`J`KaKaKaKaLbNaNbNbL`L`MaK`MaL`NbNbMaOcMaNbNbMaL`NbLaMcMcMcMbJbJbKcNcMbNdNdNdLbNdMcNdMcNdLbNdNeNdOeOeNcQfQfOcLdMeKcOgMeNfNfNfMeLdNfNeQgOeOeOePfOeOeOeQgMgNgOhNfPeOePfTjQhShPdNgNgPiQiPjRhRhReRgSfReQhSiRhSiSiRhSiTjTjSiTjTjUjUkUkTjUiQlSlTlTmTjUkUkSiUkTjTjWmVlUkVlUkXnVmXkXlXnYoXnUkUlVlWnXoYlXkYmZmXkYlXnXoZoWmWmXnXmZlZnYmXnXm[p[pWnZn[pZoYmYmZnYm[o[o[o[oYmZn[n[pZn[o]q]q]s^t]s^t^t]s\r^t_uZq_s]q`r]s^t^uau`u]s`v`v_s^v]u`t`t_vaw`wcvcwbvdxcweyfyfyg{fzi|dzg}f}h{i~j{j|l}i}kjm~k|nl~nnmnopppokpoopqqqqsvwvwz~ɀʁʁʈ̎ʖʠʤ̤͢͠МҕΎȏʐ˘ϥ٪ئԦٛג׍ډۊዛ㊝䋞㋞㌟占䉠䆛䅚惚慜剝Ꮴ瑦门闪蓦㘨ᝤۜڠޘٌՉЉԈՆԊՈ׌׊Պ׊׋؋َؑܓڔݔܔݗܚۜݠޣॲݧߦݩ଺槴ॱ⦳㤲ߩ㩹桰ᣵ롶$2d&4f'5g(6g'5d&4d'5d'5f#1b%3d#1b%3d"0`#2b#2b#1a"0^$1a%3a#1`$2b%3b$2^%3c"1a&4d%3e%3e&4f&4e&4e(7g)4i(5i'4h(5i)6j%2f$1e%2f%1e%1e%1f$1e$1e#0d&2f%0e#0e#1f"3g#3g#3g!1e"2f/c/c/c.b/d/c$3f#3f#2e#0d&3i!/c$1f!.c#0e".d#1e"1d#/d!-a#/d#/c!/c1c"/d$.e"/e"/d#0e"0d 1e/c.c0f0f-c!/f.e.e-d!0f/c 0d!1e 0e!1c-`!-a /b.d-d-e -c"/c#0e!1e 0c"2g!/h.e.e-d"/f&2j#1f/c.f.e!/e"0f"/f".f$2i1f1f0f0f 1g0f 0f 1g/e 1g0f!2h"3i!2h!2f0d 1g!/g#1h%1h"/f!.e&2i!.g /h /h /h#1g$1g$1h"/f#0g"1h"1h$3j!0h"3k$5m 1i!2k!1k!2i#4l#4k#4l#4l$5m#5m 5m2j"5m#6n#6m!6j#7l$8m"6l"5n"5m%5m'8p$4l#6n!5m#6n!5m!6m#9p"8n#8o$9r%:s$7q#6p%:s&;t#8q&;t%:s%:s"7p%:s%:s#8q%:s%;s$;s%9s(7s&7s&9t#6p%;t$9s$9s%9r(:r);t':r';s+=u(9p*;r+x*=w)x.>x/@w1Bz/@x2C{-@w+=v/B|,?y1C}-@y.@x.By+Ax+?w-By,Ax,Ax.C{/D}.C|/D}/E~/D|/D}/D}.C{.C|.C|/D}-C|/D}/D}/C|1D~0C}1D~1D}0C~/C~.B~-A|/C~0D~0D0D/C~0D1E/D~/C~.A}/B~0C~.D|0E~2G5J0E~0E~1F2G1F3H1F|3G}4G~4G4G6I3H1H3K2I4J4I6K6K8M6K7L9N7L7L7L9N7L7L9N9P8O9P9O=QS=R>S>SUS>S@U>SAVBTCVATAUAWAVBXBWCXCXDYCXCXDXBXBZAYBZCZBZDYBWAVDYEZDYF[I^G\H]G]H_G\F[I_H]I^I^G\H]F[F[G\G\EZF[G\F\F[F[H[J\K\L]L]L]M]M_I\I\I\L_J_J^K_L`J^J]H^J_I^K`J`I^K`J_K`K`LaJ_K`J_K`K`I^K`J_J_K`K`LaJ_LaMbMbK`NcMbOcNcPdNbOdOgNeNeLcLcMdKbNbNcLaK`K`MbMbJ_LaMbK`MbI^J^K^J_H]I^K`K`MbMbLaJ_K`L_K_K_K_L`K`I^I^J^LaI^K_K]I_L`LaI^H]K_L_L_J`J`J]L`K_K_K_L`Q_M^L]K]O_NaK^K^L_K^L_K^K^I\J\I\H]I^K`K`K`I^I^LaJ_I^J_I^H]K`K`H]H]J_J_I^I^I^H]H]I^G\G\I^I^H]I^J_I^J_I^I_K]J_H]F[I^I^J_J_H]I^I^G\I^J_I^H]J_H]H]J]J_I^J_J_J_K^I\I\H]I^I^J_I^J_I^K`I^K`K`K`J_K`K`LaJaKcJaNbMbLaK`J_J_K`K`K`MbK`K`J_KaJ`J`KaLbLbLbKaLbKaLbMcKaLbLbJ`J`J`J`KaKaLbMcMcLcKaNbNbNbL`N_K\L`NbMaOcOcMaL`L`MaNbNbMaLbLbKaMcMcLbNdMcNdNdMcLbKaOeNdMcNdLcNdMdObNbNeOeNdMcNdNdLeLdMeLdNfNfNfNfNfNfMeMeQgOeMcQgOePfPfQgQgPeMfNgMgOeOeQgQgPfQgPfQgRgTiQgPfPfSfShSdSgQeThRiRhUkSiTjSiTjTjSiTjRhQgSiSiUkPkQjSkRmQjXmTjTjSiUkVlTjTjSjVlUkVlTjUkWm[pXnVlVlVlYoWmYoVmYmXnXnVmWnXnWmYoYoWmYoYoXoZpYn[pXmZoYnXm\qZo[pZo\qZp[pYmZnZnZnZn[nZnZpZp\p\p^r_s\r]s\r^t_u]s_u^t_t_q_s]q^r^u_u]tbv_r]s^t_s^s\t^vau`vaw`vatcwaubvcwcwdxbveyfzgzh|f}f}hzi~k|k~jzj{k~j~j~klmpopppoonpnkqpqssrqrutwxy||}~˂Ȉ͐ɘǥ̫̬ңˠ˝͗̔˓ȣիاԥѠѡ؝ܓڑ݋ތ㊝匞苜军⇛∜䂛゚䀗䄚劝䍠③劝ݍُ݌ޏّՓԔِ֋҅҈҅҃҈օ҈҉χ։։Պ؋؎ّܑݓߕݓܔژۜܡ⡰ߦ⦴ߩ޺ឰ音$2c%3d(6g&4d%3b&4c%3b'5c'4e#1b&4e%3c$2c%2g%2g#1c!2c!0d$1e#0c$3d#1a$2a%3b$2e'5e$2`(6d%3b#1`'5g)7i&3g&3g'4h'4h$1e%1f)4i"0d#3f#4g#2e&2f$2f&2f%1e"0d$1f$1f$1f$5i#3g!1e!1e 0e/c.c!3f.`0b#3f"2e#2e%2f%2e$0d$1f&2g!.c$1f$0e .a 1c0b!2e!2d#3e$0e$0d$0d#/c#0e"/e!/d/c 0d0d.e.d,d /f-d,d/d.c /c!1e 0e!1d.a!/b&2g"2e.a/b.b#/d"/d .c0c 0e,d!0g,c"1h /f /f"/e$1f%1f!.d!-e$2i.e 0g 0g0g"3i!2h0f 1g 1g"3i 1g 1g 1g0f/e0f"3i$5j"3f!0g /g 0g!/f"/f!.e#0g#0f$1h.d!0f,b"0g$1h#0g"/f .f$3j"2i!1h#2i$3l!1i"3k!2j"3j$6n!2k"3l 1i%6n!2j$5m$4l#4l 2j3k"5l#7l#7l#7l#7l#6k'7n$5n%6n&7o%5m&6n"6n#6n!4l#6n#6m!7n#9q"6p#6p"6p"7p"7p#8q"7p!6o#8q#9r$9r$9r$9r"7p"6p#8q#7o&7o(9p$7o$8o$9p%:q%:p%:p&9q&9q':r(:r*:r):s(9r);t&9q':r(;s$8o';t*=x);v)=v'=v(=v*=x+u,>u.?w0Ay-=u.?w,?x-@z.A{+>y.A{.Ay-@y.Ay,Ax,Ax-By-@w0B{-B{-C|/D}/D}-B{-B{.C|,B{/D}/D}.C|/D}/D}0E~/C}1D~1D~/B|0C}1E~/D|,Bz-B{/C~/C~.B}/C~1E/C~/C~0C0D~2G/D|/D}0E~2G1F0E~1F/D}0E~1F3I3G4F3F3E~5H6I5H6K4I2I~2I2I~3H4I7L8M7L8M7L5K6M9M9N8M9O8P9P:Q9P:Q;PT?T>S>S?T=R>R;Q=S>V=TR@S@S@SATCUDVCVAWBWDYBWCXEZCXCYAXCZCZAX?VDXCXCXDYEZG\H]G\I^I^I^H]H^J^J\J]H^I^G\G\F[G\F[EZEZG\F[F[F[I]K]L^L[N^N_P`K]I]H[J]I\L_J]L`K_J^K_K`K_H]I^K`J_K`K`K`K`I^J_K`I^K`I^J_I^I^LaI^J_LaH]LaK`K`MbMbLaLaNcLaMbOdMbNcOgMdLcLcKbMdNeNcLaJ_MbLaJ_LaK`K`LaJ_K`K`J`J_I^J_H]J_J`L_I^K`J_LaL_K_MaK_I^I^J_J_I^K`L`K_G]I^J_H]I^J`L_K_K`J_L^L`K_L`J^L`M]PaL]N_K\J_K_J^I\L`K^J]K^I\K^J]I^I^H]H]H]I^J_H]H]H]I^EZI^K`F[I^H]I^I^LaJ_G\J_G\I^H]J_J_I^H]I^G\H]I_I_K^I`I^K`J_J_I^G\J_I^H]H]J_H]J_H]I^H]H^L^I\I^J_J^K^J]K^I\K^I^G\J_I^I^H]K`H]J_K`K`H]K`H]K`J_KbL`LaK`K`K`MbJ_K`J_K`LaL`LaJ`LbJ`J`KaH^KaJ`KaLbKaLbKaLbLbKaLbKaNdMcMcKaLbKaKaMcMcOdM`MaMaNbM`ObL`MbNaNbL_K_NaMaJ^L`MaNbLcNdKaLbLcLbOeMcLbKaMcLbMcMcJ`McKbN`NbObPdPdPcNeMcMcMcMcNfLdMeLdOgMeMeMeMeMeNfMfPeRhQgNdRhQgQgQgOeNeMgMgLfNcQhQgQgNdQgRhQgRhQgPfQgPgReReTdRgRfSgQgRhRhSiUkUkSiTjSiTjSiRhSiSiTjRmSlRlPjSnSiUjVkUkSjVlTjWmViTlUkVlUkWmXnWmXnXnVlWmWmWmYoXo[nXnVlYoWmXnWmWmWmWmYoYoWmZq[pWlYn[pYn[pZo[pXnZp[pZo[q[n[oZn[oZm[qZo[q\o\pZn]q\p^v^t\r]s_u]s_u`v_s_r^r_s`tau]s`uasau]u^u_s^r]v^vauaw`v_tcwcwbvdxeydxdxexfzeyfzh|h{h|g{jzj{j|j|m}jljlompompnnooprqoqtrrtsswzy{yǁɁʅ͉ʔΞ˩ͮάϨʣʠʛʗƞ˫װ֯רՠՙң㝫ݑቛ݊ሜ䈛㇛ㄘ߃䂚~ᄚ፡䏢≙ׇԌ֎ڊى؊֋ՈԆ҆у́υӃцԄӇӈцӈՉ؋،ڌ׏ۓޓޓߓݔܘݗݛݡܢޢޫ㢳젴#1c#1b$2d'5f&4f$2c$2d#3d"3d"3c%4e#/e$0e%2e%2f#1f 0d!1e#/e"/c"/c"0a%3c$2`%3b%3a$2a%3b%3b&4c%3e%4e$1e%2f&3g'3g$2f!2d"2e"2e"1e 0c!2e#2e%2e$1f%1g$/f"/d!.c$0f$1f 1e!1e 0e/c1e1e 1c /b 1d!2e 0c 0a$0d$2e!2e 0c 1e"0d#/d"/d!0e"2g"2f!1f!1f!1e!.d$1e%1e$/c$1e#0d /c 1d 0d,`.b0c/c"2f/c.b.b-`/c 0d!1d!1d 0c /b!.b -b0d!/d -b!/d -b!/b0b.c-f+b,c-d.e!0f#/d#0c!.b!.c#0f"0h.e.e.e!1h 1g0f/e!2h0f!2h0f0f 1g 1g 1g/e.d 1h"3f0e!0g!.e!.e -d#0g$1i#0h$2i!0g 0g .e$1h%1i#0g"/f"/d"1h$0g#0g!/e!0h!0j1i"3k/h 1h#4j$5i#4k#4l!2j$5m"3k"3k#4l"4l2i"6k$8m 4i!6k"4i%6l$5k%6o%6n%6n#4l$4l%6n2j 4l"5m#6n"5o#6p"5o#5o#8q#8q"7p#8q$9r"7p"7p!6o#8q#8q"7p$9r 5n$8q%7o$8p#5m$7p#9o"8n#8p%7o':r':r':r&:s&8o%6k'8n):q)9q*:r*:r):r+;s(9q%9q';s':r':r(:r+;s+x,>x.?v->v-Ay*?v.Cz.By-?x.Az.A}.A|/C}-B{,Az-B{.C{-@{-C{-B{-B{.C|.C|0D}0C}.Ax/Bz1D|3E0D~.C}.C}-B{.C}.B}.B}0D0D1E/B~/C~2G/D}-B{0E~/D}/D}/D}/D}1F0E~0E~2G2F3F5H5H5H6L6K7L7L7L4J4L7M6K6K7L6K6K7L8M8L7N6N7L:O9N8O9P9P9P;RS@U>S?T>Q>R?T>S?T>T=T>U=T>U?V@T?TAV>S?T@UAV?T@V?TATCVCVCVDWEXEXDYCXBWF[DYDYAYCZBYC[AXEZEZDYF[F[H]H]I^I^I^I^I^H^I^K]G[GZJ]J]J]F[F[F[F[H]I^G\F[G\G\H^H^J^I]J\K\K]J]H[I[H[J]K^I]L`J^H]I]J^N_J_K^J_K`J_I^J_LaLaK`K`K`K`K`I^J_I^J_K`I^LaLaJ_J_J_LaLaLaLaNcNcLaNcNcNbOdLdLcMdLcLdLcK`NcMbJ_MbJ`K`KaMbKaJ`J`K`K`J_K`K`KaK_J^J`I^I^I^J^I_K`MaK_J_J_K`J_I^I^K^J^K`K^G]K`K`K`L_M`I^J_J\K_L`K_I]M`N^M^M^N_N_O_N_N_M]L]J]L_J]K^F]I^J_I]I]LaLaJ_H]H]H]H]G\J_H]H]H]H]H]I^I^J_I^H]H]J_I^I^K`I^J_H]I^H]I_K_K]J]L^H^H]H]H]H]I^J_I^J_I^G\G\I^I^H]I^I\K^J^I_K_I\J]K^K^I[G\H]J_J_I^J_J_K`K`K`I^K`K`I^LaI^J_L`K`K`NcMbLaJ_J_K`K`J_K`NbLbI_LbKaKaI_McKaJ`J`LbLbLbH^LbLbLbKaLbLbLbKaKaKaLbKaKaJ`LbMcNbMaPbNaK^L_NaMbMbK_NbMdKaNbMaNbNaLcJ`KaNcMcLbLbMcLbLbLbMcLbMcLbLcNaMaOcMaOcPcNaLcNcNdNdNcKdMeMeKcLdOgMeNfNfNfNfLdOeOeOePfOeQgQgQgQgOePePfOhOhThPfPfPfPfPfSiSiSiRhPfNdRhTgRfThUiSjTjSiTjSiSiSiSiTjSiSjSjTjSiSiUkUjOjQkRlQjQjSlRlTiWlVlUkUkWlYmVlUkVlWmUkWmVlVlVlXnWmYoYoXnYnZoYnYoWmWmXnXnXnYoYoXn[qYnXmZo[pZoXmXm[p\q[m]o[oYmZnZnYmXl[o\p[p\q\q[o[o]q\p\p^r\p\r\s]t]s]s]tat`t]q`t_s^r]t]tau`t^u^t_t`u]v]u\u^tawawatbwcwdxdxcxg{dyfzfzf{h|f{h}i|k~k~mj}ljlmopqoopqrnqprprqtruuvuyz}ǀɆʉˊ͌ΏϏΕɟˬαϺֳѩʡȢ˪ӳٰӲԭիۣۘة쥱蛪▦㈛܄ۄچ݇ޅ݆⁛ლᐤ㍡ڃΆЅψԇք҃Ђ΃~΁σЁσутЅӅԃЇԇֈ׋؊׏؍ؑܔޔߓޔݔڙޠࢭ࡭ޥ޴߹ڶַض׸㟱꠱!/]#1_#1`$2c$2c$2c$2b#2b"0c#0d$1e!.b"0c#0d#0d /a!0d#/e!.c"/d#/c"1a%3a#1_#1a"0_$2a$2a%3b$2a%3d%2d%1e$0d#0e 1c 0c"2e 0c1c2c"3e!1d .c$0f&3h%3g$0e#/d$1f#0e%1g%3g"3g!1c!1d$4h#2f"2d 1d!1b#0`"0`$2d$1e 0c!2d0b0e"3f"1e#/d"/d!1e"2f 0d 1d!0d#0e$1e%1e"/c"/c#0e$0e#/d$0e"2f!1d 1c0b 1d!1d!1c 0b.a.c/c 0d!1e 1e"1e#0d"0d"1d!-b!.b#/c".b"/c!.c0b 0e,c-d-d"0f$1e"/e#0d!.b!.b#0e!/d.e-d!0g"1h 1g0f 1g 1g0f0f/e0f 1g 1g/e 1g/e/d0d.b.f.e .e#0g"/f$1e#0d -c!1h"0g!-d"/f!.e#0d#0e"/f!0g!.e"/f$1h$1i.h!1h!2i 1f%6l"3i!2h!2h!1h 1i$5m!2j 1i 1i 1i!4k$8m 4i"6k"6k"4i$5k$5m#4m$5l$5m#4l"3k#4l"2j%6n%5n"3o 3m"5o!4n"6p"7p!6o#8q#8q$9r!6o$9r%:s#8q"7p 5n"7p5n%:s$7o&8p#8n#8o%:q#8p%7p$7o$9p%8p&9p$9m%8o(8n'8n(9q'8p(9q):r'8p(9q):r(9q'9p+;q*:q*;r*w-@z+>x-@y3Bz0Ay.Bz*@w-Cy+?w-@x2D|1Az0Az/Cz-Cz.C|-B{0C}/B|.A{.B|.D}.D|/D}/B{/Bz/Bz1D|0C{2E}1E|/Dz/D}.C},B{.B}0D/C~2F0D1F1F/D}1F/D}/D}0E~1F/D}0E~1F2G1F1F1E5G5H7J8K8L8L5J6K7L7L5I6K4I4I7L6K6K6K8M9M7O8N9M;P;RV?V@W?V@U@UBWBW?T@UBWAV>S>S?T@VBXBWEYDXEZEYDYEZCXDYCXDXD[E\AYBYAVFZG\F[I^I^I^I^G\H]H]H]H]I]J\I\I\L]J]H[I\H^H]H]F[G\F[G\G\I^I^I^H]I]K]I\J]I\GZI\G]G]J\J]J]J^J]L]L]K\K\J[K_MaJ_J_OcJ_J_LaK`K`J_I^I^I^I^J_G\I^J_H]J_LaLaK_K`I^J_LaMbLaLaK`NcNcOdMcNbOdNeNbNcMbLaK`K`K_M_OaL_M`K^L^K^J]J_K`I^I^I^I^K^L`J_K_KaKaK_J_MaL`J_I^K`J_I^K_J^K^N^K`M`I^I^L`J_J`I^H]I^L`K_L`K_K_MaI^L_K]M]K]M^PaO`M_J]K^J\K^I_J_G`G_KbMaI^G\I^J_I^J_J_G\I^H]K`I^H]I^J_I^H]I^H]H]H]I^I^I^LaG\H^J^I\J]J]K^I\J\I^J_I^MbI^I^I^J_G\LaI^J_I^I^H^I\L_J^I^L^K^J^H\K^K^H]I^I^J_J_K`J_K`J_K`K`K`J_J_J_G\K`LaLaK`K`NcLaI^J_I^K`J_I^LbLbLaNcKaJ`McMcKaJ`J`KaKaMcLbMcJ`J`McJ`J`LbKaLbMcKaKaMcLbMcLbL`MbM`ObObPcM`MaNcLaNaMdLbK_M`ObKbMcLaLaHbOdLbLbMcLbNdLbNdNdNdKaLbOcL`OcOcOaLdMcPgMcMdMdNcKeKdLcLdMeKcNfNfKcMeNfMeOdNdNdOeQgPfPfQgPfPfQgPgMfMfNhQfQgRhRhPfQgRhRhPePeOePfSiRhQgRiRhTjTjSiSiTjTjTjQgSiUjTjUiShUkUkRhTiTiVkTjQkQjSlTnTmVkWmVlVkXmTjUkXnUkWmWl[qWmWmYoXnXnVlVmXmYnYmWnWmXnWmWmWmYoXnXnXnWnZoZoWlZoZoZoZo\qZm\nZn[o\q\p\p[o]q[nYo\q^r\p]q\p[o]q^r^s^r]q_r]u]t\s^rau_s`t_s`s`v`wat`t_v_ubx`t]v^v^v\ucx_u`wdwcweyexgzfxgzj|i{j}h{j}k}i|j~mj~j~knlooooorqonppqqoqrswvyy{{ǁɀƄȈnjȖ͔̒̕˓ʗǡɪ˺Կغ԰ѫίӵؽ߾۸ݮ٥ܚٓۘ➬晧ߎ~v{Ӄۆރ|}ւوڍَ؅Άтυ΃ӂ}~~|}}̀̓уЃхӄӅӇֈ׊׋ُڏۏܓݓޔܔߕܛ۝ᝨخ⳷۸ళۯةإ٥ࢯ蠱"0_!/_$2b .`"0`%2e#/d!.b!2e#0d$1e$1e"/c!-a".b".b".c$0d".b!-a#0d ._#1\#1]%3b"0_"0_#1`$2`"/^%2c"0a 1d!2d 0c!1d/b!1c!1d0b0b/d/c!2e"/d&3h%2g 0d /d#0e%0f$1f$/e"/c!1d"2f"2f0d#2e$0d$0d$2b#0a#1c#3f.a!3e 1c-_/d 0d!1f%3g0d"2f.b 0d!2f".d#/e$2e"/c"0c!.c"0d#1e"0d#0d1c#2e$0d#/c"/c/b0b0c 1e!1e/c 0d 1e!1e#/c"/c#2c"/d%1e!-a!.b -a .a!2e.a.c*b.d!0c"/d"/d"/c!.b!.b!.c.c!1i /g"1i!0g0f0g1g0f/e/e/e 1g 2h/f.d/e.d/d0d.c"1h!0g .e$1i"/e"/d"/d".d.e"-f#0h".e".c"/c#0d 0b.b%1i".f#/f$2j /i.i0h/f 1h0e!2h!2h0f!3k"4l"3k 1i 2i"3l1i!5i#7l!5j!5j2g!1h"3h"3j"4m"3k#4l"2j"3k 1i"3k$5o%3p$2n!3o"5n!5o 5n!7p!6o"7p#8q#8q#8q!6o$8q"7p#8q"6o$5p!5n"5m!7m 7n"8n$8p"5l#8k"7l#9p%9q$7o%9m%:n%9l'7m(8m'9p(8p(8p(8p(8q+;t&8r(:q*:m*;p*9o*8o,:t*9r*8s*v,Ay.Ay/@y2By0Bw0Bx.Ay.Bz.B|0B|0B}0C}/B|0C}2D~0C}/B|1C{3D|0Bz1E}1D|2D}1E|/E{.Dz-Bx/D}/C1E0E1E2F0F~0E0E0E~1F4I/E}/E}.C|1F1F2G3H4H7I5H8K9L:N:Q8O7L8M6K7L7L5J4I7L7L7K8L7L9N:P7O9QSBW?T>T?R?S@TAVAVBWCXC[CZ@XDWCX@UAV@U@U@U?T?T?T?T?T?TCXDYEYG[G[I\DYEZDYCXCXDXBZAXDZDXK\I\I_I^I^I^J_I^G\I^I^H^H]I]J\K]M]K]H[H[I\H[F\H]G\G\G]I_I^H]I^I^I^H\J]J\M_I\K^I\E[I]J]J]L`MaMaN_N^M^N^L^I_J^J`J`K_I]H^MbG\K`K`H]K`H]G\J`G\K`I^H]J_MaH`H^K`J_K`J_K`LaOdPeOdOdOcPaOaObMbLaLaLbLaLaK`K_M`M`K]M_K^K^M_L`J_J_J_I^J_J_K^K^H]K`IaIaJ`K`K_K_I^J_K`K`J^J_J^K_N^M^K_H^I^H^J`K`I^J`I]NbL`L`NbH^J_K`L_K^J]N^N^O`M]N_K_K^L_I^K_I]J`H`H`J_J_J_H^J_I^I^H]I^I^I^H]H]I^H^I^I^H]H\I^I]I^H]H]J_J_H]I^H]K^K^J^J^J^J]G\H]I^I^I^K`I^H]I^I^J_H]EZH]H^J\L_H[G]I\J\M\K[I]J_H]J_J_I^I^J_I^J_K`J_J_K`J_LaK`LaI^LaJ_J_K`K`K`K_K_J_J_LaKaKaJ`IcHbK`J`J`LbI_LbLbKaMbLbLbI_H^KaLbJ`LbKaLbKaLbKaKbKbLcMcKaL`ObObObM`M_L_MaNcNcNaMdKaM`M`KaKaLaKbJeJcLbMcOeLbMbOdNdNdMcNeLcLcM`NbNbQcMdMcNdNbNcNcOdMcKeJdJdLcMeMeOgMeNfLeLdLeOdNdOeMcOeOePfQgQgPfRgOfMhOhOhSgPePgOeRhPfQgRhMiNhShPfQgPfRhSiShTiUjVlRhShSiTjRhShUiUjTiUiVkRhVlUkSiSiTiPkPlRlQkRlVkWmTkXmVkXnUkUkVlVlXoVlVlXnYoXnWmYoYoZoXmYnYlWnXnXnXnWmYoZpXnYpZnYnZoZoZoWlYn[pZp]nZmYmXl\oZnZmZnZn]q[p[q\p[n\p[o\pYmZn[n^r]q_s^q_r]t]q^r_s_r`t`s]t`ubuas_v_u`v`t]v]u]v]u\vcy_v`tbveygyexfyhzgyh{j}j}h{h{l~j|kj~kkprnroppotsrqqonrtsyw{z|}ɂDŽɉˊˍ͐ə͚͞˙ʙ̝ϤΪαѼ׹ӴбұԲԷ׺׶ڭܚՐ֍َڋԄ}zy{xx{zzz|}́ɂˁ͂̈́ӄ{z|~z~~΁΁ρ΀ЂӅτ҄΅҉׊׋Տۏېۑܐݖޒۘۖᗦᖦٝծݹ尶ݬᨯޥܠߝߛܜܘם硰흰!/\!/\!/^"0^#1c#/c ,`"/c!.b#0d!.b$1e#2d/b 0c-_/a0c.a!2e/a /a$1a#1`"0_"0_#1`"0_ /^#4e!2d0c/b!1d"2e 0c"2f!1f 0e 0d0d/c$2g#/d$1f"/d$1f".d".c!/d 1e 1e/c 0c!1d 0c!1d"1d".c#0d$0e"0d!1e 1e 0d 0d1d0c.c 0d"1c#/c".b$0e#1g/c"1e#/e$1d"-c$/e#.d$.e"-d$.i#.f%0f%1f$1f#/e"/e"/e#0e"/e"/d".b /b 1b 1c!1e#2g#/d"/e#/c%0e$/c$0d#/c#/c!.b!.c /b".c!/d0b-b!-b!.c!-a +a-`#0c$1d!.c".c#0e!1d/b!0d!1e/d!2h0f/c2f/d.b0d.c0f.d/f.g/g-d/f!/f#0d"/d#/d"1e!1e".b"0b"3e 1d!1g%1j$0h"/f#0g .d/b 1d 0e$0h&2i#1h".e$1h .f"1h!/f"0g-d$1i#0j!0j 0i 2i5h3h4i"5k#4i2g2g 4i"2i#3i%3j#4j1i"3k 1i0h$5m!4k3j!4m!5q 2m!3m#5o"7q5m 5n$9r$7q 5m 7p 6o"8q#7q#6p$6o$7o%8p"4m$7o$6n$7o$6p$7n%9m%9n"6k%9p#6o&9r':s&8q(8u%9r%8p%9o&;n%9m*:o&8n#8o&9r&8q*y':s(v+?w,?w,>v+>v,?w/C{,Cz.By/Bz-By-C|.D|.D|/D~0C~.B|-C{-Bz/Ez/Dz.D}2E0Cz0C{0E}/E~0E~0C~1B~0B|1C}0C}1D~2G0F~4I1F~2G}1F4I1F1E0C~0E~0E~2G3H4I8M:NS;P>R;PS=R?TBVFWCWEYG[G[J^H\F\DYCXEZEYCYD[D[E\C\D[G]H]J_F[I^I^I^H]J^I[J\J^H^K`I\K^J^GZJ]FYGZHZI[I\H]I[J\J]I^H]I]G^H\K^H^H^KaJ_L`IZK\H[K^K^KZL]M^F\J^M^K\N^N]M`J^H]LaJ_LaK`LaK`I^I^J_L_J^I]H[H^J_F\I_J^K]I\I[K`J_K`NcNcMbOdMbMbLbKaK`K`MbMbL`NbObNbNaL^L^McJ_L`L^I\K^K]I\J\K^J_K`I^K`I^J_J`K_I^I^H]J_I^J^J_I^K^L^L`K_K`MaJ^J^K_L`L`L`K`K^MaMaL`K_MaNbJ_J_J_K^K^L`L_M_J_K^J]K^I^I^G`I`H^I^I]K`LbL_J^I]J`I^K`G\I^G\H]G\K]J]I_G\I_G`G_G^H]I^I^H]J_I^H]J^I\J\N^M^L\J]K]I^H]H]H]I^J_J_J_I^J_J_J^J\I[I]J]M^L^J]H[K_J]I\I\H[G]I^H]J_I^J_I^K`K`LaJ_K`I^I^J_H]I`K_MbJ_J_KaHaJaI`MaJ_MbLaLbMbJ_IaJ`KaKaLbJ`MbK`GbK`LbI_J`I_J`McLbKaKaKaKaKbM_M`L_MaM`MaK_L`OcL`LbNcNcNcMbNcNcMaLbLaMbLbKaKaMbIdMbMcK`JeJcJdMbLbMcNaNaMaNbL`NbKbNdKaMdKfKeKeIdOdNdNcNdIbLdMeLcMfQfOeOeQgNdOeNdPfOePfNdQgPfQfMiMfNgNgOhLfOiQgSiQgQgPfQgRgRgPfSiQgPfRhQfPjQkRlTiRgPkShRhSiTjTkRhSjSiTjUkSiUkUkSiSiRjVjUkUjUjTjWmUkUkVlUkWmTjUkWmWmVlVlWmYoWmYoXnXnZoYnVlYpXnWmXnXnXnWm[qYoZpYoXnXnXmYnZoYnZoXm[qYoZo\qZoYpYq[nYm[o[p]r[p[q\p\p[o\o^t^t\r^s^r_s^r_ratat^q\t]t_v_u^t`v_ubx]v]u]v]u`t^s`uawawdyf|bvg{dxfzdxg|h|h|i|i|j}i}ikikonoororqqtsrstprstux{}~}DŽɍΐɖ˙ĝǣ̟ǠʝɚǣΰӱиϽ۷ԳӱҬҫԪעՕҔۏއ~{tvwqutwvxxzy}zz~~~~|z{yz{z{|~~~΁Ёπσ҅ӅӇԉ֊׋،ڏڐېۓߕߕܔڙש߷洶߯᦬ܡݞᕤޖݕܒ⛩埮띲!/]!/^!/`"0a!/`".c!,`!.b!.b"/c#0d#0d .b0b 1c/a.a.a-`!1d/a!0`!/a%3a$2a"0_"0_!/^"/_!0b 0a 0d/b 0c!1d/c.b.a.b.b.b 0d.c#/d$1f$1f$0e$1g0d 0d!1e 0d/b/b /b#/d 1c!1c#/c$1e"/c"/c!1d!1c/c /c/c.b.b/c"1c$1e"0d"/c$0e%1g"/e!.b!.b!.a#.c'1h*`%/e%0e$.d#-c$0e#0e"/d"/e#0e#0e#0c#0d#/d"/c$/d"0e 0d#/d"/e$0e$0c#/c#/c#/c$0d"/c"/c".b /b .c/b/c.c-a ,b!/e"/d .a,`!.b".c"0e0d/c /d 0d 0d0d 0f.d/d.c.b-a.a-b 0g.d.d.d.c,c!/e#/d#0e$1f"/d#0e#/e#1e!3g!1f 0d!/d"/d"/g$1h#0d-a1e"1f$0e#0f#0g!.e#0g"/f$0g#1h!0g!0g /f 0h"1j!0f!0f#4j1g4h!5j"6l!1g!1g!2h 1g 2g 1g%3j$4j2k!1i 1i"3k"3k"4l!4l 3k 3m3l1l2m 3m!5n!6o 5n#5p 5n 6o4m!8q"6o#6p$5o#4l#7o"5m"5m$7o#6n$7o$7o#7l%9n$8m';q%8p$7o%8p&9r$7q#6p%8o$7o$7p#6o%8q';s&9r%9q$7p(9p(9o+v)v,?w.@x-@x0C{-@x-@y/B|-C{.C|/C}0B~0C}/B{-B|0E~0E/D}.C{/Ay1E{0E}.C|/C}1D~1D~.A{2D~2D1C}2H2G3H2G}1F}0F4J3I3H3H4I4I5J7L9N=R@TAT@S>QS>S>S=R=R=R;PS?U@TBVDXG[G[G[H\G[FZE[F[FZF[DYC[D\E\F]H]I^H]H^G\H^G]H]I^I^J]K^K^K^L^I\I\J]H[J]I\FYH[GZH[H[H[H[I[J]H]I^H]H\H[H]I_J_H\I^J\I\I]J\K\M_K\M^L]J_L\L]K\L]K\K_K`K`J_K`I^K`I^J_H]H]I^G\G\H]I\I]J^K^J\I\H[I^I^J_J_MbNcMbNcLaMbK`LaK`J_J_K`LbMaK_L`MbJ`K`K`I^J_K`J]K^I\I\J]L_I_J_J_K`H^K`M`L^M_K^I^I^J_I^K`I^I_K_K_K_M`J^H\J^OcMaJ^K^LaK_L`J^MaK_L`L`J`J_KaL_M`M`M_RbO`J^J]L_I_K`J_G^I_I^I^LaLaJ^J^K_H]F[G\I^G]G\J`J\GZH[J^H]I]I]I^F^I]G\G\I^H]I^J_H]J]I]J\M]L]J]G]I^H]I^I^H]J_I^I^K`J_I^J_I\K^I\I\K^L_K^H[I\I\I\I\I\H]J_K`J_I^J_H]I^H]J_K`J_I^J_K`I_I`J_LaJ_J_J_I_IaI_K`MbMbK`NcJ`KaI`KaI_J`J`LbJ`KbK`H^KaJ`J`J`I_KaI_I_LbMcI_KaKbKbL`L`L`K_MaL`L`L`LaNcMbOdNcMcNcLbLaMbL`MbKbLbKaLaMcMcLaIcKdNbMcMcMcOcNbMaOcL`OcLbLbMcLbHbKcKdMaLbNdNdMcNdJcMeLdNfPfPfOeOeQgOePfRhNdPfOeRhPfQfNhOhNgLeOhQjSgQgSiTjPfQgPfSiRhPfPfOeOePfPfSlRlTiTjTjUjRhSiRhSiTiTjUkSiUkUkSiTjSiSiSiSiTjUkTjUkUkUkUkTjVlVlUkUkVlUkXnVlXnYoWmWmWmVlYoWlYnXnXnVlXnWmYoXn[qXnYo[qXn[q\r[oZoYnZnYnZoZoYn]rZo[pWmYoZm]q\q^s\q]r\q]p^r]q_t^s_t]r`u_r_r_s_u]p_s_r^t^t_u_uaw_v_u`w\u]u]v]u_uaw_ubxaxcxdxcueydxfzdxeyg{i|i|k~k~l~klkmnonoqqpqtspqsqssuuwyy}}Ȁ˃ʈ΍̑͘͝Ȣ̦ϧУΡ̜˛ɦѯԸ׻պоտ׷ϱаӢɠ͢ӞӏόԀtuqurrsrrsssvwxyx{{|}}zzxxxvzz||{}|~~̀ρЁЁуц҇ԇԋ،؍גݑܓڔݕۓےܓؘפ޸趸㭰⥪ݞޝޔܖޗߓ㕥ݘ߱훯!/_"0^"0_#1b"1c$0d#/c".b -a!.b!.b#0d#2d/b/b!0d.a/b/b!1d/b /`-^"0_#1`"0_#1`#1`!0_"/a!0a0a/c/b/b 0c.b/c0d-a.b 0d0c"1f$1f%1f"/d"2f 0d 0d1e-a/b!0b".b#1d1c!1d#/c#0d"/c"/c 0c/c 0d!1e0d-a.b"1e!1c#0d"0d!.b"/c$1f"/e"/c"/c$2f .b#0d,a"/d"/d"0e"0e"/e"/e#0e"/c"/c"/c#0c#0c"0c"0d"/e"/e#/e"/e"0c$0d!/c#0c$0d#/c%1e"/c"0c 1d 0c.`-`.a/a.a.a".b -a!-a!-a".b.b0d/c/c0d /c/b/d0e1f/d0e0e.d.d-c 1g-e+d+c-e-c"1e#0e#0e$1f"/d#0e!0d!2e"2e 0d!1d 0d!0d#0d$1f%1f#0e#0e#/e#0e!.c$1f#0g%2i$1h$1h#1h /f"1h!0g-d!0f#1i 1h!3h 0g$3k!4h1e0f 1g0f 2g 0g"0g"1h%5l"3l!2j!2j#4l"3k 2j 3k1i2l 1l#2m#2k!2n!4n 2m 3m#7p 6n!6o4m 6n 4o"5o 3m%5m%6n#6n!4l#6n!4l"5m#6m"7k$9m!6k!6k"6n$7o"6n$8q%8r#6q%8q"4m$6o%7o&:r'9p&7l'7m'8m(9p):s(9q'8q);s$7o%8q':t':t%8r':t%8p)v,=u)R?Q?Q=PBTBTDVCU@S@U@UAV@UEZEZDYCXDY@UBWBW?T@U?T@UAV?T?TAVAVDYCWFZG[G[I]I]HZGYEYCYF[F[FZG[G\F[E[I^I]J]J]L_L^I[J]J]J]K^I\I\J]J]I\K^I\I\J]I\I\FYI\K^J]I\GZI\H]I^G]FZHZH\J^I]K_J^J]HZL\M^M^M^L]M^M^I\L]J[K\L]L^J]K_I_H]J_I^J_H]I^J_I]I^G\G\H]I\K_I]FZH[J`H]H]H]K`K`LaMbNcLaLaK`K`K`MbI_J`L`MaMaK_L`I]I^I^I^I^I_J_K]J]J]J]J\I\I\J`J_H^J_J^K_L`L`L`J`J_J_H]J_H]I]J_J^K_L`K_K_J^K_K_K_J_J_K_MaL`K_K`K^J]K]I\K^K^K^K^N^O`M^M_I]I\L_JaK`J^H]K`I^K`KaMaL`J^H]H]F[H^J^K`K_H]J]I\I\H^H]I^H]H]H]I^H]I^H]I^J_J^J]I\K^L]J[G\I^G\I^I^G\I^I^H]I^J_J_H]J^I[I\H[K^K^J]K^J]L_I\J]I\K^I^I^I^I^J_J_I^J_I^H]J_I^I^LaJ_KaH_I^I^J_J_K`K`K`MaK`K`MbLaK`JaJ`KaKaH^LbH^KaI_KcK`I_J`I`I_KaI_KaK`KaLbKaLbLbLbMdMbMbOdLbMbLaLbMbKaLbMbMbNcMbNcN`LbNcMbLaJaLaLbLbJ`KaLaIdNbLbKaKaMcM`L`OcL`MaNbKbLcLbMcNcIdKfNcLbL`MdKaNdNcNdLeJcOdPfNdNdNdPfPfOeQgOeOePfOeQfNhNgOhOhNgOhShPfQgQgPfOeOePfQgPfPfQgQgQgQfNhThSiSiRhTjRhSiSiSiSiTjTjSiSiTjRhTjSiUkUkUkUkUkUkTjWmTjTjSiTjTjSiVlWmWmXnWmYoXnWmXnWmVlYoZoXmWmWmWmXnWmXoZpYoWm[qYoYoZpXnXnWmZp[q[pZoZo[p[pXm\qZp[r\o\p[oYn[p\q_t[q]q^q_r^s_tav`t^q]t]t]s^u]t]t\r]s^t]s`v_s_v`vau]vav_uaw^tawbx`wcxdyczbveyfzfzfzfzi~j}j}lmmlmnktnpppuprrtstqtttvwwx}ʂ˄ˊΔ̘͛ǥ˦̪ͣΤ͠ΠҖ˟ϮڶܯϴѺӶβгҮԢϢئޣܐҀtponqpprsrorsuvuwuxv{z|yuwvwzvxyzz{{|~~΁ЃЅх҅҇ԈՌ׏ےړܐ֕ڒۖݓۘݠߣܢءݝܜܘޕܔݔݒۑޗ⚬蜯!/]"0`"0a#/b"/c#0d!-a!-a"/c"/c -a#0d#0d!/c0b/b.a 0c/b 0c 0c$3d$2b$2a$2`#1`"0_!/^!/_#1b"0a /a/c 0c/b 0c/b.c.c.c/c"2f0d$3g!0d1e.b 0d!2f.b-a.b/c!0d!.b!/c!0c$/d"/d#/d!0c 1d!1d!1c 0d!1e/c 0d 0d"1e#/c$0d$/c"0d#0d!.a#0d#0d"/c$1e$0e"/b#0e!.c!.d#0e#0c#0c#0c"/c#0d&1e%1e"/c"0c#0d$/d"0d#0e"/d"0c$0d$0c#/b$2g$1f!/d$1f"/d"0e0d!1e 0c/b 0c.a 0c 0c.a.a/b/b.` 0c/c.b.b.b 1e 1e-b.e/d/d0e0d1e1e-d/e-c.a .b"/d.a!.c#0e$1f"/d#0e#0d!/e!0i!/g!0h!0h-d"0d%1g"0d!1e#0e#0c!.b#0d!.c!.b#0e#0h&3j!.f"/f$1h-d /f /f /f!0g .f/e"2e1d/b"3h 1g/e0f/e 0g!0h$1h#2i1h!2j"3k 1i!2j1i3k 1k!/j#4k"4k!2i#4l 3j0j2l!4o1l 4n5n!5p 2n 3m3l%6n"4l!4l!4l!4l!4l!4m"4k#3h%6k%6l'7p%6n):r(9q'8q$7q%8r#7p"8n#9o#7n%7o'9q&7l(9o'7m(8o*:r):r*:r(:r%8p':s':t%8r':t&9r(;s'9q+v(;s(;s-@x*=u,?w0@x.?w1@z1A}.A|.A}0C~/B|.B{.C|-B{/C}0C}0C}0C}/B|/B|1D~1C}2E1C~.B{0E~/D}0E~1F~5J3H5J4H4G3F}3F5I4I4I8M:OS?T?T?T>T>S;Q:R7O6N6L9N7L8M7K8K6I6KR=R=R?T@U?T?T=R=R=Q?R?R?RBUEXDWDWCVBXCXBXE[EZCXCXCXBWAVDYCX>S?T?T@U@UAV@UAVCXEYDXDXEYG[G[DXFZEYEYI[F[CXEZH]G\H\HZJ]J^J_J^I]J]J^J^J]K^J]K]PaL]L_I\I\L^I[H[J[IZFZH[GYJZI\I\FYGZG\G\H]F[H\J^I]K_H\K_K^M^N_L]K\L]N_L^J^M\J[J[J[K\I^K^K_G]J_J_H]H]H]H]G]H]EZDYFZFZH\H\G[F]G\G\J_I^LaJ_PeLaNcLaLaJ_H]I^I^K^NbK_L`MaL`L`J]J^K`I`I_I]H]I^L`K_J_J_L`I]I]J^K_K`MaI]K_L`K_K^K`J_H]J_H]I^H^J]MaK_L`J^J^K_I]L`L`LaMaMaK_L`M`M`K^L_I\L_K^L^O_M^O`O`O`M_K^J]J]L`J_J_J_I^K`L_K_L`J^G\J_J_K^K_J]L\N^I\H[L_J]H^I^G\H]I^H]I^H]I^I^H]I^J\J]I[L]FZK^I^H]I^I^F[I^H]J_H]G\H]G\H^I^K^J]J]K^J]K^I\J]K^H[GZK^H]J_J_LaJ_I^H]H]I^H]H]H]G\J_I^I^G^J_G\K`I^G\I^K`J_LaLaJ_J_LaJ_KaI_J_I_J`H^J`MdKbLaJ`G_K_I_LbJ`K`HbLbI_McJ`J`J`KaMbNcMbOdMbNbQfLaMcLbLcNbNcLaNcObOdMbNcMbNcLcKaKaMcMcMcMcMcLbKbMaKbM`K_MaNbPdNbOeNaMcNdLbLbOdNdNdNaNbKbNdLbOeOdPeNdNdOePfNdNdPfOePfOeQgOeOeQhLfOhOhOiNhSiQgQgSiQgQgPfQgQgPfQgPfPfQgQgPfRgSiSiQhUjTjTjTjSiSiTjRhUkSiQgUkTjUkRhUkUkTjUkUkUkTjTjTjUkUkUkTjVlVlVlWmWmXnXnVlXnWmXnXnXnYnYnYoYoZpWmYo[mWmWmYoWmYoYoZpZpZoZpZpYoYo[pZoZo[p[pXmYoZp[oZn[o\q^sZo]r^s]s_t_t^s^s^s`u_t]s]s^t]s_u^t]s]s^v_r_s_satax_u`v_u_ubxaw_uaxcwdybwdyezcxfzeyj~i}h|k}lk~nmmloqrpqqsqqvxttusuuvuvxzz~Ȁȅˊϗ͚ʢʫʫʩʦʢˠ̙̗ΘϚϞ̡ϧүӳָ۲٫מџ٣⚢فurqooporopopqrstwvxxw{{ywuuuvyxyvzwzyz{|ˀ}̀ρЂЅх҆ӈ׍ԍ֏Ւِٓۓەݖݟᛦۜݚۙݙޓޓߐݑړܐٖߕܛ暭隮뚭#1a!/`!/`"0b -b"/c!-a"/c,`$1e"/c"/c"/c /b0c0c/b/b 0c/b!1c!1b"0a#2a#1b#1_#1`#1`!/_"0b!/_"1c0c.a"2e/b/b0a0a/a 0c"2d /c#/d#/e /b/c1e/c-a/c.b0d 0c"/c#/c#0d"/d#0d#0d .a!2e!1d/c!1e!1e"2f 1e 1e!1e#/c"/c$/c"0d!.b!.b!.b#0d$/d"0b"/a#0d"/c!.b"/c"/c"/c"/c"/c#0d"/c#/c$/c"/c#0c"0d$.h$.g"0d#0e"/c"0d#/c$0c"/e#0e#0e#0e"/e"/d#1g"1e#0d 1d!1d 0c.a.a 0c!1d-`-`/b.a/c-a-a.b0d0d.c0e0d/d0e-d.g/d/c,c.d.b!/d .c.b"-c#0e#0e$1f"/d#/e#0d0e"0i-c0g .f 0g"1d"0d!1e#0d#0d#0d!.b!/d!.c"/d%2g"/c$1f#0h -d#/f .e /f /f-e"2g$4g$3h$4h!1e"2g 1g0f/e/e/e"1h .e%2j 0j 0h 1j"3k!1j1i3l1i 1i 1j!2j$5m#4l!1k 1l2k!2j 3k2j 3k"4l2j!4k 3k#4l$5m!5m!4l"5m 6m!5l#4m$5m#4m$5m%6n%6n&7o&7n%7p!5o"6p$7q"8n"7n"7n"8p%7o)8n%7m$8m$8n%9q&9q&9q&8p%8p&8q&9s':t&9t&9r(;s(;s,v';s)v-?w/@x->v0Ay0Ay1By1Cz-Az/B|/B}0C~-A|,Az-B{/B|0C}.A{0C}/B|1D~1D~0C~/D}1F1F0E~2G1F4I4I5J6J7J5H2E}5H5J7L9N9N;P>S>S@U@U@U>S:P8O8O7L7K6K8M5K8L8K8K9L9N9N;P=R>S?T@U@U@U>S=R=R=R=PAT@SATCVFYCVCVEWHZHZFYFYFYEXDWCX@UBW@U?T?T?TAVAV@UAVBWDYBWBWCWFZDXH\CXFZFYDZDYEZDYF[EZG[H\I]J^J^MaJ^M`K\O`N^M^I\K^J^L]L^I]HZI\E[H\I[I\H[H[J]HZL]GZH[J\H[EZI^G\G\K^I]J^J^K_J`J]L]K\J[L]J[J[JZI^I]L\J\J[K[K]H^J]I^I^H]I^J_H[HZJ]E[G\G\FZG[FZI]G[J^I^G\F[H]K`K`NcMbNcK`K`K`I^I^J_K_L`MaK_MaMaMaK`F^G^G_F^I]I]K_J^K_H\J^K_K_H\L`K_H^K_L`L`K_K^I^J_H]K`H]I^G\J`K^J^L`K_MaK_J^K_K_J_KaL_L`L`L`MaL_L_L_L_L_L_K^M_O_N_L^M^O`M]K^L_L^L_H^J_J`M`K_L`L`L`K`I^K`K_J^J^O^I]J]J]HZHZI\G\F[H]H]H]I^I^H]I^H]I^I^GZGZI\H[H\H^I^I^G\I^I^H]I^I^G\G\I^H]J^L]J]J]J]J]K^L_I\J]I\I\I\K`J_K`K`J_H]H]I^I^H]I^H]J_J_I^J_H`J_I^J_I^K`J`K`L_J`K`J_J_K`J_KaH_H_J_J`KaKbKcKbLdMaL`LaJ^I_KaJ`K`J`LbJ`I_JaKbKcNcOdLaMbNdLcLbLbMcMcKbNcNcLaNcMbMbLaLaNdKaMcKaMcLbLbMcMcKaLbMaMaLbMaK_J^NbMaNbLbOcMdMcLbLbLbLbMcNaNbPdMcNdNdOeNdOeNdNdOeNdPfPfPfOeOePfPfPfOeMgNgOiRfSiRhRhQgPfPfQgQgQgPfPfQgPfQgQgNdRhRhQgQhTgUjUkUkTjSiSiSiTjQgTjSiRhTjSiTjTjUkTjUkUkUkUkTjUkSiTjTjUkXnWmVlWlXnWmXnVlWmVlVlWmYoYnYnYoVlWnWnWkXkWnXnXnZpYo[qYo[qWqXqZoYoZp\q[p[p[pYn[oYoYpZm[o\o[q[p[p\q]r]r^s^sav^s^s_t^s_t]s^t]s]s]s^t_u`s`t_s_s`t`v_u`v_u`v_ubwdxbvcxcxdydye{fydyeyg{i}i|lk~k~lnmmnqrorrutsxvsuvuuwvuwxwy{ɀɄˌ͑ʚ̥ͬͫɪʩΣ̠͛ϖҏґӎΒϘ֜ѠѥנӘϘәڕ؃yupnnmmoooonopnsrrttwxxwuvtuuxuuwywyyy|{~~~~΂σЅ҇ԇԎԎגّؒؑٔەޓޖ۔ٕۓݔޒۑܑݐؒېٓەݖ♮昮喩▨㙬홭!/^"0_"0a!/_!0a#/d".b!-a!.b#0d"/c#0d"/c!.b#/c-a0c#3f/b/b/a$2c$2b%1a&3d"0`!/^"0_!/^"0_!/` /a0c.a 1d 1c 0c/b!0d 0c!1d"2e$0e#0e#0e"/d"0d!2f!2e/b"2f!1e"1e"/c#0d#0d#0d#0d#0d#0d"/b/b.a"2e"2f!2f#2f#0e#/d#/e"/c#0e"/e#0d!.b"/c!.b"/c"/d"/c$2d#0d!.b"/c!.c#0c$1e#0d#0d"/c%1e#/c#/c$0d"0c"0e$/f"0d"/e#0d#/c"/c"/c#0d#0c"/c#0d#0d"/c"/c#/c"/c"/c!0b!2d!1d.a/b/b/b/b/b.a/b/c-a-a.b.b/c.c.e/e0e0e/d0e0e-c/e0d!1d!1e-a/c"2f".c,a!.c#0e#/e#0d"1e/c/e,b.d .e 0d 1e!0d#0d%1e$0d$0d#.d#0e#0d -b"/d$1e$1e#0h!.e -d"0g!1h-d 0f 1d 1d"2f!1e"2f1g/e/e 1g/f!0g /f!/f#1g"1g 0f!1g 2g 2g#3i!2h!2h 1g#4i!2g"3j#4l1i 3l 1i#3k#4l$4l#4l 3k!4l 3k#4l$5m!4l1h!6m!6m5k!4l#4l#4l#4l#4l#4l&7o#4l&7p(6r%6p$7n#6n$9p"8n#7q#6n#8m#8m$8m':p$7o$7o$6o&9q$:q$:p&:r':t&9s':q':r%7o(:r(v,>v*;s->v0Ay1Bz/@w/@y/@{.Az/Bz/By/A{/A|/B|0C}.A{0C|/B{/B|/B|1C~0E~2G3H2F3G3H4I8M7L5J7I6H6I7M7L;P:O:O=R=R?T?T=RS@U@U?T?T?T=R:OU?UAUAVDYCYEYGZEWCVBXDZCXDYDYEZCXG\EZFZH\H\H\L`K_K^L\K\J[J\G]J\K\M]K]H[H\H]G\E[H\I\H[I\H[GZGZGZI\G[G]H\H]I^H\J^I^J_J_I]I_H^K\L]J[IZIZIZG[I]I]I\K[J[J[K[K\J^F[EZH]G\H\K_H\I]I]G[H\FZH\H\G[I]H\H\I]J^I_K`LaMcNcLaK`H^I^I_I_J^K_I]K_MaL`K_L_J_H_G_I_H]G\H\J^J_H\J^I]L`H\J^I]I^I_L`MaK_L`K`J`K`K]G\J_H_I_L`J^K_LaI^K`J_K`I^LaL`J_J]J^L`L`M`M`L_I\K^L_J]K^K_K^L_O_O_O_H]K^L_L_L_J]K_K_J^L`J^J^K_H]I\K_J^K_L`K_K_G\I^I^H^H]J_G\H]J_I^I^H]I^H]H]I^H]H[K^I\I\J]H^I]I^G\G\G\J_H]G\G\G\I^H]I_I_I\K^J]K^I\H[K^K^K^K^J]I_J_I^J_H]H]I^H]H]G\I^H]J_K`I^H^I`J^J_I^K^L_J\I_L^J^I^LaK`K`J_K`K_K`I`I_I_JaKbKbK_MbK`LaLaI_I_K`KaJ`LbJ`I`K_MbOcMbLaNcOeLbLbMcMdNcK`OdMbNcNcNbPdLcKaLcMcMcMcMcMcLbLbLbMcMcKaL`ObKbM`MaK_NbMaOcMdMaMaNeMcLbLbLbLbLbMcMdNdNdLbMcOeNdOeOeNdPfQgOePfQgPfPfPfPePfMgMfQfRhRhQgPfRhPfQgOePfQgQgQgQgOeOePfPfQgPfPfQgShShUiVjVjSkTjUkTjSiRhRhTjSiTjUkUkTjTjUkUkTjTjUkTjVmWkXmUlTjUkVlUnUoXlXnVlVlXnVlVlZpYnXmXoXnXmYmYoZoXoZpXnYoYoZpYoZoWqXqYoYoYo[p[p\qZo^s[qZpZo\p[o[o\q[p]r^s]r_t`u^s_u^t^t^t^s^s^s_t^u_v]saw_u`r_t_s`tauat_v_u_u^t`uavbwdyf{ezbwf{cxgzhzg{h|g{i|lmk~nknonssqsssuzwyzwuvvxvvwww||}Ņȇɐʖʟ̬Ъ̧̦ͪРϗ͑ьՆЉԉӋӏҒӑҐϊ̒Ԅzyyqqnmommnmlmlmonoqqrwwvstuvqvtwuvvwwxxwzz|}|ʀ~́σЃЅ҅҇Ռԏ؍Ӑ؏֎ےۑۓݔߔߓޏٍۏ܏܏َڐۏבޑۑߓܓ皯딫헱!/^ .^ .`"0a$2c"0`#/a ,a$1e$1e$1e"/c#0d"/c"/c!.a/b.a 0c 0c/a /`$2b%3c$0b#0a"1_$2a .]"0`"0a"/b".b!.b$1e"/d 1d /c/b!0c%0e"/c"/c#0d"/e"/d$0e$1f%1f!.c#1f 1d!1e#/c#0d"/c"/c#0d#0d"/c"0d!1d0b1d 0e!0d#/d"/d"/e"/e#0c"/c"/d%2e#0d!-a".b"/b"/c#0e"/c#0d#0e!.d#0e"/e"/e#0d!.b"0d$/c#/c$0d$0c"0c#0d$.g"0d#/e"0c!1d".c".c!.d!.b#0d#0d#/c$0d$/c"0d!.b"/c!.b!.b0b.a.a /c"0c/b.a.a/b/c.b.b,a.b/c/c/e.d.d.e.d0d0d0e0d.d.e/f-c/c.b!-b!.c#0e#0e#/c 0c2d.b0h-f-d.d /e"2f!0e#1f$1f%0f%/f$.d"/e#0f"/d$1f$1f!.d#0g!.e"/f"/f"0g"/f#0g!-e"/f 0d$4h!1f 1g 0f!1g!1g/e!1h /f!/f"0g /f 0g2h3h3h!2h/e0f"3i 1g!2h!2i$4l!3j1i 0h 1i"3k"4l%5m!3k2j2j!2j"4l 3k"6m4k 5l!6m"5m%6n#4l$5l$5m#4l&7o$5m$6m%6n%7n"6n$6n$6o$6n#6n%8p%9m%8m%8m$9o#9p#9p$9p#8n#8n$9p$9p&9p':r':s&9q%9o%8q&9q':r':r&8p);s);s*?v+>v,>v,S;P:N:N9N;P8M9K7J8K6J9KS?T@US?SAUBVAVDXFZCWDWH[H[H[I\H[EXFYFZEZAVBW>S@U@U@V@W@U@UAV?W?UCWEZDYEZDYCXEZEZCXCWDYCXEZEZDYF[FZJ^H\K_I]I]I\L]J[L]K]F[J\J]K[L]H[H[G\F]G[F[F[H[H[H[GZH[FYI[H[G\H]H]J_G\I\J[M\L^I_F\F\K\K\K\J[J[H\G[I[HZH]I]J[IZIZL[J^G\G\G\G\H\I]J^J^H\I]I]FZF[EZI]G[H\H\I]I]K^NaK_L`OaI\K_I]J^K^K^K_J_L`NbMaK_L`L`H\G\G\I^H]I^K_J^J[H\I]H\H\H\J^I]I_H]J^K_J^J^K^I^J^K`K^K^J]I]I^J^P`N_N^N^P`O_N_M]J^I^K_K_MaL`K_LaLaK^J]L_K^K^K^K^K^I]J^J^J]K^I\K^NaL_K`K_L`K_L`L`L`J^I]L`I]I]G]H]K`H]I^I^H]I^J_H]H]H]H]I^I^I^I^I^I^G\I^K^J]J]K^H_G]H]G\G\G\J_I^G\H\G\I^I^H]H]K^K^K^H[I\L_J]K^J^J^K_H]H]H]J_J_H]J_G\I^H]H]I^I^K`I^J`G^H\J_LaM_K^K]M`L_J^I^K`K`K`K`J]K`K_K`J_K`LdMdKbLaMbK`J_I^J`JaGaJaJ`LbMbMbNcNcLaNcLaMbLaKaNdJ`H_LbMbMcNbLbLcMbNdMbMbNcKaKaMcKaMcMcMcMcMcLbMaLbMbLbM`MaJ^L`L`MaMcMaK_McNdNdLbMcLbMcNdKaJ`LbNdOeOeMcMcOeOeNdPfNdPfOfPfOeOePePgOiMfQgPfQgRhQgSiOeOeOeOeQgPfQgQgOeOeQgQgQgPfRhQeRgTiTiVkTiVkTjTjRhTjTjSiTjSiTjTjTjTjWmTjTjUkTjVlWmVlYnXmUkVlUkTjTnWpYnVlVlXnWmVlUkWnZnXmXoYoXmZoZoZoXnXn[qWmXnXnYoZoWqXqXnYo[pYnZoZo]r\qZn[rZp[nZn[p[p[p]r]r\q_t_t^tar`r`s`s`vbw`u^s^s`ubw^s_s`t`t_t_r_t`tbu_vataubwcxcxcxcxcydzdyf{gygyezfzk}k~j}nl~llnnosspststwyzxx{xvzzywzz|̃ɅɌʎɔ̕ȜƥѨӤԥӣӖюχч҈ӈևԋӊԑyxx{xrqonomlmllk~j}kmknoppqrutrrrsrrtrvuvwwvwyzz|}~|~~́͂τцӆԈԋҌՋيَ؏ڏُِېڌێَڍٍٍ֌׎ىՎ֍׋ْܔ٨蘯 .] .\!/_!/`"0a -^&1d+]$1f!.c#0d"/c#0d#0c,` /b/b/b.a!1d/b!0a%1b$0a$/c$/b$0`#1`!/^!/a$2d#2d$1f".c#0d"/c"0d/a0c 0b!-a#0d$0d%0d"/e#0e#0e#/e$/e#0e$1f"/d#/e#/c#/c#0d%2e"/c"/c#/c!.c"3f0c0d!1e"1e#/d#0e#0e#0e"/c#0d"/e#0e /c 1d!0c#0d#0e"/d#0d#0c -b#0e"/d#0e#0e#0c"/c"/c$0d$/c$0c$0e"0e#0d"0d#/e"0c 1c!1d 1d 0d".d!.b!.b$/c$/c$0d$0d#0d!.b$/c!.b!.b!.b/b!.c!-b!.b/b.a!1d 0d 0d0d/c.b-a.b.b.e/e/e-d/e0e0e/d0e1f-c0f.c/c/c-b"/d#0e#/c!1c"2d.`/c-d.f-f-f-e!2h!1d#/c%0f%0f%0f$0f#1g$1i"/b$1e%2g"/d#0h!.e%3j"/e%/g$0f!.d".e"/g!1g"2e"1g3i3g/c1g0f!1i /f!0g$0g"/f /f 3h3g3f!2h0e 1g 1g0f!2h!2i"4m!5n 2k0h 1i"3k!2j$4l"4l2j2j"3l#3k/h 4m4l4l 5m"5m%6n#4m#4m"3k#4l'8p$5m%6m%6n&7o&7o#6n$6o$6n$6o&9o$9m%9m$9n#8n$9p#8o"7n$9o$9l%:p$:q%:p&8q%8o$8l%8n%8p&9q%8p':r&=s'=t'=t*?w)S>S=R;P:Q:Q9N9L7I9L6I8J;J:J:K:L9L9L9P9OS?T@U?TAVR@U?TBVEYDXCVEXGZGZGZHZH[I\EXDZ@U?T?T?T>V>VBYBW?T@V?W?W?VDYG\F[BWEZAVCXDYCYCZCYB[CZCXF[DYGZG[H\G[J^H[N^JZL[I\E[G[HYIZK\G[F\G]D]E\EYFZEXEXH[EYH[FYFYF\G\H]H_G^H]I[GYL\J[LaH]H]M\K[IYJZJZH]H\H^I\J]J]JZJZK[K\J[G\G\G\DYFZJ^H\J\JZG\H\J^H^F[D\E\J[J^J^I]K_L`K_K_I\LaK_K^J^K_K_J]M\J^K_L`L`J_K`J_H]I^I^G\G[K^K[J]I]H\I]H\I]H\K\K^I^H]I_L`K^I^K`I^K_K_K_J^I]L_N^L]J[N_M]N^M]L\P^MbKaMaL`L`L`MaK_K_J\K^L_K^L_J]I]K^M`L^K^K^K^K^M`K^J]M`LaJ_K_J^J^L`L`J^I]H\H^H]H]I^H^I^G]I^H]H]G\I^H]H]H]I]I]I]H]I^I]H]I\K]K_H]I^G_I^I^H]I^H]I^I^G]H^J_H]I_H]I]I]H[J]J]J]I\K_K_L`L`I_G\H]J_J`H]I]I^J_J_I^J_I^K`I]H^H_I^I^I[H[J]L`I\J^J^I_I^J`J`J`K]J_J_J_K`LbL`MbMeK`J_LaK`LbI`H_H`K_J]LaLaKaI_MbMcLaLaNcMaKcKaJ`NdNaK`OdMbNdMbJdIdJdJdJdMcMcKaKaNdLbLbLbMcKaMaMaMaLbMaL`L`K_NbMbJ`K_L`LcLbNdNdLbNdMcKaLcLcOeKaPfLbNdOeOeNdNdOeOfOfPfPeRgQgPePeNiNhQePgPfRhPfPfQgQgPfPfQgQgPfQgPfQgQgQgPfPfQgRgShShTiWlShUlSjSiRhRhTjUkTiTjTjUkUkUkVlUkVlWmUkUkWnWlYnWlVlUkUkWmUnTmWlVlWmWmVlVlYoWmWmYmYnWmYnWlXm\oWlYoYoZoZpZpYoZoVpXqXmZp[o[p]r[n[pZoZp[q[q\o[o[o]r[p_t^s]r_t`u_sas`sat`s`s`u^s`ubsas^t`v_s`t_sauasau^r_sataucwcxbwcxavdydxdxezh~fxh{h{h|h{h{lqmmmonpmupsuswzz|yyxzzxyy{{y|ʀ̅ˊ͏Αʑɖəʝ͘ʚ̜ӛϒЍЅхӂЁυ҆Ҍ׆yuuy|uoqoonnok~j}k}j}ijfilmnppoqrprqrstsvvwtvuxzwxyyz{||̓π̅҅҆ЉՉӆՌ׍،׎؎׏ِڌڊڌ،؋׋׊֎ٌԋՎڌِޕۗ֜֞؝옯,]$2b,[#1_ .\"0_#1b"0a!0`#/b"/d!.b#0d!.b#0e.b/b.a.a"2e/a/_0a#2c"0^!0^"0_"0_!/^!/^#1`%3a$2b ._$1d#0e"/e#/d"/c -a$1e#0d"/e"/e#0d"/d$1f"0d!.d%2g%2g$1f#0e!/e 1e 0e#/d"/e#0e#0e#0c#/c$0e /a0b1c!2e#/e#0e#0e"/d#0e$/e#/c%1e#/c"/c#0c"/e#0d#0d"/d$1f#0e"/d#0e"/d%2g"/e#0c"/c"0d"0c"0c"0c"0d#0g#0e"1e 0d.a 0c/b"0c!.b#1e -a!/c"/c$/c$/c"/c"/c$1e!.b!-b!/c.a0c 0c!1d!1e 0e/c/c-a.b!2f/c.b/c!2e/c/a1c1d/c/c1c/b.b0f-d.d/c 0d!0c -`"0d 2d!1d0e/f0f,b-b,a0e /f.c#/d$2g$0e#.d#/e#0e%2f#0d#0h$1h"/f!.f"/f#0g"/f$1i"/i0i /h /f /f.f"0g"2f 1e 1d!1g0f!2h 0g#1h /f /f!0g.e!2i 1i"3l0i 1h 1g/e!2h"3i"3i#4i"3h"3j#4l"3k 1i!2j!2j 1i#3k 1i"3i 1g3g5i4i4i 3h"5j$4j$5j$5l$5m%6n#4l#4m$5p&7o%7o!4l$7o"8o"8n"8n$9p#6n$7p$7p$9p#8o$9p&;r#8p#8p%:q%:q$:q$:p&:n&:n&:p$:q$:p':r':r':r'9r&8q)r+>s+;q,=v,=u+v.Ay/Bz-@x-By.C|.D|/D}0F2G1F0E~3H3H5J7L:O6K7L9N5J7L7L;P;P:O;P=Q=O:P;PST?V@T@U>R=PASESAQ?SEYAUEZFYGZFYGZF\FZHZDYEZBWBWAVAV?TBWBW@UAV>S?TAVBWBWEZH]CXCXBYB[CZDYCWAVAWEWGZEYH\EYG[F[H\DXFYH[I\J\GZF]G\G[GZG[GZFZH]G[EZF[DYDYFYHXGXFYGZDZF[F[I^EZH]H]H]H]I^I]H\H\I]FZI^H]I^G\I]I^I]L]K[H^FZH]I]J[I[FZF[DYEZEZF[EZH\F[G[G[I]J^G]G\G[FZG[H\H\I]K_K_I]K^K_L`K_I]I]J^J^K_K_NbO_M^N^K[J^H]H]I^H]H]G\G\H]I]G[H\I]I]K_J^I^H]G\I^H^H^H]J_J]H\K_J^K_J^L_L]N_L]O_L^KaJ`KaKaJ_L_K_L`MaL`MaK_L`K^K^H\K_K`K_O^M^LaL`I^L`L_M`J]M`L`K^I\L^L^K^M_M^LaK_H\NbK^K_H]I]I\I\J\J]J_J_J_I^H]I^J`G^G_G_H]J^H`KaI_I_I^I^G\I^H]I^H]I^H]H]I^I^G_F]H^J]M`O_K[K]K^I\K^J\I^J^K`J`MaK_K_J^J\G[H^I^I^I^J_J_J_K`K`K`J_K`J`I^K]L_K_L^N_K^K]I\J]K]L^M`J_J_J_LaJ_MbK`MbMbK`K`K`K`LaLaI^J`LbLaH]M^M_L^O`LaMbLaMaMbNbLbJ`KaMcLbNdLbMcMcLaMcMcMbLcLbLbJ`McLbNdNdKaNdMbHcIbLaLcMaMaNbMaL`NbL`M`MaMdNdLbLbLbNdNdNaL`LbLbMcNePePeOcPePeShPeQfMeLeJdLfMfLfNeOhQgPfRhPfPfQgPfQgRhRhOeSiRhPfQgOePfPfRhQgUhShUjVkUjUjWlUjTiTiTiRhPiQkPjVlTjTjTjUkUkVlUkUkUkVlVlVlWnWnWmUkXnXoXoWnUkXnUkVlWmXnVlXmVoUnUoWmYoXoYpXpYpWoYpZqWoWmYpVp[qYpYq[rXp[o[p]o[q[p[p^r\p]q]sZq\s[qZo\q_t]r]ravatatas^t`u_u]s_u`v`v`s`s`t_s`t_s_s`taubvbwavcxdybwcxcxdydyfyfygygzgzi|i{i|k~lk}lnnlpqprxwvxzwv{wu{y{}~~}̃̈́͋˒̖̘ǜϙҙҒ̑ϐҕ֔ғԏҏ؍؋وԃӈ׉؃xxuyxuuonnokikii~i~jjkj}hjkmnnmnpprsrrtptsusvuxwyxzyz~{~~ˀ΂΁τ҈҇ҋԋӋՍ׌׎ڌڋ܍؍؍ي֌؍ًٍؐ؏؎ُאהەۖܕڙ柳!/`!/_$2a!/^"0_#1`"0a#1b"0a!/`#0b!.b!.b -a!.b-_-` 0c 0c 0c/c 0a 0b#2c$2a"0_"0_#1` .]!/]"0^%3b%3a!/^#1c$1e#0d!.b!.b"/c%2e"/d#0e"/d"/d"/d"/d$1f#/d#/d#/e#/d"/d!.c#/e#0e$1f"/d"/e#0e#0d!.b!.b#1e!1c$/c#/c"/d#0e#0e#0e"/e"/d$0d%1e%1e$/c"0c#0e"/d#0e$1f"/d"/d"/d"/d"/d#0e"/c#0d"/c#0d"0c"0c#0d".c$0d$1f!/d.`.a 0c!1e"1e!-a#/c!.b#0d"0d$1e"/c#0d#0d"/c"/c".c"0c.a 0c/b 0c 0c/c/c 0e 2f.b.b/c0d.b.b/c0d0c0c/c1e1d0c.b.f-e.d/c/c /b#0d"0c0b 0d0d0f/e-c,b.c/e,c"/d%1e"0e!.c$/e%0f$1f#0e"/e"/f#0g"/f"/f#0g#0g,c ,f%/k"0i /e!1h /g!0g"3f"2f 1e 1f$5k0f0f0f"1h /f .e!/f /f 1g 1i"3k 1i$5l 1f0e 1g0f 1g 1g 1g 1h 1j"3k"3k!2j#4l!2j!2k!2j"3i4i!5j"7l 5j 5j#8m%7l#4j#4j#4k#4m#4l$5m'8o&8o#4l#4l$5m#6o"6n"8o"8o"6n$6n#7o#8o$9p#8o$9p$9p%:q%:q#8o$9p%:q$:p$:n$:n$9n%:q$:q%8p%8p%8p)s*>s,=t*;s,=u+STBW@UBW?T@U>S@S@TAV@TBVBVAUAVBUCUEXEZF[F[EZDYEZCXAVAV@U@UCXBW?T?TAV>S?T@UBWBWCXBWDYBWDYCYBXBWBWEXDWEXEYG[EYFZDZJ]G\FYJ]HZFZG]F[CYDYGZGZH[DXF[F[H]G\DYDZEWIYFXEXFZF[EZF[G\F[G\EZF[EZF[G[H\G[H\FZG[G\G\I]I]G\H\M]G\G[I]G[I[K[J\FZG[DYG\DYEZF[FZFZI]G[G[H[F\EZDYI]H\G[G[H\J^I]J]G]I^I^L_J]H\H\J^I]J^L`K_M^L]L\K]G\G\H]H^F\H]H^I]H\G[H\G[G[H\H\K^J`I^H]I^K`H_H_H^K_I]J^K_K_K_M]L]L]N_N_N_L\O_O_O`LaMaL`MaMaL`MaL`L^K^K]L^MaMaL`L]I_L`J^MaK^K^K^H[N^L_L`LaI]L_M_N^I]K_K_K_J^K^I_J_I\L^J]J\H]H]H]H]I^H]J_G_H^I^J_J`H_I_H\H]H]I^I^J_H]H]H]J_H]H]I^H^H^I^H^I\J^M^L]M^L`J]J]I\L^K`J_I]K^I]J^L`LaK]I_LaI^J_J_LaI^H]J_J_K`J_I^I_J]K^L_M`M_H\J]L_J`J_J^LaK`LaLaK`K`LaMbLaK`K`I^MbK`J_LaLaKaK`LaM_J]J]O`LbNcLaLaMbLaLaKaLbKaLbLbNdLbJ`LbLbKbKaMcLbNdNdLbMcLcKaNdLbLbLbOeNcMcJ`NaH\NbJ^L`M`MaK_MaLbLbNdNdNdLbNdObNbMdNdNdMcNcOdOdPeShRgPePfMcOdOdPfOdQgRgQfOePfPfQgRhPfRhQgRhQhPfPfRiRhPfQhQgQgPeRiTgTiTiRgTiVkTiUjUjUiUjUiRjQkQlUjWnTjRhUkVlVlVlVlXnUkVlWmVnVmVmYpVmUlXoYpVkWmWmXnXnWmXnZpToVpWpYnYoYnXoWnXoZq\s[rZpZnYo[qXoYp[rYp[q[oYn[p[pZoYn]r^s\p]qYoZq\o\q^s\q^s_t^sas`t`s`u^t^u_u^u`s_s^r_s^r^r`t^raubv`tcw`vbwbwcxavdyf{bwdxfxgzi{h{h{fyh{k~k~j}nlomonqrrtwxyxvxxwx|}ŁƄɆ˂ȃʄΆ͋ϐʗМΟ͡ўЛҔ͒яђӕ֔՗ۑڌ؎ۋ܃քՆzwxuxuurnmmmkkki~g|h}h}h}h}f~iinnkmlnnostpqussutvuvvzzzzz|z}}~πЀ΄ІЇҊӉӋՌ֋׎ڌ؍؊ֈՈՋ։Վڊ֊َ֑ڏ؍֐דْٖٓؔߙ景"0a ._"0_ .]"0_"0_!/a!/`"0b#1c#1a!/_ .` -b,` .c/b/b 0c/c/c/` 2a"2`#1`"0_"0_"0^#1a$2d"0b .^ .]"0`$2d".d#0e -a"/c!.b#/d".c%1g#0d"/d"/d"/d"0e 1d 0d"3f!1f"/d#0e$1e,a!.c%2g#0e!.b"/c"/c"/c#0d#/c"/c"/c#0d#0c#0c"/c#0e#0e"0d$0d$/c"/d"0e"/d#0e"/d#0e"/d"/d$1f#0e"/d"/e#0c#0d#0d#0c#0d#0e"0d#0d$/e#0e"1c 0c.a-a.b/b 0c!/b".b".b#0d$1e"/c#0d"/c!.b"/c!.b"0c.a 0c/b/b!1d/b!1d"2e 0d.b.b.b.b.b.b.b-`/b/c1d1d0c2e/b/b.b0c0d 0d!/c$/c#1e"2e"2f 1f-c/e.d/e,d!/f/f!/f#0e"/d$0d%0f%/f$1f"/d#0d$1i$1h#0g%2i!.e"/f%2i!/f!/e!/f.f.d!1d"2f!2f!2f"3g/e 1g 1g 1g!1h"1h!1h1f!3i 1g 1h!2k"3k 1i#4l 1j!2i"3i"3h!2h$5j"3i"3i"3i!2i!2k!2j#4m#4l#3l!3j"7j"7k 4i4i!6k"6k#7l#5k%5l$5k$5k%6o$5m#4l#4l#4l&7o$5m"4l%8p#5n$7o"8n"6m%8p#9o$8o$9p#:o$8p$9p%:q%:q$9p%:q%:q%:p$:q$9q%:r$:p$9p%8p':r%9q':r&9q(;r)=r*>r,t,=u->v/?w-?w+?w*=u+>v.Ay+>v/Ay/Bz-@w,Ax/D{/Dz1F|0E{2G}2G~2F}5I7L7L7L9M9N:N8L7M:O9N:O:NS@U@UAVBWBW@UAVBWBWAUCUDXCWBUEXDWEZEYDXDXFZFZBUGZGZFXH\EZE[FZGYGZEXGZFYGZG\G\EZDYCXDXGYGYEXDXE[EZG\F[F[EZDYF[H]G]H\G[EYH\G[J^J^J[G[I]I^I^I]I]FZI]G[G\G\H\G[FZBWDYEZEZCXG[FZFZFZH\FZEXH\EYG[I\HZH[GZGZH\K_J_J_J_J_H]J^H\J^I]K_MaK^M^K\M^L]H\J^J^I]H\G[FZG[G[G[G[H\G[I]H\I\H\H\H\G^I_H^I^K`MaJ^J^MaL`L_N_M^L]K\M^M^O`M^M^O`L`L`L`MaK_MaMaK_K]G]H]H^I^K_K_L`K_MaL`I]J]K^L_K_N_KaL`J^LaL`J_J]K_K_J^J^L`L`J_J_I\K^K^J]H^H]H]H]H]I^I_H_J_J_J_K_F^H`J_J_H]I^J_J_H]J_H]I^H]I^H]J^J\J\I\L^M]L]O`M^L]N^M^I[FZI\K^J^LaL`K_L`K_I]H]J_J_I^K`J_J_I^J_I^K`K`H]J_L_K^J]K^N^M^K^J_J`KcKbKbNbL`MbK`LaJ_J_J_I^J_K`J_J_J_MbJ_M_NaNaL_L_M`J_MaLaLaMbJ_MbMaKaLbKaKaMcNdLbKaKaH^J`LbKaLbJ`LbLbLbNcNbPdMdMcKaLbLbLbLbPbPdMaMaLaL`L`MaOcKbMcKaLbOeLbMcOdOdOdMdNdMcPeOdOdRgShQfOdPeNeNdOeNdQgOeNdNdOeNdQgOeSiQgPfQgRfQfRgQeSfSgRfThSjPfPgWlShQfShTiTiUjUjRgTiTiUjVlRgSiVkUkSiSiSiVlUkTjTjUkUkVlVlUkWnXoXoVmXoXoXoYoVkUkWmWmVlWmVlWmWmYnYoXnXnYnXpYp[rXoZqYpYpZrYpYpYpZq[rZq[rZqZo\qYn\q[p[q]rYnZoZqZq[p]r]r\r^r^s`v`sat`s`s^s^t_u^u_s_s^r_s`t`tauauau`tbvauawcxbwcxbwbwdycxf{dyezf|hzg{gzk~j}lj}mnomnppqvvyxyyvyv|{}ÁŅȈʋ͉̇ˈ̍Ώ͑ǚˡ̤͢ϠϜΘђЏӎԖוړًԅ҅ԄԁӁԂ||vw{woponlmjf~igi|ji~jjfg}gihlhkllomppqpsuvuuuxyxywyyzy{{|}}Ё΃фΈӇшӈӌ׎ٌٍيՍ֌֍׍׉ԍ׉֎ٍ،֋֏דݕߑڒߑݑ景-]%3a .]"0_!/^"0_#1_"0a"0_$2b$2d$2c!0`"0a,\ 0`.^.`!1d/b 0`/` 2b#4b"0_$2a$2a$2b#1b"0a"0a$2c%3a#1b$2d!0`#1b#/d#0d!/c!2e"3f#2f"/e$1h$1e$0f"0e0d1e!1e0d".c!.c"/e#0e$1f$1f#0e$1e"/c$1e$1e"/c$1e$1e#0d"/c"/c"/c"/c#0d"/c"/c#0d"/d"/d"/d#0e%2g"/d"/d#0e"/d$1f#0e"/d"/e#0c#0d"/c"/c"/c#0f#0h"0d$0c"/c"/c0b-a.b-c.a/b.a 1c!/c$/d"0d"/c"/c!.b"/c!.b!.b"0c/b 0b/b 0c!1d 0c 0c"2e!1d/a/c0d/c/c0d 0d1d1d/b0b/b0c2d0c2d/c 0d!1e 0d"2e 1c!1c/c 0d 0d0f/e,b.d/d /f-d"2h"/d#0e"/d$1f#0e$1f$1g$1h#0g$1h$1h$1h"/f"/f!.d#0e#0g"/f .d 0c!1e!1e#1e$3g#4h0e/e 1g.d 1f.e 0h/c0f!2h"3l/g0h 1i 1i0h!2k!2k 1i!2j"3l"3k$5k#4j!2h#4i"3i"3i#4j$5l!2k!5m"4m"5l%9m!6k!7l#8m$7m&7m%5k%5l%6l"3k$5m%6n%6n$5m'7o&7o$7o$7o&9q%7o&:q$:q$9p$:q!:o$;q"9o$9p%:q%9q%:q$9p$9p%:q%:p$9p%;r%9p&9q(;s&9q*r0@w,=v,=u.?w,=u.?w0@x,=u+>v/Bz,?w-Bx-Cz.Cz,Ax0E|.Cz1F}1F}2G~0E|3G5G6K9N7L8N;N;M9L:Q:O:OTAUBT?R?S?TASCU@SAS@S?TAV@UAVCYAYE\E]DZDXCX@UBW@UCXAV?T>S@U=R@U@U?T@VCXBW?T?TAV?UAVBU@SDVATBUDWBUIZEYEYCXEYFZEXCVFYFZEZEZDYFZGZFYGZGZFZCYEZEYCWEZDYEXFXEWEXEXCXEZG\EZDYDZC[G[EZH]H[G[G[FZH]FYJ[JZJ[G[I]H\FZG[G[J^G[G[F[F[FZCXBWEZEZDYF[G[EYG[EYH\EYEYG[EYF\EZF\G\F[H]J_H]H\F[G\I^I^H^G\G]J]J^L`J^L`L\O_K]J_L`I]I]H\G[H\H\G[I]G[G[G[G[J^H\I^J^J^J^KaI_J`J`I]K_I]K_J^J]N_K\N_N_PaK\K\N_PaL^J^MaK_NbK_K_L`I]K`I^LaI^I^L`MaK_J^K_MaL`K^L`K_K^L]K\L`K_J^I]J^MaJ^K_J^L`L_LaJ_J_J]I\K^J\H^I^H]J_H]H]I^H]I^I^H^I]G\K`H]I^H]H]K`J_J_I^H]J_I^H]H]H]J\I\I\K^N^K]L]N_L]M^L]N_N^M^I]I]L`J^K_L`K_I]L_J^J_I^K`I^K`K`J_J_LaK`H]I]J]NaM`L_N]O_M`KbKbJaLcLcKcI`J_MbJ_J_I^K`J_K`J_J_K`J_LaL`NaL_M`L_K_J`MaJbMbMbNcK`K`NcKbJ`KbLbLbLbMdKaI`JaKaLbKaKaKaKaKaKaMbLaNcMaMcNbMbPdMcLcNaL_OcL`NaL_L_NbNbKbMcLbLbLbLbKaNbNcMbOcLcMdNbPeOdOdOdPeQfOfOeNdPfNdNdQgQgOeOfPePfPfPfRhRhQgRgRgQfQgSeQeQeRfQgUiTiShTiShUjUjUjTiUjUjVkVkUjUjUkUkUkTjSiTjTjWmWmUkTjVlVlUkVlVlWnVmXoXoXoVmWnZpVlUkVlVlWmUkXnWmYoWmYoYoZpXnYq\sXoYpXoXoZqXnZqYpYpYpZqZq\s\t\p[pZo^s\q[nYo]q[o\q[p[p]r\q_q]s\qav`s_rbu_s^s^u]s_u`s`s_sbv`t_s_sauaucwaubvcudyezavdyezbwbwbwf{f{g|f{hyi{i|i|lmlmnonoqttvzwvy{z}x~ńNJȐ̒ΒˑЏ͏͐ΓΚΜ̝ͦЧџ˝ϟՕՍВڕړڑۅzwwrturqqsorqplmmji}g|e~g~g~f~i|h}h}igfigihihhjkloqsrrsutwuwxvxwxxxxz{~}~΂̂ρσуЄшцщԊՋ֎ԋӌҊՌ֋Պ֊֍ى֎ڎَڏדܑڑڒڏږ⛯ꜱ .`#1` .]"0_!/^!/^#1`"0a#1`$2a#1_"0` .`#1b#1b -^!/` 1a/` 0a._!1c#2b%3b#1`"0_"0_#1b!/`"0a$2c#1a%3b#1`#1a$2d#1b"1b"/c"1e!1e 0d"1e$0e"/d$1f$2g"0e 2f 0d"3f!1e#0e"/c#/c&1e#0d$1e"/b"/c"/c"/c"/c"/c$1e$1e!.b#0d#0d$1e"/d$1e#0d#/d"/e#0e#0e$1f$1f#0e"/d%2g#0e%2g$1f%2g"/d#0e"/c#0d#0d#0d#0e"/c"0h#0d$/c#0c#0d0c.b-a-c.b.a.a.a 0c!0c"/c!.b -a!.b!.b!.b$1e /b0d/d/b-` 0c/b!1d 0c/a!1d!1e!1e 0d 0d0d.b/b.a0c/b2e0b2d1d1d0d/c/c 0d 0c"2e 0d.b/d-d.d/e/e/e/d.e%2i#0e$1f#0e!.c$1e"/e"/d#0e"/f%2i!.e#0g"/f"/f%2i"/b%2h$1i"/d!.c"2f#3g"2f&4h$0d"4h/d0f0f0g0i!2j0h0i 1j!2k0h 1i/g#4l 1i#4l"3j 1i"3k!2j$5m$5l"3i#4j"3i#4j#4j#4j"3i$4k$6n"5m 3k 2k 3i"5j!5j"7l"8m#8m"6k#6k$4j'8p%6n%6n$6n"6n&9q$7o&9q%8p%8p%8p&9q&:q$:q%:q#8o"7n$9p%:q$:p%;q#8o&;r&;r$9p$9p%:q%9q':r';s(:r+v(v-@x.@x/Bz/D{.Cz/D{/D{0E|0E|3I3I3H3G~5H8J9L9K8K8K>Q9K9L;N9O8M9N;P;P=R;PQ@S@S?R?QAT>S>S@UAS@SASAV@UAUDYBYCZE\E[D\CZCWAV@U@U?T>S?TAV?T@U?T@V@UCUBUCW?U?U@VCW@R@RDUBSATBUCVFWDUCVDWBUBUCVBUCVCVDWEZDYEZEYFXH[FXFYDZE[AZBZE\EZDYDVEWHXDWEWCYDYDYEZEYF\BYCYFZE[F[EYFZFZG[GZHYHYGYH\H\I]H\H\F[F[F[H\G\EZEXEZF[EZDYCXCYFZEYEYFZFZFZFZFZH]G\EZDZF\F\EZEZH^F]H]H]H]H]F[F[H\K^K_K_K_J^J_K_K\J`G[I]H\H\I]H\G[H\G[G[H\I]H\K_I]M]K_H\J`I`J`H^LbJ]J^J^I]L`L_M^N_N_N^M^N_M^L]K\M_L`J^MaJ^J^J^K_K_I^K`J_I_J_LaK_I]L`L`L`L`M`O`O_K\M^N`J^L`L`MaL`J^K_K_I]MaK_L`J`L`J]K^J]I\H^I^H]H]H]I^H]I^H]J^I\L_I_G]I_J_H]G\K`J_H]I^H]I^H]I^F[H\J]J]H[M_M^N_N_L]M^L]L]M^L]L]N_M_I^L`J^K_I]K_MaI^I_I_I^K`I^I^J_MbJ_I^I^I_L_K^K^L^N_L`M`LbKbKaI`GaKbJaL`K`MbI^I^K`J_I^K`K`K`J_I^NcN`NaL_L_L^H^H_LbMaLbK`NcMbMaKaKaLaPdMaLaLaMbNcMaLbJ`LbKaMcKaNdKaMbLaLaNcNcOdMbOdNdLbMbNcNcNdNcNdNcLaMbNdKaLbMcNdLbLaOcNcMbMbPdLcOdQfNcQfOdPdOfNdNdMcMcMcNdPfPeOfPhQhPgRgTjRhPfRhSgShRgQfShQdRfSgShShRgShShShShUjTiVkTiShShUjTiVkTjTjTjUkUkVlVlVlUkUkTjUkSiTjUkXnVmWnXoXoWnXoWoVmYnXnXnXnVlVlVlWmYoYoVlWmWmVl[rYpWnYpXoXoZqYqXoYqZqZq[r\s\sZq\p[pZo[pYn]p]p]s[o]q^s]r]r`r^q^s^s\r`sbu`s_t_s_u_u_u_u^u_uau`uau`t`tauau`tatbxezbwbwbw`uezbwezdyezezg|e{h}j|j}lllloopnruwxww|{}}|ƃƅȊɎʑˑ˓ˍ̑ёϒЗқљ͢͝ϡˠɠџԕԓ׏ܔۓڒ݂wsqpqpj~mmllkjji}kljifze~h~g~e}jg|i}h}f~d}g~gfhjigghkrlrsrtuvuxvvwwuwvyxwxz}}}}΀̈́сЁЃЄшщԋֈԍԌӋՈҊԌ֋ԋՉ֍ي֍،׌אؒڒؓڐ֝䡶햮!/^#1`"0_"0_!/^"0_#1`!/^$2`#1`%3a$2`#1`!/` ._%3c%3c#1b!/`"3d"1c%3b!/^$2a$2a#1`%3b#1`#1`$2c#1b$2c!/^&4c"0_%3a%3a#1c$1e#/c 0c!1d#3g#2g'3h&3h#/e$0e!2f 0d$4h&1g#0d&3g$1e#0d#0d$1e"/c#0d$1e$1e#0d%2f$1e$1e#0d#0d#/d"/b#1a#2b 0c!0c#0e$1e%2g%2g#0e%2g"/d#0e#0e$1g%2g$1f"/b$1e#0d .b"/c$1e$1e"/e#0d"/d#0e#2e 0c!1d 0c/c 0d!1d1c!1d 0c"0d$0d"/c!/c&3g"/c -a#0d"/c!.c 0d!1e/a 0c$3f"2e!1c"2f"2f 0d!1e"2f 0d!1e/c0d/c0c0d.a0c/b-a/b/b-b/c"1e!1e/b"2e 0d /e /f/f/e0f.d.d/f-d .e$1h!1d#/d"/e"/c#0c#0e!/g /f"/f#/f#0g$0g&2i"/d".d".d$1h-e-c"2e!1f$4h%5i#3f#3g 0e.e 1f!2i!2j 1i 1i 1i0h0h0h0h0h.f#4l!2j"3k"3k!2j 1h"3i"3i"3i#4j$5k"3i%6k!2j$5m$5n#4l1i!4l!5m 3l2j"6k#8m"7l%8m%8m$8m!6j#8m$6n&6n#6n#5m$6o#6n&9q$7o%8p#6n&:r$7o$7o&9q$:p$:q$:p%:q%7p$7o&;s%:q&;r&v,=u+v,=u,=u-?w0Ay-v.?w1Bz/@x/@x1C{/Bz.Ax1G~.Cz/D{0E|3H3H3H4I4G6I6I7K8K9L8K8K:M=P:O;O9M:N:P:O=R;P:O:O;P;P>S;PQ@SBUAT?T>S@U@U?T?U?T?T@UCXBXBYCZE\E\CZBYDWCX@U?T@U>S>S>S>S?T>SAVATDWCVBUCUBU@V@U?RARAQBTATGXEUDUBT@SBTBUBUGZBUATDWEXCXDYEZBWBWATCYEZCWF[F^BYCYDYDYEWFYEXDWEWCYEZDYCXCXDYC[C[CZDYG\G[J^H\FZEYFZGXE[G[EXE[DXG[HWHXGXE[EZG[G[EYCXDYDYDYDZH[FZEYEYFZEYDYCXEZFZFYGYHYI[H[H\H\EZF[EZG\F[F[EZG\H\K_MaK_L`J^J^K_J^K_K_G[H\I]G[G[G[G[G[I]H\H\H\I]J^K_J^H[J^H^IaJ`I]H\J^K_L`NaN^L^J_MaN_K\M^N_N_O`J^L`L`L_K_K_I]J^J_J^K_M`L`K_K^N^LaJ^J^K^O_L]N_N_L]L]K`K_J^J^J^K_L`L`K_J^K_L`J`I^J]K^GZJ]H^I^I^H]I^H]I^J_J_J_J]K]K]J]K^J]I^LaI^I^H]I^H]I^H]J_H]J_I[J\K^I]O_M^M^L]N_N_M^L]M^N^L]J]K_K_J_K_M_K`K_K^K]K]K^J_KaI^I^J_J^J_H]K_L^K^M`K]N_K^K_KaJaKcJbJaI`JaJbL`J_J_K`K`I^I^K`J_J_K`K`LaN`NaM_M^LaK`KbKbK`MbMbNcLaMbLbLaNcMbLaNcMbK`MbK`LbI_LbLbLbJ`McLbMaNbMaNcNcNcMbMbNdLbNcMbNcNcNcMbNcNcNcLcLbLbMcMcLbKbNcOdNcLaOdNcQfOdNcOdPeMdOeOeNdMcOePfOePfOeQhPhPgRiQiQgQgPfPfRfShRgQfQfRgRhTiRgShRgQfTiVkUjShTiTiTiShVkTiShVkSjUkTkTjVlVlVlVlTjTjUkTjTjVlWmUjYpWnVmXoVmXpWlWmVlWmXnWmXnXnVlVlVmXoYoYoWmXnXpYpZqYpYpYpXpZnYn[pZo[oZrZrZqYq\p]r[p[p[p]p[n^p^q\p\sat_q]p^p_t]r]r_r^q`r_u`u^u_u^t_u^t`v`v`sbvbvau_s`t`ucx`uav`uezbwbwcxf{ezdyfzgzh}h}i~ij}lmmoonmpuuwz{{y|}…DžƈɍȎȓɖΕ̒˓̑͐ϗՙ՘ҙҜԟТҡѡѠَٕ֚،א߆yrsppm~lk~h~hgf~hf|f}g~e{d{f}i{gzbyayd|fyj}g|kkhffg{ki~lf~jfihkprrruvuuuuvyvvwvvvwzz|}}ˀςЁ̀΀΂ЅсχщԉՉӈҋՉҋՊԉӊӇ֊֌؋׍،؍ՑۑՍԎԡ镮"0_ .]!/^ .]!/^#1`#1`#1a#1c#1b%3d#1b#1c#1b!0a&4e&2a%3c$2c$2b#1b#1a#0_%1a#1`!/^$2a$2a%3b$2`"0_&4c#1`'5e$2b&4d$3b#1d$0e"0c0c 0c!1e#2g&2g%2g$1d'4g'3h#0e$2g&3g%2f#0d#0d$1e%2f%2f#0d#0d$1e#0d#0d%2f'4h'4h"/c%2f#0d#/d"/c"3d"2e!0e$0g#0h%2h%2f$1f%2g#0f#0e"/e%2e"/c$1d"/c"/b$0d$0d$0d$0d#/c#/c%2g$.e#/d .c 1c#3f/b"1e%1f!0d/b/b/b.b#0d$0d$/c$0d#0d#/d"/b"/d#/e"2f"2f 2d0b2b 2d#3g#3g#3g!1e"2f!1e!1e$4h 0d/c/c2e1c!1e0d0c0c0c1b0c/b 0d 0d 0c#2g"2f"2g!0g-d-c 0g0f,c /f .e".e#1h!/f#0d$1e"/c#0c#0e-d.e.e /f/f.e!1f"2f#4h /e$/g$1h /e!2e$4h%5i%5i$4h%5i /d0f0f!2h!2j 1i 2j1i/g1i 1i/g!2j 1i 1i 1i 1i 1i 1i!2i0f 1g#4j$5k"3i 1g$5j"3j"3k"3k"3l3l2i$4l#3k!3j"6k"6k$9n#8n$9n#7l#7l';q%7p#6n#6n#8o$9p%7p$7o$7o':r%8p(;s+v-=u0?x.=w-?v.?w0Ay/@x.>v0Ay.Az/Cy.Cz.Cz1F}1F}1F}2G~4I8L6I6I7I8J8K9L9L8K;N:L>NS=U?U?T?T@U@UAV@U@UCXD[AXCZE\F]E\BWAV@U@U>S>S?T?S?U@U>SCXASBUCVFYBV?SAS@S?R@SASEVEUDUCTDUCTCVBU?R@SCVATDWBVAVCXBWAVAVDYDZDYDYBZC[BYBYC[E\CXBWFYEXDWEWCYDYBWDYDYEYD[D[DZDYFZEYH\G[EYEYEYF[G[EXE[F[FZFWGXHYIZG\DYEZFZDXCXCXCXCXCXFZEYFZDXDXFYGWHXIZEYG[H\FZH\I]G[DZF[DYEZH]F[H]H]I]J]K_H\L`L`L_I\J]I^J^J^I]J^H\H\I]H\H\H\J^J^J^I]J^K_J^J^G[J^I]L_I]I]L`I]J^K_J^M]NaK_J_K`K_O_M^M]N_LbK`J_K`I^I^I^J_M]J_J_I_K^N^M]M_K_J^MaK_L]N_L]L]N_M^N_K`L`L`L`K_J^K_L`L`L`L`J`K`LaK_G[I\H^I^I^H]I^H]J_I^I^M`K]J^J]J^K^K_J^K_J^MaH]H]H]H]H]H]I^H]I]I^G\K\L_M]L]M_L]N_N_M^M]H\J^K]K^I\J\M]K\L]I^I]H[K_K]J\I\I^K`I_IaJ_I^J_L_M`K^O`K^K_I^I^I^J_K`LcJcLbI`J_K`K`K`K`K`I^J_LaJ_LaMbLaLbJ`LbMbI^LbI`LdMaLaNcNcMbMbKaJ`K`MbMbNcNcLaLaLaKbLbMcJ`KaLbMcLbKaJ`KaMbLaMbNcOcNdLbNcMbNcNcPeOdMbNcNcLbKaLbOeOeNdNdMbOdOdOdQfNcNdOdOdPePdPfQgMcNdNdOeOePfOeMcRhPgRiSjSjQgQgPfQgPfQhPfPfRhOePfQgRhRgRgVkUjUjShShTiShTiShShShUjTiUjTiVjVlUkUkVlTjVlUkTjSiVlUnYpXoUlWnVmVmXnXnVlVlUkWmVlWmXnVlVlWm[pZnXnVlWmYoXoZp[r[r\sYqZnZnZoYnZoZo\q\p^r]q[p\qZn]s^r]o^q]p^q^q^q^q_r^q^r_u_t^r_r_r_rav_t^t_u_u]s`v_u`vbv`s`tatbxbwbwbwbwcxcxcxdycxcxdydxdzdzczg{g|g|lmllnmmrsowvxz{|{ƒÇƈNjƍƒʔʖƖɗȕɔΓ͖͒Җ֔ӖӘӟӣ֥ե՟ՙՕَ؏܈~usqonlk~jf~d~hfe~f~d}fe~d}ge|hzg|h|gzh|g{f{i~h}fhzi~h}f{g|jmggijjnprrtvuvvywvvyvstuxvwzz~}~͂ρЂρЄтЄхԉ҇҇҈ӈӊӊԈӉՈӋ؍׊ԏٍ֐َ׎֒ܕ䙮혮"0_ .]%3b!/^#1`"0_#1_#1b%3e%3d"0b%3d#1b$2c"0`$2`'2b&1a#1`$2d#1c%2c%0`$0`"0_#1`$2a#1`$2a'5d$2a$2a'5d%3a$2_&4a$2c$3d&2d"2d2b 1d#2e#1e%1e%2f%2f%0e&0g&0g&0g$/e$1e$1e$1e%2f%3g$2f$1e%2f$1e$1e#0d'4h$1e%1e%1e%2f"0d 1d/`/`!1e#2g$0g%1i%1i%1h%2g%2g#0e%2f#0d"/c$1e&3g!.c$0e$.e$/c%0d%0d%1e$/c&2e"-b!3h!3f0d!2e /b$0d$1e"/c!1d!1d"2e$2f#0d#/c#/c%2f!.b#1d&4g"/c"0d0c"2f#2f 2e 0e/d#4h$3g#3g#3f#3g"2f"2f!1e.b0d1e2e0c!1e0d3e.a1c1b0b 0c!1e"2f!2e 1e0c/d.f 0f.c/d1f/f"1h"0g!.e-d /e".d%2e"/c"/d%2g"0f-f-f"1g /f"0h-c 1d.a /c .f /f /g 0f%5h"2f#3g#3g%4h"2f!1g0g!2j!2j0j.i1i0h1i0h 2j0g!2j0h!2j 2j 1j!2i"3i"3i 1g"3i"2h"3i#3i!2i$5m$5n$4m"4m"3o#3m$5m$5m#4k"4i#7l&:n%;m#9n"7l"8m&:p"5n"5m$8o"7n$9o%8o&8p%8p':q%8p);r,;s,=u->v*v*>v(;s':s&9t(v+v/@x.?w.?w.>v0>w0?x,N>O=N9M:N9N:O;OR;NS@S@T>V=T?T?UAV>T?TBW>SBX?WBZC[CZBZAXCWCXBW?T>S?T>Q@T@UAV=R@U@SATBUFXFVCS@T?R?RAT?R?QCSFWDTCSASATAUAUBUBVATCVATAWCXAVBWAV@VBXAVD[CZCZCZCZBYBXDYDXEXEXEWEWCYDYBWCXBWDZAZCZC[DYEYEYG[G[F[EYG[FZG[FZF\CXDXIYHWHXIYFZE[FZFZDXDYDYCXDYCWFZEYEYEXEYDXEUHXGXEZEYI]G[I]H\EYEZDZF[DYG\EZF\H[G[I^L`J_I^K_J]I[I^J^G[I]H\H\H\I]H\I]I]I]K_H]I^H]J_J_J^I]J^I]K_I^L`H\I]J^K_I_L_N]K]I^K_L`K^M]M]N]L_J`K`J`I]J_I_I_J^L]M\M^M]M_N`L\M\KaL`J^J^M]O_L]O_O`M]M]L`L`K_L_L_L_L`K_K_L`L_J_I^J^J^I\K]H^H]H]I^H]I^J_K`J_K]J]K]M]N^M]J]K_J^K_L`I_H]H]J_H]H]I]I_H`F^H]J]J\N_L\KZK\M^L]L]H]J]I\K^K^I]J]M^K\L\K\J]M^L]I]K^L^LaI^K`J`G\J`I^J\K_J]L]J]I\J_K`J_LaK_I_JcI`IaK_I^K`K`J_K`K`K`I^LaK`MbLbKbI^J_I_KaK`JcJaNaMbMbK`K_LaKaL`MbNcMbMbLaNcOcK`KbKaLbJaJ`J`LbLbJ`J`KbLaOdLaNcOdMdNdMbMbNcNcOdNcNbNbNbNdMcLcLbLbLbLcMaOdOdOcOdMcPbPfRgOdPeMdNeMcMcOeMcPfMcNdPfOeQhRiQhRjQgPfQgPfPfQgPfQgQgPfQgQgUgTiRgUjTiUjUjUjUjShUjUjUjRgVkUjTiVkWlUkWmWmVlUkUkUkVlUjWlXpUlWnXoVmWnXpVlUkTjVlVlXnWmXnXnWmUkXnXlWlYnYpWmWmXpYqZqYpZqZpZnYnZoYn\n\q\qZo[p]r[p\q[o[p^s]p^q^q]p^r^q\o_r^q^s^t^s`u^ratasavav^u^t_uaw`v`v_uauawavbycwcxdydyezbwbwdycxdyezeye{byczd{i}j~li~kmlnmnqpswuy|}ŀŁňƉōƑʕʔƕĖĘȘǔƖ͕͕͔ΗђϓДӛԟ֟ӠҠՙՕَؐ܇{uspqklh~e}ef~bdf}ed{d|e~e~hge|g{g|h{eyg{g|i~h}kih}izl~j}i}jlk~giimnqstuvvtvxuxvxtvvxywzz|~̀~̀ρЂσЃЅӄ҅ԆԄӈ҉ԊԋԇӆҊԉԊ֎׍،ԐٌՐܑݗ晬햬씭"/_#1`!/^!/^"0_ .]!/^%3a"0^"0_%3b$2c$2c#0b#.f&5e&5c$2a%3b!/^#1`%4b#1`$2a#1`&4c$2a$2a"0_%3b&4c#1`$2a$2a%3b$2a%3b%3d$2c"1e2d0c!1e"3f$3h&2g$1e%2g%2f$1e$1f$2f%2g%2f%2f&3g(3g%1e&1e&2f%2f&3g&3g&2f$1e%3g%4g&2f(3g$0d&2f!1d!3e"4f$5h!2e"4f#3f%1f$1f%2g#0f#0d#0d#/d%2f$2b#1e"0d%2f#0d%2f"/c#0d$1e"0c%1d$0e$1f#0d%1f#0d%1e&1f$0d%0d%1e&1e'3g%1e".b!/c$/e%/f%/g&1j%1j%3i!1e!2f!2f 1f!2e#4f"3g!2f"3h!2g1d 1e!2f!2f 1e"0d /d0d 5h1d0d 0e0d.c.c/c 1d 0c#2d".b%1g%1f$0d"0f!0k.g-d .e /f"1h.e0d!0c%1e$1e#0d"/c$1d#0d .c#2i /f /f!1h-e%1h'4k#/g%2i$2i!0h"1g!2f 1e!2f0d4f4h3h 1h 1i0h0h 2i/g 1i!.h /h!/i"0j!1i 0h!/i!1i!2g 1g 1g!2h!1g 2h2f3h!5i#3i$5k%6l#4j&7n!4l3j 4l 4l 3l$7p#6o#8m&7m$5l';p&:n"6m%8p$4o%9r#8o#8r&t&=s(v,=u,=u+v,=u-?w/@x.?w.?w.?w.?w0Ay/@x0Bz.Ay-Ax/F{.Cz.Cz1G}0H|3H|4I~5J~9M:M:M;MT?TAU?T=R>S@U@SATATDWAU?S@S@S@RDSCTBT@UAUBUBUDSBRDTDUFVEV@TDYBWAVCXBWAVDWCVCVDXBWAYBYCZCZAXCZBY@UDVDXCYCYDYBWAVAVAUCY@ZAXDZG\EYFYEXGYGWFZJ^DXG[G[FXHXHXFWFXGYDYFZFYDWDXCYEZCXDYCXCXCXBWDYCXBXCWDXDXG[GXEYFZG[G[H\FZG[H\FZH\H\GZFZGZG[H[M\K]L\L^I^I^J^H\H\H\J^H]I[K]J^I^H\H]K`I]L]M^M]M]N`J^H^I^I^K_K_K_K_MaI\N^J_J^L`L`K_J^MaI^K`J_L`J^K_K^O`M_L^M^L_J_K_L]N^N^L\K\O_L]M^J_K_K_J]K^I]K`J^J_J^MaL`L`K`JaJ_L_K_L_MaLaI]I^H]I^H^I^J_H]K`I^G\I^J_J_J\J^L_I]J^L_J_J^I^I^H]MaH\H^H]I^H]I^H\J]J\J\I\I\L]M]M\M^K\M^L]N_H[J\H\K^L\N^L]K\K\L]K^M]K\O`J]I]J`I^J^L^K]K]J]K^M^N]M]I]K^K`I^K`J`IbKcL`K`J_LaLaJ_J_J^L_LbLaMbMbK`MbKaL^K]NaL^M_KbL`JbKbKcKaJ`KaKaLbKaLaNcK`MbMbK`LbJ`MbMbMaL`KaJ`J`LbLbKaLbJaNbLaMcNcMcMcNcNcNcNcOdNbIcKbLbNdOdOcPeMaOdNcLdLbMcMcMcPeOdNcNcPeOdQePcObPeOdRfOdPeNeNdQgNhQhQhQhQhPfPgQfShShRfQgRhQgPfQgShShShUjVkTiShTiTiTiUjTiUjUjTiUjVkUjVkXlVlVlWmVlVmVmWnWnVmWnWnWnWnVmXoXoWnUjVjVjVlWmWmVlWmVlWmYoXnWnWmXnWmWmXnWmZoZqYpWnXpZoYnZo\qYnZo\q\q[p]r[o[nZn[o]o^q_r_q_n\p^q^q]p_qasbt_sbp^rau_tavau^uau`tbvav`uaucudwcvbwf{dybwbwcxezbwdyavcxdydwexfyexe|g}h}hl~llnmppqyww~~łDŽLJƉƐǑƕŗƜǜǞȝɝʜȚʛ͗̕ҖғғՔ֗לڢܤڠԘӖ֓ؑ׀yrppnmk|g|g~f~egadcbacfed~f|d|czi~g|g}ij|j|g}ji~li}li~ke~hhimklnrssuuvvwxuuvwswwvvwzz|||~~ˀπτфхфЉЉшщ҉цԆӄ҆ӉӋՌ֌֌׏؋؏ݍޕ阭혬왯$/_$0a#2`#1`"0_#1`#1`$2a%3b$2a$2b#1c#1b$2c$2c$2b%3b#1`%3b#1`$2a$2a#1`$2a#1`%3b%3b%3b#1`%3b$2a$2a$2a%3b%3b(6e&4c$2c#1c#1e"2e!1d!2b 0a%4e&4d&5e%3c&3e%2f$1f%2e&3f'4h$1e&3g&2f%1e&3g%2f'3g&2f$2f&3g&2f#2f"2e$3f'4h&1f$2e!2e"4e$4g$4h#3g$4g&2h&3h%2g&3g&3g$1e$1e%3f#1b"0a'4g%1f&3h$1f$1f&3g$1e$1e&3g$1e"/c#0d#0d%2f#0d#0c"/a'2g%2f$1e$2f$1e#0d!.b$1e#0d$0f&0g&1f%3f"3f!2f!2f!2f#4g"4f!2e!2f!2f"3g!2e"3g#4h#4h#4h"3g0d 1e1d3f 4g!1f/b0d0d 0e/c#4h#3f"2e$4f#0e%2g&3h .c.e /f /g /f"1h /f.e 0e"2d!0d"/c$1d#0d"/c$1d%2h"1h /f.e .e"/f$1h!.e%1h$2i"1h!0g 2g 1e 1e$5i!2g$4k!1g 1g!2h 1g!2j!2j0h.g"1k#2k /i /i!0i /i"2j 2f0f 1g!2h 1g 1g!1g 2h 4i 4i1f$4j$5k$6l$5k$6l 4l"5m$7o"5m"5m$7o"5m&8o(9n&:o%8m"8n%9o(:s%9q&:s%;r%;t&;t';u':t&9s':t)v->v*v->v/@x->v0Ay0Ay0Ay0Ay0@x.@x/Bz0Bz0E|1F}2G~2G}5I~2H}7M9L:N:N:M:M:N:N:M7J8L:M9L9L9L;N:N8M8N9O8N9O9N9NS@SATAT@SATAT@S@T?T@UAVAVAVCXBWDYCX@VAXAW=SAV>S>S>S@U?T?SAS@SBU?RBUBU?R>Q?RATBRBSAU@T>Q?S@SCTCTBSCTEWAT@S@UAVBW@UBWBWAVDWCWAW@XBYCZCZAXAYBXCWBUBUBWCXCXCXBWAUCWDYCZBYAXFYEWDWCVDWFZEYG[G[H\G[GWEVGYGXGWHYCWDXDXFZEYEYBWCXCXAVAV@UCXCXBWDYDXCWBVDXCWCWFZFZFZH[FYCYG[G[EYG[EYG[EYG[G[I]L\L]L\I^J^I]H\I]I]I]I\GZH[I]L\J\K\M]M^M^M]O^O_M_J_I_LaJ_L`L`J^J^I\M\K\I^I]J^K_J^J^L`J^J^K_K_J^L`L`L\M^N^O`O_J^L_N]L]M^M^M^M^L]N_I]K^J\J]I\J]K_K_K_J^K_K_J_K_MbK`K_K_K`J_N\L`I^H]H]I^I^K`J_G\I]H]I^H]H^J]J]L_L_I\K]K_J^J^J`K_J^H]I^H]H]H]H^I]J\J\I\K^L_L\L\L^L]K\M^L]M_J^H\K]M]L]M^J[L]L]L]L]M^N_N_I]J]G]I_MaL^J]K^I\L^N_O^N_K^I\J]J_J_K`J^J_K`K`J_K`J_J_K`J`M^K`J_MbLaMbMbM`L_M`M`J]K`LaK`IaMdKcLcJ`I_LbJ`KaLaLaMbMbLaK`KbKaLaNbLaJ`J`KaJ`McKaLbLbKaKaNbNcMbNdMbOdNcOdNcOdNdMbMbMcLbMdOdNcMbQcPfMcMcNdMcOdQeNcOdOdNcPeOcOcRePeOdPeNcPeOeNdQfSjPgOfQeSgRgPfPgRfThQhQgRhPfPfRgSgTiTiShTiUjUjUjTiWlTiUjTiUjTiVkUjUjXmVkVlUkVlWmVnWnVmWnVmVmWnVmVmVmWnVmVlTjUkTiTkWmWmWmVlVlVlTjVlXmYoWmYnWmXnXnZqXoXoXoXo[nYn[pZo]rZo[p]r[p]r\qZoZp\q\q^p]p^p^q^q]p^q\o]p^q_s`rcsau^qaw`ubw_x`tawav`ubwbwavbyevcxdycxdycxbwdydybwezezbwaucvgzgzf|jij|k~klmp~oruuzz|ąņƈNJǎǓʒƒėěȡˡʟȟɜǝȚɘʗΖϕҖҚۓԒҘ؝ٚԛԚ֘ڕڂzzspnllli~gg~effdddbageg}e}i~h}f{f{h}i~f|f|h}ji~k~i}i~i~khhhk~i~mlnrqtttvuwusurvvyzvuxzzx{z}~~́Ѐ́σ΅τχϋՊъ҈щԅӃфӇ҉ӌ֍֌؏ڏ֌Քߚ陮혱#/_$0`'3c%4b$2a$2a%3b$2a$2a%3b%3b%3b%3c#1c#1b$2c#1`$2a%3b$2a$2a$2a$2a'5d%3b%3b$2a%3b%3b$2a#1`&4c&4c%3b%3b'5d%3c&4f%2c#2b 1b 1b#2c'5f'4e$2c&4e&4e%3d%3c$1d%1g'3h'3h'3h&3h'3g%2f&3g%3g%6i%5h&6h'5i'3h$4g#5g&4h(3g'3h#3f%6i$4g$4g%5h'5j(4j&3g%2f%2f%2f&2g%2g%2e$2c%3d%3d'6g%3c&4e$2c)7h'5f&3h'4i&3g&3g&3g(4h&3g&2f%2e&4d%2d%1g%1f$1f&2g&3h%2f%2g%2g$1e$1f%2f%2h%3h#3g#4i%6k"2g"4g!3d 2c!3e0d#4h"3g$5i#4h!2f$4i$4h$4h%5j$6i"6i#5i"2f#3h!2e2c!3e"3e!2f"2f$4g$2g&2g$0f"0f#2h!1d#3g"3f!0e /f!0g!/g/e 1d$3h$0e$0e#0f#0f#/e$0f#2i /f /f!0g!-d"/f#0g".e#2i#3i!2h 1g!2f!2f#3g!1e 2h 1g!2h!2h 2h"3i!3h"3h /e /e /e!/e!1g#2h!0g#3j!2h!2h 1g"3i!2h 1g 1g 1g2f 4i3h 5j#5k'7m&6l$5l"5m!4l#6n#6n$7o$7o#6n%8p%9p#7k#9m$9o#9p$9p':r&9r(:s':t&9s&9s(;u%8r':t)v-=u+v->v.?w->v.?w->v/@x0Ay.?w.?w.?w0Ay/@x1Bz2C{1Bz/Ay-@x.C{0E|0E|/D{2G~5I4H}6J8L:N:N:M;N9L9L9L:L:M:L:M9K:M:M9L9K;O:M;N:M;M;N9L:M;NP?SRBT@SCVBTBUBT@SAT@V?TAVBWCXBXCXCXBW@U@X@V?U@V?T@U@U?T>S?T?U>SDWBT@S@S?R@SAU?S@SGWAU?S>R?R?RDTBRBSDUCTATAT?T?TAVAVAVAV?T@U?TAV@TAYAYBZ@XAWAVAUATDXBWCXCXEZDXAYAYAXCZAYCWDXDVEXDWDWFZDWGZF[FYGWGXEVGUEVFWGYCXCW@VCXDWCWDXBWAVBWBWAVBWDYAVAVAVBVAUEXBVCWEXEYCVE[EZDYGZG[G[G[EYEYEYFZH\I]H\K[I^J^H\J^H\K_H\G[H\J^I_L^L\M]M\J\N]N^N]N^N_M^J^I`I]H^L`L`K_J_I^L\L\I^J^I]I]L`K_L`J^I]L`K_L`J^J_M^N_M^M^LaJ^J^J^K\K\L]M^L]K[M_K_J^MaL`J]K^J_K_K_I]K_K_L\L\N^M]K\K[M]K\L\K^I_I^I^I^I^J_J_I^G_H]H]I^H]J]K^L_J]J]K]J]K_L`KaI^J^H]H]H^J^H]H^I^H^H^H^H^HZG[H[M^K\K\J[N_N_M^M^K\L]M^L]K\L]N_M^N_M^M^M^N_I]K]K_J_K]I\J]H[K^N_N_OaJ^I\K^M_J^J`LaK`K`K`K`K`H]I^K`K`J`J_J_LaK`LaLaL_NaL_L_K^L_J`J_LaLaMaK`MbMbMbJ_NcLaMbK`K`K`K`KbLcOdJaJ`J`KaJ`J`KaLbKaKaMcLbKaKaMcNdMcPeMbLaNcOdOeLcNdKbMaOdOdNcNdPcQfLbNdLbMcNdPdNcPeOdMbPeOePePeNcOdOdPePdNeNdNdOgPgQfOdQfRgRgQfPfOePfQgQgOePfQgPeTiShTiShUjUjTiUjUjShVkTiTiTiVkUjVkTiVkSiVlUkVlXoVmVmWnXoXoWnXoVmUlXoXoVmWnXoUmVlVlWmWmYnVlUkVmWoXoWnWnZqYpXoYpXoXoXoZqYpXpXoXpYn[pZoZoXm\q]r[p[pYo\q\q[p^r\p\r^q]p]p_rat_r_r`tatatas`uav`t`ubvduaw`vcxbwavbwbxbwcxbwezdycxbwcxdyf{f{bwcwezg|e{i~h~j{lpp~o~prtutyxząÈƊŌÒƔƒÒÕǞǟŦˣɡƠǜʙʕ̗ϛӖҖԕԕҔԖחח֝۟ޕ}yvpqmnj}h}g}f~dfeef~f~e}e~heg}ffg|g}g|g|i~j~g{i~i}i~jli~klhhf~gllllpsqursuuvxxsstvvwvvxyyzywz}΁ЁρЂ̓ψϋӌҋӉщчӇ҆Ԉ҇ҋՌ֌֏ٍُؔܛ瞲훰뙰홳請調&1b&2c%0`%1a$2a&4c$2a%3b$2a$2a'5d'5d&4b%3b&4c%3b"0_&4c$2a&4c&4c&4c$2a$2a$2a%3b$2a%3b#1`&4c$2a%3b$2a&4c%3b%3b%3b&4d&4e%2b$2a%3a%3c#1c$3d'5f&4e%3c'5e'5f%3d)7h(6f&4e(6f%4e$5h%5h%6i$4g#4g#5g%7i$7h#4g%7i$6h#5g$5h&6i"3g);m&6i%5h&6i(7j)6j'4h'4h(4j&3h&3g(6g(6g'5f%3d&4e'5f'5f+9k'5g*8i*8i*8i'5e&3f)5i$1f"4f%5h$5h'5h(6f%3d!2c"2c&6f'4e'4e(6g'5f(6f&4d$2d'3i&4h#3h$3h#6g!3f#5i%7i"4f#5g%7j$5j#4h$5i$5i&7k&7k%7j"7j%:l&;m"7j#7j#6k 1f#4h"4e$6h$6h"4g$6j#3h$4i#3g#4h$4g"1g#3h#3f#3e 0c!1e!2f!/g!0g#1g#3g#3g!2f#1f%1f%3g#4h#4g#3f"3g#2i#1h#/f".e$1h#0g"3i!2i!2h 0c"3g"2f#2f#3g!2i!2h#4j#4j"2h#1i%4k"0h"1h!/f$0g&2i$1h#2i$2j%4j!1g$4k!1g#3j#3j$4j#3i"3h 4i!5j3h!5j"6k 5j"7k#7n$7o$7o#6n$7o#6n#6n$7o%9q#8o#8p$9q!6m%:q#7n&8q#6p%8p&9r(;u(;u)v,=u,=u+v.?w/@x/?w1Bz.?w0Ay1Bz/@x0Ay0Ay1Bz0Ay0Ay1Bz/Ay0C{/Bz.By1F}1F}2G~2G~4J5I~8L9M8L:O9N;N9L9N9M:N9M:N9N9MQ?R:M:P6K8M7L8K7J;L:K8I~9N9L:L;N;O8N:O=R:O:OS@U>T?T@UAW?T@U>TASCUCVCUBW@U@UBWAU?UBV=RAV>S>S@U>S?T=R@U?T@T@S@SAT@S@R@TBVAV?S?S@T>R>Q>Q?SBUBSBS>Q@S>S@U@U@U@U@U?TCX?TCX?TBVAUAVAV@T@UBW@UFYCXBWCXAWAXBYBY@WAX@X@UAV@SCVCVCVCX@UCWDVEYFVFVESFUCTGXDUAVCW@UCWCWDXDXBWAV@UAVBWAVBW?T@U@UBWAVCZBYBYDZDYBWCXDYBWBXEZEZFZDXG[G[G[FZI]I\G\I\I]H\H\FZH\I]J^I]I]JZL]L]K\K\M^M]N]M]M\M]M^J_J_JaL_MaK_J^M^M]M^L]K_I]K_K`K`H]L`LaJ_K`K_J^K_M]M^M^L^I^L`L`K`M`L\L\L]M^K\K\L^J^K_K_L`I]J`J_I]K_H\K_N^K\K\K\K\L]L]M^M^L]K\K_I^J_J_J_I^LaK`I`H_J_H]I_J_J\H[I\K^J]K^I]I^K`K`I^I^G\I]I`H]I^H]I^H^I^H]H^J^I\K^K[N_K\L]IZK\K\L]L]N_K\N_M^L]M^N_L]O`K\N`I]J]L^J]J]J]K^J]I\M^K\N^O`O_F[J]L_L_K`G\I^J_K`K`G\I^J_I^I^K`I^K`K`KaM`M`L_M`M`L_I\J_J_LaMbLaI^MbLaLaK`I^LaLaJ_NcMbMbMbKaKaMcLbJ`KaLbMcKaKaI_McKaJ`KbMcLbLbMbNcNcNcOdNcNcOdNcOdOdNcNcNcNbQdPeI_NdMcNdMdNcPeNcOdOdOdPdOdOdOdRgNcRgOdNdOeMcPgSgShRgQfRgRgRfPgQgPfRhPfPfRhQgShTiShShVkTiTiVkWlShShSiSjWlShVkVkVkWlVkVkTkWmWmXoVmVmVmVmVmWnWnYpWnYpWnWnUlVnXnXoWoYpYpYpXoVmYpWnVmXoYpWnYpZqXoYpYpXo[r[rYpZqXo[o[p\p\q[p[p\q]p^p]o[q\q^q[s\t\s^q^q_q_q]pas_rbu_ras`u`uav`wbv`vcucvcvcxbwbwdybwcxavdybwcxezcxezdyezf{g|e{h}g|f{jik}ppqqrvuuwwy|~ąĈċĎƒÐƓÑ•Üşȥ˨ʨ˩̦ɣΡ͝΢ҢԜҞӘҗӗؖڔܗޜߣ♥}wssnkmk~h}f~egeehg~hhd~egf~i}i}j|i{ki~h}i~ffe~ji~jmihhimkknopooqtsttxvvtuqsqvuvxwzzzwzy{}|̂̀˄̊ӌՋҋӇЈЉӈԉӉԋԈҊԌ֍׍׍ؒݜ坬㜮㝯睲皭垰頳럵狀諸%2^&3_'4b'3c%3b$2a(6e%3b%3b$2a'5d%3b%3b%3b%3b$2a#1`%3b%3b$2a&4c$2a#1`$2a$2a%3b&4c&4c&4c%3b%3b&4c%3b#1`)7f%3b&4c#1a%3e$2c&4c%3b%3b%3b(6f(6g&4e%3d%3b%3b&4e'5f(6g&4e&4e%4e%5e$4e#3e&7i#5g%7i$6h)9l$5h%8j%9k%7i&5i(9k(;m'7i(8k%5h*:m&4g*7k)6j)6i(7g(6f(6g)7h)7i'5f,:k'5g)7g&4c+9h*8f*8h*8j+9j*8i(7g&7h'7k'7k)9m)9k&7g%5g&6h(8i(8i&6g%5f%5f)7h)7h)7i(6h(6g(7h$5h%5g%7i&8j'9k%7h'9k%7i'9k'9k(:l%5j&7k$5i%6j'9m%;m%9l&:m';n$8k#7j$7i$6g%7i$6h$6h#6g$5h$5j&6h$4g$4g&6i$4i%4h'7k#3f&6j#3f$4h$4h#3h#2j%4k 2e!1e#3g%6i%5i$4h%5i$4h'7j$4i#2j"1h 0g"1h#0g"2g"3f!2f!4g#6i#3h%5i$5i#3g"3i"3i"3i#4j$4j$2i$2i#1h"1h%5l"1h#2i"0g"2i#4i#4j 6k!6j 5j3i 5i$9l"6j!6k!5j 4i!5j"6k"5j#7l"6k#7l$6n"4m$6n$6o$7o#6n#5n$9p$8o$9p#8o$9p#8o"8o#:q#7p#6n#7o':t&9s':t)v,=u->v->v)x.?w/?w-?w+>v,?w/?w0Ay1Bz3D|1Bz2C{1Bz1Bz2C{2C{1Bz.Ay/Cz2G~1F}2G~2G~4H7J6H9N8L8M9L:L9L9N9M9M9M:N;O9M:O8M:N:N9M;O9M;O:NPS@U>SAUCUATBUCW@UBW@UBW@U?U@V@T>T>S?T?T>SAV?T>S?TAUCVBU>Q>Q>Q>R>RAUAUAU?S@U@S>Q@S@R@R?R?R?R?T?T>S@U?SAVAU?TAUAVAV>T@U?T@UAW>T@U@UBWAVCXCXCY@XAXCZBYAXBV?TDVBUATDWBV>TAV@UBWCUDXHXDUEUGWCTAVCWBW@VCYAUDXAUDYAX@U@UBWAVBW?TAV@UAV@UCXBXAWCYCWDYAVBWAVDYBWBWEZG[EYEYFZDWG[I]K_H\I]EYG[H\FZH\I]F[I\K[K\K\H\L`K_J^I]J]J^J_J_J^K_L`K_K_M`M]N_M^K\L^H\J^L_M]L]N^N_N^N^N_M^J_K_N^K\K\L^L`L`L`L`K`J^J^L\L]L]K\L^J_J^K_J^MaJ^J`J^LaL_L\L]M^K\K\K\L]M^L]K\N_M_K_K_H^J_I^J_LaL`JaJaK`I^I^J_I\J]K^I\K^K^L_L_H]J_J_G[I^H_H`H]H]I^H]I^H]I^H]H]I^I[M^L]K\L]M^M^M^L\L]K\O`L]M^K\N^M]L]L]N_N^N_K^J^J]N_K_L_H[K]O_N_M^M^O`L]J]L_K_I]J_K`I^J_J_J_K`I^I^K`I^I^K`J_LaJ]M`L_M`M`L_K^J]J`LaLaLaJ_MbNcK`LaJ_LaK`KaK`MbMbMaLdMaMbKaJ`J`KaJ`J`J`LbKaLbJ`LbMcLbMcNdNcMbNcMbPeOdOdOdOdNcNcNcQfNdObNdMcNdMcNdPfPdNcPeOdMbPeRgOdPeQfPeQfOdPeOeNdQgRiQfQgQeTfPfRgQfPfPfRhRhPfQgPfPfShShShTiUjWlTiTiUjRgTiRhUjUjTiWlUjUjUjVkXmVkVkYmWnWnXoVmVnVmWoWoXpYqXoVmVmVmWnYpXoWnXoWnWnYpVmVmXoXoXoYpXoXoZqWnZqYp\sXoZqYpYqYqZqYq[s\o\qZo]r\q]p]p[p]r[s\s\s[t^r_t^s^s_t]s_t\qatbt_t_u_x`xavaucvcucubwcxbwbwdydycxcxezdycxeze{fzdydyf{ezdye{h~g{k~mm~nqqqtwyyyz}ÂŃƇŠċĎǓƑƑ”ÜŞţǧȩˬ̧̪̥ͩЪեѢўԟԜٌЂʂ̇Αנyurpnlkkh}e}ic~ehh}hje~f~gg}gjji}j}g}i~i~kfggh}ji~kiiilkkknnopppssttxwvuwqurtvxwyz{zzz{|{ɀπ΀ʃ΄͈҉ҌԊҍӉщӉӉԉԆҊӋՊԎ،ևגٞި갼᲻ߟߡ룶嗢יּכּ﬽'4a&3`'4a(4b'3c&5c'5d&4c&4c&4c'5d(6e(6e'5d%3b%3b%3b'5d&4c$2a%3b$2a%3b&4c&4c%3b$2a$2a$2a$2a%3b$2a&4c%3b'5d%3b%3b%3b'5d#1`$2a&4c%3b&4c%3b%3d'5f%3c'5d%3c&4f(6g&4e'5f(6g(7h'7h%6g&5f$5f%7i#6h'9k&7i(9l'9k"6h(8l&6i(7j(9k):m*9l&6i*:m(7j+8l*6k,9l-;k.u'w->w.?w/=t1Ax1B{.Bz.@y.Ay.@y.@y-@y0Ay4D|1Bz3C{3C{3D|2C{2Bz1Ay3E}4G3H3H3H3H4I7I7I:M:N9M9M9L:M:N9N:N9M:O:O:NS=R=R>S>S?T=R@U>S@T?QAU@S>R@UAU>SAVAV@UAV@U>S>S@U>S=R=R=RAV>S=S>R>Q>Q?R?R?S?S?SBVAU?S=Q>R>Q>QBUAT=O=P=R@U@U>S>S@V>U?W@W@WAVCX?T@U@U@U@SATBW?TAUBWBWDYBWDZBZBZ@VAUAVAUAUCWATATATBTBU>U?TCWCWAVDXDXEY@TBVAU@TCVCW@TAUAUAUCWAVAVBW@U@U?TAVAV@UAVAV@XAXAX?V@WCXCX@UBWBWBWCXBWEYFZFZDYG\H[H\G[H\G[I]H\H\H\FZI\L\J[J[G]J^H]H`G_H_J]I]I]J^J^K_J^J^K_L`N_M^K\K[K]J^J^J]M^L]N_N_N_M^N_M_J_L\K\O`M_K_MaI]L`L`L`L`L_M\J[K\L]K]K_L`MaJ^J^J^L`I]K`J_M]J[L]L]K\K\N^M]O_O_M]M^K_J^L`H]J_K`K`LbLcJaH`J_K`K`J_L^K^NaM`L_L_NaLbLaLaJbKbI`I`J_I^G\H]I^G\F[G\G\G]L`O`K\J[M^M^K\K]J[H\K[L]K\L]N^L]L`L_L_NaK^K_K]L]L]O_L]L_K^K^K_N_PaO_O_J]J^J^L`K_H]H]J_I^K`I^K`I^J_I^I^J_J_J_KaL_M`M`L_L_K^K^K^J`J_J_LaJ_K`LaMbMbMbLaL`M_MaJ`MbMbK`LaMbMaK`KbJ`JaJ`J`KbLbMcKaLbLbMcLbLaOdNcOdNcMbMbOdNcOdNcQfPeOdNcNaNcNdLbMcNdMcOdNcMbMbOdOdQfOdQfOdPeOdPdPdOeMcQfQhRfRgQgQfRgPeRgQgQgPfOePfPfQgSgShRgShUjShUjUjUjShThSjSiSiRiShVkTiVkUjWlVkVkVkVkYnYmWlYnWlXmXmWlVjWkUmVmWoYpWnXoYpXoYpWnWnYpXoWnXoXoWnXoXoYpZqZqZqXoYpZqYpZpZpZpYqXoZqYqZnZo[p]r`q]t\p]r[s]t^u_v^r]r]r^s^s^s_t`tbs_u_t`u`xbxdxbwcuavbxcxdydycxdydyezezdydyezdygzbzdyf{g|h}f{j{i|g|k~j~nrvttuvxwxz„ƆĈŋŋČŎčĐƔšŠƩ˨ɬ˰ϰѰЯѨ̩ϪԨդӦܘ~{xx|ďӎzrrpolmj~ijggff~e{gzh}kf~efg~jh~g~ki}ljh}kjkjjllkjimmlnoonqqopsssvvwwxusuwtuxwyz{{yyz|}̀΃ӱ́ψψҌԈюӉщԊԈԆшӉԉԈӈӊԇ҈ल(5b'4a'4a(5c'3d'3c&5c%3b&4c'5d'5d'5d(6e(6e(6e(6e)7f(6e&4c&4c$2a&4c&4c#1`#1`%3b'5d'5d%3b%3b$2a$2a$2a$2a%3b%3b&4c%3b)7f$2a'5d'5d&4c'5d'5d%3d&4e$2c&4c&4c'5g(6g'5f(6g'5f&4e(5f(7h'7h%5f'7j$5g(;m(;m)8k):l'9k'9j)7l,9m)9m':k(9l(8k-=o+m0>m0>m/=l0>m0>n1?q2@q0?p,o,?n0Bq.?m,=k.?l->l+l0>n-;m+p/As-?q+=o,>p*;p*;n*=n+>o+=n(;m(p(=l*?m/Ar.@r,>p)v(;s+>v*=u+>x-@w*>r-=s0Bw/@v0Av/Cx0Dx/Cw,Bv.Cw0Cw/Dx0Dz/Bz0C{.Ay/C{/C{1D|1E}3F~4H4J5J5J7L8N:M8L6J7L:N8L:L9L9L:L8L8L:N9L>NP>Q>Q?T>S;PS?T?T>S=S>S@TBT?Q?T?U=S>RAV@U@UAV>S=R=R;QS>S?T?U>R?Q=PR@T@TAUAUBV@S=P?R@RS>S?U>U?V?V@X@WAV@V@VAUATAT?R?T?T?VBW?TDYAVDYDYDXBW@U@U@UCWCWAUAT@SAT@R?RBWBW@UDWDXAV?TCW@T?TBVFVCT?S@TBVAUBV?U>T?T@UAV?T@U?T?T@T>U?VAXAXAX@W@XBYCXBYAVAV@UDXFZDYEXDYF[G[FZG[H\FZG\H\FZI]G[H[K[K\K[L\I[H\I^I]I]I^I^I]I]I]J^J^J^K_L`L]L]M^M^M^I]K_J]K\M^L]M^M^O`M^L]N^M^M^L]L]J_J^K_J^J^K_K_J_J]K\K\J[L]J^J^J^K_K_I]J_I^K_N^N_N]K\K[L\K\I^J_I^J_J_K_NaL_J\J_K`MbK`LaLcLdKaJ_K`J_J_McJ]L_NaM`NaM`NcMdLdMdMcJaI_H]J_G\H]G\G\I^I^H]I^H\K[J[N_K\M^L[M\K\K]I\I\N^N`J]I]J\I\K]J]L_L_L]M]L]N_M^L_I\J]M_N_N_M^J^J\L`MaK_I^J_I^I^K`I^J_J_J_K`H]K`I^J_I^J\L_K^K^I]M]I]J]J\KaJ_J_LaK`LaMbK`K`MbK`J_K^M_OaLbMbMbLbMbMbMbMbL`J_L`MbMbMbJaLbKaMcMcLbMcPdNcMbNcMbNcLaMbNcNcOdNcOdNdQbOdMcMcNdMcLbQfK`MbNcNcPeOdOdOdOdOdPePePeNdNdQgPgPgQeRgQfShQfRgPfSiRhRhSiQhRfShRgShUjShVkTiUjTiTiShSjUkTjVkVkVkUjUjUjWlXmWlYnVkXmWlWlXmXmVkVkVkWlWlXmZnYmVnWoXoWnVmXoWnZqYpXoWnWoWnWnXoYpXoXoYpYpYpXo[rZpWrWrWrZqZqZq[q\q\q\q]r[q]r^s^s^r\q[r^v^r]r_t`u_t`u^s^s`u_t`u_wax`xavavavcxcwcxawbwcxewcxezezcxf{f{ezg|d{czf{f{g|g|i|i{ilk~pqttsxvvyyƊƋčƍĎÑőŎŐƕâçīɯͰͰдӰթѨҦҥ֢ٛمyvyvxw{usrpokmjk}g|ig~fg~ii}i|hjdghjji~jikkki~mljllljkknmmopqotqqqrtstvwvusttvvwyx}x{zyyzy}}ˀɄσΉъщшыӋՈӇ҈Ӊԇ҈ӈ҆ъՅϋϠ츼۬έҰٽ圬堲)6c'4a)6c)6c(4e)3d)6f'4c(6e&4c'5d)7f(6e)7f*8g)7f*8g(6e'5d&4c&4c%3b&4c%3b&4c'5d&4c&4c'5d'5d%3b$2a$2a)7f%3b%3b&4c%3b'5d'5d)7f(6e&4c%4d&5g&5f%3d&4e%3b(6f)7i'5f)7h)7h)6g&4e(6g)6g&6g'8i&6j,;o)8l+:n*8l*9m&9j)9l)8k)8l*:m*t,=q,=p1Cu0Bt2Dv2Dv2Dv2Dv0Av0Au1Au1Cu0At/@r/@s,@r.Bt0Fu/Dq2Eu3Dt1Bs0Bt/@r.?s.?t,>p,;m-p*;o'8l(9m&7k'8l(9m)9m*w(;v(;t+>v+v*=u+=u+?u-Bt,@u-Cx.Av1@w1Ax1By.Bx1E{0Ez.Cx.Cx.Cx/Cx0Dy0C{2D}/B{1E}1D}2F~5H3G4J5J5J7L8M6L8L:M9P7L:O8L6H8K;N:O9N9N9N;M;K:KQS>T=R>S>S?Q@S?U=S>U>RAVAVAUAV>S=RQ>P=P@S

S>T@U=SRAUAU>R?SAUBVAU@T>R>R>S=RU?T@U=R>R?V=U>U>U>V@SBU@T@SAS?R?S?U?U=RAV@UCXAVEZCXAVAVAVBVATCXDXCWDY@TET?R@T@VAVAV@U?WBWAVBX@SATFUEUCUCW@T@TAUCWCVAS?U?TAV?T?T@UAU?U@W?XAX@W?V?V?WBZAYBYCYCWCXEYFZDVEYCXFZFYFZG[H\I\H[G[G[I]FZJ^K[J[L\L[JZL]L\K[K[L\L]I^I^I^I]I^K_I_K^J[L\M]K\M]K\I^I]L]M^M^M^L]L]L]M^L]L]O`L]K\I_J_K_K^K_I^J_K_L_N]P`L]N_L`M`K_K`I_M_N^N^L\N_L^I_J_H^J_KaK_K_MaK`KaKaL^I]K^I_K`J_J_KbJbJbLcLaK`LaK`L`PbM`NaL_ObObNcOdLdLdIaLcKbMaK`H]GYH]H]G\I^I^H]H]L\M^M^M^N^N\O]K\J\H[I]J^J^H[J]L_G]J_L^H\J^I^L_M^M^K\J]J]K^K]M^N_J^J^I]L^L`J`H^H]G\J_J_J_I^J_H]J_LaK`I^J_K`M`L^K^K]N^K\M^J]K^JaJ_K`LaK`MbK`K`LaMbLaN`J]L_L_MbMbLaO`LbMbMbK`LaMbLaLaNcLaK`KaKaMcMcLcLcNcNcNcNcNcMbNbOcOdNbOcPdMbNbNaPeMdNdMcMcNeOdOdPeNcOdPePePeOePdPdPeQfOcOeNdOfRiQhRfQfQfRgTiQfOeQgQgRhQhRgQfShRgRgUjTiShTiTiShShTiTjUkTkWlTiVkUjXmVkUjXmWkVkWlWlVkYnWlWlVkWlWlVkWlYnXmXmYlXmXoYpYpYpZpXnYoXoWnXoWnWnYqXoZrXoYoWnXoXoYpZpWrVrXrZp[r[rYo[oZn\q\qZo\q]r^s^s_t_s_s^s^s]s]s`u\q_t_tav_taw`w`xbycwdxcxcxbwbwcxevewcwbxezdyg|dyf{g|ezd|e}j~f{g|i~i|k}jk~lqqtssxwyx|ƒÇƇŌÐƓǐÚáǥǪǰʶήɯήҪЧϢϠѝҌɀxwutrrppospnoljh}l{j}jd{ed}jh|h}h|f~gff~jif~ijli~jmkkjkkmiilpnoonqtssorstssuuuwvvsvwyxx|x{xz{z{{~˂΁πɄ̅ΈϊщщӉӇӉԈӈ҈҇ӆ҆҄ՋєҦٲݬܨبܩױڻ雮ꔯ(4a(5b&4a%4`(6c(7f'6d%7d'5d(6e'5d(6e)7f(6e*8g)7f*8g*8g)7f*6f*5f(3d&2b(4d&4c'5d(6e(6e&4c(6e&4c(6e%3b#1`&4c'5d%3b'5d&4c'5d'5d)7f)6e(3d)5e)5e%4e'5d*8g(6h)7h&4e)7h'6g%5f%4e'5f*8i,9j,;l)9j):j-=n+p);m*p,?p*r/>n3@o2?n.>j2Dr1Dq5Es6Ds7Et6Ds8Fu5Cr4Bq3Ap4Bq3Bp3Fs6Gv6Gx7Hw5Fs7Hv4Fs5Hv6Gx8Hy8Hz8Hx6Hv6Gu7Gu8Dt7Eu4Cu5Ew3Du3Bt4Gw2Dw0Bt1Cu2Du3Ew3Ew2Dv5Gy4Fx2Dv4Fx3Ex3Ew3Ew3Eu3Eu1Dt.Dr0Eu0Cv.Au3Ix3Ht4Iw1Gv0Fu1Eu4Fy5Gx.As-Bt.As/At0Bt0?s.@q,=p+u,=q+v(;s(;s+>v)>u+@w*>w+=w.Ax.Aw0@w/?v-@s.Dv/Dv0Dw1Dx/Dz/Dy0Ez1F{/Cx0Ez3E1E|3Fz7G}2Cx4E{4G6H6K6K6K8N9K9M:N;O9O:O8N7O8L9K=L=M>NS=RS?T>S;Q>S=R=R>R@U>V@W?V@WAX>T;Q=R?R=PQ?R=P>P>Q>Q?Q?R@S>S?S?SQAS>RS>S>S=Q@U=R?T=U>T=Q?S>S?SAV?UCX@V?U@U?U@U@U@U?TAV@UCXAVAWCWCU@V@UAVCVATDV@SDVAVCWAVBUBVCVAUBU@VBV@TBVCW?S@U=U?V?U@TAUBV?U?T?T?T?T@U@U?T>V>U=X=X=X@WCZAXAW@TBVBVAVCXBWAVDWEYEYCZBWEYDVGYH\DZH^F\H[H\I]JZJ[N^K[J^K]K]L^N`K]IZL]M]K\L\L\L\K\O^J_J_I^H]L]K\M^N^K\K\L]L]L]M^L]L]L]M^M^L]L]M^M]N]K_K`I^J^MaJ^I]LaJ\M]M^K`K`K_L^M]M\L^L]L]L]L]N_M]M^L]N_K_L_L_K^J\L]L]N^J]L^K^KaK`I]K_L`K`MbK`NcLaLbLaOeL^QdNaNaPbK_NbOdMbK`LaK`J_K`I^H]I^H]I^I^H]J[L\K\M^J[L]L^L]K[G[GZK]N^M^I\K^I\K^I[I\M`M\P`M^K]I]K^L_K^I\K^K_K^M^N^O`LaL`MbM^L_J^K`K`I^LaH]I^J_I^K`J_K`I^J_J_MaK^J]I]K^K^L_K]M_H^K_LaK`LaLaLaMbL^M`L_K^McLaLaMbLbLbMbMbMbMbMbMbLaMbNcK`LbKaKaLbNcMbPeNcNcNcMaLdMdMeOfMdLdMeMeNfLcNeLbLbMcNcOdOdQfPeOdNcPePePePdOfNfNfOfLcNeQhMdPgRiPhQhRfQfPeQgPfPfPfQgRhPfPfPfRhRiThUjUjRgTiTiShTiWlVkUiShVkTiUjUjUjWlWlVlUkTkVkVkXmXmYnWlUjUjUjWlWlWlVkVoUoVoXnYpSmVoTnSmXoWnTnYpYmXlZo[pXoVp[qYpXoXoYpZpZq[qZq[rXpYu\qYp[r\q]r^r\p]r^s^r\s]u`s\q]p^q`r_t^s^w`u_tav`ubwau`x_vbvbwdycxdycxcxdyezezdydydyezf{g|fzg|f{g|i~i|k~k}lkp~sttuuuwv|ņƉËБ“ŕҝŭȯ̱̲Үըԝў؟ڝؕ҅zywwrtqqptqspomj~j~i~j}ife~ik~i}k~ifeeefhh|mkj}klkokklklnnnorqrrsvtrspqrrruttsstusuxxx{zyx}xx|{|ˀ}΀͂Ђ̇ІτΉӈ҆ЅЄ΂цԊ҉цϊЈА֙ٞڠץڥڢ٣ؠش識혮瘳&4a&4a&4a)7e'5e'5d)6f*7f*8g*8g+9h(6e*8g)7f+9h+9h,9i*8g'5d*9g)7f(6e)5e(4d'5d)7f)7f(6e(6e'5d(6e&4c%3b%3b&4c(6e'5d&4c(6e(5d(5d'5d(7f)7f)5e'5e'5g'5f(6e(6g*8h(6g'5f'5f&6g)8i,:j(7h'7h(8i)9j)9j*:k*:l);m+=o*p+=o,>p,>p/>q-=q,=o->o0@o2@n3Ap2Cp2Cq6Br8Fu7Et;Ix;Ix:Hx7Eu6Ds3Ap5Cr5Bq7Gu7Ix6Gy8Hx7Hv7Hv7Hv7Ix8J{:J{:J{8Iy9Ky:Kx:Kx;Lz:Lz8Gy:Jz8Kz7Jy6Hy4Fz5Gy5Gy5Gy5Gy5Hz6H{6H{7J{7J|8J}4Gx6Hx7Iy6Hy7Iy4Hx1Gv0Et3Gv2Gv5Jx6Kx7Lz2Gv2Hw6Jy8J{8J|6I{5Hy5Gy5Gy7Gz4Dw5Fx2Dx/@t.?s0Au/@t,=q->r,=q+p&:n'7n(9o%6l'8n'8n'8n';p$8n%9r%9r'9q':r':r':r':t(r*>s+?t)=s+>w,?w,?w+v.?v,=u,=u*>v)=u)Q9L;MU>V=SP=P=P:M;N=Q=Q=P=P@SAS>RSR?S?S@T>RBV>Q=QS?T=R=R=R=R?T=U=U=R=R?T?T>S>S>S@U@U?T@T@U@U>S@U@U?TAV@UAV@VAVAVAV@U@U?RAT@SATAUCWBVCWAUCWCWBVAUBV?SBV@TAU?S?V?U@VATAV@U?T>S@U?T@U@U?T@U@X>Vp,>q-@r.@s.As.@r.>q.?o0?p2?p2@q3Aq3Ap7Dt6Ct6Es9Gv:Hw?M|P:L|:L};M}7Iz8J}8J|8J|8J|:K{r,=q):n*;o(;n(;o*;o*s):m+;o*r*;q)9p):p(9o):p(9o'9o&;p(t,@t+?t+Av,@u.Bv.Cx.Cz.@x+>v*=u+>v+?w*=u+?v*>t*=v+>w+=u->v->w+=t,=u+t)>r+?t.Cw,Av*?t.Cy/Cx,@u.Bw0At0Au0Bv1Fy1Ex2Fy3H{3I{2Hz1Hz1F}2F|1E{2E{4H}5I4I}6I8J6J4J7L8K9L9L:LP=P;N>TS;P=RS=R@U>S@TAV@UAV@U?T>RP;N=P?R>Q;N9NQ>QRT>S=R=S>S?TS@U>S?T@U@U@U@U?T?T?T?T>S@U@U@U@U?U?T@U?T?UASATAT>RBVCWCWCWAUBVBVBUBVAUAUAUBVBV?SBWAW>T@TAT@S>T>S?T>S?T?T?T>T>V?VAW>UAX?V@TAV@UCXAVCXBW@U@U@U@U@UBWCXBWCYEXEYG[H[FZEZH\G\H[L\JZL_L_N`L^K]M_L^K]N`L^M_L]J\K]L^I^J^J]J^J^I\J[K\L]L]L]K\K\L]L]M^M^L]M^K\K\L]K\L]M^L]M^K_L_I]L`G[I]K_J_K_K_K_J_J^K]N^K[K\L]M^M^L]L]M^M^M^N_N_J^K^I\K]O`N_N^L]L_K_H^J_K`J_J_K`LaLaJ_MaKbNcL_M`K^MaNaM`ObJ`LaK`LaK`LaLaLaLaK`J_H]H]I^G\I\K^I\H[K[K\K\L]K]H\H[J]L]N^I]L_K^K^L_K^I\J]I]K^L\J]I\L_K^K^H\I]J\M^M^L\L`L`K_K_K^I_J_I^K`H]K`K`I^J_I^J_K`MbJ`L`NbJ]L_J]I\K^K^M`L_KbJ_K`LaMbK`LaLaN`L_NaN`LaLaMbMbLaLaMbNcK`K`LaMbL`MbMaJbLcJaLcLcNcNcNcOdNcNcOcPgOfLcNeNeNeMdNeLcNcKaMcMcLcNbOdNcPeOdNcOdNfMdOfOePgOfNePgNeOfOfRiOfPgQhPhRfRgRgQfPgQhPgQhRiSkRjQhPfPeRgTiUjRgRgUjShUjUjTiUjTiTiTiTiUjUjVkWlTiVjWlWlWlUjVkVkWlVkUjVjVkVkWlVkYnZpWnXoVmXoXnWoXoXoUlVmVnWkXmZoXnYqVmYpZq[rZrYqZnZn[pZnZr]t\rZtZtYt[r^q]q_s`t_r]r]q\t^r]r^t^p`r_u_t`u`u_u`u`vbwavbw`ycwcxcxdyezezf{ezezf{dyf{ezezh}g|dzh}h~izmllk~j~o}ortvw}}~{ɍɖśǛؓØǦɬɴʹʹͳѩОДˏΉ~|ztsrooj}oopqpnmi}klk~i~li}d|ie}e}g~i}j~hje~g~ggi|h|ki}j~kmnmljlfipnpqruuvyyywxsrqssptuvttuuwvxzyy{yzx||~{̀ˀ̆͆цу·υ͇ӊևЇчхφωЏՔךؙؚ֛ٛۙٚ䘯햰룷蟲褶%6b&8b(8e)7f*8g(6e)7f)6f*8g+9h)7f)7f*8g(6e)7f'6e(9g&7e&7e(9g'7e*6f*7g(6e(6e(6e*8g)7f+9h'5d*8g'5d(6e)7f(6e*8g(6e*7f(5d)9h'8f&8e*:h'5d'5d(6e)7h(6f'5d+9h)7e+9g(6e)8f'8f);i*o,Lz>Ly?Mz;Iw;Ix8Fu7Et9Gv9GvL~N;L|;M};M}P>P>R;P~P;MO>NP;L|9Iz=M~9I{;K|9I{7I|8K|7H|9L}6Hz5Gy5Hy3Ew2Dv4Fx0Bt1Cu0As/Bt.?t0Au/@t/Au/?s/?t-=r+r->r.?s+

r)=p*?r*?q+?q+?r*>q(v*=u+>v+>v,?w-@x-@w,@u-@u/Bw/Cx/Cx0Ez0E}-Bz-Ay0B{.Az/Az/B{/D{.Cx-Au,Au,?t->t->s.=t0?v,=s0@v-?u,@u0Dy/Bx/Dx.Dw0Dz0Dy0Cv1Bv2Cw1Cw4H{4H{4H{3Gz3H|4K|3Hz3Gz5I|4I{4I{6J|5I}2F{4I~6K5K8K8J8K9L:M8L9M9M:MS?T=RR@T?S@T?TAUAU>QAS?R;P>SS?T=R=R@U=R>S?T?T?T>S>S?TAV?T@U@U?T?T>S>S?T@T@S>QCV@RBUCVCWCWAUAUAU@TAU?SCW?SBV@TBW?T>S@UAU?Q?R=R?T=R>S?T@U?T@U@T>U?U@T@U@T@UAV@U@UAVAVBW@UBW@UAUBW@UBVCXBWEYFZEYEYG[FZFZG[H\H\J[NaM_M_K]L^L^M_M_L^K]M_L^I]FZH\J^G\I^K_L^J[M^K\J[J[L]M^M^M^M^K\M^L]M^N_L]IZL]L]L]L\K\I^I]H\J^K_K_I]K_J^K_L_I_K_I\L]M^M^K\L]M^M^K\K\J[K\K\N_J[I]K^I\J]L]L]J^J^J]K^I^J_K`K`J_J_J_LaH^LaL_M`L_N`O`L_M`NaN`MaKaK`LaLaLaLaK`K`I^H]K`I^F\H[H\I\I\I]I]K^K[K[K^I\K]M^K]J]M`K^J]J]K^K^I\K^I\M`K^I\J]K^L_N]O`N^N_M^N^J_J^L`L`J^I^I^I^I^J_K`J_I_HZI^LaJ_I^L_MaK`L^K]K^K^L_I\M_K^J_J_J_J_LaLaK`J^MaM`L`NbKaMbK`MbMbMbK`J_LaK`LaMbLdKbMeLcLcMdKbLcMbNcMbOdOdOdMePgNeNeNeMdOfMeMdNeKaKaMcKaMcMbOdNcPePePdMfQgNeLeLfMdMdNePgOfQhQhPgSjPgOfQhSfPeQeQfQiRjQiRiRiQhQhQhRhQfQfRgUjShRgRgShVkShUjTiVkWlUjVkShTiWlTiUjUjVkTiUjUjXmVkWlWkVlUmXlYnWlXmYnXmYmXoXoWnUmXoXoWnXoUlWoYnZoZoYnXpXoXoYp\rZn[oYnZpZp\qZoYq]sXs[uXs\t_q_t]q_s^r_t_t_t_t]r]r^s^s_tav_t^tascvascv`wavcxcwavdyezdydydydyezg|f{f{ezezi~f|k}j|k~j}k~kj}mnoutwy~}vvuyŒėɞǚĝȩ̴ʾϷ̱ȭ̡˘˒ʐ~twssoplmlloollkj~j~h|k~hg~f{f{gf~hf~iii|hh~h~hi~iijj~i|lnmkljjhjmjqorqtstwyyvwqtqqsruutqtuswvwz{y{{{}||ɂ̂{}̀ʀˀˁ΁ρ̆ͅΆυ͆ωшφЈԈӉАӒ֖חۙٚٗڕޗ嗭똰餴젵&7c(9h(9g,:i)7f(6e'5d+9h(7f)7f*8g(6e(6e)7f(6e(7f'8f(9g&7e(9g&7e%6d'9f(8f)7f(6e'5d,:i(6e+9h(6e'5d(6e*8g*8g)6e)7f&8f(9g%7e'8f'7e%6d)6e&4c(6e'5g(6f(6e(6e+9h+9h-;j,LyL{=L{NPP?Q?Q@R>P~>P=P>P=O=L~@PAOAO@M~?M}?P~P~M;L|;Ix>K}:J{;K}9I|9J|7J|8J|8J}1Cv5Gy2Dv3Ew1Cu1Cu0Bt0Ct0Ct0At4Ey3Dx2Dv3Ew1Ct/As2Dw1Bw2Cw2Cx0Av/@t.?s.?s.?s1Aw2Cw2Av2Dy1Ex0Dw,@t)>q,@r)>r*=t.?t,=p->q->s+=r+@u(=r)>s*=t->w,=u);s)=v*=x,?y*>u(;s*=u,?w,?w,?w+>v+>v-@x,>v/@x.Ay-Av.Bw.Cx.Bw2Ez0Dy0Ey/Dy/Dy1G{/Dy/Ey1Ez0Ey2F{1Ez.Cw/By2Bx/@v0@v0?w1Cx1Bx1By.Cw/Cu0Dw0Dv1Ex1Ex0Ew2Ex6F{1Bv5Gz4I|4H{4Hz4H{4I{2I{4J{4H{5I|5K|5K}5I|4H|;O4H~5J5J5K7L6J8J8J9L9M8L;N=LP;O:P;P;P;P:O:O7L7L;PP>Q>P=O>Q>P;N:L>R;P=R=R=R?T>S@U@UAVBW@U?T>S?T:O;P;P;OQR@T@T@T>R?S@T>QCU=Q>T>S=RU;T=T>T?T>S?T>S>S>S?T>S@U>S?T@T>S>S>S>S@TATCVBU@SBUASAUBVAUBV@TAU?SAV?SBVBV?SAU?T@U?SBU@S?S=R=R?T=R=R>S@U@U?T=U=T?TAV@U?TAV@UAVBW@UAV@U@UAVAVAV?TBWBXDWDXFZF[EZG[H\H\H\FZH[JZJ\K]K]K]M_M_I[L^K]L_I_J^I]J_J_I]K]L^J`K^O`J[L]L]L]L]K\L]N_L]L]K\M^M^K\K\L]K\K\J[K\K\I^J^K_I]J^K_I]J_J_H^I^I]K^H\L\K[J[K\M^L]K\M^GXL]L\K[K\L]L^I\H[J\I\J\K^K]J]I_I^I^J_K`J_KaJ`K]L^M`L_J^L_O^L]NaNaK^K^L^K`LaI^K`J_K`MbK`J_I^H]I^H\J]L]K\I\H\J]J]GZI\I\GZJ]N^M^I\J]H[J]K^K^K^H\I\O`L^J\K^J]J^K^M^L^L]M\L^H[K_L_L`J^L`K`J_J_K`J_I^I^J_K]I^J_K`K`L`K`MaK`L`L`K_L`L`K_L`L`NbK^J`K`MbMbMbMbK`KaLaObNbL`L_J`LaMbNcLaMbMbLaLdKbKbNeNeKbKbLcOcK`MbOdNcPeMeLcKbNeMdMdOfNcMbNdKaMcMcMcKaNcNcOdPeOdNcPgOfNeMgLeOeOfNeOfNePgPgOfNeOfRiPgShRgOdRgPeQfTiQhRjQhSjSkRhQgPeShUjUjTiShUjTiVkTiTiTiTiVkUjVkUjUjUjVkUjWlWlVkTiTiYnVkVkYmWoZnWlXmYnYnYnWkVnVm[oYnYmWlXpXoYpYoWk[pZnYnXpXoXoYpYr[oYn[p[o\oZpZoZp\t^t[v]xZr]r]p_s^s_s^r_t^s]r]r]r^s`u`v`tbu^qat_raubudwavbwbwdydydyezf{f{f{f{f{f{f{ezezf{cyh{i}h{mlk~nmlpqsw{‹Ŋȁrrrqsr͝ə›˧˰ɼѸϲˮɥ̣ҫܟ~yvo~rm~j|j|i~i}j~lj~j~kj~i}h|j~hf|h~g}g{ffefih~k~i}kliklii~kjlnlolkllikqoopstrtssvwuxqsqqpqtvwwsvwuvzz~~x}}Ȃ΁ɂˁ}~}~ˁ~ʀˀˆ΃̆ЇΈЇЅ·҅щЏґՖؗښؙٖؒےݕ昲헲씯꯿ힱ훱'8f'9g(8f+8g,9h*8g'4d*8g+8g*7g*7f)6e)6e)6e*7f)7e'9c'8e(9g'8f'8f'7f'7h(7h)6e*8g*8g+8h)6e+8h)6f)6e(6e'5d*8g(7e&8f(9g):h'8f&8f&9g)7f)7f(6e(6f)7h+9j)7f*8g-;j-;j-:j.>l/@o->l0Bp/Ao1Bo1>m2?n0>m2@o0?o0@r1Ar4Du5Du7Du8Fw:Hy9Gw7Et7Et8Fu6Ds7Et8Fu:Hx;Ix:HxL{?M|?M|@N}?L|>K|=Kz?L|=KzL{;IxP>Q=O=O}=O~=P>P?N=N>NBO?M~AO@M>O}>O}?P|;Lx;Kx=Nz=My>P{:Ox:Nw:Mx:Lzv,>u,>s,=r,=t/@x1Ay/@w.?v/?w.?x,@x-?x-@x.Az-@x,?w-@y.Az,?v.Bv/Dy0Ez0Ez.Cx1F{1F{1F{1F{0Ez0Ez1F{1F{4H}5I~4G|2F{1Ez1Ey1Bx1Bx3Ez4E{5F|3Dz4D{2Dy2Fy2Fy0Dx3Gz3Gz4H{4Gz6G{6G{5Fz4I{6J~6J7K7K7K}6J}6J~8L8L7J}7K}7K~7K6J~7L6K4K6J5J7M6J8J8L8L8L8L:N9M8K9M9L9L5I5J5K5H6I6I9K9J:J:J:K;L:K:M8L:M:L:N:N7K8L9M9M:N:MO=PQ=PS?T?T?T@U@U=R>S?T=R;PQ>R?S=Q@T>R?S>R?SBU@SR;ST@U>S>S?T>S?T=R?T>S>T>T=T>S>S?T>R>Q>QBU@SBUBUAVCWAUBV@TAUAUETEUCTAVAUBU@U;P=S>Q>Q?Q=S=R>S=R>S=R@U@U>S@T@U=R>S?T>S?T@U@U?T@UAVAV@U@U?T@T?TAVAVDXCXEXIYIYHZDYEYG[G[G\G[I]K^M^J\L^K]J\N`K]L^I]H]J]L^N`K]L^K\L\L]L]M^N_K\IZJ[M^J[J[L]K\K\L]K\K\L]K\L]L]K\L]K]G]J_K_K_I]K_K_K_J^H]K_L`I]J\L\K[K\L]M^M^J[M^K\IZFZGYJ[IZEYGZH[H]I\I]H]I^H]I^I]H\H]J_J^J]I\H\H\K_K_N_L]M^N_L_L_NaJ^K]L^J_LaLaK`LaK`LaMbH]H]I^H\K\L]J[J[IYK\H\I]K^I\H[I\L\K]I]I\I\MaJ]J^J^K[L]N_J\J_L_L_K]M^O`K\L^M]L^I]J]L`J_I^I^I]I^J_J_I^J_J_J^I^I^J_K`LaK`K^L`L`J^L`L`K_I]MaJ^NbL`LaK^LaMbJ_MbLaJ_J_NcMaMaMaLaN_M`K`NcK`J_JaLcMdOfLcLcMdLcMdKcMaLaMbMbOdPeMeOfNeMdMdMeNcNdMcNdOdLcLbLbLbMbMbPeQfOdQfMdJaNeQgPfPgMdQhOfOfOfOfOfPgQhNeRiPeRgQfPePfQfPeRfTjQiSkRiSiRhShShTiUjShShShVkVkUjVkUjUjUjVkXmVkUjWlUjUkWmVkUjVkVkUjWlUjWlWlWlWlXmWlWkWlYpWnXmYnVkWlXmWoWnYpWnWoXoXoYpYpYpWnYpZnZoYn^p]q]qZoZo[pZrYq\rZp\t]r]q_s^q^q^u_t_t^s_tav]r`uav`tbvbucv_rbubucvdybwezcxdyf{ezf{ezf{dyf{ezcxf{ezdycxewfyj}j}j~ki~jpli|k~k}pz̎͆wupokj}jo~Ě̛ƛàǦˬ̳ѹְܶͭͲ٧ӑ|rrqpokk~j~i}kkj~kkh|j~j~g{g{h{h|f|h~e}eegijg}ilijh~iikjjkj}mmmkkmlqonmrpprrqsstttsrooortqttuuwvxxy}~|ǁ˂ˀʂ̀Ɓ~~~~ƀȀ~ˀ̂ˀ̇υ΄͇ЇͅΆ͈҄ЎЗԕח՘ԗӗՖ֒ؔᚱ键瓬顳흭蘯)6e)6e+8g)9g'8f'9g'9g):h(:g'9f&8f'9g&8f&7e(:h(:h):f):g):h'8f&8e%5e'7h&7h%7e(:h(:h(9g(:h):h(:g'7f*7f,:i)7f)8f):h*;i*;i):h'8f&7e)8f,:i(6e)8g*8i+9j*8g,:i.k/@k1An5Bq1@n4Ap0?n1?n2@n3Ap0?n0@q1Bs5Ev7Ev8Fw7Ev9Fx;GwLx>Lz>L|@M|?N|;Nz=O|M{@N~=K}>L|@N}=Kz=Ky;L{9Iz:J{:J{N>NAO@N~@O~AOAO>L}?O~?P~=Nz=Nz=Nz=NzP{;O{;M{9Kx:Mx;NyO{>Q|=P{=P{;Mx=N{=N|>K{=K{PN;M=Q=P=P=PSS=R>S>S@UPR?S?S?S=Q?S?R>S>S=Q;Q:QSS@U?T>S?T?T=T>T=T?T>S>S@U?TBT@S@SBUCVBVBVCWBVAUBVATETDUBSDUBXBV@S@U=R>S>P?R?Q=S>S=R>S>S=R?T>S>S>T@TAV?T=RAV@U@T>S>T>S?T?T?T@U=T=T?VAU@UCWCVFVFWHYHZEYFZFZFZH\G[J^K^L]K]L^M_J\L^OaK^J]M_L^J\L^K]K^J]K\N^M^J[K]K]M_M_HYIZL]L]L]IZL]L]L]L]L]K\K\L]L]N_M^M]K\LaI]K_K_I]H`I^I_I]I]K]L]L[L]K\L]IZIZIZJ[HZGZFYEYEYEXEXF\H]H]H]F[F[I]H]G\G_I^K]I[I\I\M]M]N_M^N_L]N_M^J^K_N`N_J^L`LaK`LaLaLaK`K`K`I^J_J_H]K]L]J[L]L]K\L]K\G[J]K^L^L]M_I\I\I[M]M]M^M^M^N_N_J\J_J]I\I[J[L]N`M\L\M^J]I]G]G\J^J_KaH^H]J_I^J_J_I^K`I^J_K`J_J`L`K_L`K_J^H\K_L`J^MaL`L`NbK_I^I^K`J_K`MbK`K`L_K_L`K_L`NbH]J_LaL`JbKbH_LcLcLcJaMdMdKbMdNcMbNcOeKcNeNeLcLcMdNeMcNdNdOeOcOdOdOdOdOdNcOcOfNfMdOfQiNfNfNfOfNeNeNeOfOfOfOfQhOfQhPgPeQfQfOdPcReReOeRgSgShNgQgQhRgTiTiTiShRgRgTiShTiUjVkUjTiTiVkVkTiTiWiUgWiUjUjWlVkVkWlWlYnUjYnZoWlWmYqWnYpWnUmWlYmYmXmWnWnYpWnXnXn[qWnXoXoYpXoZnYo[l[n\p\p\q\q[p[o\q\p]r[p]q\q_t^u`v^u^s_t`u_sav`u`u`v`sdt`t_tatatbuewbvcxcxdyf{f{ezf{ezdycx`u_tavcxavavavbuexgzdygzfzdyh}f{cwbvbwexkzqˌun~slj|h{j{h}jovˢѡˣϩҢΡѣϯϰʺס{vupoj}i}i|g{g{fzh|i}j~i}j~j~i}fzi}h{g{i}i{g~d}e{ggh~k}ijiiijjih}h}i~i~mklonnjqonrqnqnpoqqoqrrrpnmqqqtsusrwuvx||ŀˀŁƄȂʃ˂ʀɁǀ˂ǃ~}́ɂ̂̈́Ά΄͆Άˇх΄͍БҖӖӗ֗ӗԗϗ֖דޗ曰옮꘯箽㵷ԫᜲ)7g)8g+9i*9g&9g'9g(9g'8f):h):h&7e):h%6d&7e(9g$5b*;f'8e):h(9g&7e%6f)9k'8h(9g(9g'8f*;i(9g(9g'8f'7f,9i+8h+7g+9i*;i(9g);i):h(9g*;i*:h+8g+8h/:k.9l-9i-;j0>m/=l0>l3@l1Am/@l1Bn3>m6Ar3?o3Bq3Ap1?n1Bp/Bp2Bt5Ct9Gx:Gy9GyLy>Ly>N|O};M{M{;L{:J|I|=L}>O;LyP~>O}=O|?P~=N|P|;My;My=O{:Nz=P~;Mz;Ox:Mw;MwOz;Mw8QyPS=R=R?S9N=R9N;P;P9N;P:O:O;P;O=O8N:O:O:O;Q;P;P=OR?S>R=Q?S=R?T=R;SS>S>S?T=S=S>T>S@UBW?TAV=R?T?S>T=TS?T?RAT?R@SCVBVCW@TBWAV@VBTCUBSCTBSEWBV@S>T>SP?Q=S>S=R=R>SS>S>T>S?UAW>R>S?T@V?V>S?T?T=R@U?T?W>V@WAU@UBVBVDTFVHWFWDYDYDXGZGZFZH\H\L^L^J\J\L^K]K]N`L]HZL^M`L_L^M\KZL]K\M]M^O`M_M_K\K\K\J\J[J[L]L]K\L]L]L]L]K\J[K[K]K[O`L\MbI]J^J^K_IaI^H^G[G\H\I\I[M^J[L]HYJ[IZJ[FWCYEWEWDVGYFXCYF[F[F[F[FZE\E]BYE\GZHZH[I\K[L]L\N^M]L]QaN_O`K_I\OaO_K_J]K`LaLaJ_K`J_I^I^I^I_J_G\J\M[L]M^M^M^K\M^M^I^I\K^N]J\H[L_I\N^K\L]K\L]L]N\N^J`I\H\J\M^L]M[P^L\K\G[L^I_J_J`I_I`J`H_H^G]I_I^J_I^I^J_J_I^K`J_L`K_NbK`I^L`K`MaMaK_K_L`I]I_I_I^I^J_J_J_J`M`K_K_MaNaL`NcK`KaLaJbKaJaMdLdJaI`MdKbNeJaL`LbLaLbLeNeMcLcMdLcLbLbMcKaNdOdOdOdNcMbMcPaPeOfNfNfLcNbNcPeOcNeMdNfOgOfPgOgNfQhQhOfQhQePdQfPdRdRdReRdQfQfRgShQgQhRgShQfQgRgTjRgShShUjTiUjUjShTiTiVkVkUjVhUhWkTjWlWlTiWlUjWlWkVlWlXmWkYnXpTnUnYoXmXm[nZnZoYoWnWnXnUpUpTnVqZpWoYpWpZnZpZl\o]q]oZp[p[pYoZoZo]q\p]t\p]s^u_u_u]r_t^s_s]v`xat_ubvdubvaueuevbvewfzeydycxcxavcxcxav_t^s[p[p^s_t`ubw`tatcvbwatcw_ravdxcxbv^waudxjxl|r~q~kvhwi|fwdyczezizl|l|h|k}qĤ٧֞ϏƌƓƶ髰xtomj~g|h|h{f{h|g{eyg{i}i}kj~h|h|g|ezh|h|h~g}eeecikjkijmjg}ij~g|jh~ij~lmmpomnqrmmmjpooqpopmlknkmprosusttwxz|ȁˁƀȂ~ÃʃɄ΄̂̂˂ʂʀȀɀɀʀʀ̀̀̓˅΅Άˆ̈ҋЏӔҚԗИӘԚՖЛՖ֚ᘭ皮횭혭瘰*7d*6c*6c(6b*7f*9h&7e):h(9g'8f'8f&7e(9g'8f(9g'8f'8g(9g(9g):h'8f(9g&8e'8f*;i'8f(9g'8f(9g+Lx=Kx>Kx=MyMy=Kx=Ky>K{>L{=LzL};Iz>Lz;Ly:Ky;LzK|?M~>L}BO>L};Lz?P~=N|>O~?P~?P~M~@M~AL|@L|?KzCQAN~AO}:Kw=My?LzBO|=KxAM{?N{>O{OzL{;IzP~=O|=O}?R|=O|Q:MR>R=Q@T=QAT=Q>SS>S=R>S?T?UT>T=S?V@U>S@U@U=R=R>S=R=R>S@T?T>S?T>S?TBU>Q@SAVDXCWCVDUDTDTDUCTBSBSDUCSEUDS>R?T?US>S?T>S=R>S>S?T@S?R>T?S?T=R=R@U?TS@U@T@T?T@U@T@VAXBXDYDYEZIXGWEZDYE\F]EZJ]L^K]J\L^L^K]K]K]M_M_M^M\L\M\L_M_K]L^K]NaM^K]M_M`M\K[L[L\K[N]L[L\L]L]K\L]L\L_L^K\K\H^L`J^I]K_K_K_J]L`I]J^GXM]IZM\K\IZJ[J[JZHXHWEWEZBWBWCYCXDZDYDZC[D[D[C[D[BYD[D\D[E[DZFZN^J[H\J]K^K_J^L_J]J]N^N_N_L_H\H^K`K`K`K`J_J_K`I^J_I\J]M]M^M^L]K\K\K\K\N_N_L]M^M^M^K[I\J^K\L]L]O_M\M[L]L]J]K^K^L\L]M^N_L_M^K\M^J^K_K^J^J\J_J_I]I[J]J_K_H]I^J_H]J_K`KaL`K]MaJ^J]L^N^N_J`L`K_K_K_K_J]K_I^K`J_J_LaJ_I_I^K`J`J_LaMaMaL_J_KcHbLaKaJ`KbKbKbJaMdKbMaLaNcMaNbMeIcOeMdLcOeLbLbLbNdOdNcNcOdNcLaOdOdPePeOdOcOdOaObRePdPdOdQeQfPdQfQfNgPfQhPgPhPgRfPeQfPePfQfPePePePeRgQfQfShRfUfTfTgShTiUjTiShTiTiUiVkTiTiTiVkUjVkWlUjXmVkWlXmWkUlWoVkWlVkXmVkXlUk[oYnYn[pWlVkZnWoXoWnXnXnUqXmXoXoXlXmZnYnYm]q`t\p\q]o^p]o]n^o[q\q^r]s]s^s^s]r]q`s`tauatauavaxaw_v_ueufvctfwfwdydxezbw^s\q]r^s_t^s^s^s]r]r]r^s_t_t_tcrdwcu`v`r^u^u^t`s_sauewexdxhwgwgweucvcwdydxf{g{fzcye{f|h}i|qۜۍ̄ǁ‘ݳӑ{sli~i{k}fzg{dxfyeyf{dxi}i}g{h|i}i}i}izi{hyg|h|j~h~g}h~h|ljijkih~h~jk}k}h}i}i}ki}klmnnmoqponoopppmpqplli}j~korstvvvvzx~}ƀǃ̅̆ͅ΅ȆNJˈ̉χˆǂǁąʄ́˂̀΁}Á~Ȅ͉̈́ͅΈφχόЖҚϟПѝҟ؜ל֢ز椱ߖ☬ꗯ嘬瞯௺ܚ镯(6c(6c)7d(6c)7f,:i(:h(9g):h):h):h(9g(9g'8f&7e(9g):g):h(9g(9g(9g'8f'8f*;i%6d*;i%6d):h(9g(9g);i(8f,;i%7b,=i&7d*;i+L}>Lz=K{>Ly?Mz?Lz?Lz>MyMw>Kx=Ky>L{=Kz;Jy8Iw9Jx8Iw6Gu:Jy8Hy9Iz;Iz;Iz?M};HyM>K}=K}?M~?M|@M|@O}O}=N|>O}?M~?M~@M}>JzBN~@L|?K{?M|?O~;Mz>Ny?MzBP}@Mz?N{=O{>Ny>Oz=Pz=Pz=PzO{>O{N=N{=N|>O~>O}>M{?Lz>Ly;My?P|?P|?Q|=Q|ARAT@T~?R}>Q}S=S=TSRS>S>S>S?T?T=U@W=S=Q?S?T?T@U@U>S>S=R>S>SS?T?T>S>R@RBVCWCW@T@RDTDUBTDTDUARDTCTAWBVBV@US>S=R>S?T=R=R>S>S?Q>Q>S?T=RST@S>S>S?T?T>S@U@U?TCY@VBWDWEYDXFXGWEZG[G[EYFYH\G\I[I[K]K]L^K]N`L^L^L^M^N^M]L^M_L^L^M`L^K]K]K]K]K]K^L^J]IZK\K\K]L]K\K\L]K\K\J]K^J[L\M]H]J^I]J^J^J^K_L`G[H]H[J[K[K[J[HYI[G[FYEXFYDVBWAVCXCXBWCXAVAWAXAYBYBYAYAXAXAYCYBYCWCVDWFZHZJ\I\H[H[K^K^J]I]J]N^I\K^J_K`I^J_J_I^I^I^I_K_I\J\M]M^L]M^N_L]N_PaN^L\M^L\L]K[I[J^L_N^K\K\M[JZJ]H\I\J]H[O`IZM^N_M^N\M^L]M]J^I]J^K_L`G\H]K^M\M^K`K]I^J_J_I_I^J_M`J^K_L_J]K^M]L]L]L]I_K_K_MaK_MaM`H]J_LaJ_K`J_I^K`K`J_J_MbL`I]I_K_LcKaLaMcKaJbJaLcLcIaL`MbMbLaLaNbLdNeNeMdMdKcMbMbLbLbMbOcNcNcMbNcNcPePdPeNcNcNcOePbObRgOdPeOdOdPeQfQfQeOgNfRjPgOcNcOeRgPePeQfPeRgRgQfPeRgRgQfQfRgTeSfSiSiTiRgTiShUjViUjVkTiVkUjTiTiUjWlWlXmUjVkWkTkVmVjWlWlWlXmVkVkWlXmWlXmWlWlWlZmWnXoXoWnXoWoWnWnXmZoYoZo[o[oZn[p\o_o]n]n`q_o_p_o^q_r]s]r]r_t_sau`ubv`tau`tav_uaw`veuductfwgxfzeycw\sYn[pYnYn[p[p[p[pZo]r^s^s`u_tbwcucucu`u_s]q^rat^r_t`udvatbu`s`t_q_rarav_rcyizgzf|e|d{fzg}j|sLj˄Ň˖ӳืϓzplg|j}h{i|eyeyfzfyeyeyfzi}i}h|fzg{g|izhyizi}h|i}i|i|j~h|j~hih~h~iig}hj|k|j~j}j~j~i}mkmnmmnmnnqnqqopmpnnkj~lkjmmrvwxvwz|{~ƁɁɅʅˇ̈̉ˇɋω̊Ίˇˈ̆ʄɆʃʀǁȁɄ΁˄˄˄Ɇʅ̈Ί͘ҠӦҠϝ̟̘̤͜ݗ癭蛯稵㗮)7d*8e+9f+8f+:f*;g(9g'7e(9g*;i):h'8f):h(9g(9g*;i):h*;i*;i):h*;i):h):h*;i+l+=k,l,=k->l+L{>L{>Kx>Ly>Nx>Mx=Nx=Ny>Ly>Lz?M|>L{;Ix9Iw8Jw7Hv7Hv3Er8Iy7Gx:Gx;Iz9Hz7Gz;K|6GxJz?M|>L{?M}?M}>Kz=Oz?P{>O|>P~?P~>L{?L}?K~>Iz@K{@L{?M|AO~>M|;Mz>Ny?Ly?M{?Nz@Ny=Ny>Oy=Pz>Q{Q{O{>O{=Nz>O{=N}>N}?L{=Kz?L{>M~:Jz;Lz;L{O|>O}?P~>O}>P}?Q|@P|AO{?NzBQ|BP{?P{@Q|@Q|AR}BS~AT~AUBUCVAT>P:Q:O=N>P:L~8K|8J|9J8I~:K~9K}9J:K6G{6H{4I}3I~4H~4C{4D{4F}3G|6J5I}3Gz3H{3I{2Hz4J{4J{4I|2G}4H~5J4I~1F}2G}3G|3G}4H~4I6H}6G{8I7H~7I6J7K5H~5J~6J4I}8K9J9J6H~:K;L;L9I}:J~;K;K8I~9J9J;L;L:KRR?S;Q>S:O=R;P=R>R=RU=S?T?T@U@U@U@US=R=R=RSS=R=R@U>S?T>Q@UBVBWCVCTDUDTDTBSCSAV@SAS?U@T?S=R=R>S=R>S>S=R>S?T?T>S=R=SS?S?T=R=T?U=S@U>U=R=R=R?T>T@V?UBX@VBXCWEYEYDYF[DXEYFZFZH\H\L]I[I[K]K]K]K]K]J\J\J\K\K[I\L^M_K\M_L^M_L^L^L^L^M_L^J\K[J[K\K\J[J[IZJ[L]K\L]J[IZJ[L^L]J[J_H\I]H\J^K_G[I]I\J\IZIZIYIZHYIZGZGZDXDZBWDYCXBWCXBWAV@UBVBWBWAVBWBWBWBV@W>WAXAYBVDYDYEZEZF\G\G]I^K]K]J]J\G[I[J\I^I^H]J_I^J_J_J_I^I_I\J^L_L]L]L]PaO`O`M^O`K_L_J^J]I\H[L\L\L\L]M_J]H[I\H[H[K]K\L]M^N_L^N\N]M^M]K`K^K_J^K_K_J_H]J^J^N^J_K^I_I^I]J^L`L`J^L`L`J\J\N_K]M^L]N_N^NcK_L`MaJ^J^K`NcJ_J_I^I^J_K`J_LaI^J_H]I^J_J^KbKbLaIaKaJaLcKbKcK_MbOdMbMbLaNcKbNeMbLbLbMdMdLcMdLcNfLcKcNbMbOdOdPePeOdNcNcOdOdMcPcNcOdPeOdQfQfPePePeQfOdRfPeQfQfReQfPePePePePePeShQfQfQfQfShRgRgVhUgUgSfSiRgTiUjTjTiShVkShTiVkVkVkUjUjTiZoXmXmUnVlXmXmXmWlVkUjWlVkXmXmYnYnXmZoXmYqWnXoXoWnWnYpYpZn\qYnZoZn]qZn\p[o^n^o^o]n_p^o_p_p]q_s`sbv_s^r^rcsbvaubvbv`t`uavbwetevgxhyhxf{btYnXmZoXmZoXmYnXmYnZo[p[p\q^s^s`udyhzf|bx]s]q\p[p`r_r_r_r_q`sas_q]o]q]q]q`t`sbxfyfyf}i~f{g|g}k}lk~mtѠںވzsk}g|f{h{gzfyfzfzdxeydxh|fzdxeyg{eyfzh|h|g{i~i}h|i}i}h|i}h}i|i~hf|f|g}g}g}e{j}i~i}i}kjlljkmmnnlntnlpnnoprnkj~k~iiikooswvvvy{z~~ƀɄɃʈʈˊˊʇɇ̇ɉʉʎ͎ˍǍˍ͆ɄǁȂȄʅ̄˅ɄȇɄɊ͋˛ҥҮԫҨϠȜɛʛɬިᓦܘ陬韯᭺얯앱)7d*8e+9f*8e)7d(:g*;i);i*;i*;j*;j+l,=k,=k*;i):h(9h+l,=l,=l,;i.j*;f+>i*=h,?i*=i/@n/@o/>l0>j/=j0>k1?l2Am4An4Bo5Co3An5Cp7Fr9GqMvNw?NzLzNx?My?Mz>Ly=Kz=KzL{>K{AM{ANzAN{@Mz=My>O{?N|@N~@N|@M|?KzM{?M|?M|AN}>L|>P}Mw@Oz>Oz>NzQ{>R{=PzO?PKy=Kx>Kx>Jy=Kx?Mz>Mz=O}O|AP}AN|?LzBO}CQ|DP|DQ{CR|DR|FU~EV~CU~DUEVCUAU~BVAUBT?P?Q=OS>S=R=Q;P8M;P9L9K9L7J8K9K:M:L8N9N7L9K8K8K:M9LR?US=R=R?T>U?V=S@U?T?T>S>S=R=R=R;OSS>S=R>S>S?T>S@TAS@TAUDUBSDTCSCTBRBW@T@U@T@V?W=R=R=R>SS>SQ?T?T?T?U=SS@UAX?U?U@W@WAVEXDXFZDXEYEYFZEYJ^H]EYJ[I[J\J\K]J\K]L^J\J\J\I[L^K]K]L^L_K^M_I[K^L^J\L^M_K[J]J]J[K\K\IZK\J[J[J[K\IZK\M^J[J[J[K\M]I[H]H\G[H\J^J^J^GZH[J[IYKYI[J[HYFYGYBXCXBWCXBWBWBW@U@U?T@U@UBV?T@U@U@U@UAW>X?XBYBYCZCWDYEZF[F[F[G\F[F[H]G\I[I\H^H]H]I^H]H]I^J_J_I^J_H]J]L_L\M]N^N_N_N_O_M^K^L_I\M`J]J]M]M^H\GZH[I\J]J]H[J_J\M\M^K\M\N\N]N]L]M^LaI]I]J^J^H\H^H\J\K^I]I\L_I\MaMbK_MaJ^L`L`L`L_J]O_N_L]N_L]L^I_L`J^NbL_K_NbL_K`H^J_I^I^K`J_K`K`LaK`I^K`J_MaJbK`LaLcJbJbLbL_KaLaLaLaNcLaNcNbNcLbNdMcLbKbNeKbOfLcNeMdMeNbNcNcPeNcNcOdNcOdOdNcNcOdRgOdPeOdOdOdOdQfPeQfPeQfRgRdRdPeRgRgRgRgQfRgPeRgRgRgQfQfTiQfTiUhUhSfSfRhShRgUjUjWlTiVkVkTiTjTiUjVkVkUjVkZnUmVnXmWlYnVkYnWlVkVkVkZoXmXmXmXm[pXmWoWoWnWnYpXoWnXmZoYnZn\o[o[o\p\p`o`p_p\m_p_p^o^n^s`tau`tau_sarcs`uauaubvaveueufwevgxgxfvcwbtYnWlWlVkWlWlVkWlUjYnWl[p[p\q^s_t`ug}mlbw^sZo]q\o^q[n]p\o]p[p[p\r\r\o\o]tas_ucwfxgzfyf|ezh|h|h|k~k~h~np䟟|so~k~h|g|i|fydwdxeydxdxcwcweyeydxg{eyeyh|fzh|h|g{j}i|h|fzh|h|j|f}e{f|f|f|e{f|e|fzf{i}h~jkljjllonnpnknnnonomnnkljkkiknpotvruvz{}Džʆ̉ˊʋ̈Ɉʈʆljȍˑ̕ɔȔɐˋɋ̉ʉʈȊˇ˅ʇˈ͍̈̉ȚϩԮҫ̦ʥʤ̨УОџٗܚ㜫⡯׶ꖭ锱앰+9f+9f*8f+9f+:g):f):g);i+l+=l+=k-?m+i->i0Al.@j-?j-?k0@k0>k0?k.?k,?k+>i.Al.Al,@l.?l.@l1Bm1?l3An3An5Cp4Ao4@n4Ao4Bp5Dp8Gr9Ht:Hr?Kv>Mx=Lx?Nx=Lu>Mv=It?Ku?Lu;HqJz=I{=Kz?M{?My=Lx=LvAP|>Ly@N{>Ly>Ly=KxL{>Kz@L{?LyAMz@N{@N{>Kx>My>Nz?Lz?M|>L{=Kz=Kz>L{=Kz@M|>M|=N|?P~;Lz=NzP{>Oz=NyQ|?Oz>Nz@Q|P{?P{>Oz?P{?P{=Nz>O{=NzO}?P~?P|?N{@N{@N{?Mz>Mz?Ny=Lw?NyBP{AN|?LzDQ~CR|AQ{BR|BR|DS~ER{EQ{DSyETzDSyFT}HVEU|DVGWEVEVBV@S~BU=O|>P=O=O=O;M~:L;L:J}:L~:L~8J|;M~9K}8I|9J5Fz7G|8I}9I~6H}6J}7K~6I|3Iz6M}5K|5L|4K|3I{3I{3I{4J|4K}3J{5H{4I|3Gz6J}3Gz5H{5Fz8I}8I9J7H~6G}8H~8J5J5J7J9J~=O;J~>N;K;L;M:J~:JS>S>U?S>S;P?T=R?T=R@U=R=S9QS=R=R>S?T>SS@UAT?SBUDUCTARETBS@U?S@S?T=S?T?T?T=RSS@T@T?S?S?S?TT>SATAU?R@T?U@V@V?UAW@V?UAWAUBVCWAUEYDXDXG\EYI]G[H[J[K]K]I[I[K]K]J\J\L^L_J[K[J[J[K[J\K`K^K[K\I[M]K\K\KZL\J]J[K\IZJ[K\J[K\J[J[IZK\J[J[K\J[K\K\J[K]I^H\I]K_H\H\J^G[H[J[JZIYHYIZIZEYDWDZCXDXDVDWDWCX@UAV?T@U=U=TAU?T?T@U@U@U?VCZ@WCZCZCZDXCYC[D[G\F[F[EZH]G[I\HZF[G\G\F[G\G\G\I^I^J_I^J^J\I[J^K^J\L`K^K_J]L_K^K^K^J]J^L^N_L]I\GZI\J]J\I\J_H^I]L_M]M^L]M]M[L[M^L]K_L`J^K_K_J]H]G\G]K^K^J]K^K^J^MaMaK_L`J^K_I]L`K]L]M^N_M^N_N_K`K_L`K_J`L`L`K_M`MaL_KaJ_K`K`J_J_K`K`J_J_J_LaK`LaJ_L`K`K`LaK`K`K`MbLaNcMbLaMcMbOdLaJ_MdMdMdMdMdMdMdNeLcMaOdOdOdNdOdOdNcPePeNcQfNcPeOdPePeOdPeNcPeQfQfPeQfRgQdReQfPfPeQfQfRgSgQfSgShRgRgOdQfTiTiSfSfTgTgShUjUjShSiTjShUjWlUjUiTiTiUjUjVkWlXmWnWmXmWlYnVkVkWlXmWlWkXmXmXm[pZo[pXmXmXmYpYpYpXoYp[pZoYnYnYnZn\p]q[o\q[o_o_o`q^o_p_p\p]qau`t`t]qbrct`uaucwbvdtfvevfwgxfwfwhy`t\nWmVjUjTiUjVkTiVkVkXmXmZo\q^s\q]r_tcxdycx]r[p\q^o[n[nYl\n\oYlYo\q[p\r[rYp^t]sbwcyfwhzfyfyg|f{fzh|j}g{h~kn~uu{p~izf{f|fyfxgyfydycwdxcwbveydxcwfzdxdxfzfzfzi}h|h{f}i~g}h~g{h|h{bze{e{e{e{f|dzf|fzi}ji~h}jjlmlmllmmnpnpnommmmkj~k}jkh~kkmrqrtvrxzy~~ƁɂˈˆˈˆƉɈʈʆƊȑʕ̜˙ǗŘʑʎ˓˔ˑˎʉʋ̇̇ˌʏΏʖˠ̢ǬϩͲյպزөΟҧޡܣݬⰵԻᚬ單)7d*8e*8e,;g+:g*j+j.=j.;h0=j0=j0=j0=j0=j0>k0=j1>l2@m3@n2@m4An4An4An2?l4Ao3Bo1Bn1Bn0Cn0Cn0Bn1Am0Am3Bo4An3An5Co4Bm9Eq7Ep4Dn7Fq6Ep7Fr:Ir9Hq;Jt>Nw?Nx>Mv=Ms>Ns@Nu=JsLq>LsLyKz>Kz=Ky=Jy;Kz:Jw>L{>M|>Lz@K{@Lx>My?Mz@Mz=Mz>O{=N|>M|?K{=Kz>Lz=Kx>L{>L{=Kz=L{Ny>OzOz@P|>O{>Oz?O{O?QAQ?P}>P|?P|BQ~AO|?Nz?Nx@P{AQ|AP|@P{CS~CR}BQ|AQ|CR~EREQ~CP|CPyDRyFT{DSzDSzET{FU}IWIYFXBS~FYDWBUBU?R}>P~?QS?T@T:O9P;O9N9M9K8K9K9K7I7I8K7I8L9M8L7J6I7J8K:M7L9M8K9L:MS>S>S=R>S>S>S?T>S=R=R=R?UP>SS@U@T=R=R?T=R>S=R=R?T?T>S=Q@S?T?SAU@U=S=S>T@V=T?S=Q=Q>R@T@T?V>S?T>R>S?TBVAUBVBVDXCWEYDWDVF[F[I]I]G[J\I[I[J\K]J\J\L^L^L\J[K\K\J[L\JZI]J]N_K\K[IZHYJ[M]JZJ\K[K\IYIZJ[K\K\IZIZJ[L]K\K\K\IZJ[K\J\K[K\H]H\G[J^H\J^I]H[IZHYJYHZGXFWEYEXCVCXBXBUCVDVCU@U@U@U?U@W@W>U@T?T>S@U?U?W@WAX@WBYC[DYBXD[AXCXDYDYF[EZG\G\EZEZEZG\F[CXDYF[G\H]J_K`H]I^J_K]J]J]J]K^K^J]J^H]I\K^I\K^K^O`I[G[I\J]I\J\I\J^I]I]K_J^M]L\J\J\L]L\I^J^K_J^H\I]I^I^J_J^I\J]K]M]N_K`L_K_L`MaJ^K_I]I]L_O^N_L]L]L]O_LaK_L`K_N]I_L_MaMaL`K_M`LaJ_J_LaK`J_J_J_L`J_J`I^J_J_K`K`K`LaLaI^J_MbLaLaMbNbObOaLbNcMcOcPeMeKbLcMePhLcOcOdOdMbOePbNdOdNcOdPePePeOdNcPeNcPeQfOdRgPePePeQfPeRgSePcRdRdQfQfQfRgQjPgOgThShShShRgRgShUgUgTgRgTiTiShUiVhUgRhTiVkVjRjUlTiVkXmTiVkVjXoWoWkXmXmXmWlVkWlXlUmUiXmXmYnXmXmYnZoYnWoWnXoXpYp[oWoYpYmZoYnXn[n\o[o]q\q]paq^n^o_p]r_s^r_s_t`sdtct`tbvaubvdxcxfvhxfwgxhyev]qUkUjSkSgUjUjVkXmUjVkXmZoYnZo^s\qZo_t^s[p\qZo]r\q]s\mYlXmVkXmXmXmZoZo[q\q^s^s_tavezdze{hzgyezf{f{hzi|i|hk}kmwn|gzg{d{f{g|e{cvdybvdwcyfzcwcwcwbvaucwfzeyfzgzeze{i}c~c}ezd{exdwe{awcycycye{e{czgzj~hjj~h}jj~kllj~lmmmkmmnkj~kki}lk~ih~lijmmorqqsrztw{~~ǃ˄͆Ɉˈ̆ɇȇʇNJǖ˛̜ȝƚŘŖɕ˜˝Η̑ʒʌʊʌ͏ȑ˕ϙ͟ʣɭбֺ۵ѳټ觱ׯܿޤנ얬퓯*8f*8e*9f+;f,:f+:g):f+j-k0>k.=j/>k0>k0>k0>k0>k0?l3An2@m3Bo4Bo4Bo4Cp5Cp6Eq4Eq1Bn1Bn4Dq1Bo0Bn/@m1Bn4Eq5Cp6Cq7Er7Fq9Fr9Gt9Hs9Ht9Gr;Iq;Is=Lr>Ls=Ls=Mt>NpLp=Kp@Nu>Lr>Lp>Lq=Ks;Js=Lv=LxLy=Kz>Ly=Kx>Ly>Lz=Kx@N{>Ly=Kx>LzJz=Hx;Jy;IxKz=Kz;Ix;Ix>L{;Jx=Jz=JyJz?L|?M}?Mz>Lx=Kx=KxN{;Kx=Jz>L{?L{O}?P}Oz=N{;Ly>O{=PzMx@O|@Q}=Oz=PzQx?Q{>O{>Q|;Q};P{=Oz=N|?P~?O{BO|EREQ~EQ}BNzBN{CP{COyEQ{BOxCR{BQyCRzDQ{CPzBOyCPzDQ|DQyBQwDRzET{ET|FU{GV~EXIWJYEVDTCUCVCV@S?Q>P~=O}>P};M{;M~;L|:J|9L~9K}:L~;M8J}OP=O=N=O>O;MS=Q=Q?T=RS>S=R>S=R=R:O=Q=TP=QS>S@R@S>RCTBTDSDUBVAU?S>RDTBRDTAU>U?U?TS>S>SAUDTBS@TBV>T>T@V@V?U@T>RAU?S?SAUBV>T@UAV@UAUBUAUCWBVDXBVFZH[JZJZHZG[G[FZH\J\I[J\J\J\K]J\K\L]J[K\J[K\J]F[H\I\L\IZH\J[K\J[L[L\J\J\K\J\J[J[I[J[IZJ[IZIZJ[J[K\J[J[J[J[J[J[K\I^J^H\H\J^H\I\JZIYKZIZHYGWFYEZCXEZCXDWEXEXCVAVBWBWBWAX?V@W@WBV@U?T>TAW?T?W@W@VBWCVBWAYDXDYCXF[EZDYEZEZDYEZEZF[F[DYEZDYEZH]I^H]H]I^J`K^J]K^K^I\J]K^L^I_K_L_J]J]K^N^J[I]H[J]J]J]H\J^L`I]L`I]I]J^M]L]K\L\I^I]J^K_J^L_K_I^H]H]J^I[K_M]L]J_L`K_J^J^J^MaJ^J^K_K_M]M^M]L]LaL_J^K_N]N_N^K`L`L`L`K_K^KaL`LaJ_L_LaL`KcI`J_K`M`MaKaK`J_LaK`K`L^J`MbMbMbJ_K`N`M`PbNaM`LcLaMbMaLcMbOcMbOdNcOdNcNcPbPbNdOdPeOdOdQfOdOdPeOdPeOdQfPeQfPeShOdPePdTfRdPdQeQfRgQfRhQjOfRiQhRfRgRgRgTiUjQfShShShUjShShTiSeSfTjUjWlVkRjTkUjTiVkWlWlVkWnVlZnWlVkVkVkVkWlUkVmWkXmXmXmZnYnXmZoWlYnYpYpYpYpXoYpXoXpYp[oZoYn[qYo\p\p^r]r\p_o_p]q_r_s`t`tbudsbt`uavbvbvdxcwcwdyfvevbs[mWjUkWjSiXmVkTiUjUjUkUjVkXmYnXmYn[p\q\q\qZpZoYn\q[p\qZp[nYlYl[pZnZo\o[n\o^p]s]rbwavbwcxe{evdzf{cxhzh{h|izil~lqs|k{h|eybyeyezf{cydvbxdycxdydybxaubvcwbvcwdxf|f}g|c{b|b{d|d}b{g|dzcycybxawcycye{d{i|g{fziimiklllkj~kkjk~kj~kj~j~ki}lj}g~ijjjknnooorrvttxxyǁȇ̇ˊ͉̉̈̈ʊʍ˒ƘȟɟǚÛǘȖȞ̢ўΙʕ˕˓͓˔ƔʗϚȢʥɪήӶﱽި׳ߜܞ훱횮旮ꔭ+9e.:g,8f+8d+:e+:e);g);g+=i+=i,>i,>j,>j+=i-?k0?l3@m1>k1>k2?l3?l5An4An4An5An5Bo4An5Bo7Dq7Cp8Cp7Eq2Dp2Co3Dp2Co3Eq4Eq7Er4Do3Co6Bp8Ft9Hs9Ht6Ep8Dq9Gp7Gp;Js:KsMs=Nq=Mq=Mo>Nn;Ll=Nm=MmLq?Ms>Lq:Jr=Ku=MvLz?My:Ht>Ly=K{?M{=Lw=Lz>J{>JzJy@K|>Jz?K{L|>L{Q{;Nx:Mw;Nx=MxDQ}AMz?Mz=Lz>P|;Oy=Pz>Pw>Px>Pw=Ov>Py=R{8Ox;P{PP;N~P>P=O;M;MN=O;N8M9O8O5O8R:P9P9P9Q;RS=RAVS>Q=O>QP@S@TCSBSARCT?S?T@UDTARARCR@QU@U>RBSAS?T?T>U>U?U>U=S>Q@S@T@T@T@TAU@UAV@UAUBUBWBVCWDXCWF[D[FZK[FXGZDYG\H]I]H\J[J[I\J\K^L]K\L\J[IZJ[J[J[G]H]I]H[I]H[IYL\M]K[L[L\K[KZM\L[L\L[L\K\IZIZI[I[I[IZL]K]L]K\L]K\L\K[I]J^I]I]I\GZJ[IXKZIZIZL\FVJZKZJZIYFYEXEXCWBWDYCWCX?Y>W@WAX@WBV?T?T@RAV@V?W?WAVDVDWBXAVAVCXCWCXEZBVDYDYFZDYEZEYEZCWDYEYG[G[G\G\H]H]I[I\K^J]I\L^K]J_J_I_K_J\L_J\N]N^K[JZJ]H[J\J^H\I^H]H]I]K_I]J^L]J]I^J^J^K_L`I]I]J]H_I^H]I^I\H\L]N^J]J]J]K^L_J]H^K_K_L`L`I]M]M^I^J^K`J_K_L\L]N_N`J_LaL`L`L`JaJaK`L`L_J`K_IaI`LaLaMaL_M`MdI^MbNcL^K]M_LaLaMbLbMbN`O_MaM`N`M`K`LaLaNcMbMbNbMbMbNcNcPfPcPdPdMcNdPdRgPeOfOfOcOdQfQfOdQfQfPePeQfQfQfRgPdSfThPeRgRgPePhRiQhRiQgQfRgRgTiRgSgQeRgShUjVkTiTiUfTgVkXlWlVjVmTkVkVjTiWlVkXmVnUmZnVkWlWlWlXmXmWmUlUkXlWlXnVlXnWlVkWlXmUnXoXpYqWoXpXo[rWoZr[oZoZoZo\q^r^q]p^r`p^p]s_t`t^sav`u]reuctetbwbvdweye{fzevctbs\mXlYnTlUkWjVkTiVjVkVjUjWlXmZoZoYnZoYnZoZoYkZlYo[pZo[pYnZo[qYpZpXmXn[n[n]o^qas`uavcxcydzg}dzdydzi|j|gzl{j{jk~lnyn}i|eze{c{eybybxdybwdzdxbwbwcxcycvbvcweyeye{dzeza|a{b~a{b}azbzcxdzcycycybxbxbxe|fzh|i~g~jllklkj~ki}kkj~k}i}ki}j~j~ki}i}jkijmljonpoopqstuuwzxƀȅȇʅɄȆʉ͋͒ϙ˛ɣ͠ɟǝƖęƙț˝ɛəǗǘʘɜɝ̚ʟ˧ͦ˭үӻ庾أΩ߱埱䖫퓬+9h+9h*8e)8d-:h-;h,9f.k4An/=j0>k2@m4Bo3Bo3An4An4An6Cp7Dq6Cp7Er8Fs5Dq4Fr5Fr4Dp5Co6Eo9Hs8Gr5Ep5Gr5Fq:It;Jv:Ir8Gp6Fn8Fp;Io;Jr=Ls?Nu?Oq?Or?Ot;Jn=Mo=Lp>Ln?Np@Op?Nn?NnLnL{?M}?M|Iz>Jy=Lw=Lx?Ky=Hy:Eu:Iv:Ht;Iv:HtLz=K{>L{KxO}:Ky:Ky;Lz:Ky:JxOw=OvP{>Q|=Oz@Q|?OyAP{CQ}DP~DQ~CP|BO{DPzDQ{DPzBOzBR{CQzCPzDRxCRxBQwER{CQzESyDSyFS{GR}FT~GV~HW}KZIXJYFU}HWHWES~ETDT?S}AS~CSAR~?P~AQ@O;L}9N}O=L~=L@O?P~?P~>O}>O}@P~?P~>O~>P>P?P?O}>O}>P~=O=O~=O~=O~P:L|S=R?T=R=R>S>SS;R;Q=R=R;O=O>Q=O?S?S@UAUARCTBR@QARAQDTARCT@Q?PAR>Q>Q?T=S?T=R@U?TAUCTBRARBSBR@T@T?S@S?SAU>U>T?VBXAX@TAUAU@U@UAW?TASCWDXBVCWFZH[J\HZHZHZFXI[J\IZI\F\F[IZIZKZJ[K^G[IZJZKZJZK\J[M\I\H]I]I]H]H]J^H]I]K[J[LZIYJ[K\K]K[K\IZIZKZKZIXHYJ[M\L^K\K\L^I_G\J^J^J^J^H[IYIZJ[GYJ[K\F[EZEZG\EYF[G[G[CUEXCYBWCWCXCZBY@W@WAXAX?T@V@U@UBW@TBW>SBWCYEXBWBX@W@WBYAXBYAXBYC[F]D\CZD[CZCZ@XDZD\D[G\G\I\GZH[H[I^G\H\G^H^F[J_I_J]J]K^K^K^H\I\J]H[I\I\J\I]I]J]J^G[H]J_H^J^I^H\K_J^I]J_J_I^J^G\J_I]J]I\L\K\L`L`K_K`J^K`K_K^J_K_J^L`J_K`K^L`M]M]L]N_L]K\N_N`N^L]J_J^J^K`LaJ_K_J_I^K`LcK`K`LaKbKbK`NcK`IaJbLbLaLaLaLaOdLaMbNdLaMbMbLbK`MbMbLaNcMbNcNcNcMbMbNcOdNdOcRfOaPeOdPfOeQgQgPgQgOfQgRhRhRiOfPdShRgRgQfPePePeQfPeRgRfPeQiNhQhQiQeShSgSiQjQjPgQgRgWlWlTiUgSiTiTlUlTkUlTkUmVlUiXmVkYmYnXlYkTkXmVkXmXmVkYnXmWlYnYoYl[o\pZmXnYnYnXmXm[oYm\qYmYnZrYpYp[s[r^r[p]r[p[q^t^qar`qaraqbsbrcsbrctareubwdxdxcydycxdxeucsZnWjWlTlVmRiTkSiSiTkVmTjWlXkYpWmXnYoZoZo[pYoZoZoYn\qZo[p[p]rZoZo\m]o]oZm]o[q`s_rcvavcyevfxfydwdwexf}dzh{h|k~lmpwʒщzkh{i|ezf{cxdydxavezbvcyaxcy`vawawbyexdzf{f{ezh|byc~c|c{azb{a|dydzbxcyawcybyexeyi{ezi}j~j|hkkj~lj~kj~i}i}i~h~h|h~j~i}i}h|h|kjkg~ghklnlpoootsqrvwx~ĀĆɄLj˄DŽ̆̋̑͞Ο̢ˢȝȚŕǔǓØŠ͚śÜƘĘɛǛȝ͞˞̥˩ͮϱܟܡ尻㪸سꗬ+9h*8g*8g+9f*8e+9f+9f-;h.k0>k1?l/=j1?l1>k0k3An3An4Bo4Bo3An5Bo4An6Cp5Bo7Dq6Er6Dq6Er4Fr4Fr6Gq8Gr8Gr9Hs8Gq4Dp5Eq8Gp:Ir;Is9Fn:HoNs=Mq@Mo>MoLzLy:Hu;IvL{=Kz>Jz;Jy:Ly:Ky9Jx:JxNyAOzAO{?Ny?Nx;NvPx>PxQ{O{@P|@N{CQ~CP}CP}CP|BO{BOyFS}FS~FR}EQ}APwBPxDSzES{DRyES{CQxBPwESzERyES|GVIXJYJYKZHWHWGVHWFUHWDUBUEVAR~@Q}@Q~@Q>P}=O}O}=O}>O}>P}@R?Q~?P|>P|?Q}?Q}?P}?P?O~=O>O>O}>N|P8Jz;M}9J{S=R=R=RR?R;Q9Q:P=S;QP=Q=Q?S>R?RAQARAR@Q@Q@QBSCTBSARDU@QASAR?S@U>U=R@UAUAUCTARARAT?S?T>R@TBV?S?S@VAW?T?U@VBWBUAUCWBU?U?UDXBVBVBVDXBVDXHZGYGYHZHZHZK]GYH[G\F[I[K]HYIZJ[I^J[J[J[IZIZIZJ[H\I]J^H\H\J^I]H\I[J[IZIZJ[J[J[K[I^JZK\K\JXJYKYI[JZKYL[L]L]JZL]J_I]H\H\I]H\GXHYJ[HXIZG[G[F[DYEZEYFZFZG[GZFYCYBWBWBXAXAXBYAXBYAWAVAV@U@UAV@UAV?TBWAUBTBWAVBYBYBYAX@WBYCZBYBYAXBYD[CZD[E\D[CZDYF[DXFXEXFZEYI\GZF\H\I^F[H]H]I]I\K^I\H[K^J]I\H[I[H[J\J]J_I^I]K_H\K^H\H\J^L`K_I]H\J_H^G\K_H]G\I^I[J]J^M]I^J^L`K`L`N^M]M^I^I]J^K_I]I]J^I^M^N_N_M^N_N_N_M^L]K]J_MaJ^J_J_K`I^K`K`K`JaIaJ_J_J_LaK`K`K`LdLdHaK`NcLaLaLaLaLaNcLaLbLbMbLaMbLaLaNcNcLaLaOdOdNcOdNdNdPcQeObOeNcPdQeQgPfQgPfQgPfQePgPgSgPeQfQfRgOdRgPePeQfQfPeRgRgOhSiQiSgShShShRgUiUjRfThShShUjThUhRgUjWnUlRiTkVmTkUlTkWkVkUjWlXmVkUjWlUjUjVkUjVkVkWmXnXlYm[oYmZnWlWlVlYnXnXmYoZo[pZoXqYpZqYp\q[qZrZq\s]q]q\qaq`qarbsbtaraqcsetawdxcweyfycxcxdxfziy`uZlXlVkWnWnTlTkShRhSiTjTjUlWoXnVlYoWnYnXmZoXmXmXmYnXmZoZo[p\qXoZnYn\n]p]p\rau_qat`vav`tcxewexbugzexh{i{i|k~llnottstp~mh{f|g|f{cxdy`ucxdy`vbxbx`v_ucycybyexdyg|g|fzdycyd{`zd}d}`zf{cybxcycyawbydxdxdxeyeyh|h|j~ijiki}i}j}j~kjh|g}ij|h|g{h|h|j~h|j~iljllnlloprosqssuxv}}‚ǁȉ̀ǂȃɅɉ̊ƔÞˤΥ̢țƘƕŕ˜ťΥͤțÞȚǙǞ˛ɛȞȟŨˬͯͽ־Ԩՠߞ⤴䲺稵߱ܦ㛫疪퓫*8e+9e*8e+9f-;h-;h,:g,:g-;h0>k0>k/=j.k3@m/Ms=Kp9GlLq?Ms>Mr=Np@Oq>Lo>LpMn?No>Mo>Mo>MnBQr?Np>Lo?Mq>Lo;Il=KrL{=KzK{=JyNy>Oz>O{Px;Nu;Nw;Nx=Py=Pz>O{@Q}?P{BO|AN{AN{BO{ANzAN{DQ{CPyESyFT|GS{EPyDPuDOvFRyDRwERwDRxESzDSyCPxANyDS|GVHWGVHWGVJYGV~GV~ET~HWDTBS~BS~@Q|BS?P|BS@R=P}P>O}P:L|PR>R?S=Q>QAR?P@Q@QCTARBS@QCTBSARARBTAT?S@T@SAT@T@TDTARARBSCS?T@TBV@T?S@TAU?UAW@VBXAV@V@VAUAUBUBVCWAUBVBVDXCWDXGZJ[I[GYGYIZI[I[I[J]I^FZGZK\K[K[J[H\L[K\K\I[M^K\I^I]I]H\H\H\H\I]GZGZJ[IZIZJ[IZK\I^H]I]K[L]K\LZL[IZI[M[LZIZK\L^K[G\I]K_I]J^H[L\HYJ[HZG[GZCVCXEZDYDYI^G[G[HZEXDZCXCXCXAYAXBYAXCZBYCXBWAV@U@U@U@UAVDYBXAVAVCYBYAXAXBYAX@W@W@WCZBYCZCZC[CZCZAXD[F[DZEWEYDWH[HXFYFZG[EZF[EZEZF[H\I\J]J]I\K^I\J]I\H[H\H]I]H^J^H\I]G[I]H\J^J^J^K_J^H\K_J]J]K_G[I^I^H^I^J]J_K_K`K_M]L]L]L]M^L`J^L`L`I]I]J^K_N^L]L]L]M^L]N_N_M^K\H]M`K^MaL`I_K_K`I_IaKbI`K_J_K`K`K`LaJ_LaL`NcLaLaLaOdLaMbMbLaMbMbMbMbLaLaMbMbMbMbMbPeNcNcQfNcOdNdPcOdQcOePeOdPePePeQgPfRfOdOdQfPeRgOdPePeQfOdQfPePeQfRgOdQfRfQiQiSgQfRgRgSgQiQfShRgSiShTiUkShShTiWkSjTkUlUlUmUmWnWoWoWoWlVkVkXmUjWlVjXmWlXmXmWmXl[o^rZn[oXlYn[m[mZm\n[mZm[nZmZo[o[rZq[sZp^s[qZqYp\s]s^r`p`rcrdteueuarevbwcwcvdxfzh{bxf{cwdwezgwbw]oZmWlXmUmUlQfPfQgSiVlUkWlXoWnWoXoXpXlZo[pWlZoYnYnYnYnXmZoXmWmZo\q[q_p[p`u_tascu`tbueyezfxdwexcvfygzh{j}j}lj}lnrtqpqj}j}g}ezcxcxcxbwbweybxawaw`vbxawaxbwdydyf{ezf{f{f|dzezc~c|fze{awe{bxawcybxexfzeyeyg{i}h|i|ijilkkhh~h~f}e}g~i}g{i}h|i}h|j~j~i}illklnmnonopsrttww{~~ǀȁ˂ˁǀDžɈȊŋĖÝɢʡǦɠǟǗ ƧЪϩ̪ͦ͞ȜƞȠɡɣ̣ȦDz㵻枫ᙫޣ岾뮻߶䧰լꭼ畬*8e*8e+9f*8e,:g-;h-;h,:g-;h-;h/=j0>k.Kq?Jp@Mo@Mp@Mp>Mp>Lo=Kn=Km?No>NoAQq@Pq?No>No?MnANm?Ol>Ml@OnAPo@Pp=Lm>LnKu>Kw;KvO{N{@M}?M{AMz@MyBN{BOzCQyCRxCQxFSwERwDQwFSxERtERuCQtETwFV{DRyBOvBPxCR{CR{DS|ET}HWFU~IXFU~GVFUGV~HWFUETES~BR}AS~AS}BVBUBUAT~>R}@S~AR~@P|CP}BQ|CR|BQ|AO{AQ|BQ}AP{N|>N}P;M:L~9K9J9K}5Gy9J|5H{4Gz7G|6G{3Ey2Ey1Av2Cy2Cy3Dz0Aw0Av0Au/Cx/Cx/Cx.Bw-Av.Bw1Ez.Bw/Cw/Cx0Dy0Dy/Cx2F|/Cw0Dv1Bx1Ay2C{2C{0Ay0@x1Bz-Ax-Cy.D{.Cz/D{.Cz.Cz-By.Bz1C{0D|1D|1Bz1Bz/D|0C{3F~2E}/Bz0D|3H2G2G1F2G0E~1C2D~3C{2C{4E}2C{0Ay2C{2C{2C{4E}3D|3D|4D|4D|2C{3E}4G3F}4H|5I~5I~5I~4H}3G}3G~4I4H6I7J9K:K;L;LRS>SAU?REUBSBSBSCTCTAUBVAU@TATAUAW?U@V@V@V?VBXAWBUAUAU@UBV@TCWBVCWDXBVEYIZGYGYHYF[EYK]K]I\G\G[G[H\H]I^F[G\H]I\L\L\K]G\H\G[G[G[G[G[G[G[GZHZIZGXHYIZJ[K\G\H\I^H[K[M^K\HYJ[JZJYL[J[L]J[K\I^H]H\I^I]FXDWJZK\HYDWEXGYEZCXEZCXFZH\EYDWBXBWAVAVAWAXBYBY@WAXAXCWCXCXAV@UBW@UBVCWCWCVBWAVAYCZBYAX@W@WAXAXBYBYCZBYBYDXC[C[CZCXCYDWGYIYFWGXGWGXFYGZE[EZEZBVGYFXFXH[GZH[I\J]H\J^K_K_K^K[L\K[I]I]I]I]I]I]K_K_J^H\J^H\H\I]I]H\H]G\G\H]I]J]M^M]M^M^M^L]J\H]K_K_K_J^I]H\K_L\M^L]M^N_M^N_M^L]O`N]LaLaMaJ^L`J`K`J_K`IaIaJ_K`K`J_H]LaJ_LaJ_MbLbMbLaMbMbLaLaMbMbMbMbLaMbLaMbMbMbLaNcMbOdNcNcNcOdNcPcOcPcOdOdPeOdNcRgQfMbOdPeOdPePeOdQfQfPeRgQfOdRgRgQfRgQfRgQeRgRfRgRhPeRgRjTkUiSfTfTgSjTiUjUjTiShVkRjTkUlTkXmUkUkUkVlUkWkVkVkWlWlWkTlWkWlWmWmWkYmXlYmZn\q\pZoYn[o\pYmZn]q[n[nZo[p[r[rYp]s]s[q[s]u]s_v`saqaqcseucsdubrdvcwawavfzdxdxdyezfzfzg{i}g{\qWlWlUkSkRiQgRhSiTjVlUkWnVmWnYpWmYpYqZmWlXmVkWlVkVkVkXmXmZoXqZo\q[p]r^sav^tataubvauaudy_ufxdwexh{i|i|j}j}mllnoslnn}k~f{dyf{cxdybwcxbwdyaycx`vawaw`vbxawdycxezdyf{bwc{dzf|e{ezdzcyczbxdzbxcydzfydxcweycweyg{i}lhii|j~mg~idheh|j~j~h|i}g{i}i}i}j~nmllonnlonprsvzw{}|ŁȂ́ɂǃLjȋ̋ÏƗŘğş¥ǧȤǠǝģɨШͪ˥ɧͤΣǡƧ̧ɧȦƩDZͱ魴眪曫ٿ镫+9f+9f+9f,:g,:g,:g,:g-;h-;h.k0>k2@m1@m2?l1>k4Bo3An3An3An2@m4Bo6Cp4An4An6Cp4Bo4Bo4Bo5Dp6Cq7Er6Eo6Ep9Hs9Hs:IuMs?Kr@Kr@Mo@MoBOqCPr@Mp@No=Mn?Oo?Nn@NnAMnBOoAPoBNn@Mm@MmANn@OnAPo@Oo>MnKo>Lq=Lr=Jt=Lu>Mw>Mx=LwNy>Oz?Q~>N}?M|>L{@Nz@NzAP{BNzCPyCQyESxFUzHOuJRwEPtFSuERtCQtDRuBOsDSxCSwBQyAPwBQ{CR|CR|DS|HWGVGVGVGVET}DS|ES|DS}ETETETBS~BR}CVBU@S}AT@Q}AO}ES@Oz@OzDS~CR|CRzBQyBQ{@P{>P|?O{=N|>O}>O}>P}=O}>P;M~=O>P:LyP~P=O=OR>RU@V>TAW@VAWBX>VAW=SBVDWFVFWFWBVBVDXCWCWDXCWHXHZHZHZG[EYFXHZGZG[G[G[DXEYJ]M]J\F[FZG[H]G[G[H[EZH\H\I]EYFYFYEXFYFYIYK[IYJ[GZH[HZGYFYI\KZI[J[IZIZK\J[K\K[K[G[H[I\HZGZGZFYH[EXDXEXFYFYDYBXEZEZEZD\CYBXAWBWCXCXCXAVDXAYBYBZAWCXBWDYBWBWAVBW@UCXCWAUDWCVCXCYBZBZCZAXAYBZBYBYBYAYAYAXDYEZDYCWCXEYDWEXGWFWFWFWGYFZFZFZH[G[FZH\G[F[G[K_J\H[K^I\H\G[J_J]K[K[K\H\G[H\G[J^K_H\H\L`K_I]I]G[GZJ\F\G\I^H]G\G\H\O^L]M^K\K\J[L]I]J^I]J^L`I]I]K_M]K\M^M^N_N_N_M^O`L]N_O_N^K`L`MbK_J_J`J_J_L`K`LaK`LaLaI^J_J_K`LbL`L`MaOcMaOcNbNbK`K`LaMbLaLaLaLaNcLaLaNcNcMbOdOdOdPeQePdPcNdOdOdNcPeNcPeNcNcPeOdQfQfPeQfOdOdPeNcRgPeQfShPePeRgQfShRgTiRdQgRfRgTkUhSgUhReUgUjShShTiVkWlVjSlUiVkWlUkWmVlTjWkXmXmWlWlXmWlXmXmYnYkXkYmXlZnXm[o\m]n]m[o[oXlXl\pZnXnYn[pZoYpZr\r\s^t^s\t\s^t^uatcrbrbrcsdtctctav_t`v`vbvcwcvcxdydwfzj~mi}_tXmWlRgPhPgPfOeRhRhSiUkUlWnXoXoXoWnVnWnTlUmWkWlVkXmVkUjYnXmXmWl\q[p_t_t^s`ubtcuawcwbvaxfxdwd{fyfyj}j}j}l~h~l~mplpnk~h|i|f}ezcxdycxbwbwbw`uayawaw_u^taw`vawdybwdydyf{ezg{f{czf}cybxeyf{gzcyg~h~f|fzeyh|g{g{h|i}g{kj}jj}ljh~h}c~eeh|h|kg{h|i}i}j}kj~lj}moomooorsttwx{|Ɂ}ǁˁˁ˂˄ȈɋːʔɓĖÕ¡ĢɪʥƤ̦ϤʪΦȢĦʤʥǨ˯ΰ͵ͳȰɯƿ鳴٢嚨昫棷⫵㣯锬퓬*8f,9g+9g,:g.k2@m0>k1?l1>k0k2?l3@m5Bo6Cp5An5Cp4Bo6Dq7Er6Er6Dr9Gt9Is:Jt;Ku=Kw>Jt=Jt=Jr@Mq?LoANqERtERsCPoCPpBNoCOpBPlCQoCQnBPmBPlCQpDQrBNoBPpAQp@On@Oo@Op>Mo?No?Np=Ln>Lo>Lq>LrMtN|AN~@N}?Mz?Mz?NzCR}DQ|DRxESxFSwESuFSvGTvHUwGSvDRsCRsDStDTwBSvBRwBQyDSzCRyCRyET|DS|ET}HWGVHWET}FU~EU~CS~GVFT@P{AR}EVCT~AUATAS~AR~ARBP}AO|ET~CR}BQ}CR|APyCR{@P{>Oy>Oy?O{?P?P~=O~=N}=O}>P~>P};M{=O}P;M@Rs+?t.Bw/Cx/Bv0@t2By1Aw0Aw1Bx1Ax1Aw1Au0Ax1Bz0Ay1Bz0Ay/@x0Ay0Bz/C{-@x.By0F|.Cz-Bx0C{/B{1C{3C{2C{/Ax/Ay3C{3E}/Bz1Bz0C{0C|/D}.C|/C}0C}.Az0C{/C{1@x2C{2C{2C{0Ay1Ay2C{3C{3C{1Bz0C{1D|2E}0C{3C{3E}2F~3I3H}3G|4H}1Ez2F{3H~5H4F~6I6I9L8K9J9J:K8K8K7J7J6I6J7J7J8K9L8J7J4J5J6K6I8J8K:M:M?O9M;NR>Q@Q?PARCT@Q@Q@QCUBUBTEWDVCUCUBTCUCUBTCUDVCUCUDV@SAT@UAT?U@V@V@V>T?UAW?U?U@WAVBUBVBVAUCWAUCWBVEYDXCWCWCWJ[FXGYEZDXFZH[HZFYI_G\FZG[EYEXIZHZH]G\G[I[G\H\H[F\H\FZDYCXEZDYDWFYEXDXEYGZFYGZFYF[DZDYFYGYJZJ[HYK[J[JZI[G[GYEXEWFYFYFYDWDWEXEXGZFYEXDWCVCVCVBWDXBYDYEZDYCXAVBWAVAVBWAV@X?V?VBVAVAVBWAV@UAVCXDXEXBVDWDVBUAVCXBWBVCXCX@TAVAVBWCWCXCWBWEZCXEZCXDYCVEXHXHYIZFWFWGXG[H\G[H\H\I]G[G[J^I]G\H\I]I]I]J^I]J^G\H\J^K_I]FZH\J^K_J^K_H\K_K`J_H\GYF\EZI^G\G]I[H]I]J^J]M^K\K\O`K\I]J^J^I]J^J^I]I]M^J[K\N_N_N_M^M^M^M^N_N_M_N^N^N_K`L`NaJ`K`MbNcJ_K`K`LaLaMbJ_NcNcMaMaMaPdMaOcMaNbNbL`L_MbMbMbMbLaNcNcNcMbMbNcNcLaNcMbQePdQdOdNcNcNcOdOdOdNcNcNcPePeNcRgRgOdPePeOdQfPeQfQfQfPeRgShRgQfShSeThShRgTiViSgTfTfSfUjTiShTiShUjVkVkVkVkVkUnWkVkWlWlYnWlVkWlWlXmVkWmYkYlWjXlWlYo]l[k[l[l\mYnXlZn[p[nYnZoYnYn\qZrZq]s]s[n^s\s]t\r_uatbrctcs`qbsarbq]r[p\q[s`tbv_u^s_taudxknrj~XmVkRgOdRjRfRgQhQhRhTiUlVmUlWnVmXoUlVmUlUlUjUjWlUjWlVkXmXmXn[p^s\q^s^tasbt`tdwcuawbvewdwexcyg|j|i|i}l~h}kk~lnnnpl~gzi|ewg|ezdydycxbwcy`teyay`v`w`vbwawawdycxdydydydyf{ezg|dyf{ezezezf|i{nmkj}i}i}g{h|h|h|i}i}i}lj~j~j~ihkd~fj~j}j~j~i}kk~ii|k~lkmoponpnsrxwz{~ɄˁǃɈͅ˃Ɉ͇ɈǍȎɓ̓ĕĖÚƞƟäƦƪ˭Ϩ̤ɦ̧ͩɦǣƧ˦̦ʭ׹ޞ䙩甤잭喬-=g-k/=j0>k1>k1?l0>k0>k0>k1?l2@m2@m3@m6Cp5Bo4An3@m2@m3An4Bo6Cp7Dq6Dq9Gt:Gu:Hu;Iw;Iu>Mv>Ku?Kt?Kq?LrBOrCPrERsFTtGTtESoDRoCQoDQnCRlBPlDSpAQmAOkCRnDQpCOp@On?No?No?Nn>Ml>MkMo?MoLp=KrMx>Mx;Ju;JuMx=Mx=Nz;Lw;Lw;Lw:Kw:Kw=My;Lx:Kw:Kx=Nz>Nz=Ny=Px>PuPv>QvOu?Nu?Mt@Os?Ot?Mv=Ls:MsOz=Ny?P|=Nz;Kw>Lz?M|@Nz@M{?MzBP}CO{CPyESxETyGUxHTvJVyHUwIVxDQsDTtBRsCSuAQtBRvCSyCRzDSzDSzFU{GVFU~GV~HWFU~ET}DS|CR|CT@R}DTFUETAS~BS~AQ|?R}>Q|BSDTESBP}CQ~@PzBQ|AP{CQ}BQ|BP{?Q}@Q~>P{?O|?P|=Q{=P|;M|;M|>P~?Q;M{s.Bw-Av*>s/Bw/?v0Aw.?u0Aw/@v.?u.?u/?u1Ax1Ay0Ay/@x.?w/@x1Bz2C{3D|2D|/Bz0C{0C{0C{.Ay0Bz1Ay/Ax1Az2Az1@y0Ay0Ay2Ay0@x.@x,@x-B|,Az.C|.C|.A{.Ay0C{1Bz/@x0Ay2C{/@x/Ay/Bz.Ay.Ay2E}0C{/Bz0C{2D|5E}1C{1D|2G}1F{2G|4H}3G|3G|2F{5I~4G6I6H7I6J6K7L8M8L9M8I4G6J3H3I3G6H7J7J6H6J7J8K7J:M8K;N8KR>R?S@SAR?P@QARBSARARDUDVCUDVASCUCUCUBTCUEWCUFXDVCUDVDVDV@T?UAWAW?U@V?U?U?UAV?V?S?SAU@TAUAUDXBVCWBVCWBVDXEYDVIXI[IYFZH\FZG[GXEWIZIZEYFZH[L\GVI[FYK\J[I[J[I]H\FZFZGZEZAYD[FZEWEXFYFYBUFYGZEXDYEZDYDZFYGZK[J[HYHZFZI[GZFXEXF\E[EYDWBUGZEXFYDWFYDWEXEXDWATBUBXDYBYDYDYDYAVBWBW@UAVCXAVAYAWCWBWCXCXCXCXBWBWDYBUEXHXFWCWCVDWCYBXBXCXDYDYCXBWEYBWBWBWBWDYCXEZEZEXDWGZIZJ[J[HXI[G\G[FZG[G[G[FZI]J^I]J^I]I]G[I]J^K_H\J^K_H]G[FZI]I\G[I]MaK_I]L`J]L\L\K^H]H]FZH]G\G]I]H\J^J^I]I]J^N]J[K[G[I]J^J^G[I]J^J]N_M^L]N_N_M^N_N_M^N_N_M^N_M^M^M^O_H]L`MaNaL`MaL`LaLaMbI^LaM`NbL`NbPdOcNbOcOcMaNbMaNbNcLaMbMbMbMbK`LaLaNcNcLaMbNcNcMaPdNcPbNdNcPePeNcNcPeOdNcMbPePeOdQfOdOdOdOdQfOePfQfPeRgQgRgQfQfQfQfTfShSgSgThSiRgRgRgRhTfTfViUkShUjVkVkUjTiUkWkVkVkVkVkTjVkXkVkVkXmWlZlXkVk^nYm[l\l[l\m\m\m\lXmZn[o[oYoYn\pZo]s]r[s^s[q^t`s^s^qat^r]q`t_sdtbsdtbs`pYoYlXmYn[r]o^p_tav`t`tcwf{nppavYnRgQfQfOdPeRgShVjSkVmUlVmWnTkUlTkTkUlUmUiUjXmVkXmWlVkWlYmYn\q]r\q`tat_tdscwatbudvexcwfwezg|h{l}n~j~lkl~mmnponh|fyi{cyezdydybwbxew`vdycxbvbw`ucwbzaxdycxezezf{g|dyg|f{f{ezf{ezf{g|kk~jloookh|h|i}i}j~i}i}klfzj~li~khj}j~h|j~j}miinlkmloppnqpqvuw{{~ƅʅȇ˄ȃɈɈˌǎɒʕɕƘÔÜơǢƨʮЬΦɤƣťȨǫ˱ѯУѧ͸砬阩䖧䗬-k2>k2>k1?l1?l1?l1?l1?l1?l2>k3?l4An4Am3?l2@m1@m4Ao5Ap6Co7Dq6Dq7Er8Fs:Gv:Iu=LxMn>No>Mp?No=Kn>Lo>Lq>Lq>Ls>LsMy=LwLxKy=Jx?Ny?Ny>Lw?Mw?My>Lw:LwPw>PuBQt@OtAOvAOvAPwBQu@Pt@Pt?Mq>Mr?Nu;OuOz?P{@Q{;Kx=MyLx=Kw>Ly@N{?MzBO}CO|CPyESyFSwFTwETvHWxFSuGTvFStCPrBQrBStBStDTvETzDRzDSzERzDSzDSETFUEUDT~DT|FU~CS}DUCTBS~FUGTDR}BQ|?S~>Q|>Q}ASAQ~CRAO|CQ~BQ~APzBQ|DS}BR}?Q{>Oz?P|>P|?O{>O{?R}@R}?R|>O|=O}>O~Ps+?u*>s*>s+?t,@t,?u->t/@v.>t0@v/@v.>u/?u,>t+Au,?w.?w.?w0Ay0Ay.?w0Ay0Az1Az0Bz.Ay-@x-Ay.Ay.Ay0Ay/?x1@y0?x.v+?w+@y+@y)?w-Az-Ay.Ax.?w1Bz/@x2C{1Bz1By/Ay-@x/Bz.Bz/C{.Ay-@x/Cz/B{1B{2E~0E}0F|2H|2G|2F{3G|3G|3G|5I4G5H4H5I6I7L7K6K9M8L6J4I~6I2G~3H3H5I6H7J5H8J:J7J7J:M7J9L;N;N9M:O;O9M8L9M9M9M7K9M7K5I~7K7K5I~7L4H5I}7K8L6J6J4I~8J6H9H9H9I9I9H9J7I7K6I7L7M7L:O:P:O;P:O9N?T:O;P:ORS?SAQ@QBSARBSARBSBSBTBTDVBTCUDVDVEWCUCUEWCUDVDVCUCUBS@UAW@V@VAV@V@V?U?V>U>T@T@TAU@TBVBVCWBWBVCWEYDYCXDXEYJ\GXHYG\DYF[G[GXHYHYFXFZFZEYHYHXIZEYGYJ[K]L^G[H\G[F[CXDZBZCXCYCWFXFYDWCVFYGZDWDZDZDYCYFXEWIYHXHYHYDWGZEXEWDZDZEZBVFXEWDWFYFYFYGZFXEYCWCVCSATCVBXBYCXAWBWAVBWAV?TCXBXAU@VAXDXCXCWDWBWDYDYCXCYEXFYGWFWGX@UDXFXEXEXEYBXDYDYDYCXBWDYBWCXCXFZEZCXGXEXH[IZK[IZJZJZH]H[I]I\I]I]I]G\H\G[G[J^G[J^K^H\H\I\H\I]H\J^J^I]H^I_I^I^H]I^J_J]N^L\J^F^F^F^G\H\I]J]L\H^H]K_G[J^G\K_K[G[H\I]J^I]J^J^I]K\L]M^L]L]L]N_M^M_N_N_M_M^M_N_M^O`PaJ_MbK`MaL`K`J`I_LbH^KaM`MaPdNbNbMaOcOcObNaMaMbMbNbNaLbMbLaMbLaLaLaNcNcMcMcNcMbOdQdRfQcOdNcPeOdOdLaPeOdRgNcPeOdOdK`QfLaPfOeQfRfQdRfSgSfSgQeSfQhQeRgUgShUhThThRgTiTiShSiTgTgVhTjTiRgUjVkTiVjWiVkVkVkWkXjXjXjTgXmWlWlYmXkZk]m\l\k[l]m^n[lZk[k[mZn[oZnWmZoZo[rYp\q]t\s\r\s[p\q_t]s_s_t`t_s_sauar_s_s\pYlYkXnYnYnZlYo]r\qau_s_sfui~nqe{ZoTiQfQfPeNcQfUjTiVjVkTlWnUlTkSjTkSjRiSjThVkUjUjVkVkXmWlZn[nZp]r]rbw`rfudtdtbvdwdwexhvewf{g|h{m}l|k~jlnnnpqqqj}hzgygzf{eyezcwdvdwcvcyf{ezf{dygzayczf{g|h}g|i~kj{g}g}f|ezfzdyk{iljk~hmoooki}j~k~i}i}lj~j}ki}kh|h|j~h|kj~h|kihi~jjilmpopqptvswxz|ʂɃLJʉɋ̈Ɋʌǎȑɓʖɖ×ĘƝȞɟƤɪήЩ˫Ǩǫ̬̱ϵѾڹ֢֪ս힭薧व랰ޘ璨露+:e-i-j0>k2@m.k/=j0>k/=j->k/=k0>l0>n0>m3An4@m5An5Bo4An5Cp5Cp7Er9Gs8Hr;IuIq@Ls?JqCMpGQrGRrGRpJTsHTqEUoGTpIRnGRoEPnFRmBQkCRlCQlAPn?OlBQm?Nj?Nl>Ml>Ml?Nn?No@Op@Op@Op@Op>Mo?Mq?Lo>Ln=KnKw?NyMx>Nx9Is;IuLu=LuRy@S{>Pw?PuBRu>Ot;Nv;OwMu@Nv?Nx?My>Oz>OzQuBTzBTyET{ET{FS}GT}ER|ERER~DS~FUCR}ES~BR}AS~BS~@Q|@Q|?R}AP}@NzERBN{CQ~AO|CP}BO|CP}BP}@N{@Q}>P}=Q|?R}>Q|=Q{=Q|:P{:Oz;Q|:PzQ|;Oy=P|;M{=O~?Qt-t*?s*?r,@v,@u+@v+@x)>v*>t+@t+@s-Av+?t-Av)>s+?u.>w.?w0Ay0Ay/@x0Ay0Ay0Ay3Dy2Cx0Bw/Bz.Ay-@x1Ay.?x.w/>w/>w/=w->z/?z.>y/@x,>w+>y)?x*?x,@y.A{,?x/@w0Ay1Bz0Ay.?w0Ay0Bz-=y/Bz/@x3D|0@x2C{1C{1D|.Ax0Dx2Gz0Ez0Ez1F{2F{1Ez1Ez4H}5I~4I~4F4G6H7G:K7H~9I:J9J7K8L7J5I~2G3H3H4I6K5J6I6I7K9L7J7J9L7J:M:M8K:N9N:N9M8L9M9L8L9M9M7K7K5I~6J6I:J9J6I7K5I~5I~5H}7H}4I}5I~6K6I5H4G4G5H4G:M9L8K7J:N;O;P9N:O;PR>R?R?OAQAQ@QBQ@PASBSARCUDVDVBTBTAQDTDTEWDVBTDVASEWDVCUAT@V@UAT@U?V@XAW@VAVAU@S@S?S@T@T@T@TCWDWBVBVFZIZIZHZGYDXH\HZGYJ[HZI[J\I[J\J[J[I[I[I[I[J\I[FZH\H]I[J\I]FZGZEZBWBWDYCXCXCXCXCVCVATBUBUFYCVDVFYDWEXEXDWEYDXCVDWCWCYBXDYDYDWEXBWDZDYCVGZEYF\CYCXDWFYATASBWBWBWBUATAVAV@UAVBXAVCVCYBWDYDYDYDYAWBWDYBWDYCYDWEXIXHXHXHYGWCWDYFZCWEWCYDYDYDYDYDYBWDYE[AYDXDYF[G[H\FZGZIYJ[F[G[EYF\H^I]H\I\K[G\H\I]H\F\F[H]G\H\J]K]F\H\H\I]I]I]J^I\M\L]L\K\G]H]J^I]I]H\H\H]F[H\H\H\K[L]I^I\H\H\I]G\G[I]J^H\J^I]H\J^M]L]N_K\M^M^M^N_N^M_M_O^O_N^L]PaO`PaN_O_N^L`J^K_L`J^MaMaMaLbNcNdNbMaOcPdNbMdLcMbP`P`LaNbLaK`NcMbKaLaLaMbPaM_PbO`OdPeOdOeOeNcOdOdLaOdNbPbObOePeQfOdNcPePeOdRdRgPdQeThReRfSgRfRfRgRiSiQgUiSgUiSgTgShShVkUkSfViSfViUjUgQgUjTiSkSlUkWlWlWlUjVkUkRiWiVkVkVkVkWmXjWjXmZoXkZm[o]n[lYj[pZoZoYoYnZp[r]s[q[s^t^t^q`tcu]maqbraqbr`ubv`tau_tbrds\qVkVkXmVkXmVlXmYn[p^oat`t]rexj|fx`uZoVkRgQfOdRgOdQfShRgWlVjTkVmSjUlQhUlSjSjSjTkTmVkTiWlTiYnZn[p[o]q^s_savavbvbvbucvbucvexfye|e{hzj}i|k}iljpnooqk~h{gzf|f{d{d{eydyawcvcvbxdydyh}f~f}fycxf{h}i~llmmnk~k}h{f|f{j~lmk}klkmqrplkki}i}h|h~hki}lklj~j~j~j~j~klkj~lkmnnponnqpsuvw~|ʂȃʉ̉ˊʎˈǍȑʕʔ˕ǘśŚŞɠɡȧʪˮϩʧŮƶͶƸԾٹڣנ֧ܩֱ䲹ݪ塴蛭ݩ㪷뚬啬,;f.=i,;f.=h+:e+;e,9g+8e+:g-;h-;i,:i-;j0>k.k/=j/k0>k3@m4An3@m4An4Bo5Cp8Fs7Dr6Dq:Ht:GuJp@IrDMqDNqFQoFQnHSqJUpJTqHSoFQmHSoEPlBMiBNjBRlAOkCQnBOl@Pm?Nl@On@On>Ml=Lk@OoDStBQrCRsBQrBQrAPp@Op@Nq?Mq>Ko;Jm;Lm;Ko;Ir;Js:Iu6Eq5Gr8It9Hr8GsKwMw=Kt?Mv@OxMx>MyMv>Mv=Nw>Nz>Mw=Lu?Nt?NuAQv@Ps>OrBOuBQy@Pv?Qv@Rx?PvBQw>OsQ|R}P~>P=O=N>Ot,>t.>u.=s,=s-=t+=q)>s*>s(=r)>u)>u*=u*=u+=t*?r(v/@x/@x.?w/@x0Ay/@x/@w1Bx0Az2C{/Bz.Ay1Bz/@x.?w/>w.=v.=v/>v.=w/>z/>y/>x/@x,>v*=x)x,>x-=x.@w.?w->v->v/@w/@y-=y.Ay0C{2C{1C{3D|/Ay0D|1D|3E~3F1F}1Ez1F{0Dy2Ez1Ez1E{5I~6J7J5H6I5I6I8I~6G|7H~7I7K7K8L9M5J5J3H5J6K8L8K8K6J5H8K8K9L:M9L9L;N;N:N9M9M9M7K9M7K;O5I~7K7K6J6J7J6G5I7J8L6J4H}6I~8I7K6J5I4G3F~3F~5H5H7I5G8J9M9M;OR=Q=R>Q@Q@R?RASDSCT@QARDVCUASASBTAQEUETEWDVBTCUDVEWBTDVDVDVAUAU>S@V@V=S@W?UAT@T?S?S@T@TCW?SAUDXCWCWDYFXHZGYH[G[EYHZI[HZHZI[DVI[GXGXIYGXI\H[L^L^I[FZFZHYK]L]GXFZGZDYCXCXDYCXDYCXEZDVCUBTASBUDWATGYFXEXCVCVDVCVCVDVCUDWAVBWBWCXEWEXDYBWCYFYFYH[GZEZF\EYDXCXCWAVBW@TAVCUAV@UAVAV@UAVBTDVCWEZCXCXDYEZDYDYCYDYFXFXHXHYKZHYEVHZEXEYFZFZDWEZEZEZEZCXEZEZEZCXCZDYEZEZEYI]FXFYGZFZEYH\H\E[H\G[G[FZIZJZH\I]FZH\I]H\H]H]I\K\H]H\I]H\H]H]I_I]L\L]J[L]J[J^I^J^J^H\GZF\F[G[I]H\LZK\G[I]I]I]H\I]H\H\J^L`J^I]H\J^N]M^K\O`N_M^O`N^LaN^M_M_N^N_M^L]N_M^N_O`N_J_MaMaL`L`L`L`L`MaNbMaNbNbNbMaNaKcKaNdMaMaObNcMcLaL`OcNcMcMcLbLbLaNcMbMbOdRgRfPdOdPePeOdNcOdOePeOdPePeQfRgPeQfPdReSgRfQeQeSgUiQeRfThUhRhQfRhTgUiRfTgShUjTiTiUhViTfViTgXmUiUiTjTiVjVjUlWkWlUjVkUjXmWiViUkWlWlWlXmYjYlXkZlYl\oXm^n]n[p[oYnYoYm[pZn\q[q^t[q]s\r`t`uctarar`qbsbt^r_s`tbvasct^s]qXmUjZoXmWlVkVkWlYn]r`rZm[n[n_q\qTiWlUjQfRgOdPeOdRgOdTiUjVkTmRiTkRiTkPhSjSkUlTlVjUjVkUkUjZm\l]n_o_pcscsdudtcwcvcvbvcwaudxbudygzgzi|k~l~niknnnoqj~k~i{f|e{e|bzeyexdwduavcxcxbwhhhfzezh}i~knnmokk}i|gxg|h}jllll~kmlj~oopkgzg{h|h|i|g{kj~j~i}jjk~h|kkj~kmlolnnnpooppptvvw{}~ǃɉ̇̈ɋʎɍȏȑʕʕȕȜǝƜǝǡʠɥ̪̪̪ʨŵ޶ذ֥եմ௵իܥ㝭ڤ며嚩斬쑪-=g/>g,;c+:e-k.k1?l0>k1>k2>l4An4An2@m3An6Dq9Gt9Gs:Ht8Er;Hp:In=Kp?Lq?LoBOpGQpGRpFQpGRnHSoHSoJUqHSoEPlFQmCNjEOlANjAPmAPmAPlANl?Nm@On@On@OmDSsDSuCRtCRtDSuCRsCRtDSt@Op@Op?No=MmJuMy>My:Kt;NvMu>Mv?Nz?NyMu=Lv=Lu@OwBPz@OxAPx@Ox?Nw@Ou>Mt=Lu=Lu>Mt?MuANuBOuAPx@PvBPt?Ot@Ow>Pv?Qw?QwAPv>Pt;Ls>Mt>Mt=Ls>Mt>Ms=Ls=Lv@Nz=Mx;Mx=NyKx?Kx=Kx?Mz?Ny@PzAO{BOyCPxFQwDPwDRvDRuDRuCRtBStBRtESvDStBRuAQuBRw>Qw@RxCU{?Rx@QxESzDSzCS{CS{DS|ET@R}AS~AR}AQ|CR}CQ|CR}BR}>O{?O{AN}?M}@N~CQBP@N{ANAP@Q>O~@Q?P@Q?Q~;N|=O|=PzQ|>Q}>P>P}=O~=O=O=Ot-=t-u-v+>v':s(;t':r*?r+>t-=v->v.?w->v.?w0Ay0Ay1Bz1Bz0Ax-?w,?w0Ay.?w.?w/?x0?x.=v.=v/>w.=w/?v,x+>x+>x*>w,>x-v.?w.?w->u,?w0Bz1Bz1Bz2C{2D|0C{1D|1D|3F~2G}1F}0H}0Ez1Fz1Ez3G|5H~5H5H4G5H6I5G7G5F8I:K7J7J7I7J4I6K7L8M8M8M9L:LR?O?QARBT@SCUBTCT?Q@RBTCUBTDSDTBTBTCUBTBTDTDVBTDVBTBUAU@UAUAX>U@V@S@TAUAUAUAU?S@TBVAU@TBVBVEYIZHZHZGYHZH[G[I[HZHZJ\J\J\J\I[GXHYIYIZHYHZK]I\GZJZJ[IZGXGYG\DYBWBWEZDYBWDYDYDYAW@V@U@VAWAVCYDYBXBWCXCXAWCXAVAWAV@UAVBWBWAVDYDYCXDYFYFYFYHZG[FZFZFZFZFYEYCWAVAVAU@U@VAVBWAVAVAVBVEYEYFYGZCYCXCXBWCXCXCXDYFZGWHYFWGXHYGXFYDXDXFZDWDYDYDYDYDYDYDYF[CXD[CXDYDYEYH]G[FZFZFZH\FZG[G\G[G[H\IZIZIZG\H]FZF[H]J^K[IZK\K\G[F[H]I\K\IYK[L\L]L]J[L]K\H]I]J_K_H\G\H[F]J^K_I]G\I]FZI]H\H\H\H\H\J^J^I]I]J^K_L_M]M^M^L]N_N_L]N^K`L`N^N_N_M^M^N_M^O`N_L]O`J_L`K_L`LaK`L`MaL`NbNbNbL`MaMaMaNbL`NbPcNbMdJaL_OcKaPbNcNcOcMaMbMbMbNbOdOdPeQePdPfOdMbMbMbOdPePeOdPeOdOdPePePePdPfTgPdSgRfSgRfThThThTgShRfThSgSgThThUiUiUiWkWkVjWjWiViUiThVjSiVkVkUjUiWlWlTiWlUjWlViXkWiWmWlWlYnWl[mXkYlXkZm]m_p]m[pYl[mYoYn\q[p]rZq]q\s]t_r`tdubucs`qararct`u_s^r^r]s]p[nZmXmWlWmUkVkVlVkWlWlXmTjUkVhVhRdRhPePeShPeOcOdNcNcOdPeQfQfThRgPeThShQfRgUiUiTlTlVjVkUkYjYmYn\m[l_p`r`qarctdudtdueuevdtfvhxhwfyfyexh{h{h{k~mllnqrpoi|h{f{f{d{c{eydzfxaxavcxavdxe{f}ii~ji~llkkklniizg}i~i~h}ml~i{i~i~jnomnpmeyg{g{fyg{g{i}g{nllkh|j~mj~kkmlmnqoopqssvtsvwx}}ɁɃɆʌ͌ˏɎǎɔƓĕƘɘȜȜŞȠɞȣ̧̧ɩǰ᭰֫ׯةյ䪭ե٭嚧ߖ甩픬.:d-:d.;e,j.k0>l0?k1@k1?k2@m1?l3An7Er6Dq7Er7Fs7Fq8Fq;IqLr=Ko?KoBOoBOkFQnEPkFQmGSoJUqHSoFQmGRnFQmCNjCNjDOkEOmFQoGPn@OlAPpAPoAPoBQpAPoBQpBQpCRqDSrDSrAPnBQqAPr@Op>Mn>MnLp:Hn;Ip9Go8Hq9Iq:Ir;Jr;Jr;Jq:Iq;Iq;Kp;Lo=Kp;Ip=Lt=Iu?Mv=Mv>MvNw@Ox=Lx=Lx=Lv>Mv=Lt>Mt?Nu?Nu?OvAQwBQxAPw>Mu>Nu?Nu?Nu>MuAOu@NrAOtAOt?MuAOuBOuAOt@Pv=Mt=Pv@QwBOwAOv=Mp>Nq=Mr?Mu>Mt?Nu=LsLy=Kx?Mz>Ly>Ly>Mw@Mv@MxAOw@OuAOuBPuCQsCQtFTwBQsETuBQtBRtBRvARw?QwBTz@RxASy@Sx@RxBQxBQ{BQzBQzCS{?P{CT?P{CS~BQ|AQ|CR}AQ|P~?P~AR>O}>O}?P~>O}=P=M|;M{P};M|;M};M~9K}t.?r*:q,=s'v+=u,=u,=u->v.?w/@x0Ay.?w0Ay0Ay/@x0Ay,=u.?w/@x.>v/=w/>w.=v-w.=v.;u0:u0;u/>w,w+>w->v.?w.?w,=u->v.@x(=u0C{1Bz3D|2Bz2D|.C{1E}3F}3E}1G~.F|/G|2G~0E}1F}4H}5I~4G6I5H4G4G6H8I6G6G5I6I7J8J7J5J5J6K9N7L8M6I7J;K9J8I:K5H7J7J9L7J8L8L8L9M7K8L6J9M5I~:N6J9M8L6J7K5H5H5J~9M6J6J6I~9J6K~6J7J5H5H2E}3F~4H2H~5J~2G{5K5J6L6K6M7L5K7N~6L:O7L9O9N9N9N9N8M:O:O:O9N9N:O:O9N:Oi.=h/>h/>i.>h.=h/>j.=i/>i.=h/>i.=h/>i0?j.>j1?m0>k4Bo5Cp5Cp8Fs9Gr8Ep8HpMl?Nm?No?NoKu;Kt:Iq;Jp:KnLr>Ls=Kr=Kq>Mt=Mw>Mu>MvLu@Mx=Mu>Mu>Mu@Ow?Nw?Nv=Ls?Nu?LsCPwCQw@NrAOs?Mq@Nr@Ms>Ot?Nr?Mr@Ns@Ns@Nr>Lr>Lt?Mq@NsBRv>Nq?Os@OwOr>Nr>Mt=Ls>Mt>Mt=Lu>Mx>Mx=Lw>Mx;Kv:Ku:Lw>M{Ly?Ly@Mz?M{=Lw?Lw=JuKu>KtAOuBPtDRvBPuESvBPsCPsBSuBRv@PuASy@RxASy?Qw@Ry@QwBQxCRyDR{@Pz@R|AR}?P{?P{?P{?NyAP{BQ|AP{BP|@N{AO}CP@M|@N}AO~AO~>P~=N|?P~?P~AR=N|>N}@M}=N:M|;N|Q|>Q|9Mw?P}=N|9Ky:L{;M}9K}9K}u.v,=u->v,=u->v/@x.?w/@x.?w.?w0Ay.?w+v->v.>w,;t.=v-x*=w(;u)v.?w->v,=u,RRg2Bl4Bn4Bp3@n6Dp7Ep6Cp9Fr8Fn;Io;Il=Kn;ImAPqBNmDQnFQoFPmFRnDSnESmGQmGQnEQmEPlCNjDOkEPlEPnEPnDNl@MkNl?Om@Pn@OnAQoCRqCRqAQoAQpAPo>Nl?Om>No>Mn;Jk=LnLqLsMv?Nw;Nr;Mp=Mq=MqKq=Kt@Mx=Jt>NvLr>Lq@Ou@OuAOtCQv?Ms=KqBPu@Mt?Ou>Nq>Lq=Kp>Lq>Lq?Mq@NsAOtAOt@Os=Mq>Ns>Nu;Nt=Ou?MuMt>Ms>Ms?Lw=Lw>Mx>Mx>Mx=MxKx=Lx;LxKv=Ju@Mx@Lw@Nu@NtESyBPwBPuAOsBQu@PsARt?Ou>PvASyASxBTwASx@RxBQxAPwBPw@Qw?Rz?O|@Q}>O{@P{AP{BQ}BQ}CR~AP{?Nz?M{>O}?O}BO?M|>M|>P~>P~>O}?P~=N|CT?N}@N~=M}=N{Q|>Q|Q|=P{;Ny;Mx?O{N}9Ky:L|;M~8J|9K}9K}9K{;M~7I{2Dv3Ew3Ew6I{5Ez0At1Bu0Av/@w1Bx0Aw/@u->r-v->v+v+v.?w->v->v->v->v,=u,>u.v';u)v,=u.@x->v-=u/C{.Ay-@x0C{4E}7H5G~1E}-@x1F}.Cz0F|1F~4F~6G}4E~0D|0C{0C{0C{4G3F8I4E}4G4G5G6J5J6K5J6K7L5J6L6J5H7J8H8H8I5I5H7J8J7K7K8K7J8K8K9M7K5I~7K7K7K7K8L9M7K4H|5H~4I~4I~5H}8L5I~6I~7I~6K}7K~7K6I5H4G3F~6J5I~6J5I~5I~8K7K8L7K8L7K8L:O7K8K7J8J7K7J8K5J9N:O9N9N;PN=OQ@P>PAT@RCRCTCUBTEWDVCU@QDTAS?T@T?T?S@U>RCSBTCU@R=R>R>R?R>S?S@T@T?SAU@T@TAU@U?TAVAVAU@T@UAVDXDVEWDXEXFZFZGYHZGYGYGYHZGYHZI[FYEVEVHYGWHXFXHZJ\I\IXFWGXEYDXEZCYAVBWBWAVAVCXCXBWDYCX@UDYBWBWCXCYBYAYAX?WBZAYAXAXAV@UAVAVAVBXAWAUBUCVFYHXGXHXIZEZH\F[F[HYGXHXDZEZHYIYJ[IYDZF[EZDYFZFZEYFYDXCXCXCXDYCXBXE[FZDYIYGXGXIZGXHZH\H\G[FZEYDZCXDYCXF[EZF[G\G]C[EZF[EYG[I]I]FZH\J[K[K\H]H\I]H[HYJ[IZJ[HYIZIZJ[JXIXJZIZKZG\F[JZK[J[IZIZJ[IZIZK\J[L]L]J[J[K\J[J[EZJ^H\H\J^H\H^H\LaL\L]M]I^H\FZI]H\J^I]J^J^I]L[L]L]K\K\L]L]L]I_J_M^M^M_N_N_N_N_N_M^N_L^LaMaK_OcK_K`K_NbL`L`NbMaNbMaNbL`K`MbNbMcLbKaLcNbL`PaRbPaN_RbNcOcLaQfK`OdPdQeRfPdQeQdOdQeMbPeReOdNcNcOdNcPePeOdQfPePeSgPfOdQfQfRfQfQfQfRgRgSgVjTgRdQgPfTgUiViSgThXlVjWlSiRgUjUkShUjVkVkUjVkWlWkYlXj\l\n[k]mXmYmZmXlZoXkYl[m\m\m_o[qZnYnZo[o\p\p^r^r^r^sbrarctatewcubt`tcsbscs_t^qWmVkWkTiTjRhVlWmZpVlXnUkUkSiNeMcPeOdOdMdMbMcKaLbKbMbLbKbKaKaMcMaOdPeQfQfPePeOdRgQfShTiShVkVhVi\m[l[m_m^o_p_p`q`qbsbsarctbsductevgxevevcwfwhyhxfvgwjyh}k~lj}lk~lnnmjcxdzcxexdwhyexdw`wcycxg|h~ijh}i~j~kmmjilklh}i~g|ki~g|g|f{i~f{kkoqpplfzg|i~fzh{fzi~h|jiikk~jkkmmooqsrvtsywwvzzz{}~ȄʈɎΐ˕˖ɞ̣ͳղЩʪάШϬάӮڪܥءү頥ܖ晪럱䭼ﳺꜧݖꔬ霱멼줷ꛪ嘩⛮㠳馻了兩虜﬽/c0>c0>b0>c/>e/>f.=e,h/>i/>i0?j1@k1@i/>i0?j2Ak5Dn4Dn4Co6Do9Fo8Do8Fm;IlLoAMo@NmDPmFRoCRnDRmDRmDRnDSnFRmFPlDOkFQmFQmDOkDOnCNlDOmCNm?Lm@Mm>JkAMmAMmBOoBOnANnCOpANnANoBNnANo@Mp>Jl>LmLq?Mr?Ms?Mt>Lr>Ks>Ku=Lu;Kt:MqKu=Jt?MvLo@Ns>Lq=Mt>Ls?Mq@Ns?Mr?Mr@Ns?Mr?Mr>Lq@Ns>Lq>Nq>Os=OuMs=Ls?Nv?Nw>Mw>Ju>Lv?Ny?Ny?Ny>Nx?Ny>My;Mx:NwJu@My@My?LwANw>KvBOxBNyCPxBQxCQx@Ow@Ow=Mt@RxASy?Qv>Pt@QtBQvBQx@OvAPwBPxAQw>Qx>Px?Ry@Ry@Ox@Ox@OxBQ{@P{@Oz@M{=O{O}Q|;OyQ{O~@O}:Lz9Ly9Kz7Iz8J}8J|8J|7Iz4Fv5Gz5Gy2Dv5Gy3Ew2Dv0Au1Bv0Au/@t1Bu/@t.>r0@t0@t.=r,v->v,=u0Ay.?w->v->v->v-=v-w)v,=u->v.?w.@x.Bz.Ay.Ay0C{2C{4E}3D|3D|/C{/E|.Cz1D|2E~4F~4E{3D}3C{2C{2C{0C{1F|2E{6G4E}2F~2E}4G4G3H3H1F}4I6K3H5J5I5H5H6I5I6I5H6I7J6I6I8L6J8K7J5H8L6J~5I|6J5I~6J6J6J7K8L6I1F{4I~4I~7K7J8M8K9J6J}5I}5I~6J~4I}5H~5I~3G|2F{6J6I~5I~5I}4H}5I6I6J7K~4H|4H~4F6I5H5H8K6I8K7M7L8M8M:O:O9N;ON>O?P?P?O?P@PAQCRCSAS@RASCUCUAS@SDVAT@S@T?U=S>R?R@U?T>S>R?S>R?S>S>R>RAU?S>R@T?S@TAUATEUDUDTBSAREWHYFXFXEYDXBYBXDXEYHZHZGYGYHZHZI[FXI\JZGWI[HZHZHZGYFXCXBVDWCXCWDXAVBWAV@UBWBWBWBWAVBWBWBWBWBWCXBWBWEZBWCWCXBWAVBVFZBVCUCVAVAVCXEXCVCVCVEXIYFWGXIZHYIZFZH[L[HYHYGXHXHXHYGYGYFWHYGXHXGXFZEXDXD[DZCYD[CXCXCXDYF[GZI\IXIZHYGXGXHZF[G[I]H\EYDYCXDYCXEZEZEZF[G\C[G\E[EYFZH\I]I]K[IZK\J[K[J[H]H\IZIZIZJ[J[J[I[IZHWJXJ[I[G\G[H\G\H[K[K\IZIZJ[J[J[IZIZK\J[J[IZK\HYK\G\I]G[I]K_H^K^K]M]O^J\K[I^K_J^H\J^J^J^H\I^L\L]K\M^L]L]L]M^J_M]M^M_O^M_N_N_L]N_M^M^N_J_MaNbL`NbNbMaNbL`L`ObMaNbMbMaNbQ_QaL_NdNdMcLcNbNbQbQbPaRbNdNbMaMaNcMbNcPcPdQePdPdRfOeNeObPdPdQeOcPeNbRfQfOdPeOdQfPePcRgRfShRfRfRfRfRfQhRfPeRgPfShShPeRhQfRgSgTiUiWkThWlTiShUjUjTiTiVkRgTiWlVkWjYl\m[l\m[lZkXmYnZn\l]mYn[o]m]nYoZnZn\p_pYn[o^r^r^r]raqararbsgwfxbtcucweududvau\rXmUkQgShShRiUkVlUkVlTjRhSiTjNdNdMcKaOdJaJ`LbLbKaKaLaLaKaKaKaMdOdMbNcPeOdMbOdNcNcPeRgPeShVkTgWi[lZl^l_m_p_o_pbr`pcscsaqduarduevevctcudsctgxfwfwhzixjykzj~mlk~k~j}mlmjezf{ezdwbvjzh|i|fyfxg~g|f{g|g|g|i~h|fzi~jjihh|jjkh}g|f{f{g|f{f{h}lkoqprqkif{g|i}i~ih~ijikk~jlkmopqprurtutyxy{x{||ȀǁƄ̋̏ґЖϕǙǥ湾ӫ̭έЫϰ׫٤ڟڞڧ楲ࢮ昨鏥옱蟰๿ݙ됦ⓥܘ䟲ꥹ例ꛪ坫垲쟲飵꘬蟲떪䘩ᖣۙܝ឴栴颶뤸좵ꤷ쨼ꥺퟳ磳鬼2>d4Ag1?d2@e1?e0?g/>f0>e/=d/i,;f.=h-g.=h.=i/=g0>h0>g1@i2Ak2Am4Co2Am4Cn7En8Dp8Fm6Dl:Hm:HkLl@Mn@Nn@NnBOoBNoAOnBOoBOoBOo?LmBNq@LoLq>Lq=Ks>Lt=JuLu=Lr:Kn:Kl9Jk=Ko@Ms=Hq=Js@Mx=Jt;Hs=KsKr@Lu@KtAMr?Mo?Mp>Mq?Ms=Kq=Lu=Lt@Nu?Mr>Lq?Mq@NrAOs?Mr?MpAOq=Ko=Mq=Mr:Nu;Mu=Lv?Nw?Nx?Nw?MwALw@Mw@Mx?Ny@Ny@Lx@My>Mx?OzMu?Nx=Jw>Kw>KxANz?LxANz@MyBOyDQxBOvAPvBQw?Nt?Rx>Pu?Qu>Os?Ps@OtBPx@OwAOwAOw@Qw?Qy?Qy@RzARz?NwBQzAPy@MvAOy@Oy;Kv=Oz:Ly>O}@M}?M|?M|=KzO};Lz>O}:Ky:Lx;Ox:NxP~r.=p.?r+=t(:o*w->v,=u,=u.=u-;r-;r/t*;s*v,=u/?w-=u-?w,?w-@x.Ay1C{5E}4C|4D|3D|.Ay1D|,By-Bx/Dw0Dy2Bz3D|4E}2C{2C{3D|3E}0Dz2D|1D|3G2E}2E}2E~2G~2F~2G~2G~3H2G~3H4H4F5H6H6I4G4G4G8J5G7J:N5I8J8K6I5I~4H|5I|4H}6J4H}5I~6J6J6J4I~2G|4I~2G|6K4J8L6J9J6J|7K}5I~6J3G|4H}5I~2F{6J3G|5J2G{3G}4G6I3F~4G4G4G6I4H5I7J5I8K8K6I8L6L7L:O9N9N:O;O;M>N>O>N=K=N>O@O@PBRAQAQBRASBT@R@SASASAR@S>T=T>T=S=S>Q>S?S?R>R?S?S?S?S>S>R?S>R?S@T@TAUBV@TARCTCTEVDVEWEVGXGXF[CWAYCYDXDXHZGZHZHZFXGYJ\EWJ[HYFXI[HZI\GYGXEWCWCWDXDXBV@U@V?W?T@U@U?TAVAVBWAW@U@U?TCXCXBWDYBWBWCXBWDYCWAVDZBWDVCVBWBWBXBWCVDWCVDVHYGXFWJ[HYJ[G[GYIZHYIZGXGXI[IYIXIYGXHYIZGWHXDYCZDZE[CYCYCZDYDYDYBWDYEZFZG\K[HYHXGWGXEZGZH\G[FZDZEZDYF[DYF[G\G\H]E\F[EZDZI]FZH]H]J[J[K\L]K\I[G\I\K\L]K\HYIZJ[IZJZJXJYJZGXF[FZFZH\G[H[IYIYJZJ[J[J[J[IZHYK\J[K\JZN^IYJ^H\J^H\G[F\I^N_N]N^N^J\I^J^I]H\H\K_H\H\I]L\L]L]M]L]L]L]M^M]M_M^O^O^O^N^M^M^M^N_O_PaK`MaMaMaMaL`MbL`KbMdLcNaMbNaNbNaO`P_PcMcKbKbLdM`LaRbPaQaMcNcOcNbNbNbNaRePdPdOcPePeQeOdOdNcOdNcNbOcRfNbPdQfOdOdOdQfPeQfPhOhOfOgPhPhQiPhRhQhQfQfRgQfQfQiSgQfQfShViUiUhUiTiVkTiShTiUjUjVjShTiRgTiVhXk[k\m]j]k^n^n[k\m\lZk[p]m\m`p[o\p[p[o_p_o\q\q_t`saqbtaqetbrgvdvfxexcxfvgweu_tZpTiRhPfQfQgOeSiRhRhRhPfQgPfNdLcKaKaLbLbMbLaK`JaKaLbLaMbKaLcMcJ`LbNbOdNbNcNcMbNcMbOdQgPfSiRgViXkZj]k_n^lZo]r^r]r_s`tav`tbsarbsarctdueudtdvevctgxgxiyfvl{h|g{j}j}j}j}llj~j}h}dycyexk{oi|kfygzhzf{f{dyf{ezf{eyfzfzh}h|g~g}h}h}ki~f{g|czezg|f{f{i~jknppttqmj|i~hf|g~g}g}h~hlklhkmopptvuvuutu{}{{Ā||ƃȄȈ͏ѐϐʕ˖Ŭڿּ֮Эͯѱףҥޭퟮ蓡ގ㎡狣ꉦ썧ꔨ鰼ଲܟ♤蓩蒪⒡ݐَ֍הܕ暨䘨䞮뗨䒟ݍّܑܔܓߝ衶쑥ᓦᓥߘ硷阨㗥ޔٕטޟ坳衵ꤸ훰衴衶袳䦹骻쪻5De4De2Bc4Be3Bg3Af2@d/f-e-Ah,>f+?f0@h2>i3@i1>h3@i4Ak8En7Fl8Fn:Hl:Hm:Hk@Mo@Mo@LlEPpAMmBPmBPmBPm@QnGUsESoESmFSnGQnEQlHSnKSrFPoFOnEMmBLjBMjALiBMjDOlDOlAMkAOkAOmANnANn@Mm@MmANn?Ml?Nm>Ll?KnLqLq>LqLr>Ls=Ko=Kp>LoKl@Mp>Kn>Kn?Lo>Km@MrANs=Lp;In=Kp>Lq=Ks>Ks?MwBOv@Nu=Kp>Or=Mq>Oq>Lq>Ls;JsMv=Lu?Nw=Lu=Lu=Lv@Oz=Mx>Mx@Oz>Mx>Mx@Ny=Lw?Ny=Kv?Mx;JuMxKy?Ly@Mz>Kx@Mz>Mz>LyAO|@Oz>Nx?NxBQz=NwPw>PuOt@Qt>Qt=Ps?RxPx=Ow>PxAPy@Ox@Nx?My>NxL{>K{=Mz;M{OO}s/@t->r,=q+r'r(v+w,?u,=s);q)=u*O=N=N?P>Q>PBT@RBRBRBR@SBSASBS@T=QAU>R=T=S=S?U>T;Q;P;Q>R?S?S>R>R>S@Q@T?UAU?S?S@TAU@T@TGUGVHWFUDWEWDYDWAVCVFYDXFZEYGYIZIXI[GYHZI[I[F[G\EZEYI[HYJYH[CXEYCXDXDW?T?T@U?T?S?T>S>S?T@UAVAVAUAU@VBWAVCXBWBWDYDYCXCXAVCXCXCXCXAWCXDYCWFXCUCVBUCVDWDWEYEXGXHYIYJ[IYHYIZHYGXIYIYHYGXGXHYHYHYHZCXDXDXEYCVCYDZCYF]EXDYEZBWG\G[EZF[G[F[F[G[G[EYFZE[BWDYEZDYEZF[F[F[F[EZF[G\EZH]GZH]K[K\HYJ[IZJ[J[K[K[J[IZK\J[I\DYJ]K\J[IZH]FZEYG[FZFZI]FZFZG\G\G\G[K[J[IZJ\K\IZJZH[G\F[F[G[I]H\I]I]F\I]J^K]J\M_M_N`I]I]G[H\G[H\I]I]H\L]J[M`M]L]O`K\N^M]M^M^K`K_L`O`O`M^N_OaJ_L`MaMaJ^MbM`P`N`MbL`LaL`NaMePfMcMdOdNbOcNbNbObP`P`QbQbQbQaP_NcOcPdNbOcQeNbQeQdRbRbRdRbQaSdQaP`NcPdQeSgOcQeQeNdOcQfPeQeQfQeQfRgQfRhOcRgQgSgPgQhSiRgQfRgPeQfShRgRgRgSiUkTiUjUjShShTiTiQhRgQfQfRgRhTiWjZm[pZoYnZo[pYmXmYm^p\n\mYo[o]qZm^o]n^o_oar`qbqdtbrbsbtbsdufxdvgyhzgzdv^t_s[nRgQfRgNdOeOeNdOeNdNdNdOcMaOdNcKaLcLcKbNeKcMdKaKaJaL_J^K`J`McLbKaMcNdLbLdMaLaMbLaNcQdReRdXhWhYj[lWj[o\n]m[q\p]q]qauaq`o_qct`qctbsctctductdufwfwfwfwfwizizl|h{j}j}k~lj}kjdydycyewi}k}mm}m}jzexcxdybzdzezdzdxdxfzcwcyfzg{ezi~h}g|f{ezf{g|f{g|h}g|h}mlkmptpmmj~lkjhg}ih~ihhllmppssxwyxwuxy|~}ŀ|}~ʄΈЌ΍ΌʕΘǤҴܸⶽ宻ꍡ剠膢숣쉦뛲㰵ߚ鋛،ՌՋҌҍӎ׏ؓܕߕߍՌԋՐюՏՔߛ蠳훭얩敦䏡ܒُ֎֒ܜ碸闦㔧ܑ֑֑֚ߛ垲硵顳젳꠳휱Ʝ뛳잴蟰ܹ㬸窽亮飶㨹孽6Gh7Gh6Eh7Eg5Cg5Ch4Be3Ac2@d3Ag2@e0?g0?g/>g/>h.=f-g->f,?f/>f2>h5Bl3@j2?i4Bl7Fl7Em7Ek8Fl9Gl8Fj=Km=Jk@KkCNmCPm@OkBPmCQnAOlCQnCQnCRlDQmHRoHRpHPnJSpKSpKSpFPpEOmDOmBMkBMkBMkCNl@Mj?Mj@Nk?Ll@Mm@Mm@MmANn@Ml>Nl=Ll?No=Ln=JnLs=KrLqLq=Kr?Mu>Lr=Kp>Lq>Lq>Lq?Lq@Kr=In>Km>Kn=Jm>Km@Lo>LnKp=Kq=Kr>Lt=Ju?Ku@Lw?LuAOv>Mt>Lr=Ls=Lt@Ov=LuMvMx?Nx>Mx>Mx=Lw?Ny=Lw=Lw=Lw=Lw>Mx=Lx=Jw?Ly?Ly?Ly?Mz?Mz?MzAO|@O{@OzAP{?Nx=MuPu=Ow=Ow=Ow?Qy?Px?Nw>Mv=MvO}r*;o/@t/@t.?r.>r-=r+:q*:p*;q):p*;q);p&;o&;o&;o's&;p'v*:r*;s*;s+:s(7p):o):p(9o(:p';p';q(:r*:r&7o*;s);s%9r'9t(O>O?P>Q?Q@R@SCRBR@PBQCUBTAT>RT=S@V?V>R=RQ?S?R>R?S>S>R?T?U?S?SAU@TAU@TBUEWDVDVDVEWFXBVDWDZCYEWEYG[FYFZG[H[J[I[HZHZI[GXGZG\H[HZGYGZHZDXDXCWBVAV@U@U>S=R>S?T=R=RS?T?T>TAS@S@UAVCXBWCXCXCXBWCXBWBWCYDYCYDYDZCXDWBUFYDWEXEXBUFYEXFYGXGXGXFWIZIZGXHYGXHYHYGXHYIZHYGXHZEZDXEYDXEYCXDZDZCYCXEYCYCXDYDYFZFZEYEYFZEYEYFZG[FZCXDYDYCXEZCXDYDYEZEZBWEZF[F[EZGZI[IZIZGXIZJ[IZJ[J[JYJYI[M^IZJ\F[I\JZGYFZH\G[FZG[G[FZH\G[H\FZFZH\H[IZIYJ[L\K[IZK\J[H]G[I]I]G[H\G[H\F]G[I^L^L^L^J\ObJ_H\I]I]I]H\H\G[M]K\L^M_L_L]N_M_N^O`L]M^L]K`N^M_M^N_M^M^MaL`K_J^L`K_NaQaO`LaMaMaNbMaMcLbMcMcObNbMaNbNbNbP`O`PaPaO`O_LaPdNaMaPdPdOcOcPdNbScQbN_SdO`O`PaPaQaNcNcQfSgQeQfQeOeRiPgPePeTiRgQfQfRgPeRgPeQhQgRhQhQfQfRgRgRgPeQfTiUjTiUlThUjRgTiShSgPhPgPeOdRgQfShUjUhUhZnXmYmYmYm[o[o[o]o^oZp[o]q]q]n]n]nbsar`rbreueuductcteufxewfxgygyhzgy`pXmWjPeOdNbLcLcKaJ`J`KaLcNdNbMcNcK`JbI`JaMdKbMdLcLaKaJ`I_I_LaL`J`J`KaLbNdNdLcKcLaLaK_NdOaPcSdVgYjXiYjZk\m\l\m[o\o^r\q\qbr`r`rctar`qbsctarctctbsdudufwhyk|j{k{fzh{k~h{j}ll~kkg|cxbwf{k}k~mll|iydxcydxazcyax`vavaubvavbwdycxcxezezf{f{f{cxf{f{ezh}h}f{jjnmonrsrmlji~i}g}h~ig}ijllpoppswxyyvvwy|ǂ|}~~ʄ΄΂ˈʈɊƍȒʢخ⮸Ჺ䟩Ӫݝ敨掣ލߑ폧팧둩풩塱굵Ы飧ўܞ韯씧ퟴퟴދ׌ՈЊъщЊψӉӌՌԌԋӋЋь҉ύՌۏޑݐی؏ڍҏӋύԐ܏ܒ⚯韲혫昬目ꚮ阬癫퓧㓣ޔڒ׎ӑԑԒݕݚ梵잱읱띲韴랱޳੹ꪹ쨹죴ޱ类㬻㩷9Gi9Hi8Gh8Fj6Dg6Dh6Dh5Cf4Bf4Bd2@d1Ae0@e1?g0?f0?g1=h.:d/=d1?f/h2?i3@j4Bk6Dk6Dj8Fk:Hm8Fh9Gk9Hj>LmMmLnLp=Ko?Mr=Ko=Km>LnMv>Mx>Mu>Mu>Mv=Lu>MvMv=Lu>MvMv>Mx=Mw?MyMxLwJwLy>Ly>Ly?Mz?Mz@Oz@Oz>Mx@Pz;Nu>Pw=Ow=Ow;MvLs>MsNw>Px=Ow>PxOw?Nv>NwL{r0Au->r->r*;o,=p,>q,>q-=q,u(=q)v-@x.Ay,?w.Ay1Bz/@x/@x3D|2C{3Cx/Aw.Cw/Dy.Az0C|/B{2By0Ay3D|2D|1C{3C{2C{3D|3D|3D|/Ay/D{-Cy.Cz0E|0E~0E/D}1F3H3G1G2G4F~4H3H2G~2G|5I{4H}5I~5I~5I~7K5I~3G|2F{4H}4H}3G|6J5I~2F{6J4H}6J7K7K6J5I~6J4H}8H4I}2F{4H}2F{3G|4H}4H}4H}4H}2F{1Fz4G|5E|2E}2E|2E}2E}3F~2E}4G3F~5H5G~9J6J7J9L4G7J8M:O:P:N8MN=N=N>O?P>OAS?Q@RATAQAR@PBSAS@S>R=R>TT=ST>T=S=T>T?R?R>R>R@V?SAUAU@U@TBWDYDVEWHZFXBTBVCWCXBXCXBYDZCZDZCYDZEYEYEYFYGXGYHYH[EZEYHZK]I[EWCWDXBV?T@U=R=R>S=R=R=R=R?T>S>S>S=RAUAT@R?UAUAVCXAVCXAVBWBWBWCWDVDVDWDWEXCVDWCVEXBUDWEXCVGZDWEXGWGXGXGXGXIZHYGXHYHZK\IYHYIZGXHXGWDYEYEYEYEYDXBWBWEZCYDZBWBWCXBWBWFZDXEYCWDXFZFZFZEXCXCXBWBWCXDYCXCXCXCXDYDYEZEZEYFYFYIZIZFWIZGXHYHYIZLYKZIZJ[J[IZG\I]L[F[FZG[G[FZFZH\F[F[FZFZFZFZFZEZF[GZEYH\G\H\G\G[H\G[H\FZH\G[G[H\H\H\J^J\L^M_L]L^J\I]G[H\G[H\K^M^K\K\K\J\O_L\O_N^O_M]K\N_M^N_M^M_N_L]L]M]K_K_L`K_NbJ^K`N_O`LaMaMaNbLaLcJaJ`MdNaL`L`MaL`OcPcQaP`O_N_QaMbNaMeMcMcPcOcNbPdPdQbO_RcSdRcO`PaPaQbQbQaQbNcQeQgOfQgPfSiSjQfRgPeShShQfRgRfQgRhSiQgSiRgQfPeQfRgPeShRgTiUjThVkUjRgRgQfOgNeNePdOdQjShTiUkWiWjYmWkYm[oZnZnZnYl\l\m[o\p[o\q^n_p_p`q`qcseueudtetdvdvdvdvgyi{i{i{j|ev^oYmThOdNcK`MbLbL`J_J_LaLaLaMbNcMbKaJbKbJaKbLcLcKbMdJaI_J_H_J_LaKaLbMcNdLbLbNeLdM`LaLaNbRdOcRcSdWhXiYjYjZkYj\mZn[o\o[n]n_p_p`qarbsarctbsctctbsbsduevctgxj{gxiyfzgzh{h{j|k~k}mmh}dyezcxh}m~j}hzdxexcvbwbw`xbwcxbvavbwbwavdycxbwbwdyezezf{ezg|g|ezg|f{f{h}jllknnqoppplg}i~i~h}i~jljmlooqsuwwyyvvx|ǂȁĀǁʃ΃΄ʇ̈ˈȌ̋͒Ԛז֜ےُ֑֒ގ⌟愙ׁ܉䐧퍨홭ևޘ蕥쏢蓦펦路쏠⍞ۍۋډֆ҈҉І̅̄́̆̇̇҈҉ψΊЉϋщЉԋԊԍ֌׋ӊЌьэЋψь֍ۑޓގٍ؏ےݒێޑڑ؏ԏԎҏԓڔڑޒޖ㚭皮靰좰樷Ȿ갾9Hh9Hi9Hi;Jk:Ij9Hi9Hi7Fg7Fg5De4Be2Cf1Bg1Be2Bg2@g1@g2@h/=d/=e/=e.d/=d1>g/;f0=h0?k0@j2Ak1>g2?i2?i5Bl7Ek8Fk7Eh8Fi8Fi8Gi9HiLk?Nm=Lk>MlLp?Mp>Lo=Kn?Mp;Il;Il=Km=KoLsLq;InMx=Lx;Ju=LvMu?Nw@OwMxLx>Lx=KxMx=Lw=Lw?Ny=LwKy=KxLy>Ly?Mz>Ly?Mz>LxAP{?Ny=Lw=Lw;Kw9Lv;Nu:Mt=OvMuPw=Ov;NuO{9Kv;Mx:Lx:Lwr->r0Au->r->r,=t,=s/?v/=s+;o*:m,w+>v/Bz,>v/@x1B{0Az2Cz.?u/@v0Aw0Aw/?v.?v/Dx/Cx.Aw-?y0@y/Ay/Cz/By0Ay1Bz0Ay3D|0Ay/@x-@x-Ay/D~1F.C|.C|/D}/D}3H1F/G3G4G~0D{2G~3H2G{3Gz2F{4H}4H}3G|4H}4H}4H}3G|1Ez1Ez2F{1Ez3G|3G|4H}5I~3G|5I~6J4H}5I~5I~3G|5I~5I~4H}4H}1Ez4H}3G|3F{2G|4H}2Ez2F{3G|0Ez1D}2E}3F~2E}3F~0C{5H3F~5H5I6I:N6H8K9L7J9O;P9Q:O9M;O9L:J;L=N>O?P>O>P?QAR@OAQCSESARBR@S?S=RT;Q=S=S;Q>T=S=T=S=S=T>S?S>Q>U@T@TAUBSASEXBVGYFXEWFXBUCXBVBWBXDZDZCYCYCYDZCZBXBYE[F[EYGYHZI[G[IZGYHZGYFWCXBV@V?T@U=R>S=R=R=R=RS>S=R?S@SAT@U@UAV@UAVBW@UAVAVAVCVCVEWGXDWDWCVCVDWCVCVFYEXEXEXFYEWEVGXGXHYIZIZHYHYHZHYFXF[JZIZHXHYCXDXH\CWEYDXBWCXCXCXAVBWBWBWAVAVBWDWEYFZEYEYEYG[FZCXDYCXCXEZCXDYDYDYDYCXBWEZDYDYDZEXEWGWHYIZHYJ[K\IZGYIZHYHYJ[L]K\K[IZH]G[G[G[FZFZH\H[FXG[H\FZG[H\H\G[H\G[I]FZG[G[I\H\I]H\H\G[G[H\H\G[EYI]L^I[L^N`OaJ\L^J`G[I]I]G[H]J[K\M]P_M\L\L\O_N^QaO^N_N_N_M^M^N_M^N_N_M^K_K_L`K_L`L`J^N^M^L`L`K_MaL`L`M`OcK`MaL`MaNbMaOcOcOcOcOcMaOcMbLbMcNdNeObPdPdPdMbQcPcSfSeRcTeTeRbRcTdRcRbOcRfOfNdQgRhTjPfThQfQfRgRgRgPeQfRiSjQhRhRiRhShRfQfQfShRgRgShUjShTiShTiOcNeNeNeOfNcPeShShVkVlWiXkYm[o[oZn\p\p\p]q[oYn]q[o\p`o^oarbqaqbreudteugweuewfxfxhzgyhzi{i{k}gy_qVkUiQeK`MbMbLaJ_K`I^K`LaK`K`K`K`K`J_JbJbJaKbI`KbI`JaI`JaH`K`LaKaKaJ`OdNdMcNeLcKbMaLbLaMbOaRcRbTeUfXiWhZk\m\m\m^n]m^n^o^o`q`q_p^o^o]n`q`q`qbsduductdufwgxfwfzgzgzfydzh~k~mmmh}g|g|g|g|h~gygzcvcubuavavbvavcx`ubwbwbwcxbwcxbwbwbwdycxcxf{f{f{ezf{ezf{h}h}g|jmmnpppoqmj~ki~jkllmmmnqrrppvvtuvy||ƅȂǁƂǃ̅̂ʃ̅ʄɅʉ̇сʀʀ|}Ίܓ攦순悖{vry{Ղ߅ボ䇡쌥ᇔӄ˂njϏ̐˒ڏݐ抝㎣甩풦쑦쐤ꈜプނބጢ狣掦铭ꏦ항퐟ׇӇԇԅЃ΄Є̓̓ɂɂ}Ɂʅʄʄ˄ͅȆ··Έ͉ψ͋ΊψΊό̌ʈɋω͊ЊыӋЍӋьҋыҋ֋ՋԍԋόҏӏӐՒגדڍؒݖݙ䡯騳鯺誯֪ѭո㡮堰좲잮㸻俿߽괽歽=Lk:Ih;Jk:Ij:Ij:Ij8Gi9Hi6Ef7Fg4Ce3Ae3Cd3Ae2@f3Af2@d2@d0>d0>c/=b/=a0>b/=b1?d.f2?h0=g1>i2>l0Nm>Ml=Lk?Nm?Nm=Lm>Mn;Hk9Hk>Lo8Fl:Hm;Im:Hm:HmLq=Ko?Mo>Lo=Kn>Lo;Il>Lo=KnLq>LqLzMx?Ny>MxMz?MzLy>LyMw?Nx?NyMxLx>MyMu?Mu@Ox=NvLy;Iv@Mzr/@t->s+t,=s+9q-;r-=q(:n(9m*;o*;p):p(9p(9n*;o(9n):n'8l(9m*;o)9m(;o$8m%9n$8n(;q(8o+t->t->t/@v0Aw/Aw-Bv.Bw-Av,Au/@v0Az+?x/@y0Ay1Bz0Ay1Bz1Ay/@x-@x-Ay-B{0E~0E~0E~-B{0E~0E~0E~0C~4F1Ay3G1F}1F|4H}3G{4H}4H}4H}5I~4H}3G|4H}1Ez3G|3G|3G|3G|1Ez2F{1Ez3G|3H}3G|4H}6J2F{4H}5I~5I~6J6J5I~5I~4H}4H}5I~6K2G}3G|4H}3G|3G|1D}5H3E~2D|3F~1D|2E}2E}4G3F~3F~7K6J7J8K7L5K6M8O8N:P9M:OO=O>PBT>Q@RAQAQEQDRCO@Q@T>S=Q;Q=S>T;Q=S>T=STT=S?U>T=S?U=R>V=SAUBVDWFXDVEVFWAVHZFXEWFXBWDXBWBXDZBXAWG]CYDZCYCYDZE[F[E[FXGXEVHZHZFXEWDWCWCWAV@U>S?T>S=RRASATCVFVBUCVDWDWCVATDWDWEXDWCVDWFYGXDUHYHYGXHYJ[J[IYIYK]F[FZG[IZEZFZEXEZCXDYDYBWBWBWBWAV@UBWAVAVCXBWATEXEYFZFZDXEYDYF[BWCXDYBWCXDYDYCXCXEZCXDYCXF[EZDWGYJZJ[FWHYHYHYIZHZHXJ[GXJ[IZGXIZI[G[I]I]FZFZH\G[H\HZFZI]FZG[G[G[EYFZG[EYG[H\FZEYG[H\I]EYH\I]G[H\G\H\G[M^K]L^N`L^M_L]K\H\H_G^I[M]K\N^P`N^P`M]K[O_N^N^N^N]M_M^N_N_M^N_L]N_M^I^K^J^MaK_K`M`O_O`MbNbNbMaMaMaLaK`NcMbMaMaMaNbMaNbOcNbQeOcOcPcLcMdMdObOcMaNbPdOdSePbPbQcSeQcReUgSeSeRdQdRfRfOfRhQgQgRhQhShQfQfPeQfQfQfQfUhSfQdQhRhQgRhRhQfRgQfRgRgTiRgTiShShOdNcMdNeJaNeOdRgRgUjVlYjYkYmZnYmZn]qZn[oXl\p\p\p[o[o]qaq^pcrbsctcteufvhxdthwdwewhzfyh{i|k}mj|mew[mTiPdLaLaK`J_LaJ_I^K`K`K`K`J_K`J_L`I^J_I^I`JaI`I`KbJbKbJaLaLaKbKcKcKbMdMdJaLcKcL`NcLaMbNcNbReTdVfTeYjXi[lYg[l\m_p]n^o]o`p_p_p_p`q_p^o]nar`qarctctfwevfwgxdwfyexh~ezi~k}k~klf{h}i~jh}i}h{dwcv_u`uav`ucw`uauavavbwavavavcxbwbwdydycxcxcxezf{f{f{h}h}g|i~jkllnponnpnrni~lmmmoppprqqqsrtstww{}ƃƀăʁɁɀǂɃ˃˃˄ɉρxtssqr~׀؂xqqrquuz|ׄލ鑩퇜xv{}ɁǀŃ˃Ѐӄ؇܉ވ݄ۃ|y|}ӂ؆܊㎥蘮炘ބދ䐪쐨댥蒧땭݃ӀτЃ΂ˀ́ˁˀȁʁǀ~{~~|ɂȂłƃ̂ȂɆ̃ʈ˅ƈȄȆɅɆƆȊȉΆʈ͉̈̇̈̉͆ˇ̇τˇΆˇ̉Ή͌ЍАԑՐԐԑՔחܖᛨ࡮⥮ٛԜ֝ۛ՝ܘܘ梳䣮ޥܩڪݧݦڬ۳޷ߺ຾9Hh:Jl:Ij:Ij9Hi9Hi7Ff7Ff7Fg8Gh7Gg7Eg4Bf4Bd2@c3Ae3Ag1?d1?a0>`/=`0>b0>d/=c1?d0>c1?e2@g0>f.Ji@JgAKiALjALjDOmFPnGTnKVqLVpOYqNYoJUjFQeEPdDOhGPmHPoGRpFQlCNkBMkCNlBMkCNn?Lm?Lm@Ml@Mm?LlANn?Ll@Lm?Nm=Lk>Ml?Nm?Nm=Lm=MnLmLo=KnL{Mx>Mx;KvMx>LyKx>Kx=JwMx=LwNz=Nz;Kvs/>t,;r+:q,;s*v.?u->t.?u.?u->t.?u.?u,Au,@u.Aw0@v.?u0Aw/@u-Bw.?w/@x1Bz.@x-@x.Ay.@x-Ay/D~1F/D}0E~/D}/D}/D}0D~/D/@{5F}1C{0D|0E}4H}2Fx1E{5I~6J2F{4H}2F{4H}3G|4H}0Dy2F{0Dy3G|3G|1Ez4G{5E{4I}5I~4H}3G|4H}3G|4H}6J7K8L7K7K7K5I~3G}2G4I~4H}5I~3G{0Cy2E~3H~1E}3E}1D|3F~3F~3E~3F~5H5J7J8L7J6K6L4K6M:P:P8N:NR;QT=S=S=S=ST?U>U?R?UBXCVCUEWEWCUCUBVEVBTFXEWEWCWDXBYCYCZBY@WCYCYCYBYF\F[G[FWFYIYHWGYGYHZFXDXAUCWAU>S=R=R;P;P;P=QP?UAU>S?S?S?S?T?T@U@U?U@U@RATBUATATBUBUDWCVBUCVDWBUDWDXFXGWGXJ[IZHYK\IYJ[F[FZEZEYG[F[EZFZDXBXCXBWBWDYCXAVAVBWBW@UAVCXBWBW@UAVCUDXFZEYEYGZDXDYBWCXDYDYDYDYDYDYDYDYBWCXDYCXDYCVEXIYGXHYGXHYIZHYHYHYFWJ[GXHYIZGXFXEYFZEYFZG[G[FZF\E[E[F[EZF\F[F[EZG[FZH\G[FZH\FZEYFZH\H\FZG[G[G[I\KZIZL^M_L^N`L^L^I\IZI]E[G^I^L\L]K\N^O_P`O_N^M]L\L]M]N]N_N_L]M^N_M^N_M^L]K`J^I]L`K_K_K_N_O`NbMaOcLbK_MaP`O_M]O_PaNbNbMaNbMaMaMaOcOcNbNbPdOcOcPdNbMbNcLaNbQbQbRcTdRdQcSdQcRdRdUgTfPeQdPdPgOeRhSiPdRfOcQeSfRfSgSgRfSgUjSgThThSfTkQhShTiShShTiRgShRgPeOdOcLcMdNeKbMdOdPfRhVkZmZnZp]mYn[o[oYm]q[o[o[o\p]p[o^r]q_o`pdtfrfrfrdvdudtfveuhxfvhxk|k|m~ponpi{[nTiPdNbJ`G\J_I^I^I^I^I^K`I^J_I^J_I^I]G\I^K_I^H`JaG_I`LcJaK`KaLcJaMdLdNeNeLcLcKbMdMbMbK`NcPbQcPdRfWhYiXi[lXiZk\m\m]o\m\lYm]n[l\m\m]n]n]n_parbsctdubsevfwgxfyeygyf{g|h}kk}k~g}jg|ezg|ezcxdwdw`vbwav`u`u`uav_u_tbw`ubw`ucxbwcxdydydydydycxdyezf{f{ezf{h}g|i~i~kjm~lmpopnrnnnqspppoortrqqrsstwy|~ƀŃˀȀyz}~ʂ̀{rsnnommqtrpprqttuvx|ׁـzsxwwz{|{|x}|~}|~yzyxzz~~фن݆ރ}}ӂ׀؁؃܏䝯銝ߋق~}}ǀ||{zzyzyxwz{{|~Ȁ~~ǀǂ}|}€ÀĀÁ‚džʃƁɀȄȅʅʇ̅ʅʃȂǃɅɁLJ̈̌Ћ΍юЕ֙ܙڥ㢯ߑؒ֗ښݗו֖֔֕ؕٓٓٓښݛޞߞݞݞݠ۟٦ױ⳻嶺෼鲸9Fh>Km:Hia1?c0>b1>b1?a0>a0>a1?d0>c2@d3Af2@e1?e0?f1?g3Ad2@f5Di7Ej6Di7Ei8Fi:Gj:IjLj=JjAKlCLmDOoDPmDOlEPmHTnKWpKVnOZpP\qOZmKVjFQfEOfGSnGQnFPoEPkEPmBMkBMkCNlBNm?Lm?Lk@Mm@Mm?Ll?Ll@Mm?Ll?Nm>Ml?Nm>Nl?Nn@Lo?Km>KnLo?Mp?Mo>Lo>Lp?NoKoJw>Kx=JwKx=Jw>Kx;Hu=Jw;Jw=Kx;IvL{Lz=Kw;Jw:Ky7Iw9Ky8Jx6Hu6Hv6Hx7Iy7Iy6Hx5Fw6Fw6Gw8Hy9Ez6Ex5Ex4Fx3Ew4Fx2Fx/Cu2Cu3Ew2Ew2Cu6Hz1Cu3Fw3Cw->r0Au+v.>v,=u->v.?t->t->t/@v.@v-Av-@u/?v0Aw/@v0Aw.?u/?v1Bz3D|0Ay.Ay.Ay-@x.Ay,@x,B{.C|.C|/D}.C|.C|/D~-B{0C.Az2Cz1C{/Cz/E{1Fz3Gz2F|4H}4H}1Ez3G|4H}4H}2F{3G|0Dy2F{1Ez1Ez3G|3G|4G}6H}3H}4G}5I~4H}5I~5I~7K6J7K7K6J7K7K5I~5I~2G1G}3H}2F{6I~3H}4G3F}0F}2G~3F~2E}1D{4I|4H}6K5J3H}6J4I~5J6L5L6M9O:P9P:P:N;P:L>O?OAQ?PAQAQFSBPAP@QBRBQ@S>PR=R=S;Q=S=S=S;Q;QT>T?T@T>R>Q>T?TBVDVEWFXDVEWFXEWDVFXGYGYFXCVBWDXDXDXDWCWBYDZEYEYDYE[GXGYFYFXGZI[GZBVCWBVCW@T>SSS=R=R=R>S?T=S>T@U?T@VBVASCVEXBUBUDWDWCVBUDXGXEVGXJZFXHYIZF[G[FZEZHZH[F[FZDXDXCXBW@U@UBWBW@UAV@VAV>S=R@U>S@U?TAVATBWCWBVEYEYAVAVBWEZDYDYDYDYDYDYDYDYAVBWCXCYDYFYEXFWFWHYHYGXHYJ[FWIZFWIZGXIZHYIYI[EYEXEYFZDXEYDZF[CXDYEZCXDYEZF[G\G[FZH\G[G[H\FZH\H\FZFZFZFZI]H\G[K\J[L^M_M_L^K]M_L_J[G[H^G^I^L\K\M^M^M^O^O_M]M]O^R`N^N^N_K\L]L]N_L]N_L]M^I^K_J^K_K_K_M`M^O`OcM`K^LbK_QfO_PaP`QbO`NbNbMaNaNbMaNbNbNbPdNbNbOcQeNbPeTdUeReSeNdNcPdPdOcNbQeSeSeQcTfSeUfPdPdSfReQeSfRfThQeSgThRfSgSgSgUdUeRfShThVjRfThRiTiTiRgRgRgShQfNcPcNeLcMdMdMdOgPdThVjVjVjXl\l\m[o[o[o\pZnZn\p\qZp\s\o_s_rascsdugufuhvivhviyfviyiyjyk|m~m~m~nnomj|asXmUhOeJ_H]I^I^I^I^J_I^I^H]I^G\I^H]I^G\J_F[G\I^J`K_JaJaJaL`MbLcKbKbLcKbKbMdJaLcMcLaNcLaPeNcPeRdPcReViXiXiYjWiZk]k^l]nZl\o]m\m]n]n\m^o_p^o_par`qarduevgxgwcvexfxf|ezh}jh}j|i|e|f{cxdycxbwdu`s`v]r`t_tcx`u`s`s`q`vcwbwavcxbwcxdydydydydycxezeyeycxezg|f{f{i~g|kmmnmnmnnppqopoqrqpopprrrtquuuw{|ŀŀȃxvyy|~~xtsonnpokn~nmnnmnqrrsuvuvvvtxssrsxuyxxxxxxxryusttvz}z|zzyyz|{~օڌ№؂Ԁ}|{yxzwwrusuvvtsttxyzzzz|y|zzvz{|{~ÃƂ|ŀāƁǁƁƁÂƃƂƄȃȄȈ̋Њ͍іئݑяϐԏґԑՐӑԑՑٕ֕ٔؓؕڗۙڗْחטӜ֢٦਴⦲᥯᧰ݮݹవݫߧᥱ梯㤵诿a1?d2@e2@e1?e3Ag3Af1?c2@e1?d5Ch7Ej6Ci9Fl:Hl9Gj;Hl:Gk=Lm;Jk9Ik=Ln?KkCMmAJkFQpGQoDNlGSnFSmKXnKVlOWoQXoU\pQYmGRgFQfEPjAPlDPmCMjCNkCMlCMlCNl@Lk>LmANn?Ll@Mm@Mm@Mm>Kk>Lk?Om=Mk?Nm@Ll@Lm?LoANq>Kn;Hk=Ln9Ij;Jk:Hl;In;IoLs;Hk?Oo=Lm=LmJv;Ht;HtKx:Ht=Jw;Hu;Iv:Hu;Iv:Hu:Hu:Hu:Hu8Ft8Iu9Hu;Hu;Iv;Iu=Jx;Ivr1Au+v.?w->u/@v->t.?u.@u,At-Av/?v/@v.?u0Aw.?u->v->w.@y.C{0C{/Bz-@x,?w,Ax+Ay-B{0E~.C|/D}.Cz/Dz-B|/B~1C}1Ay2C{/B{.Cy1Ez2Fy1Ez4H}4H}3G|3G|1Ez2G|1F{2G|1Ez/Dy0Ey1F{1Fz0Dy5H}4Dz4Dz3G|6J5I~6J5I~7K5I~6J7K5I}6J~6K4H|2G|3H3I~2G}8L6K5H2D}2F~0E}4J2G~5G3F~4H}3G|7K5K}5J4J~3H|5J5K7N8O6N8P9O9O:N;O9NO>O?O?O=QT>R?R?S>S@U=S?UAVDUDVDVDWDVDVCUFXFXFWGYDVFWHYFXCWBVCWDXBVDXDWEXJZFWGYGYFXGXJYGYGYCWCW@T@T>SS@U?T>S?T@U@U@U?RBTCVASCVEWEWCVCUGVEWGXHXHWGXFWEZDXCWDVDWCVCVDXCWCU?T@U@UAV@UAVAV@UAU@U?T@T?T=R>S=R?T@S?TATAUAUATAVCXBWBWBWCXCXBWCWDXCXDYCXCXBWDYCXCUEXIYGWHXHXGXFWHYGXGXHYFWHYHXIZGXGXDXFXFYEZDWBXBWCXCXBWBWBWBWDYCXCXG[H\FZG\H\H\GZGYFZFZG[I]H]EZI]I]K[IZM_L^K]M_M_K]L\HYG]F\G]J^M]L[K\K]M`M`N^N^N^L\P]N_O_N_M^M^M^M^L]L]M_M`J^I]K_I]MaLaL_N_O^J`LaL`KaMaOaNaTcPbSePbNbMaNbMbMaNbOdOdO`PePdOcPdOcUdUdScScRdPcOfOcOcOcQeQePePdRdTfTfTfRdTeQeRfPdPdPdPdQeQeSgSgSgQeRgSgUeVfVfTfThSgThUiRiRgShRgQfOdMaPdNbMeMdLcMdMdMdNdQfSgWkWkZmZj[k^n[o[o[o\p\o\pZn\pZq[r]r^qcufwdseuhviwiwkyjxjzjzjzizj|l~mm~pnoponj|bv_sUiJ^J`G\G\F[H]G\G]G]J_G\H]I^I^G\H]G\H]H]H]H]H]G\J^H`J_K`LeLcKbKbLcKbKbKbNeNdMbMbNcMbOeNcOeObTgVjYiZjXiYiYh]l^k]mVlZn\m_p\m_o]m[l^o^o`q_qaraq`qbsevgxdvdvbxg|h}i~h}f{gydwdwdzcxcxbv`tatas_v`u_v`u_tbvasbucu`v`ubwbwbwcxbwbwcxbxbxbwdycxdycydzg|g|g|g|ezh}h}h~k}h|mmmlnmnnnnnqpopqprtstrssruy|{}|{uttsvzzsrtssmnmlommkknlmmoonqtsrsrsppppovptttvvxxurssrprstvwxvsuwwxw{|z{}{}{yvtrrssrooqqroopqutvxuvwvsutstutuu|}€|~~~~€~|}~~ƁĄǃɅ΃ʊ͖עޟ֘ΎΒґҒ֏ӏӏяАҐӔ֖ۖەܓ֖ؓ֕דה֔ט՛ܟᜭޜ៮➪ݢܟסݢߟܠߠ㞫ߝݞܫꭹ쩴椯⣯ᣮ⡰ᥳ㧸9Hi=Kl9Fi>JnKl@Mo@Ml?Ll?Ml?Ll@Ml@Lk@Jk>JjLm=Il=Il>Lo=Kn?Mo>Ko;JmLn?Km=Kl>KmKo=Im;Hk;Il:Fi;Hj=Jl9Hk:Hk8FiJw>Iw=Gv;FtJw=Jw9Gt:Hu9Gt:Hu:Hu8Gt6Jt6Iu8Ht;GuJz:Iv8Jt7Ht8Iw7Iw5Gu5Fu5Gw4Eu6Ev7Ev7Ev8Ev8Fw9Fw9Dz6Cw7Dx4Dw5Dx1Cu3Dv1Cv1Bu3Ew4Fx2Dv5Gy6Hz3Ew3Fx/Cu-Bu+?s+@s-=s*v0Ax->t,=s*t/@v0Aw/@v->t,=s->t0Aw3C{.@x0C{.Ay-@x.Az/A|/A|/C}/E}-B{-B{,Ay/C}0Cz.Ay.Ay/C{/Cz1Ey0Dy1F{5E{4I}3G|1Ez1Ez0Ey1Dz2Bx3Cx3Cz3Dz3Dz2Bx3C|2E~1F|2Gz2G{4I~6K3H}5J3H}4J6J6J5H5G6I3F4G5H6I2E4G5H5H4G4I}4H}5I}4H|4H}6J5I~5I~6J6J9I}8J7I7J6K7L7N6M9O:P;Q7M~8L:NP@PBRBS?N?P>P>O>O@Q>O@P?R>QT:R=R@SCUAR@TDUCUCVDVDVCUGYDUDWBWCXBWCWDUGYCXCWBVCWCWAUDXBUBWF[CYEZFWFXGYGYFXDVHZDV@T?S?R=PR;P=Q>R>SS>S=R=R?T=R=R>S>S@T?W@V@UAW@UCUCUFWHXFUFWHXEWCXEYBVCUCVAUDVAVAV@U?T@U?T?T?T@U?T@U?T=S=SSS?T?T>SBS?R@UCX@UAVBWBWBWCWCXBZCZCXAVCXCXCXCXDVCVDWEXEXGZDWIYGXGXHYGXGWFXEXEXEXDWCVEXEYCXDYDYBWCXAVAVBWBWCXCXBWDYDWDXCWFXHZG\BXG\E[FZG[I]JYK[K[M\J[J[L]K[L^K]J\J\L^L^N_K]JZK\J[L]O^O_O^L\O_P`P`N_M^O`M`N_K\K\L]L]L]M^M^N_J_L`L`K_L`MaK`LaJ_L_P`N`N^O_N_O`RcP`SdO`MbMaL`P_O`OdNbOcMbMaOcNbMaPdOdPdQgOdNdQcRdRdSeQcSdSeQdOdPeSdTfReRfPdQeSgPdOcPdPdRfRfPdRfSgSgRfThRfRePgQgSiTkUhThUhThQeQdPcLaNcNeKbKbNeLcKbNeOfOfOdThUiWkWkXmZnZn[o]q\p\qZpZp\p]q`r_qasbtbtbtbuewewevfwfxhxk{iyjzp}o|q~l}n~m|mopong|fz`tZnPeL`I]H]H]H]G\J\GYF[F[H]DYF[H]F[H]F[F[H]I^J_J_J_I]J_I^K_LcI`I`KaE`LcJbMdMdNbNcMbMaQcPcSfReReTgTgTiUhZkWhZk[m[lZkXjXk\oZm[o\q^n]n_paoaoapasductctdwbxavfzg|g|ezdybxbwcxezawcxbxcucu`s`scu_s_u`u_tav`u_t`vbtbtavcxdy_vbvcxcxcudvbxcydvdwfxfxe{cxdyezdyf{fxh{l~m|o~l}llnmlnlnnnoqooqqppsstwwwwwytuuqlmmnqsrpoommp~m|ll~k~qnoompoopppnrrpqqpssnqppoosvuutsutqqrooqrrsqtqorstrrsstvuvusqmnmj~ljkmponoommmoprtrtsqqnqnmorwyy|wxyxxy{{{wz~{||~~ƂȅˆˋЋЎюЊЊ͈͇͇̇̈̊ЌύѐԏӐԒԎԎՍԏґ֐Ӎђ֒גؓڔڔەۘܙߛ☦ᕤݓْٖؕڞޝᚦܘۖؗ؜ݚߞ࠯⧹歾?Km9Fh:Gi;Hk;Hk;Hk8Eh7Dg7Ei8Fi6Dg7Eh6Dg4Ad5Bg7Eg6Ef3Bc5De2Ac3Ad2@c2@c2@d2Ae2@b2@c4Ae2@c5Ce4Bg5Ci8Fj6Ch8Gk7Hl9Il9Gj;Il;IlLj>Jj@KkCNlDNlEPnEPmDNkIVpIVnJUmKUkPXoQZpOZrR]vNZsLXrERlBQjAOj@NiAPjAOjBMjCNlCNlALk?Ll?Lm@Lm@Mn>Kk@Mm>Kk@Mm?LlANnANn@Mm?Ll?Ll>Kk?JjKl>KmMn;Ik>Kl>Mo?Mp=Kn=Kn=Kn=LlJm=Jl;Hj=Jm;Il;Jl;Ils->t0>u.>t.?u.?u->t.?u,=s.?t->s-=s/>u,=s.?w/@x1Bz,@x.Ay+>v-@z/B|-@z,@z-C|0E~,Bz-A{/Bz,@x.Ay1C{.By0Dx/Cx.Cx0Cx0Dx0Dy/Cx/Cx1Dz2Cy0Aw2Cy2Cy3Dy3D{3D}4F~0C{1D{3Gz2F|3G|4I~3H}4I~4J~4H{4H}4H}6J5I~5H5H6I5H5H4G5H3F}6I6I4H|3G|9M4H}7K6J7K7K6J6J5I}7J6H6J7L7M6N7O8P9O;Q9O:N=Q

N?P?PO?P>P?P?P>OQ;Q;Q;Q:O>R;Q=RPSS=R?T=R?T?T>V>UBVBW@UAVBT@REVFWGWDWAUAVBU?Q?RAS@U@S@T?T@U>S=R=R=R>S?T?T?T>T;R;Q;Q;Q:P:P:P:O;O=R>S>S>S?T=S>S@UAV?T@U@U?TCXBWBYBYAVBWCXAVBWDXCXATCVDWCVEXDWDWFVEVAUBUBUBUDVCVCVFXEXCVEWDYCXCXBWAV@UAWBW@VBWBX@UCWDYCWCXFYDWDYCXDYCXG]H[G[F[K[J[J[J\J[L]M`M_M_L^J\K]K]M_L\K]K\I[M\L\O_M]N^N]O^N^N^O^O^M`M_J[J[J[L]N_L]M^O`LaJ^L`MaL`L`K_MaK_K`N^O`M]M^O`O_N_N_RbOaK`MaPaPbPaO_LaNbMaMaOcNbQeNbQeQeMeQdPeTdRdPbQcSeTfRdQdPdQeSeSeSdPfQeOdPdPdPdQfQfPdQeRfQeSgSgRfQhSfRjUkRhQgRhTjTgUiSgQfQcPaOdMaKcMdKbKbLcNeMdMdNfQdRgWkWkXlWkYm[oZn\p[o[o\p^r_s^t_q`r`rasbtcufxgyfxi{i{j|l~k}m|l|p~n|oo~m}lnoooqjh{dx^rVjMaM`I^F[G\G\I\EYH]G\EZEZEZDYF[H]G\F[H]F[H]H]H]J_I^J_I^H_JaH_I`McI`MdKbMdNbMbNcOdPaPcPcRdUhSeSfUgTgYjYjXiYjXiXiWjYlYlYl\p^n]n]n_paoapbpbs`qbscsbucwcxcxcxdydycx`u`u^tbwav`tbtbu`s_ratatasat_tav`uav_t`sat`r_u^savbvav`vbt`scvdvcvdwbudwcycxezf{bwdyezfygyh{g{m{mkkllmllklnpoonrqrprortuvvvspqrllkmmpoqljmh|i|i|h|izi{k|m}pmnopmononproounlqk|m~m~l}om}prnoqrqoqqounnomnppomlnnqnqnqpuoqonllk~i{h~g|g|g|i~h}kkkh}i~kkmnnoosoom|on~i~mnrrqusttquuuuvstvruxy|z||~~ƀł}ȂǂǂʃƃȂƃdž΃Džʅʆʉ͉͊ЉψΉЊъ͋ϊόьЌԎՎԍӐ֑דڕܔݒ۔ݒۓؐ֏ҏԑԓڒܑؒؑؓגԕڙܛޠ⪻访Li>Mi@Nk>Li>Kl?Ll?Ll@Mm=JjAMm?Kl@LmKk?Kk@Kk:Hh;Hi?Ln=Jl=Jl@Ln>Km?Kn>JlKo;IlMns.?t-=r):n+u,=s+:q/>u.=s,=s.?u,=s/@v.?w->w,=v,O=N?P=N?O=O=QS;P>S?T>S?T?V?V@T?T@UAVAUASATDTFUFVBVBUATATAT?R>Q>Q>R?R?Q=R>S=R>S=R=R?T>S=P;Q;Q9O:P:P9O;Q:P;Q;N=R>SS>S?T=R=R?T>S>S@U@UAV>V@VAVAVBWAVBWAVBVBUCVDWCVDWDWDWEXEVBUCUCVBTBXDZBWBWDWCVDWDYDYDYCXCXFZDXCWCVBVDXDXBVCWBVCWBVCWCXBWCXCXDYEYGXEYH]L[JZK\J[J[J]L^L^K]K]K]K]M_L\L\L[M]L\N^N^M\M]MaOaNaPbNaM_OaL^J[K\J[M^N_K\N_L\J_L`K_J^K_J^L`L`L_LbM_OaOaN`OaOaPcPbPbQbMcK^N`SeQcQcNcMaNbMaOcOcMaNbOcMbNbPcPfRcQcRdRdQcRdQcSePfRfScRdSeSeTfVhUfTeSdTfUfQeRfSgQeQeQePgRgQdQhRhRhTjRhTjRfThThQeNaLbH]LcMdJaMdKbLcLcNeMdMdPdQgUgUiWkWkXl[oXl[o[o]q]q]q\q`p^oaraqbubtgzgygyi{j|k}k}l~k}nl}l|l}k|iyiyk|k}mlmmllh|^rVjOcJ^F\G\EZCYFYGZDYCXF[EZDYDYF[F[EZF[H]G\I^H]I^H]G\K`I^H`H_H_H_I`JaLcMdJcMaLaLaNcNdOdPePePfOdQfPeSfTeVgXiVgWhYjYlXkZm[mZo]m\m]n_p_q`q`rbs`qarcsdwavawavcxcxavav^sav_t`u_t_t`s_rds]qat_r_rbu^t_taw_tavas`sas`v_tavavar_satauaubtbwcvascuavcxdybwcxdycxezdygyhzi}j}ijji~i~hkjnnpopqqqpppmqqqqoson~l}i|h|mg~l~k~k~nnj}k~g}j|dwi|h{fyexfykzl{n~m|o~m}m~omnonlro~o~kll}k|k|j{hyj{g|h{hzh{j}i|mlmnoq~qrrmllikji~i{i|lk~hmjljml~i{k~i|i|h|g{fzf|dydyezg|ezg|cxf{dyf{f{h}jljnj~i~lzk{hyl}jzi~fzf{kzm~m~hyk}j{m~m}pmmnnkmlmqvutws{zz|{||}|||}ĀǀȃɃɁ˂̃̆χЈӉ͉ІψωЉφЋЉЉЋҌӎՍӏ؎׏َԋҊҏҏӍՌӐ֏ڏ֎ԎӏԒדؘޞޥ㩺驻쬿帿伿9Gi:Gj:Gi;Im9Gi5Dg5Gh6Fh6De8Gh7Fg4Cd9Eg8Dg:Ae5Cf6Ef5De5De4Ce3Ae3Ad4Be4Bd4Dd5De4Cd6Ef5De5De5Cf7Eh7Eg6Dg:Hk7Eh:Hk:Hk;Il=Kn=Jm>Jm>Jn>KlKgBLj@KiALjALjCNmCNlGQmHQmISoKUmITkHRlJTmKUnNWqO[uNZtMYrHSnCMjAMhBLhBMiCNj@KiBMk>Mi>Li?Mj=Kh@Nk=Ki>KlANn=Jj@OnKk>KkKm=Jl=Km>Km?Ln=Lm>Mn?MnMnKmp)s,=t,=v->w->v->v->v-=u.=s,;s-;u.=v/@x.?w.?w->w*=w+>x-@z.A{,?y-@z-@z,>x.?w/@x0Ay0Ay1By1Bx/Dy/Dy-Bw-Bw-Bw-Bw.Bw0Cx1Bx/@v/@v.?u0Ax3D|3D|4D|1D|0D{1Ey3G|0Dy4H}3E{8H~3Cw6H{4H{5I6J6K7K8K7J8K7J7J6I4I2G|3H}3H}3H}2H|4I}6K4J6K5J~4I~7L6L6K2G~5J6K:O:O7M9OP>N>O@Q>O>O=N=NAQ@P@P=N=N>O@Q?Q;QT;S=RS=T=T?S?T>T?T@U@UAT@S@SBT@S@S@S?R?R@S>Q?R=P=P>R;P=RSS=RR@TS>TS>S?T>S@UT?TAV@UAWCV@U@V@UBTCVBUBUCVATDWBUAVAVBW@VBWBWCXCXDVCVCVCXCXDYCXCYDXCWDXBVDXCWCWDXDYFZCWDYAVCV@UBWAVCXBWBXCXGYDZH[JYK[K]I[K]K]J\L^K]K]M_K[IZL\M]M]N]N\L^K^M_N`M_OaOaM_M_J\K]JZK[K]K\L]M^N_I^K_I]K_I]K_L`K_J^MaO`OaN`PbN`OaM_RdQcPbQcRdPbPbQcQbQcOaMbMaMaNbOcNbNbQbPdQgTeTfRdRdQcQcRdSeSdPfPdSeRdRdTfSeTfTfSeRdSeSeSgQeRfQeQePePfOfRfRiQgTjSiQgTjSgSgThTgQcMcMbL`HaKbLcKbLcNeNeMdPgOcRgQfThUiXlWkYmZn\pYmZn\p]q_oararbs_pbsaqevhzj|k}l~l~k|k}l|l|gwgwfxcs`u`tbvezj~llllmlcwYmQeI]F[EZDYEZEYGYDYG\DYEZDYDYEZDYDYE[F[DZDZF[H]G]G\I^I_J^H`H_I`I`H`IaJaL`MbMbNcMbOcNcNcMbOdNcQfNdQdTeTeVgVgYjYjViYlXkXn^n\m]n]n_p]n`qarararbsctdu`ubvcwbvcw\r_w]t]r^s^s^s_r`tbrbsbrat_s`s`sasauaudtdt_ubu`sas_u`ucvavdtctdtdt`w`ucu`wbw`uavdybwezbwbwdydyf{ezgyi{i~g|g|kijijjjnnooonnpno~n~oprlpn}m~j{i|j}h{i|h{gzh|kzi~gzi{eze{h{exdwewbucvfzdxjyjzj|i{l~lzki|i}n|n}l|l|jyj{gxizizfwfwfwgxdxfzdyfzdwexexi|j}k}ooopo~o~i}k~h{g}e{fwexexeyfyhze{ezdzi|gzeyg{f{h|g{cwcwdx`u_t`u_t`u_tavav`ubwavbwcxdyf|ezfzeydxbwaveueugvaweydxbvfvfweudugwiyf}g|i}g{lh~ijk~lkoqrqtxvwwtvsuwxw{z{~~}~~~ǀ˄Ђ˄̈́ʄ˅ˈ΂̄΃͇͆ΉЉЉЉЊԊԉЉϋ҉͋ьόӌӍӍӌӌӌЎӏԐ֘ݛݞݦ㩺檻淿䷿豾貿Kn>HlIg>IgALjDOmBMkEPnHRnHQnIRnGPiGPiKUnISoKUpMYrLZsKXsGRnBMi@KgBMhBMiBMkALjBNl>MiANk=Kh>LiLi@Nj?Nj>Kk=Jk=Lk>Nm=Kk>Kk>KkKl=Jl=Km=Jl>KmKm;Hi=JiKk=Jjq,;o-=p.>p.>q.>q+;n)9l'9l'8l'8l(:l&8j'9k'9j&8j&7i%6j$5i%5i&6j'7k%5i)9m'7l%4k&5l 3i#4j#4j$5l"3j#4j%6m"3j#3j&5l#2i'3j(4k(4l%2i'7l$5k%5k%6l#4k$5k%7m"5n#5n$5l%6k$6k&6l&6l%6l%6k*8p*9p(7n)8o(9o(9o(9o(9q(9o):o):p);q+:q+:q-w->v,=u->v->v.?w-?w,v->v*x-@z,?y+>x,@y.@z/@x1Bz0Ay/@x1Ay1Bx.Cw.Cx.Cx.Cx.Cx-Cx/Cx.Av0@w/@v1Bx2Cy1Bx1Bx1B{2C|1D}/Cz0Dy1Ez4H}2F{3F|4Ez5Fz6G{3Hz4I|5J}6J5K8M7L7J6H8K6J~2G|4I~5J3J}4J~4I4J4I5J5J6K6K7L6K5J4I6K8M7L9N:O=QO?P=N>N=N?P>N>R>R8O9O=S;Q=STT>TU?RCTBTBTCUASASASBSCREWDVCU?TAUBSFWDWEWDVEWFXFXFXGYGYFXFXFXFXEWDVI[GYGYEWEWBV@T@S>R;PSR@T@T@SATAU@U@U@U?QAT?R>Q=P?Q?RS=R>S>S?T=S@U>TAUAUAUBUCVBUBUCVATDWATAVBWBVAVAVCYBXAVBUEXDXBXCXDYCXDXEYEYEYDXDXCWAUDYCXDYDXFVEUCXBWAV@YAVCXCXBWDYFXH\DYF[H\L]I[I[L^L^K]L^N`J\JZK]N]O^M`M`M_J\M_L^N`OaMaN`N`N`L^J]M_L\N_N_N_M^I^K_L`K_K_J^K_J^L`LaPaN`PbOaPbOaPbPbOaQcPbQcRdPbNaPbQcQcNbOcNbNbNbSdQcScOeOeRcRdQcSeTfQcRdUgTeQgQeSeRdTfSeUgUgTfSeSeTfSeRgQfRfRgSgSgRfQeReRfOfRiQhTjTgSgUiSgShRfOcK`K`LaLcKbLcKcMeLdMdMeMdOdNcUiUiWkYmWkWkYm[oZnYm[o]m^o_pararar`qctfwl|j}k}l~i~g{ex^t^r^r[o^r]q]r`tdxh|j~mnmnmey]qThK_I]F[F[DYEZDZDYCXF[DYDYDYCXDYFYFZEYFZGZI]G[H[G\H]K`G\H]K`J^J_I^MbJ^LaMbMbLaMbLdKbNcMbNcNcPeOdReSdTeTeWhWhZkUiWi\l]m]n]n\m^o^o^o^o`qar`qarbsdt_u`uavau`tau_s_v`u]r]q[p^s`raq`q_p_p`p]q]q_t`u]sasarbr_s`s_s`sbu`ueuctctduducrbwcuaw`u`ubwavavcxdycxcxezezezdye{dyf{g|g{hg}ih~kjj}nnmkjllk}j~n{m}m}qn~l|l|l}l|h}gyh{h{exgzhxhyizgxbudyavbtbuauau`tdwcwhxgxfwhzjxhwhwiziyhyj|j{j{j{fwgxdufwcuduevctcsbwcwdx`tdwcvbudwfyfyhwk}k|l}ol}ofzgzdwewexcvfvct`tbtbu`vawbv`tavbvbvaubv`t_s_sZo[p[pZoYn[pYnZp\qZo^s^s]q`sasbucwcv_s_r_s^q^s]q]q]qauaucsctbsbsbr_ubvcwdxdydyf{cxf{ezi~k|j}lmrstttprqunpqqrwxyzy{z{z}˂ʁʁ̂ȁȂʃʂ̀ƁDŽɃɅɆ̅ɉ͇Έ·ΊшЉ͋ЊΈӊԇҊӈωшЈϑՐӔؘܚڦ㨹䩺嬽ﰿ㹾絿渾ﶿ鲽벿:Hf8Ef8Ef8Eh7Eg6Ff6Ff6Fg5Df7Cf6Ce:Fg5Cc7Cc:Dc6De6Ef6Ef4Cd5De4Be5Cf5Cf4Be6Ef6Ed5Dc8Gh5De6Ef6Cg9Gj9Gj9Gj:Hk9Gi9GkIm;Il;Hj;Hj;Hj=Jk>Ji?Jg@JhALiBLjEPmBMi@KhEQkHToFRlEQjHRmFRlFPkFQnGToDOkALh?JeBMiBMkALjBLjAMk?Nk>Mj=Li=Li=Lh>Mi@Nl>Kk@Mm?Om?Nm?Nm>LkKjIl>Lm=Jmq-=p.>q-=p,=p,w->v+v+v.@w,?z,?y*=w+>x+>x-?y/=x->x/Ax1Bz0Ay1Bz/Ax-Av0Dy/Cx.Dy/Ez,Bw,Bw/Cx0Cx0Aw0Aw0Aw/@v/@v1Bw1Bw2Cx1Fz1Ez0Dy1Ez4H}2F{2F{3Dz3Dx7Fz6F{7H|7G{7H|:J:J;L;M8M6J6J4H~5H}3J{3J{3J}5J5J5J4I4I6K5J6K4I4I5J6K7M8M9N9N;P;P;P;O:N>O=N=N=N>O?P=N>O>O=N=OT;Q=STTSATFWFXFWDVDVEWFWGYFXEWEWDVCUEVEVDUDVGYEWCVBVBVAU?RR>UR=R=R>S?T@U@U@U?TAT@S@S?T=S=R>S>S;PS=RSS>R@R?R?R@SAT?RCW@VAVAWAWBTEXDWDWBUCUBUCXCXAVAVCXDXCWCWCVDXAUCVDXEYDXGVCUDUCXBVAVBWAVAVBW@UCXCYEZEYG[G[H\J\I[L^L^L^M^M^N_L^L^M_J\O_M`M`N`N`N`M_M_J^M^M`O_O_M]M`L_K\N_N^N^K`J]J^K_K_J^J^K_K_K`OaOaN`OaPbPbN`PbPbOaOaOaQ`PaQ`QaPbQcNbOcOcNbTeQdQcRdRcSeSdSeSeSeSeTfRdSeRdPgPeTfTfSeTfSeUgUgSeTfSeTfVhUfUfUfQePeThPdQfRgPdSfSfTgShRgRgSgVePfReMaK`MbMbMbK`LaMbMbL`MbLcNeNdQfUiThXlXlXlWkYmXlZnZn[l_p]n^o]n_p_p`qduizk|ll~f{bv\pYlYkWmVlVlXm\n_q^qatg{nlni}fz`t\pShMaH[DYF[DYEZCXDYDYCXBWDYCXDYFZFZDXI]EYEYG[H\I]J^J^I]H]F\H]I^J_K`G\H^MbJ_K`J_MbLcLcK`NcLaNcNdOaQeTdTeVgVgXiWhUhYjZkZk[l]n]n\m^o^n`p_par`q`q`q`qbraq`q]r]q_s_s`u]t\s[s^q]ras]p_o`q`qar]m`p^ncscsbparbr_r`sbrbscsarduarctdudt_tbw`sbububuatcvaw`vcxbwezdycxezcxdxezdyf{f{g{e}f~hg}igkmki~i~gzgzg}h{ezkxm|kzjzjyk}k|izjze|f{cwdxbxdtdvdududu_uau_scsarcsdtbreubsevducufsiwesevevgxfwgxfwevevbsbs`rcq_r`qarar]r^q_sau^r_q^q^q`sbucwfvevhyizh|jzgwbwbv`taubqbsbr`p[o\o\o]q]q[o\p[o[o\p[o[o[oXlYnYnVkUjVkUgUgViUjXmXmWlVjYmZn\o]qZp^t[qZqZp\o\p\p[o\p[p\q]mar]naqbq^r`t`t`vavavbwbwbwdycyfyj|mh{i|qn~qj}mnlnppprrwvvszxyyy}~~~||}~ƃɅʆˇ͉΄ɇχΆ͈̆ˉΉ΋ЋӋ҉ΉΉΎэюяґ֐Ԕٙ٠ޡߪ⮿絾ڼߺ⺿緽ⴽ泾챿9Gd8Fd:Fg8Ff8Fh8Fg7Eg7Df9Fh8Eg7Dg6Ce6Cc8Ff9Fg6Dc6Ef5De4Cd6Ef4Be5Cf5Cf5Bf4Cd5De6Ef6Ef8Gh7Gi5Cf7Eh6Dg7Eh7Ei9Gl:Hl=HnKoLj@LhCMiAMgAOi@MiCMiALiDNkDPlBQlDQlBMhCNkBMk?JiALiCNlBMmBMlBMjALjAKi@Jh?Ih@Kk@Jk=JjMl=Lk;JiKk>Kn=JlKmq.>q-v,=u+=u*=w)x,?y-=x,?w-Ay.Ay/@x.Ay-Av0Cx/Cx/Dy0Dy0Dx0Ey.Bw/Bw1Ax1Bx0Aw0Aw0Aw0Aw1Bx2Cz1E{0Dy0Dy1Ez2F{3G|4H}5Hz7F{5Fz6G{6G{7H|9J:K9J9J:J8K6K5I}5I|4H{3J|4J{4I|2G2G~3H5J5J5J5J8K7J9M8N6K7L:O9N8M8M:O9O;O:M>OO=N=N=N=N?P?Q?Q>P>QR?U=S;R>R>QTS=RAUBWAUAUBVCVEWDVDVCTDWCWAVDXDVEWCUAS@UAUAU=Q;P;P9M:L9L9L6I8K6L5J8M5J5J6J5I7J6L4I7L5J5J4I5J5J7L7L6K8M6K7L9O9N9NU?S?T>S>S>S?T?T>S>S=S>Q>R?T@U=RHj>Jl;HjIi=Ii=Jj=Kg>Lf@MfAMh@Pj@OiBNjBMiALh@Lh@PiBPjBKiAMkBMk@Li?Ji?JlALl@KkBMj>Ig@Ki>Ii@Kk?Jj?Ii=KkIj=Ji=JhHk>Hk>Hk?Il@Ilm3@p2>n4?o3?o4?p5Ap4Am6Cp6Co6Cp6Ar5Br3Ap6Ds3Ap3Bq1Cq0Ao0Bp0Cq/Dq0Er.Cr3Bs1Cp1Cq3Es3Es1Cq3Es5Ft3Cq5Ft7Hv4Dr6Ds8Ft6Ds5Es3Fs3Es3Er3Dr6Gu3Dr4Ds5Eu4Du3Cs3Cr3Dr3Dr1Bp4Es1Br1Ar2Dp3Er0@n5Br4Ar6Du6Du3Bs7Ev4Bt6Ct6Bt6Du5Ct5Ct5Cu3As5Cu2Cu2Ct2Bs4Aq0Ao/Aq1Dt0Br/Aq-?o.@r0Ct0At1At/>q,q-=q-=q*:m+;n+;n+p)v,=v,=w)=w+=x*=x+>x*>x(P>P@R=O?Q;O=Q;R;Q;QT?S

T?T>Q?R>Q>R=P;R>SBQAQBTBTBTARBTBT@R?Q?S?S>US>R>R=PS=RQ@R=S>S=RSRAS@RBT@RBT@SBUAUBTEUGWFVEVAUBVCW@UAV@U?S?T@U@U?T@UBWBXDZC[EYGWIXGWEVDUBV>UAVBWAVAVAVAUCWCXEYG[FZGZIZL^L^K\H]FZJ_F[I^M_M_M_N]N^O_N]K^L_L_M_L_M_Q`R]Q]P\O_PcPbPbPbO_KaK_K`L`J^J^L`K_LaMbQcNaOaPbOaPbPbN`PcN`QbQ`PaP`P`P`RbQdNbNbPdSePbPbRdOaRcRcOaSeTfRdRdTfRdTgSePfQfRdTfSeSeUgSeTfUgSeVhTfVeVfTeSeUgVhVhXjWiTfUgTiTiWiWiVhViYkWiVhVhVgThObKaJ_NcK`K`I^K`J_IaKbMbPePdThRfVjVkUjTjUjWkWk\lZkZk]n]n]n]n]n^o_paoctfw^sXnUjUjUjVnUiXmXlZl[n\o]pZp[q]rbv_s]r[p[nYiRgK_H]EZDYEYFZCXBWCXAVCXCX@VEYG[EYFZEYEYG[I]GZG[I]FZG[H\J^I]L_J_H]G\J`K`K`KbK`KbL`J_MbLaOcNcOdObScQbScTdWhVgThWhWhXiYjZk[l\n]m[p[p\p_p_p_q_p^o_p_p_p]n]r]q]q]pYtXt\qZq[n\q\pYo\n^o[m_p[l^n^naq_pbs`s^r`s`ohsbpbsarbsbsbsav`scu`taububu_qatbucvcvcububxavavdydycwbwdydydyf{cxf|d|e}e|fzg|f{dybwbxaxdxbxcufxfydwgvgvftdrdr`rarbr_t_tbrarbr`mbp^oZp[p_o\m[l]k\j[m\mZk_o]o\m[m]n]oZk[l]n\m\mZkXiXiWiXhXiXh[kWiZjXkUkWmVlXlWkViVhVhVlZnXl\pZo\q[o\pbrcscs_p_p^o^o[k]kWhXgTgReTiTiShTiRgUiThSgThSgRfPePeQfPeRcPcPcPbOeNcNcPeReRfRfReUiRgTkTlQhUlWnUhWlXkWkYmYm[j\l[l\m^p^n\o\o\r_t]r_t_s_t^tavavbwcxcyexdwhzjzn~h|kl|m}j~nlnomsqpsvtwyyw{|}~z}|„ȃDŽȅɄDžɃdžʆʅʆˇ͇̈̊Њϋ΋Ћ̊͊ɊɌϋʍАԒԔؙ֛֠ڦૼ誼쮿硰᫼ꬽ޻賾絾붿궿嵿嵿簼篺뭻쭾밿걾譾Kh@Kh>Ig?JkALl@Jj=Jj;Ji7Fe?KkGgKm7Fg;Jh:Hg;Ji:Hh;Ji;Jij/>j.@n0Ao0Ao0Ao.?m/@m4>o0;k3?p2@o1@o5Bq0?n3Bp2@o3Ap4Bq3Ap4Bq4Bq2@o3Ap2?n1@o1Bo/Ao/Bo,An.Cp-Ao0Bp/Ao.@n3Es2Dr2Dr1Cq1Cq2Dr3Dr4Dt4Du8Ew7Ev7Ds7Cs5Bq4Bq3Bq4Fs3Es1Dq1Do1Eo2Dq1Ep1Cn3Dq1Bq1Bp2Cq3Cq0@r2Bs1As6Ct2@q4Bs6Ct6Bt6Du4Br6Dr5Cs6Dv5Cs4Au5Dt2@n3Ap4Bp2@n4Ap5Cr4Bs3Ar3Aq0@q1Ar2As.?r,>p0Bt.@r.@r->p/?r-=o-=m+;l,x.?w,=v,=t->s/Aw,?x+>v-?w-=v,:v,>t.>v.?w,Au-Au.Bv/Dx.Cw.Bw-Av.Bw.Bw0Dy-Au.Bu0Dv0Dv1Ey/Cx0Dy0Ez/Dy1E{2F{1F{2F{1D|2Ez6E|4Dz7G}8H7H7H~6G{5Fz7H|:J8J8M7K8L4H}4J}4I~6J5I~5I|6L4I~2H1H1F}1F}4H4G~6K4I~4I}7K}5I|9M6I~9M8L:N9M:L:N9M9M:N;P9M:KP@R?QAS;O>S;R;ST@S=S;Q;R;R;Q;R=S>T?R>R>S@T@S>R>S@S?QDW=Q?TT>TDUCUAUBVBW@T?S@T?TCUBTDVCUBTBWAVDSDTDVAS@T?S>T=QS>T@VAVBUATATATBUCVEXATDXCV@UAV?T@U>V?V?T?T@UAV@WCWDYGYHXDUEVFVGWGXAUCWATAWDYAVAWC[BYAYBWCXG[GZIYJ[J\K]K]J[L^K\J\M_J\P`L\M]O_L_M_O]L\M\M\M^P`M^O`O_P`P`NaN`OaOaPbN`N_M^J^J^MaK_M]ObO`Q`OaRdN`N`N`M`P_PaQ^SaS_RbRbScScQaQcRdQcQcRdQcPbOaMcOeMcSdRdQdQcRdTdTdSfSdTeSeTeTfVhSeSeSeTeUhQhSiRgRfQgTeUgTfTfUgTfVhVhUgVhUgUgYjYiYiXgXjXiUjVkPgNbL_K`LaI]I_JbIaK_K`MaNcPeRhThThUiVjXlWkUiYmWlYm]m\m[l\m]nZk[l[lYl[nZn[mVhTiUjUjTjVkUjWlXnYlYl[n\p[o]q_s[o\l^n]l[lYjRgK^F[F[B[CZDXCXBWBXCUDWFYDWCXCXFZFZEYEYH\G[G[G[G[I]J^K_I]I]H]I^H\K_K`McI_McKbJaMbI_MaObPaO_PcPcMaPcPePeVeVfUgVgXiXiYj]n]k^n]m]m]o_p^p`oaq]l^o]n]n`qZo\p\qYn[qZpYp\sYoZp[o]m_n^m^l\n[lZp[o]qap`q_o_p`p_q`p^n`qcrbsctaq]rau^sdsbr_s^raucw`v`vav`vbubtcvbuexdw^vdybwf{cyf{bwh}h|g|fzf{f{dyawbx_u^t^tawbwavatbudscsbs^p]o]n_pas]r^r]q\q\q]m\m^oZj[lZk\mYjYjYjXiVgXjXj[iZiZhXfZhYhYgYjYjVgVgXfVdUcUdUdZiSeUeShUhUjRgRgShRgOdSiRhUiVjVjWkXlZi[l\lYmYnZlZn\m\m[lZlWlRfRgTiSgUdUfXhUfVfReSfSfReQdPcReQeNcOdNcMbM`NaOaLaNcNcPeMcSgReQgRhRgRgQfRfThShSiTjRhUkXkVkVkVkXnYo[p[qZq]s\r^p\s]s]s^r_t_t_t`u_udybwcvexdxkzixg}j~j~pn~lllksqrqtutwyxxxz}}~ĂƁŅ˃ȅʅʅʅʂȇʈ̉ʉ͈Ί΋ϊ̊̌Ή̍ЍύύώБӒ՘ԡܣۥ߫婺䮿ꭿ鯼밽렴竽岼캾糽᳻߷涿緾洼뷿费䯹毺籼ꬺ宻譼諸歿뮻鱿밾찿봿>JcLi@MkBLiAKi@LiBLj@LiALjALj@Ji@Ki>Jk=KjLg@Jj?Jj?Jj@Jj?Jj=IiIi=Hh;HhKkJim/=l/>m->l0Ao.?m->l->l0@o1>n0>m1>m1?n1?n0?m0>o1?o3Ap3Ap3Ap3Ap3Ap2@o3Ap3Ap1?n3Ap0Ao->m.Bo,Bn,Bo,Am,>l-?m0Bp/Ao0Bp0Bp1Cq1Cq1Cq1Cq2Cr3Cu4Cs7Et5Cr6Ds5Cr7Dt2Ap2Bp3Dr1Cr1Eo2Ep1Do3Ep3Cp3Dp1Bp0Ao1Bp2Br2Bt2Bs/>p/=o0>p1?q4Bs4Bt4Bs4Bs3Ap2@p3Ar4Bs3Ar3Ar2@o2@o2@o1?n1?n2@p2@r2@q2@r3Bs0@q.>p/@r-@r-?p,>p-?q.?q-=p,v->v,=u+w->v,=u+v.@w+@t,@u,@u/Cx.Av-Av.Av-Av/Cx2F{0Dz/Cu1Ex/Dw0Dw4F|2G{2G{3H}4I}3H}2G|4I~5I}3H|4H}4G}8G7H}6G{6G{5Fz9J~7G|9J}7L:N7K8L5K~6L~6J6J6I~8M5L3H~2F3G~4I3I3H4G4H}3H}3H|6J|5I|7K}4H|8L7K:N9M8L8L7K:N9M8L9M;L;L;M8M:O;O:N>N>P>Q?Q@R@R?RUQ>R@TBUEWCUASCUCU@U@SCRAQBT>S>Q=S>RS?S@U>U?T?T?TAV@VAXEYFZGWEVGXFWFWEXBVBXAWAVBWBWBWAVAVDXEXFZEYJZJ[HZI[L]HZJ\HZJ\K]M_M[L\M]M]L\K]K^M\L\L]P\S_O`P`O_P`QaPaN`OaPbQcPbL^OaK^J_J_K`QaN_OaPbOaRdN`PbOaOaRcQaQ`UbN_P`RbScQaScRaObQcQcQcPbPbRdQcMbRbQbPcQdSdSdRbRaScReRdSeTfSfSeSfSfSfSfSeUfTfTkTiRhUfWiTfTfTfSeUgTfVhTfUgUgUgXjXhYhViWiXjUiYmSiQdMaNbK`J_J_J_IaJ_LaK`MbOdRfSgRfThThVjYmWkXlWkYm\l\l\m[l]nYjZjXhThViVhTfRhRgRfTiThTiTiVkWlWiZmXkZn\o[n\p[o[n^n^n]oZkYkTiLaG[D\E\CXBWCXBXEWFYFXFZDZDXEYG[FZFZGZFZI]H\H\H\I]H\H\L`I^J]H\I]H\JaJ`J`J`KaJ`JaM`MbQ`QaNaNaPdQfOcRfRgRgUeWgZjYiYiZlYl]k\n^n]n]n]n`o`obq\n^o^o]m\qZnZpZpYoZqYpZrZoXo[n[n]k]k\n^n]mZo[o\p\p]r[p_o]n`q`o`oar_p`q_p`q`p_t`t]q]q^r_s_s_s_sau^s_tatavat`satewaxavavewdwdwawbwdzbwbwbwbwav_v]sau`v`v_vbwbwasat`tcrar\m[l[l\mZn[oZnZn]q\mZkYjYjYjXiWhWhYjVgUfUfSeVeUdUcVgScVgTeVdUhWhTdVfUfSeSaQcUbUdScQcRbMbQdMbRgQfNcQfPeOdOeReUiRfUiQfUfVgXiTkShReShXiWgVgTdOdQeSgPdPeQfVeUeUfVgReReQdReReTfQeReNcMbK`NcOaK^NaN`McQfMbMbQePdPgPfOdPeQfQfShSgUlUkPfVlSjWjVjWkVmWmXnXnYoZpZp[q]s\r[q\r\q_t`u`uav^s_tcuatdwdxfzfyg{h|m~k|i}j~j~j~h~n|oprtuusquwyz{{}ƒǁŃŅ˄ɃȃɆ̈ˉ͉ɉʇˈ̋Ћ͋͊̋͌΋͍όΎЍϑә՟ܥޢަߨ䪻ꬾꭺ창לּ亾㺿赾⳻ߴ䲼絽鵾絽㲻⵿贻㳺鲼꯽鯼鯻鮼鯼꫽髼诽ꬺ毽鱾볿?Ka?LcLi;Ii>Jk?Ji>Lh@Ih@Ji?Ig@Lh>Lj>JkBMmBMm@Ll=Jk?Ll>Kk;Hh:Gg9FfKk;Ihk.m/>m,=k->l-=k,?m,>k.?m-;k-;j0>m1?n.m1?n/=m0>m/=l/=l0>m1?n2@o2@o2@o1?n2?n/@n-?l/@o-?m.@n.?m-?m,>l-?m-?m.@n1Cq1Cq1Cq/@o.@n2Cq2Cq4Cr6Ds6Dr6Dq4Cr2Cq2Cp2Cq1Cq1Cp1Do1Eo1Co3Dp1Bn0An/@o/@n/@n1Bp.?m.?l/?m2?o1?n0>m1?m2@p4Ar4As3@o2@o3@p3@r3Ar3Ar2@n0>m1?n2@o0>m1?n2@q3Ar2@n1?o/?p-p,>q->r.?s,?p+>o*;n,v+O>N=N=O>P?Q?Q@R;QTR=Q>R@TBVEVASAU@T>T>T=S@S=PAS@R?QASAS@SASCVBT=RAT=T>T>T;P:O9N7L8M6K6K6K4I4I4I2G4I1F0E0C0C1F0E~/D}.C|0E~0E2G1F2G4I3H2G3H4I3H3G5G5H3H3H6K8M9K8L6K6K6K8M7L:O;P:O;P;P:O:O;O9O;P;Q9O:P7M;O8M;N:M;O8M9M:N9N9N9M8M8M6L5K4K5K6J7K7K6J6J8L6L8M6K6L5J4J5J5J5J4J4J4J4K5K6K5J5M6M6M7O9M9N9N:O;P;P:O:O=R;P>Q@S?RCVATBUBUBUBVAVBU@U@U=R@U@U?T?T@U@UAWBVCWDXGXEVGXGXEVFWBVCWBWCXAVAVAVCXCXCYCWEZHYI[I[LZI]J\I\J]HZK]J\I[L[K[K[M]M\K^K^M\L\L\O\RaR_O`O_QaQ`PaJ]QcQcOaPbOaQcN`OaOaM_O_OaRdN`OaQcL^PbN`OaRdRaP`QaRbQaP`ScQaScScSeQcPbPbPbQcQcQcQbPbRdTcRbTcTdUcSdTcTdReSeUgWeTdUeUdTfUfUeTgVhVhTjUfSeSeUgVhVhTfUgUgWiVhWiWiVhXjViUgTfVhWiSgWjSjReNbMaI_K`H]I^LaLaLaLaNcNcPeQgQeThRfSgVjXlUiUiWjUhUiYlYlVjSfReQdTgReOePeQfQfNfRiPhRgQfShShXjYlXkXnXnXm\qZn\p[o_o`n^lZlWhQfJ_H]D\G[CXBWBWDVEYEZFZCWEYFZFZDXEYG[F[EYG[H\H\H\I]I]H\H\H\J^H\I]H\G^I_H^L_J^J]MaK_L`K`K_L`NbOcPdNbPdReVeTjVjUjYm\kZk]o]m^o\m\m]n\n\m\m^l]o^o\mXn[oYmXlZoZnWnXoZrXnYmYn\l\m[lYmYmZmVlYnZn[oYn]n^o`m_n_nao^m^p_p_p\m_p^s\p\o[o]q]q]q^sau]ravat_tatat_rbvavavbv`sau_sbuataucucucuas_tat\t]q]rbwbw`uavbtat`sbr^o[lXiYj[kXlYmZnXlVkYiVhVgVgUfXiXiVgWhVcTcVdSaRaRbVfTdTcRdUfTfUfVfQfQgUcQbPaPaQbRdM_O`O_K`NbObOcPdRfMaMaOcOcQeThQeQeQfVeSdTdQcScRcRcMaNbQdQdNdOcPdNbPdQeQeQeVfVfQdPbPcQdPbOdPdPdOcQeMaN`M`L_M`M_K`MbLaJ`NbOcOeNbPeRgRgQfQfQfShQfShSjSiRhVlTkUkUkUkVlXnXnXnWmZpZp[q\s]r]r]r]r^s`u]raw^tbsewcudwdwdyizjzg|i}h|i}i}j~m}ooqrutswvwzz}}ĀǁɈ̆ˆɆʆʈ̆ȇȊΉ͊ˋ͌Ί̌΋͌ύϋ͌ΐАәԜ؜ٟۜܟޥ極橹埫㠰䣴ۺۻṼ㷽䴽㷽簺䮳㪷௼紾巽䴽䵾浽浾굼뮻诼鮻謻谽ꩻ笽誼讻讼鮼谿첿곾賿峾봿BMc?Mc=Lb:Ja:Ic:Ic:Id;Jf9Ga;Jd:HcJjLi>Lh>Li=Kh>Mj?Ml=Ml?Ml@Jj>Jj=Jj>KkIiAKk>Ji;Hh=Ii:Gh;Hj:Fi;Hj8EgIi9Ff8Ee=Jjj1@j2Al3Cm1>l.l+;k,;n-;l/=k-;j0>m-;j.=k/=m/>n0>m/>l/>l/=l.=l/>m0>m0>m0>m.=l.@m.@n,?m*l->l/@n-?m-?m0Bp0Bp0Bo0Co0Bp0Ao/@n2Bn5Ao5Bo4Dq0Ao1Cq1Bp1Cq0Bp.@n0Cn3Dp1Bn3Dp2Co/@l/@n0Ao/@n/@o.?m.>m.q.?o/?m0>m0>m1?n0>m1?n2@r1?p1?m1@o0@n/>q->q+p):l+:n-=p*:m*:m,;o+:m*:l(;m'9k*u,=s*;q,=s,;s-s+?t+?t-Av/Cx-Av0Dy0Dy0Ez1Ew3Cw5Fz6G{4Ey6Gz4Ex3Dx6G|6G~4F|6G}9J4F{3Gz6J|4H|5H|2Gy4I|5H{8I~7M7L~:N9M7K6J8L5I}7K8K8L8L8K}7L~7M4I~3H}4H~5I}5I~7K5I~6K3H}4I~6K4I~4H}7K:N9L6J5I7K6J}7K~8L~8L9L8I9J~O>OR;PR=Q;R;R;RSR?S@UCU=PR>U?US@SBR>O>O?P?P@QBSBU=Q=R;Q=SPBU@S@S>Q@S?R?TAU@TBVBV?U?T>S>S@U@U@U@UBXCWEZGXGXEVEVEVEWEZBWCWCXCXAVCXEVF[FYGZF[JZJ[KZKZIYM]IYL\L[K]J\L^K]L[L\L\L[L]K]K^M]N^M]Q`R`R`Q^PaQaRbP`Q`PcPbPbRdPbN`PbPcM`NaQcM_N`OaPbRbPdPbP`SdPbTcScRbP`ScRbRbScScUeRdQcQcRdRdPbPbPbScScQaRbSdS`VdVcRcScTdUeUfUeTdTeVeVgVgTeTeTeTfUgUgUgUgSeTfSeVhTfTfUgUgUgVhWiVhXjWiTfTfSeTfQfQeRfQeNbL`L_H]K`LaI]K`K`MaLcNcPeNdPdSgThSgUiWkUiVjUiThUiViUhUgRdRePcPfPePePdNfNeOfOfPgQhQfRgShTjVhXlVkXmXmXmYnZn\q_q_m^m^m[lSgLbK`G[F\DYCYEYG\EYDXDXEYDXEYFZG[G[FZE[FYEYFZEYH\G[G[H\G[J^I]G\H\I^G]I_I^J_J_J^J^L`NbL`K_K_NbNbNbNbNbPdQfWkUiVjUiVkYj[l\m[l]n]n\m\m\mYj[mZkZk\m[kYoZnWlXmZoYnYqXoVmYoWm[lXlYmXlZnVlVkYnYnXnZn_p]o^l^m^m]o^o]n_p^o\m`p\o[p[pZn]q\p]q`p_r\q_tbuaubwbuatasauau_saveudt_s_rdubsas^r_r[q]q[t]p[p_t`uav`uatdwau^r\mVgVgUeShUhWkUiShWfTeUfQbVeQeTcRcPbUbP_SbUdT`UaT`P`P`QcRcScScQbPePcOcPeP`PaO`O`N_O`L]P`LaL`L`NbNbL`L`MaNbMaLbLcObK_OcNcRbPaN_M^N_QbMaN`M`MaQ`NcOcLaMbNbOdOdSdTbOdOdPcObPeOdOcNbPdOcMaMaLaL`J_L`MaI_J_K`MaL_MdMbNcOdK`OdOdRgOdRgQfRgRhRhVlRhUkTjUkVlUkXnXnWmYoXnYoYo\rZoZoZo\q`u_t_t_t`u`vcxevdxdzfvevg|h|fzfzfzi}h}mpppssurtwxz|}}~}ƒDŽŅǃȄɅʆˆȈʈ̇̉ˊ̊̋͋͋͊ȌΊ̋̍Ώϐіךژؚۛޟᢲ壳瞮㝩⛪ᜭݩۺո߸긾鷽곽鮸檴㬵䮺䲿䴻㶾崼㷿鵽泻㵽尽导髸譺箻诽ꬹ檼뫼ꪻ骻꯻簽ꬼ鲾벿鰽鳿豽糿鲿밽븽=Ma?Mb?Jc@Le>Jd=Hc>Ie>Id@Kf?Kc=JaMkKkBLl>JjJj=Kmk0?k.?j2Bm3Bm0?k1?l,:g.l,>l+>l)l/@n.?m.@n-?m.@n/Ao.@n/Bl.@o0@n/@o1Bo0Am2Bm0@m3Dr0Ao1Ao.@n/Ao.An0Dn3Dp3Dp2Co0Am1Bn0Al->j.?k->j-=l/=l.m0>m.n-=n.?o.?m.?m.?m->l.?n.=n/An,=k1Ao1>m0>m2@p/=n1?p/=l/=k,=l-=n,o+=o*>p*s*?t,Av-Av+@u0Dy2By3Dz3Cw1Bv5Fz5Fz5Fz5Fz4Ey7H|5Fz4Ey6G|8I7H6H}4I{4H{4H{5I|4H{5J}6I|8H}9I:L8N9M9M8L7K8L8L6L7K9I~7L~7K}6I}4J5J6J3G}5I~5I~6J3H}4I~6K3H3H5H}5I~5I}7K5I5I}4Hz6J}6J}7K~6J~9L:K;L;L;K=L=N=N=N>O>Q?Q>P>P@R?P;O:O:O;NQ>R>R=Q;P?R?S;P:P;QO=Q>R?S?S?SBUBS=R>S;QQ@P>O?P?P@QAR>S=R=S=S;Q=T9Q6N7L7L7L6K5J4I4I2G~2G}2G~3H3H4I3H2G0D~/D}/D}0E~/D}.C|/D}.C|/D}0E~0E~1F2G1F1E/C~0D1C~0D1E3I3H5I6H6J5K5J6K9N7L5J8M8M7LR9P9Q;R8O7M7M8N8M9N8J8J8K7L7L7L9N7L6K6J6I5H2E4H3G3G3G2F2F3G3G5I6H5H6I6H4H4J1G1H0F1H3I2H3I2H2H3K4K6M7K6K7L:O8N7L9N9NQ=PQ@S?SSBV@TBVAU@S>S?T=S@T>T@UAWBWEWGWFWGXGXGXFWFWGXFVDWBWCXCXDYEZG[FZFZIXIZHZKZJZJZIYHXK[K[J]K]K]M_KZK[JZJZN^K]L^L\M]N^Q_P^SaRaOaO_ScRbRbQ`UdSePbQcOaN`QaQaQaObOaObOaObRaRbQdSdRbTeTdUeScRbTdScRbScWgTdTgRdRdPbQcSeQcPcQaQaQaRbVbVeWeVcTeScTdScTcRbUcYfVfVhRdTeTeTdQcUgUgTfUgRdTfTfUgTfUgWiXjVhUgVh[mUgWiSeQcSeUgPaK`NaNbNbK_MaK_G]J_J`J_OdMbNcNcNcOeSfRfSgThUiWkUiRfThRfQeReSfPfNdNdPeOdRgOeNeRiPgOfNeOfPgQfRgTiTiVhViWlVkVkVkWlYnZnZn]k\k_n]mViQcKaJ`I]H\FYFZFYDXFZEYEYDYCXFZEYEYEXDYEXCWEYEYEYG[FZH\FZH\H]L[I^G[I\F]J[M]M^I_J^J_I]K_I^LaMaI]MaOcNbNbPdRfRfThVjVk[kYj\mZk\mZk[l]nZk\mZk[l[k[jXmZnXlYmYjWlXmXlWnVnVlXoWlYmXlXlWmUjYnXmYnXmYm\mYk]k\k^o[k]m]m[l^n_nXn[oYo[q]q[o[o[o^n[o[qZp_r^r`t`u`s\n]q^r_taq`q`qbs\p_o^o_p`q]q_qZp[p[q\p]r_tav_tbt^q_rZmWgVgUfVfPeSgSgRfPdRbO`PaQbP_NbMbO_PaPaR`P_P_O^M\O_QaO^L^P`LbMaMaK`NbK`MaMaL`P_M^N_N_K\L\K_J^MaL`K_K_J^I]I]K]G_KaL_L`K_MaP_L]N^L]M^P`L`K_NbL`P`LaL`K_LcLaKcNfOaRgQaPaO`PbO_QaNcPdK_K_NbL`MaL`O^K`MaL_I^LaM`NaMdK`NcMbMbOdNdMbPeRgQfQfPfQgRhRhTjTjUkTjXnVl[qVlXm[qXnYoXo[pZoZoXmYn]r]r_t[p^s]raubv`ufvfvavdxdxcwfyfzjl|k}oprppqrwwwz||~~}|ƒłāĄƄƄņȈɆˈ̉ʋ͉ˈʊ̉ˊ͉̋ˎьώЍЏҒהٗٗەٚ䜮圫嘩ᚪݟ۲嵷ଳݭ⯸㱹䵿츿ꬵ失㨳穵䮻毾㱼Ⲿ賾鯻㭺䰼鬺ꪻ髼못騹签쬽못ꪼ꯻譺簼鱾갽鯽賽겾걽尽첿뱾豾괾벿쳽>Mc?Me?Kc@Ld@Le@Kf?Ke>Je?LdAMe>J`>J`:J`;Kc;Kd:Jd9Ic7Gc9Hf8Fd;IfMj=Li?Ll=Kk>Jk@Kk=Ij=Jj:Gg:Gg;Hh:Hh8Gf:Gg=Jj;Hh:Hi;Hj;Hj:Gi9Hi;Jk9Hi9Hi:Ij;Hl:Hk9GjIh=Ii:Hh9Ff;Hh;Hh9Fg;Hkj/@l0@k2@l1?l0@l,>h,=h/>i.=h.:h.;h.l,>k/@n/@n.?m->l+=k,>l-?m/Ap/@n/@n/@n0An->j0Bm1Bn/Ao.Ao/@n.?m/@n/@o3Co3Dp1Bn1Bn0Am0Am0Am->j-=i/l.?l/An.?m.?m*o-;k.v->v,u,>s+O?P>P=O@R>P>P>P?R?Q?QAR?QQ?Q@RAS?R?S;OO?T>R=Q=Q=Q=Q=P@P>S=R9P=S;QS>S;QQ=PS>R@T@T>RAU@T?S@SAUCVBVAVAUCXFWFVIYFUFWFWIZFWFWGWEYDXFZFZGZH\G\G\I[JZL[IYL\K[L\L\L\KZJ\L^L^H[J]K]L\L\JZM`M^K[P`M]Q_R`Q_R`TbN^QaQaRbP`QaRbSfRdOaOaP_P`QaP_RaQaN]RbScScQaRdRdPbTdTdScTdUeQaUeTdRbTcRdRdSeRdSeRdRdTeRaSbSbRaUeVbUbTaScUeTdScRcUgVgUdTfXdTbSdSdRdRdSdSeTfUgTfRdSeTfWgSdTdUgVhYkWiXjXjTfPbOaQcQcN`J^J^K_J^MaJ^I]J^K_I\J`J_LaMbLaLbOcPdQeSgSgThThSgRfQeQeRfSfRePeNcNcPeNcPeOfQhNeQhNeMdMdNeMeOdQfShRgTfShTiSkTiUjWlWkYm]m\l^p_oUiUhQdNaL`L`J^I]G[FZFZCWDYGWGWGXDYEYCWBWEYEYCWDXEYEYFZFZG]K[JZIZK[H^J^I\J[J[J[N^JZM^J_J_K[K\H^J^MbL`J^NbOcPdQeThRfThWhYjZkZkZkZkYiZj^nYi\lYjWmWlWjWkXlWkUkUjWkWnVmSkWmVmYmWkVjVjUkWlWlVkVkYmWl[l[lZkZj\lXnUjXmYnYnXl\pWk\pXl[oZn[oZnZo[oYnZo[o\p\p[o[o[o^r^rZo`p^o^o_p\lYn[n^n\m`r[n[m\o[nYo[p\q\q_t_s`u[nYmUjXhXiRgRfQeRfQeNbNbP`N`O`N_J_MaM]K\J[L]N`P^L[LZP`K[M]M_M^O_K`K_O_L\O_M]K`K_H]L[K\J[IZJ[H]I]H]G[G\G[G[FZH]I\F]G]H^K^J^J^H]N]M^IYK\K[G^I^J_K`K\I]K_K_JbI`M`L`MaMaO_O`N_O`N^N`J_I]J]M`I]J]I]I]L]N^L`K_I_JaJ^L`McLcMbNcMbObQdQeNcNcNcRhPfRhSiTjRhRhQgTjXnVmUlVmVnUlXmYoXnXnYmZnXm[pYn\q^s\q^s_u^rcw`ucsbravaucwbuf{f{f{jk{l~ol~qssuuxwuz|x{y{|„ńƇȃņʉΈɊˉˈɈ͈̋͊ˎҌόΌҋЌъΌώԏԒבוޗᙪߔᕪ▩٥߱笴㥭ߥޟڞܞܥߨ嬷馲䠭ឫ࡮㢱婶嫺导沽簿尽簽민譺櫺嬺魻詺詺諻ꪻ꨺媼竽诼鯼魼魻豽鯼谽豻鲽簾걾鱾갽汿㱾鲼鰽벿ﲿ쵽緿鷿=Kf@Mg@Kd@Ld?Kd?Ke@MfALfBKdBMc>K_=K]:I\9I`:Jc;Kd;Kc;Kd:Ke;Ke;Ic;Hc:Hc9Gc9Hd8Gd7Ff6Fe5Dc5Dc5Dc6Ee6Ee3Bc3Cc4Cf2@d1?b2@c2@c2Ad4Bh6Dj4Bf6Df7Eh6Ef7Eh6Dh7Fg9Hi7Fe8Gf8Gf9Hg9Hg:Ih;Ji:IhKk>Kk?Ll@Jg=Ig>Lj:Gh:Gg8Ff:Ig:Hh;HhHh>Hf>Ig?Jh=Hfi.l,>l,>l+=k,>l/Bp0Ao.?m.?m/@k1Bn.@m-?m-?m,=k.?m/@n1Bo/Al/Am1Bn0Am0Am0Am.@l.>j/=j.k->l->l+n)=o';m);m):m(8k(8i'7h*:k(7k)8k)8k'7i'6i&6i%5h#4h!2f%5i'7k%5i%5i&6j&6j$4h#3h#3f$5h$5h$4g"2e#3f$4g"2e!2e!1d"2e.d!/g.e.d /f-d"0g".e!/f!0g-d/f/f /f!0g!0g".e"/f 0g!0g0f 1g 1g!2h"3i!2h!2h0f0g!2g!2h!2h"3h$4g$4h#3g#4h"3g$5i&7m$5k#4j$5k'8n&7m&7m%6l&7m%6l'8l(9m'8l%9n&;p':p*;q*;q*;q):p(9o+=o+O>N@S@R>P?Q@R>P>P?P@P?RBSAS@R?Q>PAS?Q;P>S=S9O:P:Q;R;Q;Q:P:P;R=P=RQR>RQ?N?O>P:O;O>Q;Q=SO;O=P=P?R=QR?R@U@TAUBVBV@T@T@TAUBU@TAVGXGYHYHXGWFXEVFWFXGXHXDYCXFZEYG[H\G[H\L]L^L]LZK[K[JZJZKZM]N`K]K]L]JZL]K[L\L\J]K]M\M]M\Q_R`P^R`SaR_O`O_QaQaScTdObRdPbPbO`M`PbRbP`O`QaPaRbScTdUeQdQdVeUeTdUeTdTdTdRcVeTdTfTfSeSeRdSeSeRdRdPcRdUfTePbRcRcScTdTdTdUfTeUfWeSeUfTeUfUfSfUfQgUgUgTfRdTfSfTdWgTfUgUgVhVhWiYkWiTfQcSeRdQcOaJ_H\H\H\I]J^I]K_K_I]L`KaMbKaLaOcOcNbPdQeQeQeSgQeQePdPdRfPdPcOdNcMbOdNcQfOeOfNeQhMdMdNeMdMdOdOdRfPfPfQfShThUjTiVkVjYmXmXlYk]qYlUhQdPcNbL`K_L`H\FZFZEXGWHYGXFWCYCWDWBWDXEYCWEYDXFZG[DWIYHYHYJ[IZK]K_H\KZJZL]J[L]M^M^K\K\M^M^L]O_J`J_K_MaNbPdRfThVjWfVgXiXiXi\mZkVkUjWlVkThWkVjWkVjXkWkWlVkUlUlUmViVmVlYmWkWkXlWjVlUjVkZmWkXiXj[lXiTgUhUgUhYlYmZmWkYmZnXmYm[oXlZnXlYmYmXmYn\o[oZn\p\p\pZnYm[o[k\m\m\mVjXjYlYlYl\m[kZnYm]o^pZqYn[p[p\p\oZmWlVi]l\lWlYmSgSgQeNbL`P`L^M]N^I^H^M]L\L\J[M]IZK\K[I[K\J[J[J[M]I_I]N^L^L]L\I]H\K_G[KZK\K\L\F[I^K]K]K\J\K]I[K]I[CZG]F\G[FZG[H\H\G[G\J^F\M]L]K\L]L[G\I]K_J_K^K`J^K_K`M]N`M^L]K\L\H]I]I`H^KaH_J]H]HXK\I_K_K_LbL`M`JaKaLbNbNbMaMbNbOcPdPdPgPfPfSiSiTjRhTjQgUlRiWnUlVmTlYoWmXnXoViXlUkXmYnYnZo\q\q\q^r`t^sbqar`u]qcvbvcxcxcxi~f|k|hznmoorutwxwxzyxx|{{}~€ŁƀĆʆʇˈ̉͊Ί΋ЉˎώЊΊΉ͉͊шΉόьяԌ֍֏ڒؒܔ㔨ޘݢ࠭ߠᝪڞޗܕܘٛߙܚޗݕܗޗݡ⥸㬺殺笻㲾걾ꬸ窹檸媸窺쨹橺誻驺橻歹殼髹欹歹櫺箼魻鰼鮻筻汻鱾ꮼ诼诼簽簽泽ꮼ鱾벾믾뱾벾鵼籾綿淿浾涿綿鵾赾巿帿;Jf?Mg?Kd@Le@LdBNhAMg?KdAKcALa?K]?K^;J];J`;Kc:Jd;Jd9Ic:JcHg>IgAKj@LlJiALj=Jg=Lg>Lj;Ij:Ih;Ji:Jj;Ih;HiHh>Hf?Igi.;h+8e.;h,9f.;h,:g+8e*9f)7d+9e)7d,8e*8d*8e*7d+8e*7c,8d-7e-7e+7e+9e+8e+9e*8d*7c,9e+9e.l+=k-?m,>l.>l,=k,=j.?j->j.?l+=k,=k,=k->l,=k.>k2>k0>k.@l.@l0Am0Am.>j0=j0>k0>k1=n/=l/w-;t,>t->t->t,=s/@v2Cy0Aw2Cz2Cy4Ey5F{5G{6Fz7G{9I}8I|5Hy6H{7H{5Fz5Fz7H{6F{6G{3Hz5Ez4Fz5Fz6G{6G{8I8H8H~8J9J;L8H9I9J9J8J~:K;L9J}9K|6Iy6K}5I|5I|6J|6J}6J|6I~6K4I1G}3J2J4I3H}4I}3Gy3Hz6J}5I|5I|5I|7K~8L6J~9M:L:NAS>P;K=L=NP>P?Q@R>P@R@P?O@R@R>P@R?Q?O=PR;P;RO=Q?Q@R>Q=R>S?T@RCU@TBVAV?SCWAUDXAUBWCXDXEXFYGYLXGWHXGXGXFWGVHWFWHXGYFZG[H[G\H]J\K]J]K[JZIYK[L\N^L^M_M^J[J[K\J[L\JZKZH[L^M]L\L\O]P_R`Q_SaR`Q^PaRbRaSbRaScSePbN`N`N`OaRbQ`SaUcTbVdTcTdSdReSeTcRbTeUeSdTdUdYdXdTdTeSdTfTfSeRdSePbRdQcRdSfTdTeTeUfTdScSfTfSdSdUeVdUgUeTeUfTeRdPgQfVgTfVhRdSfUdWfXgVfTfWiXjVhXjYkVhUgN`N_N_N_M_H]J]H]H\K_J^I]K_I]MaL_L_M`M`NbL`NbRbQfOcPdRfQePdRfPdPcQeMaPbNcPeLaOdMbQfQeQeQiNfNeNfMdLcOfNbNcOdPeQfPeShTiUjTiUjUiWkWlWiWjXkViVfScQaLaMaL`J^I]H\DXFYFWGWEUGWGWEYCWAWEYEZBVDXCWBVDXDYJZGWHYHYH[J\I^K_I_K`O^N_K\J[M^M[L]L]L]L]O_O^N^K`L`MaPdPdQdTcTeSeVhUgXiYjXjTiThUjVjVjUiUiViTiSiTiUjUkTjRjThTiTjVjWjXlXlWjViWhUjVkWjUi[jXgWiYiUjTjSiUkTjTiVlVjWkXl\k\kXmVjXlYlWlVkUjVkZmUiUiXlWkXlXlYnWlYhYiYjWiThTiShViXkZkYiZk[lXlZnZmWlUjXmZmYlVjVjUkYnZnYnZoWjOePdNbMaH^L\L_K\M]IZK[F]F\G]J`H]F[E[L\J[IZJ[K\IYEZH]K[K\K\L[G[J^G[H\F[K[JZHZF[H^IZJ\J\I[HZIZIZGYF[E[D[EYG[G[I]H\I^G[F[I]K]J\L^K\H]H\J^J^K_I^L[O^G]I^M]L]L]JZK\JZF\H\I`I_G]G\I]I]N^L]F[J^I\H`K^M`JbLcKbNbL`LaP_O^LbObPcQdOfOeSiQgRhRhRhSiSjTlUlTkUlUlYnWmYoWnViYmVmWkVkWlYn[pYn\t]q_s[paqarau_s_r`vbwcxdydybxf}j{lnnopstuqsuxyuwyx{z||~€ńDŽdžʅɆʈ̈̈Ό̊ΌΉΊ͉чщч̆ˊϊΌӋ֎Սؑӑڒᓦߘޘؙܙٜۜݔܐې؏֓ې܏֎ܑےܙޢਸ਼䧴䩺㭻騶檹窸誸㪷極診秹樹詺䩻笹笹欹殺欻笺箼魻謻護ꭺ꯷ꭻ밼쮻谽诽殼費꯽鲿쯾믾걾鯽毻㴽䳻ⶾ嵽㵾賻䶾洽浾浾糼嵾굽峿趾綼涽綿淿丿帿巿㸿>HdAKhALh>JcAMg@Lf>Id@Kd@Md?Ja?K`@N`?J^BMe?Lc>Kb?Lc?Lc@KcCOfAMdBMd@KbJe=Id;Je:Iej/=h/>i-9g.;h.;h-:g,8e.9g+8e,9f+7d+8e*7d*7d+8d+8e*7d*7d*7d+8e,8e*7d-:f+8e,9f*7d+8e*7d+8e*7d+8e+8e)6c(6c)7d)8d(9e):e(9f);i*;i*;i+m->l+l+>k+l*;i*s)>s';q(=q(;m*:o*;o+=r,=s,=s,=s/@v.?u.?u/@v1Av5Ex5Dy7Gy5Ew7Gz8H|8H|8H|8G|4Cx7F~6E|6G~5G}3Hz3Gz6G{5Fz6Fz7G{8H|8I|7I{6H|6H}8H7H~7H~8J5J7L6J~:J}9J:K~:K:J9I~8I}5J~6J4H}6J|6J}5I|5I|6J}3Hz5J|5J}2Iz4H{4H}5G}9G7I~5J|6J}7K~:N:N:M;K;M>O=N;L;MP@R?Q>P=P@P@Q@S@R?Q@R?Q@P@Q@P@P;OR=Q=Q

OP;O;OHg>HeK^ALcBNdBNfCOgBNdBNdCOeANd@Mb@L_=I]:J`:Ka8Hb:Jd:Jd9Hb6H`8Ha3C^6Eb5Da3Ba3Ba3Ba3Ba3Bb3Cd3Ae5Cg7Fh7Cf8Eg8Ef6Cd8Ef6Ed6Ee5Dd6Ec6Eb7Fb8Gd8Gd7Fc:If:If:Ie=Jf=JeLg>Lf=Kg?Mk>Kh?Ig?Jh@Ki@Ki?Kj=Jkh,9f,9f-:g,9f,:g+8e,9f,9f+8e)6c,8f+7e*7c,9f-:g)6c*7d*7d)6c*7d+8e+8e,9f)6c,9f*7d+8e*7d)6c*7d*7d*7d(6c(7d'9e+l-=j,:g,:g0=j0>k0>k.k,=l+l+=k,=k*;i+t.?u/@v.?u0Aw0Aw2By4Cy4Dw5Ey3Cv7Gz5Ey6F{8H|9J}7F|5D|6F|3E{3Dz4I|3Fy6Fz5Fz7H|9H|9H|6G{8I}8I}4J7I7G~8I6G~5J~8L7J:K~:K~9I}9I}7J}7L}5J{5I|6J5I}6J|6J}6J}6J}6J}5J}3J|2Gy3Gz3G|4H}3G|5I~4H|4H{5I|9L:N7L8LO?P>OP?Q@R@R?Q@R@R?Q@R@Q?Q?QAS@R;O;O;PN=N?P>P;OS>S=R?TASAV@UBVATDVCWDXEYDXDWHYFXGYGYGWJZGWH[HYFXGYHZJYKZHYHXFZDZF\HZHYJ]L^I[K]KZJ[L_L^L^K]J\K]H\K\HXHYK]IXJZJ[HZIZK[IYK\OZNZQ\P_TbR`SaVdScQaRbSbTdReQdRePcQcObQcOaPbPbRdQcQdScRbRbRbRbTcTdTcUfSbUeUeVfUfQgTeUgUgSeTfSeRdRdQcQcPbRdSePcSfTeTdSfQcRcQcQcTfSdTfTfSeVfSfVhTfUhTdUdUgUgUgVhVhThXjXjXjYkYjUfQfP`J^J^L`J^I]I^K[J[K[L\G]I^J^I]K_I]H_I^I_L`K`Q`P`NbNcMbLbMaK_L`J^MaK`H^I^LaMbLaLaKcMeKbMbK`LaMbNdLcLcLcMbMbNcMbPeOeQfQfShSgTgThThQgRgRgUfReNaK^L_K^L_K_I]G[FZFZEYEYDXFZEYEYFZDWDVCXBWDXCWEYDXEYEYDXF[GWGXIYGYL]H]M_L^K]I[L^OaN`N`J[M^M^M]J_K_K_L`K_L`MaMaNbPcRcQdQaTdUfUeRhRfShWgShThQePeQfPePeQfQcQfOdRjQiRgThTgSjVjViThThSgThThWgTeTdWhZjRgThThQfQfQgTgUiUiRfUiSgRfTgTiQfUjTiShThSgThThRfSgSgThSfSfTfVjQfVdVfVfScVgUfRcUfWhWhVgVgWhVgTiRfRfSgRiUiWgWhYjZkXiYjTiTcNcNbL`I^M]J[K\J[J[J[GXHZHYHYIYJ[HYHYJ[HXI[J\J\I[K]M_J]L]F[H\F[G\G[F\IYHZI[K]J\GYGYHZHZGXCYDYF[EZDYDYF[F[E[F[G[E[EYI[J\L[I[L^K\G^I_H]H]F[H]M^GYI\J\L^J[L\K]I[K]K]J\K]I[L^N_I]J]K_H]M^L]K]J^K_K`K_MaMaJ`JbL_K_LaO^P`McNbQdPhNdQgPfPfOePfQgSiSjSkYkRiUkVlUkWlTiViRfTiShShShUjUk[oVmVl_s^p_q[n]p[q\q_t`u_tavducugxeygyj}llnqnststuwvxwzzwy|ŀÁÃǀĄɃȅʅʄȅʂˆ̓ɂ̄ͅ΄͆Єˉ̊ъчӉԎ֒ڐ؏אڒؔڗݗޒؑגَ׌֍ՏٌՐًՑ٘ۙۛ٣ߣ⤵㧸樴䧵䦴㦴㦴䩷榴㧵䦴㨵夸奶ᤶ⨵婷檸䬺竹筹笹毻鮻簻쯻묹䯼篻Ჺ岹鮻簽诼譺氾殺Ⲻᱹ௼精䲾歼㱽䲾鶽鳽紼糼絽泼浽㳼㴼㴽㲾汽峾赾沽精屽Ⲿ䲿㷾絼鳾9Gf9Hd9Ge;Ie:Gb?If=Hc?Jf@Lf@Le@Lc@Lb?J`@McCOfCOeAMcEPgEPfCPbAL`?J_BM`BN`>M`>Ma:Ka;Kb8H`9Ia8G`7E]6F^7Ga4D]5Da4Ca5Dc5Dd5Dd7Cd7Dd6Bd6Ce7Dd7De7Dd7Dd7Cd9Fg8Hf7Fb7Ge7Fd8Ge9Hf:If;If;If9GdKfLg?Mh>Lf>KgCMl@Kj>Ig>Ig>Jhd-?g-j,?i+t/@v.?u.?u/@v1@w1@w3Cw5Ey4Dy5Ez7G{7G{9I}7G{4Dy5Cz3Cy1Bv0Bv1Ey7G{4Ey5Fz6Fz6G{7H|7G{7I~4I~5J8J8H~5J~4H}6K7J9I8J6K6J5I~6J6J4H}6J6J6J|6J}6J}5I|5I|5I|4H{4H{4H{4H|3H}4J3H}4J~5I|:N9N9M:J~;KO:K?O?P?Q@R?P@Q=OAS?Q?QASAS@R>P>QO=OR=R=R>R=R>S@U@U>SAVBVBVAVDXDXCWEYEXDYEYEWGYH[J\HZI[GYHZHZJ\I[L]HZGXEZEYEZFZJ\L^K]K]J]L[L\J\L^M_J\J\K]L^K^I[HYKZJZJZJYFWHZJZIYK\O]N]Q_N\P^Q_SaRaO`RbRbQaSbScQaQ`RaRaP`ScPcRdRdPcQbSbRbScSdScTdTdSfTfVhReTdTdTdRbRcReTfUgTfTfTfTfRdRdRdRdTfRdSeRcUfPcSeRdQcSeRdRdPgTeSfTeVgUeWhUeYeXfWgThUgWiWiWjXiUgXjZlZlUfUeOdMbJ^J^J^K_J^H]K[J[K\J[K[K\I^H\I]G\J^H\J]J^I]K_K_L`K^KaJ_I^L_I]L`J]J^I]I^I^J_J_J_J_L`K`K`LaK_KbLbKbMbMbNbNbK`LaLaNcOdOdQfPfRfRfQeNdQfNcQfMbObK^I\K^I\H\J^J^G[EYEYF[DXFZBVDXCXDYBWBWBXEYEYCWCWDXDXFZEZDYIZHYGWGXE\K[L]L]K\L]L]IZM]NaK]L^N^MbJ^J^K_L`L`L`I]L`PaPbSeTcPaQbQfQeRfQeOdSgRfPdPfNcOdPeQfPfOdQeOgOfQfSgRfSgUiSgSgRfSgThSgUeUfTeVgQgRfTgThRhRgQfQfQfThRfQePdQeRfSgThRgRgTiSgRfRfSgThRfThThSfRfQeThRgRfQeQfQePeVeWgTeUfUfWhVgTeTdPfNbNbQeQeSgUgSeUhWhVgUeQePeMaL`L`K_J_L[J[HYHYJYIYJZJZHYI[IZHYHYGXGZHZJ\GYI[I[L^I[L^L^J\FXGWGXHYIYHZGYGYJ\GYGYFXGYGYIZCYDYE[CYDYEZEZEYDZCYCYF[IZEWJYGYI[I[J\J\J[J[K]I[EYG\L[K]J[J\I[IZM^J\I[K]K]I[I[K]H]I_L]K]K]K]K]N_J^L`LaMaM`JaH_M`K_K_OaQcRcNcRfNbPdOcMdMcNdSiOePfPhSjTiTjVmSgTiRgSiRfTgQfOdTiShUjTiVlVmVlWl_o]p\n[o[p\p]s]r`u]sductbvgzgzj}lmnmrpsqsuvtuzyx{w|{~ÃǁŁŃȃȂǃȂʃʂɂʆ̂Ʌ˂ʅʆˆˆˊщ҉֌Ќ֋Րٍғٓڒِ֑֓֐؎؎Պ֎֏،Ւؒ֗ۛޞݧ⢳椵⧹楸楷娵䧵娶槵䥳㧵䧵䨶奲ᢰߦ⥷婶橷䭻譸䭹櫻孼筺諹殸꯼箹殻ⲻ糺䯼鮼讻簽鮻簻䯼䱾寻宺䰻⮽㰼糿鰽涼谽谼䰼䯼䱽Ⲿ㲾沾籿尽鯽鰾갽걿걾鰾殽㳽䱽䱽㱽沽汻⯾:Fh:Fg:Gg:Gd>Mj9Gd@Ki=He>HeJb?KdBMeAK`DNcDOdAMcCPdBPbCNbCNaCM_EK^CN`>O^?N_La;J`8G\:I]8I]8I_5E^5F_5E_5F`5Db7Gb6Da6Eb:He9Hd7Fc8EcNd>Me>LgAOi>Lf>Lf?Mh>MhIf>Ig?Jh;Jf;Iff,>f.N>N=MR@R@R@R>PAS>P>P?Q;P=QTO>P;P=Q=Q;O=QT>R?T=U=U>SAV?T@UAVBVDXCWDXDXFZCWEYEYEYCWGXEWJ\HZJ\HZHZI[IZJ]F[HZI[DXEYH\H\K]L^K]L^J\L\L\J]L^L^J\K]H\M\L\M\JZL\K[K[IZIZHYJZHXK[M[O]O]M[P^Q_R`SaSaSaTbQbRbRbRbQaPaO_QaUeScQcRdQcPcRbP`SbWdRbRbSdPdSeSeSeRfTcRbRbSdTcScTfUgSeSeTfSeRdSeRdRdSeRdRdRdRdSeRdSeSeQcTfTfSeSfUfTfUgUfTfXeYfUeTdUgXjYkXjYkZlZlYkWiUgNdL`K_L`K_I]J^J^L\J[J[K\IZIZIZL]J[F[JZJZI]I]H\J^J]K_K_I^I^I^J_I\J^I]I]H\H\G[K^I]I_I^J^I^K`K`I^J_LbJ`LbKaLbMcLbJ`LaKaK`MbMbNcOcQePdQeMbMbLaJ`MbL^K^K]K_K_J^I]FZFZDYEXHXEZFZEZCXDYCXCWBXAVBWCWDXCWDXDWDWDXFZFZIXGYHYJ[J[K\L]L]L]IZL\M^K]J\J\L^N^I_I^I^K_J^K_K_M^PbTfPbSePaQaNcNbRfPdRfNbPdPdPfPfNcOdPePeOdNfOfPgQePeRfQeRfRfSgQeSgSgRfUeSdSdUeRgSgRgPePePeNcPeQfPeOeQePdPdPdQePdSfPdQeRfOcRfSgRfQeSgOcPdQePdPdThSgPdSgRfThRfShUeVhTeTeSdQaUeOdNbNbPdPdPeUeRcSdTeUfUfPeQeMaJ^I]J^I]L\J[IZHYHWK[IYIYK[KZK]J[EWGXH[HZHZGYHZHZHZK]HZK]I[GZJZJ[I\GYGYI[FXHZGYHZFXHZHZHZAXEZFWHXDZDYF[CYDZBYEZDXHYH[IXIYGYH[HZHZGYFXFXGYF\EZG\F\E[F[DYDYEZF\HYJ[L]J[J[JZH^H]I[J\J\M_I[L^N^J`K^M`IaH^H^J`L`MaRcRdSeRcOeMaMbOcOdQdMdNdPfRhRhRhRhTiQfRgRgQgReShQeRePfRgQfShTiTiSlUlWiWlXhZl[m^oZn]p]r[p^sbrbvexdwgzi|i|mmpnoqrsstwtwxyyzy{~ǁĂǁŃǃȁƂȁɃʃʄ̄̅̃ˇω͆ˇ̇̅ʈ̈́ψщЉЊІыӊԌ֑Ս֑֐ב֍Սٌ׎֌Վ؏ٖ֕ڠߢᣰलᦵ䩶橶娵䩷秵⧵⩷㧵⨶槵䪸祳⣱঳夶䩶⩷䭹孺歺櫸䬼笻嬺竺魺簺尷ᱹ㳺殻讻篼诼鮻导氼毻嬺䭺䯼箼ⰼ䰼簼氽簼㰼䯻㯽䯽対㭻氼殻䮺䰽鱾걾뱾밼鰼鰼氾殽⳽䳾箼㱿䰾8Gh8Hi9Hg9Gg7Cc=Ih=Hh?Jh=Ge>He=He=Id=Ib?Jb@IcALdBNfBNdAMdEQfEPcCNbENbDPaBN_AO`O]=N^=M]@K]?O^;K[;J]:H^8H`9Fa9Fa8Fa9Ea=Ib;Fc;Eb=Gc?Ie;Fb=Hc;Gb;Id;Id:Hc;Ic:Hc:Hc:HcIe?Jf=Heb6Be4Ad2?b3Ad3Ad2@c2@c2@c3Ad3Ac4Be3Ae0>b1Aa3Cc1@b2Bd0@c0?d0>g-=c-?f-N>N@P@P@P@POP@R?Q?Q@R@R>P@R8M;O;O;O=Q=Q:N9O:P9O:O9P:Q;Q=O?O=N=N=N=N;O;OS=Q@T>S=T?U@U?S>S@U?T@TAUAV@TFZDXBVCXDYBXCWDXFZDXBWGYI[I[I[GZH[K]L_J^H\I[I\CXFZG\H\HZI[J\K]L^L\L\K]L^L^K]J\L[L\L\K[L\K[L\JZJZJ[HYIYJZK[N\N\N\O]O]O]R`Q_SaP^R_VdPaRbP`QaTbTbQaRbRaPcOaRdQcP_TdRcScScTdSePeNbPbRdTcQaRcVaUbRcTdUgTfSeSfTfQdRdSeRdRdRdTfTfTfRdQcSeRdSeSdReTgTfUdUeVfWgTdUdYfUfTdTfVhYkXjXjWiZlWiYkUfScK`K_K_J^J^J^G[G[J[HYJ[IZHYIZHYJZJZF[IYF[G[GZF\K`G\G\F\G\G\EZG\H]GZK`H]F[I]H\I]G[J^J^J_H]I^J_J_KbH^KaI_KaKaMcKbL^NaN`McK`MbLbOcMaPdNbJ_K`I^H^L^K^J]J[I]L`H\G[I]H\G[DYG\EYDXDWDYCXCYBXBVCXAVDWCWG[CXEVCWDYG[FZE[FWGXL]IZL\L]K\K\K[K\K\N`I[I[I[N^K\K\K[H]K_J^K`PaN`N`PbQdRdPbNcNbPdMaPdPdNbRfMdOeNcNcNcPfQgNfPgNfPfOfPdQePdQeQeQeRfOcReTdTeTdQfRfOcOeNcMbOdMbNcOeMcNcPcOcPdPdOcNbMaOdNcOcLaNcOcNbQeQeOcPdOcOcQeOcQeQePdThSgQeRgVeTdSdSdScNdMbOcL`OcOcMaNaRcUfTeRcTePaOdNbJ^J^J^J^I]IYHYHYEVJZIYJZIYIYK[JYHXL\FWGYGYFXJ\HZHZI[HZHZJ\HZGYHZHZHZHZHZHZHZHZGYFXEWH[FXFXFWBZHWEZDYCXDYCXDYCZCYDZGXFXFXHXHYK[FYFXGYEWGYGYE[DYDZDZE[DYDZDZCYDZF\G]H\I_G\I_GXJ[J\J\K]O^J]L^L^J`L_J^I_I_I_J`OaNaKaSdPbSdRcRdRcRdSdPeNbOfNdPfPfRhSiThQfQfQfPeRfRfQeOdQfOdRgShPeRgQiUhTjTgVk]mYkZmZnZnZo^s]r]rbudwfyexfyk~i|llnpoqrsusvxvwzy|~|āŃǃƃȃʂɂɅ͂˅ʉφ̋Ј̈́Ɇˆˆʄ̄ˇ̇̃φψшъӇΎՎՐӍԍӋԌՌՊՊύԋԖٚܣ㧴⧵䨳㬸駳੷㪶㧵⪷⩷䨶㧵⧵䥳㤲ॳ⨵奸檷䬻歺䬺䬹㬺筹櫺娶䫺鯼諹䮻岹᰺嬺䮻宻尽笹㰽篺㮸篽殻歸౼尼䯻㰼㲽篽䮼䮼㰾宼㰼箻䮽鮽謼筼篽꯽ꮼ鮼魼讻簽篽䯾氾寽䰾Ɀ6Dg7Eg7Ff8Hi6Eg;Hj8Dh:Ee:Gh;If>Hg>If=Hd>JeBMg?Jd@LfBNfAMfBNcCNdCNeCPcCQcCPcCOb@M]?K\?K\BO`?K]@N_Na=Mb>MeIb=Ia=Id?Je?Jf>He=Gc>Jf;JeLg>Lg>LgMe=Lc>Lg?Mg?Mg>Lf>Lf=Jd?Kd=Ic=Hd?Jg>Ig>Jhc7@e4>c5Be4Ad6Cf3Ae1?b5Cf3Ad2@c2@c2@c1?b1?b1?b3Ac1?c.>`1Ac0@d.?b,=b.>e.?e.=f0>h.;e-:d,9c-9c,9e,;g,t-u1@w2Ax0?w/>v/>u3By3Bz5E{6Fz5Ey6Fz7G{3Cw4Dw2Bu3Dx5Fz3Dx5Fz3Dx4Ey4Ex4Dy4E}2F{3H}0F{1F{5I~5I~4H}3G|7K3G|5I~6J4H}4H}4H}5I~5I~4H|4H{8L~5I~5I~5I~7K6J7K8K~6J}5J|3H{2G}3G|3I~2H|7J}6I|=NN?O@P=M?OO:O:N;O:N;O>QAR@R@R@R>P=P;M=N:N:N9M9MRS>RBRBSDT@Q?T@UAU?SAV>SAV@T@UBUCSDVAUEYDYCWHYDWBYCWCWFZG[HZGYJ\GYHZJYJYL\I\I]K_J[K]HZJ[L]I[HZJ\K]K]K]K[L\M_K]L^L]H[L[K[M]K[L\J[L\L\L\J[JZL[J[L[LZMZO]M[O]P^Q_Q_O]SaP^S`PaQaQaScUbTbSdP`RbObOaOaOaM_P^O_P`TdQaQ`PbQbPbQaP`RcVcTbTcScTdTfSeSfTeUdUfUfSfRdRdSdSeSeSeTfSeRdSeRdQdUdSdSdTeVgVfWgTdTcVeTeTeTfVhZlWiWhVhYkXjVhUgQ`J_L`I]H\I]H\H\F[J[HYIZHYHYHXHXDYEYG[F[GZEZFZFZG\EZF[EZEZF[G\F[F[GZGZF[DYF[FZE[H\J^G[G]H]G\F[H]KbI_I_J`KaJ`K_K^L`MaM_KaJ_LaL_NbL`L`MaJ_K^I[K^GZK]I\H]I]H\G[FZG[H\EYG[EYCWFZDYCXAVBWBWAWBW@UEYBVEYGVGXGXGXE[EZHXGXGXK[M`L]L^J]M`OaK]J\M`L_I[L]J[L]J[L\H]K_I^OaN`PbOaObRbPdObKaMaPdOcMbQeQePeLbOeNePdMbNdNdNeLcNfNdPdNbMaPdOcOcPdNbOcQeRbTdPePdPdOcNbNeNbMaNbMdOfNbOcNbOcOcNbMbMbP`PaQaO`O`P`L`MaNbNbOcOcOcMaNbPdPdObRfPdQeQdSgNbMbRaQaLaMaMaNbMaOcNbL`LaO_O`RcQbQaPaK`I^I]J^H\I]I^JYGXHYEVIYHXHXHXJZHXIYL\K[JZGYI[FXHZFXHZI[HZI[I[GYI[HZFYEXGYEWEWGYHZEWGYGYIXFZGXEWFWCYCXDYF[CYDZDYDYIZHYGYEWGYHYGYEVHYDWGYEWFXFXDZDYGYFXGXGXGYGXGXIZDZEZH]H]F[G]J\L^L^K]J\K]K]J\L]H^M_G^G]I_H^I_MdMcMbMbShNdQcQcSeTfSfTfPdRhPfOePfNeQgQgRhRfPeQgSgSgQdQdOeOdQfQfOdQfPeShShTkUhVkYkZjVlZn[pZo]r_uasat`sfyfygzi|l|nm~ornopstrywxwyyz{}~}~ǁȂȀƅdž̈΂ɇΉΊω̇ωЃɄ˂Ʌ̅ͅˇЇЈхΎՋӋΑыϊ·ъъщь҇ҊϋӒ՚ޜޡߤ㨶婴䩴嫸婶⫸䩸媸娶㨶䧵ᨶ橷樶榹䩷䬸䬸⮻᰽笸᯼骺䪸䪸筸谼鰼䭺䰺孹㮻孺宻死宺䯼汻䭺宾㮺⭺⮻㯼䮺㰼孻᭻߮䮻⯾篾ꭽ笹欻毾鮾讻鮼鬺诽꫺簼鰽簾寽᯽䱿籿䱿4Df4Df5Ef7Ef5Df8Eg;Fh9Dg8Ee8Ee8Ef:Ge?Ig=HfIe?IeK_AN`@M^@L]CM`CN`EPaBN`DPdAMa?LbCOeCNg@Le@Lc?K`BNdAMfAMh?Lf:Ic:Hb=KeLf>Mg@MgAMh@Kg?Kf>Lg?Mh>Lg>Lg>Lg@Ni?Mg;Ic=Le@Nh>LfLf>Jda1@b0@b0@b/>`/=`2?d/>d->e.e-9d.;e.;e,9c.t,:q/>u.=t/?t1Bt4Dx1Au2Ax2Ax3By4Ew5Ey5Dy6Fy6Fy5Ex3Cv6Ex3Dx1Cw2Cw4Ey3Dx1Bw2Dz1F{2F{3H}1F{2G|5I~5I~3G|3G|4H}4H}5I~5I~4H}6J5I~3H|3H}5J~5I|6J}5I~7K8L8L8L7J5I|5J|3I{4H{2G}3H}4J3G{6J}8K~;K>L?K>K;K>N>N?O?O>N@P>N@PP>P@R?Q>O>N=P:M~8L:N:N8M;N;L9M8L;O9PR>R?S?S@V?U>TS=R>S>S>R=QBRDUBSBSBSBTBSCTFVCTDTAW@T@U@UAVATDSEUBUCU?TEYGYFXFXDYCWBVEYFZFXHZGYFXJZJZFWIYGYFZGZL\I[GYI[I[I[J\I[K]IZIZHXJZFWJ[I\H]K[L\M]K[L\J\IZK[L\K[IZJ[JYJZJZKZK\K[N\O]P^O]O]P^P^Q_N^P`P`NaQbTbR`QaP`ObN`N`O`N_L^N`O^N^O^RePaQ`O_P_RaVcTbSaSaVdTeUfUgTfUdWdUcSeTeRcRcRbRfRfSgSeTfTfSeRdSfUdTd[hYfXeXfWeVfUeTfSdTfRfSgUfUgUfTgUgVhVhSeQcI^H\J^H\I]H\G[I\J[J[HYIZHYIYEZDXFZEYEYBWBWBWAYE\DXCXEZEZCXDYCXDYEYGZDWAVCXEZDYF[EZEZG[H[HZHZH]F[H]I_I_G^J]J^G[K_K_K`L^H^I^I^I]K_K_J^H]H[J]GZI\H[I\I\J[I]H\DXH\FZFZG[EYDYAVCYAVEZCXBWAVAVBW@VDWBVDXFVGXGXEVGWIZGXIZIYJZI\J\K]I[K]K]K]M_J[K\K\J[J[IZL]J[J_L`N`N`OaOaM_N`O_P_ObPaNbMbNbRbK`NbMaLbNdMcNdMaLbLaLcMdNdKbPdRaMbQeQeOcMaMaPcNePdNbMaMaL`NbObReLaLdNeNaNcOcNbNbOcMaMbP`P`PaPaM^O_N_O_I^L`K_K_OcL`NbL`MaMaMdPfLcQePeOdOcQeOcMaJ^L_J^J^M`M`M`K_M`J_N^N_O`O`N`PaJ_M^H]H\H\G\IYHYGWIYFWGXIXIYGWIYJZIYJYM\HXFZI[FXGYHZHZHZHZEXHXIYHXHWGVGVFXFXEWHZGYHZI[HZHYHYGYGYGXE\CXCYGYGYGXFYFYFXFXGYFXGYHWIYIZFWFVGZDVEWDVCYCYGYEWHZFXFYFXFXHYE[EZF[EZG\F[K\J\K]I[J\M_L^K\I_I^J^I_I_KaKaKaNcL`PdPeNcOeTcSeQcReTcTgPcOfOePfPfSfPgQgQgQgPfSfRfQeRcRePdPeNcMbQfOdQeQfQfRhSjSjViXkVjXkVn]r[p\r_rbuexgzh{i{i}k|m~m~npqqpsuttvxyvz{}{~~{}~~‚Ƀʅˇ͆ˇϋόъϊϋӋҋ҇чͅ΅ʈχЊՎ֌ՎԋҍюҏЎьъϊχЌψψΉЉЋЍҐӓזٚݛޠࣱ⢰ߥᦴ᪸娶㩷䨶㪶㫷䩷䦷㩹㬺㮺Ⲹ⬷߭㪸䬸䬸穸ꭺ鬸宺䭹㬹㬺㪺㭻᭼Ⲽ䰽䰸䳹櫺఼㮻䱹౹ᱺᮻ఼᯾㮼᱿㮼㭼歽譺誸竹笺諺嫺宽讽譽箾读谼篽䯽ᮼ䮼䯽ⰾ5Ei4Df4Df5Ef6Ef5Ce8Dg8Ee7Fe8Eg8Eh;Hg:Fg>Ih>If=Hg=Gg;Gd9Hd:Hc9Ga9Ha:KdKdBLgBLiAJg@Lh>Lg>Lg@Ni?Lh>Lg>Mg=Oh>LgALg>Le=Kd;Ic?Mg?Ke=Ib>Je>He@Jf=Gd?Jha2@c1?b1?b2@c.<`/=c1>c/>a/@a/?a0>b/=c,<`->d+;b,:c-:e0K~>LN>N=M>NBR@P=L?O>N>N=NQAR@QBT=O>Q>MSSBV@TBVAU?S>U@V?UU>T>T@U?TAU@TDTBSCTCTEVEUEUDUASDUCTCSDTCSDTDVCVDUETETCVDVGYFXKZEWBVCWCXBWIZGYHZI[IZIYKZMZIYGYH\FZF[IYI[IZGYFXK\F[JYJZHXJYIYHYGXIZF\J[KZK[L[L\J\I[L\K[JZJ[HZHWJYL[K[L\M]N\N\O]O]O]O]Q_P^O_P`ObRcOaVaR`P`P_NaOaObKcLaM_N`K]M`M`ObO`R_R`R`S`UcR`Q^SaS`ScRdSeTgVeYfWeTfTeRdTeUfVgUeShUgSeTeSeSfUdTdUeVd[hXeXfXfSdTfQcNdRgPdOdOdRcTgRbSdTeQbRcN`I]I]I]I]J^FZFZG[IYIYHYGXDXFYFZFZDXDXCW@U@YAX?VAYAXDXCXFZCXCXCXCXAWDUBTAXCWCXDYF[DYEZEZEXFXFYF[F[F[G]G]H[FZI]H\J^J^I]J\J_H^G\I]H\J]I\J[FYI\H\H\L\IYJZJZF\G[FZDXEYEYCWCWAVCXAVCXCX@UBVCU@U@UAVAUCWBWEVFXIVHZHYIYIYIYIXM\I\K]L^K]L^K]M_M_L]K[L]M]K\J[K\K\J`L^K^ObM_OaOaObRaQ`QcPaPbP`P`O_MbL`L`McNdJaH^LbJ`OfMbJaKbM`K`Q_MaMaMaJ^MaLbKaLbL`L`L`MaL`L`K_L`KbN`NbObNbNbMaMbMaMaMbN_O`N_N_O_L^M^M^M]J_K_K_H]K`J_J_LaK_LbLbMcPaRbRaKaMaL`L_H_F]H_G]H_LbH`H^I`J^M^M_N`L^N`L]I_I\K\H]F[IZHYIZFVDZDTDVHXJZIYHXHXIZIZHYIYGZFXHZJ\GYGYGYGYGYGVGVFVFVHXHWEWGYFXHYIZFXHZFXJ[JZJZHZK\F\G\F\IYJ\IWKZGVFYEWFWFWFXIWHWGWFWIZFWFXFXFWBYCXGXDVGXGYFXGYGYIZD[DZE[CYF[F\IZJ[I[K]I[J\J\J[F^J_I_I`KaK`MbIaMdLdMeNdPdOePfSdRdSeVeTfPfPgPfNdQgSfQgQhSiQgPgReReQeQdRdQeQfPePeQfRgPeRgRgTkSiTjSjVnVhWnXnXo]r]ras`sbudwh{h{k{l}l}n~moqqqsttwwxyzz}{yz||}ÀĂǃDžȅ̅ˊҋύэҋЎՐ֊Ռևψшχόӊ֍֌֏ՉЍҍБӏϊ͈ˈ͉΄͇̆ΉЇщԊьҎ׍֐֐֓ג֕ؗۛנۤަ᧵⩵ᨶᨶ⧸䩷㬹⯷௸ⱹ㭹㬷婶䪵欷멸誸櫻櫹⭺㪹⮽孾孻⯻㳻䴺䳺讽㮺⭺߰߰హ఼߮⮽⮼୻ହ଼嫺嫹櫹骸婸孽謻欼笻歼謺欹殻箽⭼౿㯽䰽㰽5Be4Cf5Cd5De6De5Ef8Gh6Eg9Fh8Eg8Eg9Eh:Hj:Gj;Hh6De9Fg7Dd9Ge8FcGc?Ld>Jc=I`@Lb@NaAOaANa@Na@M`DQc@M_CPb@M`BLdCLfCMe?Kd?Ke>Je?Je?Ie>Ic@Le@KfAJg>He?Hd>JeMh>Lg>Lh>Lg?Lh>Mh>LgKe?JeIe=Je:Ie:Ig:If9Ie:If9Gd:He:He9Hd8Fd7Eb6Db5Da5Eb5Da0@^3Aa6Bb7Aa6Aa5?_3A`2A`3@`5Bc6Ce6Ce6Ce8Ef8Ee4Ac6Ce4Ac5Bd2?a4Ac1>_6Cc2@_2A`1@_4?`5>b5=`3?b3@c1>b1>`3?b2>a4Ac3@b4Ad3Ac/>a0@d2Bd/>`2?c0>a0>b/>a0Ab.=`.<_0>a0>a1<`1>a/=`/=a-:`-;_.;a-r1s.>r/@v0@w/@v0Aw0Aw1Bv1Bv3Dx0Au1Au4Dx2Aw3Bw1Cu2Cv3Dx0Av2By3Cy3D{1C{0Dy1Ez1Ez3G|3G|3G|3G|2F{4H}3G|3G|6J3G|5I~4I~3G}3H}4H}6J5I~5J8L9I}8L4J4J4H~4I~4I2G1F4I}5I{4H{8K;M9J~7I}9I}9J~:J~>NONO=N9N7N8NS:O>S>O@P>O:K;K;M9O9M9M9M9P9O8N8M7L6K6K8M:N9M9L~;K;L:K;LU?TAVAVCXAXAX@WBWBWAV>T>T@UBWBWBWCXBW@UBWCYEYCXDYBWDXCXF[F[F[G]F[H]H]G[J^I]H\J]H\H\I]FXHXJZIZJZIYJ[HYFVIZEXG[EYDWF[BWBXBYCXAVAVBWBWBW@U@U@U?T?TBUATGWCTGXGXGXFVEZG[G\I\IZJ]J\L^L^K]L^HZK]J\K]M_L^K^J[L]L]L\L\L\K\J[M_ObPbN`N`OaNcLaQbPbOaJ_L_MaI]K_L_OaObK`L`KbKaJ`KaM`K_L`L`K_L`K_IaLbLcL`L`MaL`MaJ^I]K_I`LbLcNcKaMaNaM]L`L`K_O_N_M^K\M^N\I[L]M^N^K\L]K[K\L]M]N^N^N`N_M_J^J^LaK_K_J^I]H\FYE\H^I\FZH^H[N]L]L^K]GZJ\K]J]J[JZKZIYI[GYGYI[GYGYI[HWGWGXGXGXGXLXJVKWH[HXI\HZBXHYFXGYFXEWFXGXFYFYGYFYEWFXGXDYD[HYHZHZJ\I[J\J\J\L]L]J\L^I[I[HZI[GYGXDZDZDYHYHYHYHYFWGXGWFXEWFWFYEYGZH[FWGYGYI[FXHYJ[HZL\F\G\F[G]H]J_J[K]L^J\L]L`J_I^K`IaIbNbOdOdMbNcOdSdTeTfSeSeUgUgVhTgQgSiQgRhReSgOgOeQgPgPgReRfOeQfPeQfQfQfPeOdQgRfTjVlSiUjWiVmWmYo[p]r]rcv`sgvfygzi|j|l|m~m~mnqsrruwtuvxyz}{z}}||}|ƃDŽȆɈЈΌӋԌғ֕ړُؕؐیՉՋ֌׌׍҈ЌьѐԏЏ͍͎҉у͈̉̄͆υˉ̉͋΋͊ӊωщъҎҏ֒ړחڛۦ᪵ᨵᩲ৲ߥᩴᨵܭڰޯޮ୸竹䩶㪶䪸橷橶姹䨷䨹䩺死篻讽㮺ޯ䱸᰼䭺ⱹⰶ᳹䯸⬺⯻㮿ᮽ孼嫺嬺謸歺箻櫹櫻嬸㭻⭺⭹⭺᮹㭹㮺᭸ⱽ⯺஼⮿߯3Ad6Dg4Cd6Ef5De3Bc6Ef5Ce6Ce6Ce7Eg7Gg8Fg:Gi8Eg8Eh9Eh8Eg9Ge7Ea:Gd>HfJd=I`?Kb@La?Ka@L^AM`@M`@N`BNcCNd@Mc@Kc?Kc?Kf?Ke>Kd;Id;Hc;JdGd;Ic;Ic:Hc;Ie;HcLf>LfKf>Lg@Ni>Lg>LgIe=Hd;Je`2?a3@_3@_1A`2A`2?`2@a0=_0=_2?a1>a2?`3@`2?`3@_1>_3?`5Ab0@a2@a0@a0?`2Ac3?a3@b2>_1@a0@b0=a/>`0>`0>b0>a.<_-;^,:\.<`-;a.;a-u0@v0@v/@v/@v0Av/@s/At3Cw2Bv2Bv1Au3Cw4Ew1Cw2Cw2Cw1Bx0Aw2Cy1Cz0Dy/Cx/Cx1Ez1Ez3G|1Ez3G|3G|5I~5I~2F{3G|3G|4H}5I~4H}4H}4I~5J5I~8H~7H|;K5J5H~3G}2H}1G|2G{2G|4I}3Gz3Gz8L9J~8I}:K8I}9H|;K:K~;L=O;M=N@O>ONQ?P?P;LS@U@T?S?S>S?T@T@W@V?U>T=S=S@VAWAWAVBVEWFXEXCWGVEUFVFVFVGWGWFWEVCVEWDVDVDTDUEVEVDUDUEWFXGYI[FXCWDXGYGYHZHZHZGYGYGYGYHZI[DXDXE[DXCWBVCWCWCWBWAVDXFZDXEYEYEXIXGXGYH[HXGYHYHYIZIZGXHYIXGXGWGWHYGXJXIYJZL\K\N\O]O]N\P]M_N^M`N`McNdL^O^P`RcO`PaScPcQcNaQ`QaN^RbRbRbVcUcTaUcRcSbQcQbRcRdWeWeXfXfYgXfTgWgYeWeWfWeTeTeUfUeSgThVfVfVfUfVgVfVhXeWeQaO_QaN_M_N`K]N_N`N_N_L`J^I]H\I]I]I]EYG[FYBWBXEZG[F[FZFZDXFZDXEXAVAU?VAT>V?V=T@W@T?T?U>V?V@X@U@U>S@U?X>S?TAV@U>S?T@U@TAYAXDXBWBWDYBWCXEZCXBWEZDYDYFZFZH\GZH[EZEYEYEXHXCVEYH[FXCWDWDWDWDWFXEZDXEYCXAV?XBVCXCXAV?TAVAVAV@U?T?TAT?SFVDUDUHYFWIYDZG[F[JZHYJ]K]I[M_L^N`K]K]L^M_K]K]IZJ[K\J[J[J[J[J[M^M^N`N`OaOaO`LaK^OaOaPbJ_I^J^K`J^O_PbO_LaL`K^G^LbL_K_K_K`J^K`J^K_L_K_I^J^K_K_I]J^J^MaI\LcLbJ`LbL_K_J`O_KaJ^I^M^L]L]K\IZK\K\K\IZK\J[K\M^L]L]J[M^M^M^M^P`J_J_L`H]H]MaI^I^J^G[GZH\I]L\J[IZJ\FXGYHZHZGYGYHZIYIYIXGYK]J\J\J\K]GZFVGXJWKWJVEXKWKXJWIUGXIXHZI[FXFXGYFXEWEWEWGYEWEXFXEWDVGYHYFWHZGYHZL^J\J\K]J\L^L^J]K]K]HZJ]GYI[HZE[F[E[FWHYFWHYFVEVGYDVFXEWEWEWDVGYHZI[I[FXHZJ\HYHYF\H]H]H]G\G\G]IZK]I\J\K^M^H]J_J_K_K`NcNdNdMcNdNdSdSeSeUgTfVhTfShSgQhQgSiRiSgThSgRiOfQeQfUdRgRgQfPeOdOdQfRgQfRgTjVlSiUkWmUjXnWmXo\q\q\rcvdwdxgzgzi|i|j~m}l}porstsuvtxux{x{~{}}}}ȁƅɉ̋ΌӎԐגؕݜܙ۞☪ۚޒڌӌӍԎՌӏ׍яӑՕ֗ՔԎҎԋόψυ͇̅ˇˈ͈͈͈Њ͉ЋЌӎҐՎԒה֙٦ߨ⩶⥲ޣۡݤܪ۲ݯۯݬެષ䩶⪷䩷秵䨶究㦹䤶⧹䪶䫸䬹ⰻᮺ㲺䰾毺䲹䮸௹ⰷ᰽嬻ⰾ㫹ᬼ孼髸䬹嬹殻謹嫸䭻㬻⯺㲽毻㮺ᬻ⮹⮺㬸ޱ㯼ⰿ䰾2@c3Ac5Ee6Ef4Cd7Fg6Ef5Ce6Ce7Gh8Gh8Gh6Ff8Gh7Eg9Fg6Ef:Ii7Fe9Ge:Gd9Gc:GbJb?Ka?J`?Ka?KbAMdBNg@Ld@Le=Kf=KeKf=Kf>Lg=Kf;IdHd@Jf>Jf;Jd_5Be5Bd3@c3@b1>`3@a2?_2?_1@_1@_0=]2?a/<_1>`1>`1>`1>]2@]4B^2A^4B`2A^1@]2@_1>_2?_1?\2?]0=]2?_2>a2@a0@a1@a0>`.=]/>`-;^/=`.<`/>^/>_-;_.<^.<_.LM>O=M@P@P>O

Q=O=P>P>R;P:M;O:NQAS>P=O?Q?Q>P;LQ>S?T@U@T@UAUCSCT@T>T?UBX@V?U=S>T?UAUDWFWDVFXEWCTHXEUDTHXGWFVGWFWFXFXEWGYEWCUEVEVDUEVDUDUGYGYCUDWFZAUGYFXGYGYFXFXHZHZGYFXHZEYAU@VCV@TBV@TCVBVAUCWBVCWEYDXBVDXEYGVEVGYGWHXGWHXGWGWHXHYCYEYEXFZEVGXI\GYIXJZKZO\N\O]O]P]Q`M]N^N`N_K`RcN`SdPaPaQbP`PaQbQbQbO_RbQbRcQaTbUcUbVeRbTdUfTeTeWgYgYgXfYgWeWeUgUgYfWfWeXfTfVgUfTeTeVgVhWhZgYfZh[hZhXfWdScO`OaK_J^H\I]I]J]I]K_I]I]J^J^H\G[G[FZAWDYDYBWBWBWCVEWCYCWEYDVCX@U?W?V>U?V=T=T@W@W@U?T>U=U>S=R@U@UAV>S>S=R=R>SS@U@U@W@WAY@U@U?TAVCXCXCXAVDYDYDYFZFZFZEZDYDZCXEVDWCVFXDVFYDWBUBUCUBUCVCVCWCXATAV@U?V?SAV@U>S?T?T?T@U@U@U?UAS@S@SEUDUEVEVIYHWEZG[HYIZHZI[N`K]K]J\J\J\HZJ\K]J\K[J[IZK\J[J[J[J[L]K\M_L^N`N`OaLaK_O`L^N`N`M^N^QaN_I]J_J^H\K_J^K^G[I]H\J_N^L]M^L\LaK_J_I^J^J^I]I]H\I]J^K^I_K`KaKaMaH]O_L]M^N^O_N_K\L]J[HYIZIZIZIZIZJ[J[HYJ\K]K]N`N`M_K]N`M^K\J[GYIZJ\L^J\G\H\EZG[F\J[I\JYH[HZGYI[FXGYHZJ\FXJ\GYK]J\L^L^I[H[JZHXJVKXIVJWJWIXIWJVKXJVGWFYFXHZEWEWFXGYFXGYGYHVGWIXEYFXEWFXGYGYI[J\J\J\J\L^L^K]L^K]J\L^J[K\IXJ[GZHYF]H^GXIZFWEWGYEWEWDVFXGYGXFYGYDVFXHZFXJ\J\J[H^I_I^F[G\I^I^J`L]I\I[P_K]K]J\K]M^KaLaLbMcRbRdRdRdRcSeRdSeUgTfTfViVkUiSkSiSiSiUiUiRfReQeTcSdUeReTkSgUiVjThTiUiThUjUkTjWmXnYoYoZpZp\r\o_u^sctcvfyfygzk~k~i}m}pmqqrsttuwtyzyz|y}|ĄLjɇˉ͉ΐՑ֒הؔטۚܙۘݚߙܘړٕٖٔۖؗٗה؛ڠߞܒ֏ӌ͍ˌΌΊΉϊ҉щ͈͉͉ΈϊяяђԓԒԔәٝۧᥴᤰݣޥئڭڰڴٯܰݫશ䪷㨵㦵⧵⩷䩶㦳ॹ㪷穷檷㬹䭹ᬸ಼孻߮௻ᱸస߯᰻᯽⭼ૹޭ⬸㮻笺孹䯻䬸嫸᭹⭹఼߯⭹ޯ嬼఼߭ⰻᮻ୺௼Ȿ4Be5Ce4Cd5De3Bc5De5Df7Fh7Fg7Fg6Ef8Gh9Gj7Fg;Jk5De6Dg8Gh6Ef8Gf7Fc7Fc9Ha;Hd9Ie;Ic;IeLf>Lh=Kh=Lg;Kh;Ig`4Aa0=^4Ab1>^1A`0>_0<]2?`1>]2?_/<_2>_4B]3A^2@[2@[4B\6D_3A^3@]3A]5B^2@]2?\0>[1=_1>a3@b/>`-<\/?^.=_/>_,;]0=a,;]-=].=^-;^-;^.<`.w0>v0@w1At1Au1Au/?s0@t1@t1Au1Av0>v0?u0>v0?v2Bx/@v/@v1Cz/Cx/Cx.Bw/Cx1Dy/Dy0Dy3G|1Ez2F{2F{2F|0Dz3G}3G}3F{7G~6G~9J8I}6I{5J}5I|3Gz5I|3G{3H{4Hz2Gz4H{3Gz4H{5Fz6G{6G{7H|8I~:I;J;J>M>J:L~;L}@P>N@PAQ@O=P>O@Q9N:N;O;O=Q=QCU=OBT>P=O@R>P=P=O;M~;M:OR;P=R?T>R@TCTBSBT?W@VAW?U?V?V=S@VBVBWDXEVFXFXFXEWEUEUGWGWGWFVHXIXEXDVEWFXFXEWEUCTDUEUGXDWDVFXGYFYCWBUGYGYFXFXGYHZHZGYGYHZFX?TAUAWBVAVAVAVAXCYAUBVAUAUAUAUEYCWDXBVBVAU@UBVBWCXCWDYCWCWCWDXDXDXGWHYHZI[GYIYIYIZN\M[O]O]O]L]L\L^M_OaN`OaN_PaPaRdRdRbPbOaQcQ`TaVcUbUcUcXfXfXeWdUgSdUfTeWhYfXfYgVdWeYhUhXeWeXfWeYfTfVgUfSdVfWhUgZfXfXgYh\jXfXfTeUdNaK]I^G[H\G[FZI]G[I]J^I]J^I]FZFYG[DYCXCXBZAYAU@U@UBWAVAVBWAVBW@X>U?V=T?VUS=R;T>US>S>SSAVAVAVCXCWCXFZDYCXAVBWCXBWBTCVCXCXCYAV@VAV@UBWATBTBUBT@UAV?T@U@T?T>S=R>S=RBW>S?T?T?TAS@S@SBVBREVEVGXCXEYEZIXJZIYGWIYJ]L^J\N`K]I[J\L_IZIZJ[IZJ[J[IZIZIZL]M`L^M_N`L^M_N`M_L^L^M_L\L\MaM^M_I_I]H\H]K`H\I]J^J^H]I_L]M_L_L^N`K]JZM\I^H\I]I]H\J^I]K_H]G^J`H_J]K_N^M^M]L]N_L]L]M]HXIZHYHYGXHYHYHYIZGXI[I[M_K]K]K]J\L^HZHZI[J\GYHZI[K\K]J\IZL]K]HZHWIZHZHZFXGYI[HZHZI[HZI[K]I[J\I[I[K]K[JZFWKWHUKXHUJWHWHVLZIWKYHYGWFWEWFXCUDVGYDVGYGYJYGWEUHWFVHXGWFVIZHZI[J\J\I[J\K]K]J\I[J\M]J[J[I[J[HZH[IZGXJ]K[HYFXFXFXGYEWEWFXFYHXEWFXFXI[I[K]L^H_K`J_LaLaH]K`J`M]N`M_L]M]J[J\M_K]L^M^PaO`PbPbQcQdQcQcRdRdVhTfUgUgVgUkUjTgVmTkXjWkVjSgVgViXhXhXhXlWmWmXnYoYoYpXnXnWnWmZpZpYo\rZp[q[q[r]p^s`uctdvdxh{j}lj}lmpqprutvtsx{xzzz}~Äćňˊ͍̋БГӔԘד֓՚ٛۚכݝޠݛ۝ݜݡࣵᠯ⚫ݚۛנ۝ڗԔѓΕ͐ϐЎЎҊχ͈̊΍ьэϐёҖҕӕӕӖӘ֞ؠܢޠۦ੶ڭܴܷܲݱޱ୸ᬹ⨵⫸嬹䨷妴᩷姵⨶㫹ꪹ謹嫹䭷ݬ᯹⮼౽㮺௻⮺୹߯ᮾ᰻᯻᯺㬼⮻歷⮺㬹ᱹ䱹⬹௺ᮺ஺௻ᱼ㰾㮽⯺᰼ⴺⴽ䳼㲺5De6Ee4Cd6Ef4Cd5De7Eh7Eh6Ef7Fg8Gh9Gj7Eh7Ei:Hk7Fh8Fi8Gh7Fh8Ge7Fc8Gc8Fd;Ig9He;If:Hd:Gb>Hd>Id>Jc=Ic=Gd@Jf9E`;JdLjLe>Kb@La?KaAMb@Lc=IaAMg[0>Z0>\1>_2?`2?_2?`0=]2@\1?^4B_3A^2@_3A^3A_1@^0?]2@^2B_0?]1?`.=]-=].=[.=Z.=Y/<\/=\.=].=_-=^.<_-<_-;_.<_-;_-;a,:`,:^-:]+9],;_,<_*;_)9^):^*:^+9^(6[*8]+9^)8\)7\*8]*8\*8\'5Z)7[)7](6^(7]*6^'4]$4['7`$3\#2[$3\%4]%4]'6^"1Z&5^#2^%3_$3_%4_%4_"2\$3^$3^#2]$2^%2`$2_&4a!2^#0^#1^#2`"4`$5a!2^"2^!2_"3`!2]!2_"4`0\!3_%6c$5b!3_!2^$5a%5b"2_#4`"4_#4_&5`&5a%3`&4a%3`$2_$2_$2a$2a$2b#1a#1a&4c%2a#0`%4c"3a"3a"3a!2` 1_0^1^"3` 1\0^"2b"1c!/`!0a#1a!/`#2c-_"0b"0a"0b!/a"0a!/a$2b!/^ .]"0_ -\ ,\ ,\ ,_".` ,^ ,\ ,\!-\ ,\!,\#/_!/^ .\$2a -^ /a -b -a,` -a!.b -a".c .a .^ /_!/a"0a 1b 0b0d2d 2d1c 2c1c/b/b"/c -a -a"/c"/c/c.a/c0d 1e.b!1c"1e"/c!-a#/d%2f$1e#0d#0d#0d"3f 0c 1e#4h"3g#3f$4g"2f"2f%5i%5h%5i(8l%5i#2h"1i#4k$5k%6l#4k$5k#4j#4j$5j&7j'8l'8l'8l):n%8l%9l%9m&:o';q(;t);t(;s)t->u.?u/>t.>r0?s/?s.>r0@t.?r,u1@w.>t/@v/@v/Av.Cx.Bw/Cx.Bw2Ez/Ez/Dy2F{2F{2F{2Fy1Ex3Gz3Gy3Gy3Gz2Fy7Gz9I}7G~8I6G{4H{3Gz2Fy3Gz3Gz3Gz4H{3Gz4H{4Gy7G{6G{7H~9J:I:H8G~N@O?O>P?Q@R?Q>O=N9N;PP=O@R@R=O>O=O>Q8L~:N8L:N8L7K~7K~7K~6J~9M8L5I:N7K8L7K8K:K8M7L9M:N8L8L9M8L9M:N:N;O8L7K~:N7K~7K7K8L:N9M:N8L7K9J;L;L9J:J9J:K;L9M9M9M9M9M8K8K7J8K4J4I2G~0G2G3H2H3H4J3H2H~0F|1G~1F}1F}2G~3H0E|1F}3H2G~0E|2G~2G~2G0F1G1D1F1E1F1G2G3F4G~5G4G4J6K6I3I5J3H5J5I4I~4J8M7L8M:O:O;P;P:O:O:O;P9NTAX>U>T?UAV@SCWAUFWEWFXCUEWFVETFUGWHXHXHXEUEWFXDVDVCUFYFWHYDVHZFXGYFXGYDVFX@UBVGYGYFXFXI[HZGYHZI[HZDV@T@UAWAUAT?U?T?T?SATAUAUBVCWCWDXDXBVAUBVCWCVAUCWCWCWCVDYCWDXCWFZDWFVEWFZHZHZGYJZJZN[N]N\O]O]K\M^NaL^N`N`K]O`PaRcTaTaQdTaS`SaS`UcUbVcVcVdZhVdYgUeVgUfUfVgVgXfYgXfXfYgYgYgXfWeWeYgVhWhVhXfRcWhUfWhXeYgXfYeZhUcTeVfObL^I[EYFZG[FZFZFZG[G[H\I]EYH\I]F\CWDYCW@XCZ@WAXBUAV@UBWAVAVBW@U?V@U?T=U=T@W?V=T:R>R>SSS=R=RS?T=U>U>V?S?T=R@U@T?TAV@UAUCX@VAUCUBXDYCX@TAV@VCVBUBXCXBWAV@U?T?T?UAS@U>S?U@U>S@U>S?T=R?T?T>SSLcBMcCOeDNdBMb@Kb@Lb>Kc?Ke>Ke>Ke=IcZ1@Z0@Z0@Z0@Z0@Z.?X1A\.>Z/>Z2@[1>Z-;W/>X/=Z.r-=q->r0@u->r.?s/?s/>s/=u/>u.=t,;r.=t/>u/>u.>u-?t/@v->t/Aw,@u.Bw.Bw,Av/Dy0Ez.Cx/Dy1Ex0Dw1Fy2Ey2Fy3Gz1Ex4H{2Fy4H|4G}5G|3Gz3Gz3Gz2Fy3Gz2Fy1Ev3Gy5I|2Fy1Dw4Ez6G}6G}6G}8I9J7F}8G};H;HM@R=O?Q?RPARAS?P>P9N:N:N9M;O8L8L8L8L6J}8L9M6J}8L7K~9M8L8L7L6K6K6K8L8L8L~8L~:J:L8L8L9M9M9M8M6K~6K8M9M8L8L:O7L7L9MP>Q=P=P?TAU@TAV@SBU@V@V?U>V>U=T=T@V?UBUCVGXEWHZGXCUDWEXFXEWEWDWEWFXDVFXDVGYEWEWGYDVFXGYHZFXGYGYFXFX?TBVDVFXFXFXHZFXHZI[GYEWCUBV@U?V@T>S@U?T>S>S?TAXBWATAUAUAUCWCWCW@TAUAUBVCWDXDXDXDXCWAUCWBVBVEUGXEZFZGXGZIXIYK\N[O]O]O]M]N^M`M_LaN`N`O_N^S`R`S`SaTbR`TbUbTcSeTcSdVcVdUcRdTeUfUfTgXiVgWdWeXfWeWeXfXfXfXfXfVhVgWhVhYeWjWhVgTeXeWeXeXfTcP`M`L_N_IZHXEYFZFZFZFZFYG[G[G[G[H\H[F^F[F[CWAYBYBY@W@W?TAV@UBWBWBWAVCX?W@W>U?V=T>U>U=TR=RS=TS>S>S@U?TAV?T>U>U?T@UCX?UAV@U?T?WAU?T@VBT@VAU@U@U@U@U@U@V@R?U@UAU?T?T@T>T?T=R=RSJ`?J_DOeEPfCOcCOcCOcBMbALb=Jc?Kc>Ha=Ha;H`=IaY/=X0>Y/=Y.=[0>\/<[.=Z.:Z-:Z,9Z.;[-;[-:\-;],<]-<\+:[*9Z)8Z)6Z,:]-:^*7[+7[(6Y)7[)6Z+8Z*8Z)7Z)7Z(6Z'6['6]'5[(6Z'5Z'5]%4[#3Y%3Z%4\&5\%3\%6\$7\$6\"4]#3\$2[#2\!0["1\#2]$3^$3_"1]$2] 1]"1\!0[$3^#2]%4_%3^$4_ 1]$1^"1\#1]$3_"1]$3_%5_!0\#1^$2_&4a%3a'5b%3`%3`"1]"3^"1\#1]%3`%2^#0\#0\%2^"/["/[!.["/\"0]"/\#0] .[.[ -] .^ 0^1_/\/[ 1]!2a!2` 1_ 1`/\1\ 1^!2^!2^"3_"3_#3` 2a 0_1^ 0^"/_"0_ .]"/^ ,\ ,\!,\ .^!-]!-]!-]!-] ,\!,] ,[ -\!-^"-^!,^%1c"-_ ,^"._ -` -a!.b -b!/` ._ ._"0`!/]!/_!/_#1_#2c!1a 0a2d1c1c!2d 0c"/c#0d#0d -a$1e#0d!/c/b!2d 1d 0d$0d$1f!1d"1d!1d"2e#2e&3g%2f$1e#3g$5h$7h"3f#3g"3f%4g'4h%5h&6j'7j&6j'7j$4g"2f$4h%5i%5i$5i$5i&7k%5i$4h#4h%6j'8l'8l(9m'8l);o';n(q*=n,>p0Bt-?q,>o-?r.>s.?t->t-;s-t->t.@u-Av,@u,@u-Bw.Cx0Ez-Bw/Cx1Ex3Gz2Fy2Gz3I{3Gz2Ex2Fy1Ex3G{2F{2F{3Gz4H{3Gz2Fy3Gz3Gz4Gy4H{4H{3Gz2Fy6G|5F}6G}7H~7H~7G}5G}9F7C|9G};H;L>O>N>O;N~=O;O=O=NO;M:OPAR@S>S>S=Q=P;O:NP>SAV@SBV?SAU@VAWAW?V?V?U>U?W@VBYDWBVFWEVGYDXDVDVEWEXEWGYEWEWDVEWDVDVDVFXEWGYGYDVHZDVFXGYEWFYBVAUFXBTFXFXGYFXFXFXJ\HZDWAU@T@V>S?T>S>S?S?T=RAU?VAWCWAUAUCWBVAUBV@T@TAUBV@TBVBVBVAUCWBVCWHZFYEWAVEYFZJ\FYIYK[N\O]P]L]L\L^M^J`LaN_L_O^O^SaR`SaTbR`SaTbQcRcTeSdSbUbTfUfRcUfSeTfTfWhVgWeYgXfWeXfYgZhYgYfVhXiVgUfWhVgVgTeTeScRbVcUdSdP`N_M^K_H\FWFXE[CXF[CXDYDYEZEZH\J^FZFZE[EZDZCWBWC[BY@WAX?TBWAVAVBWAVAV@T@UAX@W@U=U=T=T=U>R>S=Q:Q;P=RS?TS;PS>U>U?T=R=T=T>U@U?T@UAU@W@X?W>U?T?T=S?T@U?T>S@UAV>S>S@U?T>SS@R>Q=P=P>QATAUAVF[CXBXDWDXDXBVEYFVFWGZK]J\J\L^L^J\HYHYJ[K[HXJZJZIZJ[OZM[M]K]K]I[K]J\L^L\L\K[JZL\H[FXFYM_I[I[J\K\J\GYK]K]K]K]K]HZI[K]L\FVIZK\GYEZH\G[FZG[FZFZG[H\FZJ[HZI[IYIXJYI\HZHZHZEWDXEYEYDYGWFWEVHYHXKZHZHZJ\GYEYEZGWFWHYGXGYIZHYL]L^OaOaK[M^L^HZHZGYHZGYFXHZGYJ\N`K_J_G[K\L^I[I[GYHXHXGWHVIWIWHVIWIWJXKYJXJXGUHYIZDXAXEWDVFXEWHYFWFYFXGYIZHYIZIZHYHYK\J[K\FYI[K]K]L\L\K\I[J\J[L\F]K^I]J]L^J\M]G^G[J[J[JZFXGYFYH[H[K\K\K\K[J\M_OaOaPbM_N`N`O`OaO`QbLaMcPaL^L_M_N`OaO_N`OaM_N`RdQdUdXgWgWfWjVhVhTfVhTfSiTiWlVkWlWlXmWjYm[m[k\m[kYnZnXlYpYo[qXnXnZpYoYoYo\r\r\s]t\t_v`u\r_uatdzbwfwgyg{j}i|lmnosttvw|zz}}~~ČʏΏ̒ϕԘՙךڜܜݡ࠱ݢޟݢࡳޥ⥳⨴ᦸ㦷䩷⧵਺槷⥶䥵禴墰ݡި㦱⢰ޝڢ٤եԟЙʘʚ˙ΛҚԙҘјӛԜҢ֦ۭ֬ܭڮڨ٧רح۬ګۨרת٫ݨݨᨶ⧴ષ᪶੷ީܫݬ଺ᬺૹ⫷䨷ޮ૷ުܲ尹߯ᮺ౽㯻᰼⯻᰷߲㯼᰽䰽߯౾㰽ఽఽ߰߰ఽߵ㳽హ᯸ߵⴽⴾൽⷼ߷1@_3Ba4Cb4Cb2A`2Aa5Df6De6Cd7Fg6Ef7Dh5Cg6Dg8Dh:Fj8Fe8Hg8Gf6Ed7Fe7Fe7Ff7Fg;EeGd;He:If;Ie8Gd8Gd8Gg9Hg9He;JgJa=J`:G]>J_=I`;G_;GbY0>Z-r-p,>p->r,=r+;r+;q/u,P;O@R>P?Q;O?SO?O@PBR>RAU@S?S@W@V?V=U?V>U>V@WAW@VBUAUCWAVAVCVEWEVEWEVFXEWEWDUEVFXDVEWEWEWFXFXCTHYDVFWGYFXEWDV@VBVEVDVFXEWHZHZGYFXGXHYEXBUBW>S?T?T>R>TR>S?T>S?S?TAU@TBV@TBVAU@TAU?SCWCWCWAUAU@TCXCXCYGXDVHYCXCWG\GYHZJZK[NZM[O]K\O_J]M_J`I^N_N`M`N^R_SaP^R`S`UcSbSeTeRcRcRaSeSdRdUgSeSeTfTfUdTdWdXfXfXfZiXf[eXgXfUgVhWiVhUfXiUfUfP`P`M]P]R_O_N`L_I_I\EYGXEXDXDYBWDYDYDYCYEZFXFZGZCXBWDYCXBWBW@YAX?VAXBWDX@UCXAVBXDYCXCYAZAYAVAV>V=V@T=R>SV=T?V>U?S>SAUAU?W?V?T>T@T>R=Q>S@U?T>S?T>S@U=R?T?T?TU>S>R@S=S>S?S?T=RT?PP>Q>P>SAV@UAVBWAWCVDXEYDYHXFYGZIZJ]K]I[HZHZJ\GZI[IYJZIYHYMXLYKWN[K[I\I[I[J\HZHZIXHXIYKZJZIXHXIXJZIZFWHZHZJ\HZI[J\J\I[J\HZHZK[KYJZHYHXIYIYG\G[EYEZF[CXG\EYEYHZGYGZHWIYGWHZHZGYHZGYCXDXBVDYFVCUFWFWIXGVFYFXFXFXE[EZFVHXGWGXJZIYL\K\L^K^O^K`I^L]I[H[HYGYFXFXHZJ\J\L^H^H\I^L\K]I[I[GYHXEVGZJWGUHVGUIXFWIVJWHUKXHUEXFVBWDYCTEWCUEXIYGXGYEWGYDXHYIZHZHZJ[IZJ[J[M^H[K]J]K\L]K\HZI\I`I`I_M^J]J]K\L]K\G^H]JZHYIZK\EYFZH\H\JZK[J[J[M^NaOaMaN\M`M_M_OaN`N`PaLaNdO`OaQcQcPbM`N`OaN`OaPbRcQbUfVeVfXhXkXjWiUfVhUgQgTiXmWlXnYnXoWkWl]mYi[kUjWkSjXkWnWkXlXnXnXnZpWm[pXn[oXp\s^w^v`u`v]sbv_taxevewizi|k~j}lroprvuyz|||}}ĒŔǖ˘ЙӘՖԙ֚؝۟ݡ⢲ޠݢݦ঴ᤱޤࢯߠޡޢި৴᧴᣶㢴ឯߢߙݜڠޠޙזәѝСѦӣΨӥԤԡԜњ֛֚Ҟ՝ҠҢӧүӱڮ۩֯ܫ٬׭֩ܩک٦ڧݧ৶⨶娴᩶ߩިܫު۩ީ۪کܪ߫୷ݬܪܱ㰹౹୻఼⯻᯼ⲹᱹ᰷߯㯼ްாޮ޲ᱽ߯஽߱᱾ᵽ౻೼㴾㲼㲽಼ߵ޸඼3Cd2Bc3Db3Ca3Ba3Ba4Ba3Bb5De5De7Fg6Ef5Dd6Ef4Cd7Fg8Hh8Ge7Fd8Gf6Ed5Dc6Ed9Hg6Dd7Gf:Hg7Ff8Ge8Gd8Gd7Fc8Gd9He:If:Ie8Ge:If9He:Ie9Jg;Lh9Jg9Jf9Jf;Jg;If:Hf9Hg9He8Gc:Ke9Gb9Gb9Hc:Hd8Ge;Jg8Gd:Ig:Ii8Gf:Ii8GdHc@L`AI]@I]@I]@H\>GZ?I\>I[=J`;Kb;Ia?Kc=JbZ1>Z3>Z/Y,;X-;W-:V.7U/:X.;Z+9Y+7X+8Y*8X,;[*9[)7[)8Z*8[*9[)9[)9[)6Y'5X&6Y&5W&8Y$8Z#6Z$5Z&4\&4\$3\$2["1["0Z#0W#1X#0W"0X#0\#1Z%4]#2["1Z"1[#2]"/[ .\!/\"0]"1\!0[!0["1\.Y!/["0] -Z!1["1Z"1Z"1Z%4]%4^$3^!/[!0[!0[ 0[ /Z.Y!/Z -Y,X"/[!.Z!.Z!.Z,X -Y,Y -X-X-Z -Z,Y+X!.[ /[.Z0[/Z/Z/[0[/[/[0\/[!.[!0]0\"3_!1]!.\"/\!.[!.[".\!.^!-]!-]".^#-^!-]0^!.] ,\!-]!,] -\-]0]!/^!/^ .\!.^".`".`!-_".` ._ .a -b ,a-`!/` ._!/^"0_ .^#1`$1a#1b$2c&3d"1b /b!1d"2e$5g 0c!2e$4g%4g&2f%2f&3g&2g#1e"3f"5g$4g$4g$3f&2e'5f$4f#4g&6j)8j&8g)9m*:m&6i)9l%5h(8k'7i'7g'8j'9k'9k(8k(8k(7g%6i&7k%6j&6i'7k%5i&7k)9m'8l'8l%6j'8l'9m%:m&:m%9l(;o(9m):n*p+=o,?p,>p,p+=o/As,=p+s*t,=s/@v,=s0@v/Bw,At-Du.Du0Gx0Ew0Cv2Du5Fx3Dv0Dv0Dx0Ey2E|0Ey0Dz2F{1Dy5Ey3Hz1Dx2Gx2I{3I{.Fx2Gz2Fy4G{3D{4F|5G}8G6H7I8I7I8G}6F}8G}:E}=J=J;JNN@R@R>R;RT@S?U>T?U>U@V@T?S@VAWAW@V@VAUAUDT?UCWBVAVEWDVEWEWBXEVCUFXEWFWDWDWAUAV@TATDXDVGYBTHYFXDUGYEWFXFXGYFXEXFZDYDXBYAV@S=R=R>S>S=R=R=R>S>S@UAU?S@T@TBVCWAUAU@TAU@TBVCWAUAUCWFVFXFWFXFWFXE[BXEXGXGZHZHZKZIYKZQ^O\N]L]M^L^N`NaO^O_SaQ_SaR`PaQbO_QcRaQbQaSdSdTeWdXfWfVfTeTeTeUeTdUfTdXfZhYdXfXfZgYgYgYfXfWhVgVgQdQcJ]J\K^K]J]L^K]FZDXFZBWCXAYAXBWCXBWBWBWDYDXCXBXCXCXCXBWCXCXCWBW@U>V@XAYAXDWBVDWCXCXCWCXCWAV@U@T@U>S=R>S=R=R?T=RU?V?V?V@W>U>T>V?W?T=R?T>S=R=RS?T@U>S?T>SSS@UAVBWBWDXCWBVCWDUFWHZGYJ[J[I[I[HZFXEVFVGXGXHWIYHXIZIYHYL\JZKYI\I[FYHZFYHWGYFWDVGYGXCTGXG[AVCWBVIZJYIYJZIZI[HZI[DZDZF[EXEZEZDXG[DXEYEYEYGWFVHYGYH[FXFXHWGYGZFXHZHZHZI[FXFZBVCXGVEVDUEVDUFXGYEWEWEWGYEVGWFYEWHZHZHZI[L^I[M[K]I^J^I]G\K\JXFYH[HXIXH[J\K\K]G[I]I]EZJ\I[GYFXGXHYGSFTIWFTGVGTFXFWEWGWHZEWHYEVEVCTCUCUBTFXEWGWEYDXDVFUFVHZKWKXI[IZJ[K\J[J[K]KYL]K\K\L]K]L^K]K]K]L^M^I^I^H^K\J[JZJZHYHYIYH[J\HZK[J^J\I\J\OaOaL^L^M_N`L^M_PbOaPaKcO`QcQcQcPbRaRbOaPbN`PbO`NdNdRgOeVkWjYiXjYjUjUjVgQgVkVkWl[lXj[l^oZl[lXlYmVjUiUhUlUkTjTlUlVlWmXnVmUlWnXoZq[r]t]u`x_w]s`w_ueuewfvc{ezk}j}nm~rrtxvxy|z|дϤƝʝқ؞ܡآ۟٣ߡݡޠޜݞ࡭ݞܞݝޞޝݜ֛ۚۚ۞ܞޜܜݙڗڕْՓՑԐђїӞҦ֮ض״ҲҪ˨̣Ϥͨ˦̨ҧԦѪӫбز۵صصղײܯڮ۬ڪߨݧܧޥߩݨܩۧܩݨب٪٩ګީ߫સޫܫ߱䯸ޯ߰೻ⰸ௹ۭݮޮݯ߮߱߯ޱݭڰ⯾ۯݲݯ޴೽೽޵ᵽᵽᶼᶽ߷޷޶ڷ2Bb3Db3Db3Cb5Dc3Ba5Dc3Bb4Cd3Bc7Fg6Ef5De6Ef5De7Fg7Fg7Fg7Fg5Dc7Fe6Ed5Dc7Fe7Ge8Gf9Hg8Gf9Hg9Hd9Gd9Ge:If7Fc9He9He:If:If:If:If9Jf8Ie9Jf9Jf:Kg;Ig9He:If9Hf:If:He7Hb:Hc9Gb9Gb;Jc8Hc9Hf;Jg8He:Ig9Hh9Hh9If:Jc9Ib9JdFY@GZAI\AJ\@MbCPcCPcCMc@Lb=H_?Hb=H^@H\?G[>G[@J^>J^>H]?H]=DZ>F[9EX;FXHY>K[@L\AM^>L\=JZ?K\>J[=I[=HY>I[=J[>J\;H[;H[U2>W0=V0=V0p+=o-?q-q.>q->p-@r,?p/As):n-=q(9n'u+t,=s,=s.?u.?u/?u.Bw,Bt.Dv-Cv3Fz0Cv4Ew3Ew4Fx1Ew2Fx0Dw0Dw0Dz0Dz1E{2G{1Ex3Gz3Gy1Fy1Gy2Hz2Hz2Gy1Ex4H{4E{6F}6E|7F~7E8H7I7H7F~;I:I;J=INP?QR;Q=R>R@P@Q=R?S?R>R@U@V=S=STAWAWAWAW@WAW@SBVAV@TBWDWEVBTDVDVDVFWFXDVEWDVGYEVCTAVBV@TAVBVFWFWDVCWBWBVEVFXFYGYFXGYEWDXFZDXCYDXBV>S=R=RS?T?T?T=R=R@UBVAUAU?SAUBVAU@T@T@T@T@TAUCWBVDUFXFXEWFXGYCYFYBUFXFXHZGYIZHZKZM]OZO]M]L]J]L^M_M`P_P`R_S`Q_SaS`SaTbS`UcUcSbSeTeTbWeZgTfVgUgVfVfUgUeWfXfXeYeVeWfYgWeZhViWgVgVgVgUfNaJ\I[HZI[K\I[CXDXDXDW?UBW@X>V@UAVBWBWCXCXCXCWAV@UAVAVBW@U@U@U@U?T?T>S@T@WBVBVAUAV?TBWBWBWCXBWAV@U@U>S>SU9QUU>V=T=R@U@U=R>SS=RS;P?TS;P=RS>SBW@VCVCWAUEYEVGWFXEVFWIZHYH[FXIZFWDUEVGWK[IYHXHXHXGZIYIWI[K]GZIYGXGXGXCTCUFWEUFVEVGWDXDXDXDWIZH[HXJXGZHZHZHZGXGWGWCYFZG[EYEYG[CWCWEYIYEVI[GYFXHZCUGXDVFXFXGYGYFXFXHYBWDXEXDTCTDUDUGXGYFXDVEWFXDVGYGZEWEWFXFXGYK]GYHZK\J[I^G[G[GZI[FXI[EWEWI[I[HZJ[G^G\H\H\H[K]GYGYGYFVHZIUGUHWGSJUETEWEVEVGXGWFWHYFWEVBTDVBTDVCUDVDTEYDVEWFVHYIZGXHYJZK\J\J\J]L\L]L]L]J[K\L]I\K]M_M_N`N_JaI^J_H]K]J\K^K_JZIYG[J\K\IZK^L^L^L^K]N`N`L^N`L^N`OaN`PbPbPbPbPbPbQcRdSeSeSeQbO`QcO`KaOdOdRfRhUiXmWm\kVmWlUjZjSjRgVkWl\mZl\n[m[m\mXmWkWjVjWkUlTjTjVkRjVkVlUkSjTkVmWnWmZq[rZq`x^v_t_vcxcudvfxfygzi|i}m}m~ptuutwyvz|ѶغϺͼַթϡӡأۤݤئ঵ޥܠޠݜؗמלםכڛ֚ٙיט՘יؚٙڗܗ۔ّԒԐՎҐԎΏϒЛСѫղձѳйֹպռԷͱβӱׯԮҭӳٲڶٵֶضڵ޵۲ޮܫܩ۩ާڧܨܪީܪݪݨܨةڭܬޫᨷݩܫܬᨵష௸᯸ᱺ౺᭶ݭᮺ୺᭻ݮޮ߯ޯݱ߮߯ޮ߰ް౺߳ߴ޵ᴽܷⷻߵݷൻڸ2Db3Db3Db3Db4Cb2A`4Cb2A`7Fg5De3Bc5De4Cd6Ef6Ef7Fg6Ef8Gh6Eg7Fg7Ff4Ca4Cb7Fe9Gg9Gg8Ff:Hg9Gg8Gd7Id7Id8Jf7Gd8Gd7Fc8Gd;Ig:Jf7Hd8Ie9Jf8Ie9Jf9Jf:Jg:He9Hf;Ji8Gd;Jf8Ga:Gc9Gb:Hc>Lf;Id;IdJbCKaGOdIPdGM_HO`HOaHOaCL^CK]BJ\BI^BG^@H]CK`BJ]BI]DL^DN^DL]GN`CO_DOaDN_CLaBJ_AI_BG]BH^DKaHNcDJ]KQdDJ]AI\CI^AH^ELbBK^DL_DN^DL^CJ^FM`CJ\FN^FL_CL]FO]EM]CO]DP^DP^DP_EN_EN_FM`EN_GP`HQbKSfFOa?J\?J\CK^CM`BH]>I[>IZ8DV;FY8EX6BW5AU6BW6CU6BT5@S5AV5AV6AW5AY2?V.q.?r.>q-=p.?r-?r->r);n+@s*=q,=p,=q,=q,=q,=t-=t.>u->t.?u.?u->t->t.?u/@v0Av-Bt0Ew/Cv2Fy1Ew2Ew2Dv2Dv0Ew2Fy1Ex1Ex/Cu/Cw1Ex0Dw2Fy2Ex2Fy3Gz4H{2Fy2Fy2Fz3G}7G}4D{7E~9H:I7F}7G}7H}7H|7G{9J};K~9J}=L>J:I;JO@Q?P>Q=N@R?QR>Q=Q?O@Q?P?O?Q=R@T>R>T?U?U>V?U>TAW@VAWAW@V>TAU?S@UBTEWCUCUCUDVEWDWEWDVEWDVBVBVBUAVBVAUBVAU@TAVAVBVAUCWCXDWGWGYGXFXGZFZCWBVCVDX?T=R=R=R=R:O?T>S>S@U=S?TAT=QAU@TAU?S?SAU@T@TAUAU@T@TAUBVFWDVEW@VEXAVDXAUGVEWHZGYHZL]J\L[L]L\M]M]M]N`M_M_M_P_OaOaR_P^R`TbQ_R`SaTbSaPbRcTaVdTgSdUfTfVgVgVfUgUeWdXfZhZeWcWfZhYgVhWhWhVgVeTdP_I\J\I[I\FZEYDXCWAW@V?T@U?T@U?T>S@UAV?T>S>T@V?U@V@U@U?T=R@R>S@U>S=R>S@U?T@UAV@UBWBWAVAVBW@UAVAVBW@U=RS;P=R;P;Q=O;QS>S>S@T>S=RS=R>S>S>SS>SS>S>SS>S>SS;P?T@UATBVBVAVCSDUCTEVCTDUEVHYFWFWDUFWEVEVJZFVHXGWFVFVHWFYFYFVDUDUCTDUCTDUEUDTBWCVDXCWCWBVCWBVEVGXGWIXHWFXFXFYGWDUDUEUBWBVBVEYCWDXCWDXDYAUDYCXGWEVEWDVFXDVFXGYGYHZFXFVCYCVCXEUFWBSEVCTEWCUEWFXEWFXDVGYFWFWFWFXHZGYI[I[HZHZG]GYEYH]IZGYGYI[GYI[I[HYDZF[G\G]G]F]HYGYGYFXEWFXDUHVJUFRHTHUEVDVDUEVEVFWFWDUFVEYDWEXCVEXBVJYDWFWFXEWEWHXIYJZH]L^K\L]K]J\N^L\M^L]M^M^J\K]N`OaQbLaLaNcNcK`OaL^M_L_I]I\L_K\H^KaPcQaO`NaQcOaN`N`PbN`M_M_OaQcRdRdObQcQcRdVhVgVhUgQgNcLaMcOdNcPeShSiWnWkWlYnXmVkShSiTiVkVkXnXm\m[mZlZl[mUjWkXoVlUkVlVlUkSiSiTkUlWlVmUlUlTlVmWnYp]t[t_wawatdzewewewfxi{i{k|l~m~o~rrutwywz|~͹ѿۭܺҪզإؤۨݤۦߤܨᡳޠܟܠ١؜՝מ؝֜ٛיכי֜ۀٓ׏֑֍юҋЌΎ͐͑ΚϡңѩѱֿڸԱϳֲ״زصֶַٳط۳ۭܰ۬ۮݬܫ۪۪کܨܩݩ۫ުܮثڪݫݫܪ٬߬߭ᮻ㰷⮷హⱺ⯸௶ޯ߯߰఺ݳ௾౿ಾⱽ఼ޱⲿ岼ޱಽ഻ൿߵ෽߸ᵻ޸඼߹2C^3D_2C_3C`5Cc3Ba5De3Bd4Cd3Bc5De7Fg7Fg6Ef4Cd8Gh7Fg5De5De4Cd5De5Df5De7Fe7Ge7Hf6Ge6Hf7He6Gc6Gc6Gc8Ie6Gc7Hd7Fc:If8Gd:Jf8Ie6Gc7Hd9Jf8Ie8Ie9Ie8Gd7Fd;Jg4C`:Ie9Hb;Id;Id:HcLgLg;Id;Ie:Hf;If9Ge9Ig9He;If:He:HeJa>J_AK`FMdFM`EL]GN`IPaIPbEL_FL_DK^EL`FLbEJ`EL_EM_BL]EO_EN]FN]GN^GQ_FRaBPaBL^CI]EK^EI_FK`GL`IMaFM`JQcHNaJNaFL_HPbKSeKReJQcHOaKQdIQbJRbJQ^KQ_KRaKP_LS`JS`KS`HP]IT`HR^GN\IP_IO_LRaLSbKQaMTdKQcJQ`IP_PVeQXgLSeMSbLTaIRaBLYFN_CM\@LZq->p->q,=q-At-@t->r,=q->r->q.>r->q,>r+?r-=t/@v->t-N>N>NMP>P>P@R>P@R?QOBS@P?P@P>O>R?R>T?U=S>U>T>T?U>U?V?UBX@W@TAUDTBREWBTBTDVCUDUETCVEWEX@U@T@TBVBVAUBVBVAUBVAUBUBUAUBVBVBVDXFZDXGXH[AUCWBVBVDXAU>S?T=RS?T?T@U?T?T>S>S?TBV?T@U@T@U@U@U@U@T@U?TAUAU>RAVFXDXCXEYDXAUAVAUFVHYGYJ\H^J\J[K[L\O[P]O_L^M_N`L^K^P_O`OaR_Q_Q_R`Q_SaR`TbRcRcRcRcTeUfXiUfVgTeVgTeRcUcXfUdXcXcXgYgVhWhQbSdScO`Q`O`K]I[HYFYEZCWCZ@VBWAV?T>S>S?T=R@U?T>S?Q?R>QS=P=P@S@S=S>S>S@U@U?T?T>S?T?T>S@U@U@U@U>S?T>TR:R:R;Q=P=RS>S=R>S=R>RS=R=R=RSR=R;P;P;PS?SAT?S@T@TBV@TDWDTCSCTDTEVEVFVEUCSBSFVFXBTDUFTHWEUFVGXEWDUDTEVCTEVCTDUDTETAV@UBVCWBUBV@TAU@TBV?SFXFUEUGWFVGWEUCTCTDUCTFUBWBWCWBVDX@TBVCWCWDXBW>S@UCTDVBTCUEWBTDVCUFXDV@TAU@TCSBSBSBSCUCUFXDVFXFWEWFXDVCXBX@WGYFXFXHZHYGYGYF[H]G\F[HZJ\J\I[GYHZHZHZE\G[E[F\F\JZHZHZGYGYFXGUFVHVJUKWHSGUFVDUFWFWEVGXGWCUEXDWFYATCVCVDWDWFXH[GYGYFWFZH[H[K^N^G^LbM]OaPcL_M^N_O`K]M^OaOaRdMdShPeQfRgRgXjTfOdOeOaM`PbNeOdRfQgUkRhXiRdSeSeRdQcRdN`N`OaRdRdOaQaRdQcUgYkWmXmVkRgPeQfOdNcOdNeRgSiUkYnXmXmWlXmTiUjVkShXmYnYn\lZm\j\j\nXlYlXnWmVnYoXnXoUkUkUjVkVkVmWmWmWmTkTkVm[s\q`v_wdwczhxhzi{jwmzkxj}j{o~qrrtwuuyyz|~¯˴̱˳͵ҹٺܬץզ٥٧ڦܨܧݦݧޥܥۥۤݤܡܠڢݠۡܟڜחԓԏґ֏ՐՏԎЍό̎̑ϛӢңԢҥեбԲػϷӳԳԳҴӵӵշַڷܵ۱ٱڰ۲ݱܱޮ۬ܩ۪ګ߫߬ޱٯ۰ޭݪ٪ڪڧ۬ޫްް಼Ჽᱼ߯ݯஸ߱ᳺ᰺᲻Ⲻ⮽ޮ᭺᰽⳻ⱺⱼ߱ݶⲼ۲۸ݶݷ۶߸෽5Fc2C`3D`2Ba5Cb5Dc4Cd5De4Cd3Bc7Fg6Ef4Cd7Fg5De6Ef5De8Gh6Ef5De5De6Ef5De5Dc7Ge5Gd9Jh7Hf5Gc8Je6Gc5Fb6Gc8Ie6Fb;Jg9He9He:If9If9Jf8Je8Ie9Jf9Jf8Hd:If8Ge7Fd9He8Gd:Jd:Hc:Hc9GbLg=KfJd@Le@Lb@I_@H]FNbHObGNaKRfIPcLSfJQdGNaFNaBJ]DK^DK^EL^KRdHO_JQaHP\EM[HP]EP]GTcBP`AP_EL^HM_JMaGL_GMaFMaHNaIPcJPcLSeNTfLQcKQbLRcLTdOVfNTdPWfNUcNUcNSbSVdTXcSXePXdRXdR[fR[dNVbNVaOWaMR_PT`QS_QXdQWfUZiUZdZ_j[^j_co_co^cqY`mW]iZbmV^jS]gOVbLU_KU`OVcIQ_FM^EN`AI[>I[:FX;EW=DX9DY7CZ7B]2B[1?Y0>Z/=X/>Y/>Y,s*:n*:m);m+:m(8k)9l,r,=q/@t,=q.=q/>q+q.>r/?s2?s/p.>q-=p/As-@t*@r-As/At/@t.?s.?s-Bt*?s)?r*=q,=s.?u,u.?u/@v/@u1Au1Bv1Bv2Cw0Cw4Fx4Fx3Ew2Du5Fx3Fx-Bu/Cv0Dw.Bu0Ex2Hz2Hz1Gy2Fy4H{4H{3Fy5Gz3G}5E{5G|6Fz:J~9H|8Hz:K}:L~O?N>NO>O=NP?R?Q?P>O=N9M9M9M9M:N7L~4K}4J}3K5K|5K}5J|6J}7K~4H{5J}5I~7K}8K~6J}5I}7K5I~7L6J7K7K6J6J8L9M7L~7L7L6J|:I~8H}9J~7H~8I7H~8I:K9H9G~8G~9J6G8I~7H~7H~7H~8I9J8I9K7K7J8J8L7K9L8L8L~9M:N:M;K:K9J:K9K5K|7K6J8L8L:N8L~;O:P9O9P:Q8N:P8L9M:N9M:N;O:N9M:N:M:M7L8M8M8M4I~2G|4I~5J7L9N:P9O8L6K7L8M8M6K:O:O:O;P:O=Q>Q@Q@QCR@Q?P>OT=T?U?U@V>V>V>T@VAV@TAU@UATEVCUDVFXCUDUCTEUDUCT@T@T@UAV@TBV@TAUCWCW@T@UAVBVAUAUAUAUBVEWFVCWCWCWDXBVDXAU?T?T=RS?T=R@U@U?T?U@U?TBV@TDTCSARCSCSCSDUBRBR@U@TAUDTBWCWEY@TAUBVAUBUDSIYFYI[G[EZJ\I[KZL\N\N]K^K]L^L^K]O^N`M_N\P^O]P^R`SaR`R`PbPaQbRcRcRcRcTeUfSdTeQbSdRdVcVdVcVcYfWdWiTeRcP`O_N^M\I]GYIZGYAVAWBXBVAV@U?T?T?T>S?T?T>S>RQ>Q>R;N?R>T;PP=P>Q>QS@U?T=R?T?T>S=R?T?T@U@U?T?T>S=R@R?Q=PS=R>S>SSSS?Q?RRAU@S?TCW@TAVBV@UAVAU?T@U@TCWEUDUCSCTFWGVGWDTBTBSARCTCTBSEVCS?V?T?S?S>R@T@T?SAUAU?S@TBVBQDUGUEUHXEUFVFVCTCTCTDUDUDUAVCWAUCW@TCWBVAUAU@T@T>SAWDVFXEWEXDVCUBUCU>R@TAVBRBSCT@QDUBVAS@Q@U?RFVFYDWDX@WFWBTEWGYGYH[HZJ[D[G]G]H^IZGYGYHZGYI[GZI[IZD\J[I[HZGYI[HZEWFXHYG[HXJWIWJUJUIVFVFWDUEVHXEWFXEWFYFYEXEXDWDWDWGZFWHZK]I\L^K^H[NaObJ`OdNcNcPaN`PcQaNaPbSeTfRdVhXjWmWlWlUjWl[q^p_qYoWlTiRgRhVkXmTlXoXl[q`s\nZlWiSfRdRdRdRdQcPbQcOaQcTfQcXjYjZo\qXmWlShUjShPdMcPfRhTjYpXlXmXmYnYnWlUjUjXmWlYmYnbrYl_m[m[mXnZlVmWpVmVn[qXoYmYlWlWkYpVmWmUlWmVlUjVlYpYo]ubueygxgygyizjwl{k{l{po~pqqswuwvvyy{{ƦЯͱд޺ڭѩժ٨بצةܨܩܪݤ٧ޥܧॵܥߥިरܥߧߣۚ֘ӒҒӏЎѐЏϓёϘϙўӢРќОѡѢϪϮϷܾվҷѸϷԷջۺع۷ڷڹ۸ܵڲ״۴ڰڱۯܭڭܱ߯޳ױٲ߮ۯܰگݭ۪ۯۭ۱ݴ߳߳ޯ۰޳߰᳻ⴼⱺ᯸఺ݳ㱺ⱹᱺⲺ⯺ݲޱܵڳ׶ݵ۶۷඼޶2C`2Da1B`2Ca3Ba4Cc2Ac3Bc5De5De3Bc5De4Cd4Cd4Cd6Ef5De4Cd3Bc8Gh6Ee4Ca6Ed7Fe6Ed5Dc7Ge7Hg7He8Ie6Gc6Gc6Gc6Gc9If9He8Gd8Gd8Gd8Gd:He9He:Lh8Je7Ie7Gc8Gc9Hf7Fd8Gd9He9Ha8Fa8Fa8Fa;Id=Kf;IdJd@Ke?Kc?Kb@LbAK_BJ_HNaHObHObKReHObIPdLSgKQdJNbHMaGL`NRfNTfKSbKTaJR_HPZKT_JU^JS]FS_EQ_JUcJRbGN[GM\HO`JRbJR`ITbJUbKTbSZjU]kTZgOUcOUcQWePVdT[hSXfWZfUYeUZeW\gW[g[_jadobeq`fq`jsZclW_iW_jPVbRWdTYdVZfV\g[_j^`mceqdeqgiuhkxko{lp|mq{ip{elw`gn[bkYbiZblSZePWcNVdKSdCM_DM]CK\CI[BJ[>FX;FY8DZ7EY4AX5AX4AX5@X2?Y/=X.?X.>Y-;Z+:W,9Y+:Y+:X+:Z*:[,:\,9[+8[+8[)7Z'5X'7W%4U%4T$3U%4V&5V$2V&4X$2W$1V%4X#1V#1V%3X$2Y#1X#0X$0["/Z -X -X!-W,W -X!.W!/V!0W /W!0Y .X ,V!.W,W-Y .Z,W+V.Y+T-U-V+W+W+W*V*W*X(V+X)W+Y,Y*W -Y+W*V*X*W,Y)V+Z*Z+W*X+X,Y+X,Y+Y+X+X+X*W*X*W,X+V-Y,Y -Y .Z,Y -Z -Y ,Z-Z -Y*V".\*[ +\ ,[+[!-^ ,_ ,_ ,^!-\ ,\ ,\ ,^!.^#._#/_!-]"0_#1`"0_#1`$2a%3b'5c$2a#1`&4c&4c%3b&5c%3b'5c'4d(6g'4e(6f'5f%3e$5h&5i)5i(6g*9j%6j%5h)9l&6h)9i(8i(8i*9k'7j$6k(8l+8k(9l(9m'8l&7k'8l(:l);m)9l)9l+;n+;n+;n+;n,r->r.>r/?s-t.?u,=s,=s.?u/@v/@u.?s1Av3Cx0Av/Cu2Dv4Fx4Fv5Gv4Fx5Fy3Dx0Dw0Dw0Cw2Gx0Fx0Ex3Fz5I|4H{3H{4Gz6G{3G|1Dz7Gz8I}7H|6G{7H}7H|9L}:K}:J|=MP;M>M>N=N=NP?O>N=MO=N@P?O;P>R>S>T>T=S=S?U=S?U>T?UAW?U?VATAU?S>S@UBVDUDUDUFVDTCSBSBV@TAU@T@U@T@T@TAUCVBV@T@T@U@TAUAUAUBV@TAVCWBVCWBVDXBVBVBW@U>S>S>S=R=R?T>S=R=R?S;S=R>S@TBSARCTCTCTBSBSARBS?TAUCSBSFXAV>RCWAUBV@TCW@TEUEWHZHZI\J\HZHZJZN[P^K[KZL^M_N_K[L\Q^N]M[O]O]N\O]O]R`Q_OaO`N_OaPaPaQbQbO`O`M_PbQ`QaQ`QbQbO`ScSdTeOaM^KZL\J\FWFXBWBU@TAXAV?T?T>S?T>SS>S?V=S>R?PP;P?T=R>S?T>S?T?T>S=R>S@U=R=R>T>P?R>Q>QS=R=R>S;P;P=Q:O=QR=R;PR>R?SAUAU@T?SBVAU?S?S?S>R?S@T@U@T?TBRCTDTDTCUCTAR@QCT@QBR?TBQ>S>S>R>R=Q?SDX@TAU@T?S?TCRCTARDTDTEUFVGVCTCTDUDUBSBSAR?SAU?S@TAU?S@T>R>RAVO@Q?PAQ?S>R>R=RBREW@UDVEWEWATEWIXIYI\HZGYI\IZHZHZJ\I[I[IZHYK\HZK]J[J]K^J[I[HZHZI[I[I[IXGXHXKXIWHUIWFXGXFWFWEWGYFXHZH\I\J]DYG\I\H[G[K\M_N_NbPcReTgTgQgRgShShRgVgUgUgTfUg\nZl]o\n]obt`v\q]q[p^savewevbx`ucx\q\q\q_t]s\t]rardsct`r\mZk[k[lVhVhSeQbNdOeQbQcQbVgYjYo[qZo[pZoYnWlVkRhQhSiXnUlZn[pWmYnXm[pYnWlWlXmZoYnYo\n\m\n]mWnXlZqWmWmXnYo[o^o\o^mUkXkWkVkXkUhVmWmWmVlZn_rcwcxizgyhxhzkym|l{l{o~n}q~ttuutyytxz~~ƖΤͮԳ׵ײԳخձ٩٧קبת۩ڪکۧܦܤ٦ܢڣڦޣڤڣڢءסךӛ՗ӓғϓϝҝҞѡӢӤңѣ֧٥ةڬخۭصܽ฾ٴֻԽոԸֹֺ׻ڹؼڸۼܷٹۻܺݶٵڴ۴߰ڱݰݶֳسܱܴܳ۲ްݲݱڱڳܲܰس۲۲಺߲߱సಹ఻ޱޱݳ᳼౻಼఺޲ܵݲڲڷݶܸ޸ݵ߸0@a1Ab/?_3Da3Ba3Bb3Bc3Bc4Cd4Cd4Cd5De4Cd6Ef6Ef6Df6Ef6Eg4Cc7Fd5Dd6Ed6Ed7Fe6Ed5Dc6Fd6He5Fd7Hd8Ie9Jf8Ie5Fb:He7Fc7Fc6Eb8Gd:If8Gd7Fc:If:If9Gd9He9He9Hf6Ed8Gd9He9Ic:Gb9Gb8FaIa@Lb@Le?Kb@KaCOdAK_DK^EL_DK_JQcMTgOVhNUiMTfJQ`LQbKQbKPcPVgQXhSYfOVbLU_PZcQZbOYaR\eR\eRZeT\fRXcQV`PX`OV_QYbR[eU]gT]gV^iYamZblT[dRXdV[hTZeTZfZ`lZ_k^bo]ao]am_an`bnegtknypt}qu~mv~gpy_hq]eo[cmW_hV^gU\g[`m`doadoegsikwln{kmzpsos~nr}nr{osxmtwhrsdkmcjm^emW`iT[gRYgHQ_IP_GN\DKXEMZEM\CK\?I\;FW8CV9DV;DY=F\;F[4C[4CY2BZ3C\0?Y.=W,r-?q-@q->q,r.?r/>q.>q0@s/?r.>r/@t0Au/@t/@u-Bu-Bu-As/?s/At1Au/?v.?u,=s->t.?t->t/@v/?u1Au2Cw0Ew2Fy1Dw3Ew4Fx2Dv2Dv5Gy6Hz3Dx1Fy2Fy1Ex/Cv0Dw1Ex1Ex2Fy2Fy4H{4H{5I|4H{2Gz4H{5Fy4Ey4Ey7H{7H{:I|8H{8I};L=M;L;L:J;LN=MO=N;LQ?O?Q=R=Q=Q>RS=R?T=R>T:P>T=S>RRAU@TDXGXDWDVGZFXGYHZHYJYJZM[J[L\K^K]K^K[M[O]N\M[N\O]N\P^O]P^O]L\L[L_L^L^M\M[L]J\J\L^M_M_OaP`N`L^L^N_QaQaL\IYI[HZGZBWAUAT@WBXAW@V>S?T>S=SS=R;Q;Q9PS;O;R;R;RSS?T=RR=Q?S?S@T?S=Q?S?S=Q>R@T?S=Q=Q=Q?S@TATT>S@P@Q@QR=QR?S>RCSBSCRCSEUASDUBSARBSARARARAQ>SAU?SAU?S?S?S=Q>S@PCS@Q@Q>OARBS@Q@R@QAR=R>R=R@P@Q?Q@Q>O?P>O>S?R@T>RARCTEXEWEWDVGVFVHXIXGYGYHZGYI]H[HYH[IYJ\IZJ[JZJ[H\J[K^IZJ\HZI[I[K]K]K^L[K[K[IZJ\HZHYHYFWFVIZGYGYGYK]M_M_N_KaK`L\K\N_N`ViWkXkViYl]p\oZm\q\q[p\q`qZm]o]o]oas`rbtewhzhzf{czaxawdydzj{j{ih}ezcybwdyg{e|dzf{dubrjwer`s`p^o]nYkWiUgSdNcLaNdNcNdTiXj]narar]rZoZoZoXlVkVjXoYoYmYnWn\lXnWlXmXmYnZoYnZoZoYo]oZqYnYnZoZoWmYoYoYo\q]s^p_m_s]o]r]n[mXmXlWkXlVmYp[n^q_ugygyj|j{k}l}l}l}m~rpstvwxyw|}|~ƒČǕǞͬٶ߲ٷ᭺٨լ۪۬کݪݪިۧۤؤצ٥إاע֣եեըا٨ܤܞ՛ѝҦ׬ݯ۪֭تׯݭڰޱ೿⳼಼௾߱඿߷޹ܽݽڼ׻ݸպػڼټٿۿܽۺٸٶٸܵٷ۷ָܵ۴ܵڵٶڵڳݲڳشڴڶܶܳܳڱ۵ߵⴼ᳻౸ݰݳൽݳޱݲ఺ݯ۴߶ڳ۳۷ܷܶݷݶܹ2Be1Ab2Ad3Bc1@_1@_3Ba1@a2Ab3Bc4Cd5De4Cd5De5De6Ff4Dc5Db3B`5Db6Ed6Ed6Ed4Cb7Fe7Fe5Cc9Hg4Eb6Fa6Gb5Fa9Jf7Fb:Hf7Fc8Fc8Fc:Gd9Gd:He:If9He8Fc7Fc8Gd8Gd7Fd7Ec8Ge8Gc9Ic9Fb;Id9Gb9Gb;Id:Hc:Hc:Hc:Ic;Hd9GbKg:Jf;LeIZ;I\8I]4D[4D^4D^2A\2BZ0@Y1>Y0>Y/=Y/=[/>\/<[.;^.;^-:],<\+:Y*9X*9X(8X'8X*9Z(6Y'5X'5X(6Y'5W'5W'5Z&4Z%3W%3Y$2Z$2Y$2Y!/V#1V#1U#1U$2W#3W"1Y"1Y!1X!0W-U!0Z-V!0Z.Z-Y0Y.V /Y"0Y*U,X*V*V+W .Z,X-Y,W,W,W*W+V+V)U*V+W(T*X+X*X!+Z+X)W*X,Y*W*W+X*W*W+X)W*V+W*V*U+W+W+V+W*V*V+V*V!+X*W"-[ +Y!,Z +Z )Z*[+^+\ ,\ -\ ,\".^+["._ ,\".]#.^#/_!/^"0_!/^$2a"0^"0_#0`$2`"0_"0_%3b&3c#0c$2d%3e$6e$5b&3c%2a&2c'6g(6h%3e'5f)7h'5f&4e&6g$4f(8h&8d(8f%5g%7j&7m'6k)7j(8k&8j$6j$9k$8j);m(:l*:n)9l):m,p-Ar-@r.@r0Bt*=n-q2Bu/?s0?s0Au/@t/@t-Bu-Bu-As->s/=u/>v+@u.?u->u.?u->u->u-@u,Au-Cu,At/Dv0Dw0Dv4Ew5Gx4Fx4Gx4Fx4Ex5Ey5Gz0Ew1Fx.Cu0Dv1Ex2Fx1Ex3Gz3Gz1Fy3Hz3I|4Gz5H{6F{5G}4E|6G}6G{8Gz9H};L9J~8I|8I:K;L9J:K;L=N>P=N>N=M:K:KRTAWAW@VCWBUCV@UAU?S?S>S@U@U@T@T@T@T?S?S?S@TAR?T>R@T@TAV@U?T?U?S?S@TAUBVBVCSCT@UAVAUBVCWAUAUBVBX?T>S=RT=T=S9P;R:S=Q@RBTBSDUFWGYFWHYGXFWEVFWCTFYHZIZEYAUAW@TBWBWCVEVDVHZHXI[I\I[EXHZKZNZN\JZJZI\I[K[N\N\M[O]N\O]O]LZP]N[M[N_M\M`K]J]M\K[HZK]I[J\K]L^M^K^I_L]L^K]O`N]K[KZH[GYGYAU@T?U@U?T@U?U>S>SO=PS=R;Q=OP>PQS:O9N;P9N=R:O;PN=M=R=Q?S=Q=QR>R?S=Q@S>Q;O=R>Q>R;P;P?R@QBRCS@TEVBS?PAQO>O>O@P;O;P@U=Q

R@T=Q?SR>S=R>R?S>R>RBRAR@Q?P?PAR@P@PBRAQ?Q@Q>Q>Q;O@O>N>N@Q?P>O>N;O;QKgr+;p.>q.>q-q-r/?s0>v0Av1Au2Cw0Au3Dx4Ey3Dx4Ez4Dy4Dx3Cv3Ey4Ey6G~4D{4Dz/Aw.Cy0Dy/Cx1E{1Fx4Fz5Dx2Fy2I{3I{3Gz5H{5Fz3Fw6Hz7Gz7J{7H{6G|:K7H9J:K9J=N;L:K;L:K:K:K:K;L:K:K;L:K:L9NR=S?U?UAX@W@V@V?U?VAWAT@TBRBRAQ@S>T@S@T?S@T@T?S@T?SAUAUAUAUBV@T@TAU@T@TAU?S@TBSBRCSCSCU@TCV@VAV@UAVBT@TS?T>R?S>SBSFWHVJZKZJYK\KZGXFWJ[HXH^FYFZDXEVFXGXGYEVGYFXIZCYEYL[GXGYGYHZI[JYK[GZI[I[M\O\NZM[N\N\M[N\Q_L]JZJ[L\K^L^K]J\J]I\HZJ\GYM_J\J\J\I\J[K]L^J\I[J]J]J\I[EVAV@SAX>T@W?U>WQ=PSO;Q=R=S>S:O9O>OO?O=Q=QN:O:N>NO?Q:O;O=QN>R=Q=Q=Q?S?S@P?QR>R=R@P>O@Q@Q?P?P=N;PR:O;P:N>M=N=N>N;Q=P>S>RAO>OARBUCUFXEXFVEULXHVKXLXL^JZJ]J\H\J[LYLZLYLYO\HZI]IZL^J\J\J[M]N_OaQbUfRaQ_ObPbOaOaM_N`L_N^SdQcOaRdTfVhXjZlXn\pYoXl\mbrasevc{ih}g|m}hh~jg}f{i}g|f}f{g|jyk|izl{iyg{k}h|jh}j~m~j{om~oih}kg{j}h|i}dyfxcudvdveubw_q^p]o[mXjVgRgQiPhQhTmYn[qYo]r\q\qZo[pVlXoWmUlWmWmWnWnXmZoZoZo[l]mUlZoYnYoYo[p]r`u\rZpYrYpZqZq[q^tctcsbt`q_r`ratbp_p`pZq[n\q^p_pasascwgxjyj~ln~n~psutvvzzvxyw~~}ȋɐʕ͛Ϥ԰۸୽ޭ۬زܮܯ߭ޮ㫹ܯޯݰܲٱ۱ٳްݯݲݳڲٵ߲ܳ޵ⲻߵߵݵڵ۳ٵ۴ݴ۷᳾޷޶޲߿ܿ۾ٿۼڿܾ۽ڻڻݻ޺ݸڹܷݸ߹Ḽ۷۷۵ٷ۷ڷ۵۶ܴ۷۷ڳܲݱܳ״ֵ۴ܲڲڴܳ۲۳۴ݵݵݹݻ.A_.A_1Ca2Ca1@`0?a3Bc3Bc3Ab2Ab3Bd5De4De2Bc2Cd3Bc6Cd5Df5De5Db4Cc5Dc6Ed5Dc5Dc6Ed5Dc3Db5Fe6Ge6Fe7He7Hd5Fb7Ie6Gb8Gd8Gd8Gd8He:If9He:If9Gd9He9He:He:Hd:Ic:Jc:Je:Jf:Jd9Jc;Je;Id;Id>LgNh=Mg;Kf;Id?Mg>Mf@MgALi@Lh?NiAOiFQhFQhDRgHUhGSfFQdGRdFQcGQcKSgMVhPYjX^rY`qY`qU\lZ`p_eu_fu\cpZalZam_frelwgmvkpztw||{z~|}z{~uzovkr|hpzip{hn{kp|ko{ko|lo|ns~twvy{|z{lwwdnq^im\ekU`dT_dT^fPZcNV_NW`NU_KP`HO^IR]JSaEQ^BM]@L\=L[X,;X,;W,:W,:W,9Z-:\,9[*9W*9X)9Y)6Z*9[*8['6X(5Y)7Y(6Y'6Y&4W(7X(6Y'5X%3W*8Z%3V'5X$2V#1W!2U!1U!1V"0Y!0X#1X#2X"2V"1V 0T"/W /T+T+V*S.V.X-W,W+V,V,V+U+X+W)V*V+W,X*V,W+T+T,U-Y,W+V*W+Y*W+X+V)U*V)U*W(U)U)V(T(T)V)V'V")W'V*X*X'V)W +Y*Z*Z+X)V(V*W-Y+Y +\ +\+[ ,\,[,[,[,[ .\-]._ ,_ -` ._!/` ._ /],[!/^"0_"0_!/^$2a"0_#1`$2`#1^#1_#1`%3b%3b&4c'5f&4e'5g#4e#3d%5e#4e#7i#6h%7i&7i%7i&8j(:l'8j(:l(:l&8j(7k'7j(9l)9k*:m+:m)9k'7k'9m(:n):n*:n+;o)9l,q-=p,s0?s->r+=p)>p*;o->r,=q/@s-?s/=u/>u2Aw0Au2Cw1Bv2Cw3Dx2Cw1Bv2Cw3Cw4Ey4Ey3Dx4E|2Cy4E{2Cy1Cz/Dy0Dz2Fy2Fy3Gz1Ex2Fy3Hz4H{4H{4H{7G{5G{4Ey7G{5G{5Fz6Gz7H{7H6G}6Gz8I}7H|:K9J9J;L;L;L;L9J};L:K;LQ;O;OT>U>U>V@V@V>T?U?V@TAUBRDTBR>S=S>T?R?S@T@T?S@T?S?S@T@T@S?S?S?S?S@T@SAU@T>RAPBSARBSBT@T@T?T@U?T>SRU=S>TRBUDTCTEVJZFWKZL\I[K\FWIZJ[HYCXG\I\IZHZHZHZHZI[I[HZK]IZHZK]GYGYFXHZIXIXHZI[IZL[N[LZN[N\M[M[M[IYKZI\KZL[J]J\L^L^M_K]J\L^K]K]J\L^I[HZJ\J\K]I[J]I[GYI[HZCXCW@T?U?UAW>T=TOO=N=N=N=N>O=N?PRAP@P>R=Q=P;P>O?P>OM:N;P=P=P=Q=QAPARCTCTCTDVCVFUEVGVDUGYHYIZH[J]H\IZKXJXLZLZN[L]K_K]L]L]N_K^K]PaSdScTeVdTgTfTfTfTfRdRdSeSeTgXjWi[m\n[m[mbsbw_tbwawftewhzhzi{m~omonpm~nki}fzf|g{h|g{ixi{hyk|i~jh}h}kjl}l~i~jjmki~i}h|i}fzi}i{i{fxfxewcu`r^p]o_q^p\mYlUmTlSjXmZo[pXmYnZpZoYnYnWlXmVmXoXnXnXmXnYoYoXmWn\nZlYl\n\n]n]o\r_tawbwbw_t^s^s]s]r^tar`qbqapbr`pbq_qeq_r`qYq_o`q_qasexfvhyl}k|ko~pprtsxxwzyyz{|~ÈƋȍǒȗȟϭܯ߯޲Ჺ޲ޯޯ߯߳㯼ݮ߳ݳ۴۴ಽݯݶߴߴڵ഼޲ݵാᵼ߷߷ݷݵ۶ܵݴݴ޶޴޼߼ۿ޻ؽ۽޽߼޻߻߸ݹ߸ڶڸܷ۹ݵܷݺ޺޸۲ܵݳܸ޶ܷܵڴߴ۵ݵݵܴݴܴܸ޶ܺ߻1Ca2B`2Db2Ca2Ca2C`4Cb4Cc4Ce3Bc3Ba4Ca5Dc5Dd6Eg5Ef5Dd5Dc6Ed8Gf7Fe6Fe7Ee8Ed8Fb6Fc7Fc7Ic5Fb4Fb5Gc6Gb7Hd4Fb7Gc:If9He9He9He9Ge:Ge8Gd:Jg8Hf;HfJ[=H[;F]=H^[0>\.q,q-=p.=p.>q.>q.@t+T?U>T>T=S>T?R?R>R@T>R>S=T>T=S>R?S?S?S?S>R>R>R?S@T?S@T?S>R>R?U>TAU@T@TATCTDT?T?S@S=R?T=R>TR?S?S@TT>T=S=S;Q>U@V?RCWCWFVCTCTGUGXGWHXIZGXHYGXIZIZFYEYIZHZFXGYI[GYI[HYGYI[GYGYFXFXFXH[IXFXHZGYGYJYJ[M[J[L\L\K[JZKZJ\I[JYKZK^J\J\K^K\L]K]J\K]J\K]I[GYI[HZHZJ\IZI[GYJ[EXAVAUCW@WCYAW@V@U=UQM=N=M?O=M=MN=NPR@TDUBTCVFUEUFUJ[GXGXHYJ[JZI[GYKXM[N\N[M^M]N_J`J`KaPaObScRbUgTfVhUgVhTfWiWiZkWiWiWi\nYkasbscuas_qeve|h}g|h}j{l~nj|nnrspopnnmk~l}f|h{h|fzg|g{ezi~i~i~i~i~i~kik|kmj~jjh~j}i~h|i}f{l~gyhzgycucu`r`r]p^p_q]o\nXnXmWlYnXmYnWmVlYiVlWlWmUkUkWmWn[mYl[n]o\o]m\l\l]n]m[m[m]n^q^pbtasewf{ji~hyctducucu]s^sbs`rcqcsbtfsescq_sar`sbrbt`sfwfwizl}k|opoovsxuxzyy{w}{}~ƒˆŏȎǓʖˠҪܰᮽి㱽ⰽ೻߲ܱా೾ݴ߰ܲݴ߳ݲݵ߳߶ߵ߶᳾ݵߺ߷ݹ߶ݲܷܾڿۿܾܺݼܾ߼ếݸڸܹݺ޹ݹ۹۹ݺܸܶڸാݸݵڸ෽೾ߴ۵ݳ۷ݷݷݷݶܷݺ߸3Db3Db4Fd2Db2Ba4Cb4Ca3Bb3B`4Cb3Bb5Dd7Fe6Ed6Ec5Dc4Cb5Dc5Dc4Ca6Ed8Fe7Ce7Dc8Fb6Eb5Da6Db7Eb7Eb6Eb8Gd8Gd7Eb9He5Da7Fc9He8Gd9Gc:Hc;Hd;Id;Ic=KeMg=MgKg@Nj@NgALdAMeEPfCPfDPfDOcHSfEPaFRdHReLSfKReMUhNUhLTgLTgOVhOVfOVfUZgW]kY_mY_mX^mZ_p\cqfjxuw{}|~y|zw|{y~{~|{}|~~}z~|}~{|vxkupgpraknYbfUafR`gO^gN\eKYbKXbKWdHT`HVaES`CM_@K\?J]?J\\.=Z.=Z.=Z.=Z,;X-q,TT=S?R?S>R>R=Q?SR>R?SAU>R?S>R?S@T@T>R>S?U?U>T>T?TAT>R@TBSAU?S?SBV=R?T=Q=T>TT?U>T@V?U?R@TCWCWETBTEVEVEVFWGXHYGXGXEVHYK[J\GYGYGYGYHZGYI[F[HYI[GYGYI[HZDWIWGZFXHZJ\H[HZJYJZJ[KZK[JZKZI]I[IZIXHZHZHZJ]J\K\M_L^K]K]J\J\GYGYHZI[IZK]FZG[FZBVCVAT@TAW>T@V@V@V?UN:P9N8MR=Q>S?SCTASDVEWCUFXDVEVFWFWGXHYFXJWJXLZN[K]L]K^KaLaNcMbO`RdSeSeVhWiUgWiXjYk]oZp^o\n^p]obtdvbxgyfxi{j|izh~ji~mmmno~qutrqoooppl}h~h{i}lh|h~e~ijjki~kjji~h~jikkkim}h}h|l~i|l~k}i{gygycuascrar_q_q^o[pZoZpYoXnXnZjXjYkYkXjXiZk[mZlYl[l\o]p\m]nZpYnYo\m]o^o\n]n[q[p_taxcyg|koqol~kk~dze}byawdwdtasargs`q_p\s`rdtbueugxizhzk~nopsstvuyyy{{~}}~ƉljnjɎʕ͛Ѣٟآ٩ޮ᲻޲ۯܱܴ߯߳޲ݲൽᴿ޳޳޵ൽ߶޶ݷ߷ݳ۸޸޴ߵ޶ⴿ߷ݵ޵޸ؿؿۿݼۼڽ߽ܽ޹ܹܻ޺޻޺ݾ߼޻޼߹ܸݺ޸๿⸾ලߺᵾݶ޳ܸݸݶݶܸ޵ܹߺ2Ca1Ca4Db3A`5Cb3Ba3Ba5Dc4Cb3Ba4Ca5D`4Ca5Dc6Ed4Dc4Cb4Dc6Ed6Ee8Ge8Fc7Ec8Fc7Eb7Fc7Fc7Fc8Gd8Gd8Gd8Gd7Fc7Fc7Fc7Fc8Gd9He7Fc:HdNgNg>Ng=Mf>Lf=Ke@Nh?MgMf?Mg?LgDOiBNdDPfBOcHTgEPdFQbGRdFQbGQcLSfLTgNViQYlNUhRYlT[nSZlT\mX^magu]cp[aoY_m\cs[bq[apgk{vy}}}~|{~y|{~s~ylxteqqaos^mtYgqWdnVdmQ^hSakP\hM[eGVcCQ`BP]DP^AM\AM[?M\@M^@M^>J^Z1?\1@].=Y0=Z->X,;Y.=Z-=Z,;\,<]-<],;\'7X,:[(8X&7Y%5W&6X%4W'7X'7X%6W%4V&3W&4X&6Z$4Y%6X$5W'7Y&3X%3Y'5Y'5Z$2W#1X#1X#1X$2Y"0Y!-X$1["/X .W/X.W,V.X-U+U,V-V,W,W*U*U(S*T)V(U(W'S'S )S&N!*R'P!*T )U*T*T*T*T+V*W(U*X(V(V +Z!+])[ *[ *Z,X+X+Y,\+Z,Z-[*[-\ .]-\!-]!,],Z -Y -[-\ .]#1`-\!/^-\ .]"0_"0_$2a!/^#1`"0_"0_"0_"0_!/^"0_$2`$2a&3b%3b&4c%3c$5f"2c"2d"2c"3d%6i$4h%4h!3g"3g&6j'7i&6i(8k'7j'7j(8k'7j)9l'7j&6i'7j)9k(8j)9l(8k(8k(8k(8l*:n)9o+8l*7k*8l*:m)9l+q.=q->r(9m'8l*;o'8l(9m*;o):n,=t,;r.=t,;s,>s->t0Aw1Bv/@t.?s/@t2Bw4B{3Bz2Cy2Cy0Aw0Aw2Cy1Bw3Cv3Ey2Cx2Fy0Dw0Dw1Ex2Fy1Fx3Gz3G{8H6G}8I9J6H~3H}2G|3F|6F}6G|5G{5Gy6Hz7I{7H}6G|:K8I6G}7G}9I7K9J7H~;L;L:K;L:K:J:J7L8L7K7K8L7I6K8K9I7I9J9K9K;L9J7H~8I;L:K8I8I6H~3Hz6K}4H{4H{3G|4H}2F{2F{2G|0Ez2G}1F}3H1F}1F~0F{2Fy4I|4J|4J|7J5I~7G}6G}8I4E|5F{3E{0D{3G{2F{5I~5I~1Ez4H}3G|2F{3G|4F|6E|6F}7H~6G}7H}6G}6F|9I6G}3H}5I~4H}5I~3G|4H}5E{4F|3G|2F{4H}1Ez4H}2F{1Ez4H}2F{2F{1Ez5I~3G|4G}5H2E~3H1F{0Ez4I~3H}2F{6J2F{2F{6J4H}5I~4H}6K4H}6J5J3H6K6K5J5J5J6K7L7L7K7J7K:N7K7K8L6J6J9M:N:N8L;O;O:N:NR>R>RT>TQ=Q>R>R?S>R>R@T>R@T?S?R@V>U?U>T>T>U@S>R?S@T@T?S>R>S>T>S?U=S=T?R?S>UAW@V@VAWAWBWBX?V>RAUAUAUAUETDUFWCTGXFWFWFWGWEUHYG\GZI[GYHZFXGYGXFZG\GXGYHZFXHZFXGXGXFXFXJ\J\GYH[I[JYI[I\HZGYJ[HZH[GZIWFXGYHZGXHYI\I[K]L^J\I[HZJ\HZHZGXDYCWEYEYBVBV@T=T?U?U>U>V@VBX>T?U;SR=Q?SCTASCUBTCUEWBS~EWFWEVGXIZIVKYM[LYJ\M^L_M`LbKaMcQbSeTfXjYkXjXjXj[m]o^pZrasasascuewfyd{hzi{j|hzk}k~l~k}mmnk}orttpppo~qqppjkj~j~li~jlqonlmli}l}himonjnl}h~l}k}jzk}k}k}k}j|k}i{guhxcwdudv_u_ucs_p`r[m[lXjYkWiZl[mZl\n^p_pZq\p[q]r\sZp\q\q\rZp\pYo\p[p\qcxdzezezjkqsqpolljf{gyeveybubsarbs\tbvdtfwhzi|lzm|o}q~prtvuzzzy|}{}}~ņÈŋȊƎ˕ЖєјӜ֧ݨٯް߱୻ݵ᳼ߴ᳼߰೾޵೾޴޳޸ܸ߷ݸܹ߹߷ݶ߶߸߼ٿݽۻۼ޽޽޻ܻ߽ܺۻ޺ۺ޹ۻ޼ߺݻṿ㷽߹޵ݵ޷ܷܸܶܶ޸޸޶3A`2Ba3Ba2A`2A`4Cc5Dd5Dc5Dc5Dc4Ca6Eb5Db4Dc6Ed5Cc4Ab5Bb5Eb5Ea4D`6C`8Fc6Da7Eb7Fc8Gd5Da6Eb8Gd8Gd8Gd8Gd:If9He9He8Gd9He9HeKh;Jg:HfLi=Ki;Kg>Nh=Mg>Nh=Mf=Mg=Mf=Nf?Lg=Ke>Lf>Lf>Lf?MhAOj>LeBPgCOfDPfBNcFRdHRcFQcKVgJWfKVfPXiOViPXkRZmSYlU\pY`r\bsafybgxgm|ek{agv[br`fw_fx^dwadxlo{z}yr~ly|hsvdotensgrv`lsZfnT`jS`iN\gLYcLUaKVaKXbFS`CO_AM^BN`?K]?J]Z.t->t.?u/@v.?s0Au/@t->r2@x0@v0Aw0Aw1Bx1Bx1Bx/@v1Au4Ey2Fy2Fy1Ex2Fy1Ex3Fz5Fy6H|6G{5Fz6G}6G}6G}6H}5I~4H}3G|3F|5F|5Fy5Gz9J|:I|:J~6H{8I}9I7I5J4I}7K5H}7G}7H~8I9J9J:K7L8K8L8L7K7K8L9M9O6J7H~9J;J9K9J9J7H~7H~6F|6G}9I8I8I7H~7H{5Fz6G{5J|2F{5I~1Ez/Ez0Ez0Ez1F|2G~1F~1F~2H2H|4G{3Gz4J|4H}5I~5I~4H}7G|7H~6F|5E{1C{0C{1E|3H|3G|3G|3G|3G|0Ez2F{3G|2F{2F{2F{3G|9H8I6G}5G|5J4G}5I~4H}6J2F{2G|4G}5E{5F|2Fz3G|4H}4H}5I~3G|3G|2F{1Ez3G|2F{4H}2F{2G{1D}1C|2G~2G~1F}3H|/Ey5H}2F{5I~1Ez3G|0Dy6J1Ez6J4H}6I~4J~4I~5K4I~3H~4I6K6K7L6K7L7M;N8L9M7K7K:N:N9M8L8L7K8L:N:NR=QT>TR>RR@T>R?S?S>R@U?V>T?U?U?U?U?S?S@T>R@SAV@W?T?T>S@V>S?S>RAU?T@V@V@VAW@V?W@V@US?T>S=R>S>R?U?U?U=S=S=T9P9P9P8O8O8L7L8M6K7L8M7L8M7L8M9N:O9N8M7L7L7L7L:O:ORASBTBTBTASBTCWBREVGXGXFWKYIWM[LZL^N_MaNaO`SdQcSeVhYkZlYk[m\n\n^qasbscucudvdvfxdvfxizewi{k}i{j|k}l~l~k~j|moo~psursqpqoollllmmlppooqmmlm~okmmomkpk}hzk}k}j|i{j|j|j|mmmk}nj|k}l~j{j|i{fxcubtas]o\nZl]o_qas^p`rct]t]r`v`ucvexg{fxcwat[r\q\q_taxg}g}ig}i}l~oruwswupm}j{fwgzeuguewdwbvevhygyfym{o~qsuwvx{Ìċ‰ċËŒǓɓ̝͗̕ѢԦתڰ߱௽߬ݫٱ߱೾ݴ߶ߵ߹޺ߺ޺޽ߵ޶߸߳߹߻ٽٿ߼߼ܻ߻ݻߺݻܼܼڸ۹ܹ۸ܼ޽຿ݹܺൻܷݷݶߴܵݷݸݸݷݴݸݹ߸1@_2@`2A`3Ba3B`3B_4C`4C`5Dc5Dc5Db5D`4B_7Cd6Dc5Ab8Ba4B_5Da5C`7Eb8Fc6Da8Fc5Da5Da6Eb6Eb6Eb7Fc8Gd6Eb7Fc9He9He8Gd8Gd;Jg9He:IeLfNh>Nh=Mf>Nf>Of?Oh@Mh?Mg@OiAMg?Lf>Mg?NfAPf@PfCQhCNfDPhDPfDQdERbERcITeKVhKWhKXgMVhRYmSZmU\oW^qZat^dwdi|ch{di}djz_fv^ev_fx_gz_exagzei}kmss|{ľ¼»}{}~s|zitueov`jsZcnYbkU_iT^gQZdQ[eNVeIQaFP_DQ`CPaCN_AM\BN]AM]>J\@L_;K^;I\=I^;G\X/=X.t->u->r,=q,=q->r-=r0?w0@v/@v/@v/@v/@v/@v3Cz1Cy0Dz1Ey2Fx0Dw0Dw0Dw3Fz4Dy4Ey4Ey6G|5F}6G}6G}6H}2F{4H}5I~3F{5F|5F|6G{9J}9I|9I}8I}7H}6G}7G}4I}3G}7J5H;K8J9J8I7H~:K7L8L7K7K6J8L9M7K6L8K;K;J;J;J:K9J9J7H~8I~5J7K8H~8I8I6G{6F{5Gz3Gz3G|2F{3G|2F{0Ez1Fz1F{1F{2G|2F{3H~4H}3Gy2Gz2I{4I~4H}4H}2F|3G~2F{1Fz1D|1C{2C{3D{5F|5G|4H}2F{2F{2G|2G|3F{2F{3G{1G|4H}5H}5F|5G}3G|5J3H}5I~3G|5I~1Ez2F{2F{4Dz4F|2F{2G|2G|2F{3G|3G|3G|1Ez2F{2F{1Ez2F{2F{1E{4D|3F~0E}1G~1F~4F~4H3H{3G|4H}2F{2F{3G|3G|4H}5I~2F{3G|4H}5I7K3H}2G|3H|2G|3H}5J6K7L7L9M7K7K~9M9M9M8L9M8L:N:N:N;OR>RTT=Q?S=Q=Q>R@T?S>R?S?SAU>U?T?U=S?R?S?SAUAUATAT?U?T@U=R=R?TBU?S?S@T>UAWCYCYAW@W@W?T@U>RAUAU?SCTAR@QBSBTAUDXAUAV>SAUDXCVFVGXGWFWFWEVAV@T?S@T@TAVATFUHXGWDZGYGYHZHZJ[FVCYCWFXCUCUCVDUDT?TCSBSEWAUDXDXHYGYGWFVFVETGWFVCTEVBWBVAU@TAU@U?T>S?T=R=R>SU=S=S=S=S=U:Q:Q9P8O8O8L7L7L6K8M8M7L7L8M7L7L7L8M6K7L4I6K7L9N7L8M6K5J7L5N5L5L7K6K6K9N:N6K6K8M6K7L6K5K6K8K6K6K8M7L5J7L6I~7L6K7L8M6K7L9N7L7M8N8N:P9O9Q;R9P;O:O8N8M8M7N7O8M9M7N7N6M6M7L4I5K7L7L7L8N9N9N9N6M6L6L8M9N7L5J5J6K6K6J5M6M7N5L6K7L5J6J5K6K5J5J7L4M6K5J5J3H4I4I}3H}3H}4H~5J{5L}2Hz4J{4H3H2G~4I4I5K5H}6J5I~4H}3I|6K3G|4H|5I4H~4Hz4H{5I|4H|4H|6J}6J}6J}8L6J}6J}4Hz3Gy1Ey7G{5Fy4J|2Fy5I|4J|6L~3I{1I{3I{2Hz3I{5K}3I{3I{5K}3I{3I{2Hz3I{4J|5K|4J{1H{2Ex3Gz7G{6G{9I}7I}6G{9J~:J;MMh=MgNe=Me@Nh?MgAMhBNhCOiBNiBNi?MfBSg@OfAPgCQiCOfFQgERdGSgEReJWgIUfITfLWiLWiN[lRYmSZmV]pU\oV\o[_t]au^cw\as[br\dvYbt[cw[cx^dx_bwdg{louw{}~﻽Ľſ¥ƥŸŸ xqz~js|gpzbkr`io^ho\gmYckV`jU^jR\fQ\gQ\iMYeNZgMZdMXcIWdIVfHUeFScDQbBP`?L^>K]X1@Z/?Y.?Z0@[.>[/>]0>^.=].=_-<]-<^,;],;],;])8Z+8\,:^-:`*8]*8])6](7\)7]&4Z)7\(6\(5[(6['4Z(3Y&2X%1W%3W#1V#1X!/V!0V-R".T#.T#,S"+Q+P /V-S".S!0T .R!/U.T,U -X+S -T+R)P ,U ,V+X (W )W*W*V*X,Y+Y +\ ,] ,\.[.] .].].\!.]".^ ,\ .] .]!/^-\!/^!/^-\ .],[ .]-\.\"0_-\ .]-[!/[ .[-\-\-]!/` .` /_!/^"0b#1b#1b"0a$2d#1b#0b"0b"0b$1c#2b0b!1f0e0d0e"3g$7h$6g&6h%5h$5h%5h'3h'4h(5j(5h(4h(5i)5i*6i(5i(6j'8k'8k,s,=q->r.?s1?w/>u.@v/@v/@v/@v.?u/Ax-Ay/Dx/Cx1Ez/Du0Bv1At2Cv2Dv2Cw1Bv1Cv2Cw1Bx5D{4G|1G{2G|5J2G{6F|5F|6F|6H{:J|7Gz7H|7H{7H~7H}5I~6J3E~5H7G7H~7H~8I9K9J7L8L~6J5I~6J6J8L7L6K9L;L9H9G~;K;L9J8I~7H7L6K5J5I~7G~6G|4Ey5Fz4Dy4Fz2G|2F|4G}0Dy/Dy1F|/Cy0Dz2E{3F|4Hz3Gy5H|4Iz2Hz3I~0Ez1F{2F1E|1Ez1D~.Bz1D|2C{5F}2Bw4E{1G{1Ez1G|1F{2G|3H|4G|2F{2G|4H}3F{8H7H}3G|4G|3H}4G|4H}2F{4H}2F{2Ez4Dz2F{0Ez1G|2G|3G|1Ez2F{2F{3G|4H}3G|3G|1F{1F{3Dy4D|3F~1G}0D|2E}5H5H5G3F2G{4G|4G|3G|4G|3G|6J3G|5I}6J{4H{4H}4H}2H|5J5J5J4I3H6L6K5I~7K9M7L7L7K7L8L9M:N7K9M:M9M:N;N9MRU>TR>RR?S@T>S>T>U>U@T>R?S@TAU@U@W?V?T?TAVAUAT@TAUBV?SBUAWAVBWAW@X>VS@QARCTARBR?TAV@UBV?SAU@UAVCSEUDTDUCTBS@UAUBT@TAUBUBV?T@TBVDXGXGXI[GVEVBWDXDWFVCVCVDUBSBSCTARCTCS>SBVBVDWEVBVAU@TAVDXAU@UBWBV?SAU@T@U?T?T=R>S>S>S_5Aa5Cd3Ba4Bb4Aa3A^6A_6A^6@^7B`8Ca7Cb6Cd6Bc6Cb6Da9Db7Da8Fc6Eb7Da5C`7Eb7Eb7Eb6Fc6Eb7Gd7Fc:If9He8Gd9He;Jg:Hf;If;Jg:He;IfKiKe=Je;Jc;Ld=Mf>Ng=Mf?Oi>Ni?NgBOgBNeDNdDNfCOfBNfDPkERkCRhCShCRf@OaBQdESfIViKXkJUiKViLWiLWiKTgNViPXkQYlT\oT[mV^pV^pW]pY^rV_qZcuW_rX`rX`sZbu\cv\audj|koossuxz~}}z}ƾ¢àĿüǼĺü¼½ľŦĤƥĥŧâþǨǨ¾zs{{pvymrvnt|msykpwgpthqrgqrfovglrhorjqrgnrhoucnseot`ir[fpT_mNZiLXfFS^FQ^CO`DP_@M\AM]@L[BK[@JZAL]?K\=JZ>L\>L^=J\?I^=H[].=].=[,;Z.<[,>\.?^,=^-?`->`+;]*:\(7Z-:^,9\+9\+9Z*8[)7Z)7Z*8\'5[&5Z'5Z(6\(6\'5[%3Y&2V&3V%2V'1X(1Y(1[(/Z(/Z)1Z'1[&2[%0Z#0X!/W"/V ,U#-T#+R")S *R+R)R+V,U+U+T -W ,Z,W-W.Y-Y,Y-],\!,]#._!-]".^"-] ,\!/^-\!/^ .^ .]!-\!,^ -^ .]!.]".^+[ ,\!-]*Z ,\-]-\+Z .])Y!.^ ,\".^-]-^ ._"0a#1b$2`#1`"0_$1a"0`!/a!/c0d/c"2d 0c!2e 2f!1f 2f!2f%5g&2f$1e$4e#3d'5e&3g'4h(5h'4i'4i(5i(5i'4h*6j+7k*7k+8l+8l*9l*:m):m)q/?t.?s.?s->t->t/@v->t/@v/@v-Av.Bw.Bw1E{.Cx.At.>s0Au3Bx3Ay2Ax0Bx1F{1Ez5E{5F|3Dz3Cy4E{4F|2G|5F{9H{7G{6G~7H~6I~3G|4H~6J5J~4J5I}8H7F}8G~QR>R=Q=Q=Q=Q?SR>R?S?S>RAT>R?S?S@TAUAU?SAUAVAU@TATATAS@SAVAUBV?S?S@V>U>U?VAU>S@TBW@UAU@TAU=Q?R>Q>Q>R?RAS?RAUAU@TBW@TCRBSCTBTBSDUDTAU@U?T@U>S>S@UATAUBVEYEYAVBVBVAUCWDXBWETCTCTARBSCTBSBSCTCSBV@TBU?T@U@V?U?S?T@TATBR?SAU?SAU=SAV?T>S?TS>T>T=S:P8O9P8O7N4L7N7O5J7L7L6K7L7L5J7L7L6K5J5L4L6J7L5J6K4I5J4I4I5J5J4H4I4I6K5I5H4J2G~3H3H3H5J4I4I5J5I6J8L6L6J5J5J6K6K3L4J5L5M8M7L7L7M7L5J6K6L8M8M9N7L5I5J7L5L6M3M7M6M5L2G~5J5J5J6K7K6L7K6K6K6K6L8N8M6K7L6K7L7L5J5J6K5J7K6K5J6K5J5J6G5J3I4I4I5J2G~2G~4I3H3H2G~3H1F}1F}1Fz2G}3H}2G{2H|1E{4G}2Ez4H|3G|2Ez.D|1F}0F}0Bz2E}1E|1Fz0Dy1Ez1Ez3G{0E{0Gw4K|0Fx1Gy3Gz3Gz3Gz3Gz2Gz5Dy2Gz/Cw0Ex0Cw0Gx/Ew0Gy2Hz0Ez0Fx1Gy1Gw/Eu/Fx2Iz1G{3I{2Hz3I{0Fv1Gx1Hx/Eu1Gw0Fx/Fw3Gz2Gy2Gy7Hz7I{7I{7I|3Ew6Hy7I{9K}7H{7L6K~;L}8K|9J8I}3Hz7K}6J}6K{9M|6L{6Kx5H{1Gx4J{2Jz3L{5L~5K|7M~8K9M5I|8K~9P8L8K:Q:O:Q:O?P@Q>T@U?UBSCTCUCVEXEVFWGYKXLZN\K]O_NaRcQbRdTfWiZlXjYo^o_q_qbscvexdwhxizhyizgweyiyi{gyhyh~f{g}g}l}k|mmmpporttrqrqomomponpo~poomlpjijlqrqtusqqnl~nmj|i{k}j|m}l~k}g~ljllll~osqorrnlmqpruvxyŒƒĒ’’яēɐȏƏÐÒĖǕǘʘȚɛȝɠʨˮҸվؼҵԯ֥֦ңԤҤӣΦӪק׮ֲ٭جج׭ٴ᳿޻ܹܺݹܹݺ߾޸޺߾ھ޾ۼۼ۽޼ܼۼٸ۸۵ط۷۸ݵٵٸ۸۷ܷ۸ݲڳްݱزٰױٯײڱڰ1@_2A`1@_1@_2?_1>^1>_2@_2A^3B_4C`4A^7A_6A_6A`7A`7B`7C`4Ba5Bc5Bb7C`8Ca8Ca6Ea6Da7Eb7Eb6Da8Fc9Gd8Gd9Gc8Gd8Fc7Fc7Fc:If:If9Hd;Ie:Ig:If;IfLh>Kf=Kg=Mg=Me=Mf;KdNg>Ng>Oh?NgCQhCNgDOhDPhCPgCOiEQkEQjAQhBQfCRdCReBReCQdHTgHUhGTgIVhHReITfJUgMWiOViOViPXlQYoSZnV]oTZmQZlT]mT]oSZmW^qV^qX`sZas]bu_dwaezhkijpssvtwz|y}}|z|{}~{~}|{xw~vzy||x{{~ŻĽûǿ޼üá¡»}}|}|uq{~lu{iu|`ntUcjSajN[fNZiKWeJXfLXeKTcJTbJSbER_CR^CQ_DQaDQaBO`?L]?L];IZ=J[;GX5DV5EX6E[5D[2AY3C\1AY2C[1B[0@[/>Y0>X/=Y.=W->Y0?]0?]2Ab1@a0?a.>_0?a/?`0?a1@a,<]-=^0>`.<_-;_-;^,:],:],:]*8[)7[,:\*6Y)6Y)7Z,8[,6[,4\,4[*3Z)2Y(4Z(3Y'4Z%3Y$2V#1V#1V$1W$0W".W".V!-U ,T ,U ,W ,V!-W,V -X/Z!0[!0[-X -Z-Y-Y -Y#/]!-^ +\#0` /^"0` .[!/\!.\"0] /_ .] .] .]-\!.]".^".^".^ ,\!-].\ .]-\!.]!,\ ,\"-^".^.]"0`!/b-^!/`!/^"0_!/^#1`#1_!/a"/c!/c0b0c0b!1c/b/c 0d"0d".b"/c$2d 1a!0b%2g#0d%2f'4h&3h%2h'4g'4h'4h(5i*7k*7k*7k*6k(7k(9k+:m*:n'9m(9m&7k&8l'9l'8k'7l'8n'8n(9o'7n(:o&:m*;o+;p+s,=s+=s+?t-Av.Bw-Av,@u,@u-@u0@v0?v2@w1@w-?u.Cx1Dy3Dz2Cy3Dz4E{2Cy3Cy4E|4F|6D|6F}6G}6G}5F|3H|3G|2F{4I~5I~7G}7G}8G~9G~8G~7I~7K6J7K6J7K7K6J8K6J5I~6J9I7H1E}7I6J:K8I7H|6G{7I{5Ez5F|3D{3Dw3Dw3Dy5G}1F{2Fy1Fx2Ez0G{0Fw2Hz1Hy/Fw0Fx2Hz1Hz1Gy3I{3G|2G|2Fz2D}0Bz/Az/Bz-@x1D|4E}2C{1Bz1B{4E|2Cy0Dy2F{2F{4G|0E|.C{.D|0Dy3G{2F{4H|2F{4H}3G{3H|2F{3G|4H}4H}2G|1Fz1F{/Dy1Ez2F{2F{2F{3G|3G|1Ez2F{4H}2G|3G{4E{4E|5F~3D}3C1D|5H3F~3F~1E|0Dy2F{2Fz2G|2F|2F{4H}2F{3G|6F|3G|4I~3G|5H}4H}3G|5I~2F{5I~3H}4I~6J6J5I~7K9I9I:H9K:L6K8L9M8L5I~6J}5K~7L9N7L7L7L8M8O9N8M:NTR?S@T>R>R>R@T@T?S?SAUCW@TAUAU?SAU@TAU@T@T@T?V?V>U@VAV?T?T?T>S=S?S@T=Q?Q=P?R>Q?R=P@R>RAU?SAUAU@UCSCTCSCSCSBR?S?T=R?T@U>S>T?S?S>R>RAUCW@TBV?SAUBVEYAUAUFVCTEVCSBRBRCTAQ>T@T@S@T?V?T?U>U?U@S?R?S?S@T?S?S>T?T>S=R@U>S?T?TSR>U=S_1>]4B_4B_2@]5B`6@_6@^5A\4A]6A_6B`8Fc7Dc7Dc4B_8Ca7Da6Ea6Eb6Ea8Fc8Fc8Fc7Eb8Eb:Fd8Gd8Gc:He8Gd9He8Gd:Ie:Gb8Fa9JeNg?Oh>Ng>NgL]9I[9H]5H\8I\8HY8FZ9G]9H^8F^6D]6D]3BZ2AY3A]4A\3B^5Ca5Cb5A`4A`2?_2?_4A`2=_2@`2>_1>a0=`0?b0?b.=_.=`.<_.;^1=a/=`.<^.:\2;^09]/9]-;].;\+8Z+8[(7Y'6X)6X(6Y'5W$2U'4W&2X$0V%1Y&2Z#/W".V"-W!-X"/Y"/Y!.X.X"1]"1\ .Y/Z.[ -[-[ -Z!.[ -["0]!/\#0] .\"0\/Z"/]"0\"0]"0_!/^"0_#0_!-] ,\"-^".^!/^-\!/^-\.\ ,\!-]".^ -\ /^ .]!/^#1b#1_"0_ .] .]"0_#1`#2b#0d!.b"/c".c"/c"/c-a0d#3f"1d$/d$/c$1e!1d /c%2f#0d$1e#0d%2f%2g$1e&3g&3g&3g'4h'4h'4h(5i(5i*7k*:m(8k%6j&7k%6j&7k$5i&7k&7l'8n'8n&7m"2i%7l':m):m*=m*R?S?S>R>R?S@T@TAU@T@T@T?S@T@T?S@TBV@T>R>QS>T=T>S>S>SQ?T?S>R=Q

R?S?SDSBS?T@T?SAT=Q>QR@S>Q@T?S?S?R>RAU>TAU?S@UAUBV?T@S?T>R>R?S=Q>S=R>S=T=R>T?U>URAT?T?T>S>S@U?T@UBV>S?ST=SSBTBT@RBTBTDVCVFVFWEWLXJ]M^M^M_NbPcQdSeSeXjXjZl\n^p`sardugxfyhzj{k|k|n}j|i{i{i{j|h~h|g|f{g|i~izmml~nmn}n~m}m}n~oq~q~pmnljnmonmmmk}k~lo~n~nn~poqq~utttoqqnnmmk}mj|ii~kjjlmnl~nqpqoosqrvvwy{ӻǫ˷ӹϻ̽ʼûÎē”ĕǕȒĔ–ĖřǕ“—Ǖ—Ǚʘɘɖ˜ġ̪ѯԮ԰ײصٴسطڶضԵζ˶Ǹͼո׹۹ܺߵ߶߽޻߾ٿۿۼ׼ھ۾۽ٺԹڷٷ۴صܴٷݳ۹ڹܼ޸ܹ۱ٱݯڱݱ߱ޮܯݮݯܱ/?^0?]0=]1>^1>^3?_4>_3?^3A^1@]4@^6A_4A\3A\3B\5C]6@]5A_5Da3B_3B_6C`7A_8Ca9Eb:Db7C`7Eb6Da8Fc8Gd:Gd:Ec;Fd9Db:He8Gd8Ge:Hf8Gd:Hc:Hc9JcNh>Ng?Oh@Pi>Ng?Ng>KfBOiCOgBOfBNfCOgDPhDPhCPgBQfAPdAPc@PbBQcDReCRdESeHUgFSfGTgJXhIWgKViLVjMXkMYmNYnMXkMYlOZoOZlOZlP]nN\mQ_rS_rT^rT_sWauZauZav\bx^cy]by]dw^ex_gz^fy]ex^gx^gx^iycl}ek~`j}coalalbj~_gz`gzah|cjgnhognmtqxpxpykringjnostyzz{º~~zx}sz}sy~ou|nu|irxou|qv{ux|uz{t}}t~u~{pxzkuuisvgrvbkrZenXbiS]gQ[dP\eO[dJYeHVbEUaBR`?P^=N];L[:J[:I]9H^9I^7H]6EZ7G]6E^6C^9C_:C`7A^6B]6C_5B^3A\5B_4B^4B`4Aa4Ab2Bb2A`3Bb4Ac3@b3?b3@c3>a5>a3<_2<_2>`1?a/;^-=]*9[*8[,:],:]*:\'7X)7Z'6X(5Z'3Z(4Z(4Z&2Y&2Z%3[#1Y#1Y&4]"/Z&4_$3\#2[!0\!0\-Y0[!0[/Y /[!0["1\!1\"1\"2]#2\#2]"1\"1\!0[!/]!0]"0`!/_#0`!-]".^".] .]!/^!/^ .] .] -]".^!-]$0`".]!0_!/^-\!/^!/^!/^!/^ .] .] /^".a".c!.b!.b -a".b"0c"2e.a"1e 0d.a$1c!/b 0d /b#0d#0d"/c#0d"/c"/c$1e$1e$1e#0d%2f#0d&3g'4h'4h&3h&6i%5i$5i%6j"3g%5i#4h$5j&7m"3i$5k"3i$4k"4j$7l&7k(:n*;n(;l(:k);m';n*:n(9n):p(9o):p):q(9q+s*?t(;t(=q+?t,?t.>u/@u.?x-?v/@w/@u.?t/@v0Aw0Av.?u/@v0Aw1@z0Az0Ay0Aw1Cx/Cx0Dy1Dw2Cy2Cy2C{1Cz2C|3D|4E{4E{3E{2H}4I}4H}3G|3G|2F{5I~4H}4H}4H}5I~3H}4G2E}3F~7H}5F{7H{5Fz4Ez7H|5Fz4Ez1F{2F{1Ey2Cy4E{2Cv1Cu2Dw0Dz1Ex0Dw3Fy0Dw2Fy3Gy1Ex1Fy2F{/Cx1Ez/Ey/D|,By-Dz.Ay-@x/Bz.Ay-@x-@x+?w0D{/Cw/Cx0Dy1Ez0Cx0Ey0F|0E|0E{/D{/D{3F~2E}3F~1D|1E{0Ey0Dx2Bx3Cy2Cy1Bx3Dz0Bx/Dy1F{2G|2G|1Fz/Dy1Ez1Fz6F|4E{5F|5F|4E{3D|4E~4E2F~2F~3G2E}1D|0C{0C{1D|1Fz5E{5F|6G}4E{2Cy4E{3Dz3Dz5F|4Dz4D{2F{3G|3I~4I~5I~6I~5I~4H}4H}6J6J7J8H};K6G{6H{5J|8L4H{6J}6L}5L~6K5J5J5J6L6K8M8L9M8M8L7K9M9M:N;O:O=M=N:O;O:N;Q:O:NR>R>R=Q=Q?S@T@T?S@TAU>R?S@T@TBV@TAU@T=QS;P?T?T=RSR=QRRT>U?V=R>SSAU@T?S@T@U?S?S

R;Q=RR=QS>S=R=R>S>S@U@U@UBX?U>U?U=R;Q^3>^4>_4@_2@]0?[2@\4>Y3?Z2A\1?Z3A\5C^3@\6@^5@^6B`7C`5Da6Ca:Db8Db5C`8Fc8Fc7Eb8Fc9EcNh>Nh>Ng?Oh=Mf?Ng>LfAOiAOhCOiBNhCOgBNfBNfBNfCNg@OdBQe?NdBPeEPfCPeBSgEUkDSgGTfFSeGTgHUgJWjJVmJVmKWmLXnJWkLVkLWkLXlKXiMZmP]pP]oP\pO[pT^rQ\pS]tU\sV^uW_wU^tV^sW^sU]oW_qW_qV_oW`qZbsZas[btZbvWcwYbwX`uX`sY_rY`sZatZat[bt_ey_e}cicjglelbj}bg{ai|`gzfljpmqqtsww{{}}}Ǿļſ¼|}qzupvvlttiqsenoeooitsdoo]ilZgiUagM[fKYdDR_BP_?P`AQc=Na=Na=M_:J]:J^;Ja=Lc:Hb;Ic8F`8G^7E]8F^6E^6D_4C^6C^5C`6Da5C`5C`4B_4Ca3A_4A`5@a7Ae8Ae5>b5?b2?b2@b0@`.>`.<`-;^+9\,:]+9\+9])8^,9]-:]+8[)6Y)5\(5[%4X&5Y&4Z&2[%4[$3Z$3[#2\"1Z!0Z!0Z 1]!0[!/Z /X"1Z /X.X /X 0Y"1Y-W!0Y"1Z"1\!1\"/]$2_#1^"0]!/["/]!,^!/^"0_ .] .]#1`!/^".^".^#/_$0`"/_"-]!.]$2a"0_!/^ .]"0_!/^!/_"0a .`!.c!.b!.b"0d!1d 2d0b0b0b 1d!0c/_ 0c!0c -a!.b#0d#0d"/c"/c"/c#0d#0d$1e$1e$1e%2f&3g$1e%2f#3f$4h&6j&6j&5i$4h#5h#4i#4j!2h#4j 1g"2h!2i 3i%6l*;r(9m'9l&7k$7j#7j$5i):o):q(9o'8n(9p):r*;s):r(9q&:q';p*>s';p(s,w->v0Ax/@x.?w.>w.@w0By1B{1By1Bw/Av.Bw/Cx.Bw2Bx0Aw/@x1Bz/@x1B{0Aw3Cy2Cy3Dy2Dz2G{2F{2F{2F{1Ez1Fz2F{1Ez1F{3H}2F{1Dz3E~5F{4E|6Gz6G{6G{4Ex3Dx3Dy1E{1Ez1Dy1Bx2Cx1Bv1Au2Dw0Ez0Ew0Ew/Dw0Ew1Fy0E{2F{3Cy3Cy0Bx0Dy/Bx-Bz.C{.F|-By-@x-@x/Bz-@x.Ay,?w-@x0C{/Cx-Av-Bv-Bv-?x-@x/B{.E{-By/Bz0C{0C{0C{/Bz1C{4D|2D{1B{0Aw2Cy2Cy1Bx3Dz3Cy4D{5F{5F|5E{3E{2Cx3Dz5F|3Dz3Dz4E{4E|2C{2C{3D3D{4E}7H0D|1D|1D|0C{1C{4Dz4Dz4E{4E{5F|2Cy2Cy5F|5F|3Dz4D{3Dz2F{3H}4I~5I~4H}6J6J4H}5I~5I~4H}6J4H}5Iz6Fz6G{7G{5J}6J}4J|5K}7K4H~7K5I4I6K6L7L6J7L:L;K8L9M8L9M9L;LR=Q>R?S?S>R?S>R?S>R?S>R?S@T>R?S>R;PR:N:O9N;OQS:OR@TS>S?TT=S>U;S;S=S=S;P:O8M7L7L6K4L4L5I5J5J5J5J6K7I6I7J4G5H4G6I4H~4I2G~4I2G~0E|0E|3H3H2G1F1F1F4I2G~1G}3E}3H2G~1F}1F}3H1F}2G~2G~4I4H3G|6J6J4H}3G|5K4G{6I~1G|5I~2G|3H~5I~7J3H}3H}5J4I4I4I5J5J5J3H4I2G~4J3K2J}3N4K{5L3J~6I5K4I~5J6K7L7L7L5J5L7L5J9M6K7L5J7L7L8M5J9N6K6K5J5J4I~4I~5J4I4I4H6J3H1F}3H2G~3H1F}1F}1F}2G~1F|1C{1Ez4G|3F1D|4G/Bz0C{1D|2E}1D|/Cx1Ez.Cx-@x.Ay.Az.>w.?u/Av/@v/@v0Bw2Ay-Cv-At/Dw-At.Bu/Bu-At.Dv,Bt+As+Bt,?r,@s-Au.>r.?t0Bt2Dv0Bu/Cu/Cv/Dr.Cr/Ds0Et2Fx0As1Cu4Fx1Cu0Bt2Dv2Dv/As0Bs2Dt1Cs1Cs3Eu3Eu3Eu4Fv2Dt1Cq3Es4Fw4Fv4Fv7Hx4Jx1Fu4Ix3Hw3Hw4Ix4Iw5Jy5Ix1Gv3K{1Jz2Gx2Iy2Hy5K|8K~7I}3Jz5L}5L}6L}8N5K|6L}5L|9L~;O:N;O9M>R

R@QBTBTASASCVDVFYDUGXHYIZM^L^M`M`M`QdTgUhTfYk[m\n\nasbrctctgxhyfwhzizizl|h{hzj|mk|izg}g|f|h}k|l~mk}mml~mok}l~lm}p}p|sk{ojjoi{k}l~k}k~m}m|n~m}j{q~p~p~n~ol|qsq~ontpppol~ol~jkji~jh~m~nnnnppqoqpqqrux{|}{~ŠȾ˼ڼͿ̽ᨨž­ūŧţǩάϲӱϮγհձֲײذֳڲس۹޶ܸݾؾ־ۼߺ߻޼޽ܽܿڻټؽܼܼ۸۸۷ڵܶ۴ڴڳ۷ظںܵصܰدٰۯۮڮ۪۩٪گܮ0?^1@_.<\1=^1>^2?_3>_3?]1?\6@_5A_2?[0?Y2?[2A[2B[5C^4B]6@_6A_6A_3?]5D`7C`7C`5Da6Da5C`6C`6Da6Eb5Da9Hd7Fc9Gd8Fc9Gb:Hc:Hc:Hc;Id;Id8Hb:Jd:Jd9Ic;Kd:Id:Jd:Jd=MgNg=Ke>Lf=Ke>KeALeBNeAMe?Kc@Kd@Ld?Nc@Od@NdBMcBNeBNfBNdBNcBNeBNeCOdESdERdFSgGSjHTjHTjJVmHUiJWiIVjKXjLYiMZkLYlJXlP[rN[qP\rO[qNZpS]sU\tT\sU\rPZnQ]pQ[pTZsU\rU\rS[pW^tX]tY^wZawX_rV^qV]oW^pW^qX_qW^qX_sW]sZ`v\au^cv^ex_fy`gz^ew^gv[du^ex^fy`h}dlfmhokrnuv|z~~~}}||{|zzzxw|~}ݼmxycnn\ijTagP]dLZeIVcES_DQaAN]AO_>N`@Oc>Nc?Nd=Ma;J]=L`9H[w->v->v+v,=u->u->t,=s,=s,@t-Aw0?y->v-=u/?w/@x0Ay.?w/@w->t/Av1Ax2Cx/Cx.Bw/Cx3F{3D{0Dy/Cx/Dy.Cx/Cx1Ez4D{3Dz3Dz4E|4E{2Cy3Dy3Dw3Dy1F{/Cy2Cy1Bx0Aw0At2Bu4Dy3Dz3Dw2Cx2Bw3Cw2Dw3Dz3Cy1Bx2Cy1Bx0Ax2G|2F{,By.C{/Bz-@x.Ay+>v.Ay0C{/Bz0Bz1Az0@y0Ay2Dz,@x/Bz/Bz/Bz0C{0Bz0C{/Bz.Ay1E}/Bz.Bz/Bz1Bz2B{2C{4Ez1Bx0Aw4E{4E{4E{4E{4E{2Cy4E{2Cy2Cy3E{2Cy3Dz1Bx5F}3D|3D|4C4E}2C{1Bz3D|0C{/Bz0Cz0Cz1Ey0Dz1Dy4Dz5E{3Cy3Cz4E{4Dz4Fz2F{1Ez1Ez2F{4H}4H}4H}5I~5I~5I~4H}6J6J4G|3H}5I~3F{7H|7G|4I{5I|7M~5J3H}4J~2J}4K~8L7L7L7K8K8L9M7L9M8L9M7K;N;L:K;L;M9M9M:O;P:O:N=M=N>N=O;P;OR=Q>R@T=R=Q>S>R>R>R=Q?S=Q=R>S>Q:PS=R;P;P;P:O9N9N9N:O;P;O;P;OS?U@V>T?V>Uq)=p+@s+@s,Bt.>s.?q/@r/@r0Bt)=o/Cu.Dq/Ds/Ds/Dr-At,@q0Bt2Dv/As/As.@r1Ct/At1Cr1Cq.@o2Dt3Eu1Cs1Cs4Fv2Du3Er4Fs2Du3Eu5Gw5Fv2Hw1Fu4Ix0Et5Ix4Jx6I|4Jw6Kz4Ix2Iz3Iz4K|7J}8K~8L~8L8M{8M7J|5I{7M~5K|8N9N:M=Q]0?^.=\0?^1>^0=]1>^3@`4?_4@_4?\2=[3B\2A[0@[2B]3A\3A\6A^6A_7B`4B^3A^4B_4B_4B_5B_3A^5Da2A^4C`5Da8Eb7Eb7Eb7Eb7E_9Gb7E`7E`8Fa9Ha6F`7G`8Hb7Ga8Hb9Ic7Ga9Ic8Hb8Hb8Hb8Ic;Kc=Ke:HbJb?Kc?Lb=La@NcALbAMeAMfAMeBNgCOgBNeCOdBNdCOfCOeGSiGSiEQgFRhHTjHTiHUhIVhJWgKXjKXlLYmKZpN\qOZpO[pN[pP[qP\rR]rQ\rQ]sQ\pR]qQ\nQ\qS^tT_uQ\sT^tRZrU\tX^tV^pU]pU\rV[qV[qU\qW_tU]rU^tZaxX^tV[sY_tZ`sX_q[bt\dwZav\dyYe{[f|_j`k}`k~coeqluoys|s~q}lwmys}q|s~r}q}stvwwz~}~~|||{}}}|~~~vwnysfpo`jn\elT_gO[dMZeIVdGUdFScASbARb@O_9JX?O_>M^?L_>J_=Ib:F_=I`;H``2>`3<`2=_0>_.<_/=_.;]-:\,9\+8\+8[*7Z+7['6\(6[(6[(5Z&3Y$2[$4]#2["0W#1Y$2Z$2Y$3V$3X%2Z#1W!/W!.X!/Y!1\"0\!/]0X!/[!0["0\!/[ .\ /[ .["0]"0]#1^#0]#0]#0\"/\#0\#0]!.[".^#0`.\!/^"0_!/^!/^-\-]#1b!/`.a/a/a/b!2d 2d1c2d0a!3b /a 0a 0a"0a"/`!0b#0d#0d#0d#0c"/c"/c"/c#0c"/c"/c#0d"/c$0e$1e#3g"2f"2f"1f 2d 2e!1f!2g 1h!2g 1g!2h"4j 4i"3g%6j%6k%6j#4h#4h#7j&6j&7k%6k%6j$5l$5k$5k%6l&7p'8o$8m&:o&:p';p)=s':p(;q';q(v*=t*=u/@x-?w.=v.?w.?x->v-?w.Cw/Bx-Av/Bx0@v0Aw.Bw/Dy/Cx0Dx2Bx/Aw2Cy2Cy1Bx3Dy2Cy4E{3Dz2Cy3Cz3D{3Dz/@v0Ax1Cx3Dz2Cw3Dx2Cw3Dx2Cw1Bw3Dz4E{3Dz1Aw2Cy1Bx1Bx2Cy0Ez1C|-@y.Ay.Ay-@x.Ay.Ay,?w.Bz.@y0@{/Ax1Bz/@x/@x/?w0@x.?w.Ay0C{-@x/C{1Ay2C{1Bz0Bz0Bz,@x-?w1Bz2Cx2Cy2Cy3Dz4E{4E{3Dz3Dz2Cy3Dz2Cy5E{4Cz6E|7F~4F{4E|3D|3D}6E4E|4E}6G2C{/B{/B}2E0C{1Ez1Ez0Ey3G|1Ez/Dy1Dy0Dx0Ez0Ez1Dz1Ez1Ez2F{3G|2Ez3G|3G|5I~4H}5I~5I~4H}6J3H}5H}5I}5Fy6G{5I|6I}4J}3G|6K4I~4K2J~5J5I6K7L6K9M9M7K:N8L8L8K9L;KN?P?PR>S;M;N=PR=Q>QP;Q;P;P;P;P:O;P:O9N9N:O8N:L;N;N;O:N:N;O:N=Q:N:N:O;R8M9N9N:L8K:M:M:M;M9O9M8M7M8O8L9M8L9N7L8M8L9N;P9N8M;P9N:OQ=Qu.?u.?u1Aw,Av-@u,Ct*@r/Bv.Bu+?r/?s.?s/?s.?s,r+p,@r-As+?q,@s-@r,@s-Dq.Cq,@s*>p,@s.?q1Cu1Cu1Cu1Cu1Cv0Bq0Br0Bo/Ao2Dt0Br2Dt1Cs3Ev3Eu3Er2Dv2Dt5Gw5Gw4Gw3Hw3Hw2Gv4Ix3Hw5H{3Gy4I{4Hz6Lx8K~4I{4Hz6J|7K}9M9P}:O~9K~:M7K}8O8N9P;N;O9PR@Q>OR=S=Q>SARATBREWFXIZGXJ[J[M^M^L`NaPcReTgYl[n[n`s`r_q`r`revgxfwfwhyhzkxhvhvfygxgxizh{kyl{i|j|i{j|l}l|l~i{i{l~mk}k}l~m|m}l}o{n{k|kzk~j|j|i{j|hzi{i{j}k{l|k{m}llm~m~ml~noopnmoonmmoonli~h}mkl~mnpqpqptrrqrqqrsxwxzy{{{|~}~}{ƭп¿ļ¾ž¸Ĵëįɵ­ɲдӲԷ׷ڵڴ۳ٴ۶ݴߴڶݸ߹߸ݺݼܿڽڽٻٺڹ۷ڷڱڭ֯ײڲײڱٵڳٳܰرذڮڪڭݫܩܫݩ۫/>]/>]-<[0?^1=^0=]2?_2>^3>^2=]3>\3?\1@Z-Ib>IbHa?Ic@Kb:G`9E`;Fb9Db7Da7Fc6Ea5B_5C_4C`5C`4@]4?]5@^4>^5@a2=]2=^/<_0>`/<^.;]-:]-9\.;^+8Z*8]*9^*8](6[&5Z&5]&5]%3[&3Z%3X'5Z(6Z&5Z%3[$2Y#0X".Z#/Z!/\ ,Z /Z1Y 1Y 0Y!0[!0Z"0\!.\ .\ -[ .\$1_ .[!.[!.[".[$1^#0]".\!,]"/_ /^"0_ .]-\ .] .]!/^ .^/a.`/b /b /c 0d0c!2e-_ 0b.` 2a 2`"1`"1`"0a"/a"/d"/d"/d#/d"/d"/d#0d"/d#/d#0d!.b#0c%1e#1d 1e"2g#3g#3g"3f!3d 1c 1f!2g 1h!2h 2h 1f4e$5g$7g#5g%7i#4f"5g!6i%6j$5i%6j%6k%6l%6l#4j"3j#4m$6n!6j&:n%9l&:l(v/?w->v+?t-Av-Bv1?v0Bv,@u,@u-Av,@u2Bx2Ax0Aw0Bw1By3D}2C{2Cy1Bx0@w2Cv2Cw2Cy1Bx0Aw1Bx3Dz1By2Cw3Dx4Ey0@t2Cw2Cy1Bx0Bx-Bv2Cx0Aw0Aw3Dz1Cx-Bw.Cx-?y,@x.Ay,@x-Ay-?w2Bz/>w.=y0?z0Ay/@x/@x2C{0@y3C}-Ax.Ax/Bz1Ay0Ay/@x/@x/@x-Bz0C{-@x.@x2Cx0Aw2Cy3Dy2Cy3Dz3Dz4E{1Bx2Cy4E{4Dz5D{3By5Dz2Cy5F|4E}5F5E4D|5E}2C{1C{/B{0C}/C~1C}1Ez0Dy0Dy1Ez1Ez0Cx-Bw-Cw.Cx/Dy/Ey2Fz0Dy1Ez3G|1Ez1Fz2G|3G|3G|3G|5I~5I~3H}3H}4K6H}7G}6Gz5J|5I{3J{3H5J4I~4K3J~5K}6M6M7L7K8L9M8L9M9M7K5H6G7GM=L8L:N9M:N=QR=OQ=Qr/>r0O~U>Q?SASBS>S>S;R=S=S?SASCTHTHUKXI[M^M^M^O_NbObSfUhYl]p[n_r`s_scsasbueueudugxevhzjwhvjwiwgzgyizj{k{k{l{hzj|l}l|l}izk|i{j|k}l~j}l~l}l}o{mzn{m}j|i{i{i{i{i|izizgxhyk{kzjzl~j|l}hjlnmnol}nm~oomonm~pm~h~jh}kkm~m~onoqrrssrqqqprtuxz{x{y|||{~}}}}ºźȿǽ¼½ſ¿þ¼ʻɿ˴ųȫð¼͵ȬɲұӱԵַݶ۷ܶ۶ܵ۷ܵܺݶ۸۹ܻ޼߼ػչԷչڸ۵ٱװڮرڴگ۱۰ڵݱٰرگڭܮ᩵߫਷ݫޫޭ.=\-<[-<[-<[/=].>\.>\0=\2\3>\2=Z/=X/>Y/?Y6?]3?]0>[2@]2?\3>\4?]2?\2B\0@Z2B\1B[0@Z0@Y1AZ3D^4C]5C^7C^7B^5A]6@\6B^7B^6A]5C^7D`4C`5Da4C`3D^5F_8E`7Fa8E_8F`7E_8F`:Hb:Hb:Gb:Hb:Ha;I`=I`=H`=Ia=Ib>Jb>Jb>Jb@Ld?Kc@Ld@Md?KcBNfANf@NeBNeCOeBNcCNaDOdDNeFRhHSiFQgGRhJSjKVlIViJWgJWhJWgKWjLYkN[qJ[pKVlNYoNZqNWqQXpQXpOXmMXkOZmOYnOZoO\oKXjLYjO\nL[nQ[mQYlPXlRZoQYqS[rQZpSZrU^tS[sS[rS[pS[nU^pP\nR]qQ\pX^tY_tV_tXbvYdyZf~[i|_k`k`jbmbndqcpcqcqbpbngtgsivhuiukwlyq|t~vxxyzxxuyyxzyvwwwxxwyz{yz{{w{~~}~}~~u~}luwepu^ioXflQafL^eJZaIV_GR]ER_IReHNbGOdFQdDObFMbDK_CKaCKbCJbAK`?Kb@Ld=Ha;Gb=Ic]4?]5@`3@a3?`5>b2;_1<`,;^.<_/<_/<^-9]*8^*8]+;^+8])8](9]'6['5\(7^(5^)4[%3Y(6[$2X%5["1X!1Z /X-V .V".Y".["/]!/]"1^#1] /Z#2]#2]!0]!/\.[ -Z!/\.[!.[!/\ /[!/\!/\"0] .^-\-\!/^,[ .]!.]!.]!/^"0a#1a"/`#0a"1_"1`!1`2_#2a#0_$/_#0_!0^#1`$2c$2c$2b!/a$2d$2c#1b#1b#1b#1b#0c"/e#0d"/c#/c#/c 0c!1e"2f#4h 1e!2f"2f 1e 2e"2f!2f#3h$5j$5j%5j"4h#4h%5j$6j%6j$5i$5h#4k$5k#4j#4k#4l%6l%6l(:o%9m%9l';n';n(r*>s+?t+?t,Au*=s*>s,@u-@u->t.?u1?v1?v1@x2@w1@v2Dz0Aw1Cy/Dx1Bx/@v2Cy1Bx2Cy2Cy1Bx4Ez4Ey2Ex0Ex.Cv0Dz1Aw1Bx/@v0Az0Bz1Bw1Bx2Cy3Cy/Av0Ax-@x/?w/?w1Bz0Ay-@y-@z-@y/@w1Ay/@x/@x/@x,=u1By.?w0?{2@|2A|/?y.?w0Ay1Bz0Ay0Ay0@x1Bz2C{2C|/@x/@y2C|3Dz2Cy2Cy2Cy2Cy6G}3Dz6F}7E}5E{2Dz3Dz4Dz3D}1E|.By0Bz0C{2E}1D|2C{2Cy4D{2Cz/Dx0Dy1Ez0Dy.Bw.Cx-By-By.Bw.Ax.@y2D}0B{0Dy1Ez1Ez5D{2Cy1F{1Ez2F{3G|4H}4H}5G3G|6J3H|5H{4H~4H~5I4I4I}5J~2J~3J~4I~6I|5I{6J}8K6I|7K}6J9M8L~7K~5I6I6J5J~7K5J~6J7K9J~9J~7I}9N9M8L9L;N8L8L:N;O:N:N;O;O:N:N:N:N9M:N;Or.?s/Bv.?s/Br/As0At.As.?q-@r-?q-?q+?s+?r*>p,@r-As,@r-As,@r-Bt.Bt.Ct3Ds2Ds4Ct3Ct3Ct2Dt2Dt1Cs2Dt3Et1Cp1Cq3Es2Du0Hv2Fv2Gr3Hu6Gu8Jx9Ky7L{7Kz6Kz4Jx5Ky7M{8M{7L{7M|8L|6M{9O}7N|9O~:P:P?Q>O:Q>S=R>S?T@U?U@U>S?U?T?TZ0?\/=Z0>[1>[3=[4=\1?\0AZ1A[1A[.>X.>X1A[/@Y0AZ2B\3A\6D_6@\4?[7B^5@\6A]5A]4B]4B^3B`5E`4E^5E_5F_7D`8E`7E`7E_:Ha9Ha8F`9Gb9Ga8F`9Ha:H_>IbKe^1?_.;].:].=^.<_,<]+;\-<]-;^+9\)7Z*8Z&4Y(6[)6\)7Z%2V)6Z&3Y$2W$3Y"1Y!0V"/U"/W"/Z!.Y!1["1["1Z"1X#2[#2Z"1Z"1]#1]"1\!1\"0\"0]!/\!.\!/\"0]"0]-] .],[-\-\-\-\ .]!/`!/`!/` ._"0`$2`!/^"0_#1_#0_&1a#/_".^"1_#1a"0b#1b#1b"0`#1_#1b$1c#1b ._!0`"/a"/e!.b#/c"1d 0c 0c"2f 0d 1e0d 2f#5i"3g"4h&6j 1e 1e%6j#4h%6j#4h#4h$5i%6j%6j"3g#4h"3i#4j$5k#4j#4j#4j$5k'8n&8m&:m&:m%9l&:m#7j$8k$8k#8m$9n$9n$8m$8l'7m%6l):o*;p(:o't/>u1@w0@w0Bw/Av0Aw/@v.?u/?v0Aw0Aw0Aw2Cy2Cy1Bv1Bv2Dx-Bu1Bx.@v/Av1?v1?v0?w0?v1Cy0Bw/@v0Aw1Bx/Bz2Bz1Bz1Bz/@x+?w,?w-?y*>u-?w0@x/@x0Ay.?w.?w,=v/>z/>y-=x/?z.?w/@x/@x/@x0Ay1Bz/@x0Ay1Bz0Ay3D|1By2Cy3Dz2Cy2Cy2Cy4E{4E{6F|7F}4Dz3E{4E|3D}3D|1D|0C{0C{/Bz.Ay/Ay5F}4E{3Dz1By/Dy.Bw.Bw0Dy0Ez-Bw-Bw.Cw/Cw.Bw0Dy/B{0D{1Ez2F{1Dy3Cz/Cx1Ez0Dy3G|3G|2F{4H}4I}5I~3G}3G|5I{6J}7K6J4I}2G|2G|2I}2I}3G}4J{3J{3I{4K|7L~7J7K5I~7K8L5I~6J6J5I~3G|6J7K7J8H9I;K8L8L8M8L9M8L:N8L9M9M:N:N9M8Lr0Au/@s0Bs0Bt1Cu0As->p.@r0Bt/Ar-Bu-Au-As+?q)=o.Bt-As,@r,Ar-As0Dv1Ex3Et3Et4Cu4Ct4Fv3Eu2Dt2Dt4Fv5Gt6Hv5Gu5Gw5Gw3Ix6Kz5Mx8Iw9KyO>OP>P=S=S>T>S?UCS?U=S?T?S>S=R>S?PASEUFWFWGXGXI[O]K\O`RcQcSfSfYlZm[m[o[m\n^p]o\n]o]o]o_q^o`qarcubsctgshufxizk{j{j}k}hzj|hzj}k~j}j}k~gzi{hzh{kzl}m~l{l}nyk|jzh{j|gyhzdvewgwbydzdzg|f{izhzi{j|i{lil~mn~om~iij~ilkijjjn~ii~jlljol~qoqqqqnrrtsqqsuqv{{xu{}|z{~~}~ÿʻ±ëշê­̶ض۶ڴڴ۲߲ܷ޸ݶݶ޶ڻ¿޼ܿտּֿջٹشص۱ۯذڱհլذگٲܲ٭ۭک۬ܪ۫۩ݨݪᦳ,;W+:V+:W.=Y-[1>[0>[/>[0>[0>[/=[-[0=[0>[1?\/=Z/=Z/=Z1?\0>[3@[0?Y1?Z1>Y.=W1>Z0=X2@[1A[1?Z5B]6A]5@\7B^2=Y4?[7B]4C]5C_6E`4E^3C]4D^7Ga4D^5F_7F_7D_6D^7E_9Ga8F`8F_:Hb:Ha:H^Jb>Jb?Kc>Jb?Kc>Jb>Jb?Kc?KcAMe@LdANe@Pg?MdBMfEPhFQfFPfEOeEQgCPfEQgDPfHTjIUlIVhFVeIUgHUfHSeITgIUiJUiJUjLVkKRkJQkKSlKSjKSiLTiMUjKTiJUiIWgKXiIVgGTdHReLVhMUhMUkNVkNVlNVnNVmQYnOUkSYoS[oOYkOZlNYmP]pR]tR^sT`tWbvWbuXcwYdxZdzYeyZgx]hz[hyYfx[h|\i|\i|^k}\j}`m_l|`m~`m~`mcpfsfsgulxnxozq}rrsvursvtvuvutwvvvtuutssuvvvuvxvxxz|}um{~dpv`jt[dnYclQ^gO]hMYeKVdOXfLUdMUeLSdJSeJSeJSeIRdFRcDPaCPcCPbBNc?La@L`@L^AK^AK_ALa@Kb?Ha=F`>Hb>Jb;G`8E^5D^6C_7B^6@]4A\1?[4B]2@]0>\1?]/>[0@]/>^/>^-=[-<[+:[*:[-=]+:\*8X'7X(7Y&7W(6Y'5Y(6X&5Y&4X$2U#2U&4[&5]&6]$3[$2Z&7Z$4W&6\%4\&5]$3[$3\#2\%3^"/]#0^"2\"2\#1_"0] .[!/]-\ .] .]-\ .]!/^!/a!/`"0a"0a ._"0_!/^"0^!/_!/_"/^"1`"0_"0_$2a!/_"0`"0_#2`!/^!/^"0_"0_!/a#1b#1b"/c /b 0c 0c 0c 0c#3g#3g!1e.b#3g#2f#3g 0d!3g!2f"3g$5i"3g"3g$5i$5i#4h$5i$5i#4g$5j#4k$5k#4j#4j$5k%6k$5k'7n$7k"7j%8k$9l%8l&7k$5j%6l&7m'9o'7m'8m(8o&7p'8o):s&7o(9q(:o(:o(9o+r(;q)u(=t't-u.=t0>u1@w.>t.@v.@v-?t/Aw.?u0Aw1Bx1Bx1Bv0Au0Au.?t.?u.>t/=u.>u/>u1@w0?v0?v2By0Bw2Cy1By2C|1Bz1B{2C{2Bz0Ax,@x+>y*x,@y*=w->u.?v->v+=v+>y*>x*=w/?z-?v->v/Ay0Ay.?w1Bz/@x/@x/@x/@x0Ay1By1Bx3Dz3Dz4Dz3Cy2Cy4E{5E{5C{5D{3Dz1Bx3D|1C{/C{-Ay0C{/Bz0C{3C{2Cz2Cy3Dz2Cx/Cx/Cx0Dy1Ez.Bw.Bw0Dx1Ez.Bx-Av0Dy1Ez0C|1Ez0Dy0Dx3Dz0Dy1Ez1Ez1Ez1Ez2F{3G|2F{5I~3G|3Gy3Gz5I{6J5I~4I~4I3H}1H|1H|1I}3Iz3Iz4J|5J3H|6K4I~6I~4H}6J4H}4H}5I~6J3G|4H}3G|3G|4H}5I~4I~7K7K:N8L8L8L7K7K9M9M8M8L8L~7K8L7K8M8L8J7K8K9M8K9K9L8K8K:M9L8K8K6I8K6K4I2G~7L4I3H3H5J4I1F}2G~3H2G4I7I7J5J}6J6K8I~7G~6H6I5I6I7J7H5E}4G5H5H6H5J6K7L5J4I3H3H6K5J8J7I5H5K6K4J5K9K7I8J9L7J8K8J9J6K7J6I7M6K5K5J5J7L7L5J6K5J6K5J4I~6K5J5J7L6K6K6K7L6K7L7L8M9Np+?q-As.Bt.Bt,As0As-Bu-As/Cu/Cu1Ew3Ew3Dt3Du2Dt2Dt3Eu4Fv4Fv6Hy7Iw5Gu9K|8Iy5Kz6M|P=P=O=O=O>P@R?Q>P?P@QASARASBTBTAR?U>S?T?T=RDTBVCUDWHYIZHZN\L]N_O`RcTcViWjXkZmZmZn[mYk\nZlYk[m[m[m^o^n_qararbsctctduevhxh|h|i{i{gyj{kj}j}j}h{k~h{h{hyhzk{j{i{kzl|lziyixh{hzgyhzewewgyhyi{jze|e{izi{gyj|j|j|ik|k}m~j|m~ij~kmi~g}nm~mm~iki~kjjiomppppopmrqotpssqruyyzvy|{|{y{||}{ñ㬩Ũêêʱũ¬ʶٸ۷ܳ۵۷޷ݶݷ޶ݸ޶۽ԿռӾضչٵٲذرٯٰճ۱ܱ۰۲׬٬۩جۨת٨٦ڦڦ+:W,;X-[0=Z/=Z0>[/>Z0>[/=Y1?Z0>Y1?Z/=X0>Y/>X.Jb?Kc@Ld>Jb>Jb?Kc?KcAMe@Mf>Lg@MfBMeBOgEPgCOdBNcEQfCNeGRhDPfEQgFRiDSfCRbGTeGSeEPbDN`GNbIQeHPfHOgJRiKRiJOhJOhIQiIQhIQfISgJUgHSeHUfFRdITfIUfJTgLTjMUjMUjJRgLTiMUjPXmOVkQXjPWjOXkP[nP[pQ]sL\rP^rS`sTatU`uWbuU`qVasXctWbsYfvXevWduYfx[h{[h{^j_j~_i|^i{`l}_m~`m~cpeqhskukwmwmxp|r~p~o~p~sr}s~r~q}sq~rsspqtspqp~qpo}q}q}q}q}r~ttuwwy{z||~}}|}zzyx|z||ys{~jsziq|blu_irWaiU`iU_jQ[eR\hRZfP[hPYhLVdLUdKUdIVcIUdERcCP`BO_CO_DP^CO^CO_AL]AL`BMa@Jb?K`>K^>K`=I_=IaY0>[1?\0?\/>\0>^/;\0=^0>]2?].;\1=^.<]0@`/?`-;^-;_-<],9]+9]-;`,:`-;`*;_+:^(6Y+9\(7](7_(7^'6]%4[(7_&5`%4_%4_$3_&5`'5a$1_!/\!0]"0\!/]$2b#1`"0_#1`!/_"0b!/` .]!/^#1_#1a#1b"0a"0_$2a%3b .]$2a#1`$2a#0_&2b /^"0_!/^"0_#1a!/^!/`#/d#0c 0c"1e"2e 0c"1e#0e"2d"2f"2f!1e$4h 1e 1e 1e!2f!2f!2f#4h"3g#4h#4h!2f$5i$5i$5h#4i#4j$5k$5k$5k%6l$5l%7l&6k&8k%:m$8k%9k&6k&7k%6l&7m'8n'8n%6l&7n):r):r*;s*;r*;q*;q):p(9o(9o'8n):p$5k&7m(9o(9o&8n&:r%8q(;s%:m$9n&:o&9r(;s(s':s'9r':r't,>t,u/>u,;r1Ax/>u/>u.=s/>u0>u.>u1?v1@v/@s.?r/@t.?u,=s.?u-u-u2Dz0Ax2C{0Ay2Cy0Aw.@x.?w+>v*>v*?x)>w*=w+=x-w+>x,?y+>x,=u-?v,=v.v/@x->v.?w/@x1By0Av/@u0Aw-Bw1Ez3Cz3Dz2Cy4Ez3Dz1Bx3D|3D|1Bz3C{1Bz4D|1Ay0Ay0Ay1B{3Dy1Bx1Cx/Dy0Dx/Cy/Cx.Bv/Cy/Cy/Cw1Ew1E{.Bw1Ez2Fz1Ez0Dy0Dy1Ez1Ez1Ez3G|2F{2F{4H}3G|4H}3G|2F|2Fy5I|5I|4H|3G}2Fy3Gy0Gz2I}2I}2I~0G|1H|3H}2G|2G3H5J5J3H}5J4H}3G|2F{4H}3G|3G|4H}4H}4H}3G|3G|5I~6J8L7K8L6J7K6J8L8L:K:K9K7L6K6K6I7J7J6I6J9J6J9L7J9L8K6I6I8K6I5H5H3I3H4I2G~3H2G~3H4I4I1F}2G~1F}2H~2H2G~6H4G5H8H5F~3F~4G~3F}0C{3F~3F~6F~5F~3F~2E}6I5H4G5K6K3H4I5J2G~4I4G5H5G7H4J5J4G6I5H7J8L8K8K4F7H8I9J6I5G5G6J7J7K3I}3H}6K5J4I4I~3H~6K4I~5J6K4I7M7L7L6K5J7L6K9M7N9N9N:O9N:O:O;P8L7N7N6K7L4I4I6K5J5J4I3H3H4I4I5J3H3H3H6K3H3H1F~0F}1E}2D|1E}2G~/G}0G}-Dz/F|/G|/Cz.Cz/D{-By.Cz-Cy.Cz-Cy.Cz,Ax/D{-By,Ax.Cz.Cz-By/D{,Ax,Ax-By.C{0Bz/Cx1Ez1C|2E}/Bz.Ay0C{1D|3F~2E~/E|1F}1F}/D{/D{1F}0F|3E}5H4H2H3H|4I~3H~4I3H3H3H4I~5J6I7K8L~8L~6J6J8L8L6L5J7L5J8M6K7L8M;Q=S9O8N8N:P:P9N9N7L6K7L7L5J5J4I6K7L6J6K}8I}8G|5J|4H{3J{2F|2G|2H|3G|2F{2F{1Ez2Fz.Bw1Ez0Dy1Ez0Dy2F{/Cx1Ey1Bx0Aw1Bx2Cy0Ax1Bv0Au/@u0@u.Cv/Dw.Bu-At/Cv0Dw/Cv.Bs2Cu0As0Ew.Bt0Dw.Bu/Cu-As*>p,@r0Bt/As+@r-As-As,@r1Bt/As*?s-Bv.Bt0Dv0Dv1Dw0Es0Ft3Du2Dt3Eu4Fv5Gx7Iy9Ky6Hv6Hy7Iz:K|;M};M{=O};M{>P~=P}>P~=O}=O}P~=O}?P?P@R?PARAS?QASCQ~BQ}?Q|@P@S@S@RASAQDT@SBS@V?T>TBW?UCUEXDXHYHXQ`N\M^PaPaQaUhUhWj[n[n\oZl[m[mYkZlXj[m[m\n_p^o_parar`qbsduevevfzh{i|i|h{j}i}i|h{h{h{i|j}i|h{h{i{g|jzh{j|hzi{i|gyi{hzhzewdvewewfxi{gze|izhzi{i{hzgyhzi{k}j|hzj|j|h~h}ji~jnl~mo~m~ki~kjjiomnmmpoopoqssqurqsowtxtvz||zyxx}{{}Þäåţ¤ŧȥêî͵յڵ۶ܸݹ޺޷ܷݶߴּ¿ͼͼ׿ڽؾؼӾ޿׿ֻѼչ׵ٶگٳ۱ڴٯ׳ݰڲۮۭ٬ܨ֩٨اשب۪ۧ*9V+:W,;X+:W-\.=Z0>[/=Z/=Z/=Z/=Z.=Z/=X2@[1?Z/=X0>Y0>Y/=X/>Y/?Y1?Z3>[4?[7B^4?[6A]7D_5D_5C^6D_3A\4B^5F^6F^4D_4D^6F`5E_5D^7E^7E_7E_8F`7E`7E_;J`9G_JbJb>JbJb>Jb=IaAMe?Kc>Kd=Ke=Ke?LeALdAMeAMfCOgAMeAOfAOeDPfFRhEQgDRfBRaCOaEQbENaFM`EMaGPdGOeIQhGOgFMeIOgGMdINeINhHQeIRdFQcGSeFTeHUfGRdGQdKShLTiLThLTiNVkNVkNVkNVkNUiQXkPWjLUhQ]pP[oO[sO_wO]sQ_qS`sVbtU`qXcuU`rWbtXduXetYfvXevYfxYfy[h{^j~`i`i}_k|`k}_k|cqdqgqgrjuitjvjvjulxmzmzq}p|o{o{q}myq}q~q}stq|str}r~o{o|n}m|n}p{p|p|q}p|r~r~ssttu~uuxtstuuwwvwxxzzz|{y~}ypu}mu{gnwbjv`hqZclV`iXblU]gV^jT\iS\iPZfNXeKXfKXgIVdGSbGTaEQ^HUaIVcFRbFRdEQcEPcCNcBN_@K\@L_=H]>I_=I^Jb=H^;G\:F_:F^8E^5D_3B\8E`9Ea6E_6E_3A]5Da8Fc8Eb7C`6Ea6Eb6Db4Ab3@a5Ad5Ce3Ac1@a1Ab0?`.<`/=`.<_.<_+:\,<]-;^-;_,:`*8_(8_'6]'7]'6_(7`+:c)8a)8a&5_%4_&4_%3`$2`$2_$2`$2`"0`#1`!/^"/^!/^!/^#1a#1a!/^"0_$2c#1`#1`#1`"0_!/^#1`#1`$1`%2_#/`"._$2a"0_$2a"0_#1`#1c!0d 1d 2d1c /b%2f$2e 1c#3f"2e$4h$4h"3g 1e!2f0d!2f 1e 1e!2f!2f!2f"3g#4h#4h$5i$5j#4j#4j$5k$5j%6j$5j&7k%6j&7k(8m'9n"6h$5i$5j%6l%6l%6l%6l'8n'8n'8p'8o'8m):p(9o(9o(9o(9o&7m(9o&7m'8n&7m(9o'8n'9n(;t&:o&;o%:o't-=t-;r.u1?w/=u,;r-u/=u.t.?u,=s->t,>t.=t/>u/>u/>u/>u/>u/@v0Bw0@v/@v/@x0Az.?u0Aw.?v-?w+?w*=v)?x(=v*=w*=w+>x-w*=w,>x/@x,=u-?v/@x,=u->v.?w.?w->v.?w/@x/@x3D|1Bz/?w.Cw0Cx4Cz1Bx3Dz2Cz2C|3D|1Bz1Bz1Bz0Ay1Bz0Ay.?w0Ay/@x2Cz2Cy4E{2Cy/Cx0Dy0Dy0Dv.Bu/Cu1Ex2E{0Dw2Fy/Cx1Ez/Cx2F{0Dy2F{2F{1Ez3G|2F{1Ez2F{3G|3H}3G|5I~2Fy1Ex2Fy4H{2Fy2Fy4H{3Fy0Gx/Fz1H|1H{1H{2I|1G|2G~4I2G3H4I4I5J2F{3G|4H}2F{2F{4H}2F{4H}4H}2F{2F{6J6J6J8L6J7K5I~5J7J7H~6G|6G|5G}4H6I6J6I5H7J7J6I9J:I8I4H4G4G4G4H3F~3F~5H3F~0E|1F}/D{2G~3H3H3H3H3H2G~3H2G~4I2G~2G~3I3F~5G7G~5F}0D1D2D2D|2E}2E}4D|4F~1D|3F~3F~2E}5H6I6I3I3H4I2G~3I4G2E}5G8I5G5H6I6I5H5H4G6J7K3H|9H8I8I~5I5J~5J~6J7K5I~4H~5H~3I}6K1F}2H{0Fw4J|3H~5K8I6H6H6K6K7L5J7L7L7M7M8M7L8M7L7L8M9N8L5M7N9N6K6K5J7L3H4I6K3H4I4I6K4I4I2G4I3H~3H2G~2G~0E|0E}2D|0F}1F|0E}.F|0H}-Ez-Ez.Cz.Cz.Cz.Cz,Cy/Az,?w0Bz,Bx.Cz/D{.Cz/D{-By+@w-By.Cz-By-By-By,Ax.Bz0Ey0Ez/Bz1D|/Bz0C{.Ay/Bz1D|/Bz0E|/D{-By/D{.D{1C|1E}3F~2E}4G3E}3I|3H}3H}3H3H4I2G~2G{4I~7K8L9M6J8L6J8L6K7I~4J6J7L7L5J8L:P[2@]0>[.[/>Z/=X1?Z0>Y1?Z/=X.Y/?Y0=Y2?Z4>Z6A]5@\6A]5A]4C]4B]3A\4B]3B]4B]5C]5E_4E^3C\4D]4A\5C]5D]7D_7F^7G]5E[7F]6D\8D\:F^:F^Jb=Ia?Kdu,=s.?u,=s->t,=t+t->t,=s,=s,=r,;r.=s,>t-=s,=s,=q-=s.>t.?w/?w->w/@u->t/@v.?v/?x+=u*=v+?x)x*x*=w)u->v+v,=u,=u.?w.?w.?w.?w0Ax0@x-@x/Ay+>u-Au1Bw2Cy2C|1By0Ay2C{0Ay.?w2C{1Bz/@x0Ay0Ay0Ay1B{4E{3Dz1Cx0Ey0Dy/Cx0Dw/Cv0Ex3Gz0Ew0Dw/Cv1Ez0Dy1Ez0Dy0Dy1Ez1Ez1Ez2F{2F{3G|3H|4G|6H}2G|2F{2Gy2Gz3Gz3H{2Gy3Gz3G{1Gz1E{/Fz1H|1I~2I0E|1F|0E}0E|2G~2G~0E|1F{2G|5H}3G|3G|4H}2F{4H}2F{3G|4H}3G|1Ez2F{1Ez3G|4H}4H}4H}3H|1Ez4Dz4Ez3D}3D|5G5F~5E}5F~5G3F~3G2E}4G3F~3F~3F~2E}2F~2F~5F~5F~2F~2E}2E}3F~0E|1F}0E|0E|/D{/D{0E|2G~4I2G~2G~1F}1F}0E|1F}2G3E}2E~2E3F0B|1C}0E~.D|2D|2D|8H5F~3G3F~3F~3F~2E}5H4G3F~2G~2G~3H4I3H6H3E~3F~3H4F4G4G5H3F~6J}5I}5I~6J6J5E{2H|4H}4H}2G|5H}5I6J|6J}5I|1F|1F}2G}2G|1F{3G|4I{5H~5J7K5H6K6K4I6K6K7L8O9O9L7L7L8M8M9N8M8N:N6K6K7L6K5J5J6K5J4I3H5J2G~5J3H2G~2G3H~2G~2G~1F}3H2G~0E|/E|/D{1F}1E|0F|0E|-By/D{0E}0E|/D{/C|0B{0B{/Bz/Bz-Cy,Ax/D{-By.Cz.Cz-By-By.Cz,Ax,Ax-By.Cz,Ax/D{-Aw.@x/Bz-@x-@x/Bz/Bz-@x/Az.D{-By-By-Cz.Ay0C{1D|1D|2E}2E}2D|1G{3H}1F|0E|1F}2G~3H2G|2G|4I~7J9M9M9M8L5I~8K6J7M7L6K6J7L:P9O9O:P9O8N;Q9O9O9O9N6K5J7L6K5J5J5J7L5J5J9K5I~8K|4G{4K}4K}5H{4H~5J5I~2F|4H}1F{1G|1F{0Ez0Ez3G}2F|0Dz/Cx/Dy3Cz1By1By3Dx3Dx2Cx3Ev2Dv1Cu2Cu/Cw/Cv0Dw1Ew.Bt.Bt,?r0Ew1Cr1Cs0Br1Bs-Ds.Cs0Et-Br.Cs-Br-Br/Ds/Cs,Ap1Ft-Bq-Bq2Cv2Dv/Ex/Es3Ix/Ds2Hw2Ht5Es4Ft5Gu5Gt5Gv7Iw7Ix8Jx8Jx8Jx9KzO}?Oz>O{@Q}BSBSAS~EVDVCT@SDQ~DRESDRIWESDRDRER}CQDR@R~BSAR~@QBSDUDTEYDXCWDXEVFXGXGYJ\K]M^M^O`PaOaPbReTgTgWjYlZmXjYkXkXjWiZjYi[lZk\m_parar`qbsarasdvkxkxhzgxizhyeygzexexg{i}i|h|iyk|i}j}i|j}j}j}i}izj|hyi{h{jxjzgzhzfwgxiyi{hxj{h{hxhyiyhwh|hzhzkyi|hzgyj|j|hzk}k}j}l~l~nono~nmm~k}mmononpoppqppsuprtrvquvuwswuxwwxyz|{~ę¡£¡Ģšâšƞ¤ȡŧƭȳжӸٹݺ޼̿ɼͳɲƷ߼޾ݻڿԾս׿׿־Ӹ׸ڶصܲۮײױ٭ٮ۬٬٫ث٧ة٪ڨ٩ܨۧڨ+:W-[/>[0>[.Y0>Y0>Y0>Y2@[0?Z0?Y0>Y1>Z2=Y3>Z4?[6A]4?\4B]2@[2@[3A\3A\2@Y3A[4B\3A[3A[4A\5C]6D_6D^6C]6E\6E\5D[7F]6D]9E]9E]8D\:F^;G_MdKcAMe@LdCOgAMeANcCNdBNdCNcCMaBL`DL`CK^EM_FNbEMbFMcEPdEMbENbFN`GNaGPeEMbFNcHPcGPbFRdHSdHSeIQfJQgKRgIQgIQfKSfLTgKSgMUjMUkNUjOViQXnQYoR[rP[qO\rQ]sQ]rS_qQ_rR^rS^qS_pSarR_pTasUbuVcvWexWdwXcwZdx]ez]dy_f|^g{^i{`l~am~bmfpfnfogrfrgthtiujwkwjvkvlyivlyjvititgsgshtgsdocpbocpbmam`pbqbrerfqeqfrhtkwlxlxmymynynyo{p|o|m}ppqrqrssssstuxyzz{}~~|ytzqykt}fnwcmvdlw_gr`hr_gq[dm\eoYblW`jQZdQYcS\eU^h^fpbiselviqzemw^eoYbkXajU^gRZePYdQWfQZgPZkNZhMVdLUcHTdFSbEScGTeGTeEQdHReEQdFQfDPeDQdCOdDOfDOdDOdCMeBMeCLgALf@IfAIg?Ie>Id?Kfv+t*;q+t,=s->t+t*;q+=r,=s-t+t.?x+v/?w.?w+v,=u->v+R}AQ~@Q}DS}GVHWJXJYLZKYLZIWJWJXJXJXHVHVHVFSFS~FTDQ~BSAR~BSDUDTDXGZFYHXHYLZK[O_N]LZR`M_O`N_M^NbObQdQdTgUhWjWjUgXkXjVhViYjYj[l[l\m^o`q`qarct`qfsfuiwiym|m{i{fxgxfwfwfwiyj{hyjzk|k|i|i|j}j}j}i}izh|i|j}i|gyjzizfxjzfwgxizgxj{j{hwfvgxeufvhxixh{izizgyhzhzi{gyj}jzl|j|j{nn~m~ml~l~nl~l~onq~onpotroporrttvuurwutusswvwxyw{{}–—Ùšàß ß¡ŠŠçðɳκֹټ߽߿ݿǼ½ľǽǼιǰŴȷʼڽ׿ڽ߾׾տ׸պ۷ڵ۱ٰز׮ٮ٫گۭ٨٦֭֭ׯݬت٧با+:W*9V+:W+:W+:X-Z,;V.=X-;V-;V.X.>X/=X0=X1Z3>Z4>Z3?Z0?Z2@[2@[3A\2A[3A[3B\5C]3A[3A\4B\3AZ4DY4BZ5D[5D[5D[5D[5D[6D\8D\8E\9E]:F^;G_Me=Lc>Le9Ib8Hc9Hf5Ed5Df5Ef3@d3?e1=c/>c-;`.=a+w+;s+;s,=u+s't&;r%;q)=t*:u)8t+:v*:u):q+=u)v+=t,=u,;w,x/Ay1B{/@x0Ax1Bw2Cx1Bx/C|.Cw0Ez.Cu0Cw4Dx5Ey5Ey0Ex1Ex2Fz5I~1Ez3G|3G|/Cx2F{3G|2F{3G|1Ez3G|6G}6F|7G}7H}6Gz7H|6G{4Ey5Ez6G{3I|0E{1G{.F{0G~1E~1C~0D~/E|0E}0E}/D|/E{0Fz2G|1Ez0Dy2F{1Ez3G|1Ez2F{3F|3H|1Hx0Gw/Ew1Ex3Gx1Ev1Ew1Dw0Dy/Cx0Dy1Az1Bz4C|3B{3B{4C{5B|4C|1C{3D|1Ay/@x0C{/Bz/By0Cz/Ax2Cz4E}4B{4D}1D|0C{/Bz1D|0F|0E{.Cz0E{/D{/D{/D{/D{0F|/D{1F}.D{2E}1D|1C{0E{.Cy/Ez0E.D}.D|.C|0E~-Bx/D{2D}2E}3F~2E}0C{1D|0C{2E}0C{3F~0B|0E|.Cz/D{/D{2G~0E|0E{/D{1F0E~1F}0F}3D}1D|2G{4H}4H}4H}4H}4H}2F{3G|3H}2G|3Gy3Gy3Gy3Fx2Hy0E{1F{/D|-Cz0Ez/Cx3Iz4Hz5I{5I{5I{3Jz5J}5J4I~4J6K8M6L8K8K6K5J6L7L8O6L7M7L6K7L6K6K6K6K4I4I4I2G5J2G~6K3H5G4F~2G~2Gz0Ez0E}0E}2F~/H~/H}3H3F~3E~1Ey1Dz1F{1G{1F{1F{1F{3F{1Dy/Cx0Cy0B|/Az/Bz/Az0B{.Ay/Az.@x,Aw-By-Bz-By.Cz-Bz/D{.Cz.Cz,Ax.@x-@x-@x.Ay,Ax+@w.Cz-By,Bx.@y.Ay0C{0C|1C|1G}1F}1F~1Fz0Ez1F{1F}1G~2H2G~0Fz1G|3H|1Fz3H|2G7J~8L5J6K5J~5I3H~4I5K|7N7M6M~7M8N6L~7L6L6L6M}7M~7M~6K~5J7L6I~3I~5L|5L}4J5J5J4J4H~4I5J4K|6I|6I8I8I4I{4H{5I|1Ew2H}1F|0E{0E{3Fy2Fy0Dw1Ex0Dx3Dx3Dx2Cw4Ey2Cx3Ev6Gy/Ew2Fx1Ew0Dv/Cw0Dw1Ex0Cv0Ev/Es2Gv2Cs3Cp3Dq1Cq1Cq2Cq,Cp/Dm0Eo2Gt1It/Gs/Gr0Gr/Gs1Gw3Ix3Ix4Jv3Iw7Hw6Hv6Hv7Ku8Ku6Hw9Ky9Lw;Ny8KvR}>Q}=Q{?S~?R}>Q|AR~ARET~GVJYM\M\Q`Q`Q`Q`RaO^N]N]K\L[KZKVJVIWGUGYGXGXEVDXG[GZI[HZM\O^Q^O[N\Q`O]Q_N_M^M^NaNaQdObPcTgTgThUfXiWiWiYiYjYj[k\m[l_p_p^o_parbrftftjvkxlxn}n|o{k|gxhyevgyizgyhzizizj}h{h{i}h{j|k|g|i|i|h{j}jzn|g{izgxizgxj{hyk|ixjxjwhuiwgvfvh{j{iyk}hzi{fxj|i{j{l}j{m~l}ll~ll~l~mmnnq~rnmprrqsrrsuuuuwwuvxusrqvtvvxy{}}~՘ٚęۛŸßŸğßĞ¢ŰߺԹӹټ޾ݼȻʶɹ˹ʸdzƲDZȴƿսҺӸֽԼؽؽҾ޽ӽӽӻ׸շٶشد׮رխحۭ۩٨ثܪܬѭմޭ٪֧קը+9V*:W*9W+;W)9R*;T*:T+9T/>X+;V-Y0?Z2?Y3>X4?Y1?Y2@Z6@Z6B\2@Z4A\1@Z4D[3DZ4D[6E\6E\5E\7E\7C[8C\;D_:C\;G]Jb@Le@LdAMcBNd@Lb@McBMdALeAKbCKaDL`DL^EM_DL`BK_ALbEOgDOcDOcAK^EL`GLdELcEM`GPbFNaHNbHPbJReJRdHOdHNeHRfIThIThJVjKTiMTkOUnOVnPXpOWoO[qOZpP[qP[qP\qQ^pQ^pP]mQ^nP]nS`pQ^oS_qT^sS^rX_tW_tYavX`u\dyYawXbzWcxXcxZey[fz[fz[ez\g{\f|]i|[hy\iz^k|]j|`l\l^n^n`o_lbo_m^m_n^m^l]j~^k~]j}]j}]j|_l}`m}^l_oap`o`odqhseqiugshtiujvhthwiwlxoyo{o|lzp}o}on~pqpq~rrsrsuvuwyz||~|}~~~~{zyzz{{{{~yv}v}s{pwu|x~z~y{}~{~~~z~|~z|{s|xqzxrzznuuirxcmvdmu`iq_gp^hu^gu_fs]fsZeq[drYcqXap\dsX`pW`mX`rV`qT\oS[qS\qR[nS[oPXnQXmRXoNXlJWjGUhEUhDTgCRgBPf`0>`0>`0?c+v-=y.=y-@x0Ay.?w0Ay.?w.?w.?w.>v/>u0?t1Bw0Ay1Bz2C{0Az1By0@u0Av2Cy0Aw0Dx3Gy1Ew3Gy3Gz2Fy3Gz2Fy4H}1Ez0Dy3G|1Ez4H}1Ez2F{1Ez2F{5I~3G|4G|7H~7G~8G~7Gz5G{2Gz2Hy1Hz1Gx1Fy0F{1F|2E~1D}1C}.Cy/Dy1F{1Ez2F{4Ez6Fz4Fz2G|1Ez3G|3G|1Ez3G|3G{2Fy/Dz1Ez/Cx0Dz/Cx/Cx/Ey/Dy1Ey/Cy1C|.Ay.Ay/Bz2Bz0Ay/Ay/Bz.Ay/Cz-By/Bz/C{/Bz.Ay/B{/B|.B|1C}1Bz2D|2C{0C{.Ay0Cz.Dz.C}.C{/D}.B{-Ez/D{0E|/Bz1C{0C{/Bz0Bz/E}2G.C|/D}-B|,Az/D}/D}0B~1C/E}-B{.Dz1D|1D|0C{1D|0C{.Ay.Ay/Bz/D{.Dz0E|/D{/D{-By/D{/D{-By.Cz0E|/D{1F|.Bz2E}0C{2E}2E~1G~3G{2F{3G|4H}2F{3F{1G|0Ez4F|1Ez2F|2F|2F|0Ez1Fz0Ez1Ey3Fz0Ez2G{2G|3I}3G{5I~2H}3H3H4I8L8L6J|8L7K9M6J8L7K7L8L7N9L7K6K6K7L6K6M~7M~5I4I~5J3H}4I~3H|6I6H3I~3H2G~2F~2F~/D{2G{2G{2G~/D{0E|2H|0Ez0Ez0Fx1Fy3E}4H{1Ez3H}0Ez/Dy.Cx/Dy.Cy/Dy,Ay.By.Cz-Cy.Cz-Cy.Cw,Au.Bx-E|/Cz,Ax-By.Cz.Cx-Bz-Cz-By-By-By,A|.C|-By-By,Bx,Ax/Bz1D|6F{4F|1E}1D}0Fz1E{/Dy3H}1G{2G5G}4H}4H}3F{3F1G}1F|5J3H4I4I}3Iz3Iz4J|5K|5K|4J{4I|8K~3Gz6J}4H}5J4I4F5J5J4I5I4J{3I{5J|4G}6J6I6J6J3H2G~4I5K}5K|5J}6L~5K}6L}4K}2Iz7Hz5I{2Fx3Gz4H{3Gy4H{3Hz3Gz2Fy2Fy1Ex1Fy4Ey5Fz6Fz5Hy6Iz6Hz4Ex4Fx3Gy2Fx/Cv1Fu1Eu2Gu2Hv5Fw4Ft7Hv8Hv4Fw4Gw3Es4Ft3Es0Bp2Hu3Hu4Ix4Hu3Gu3Hv2Hv3Ft6Fu6Gu7Iz8Jw7Ju6Hs6Hx:Jz:Jx9JvY.Y3?Z5A[3@Z5A[5B\2@Z2@Z6D^5D^3CZ0@W5E\4CZ5D[8G^7F]8FZ9E[:F[;F\:F^Ia=Ia?Kc>Ia>Jb@Ld@La?KaBNdALb@Lb@I`CIdCJbAI`BJ_CK`BK]DK]DM`ALaCMaDNbAK_EP_FN_EKaFMeFNcGOcFN`GNaFM`FOaGObIPdLSiIRgGRgISgKVjJUiJVjMVjNVjOWlNZmNYnMXkMXmLWkMXlMZmMZmMZjN[lMZkLYjLYiNZlNZnOYnRZoPXmU]rV]sX_uV^sUatVavWbvWbwWcwYfyXeyYfyYfyZgz[hy\iz\hy\i{`n]k~\k~\l_n`m_l]j|]k{]iz]j{^j}\i|]j}\i|^k~\i{\iz^k`m`n^naococperfseqgshtitivhxixiwlwoyp{l|o}o|nznm}o}p~n}p}uqrp~qqsutsss}uxy{}~~}|}||~~|}~~~}~}||}xyv|xz{{}}|w~y{zzu|s{ryu{muhqgp{emyelxbjxcizdj{`gw\ds`kv^gv]cv\cv]dw\cv[bu]dvZasW^qX_rV]pS]pQ]nO]nJXhHUfFSdDPeAMd=Ke:Id9Gd7Gc7Fc5Cc5Ac2Ab3Ba/>_1>b/=c/=b.=a-=c-;b-u,=u,v->v-w.@x0Ay0Ay/@x.?w/@x->t/@v0Aw1Bx/Ey1Ew0Dx3Gz2Fy3Gz2Fy2Fy2F{1Ez3G|4G|4H}4H}3F{4H}3G|3G|3G|1Ez2G|5H}7G~7H~8H|6H|3Hz3G{3Gz3Gz2Gy2Fz2F{1Ez1F}0D{1Ey/Ey0Ez2F{2E{5E|6Fy4Dy2F|2F{2F|2F{0Dy3G|2F{0Dy0Dy0Dz/Cx/Cx0Dy1Ez/Cx0Dy1Ez0Cy/B{/Bz.Ay-Ay/?w/Ay,?w/Bz,@x+Aw-Ay.Ay/Bz0C{.Ay/By.Ay.A{.@z2Bz2Bz0Ay0C{.Ay.Ay/D{-B|,Az/D}.C|-Bx.Cz.Dz0C{.Ay/Bz3F}/B{.D~-B{.C|/D},Az/D}/D},Az/A}/A|-C|.C|0Cz0C{0C{/Bz/Bz0C{0C{.Ay/By/D{-Cy/D{-By.Cz,Ax.Cz-By.Cz-By-By,Ax-By-By/Bz0C{0C{1D|0E|4H{2F{1Ez3G|3G|2Ez2G{3I~3F{1E{3G|3G{0Fz0E}1F}1F}3E~0F|0E}0E|1F}3H3H3E~2G~3H2G~4I8L5I4H{6J}8L}9M7K7K8L6K6M~6L~7K8L7L5J6K6K6L}5L}8L5J5I5J4I~5J6J5J4I3H4I2J0H{1Ez2G|2G}3H2G3G5H}1F{0F{3J{2I{3F}3G}4H}4H}0Ez.Cx.Cx.Cx/Dy,Av.Cz.Cz-By,Ax-By+@x-Bw,Av.Cx/Dx.C{-By,By/Az0Ez.Dy.Cz-By.Cz,Ax,Ay-B{.Cz,Ax,Ax-Cz0Bz2D}5F{5F|0D}2Fz2Fz1F|/Dy0Ez/Ez4G|2F{2F{3G|1E{2E~3E}3F~3F}2H~1G}4I~3Iz4J|4J|2Hz4J|2Hz1Gy2Ey5I|4H|4H~5J2H5I|3G}4H}5J4I~2Iz3I{4J{4G{3Gz4I|5I{4H{4J}3I~3H~3I{5K}4K|6L~4J|6L~2Hz3I{5Hz4Hz6J|6I}2Fy4H{4H{2Gz3Gz2Fy1Ex3G{9I~:J8K{7I{9K}8J|8J|6Hz6Gy3H{4H{2Gy3Ix4Jy4Jy6Gx7Jw9Jx:Ky7Iw7Iw8Jx7Iw5Gu8Jx5Gu4Iv4Iw4Iv4Iw3Hr4Ju7Ju7Ju7Jt7Ju8Jw7Jv8Lv7Kv9Lx:Ky;Lx9Jv>Lz;Lw;Mw;Mw=Pw>MwAQzBPy>Q|@RzAT}ET}DS}GVGVIXKZN]O^TbTcP_Q`QaP_P_P_M\Q]N[MZNZK[M\N]M\LZLZO]I[L\L\N]N]M\N]L]L]L^N`M_M_M_N`O_N_MaNaObObQdQcTeXhThWkVhWhYkWiVhVhYiXiYj[l]n_p^o_qbodsdshvgygxhyizizl}k}p~n}l{m|o{l|l{m{j|izj{k{im|j{iymj~m}m~izhyj{gyizhyhyfykwfygwevexdvizgxgxhyhyfzhygyhzj}i}m}m~k|j{m~k|m~m~nqnpoqosnsspsrrswvtvtvstutttvxuwxywy{||z}ėœŸ žàģŦƨκֹܹߺ޿߿˻Ǻ˻˽ʻʹǷƴ¶ƿ̿ȸƻƾƸ÷ſݿտڿӽعնѸѺԻѿٿԾغٶٷٴ۱ڲڰذհ֬ӮӮ׫ԭ٭׭ӬѾ೺֭ԩרئ)8R+9T+9T*8S)7S*8R)8R+:U)8S,:T-;V-Y,:T-:T0:W0;W0;W0;W1X-=W4?\4?\5A]3A[0@Z3@Z2@\3A\4B\5D]2BZ3DZ3CZ3AX3BY5E[7E[6E[8EZ9FY;G\;G]:F];Ga;G_;G_Jb>J`?Ka?Ka@Lb>J`?J`?JaAI`@H_@H]CK`CJ`BG^CI_BK`BN_AOaANaDPbCO^BN^GMaFMbGOcFNcGNcFLcFMcFNbHPbGObGRdFQbEQcGTfGUhHThJThHSgHRgITiIThIUhGSgHSgGRgIUhGTfHUeHUfIVgHUfHUfJXhMYjNViPXlPXmS[pU]rU^rT`qU_rW_tV_uWcwWbuWctXfvXeuXewYfy\izYfwYfxZgzYg{[f}[h}[i|\i|\j|[h|\j|[hy[iyZgx[gzZfzZfzZey[gz\j|]j|[h{]j~`m_l]l_mbocpboererhtiuivgvhwhwivmxoyn{k{l|n{n{n}n}m{m{r~q|r~o|m}o}sstr~p|p|o}p|t~r~rt~x~}~||zv|x}w}w~yzz|{~}|{|xzywyzy~w|x}{{{y~|}z{z~{|zt{szrxrylthp}gp|gn{gm{biwajwbizbi{`gy^ex^ex\cv]dw[buZatZatZbsZctVapTaqP]mNYiKXgJVhGRfCOcAMe@Ld;Ga9Hb7Ea6Db5C`6D`3A`3Bb0>a/=`1?b1@b0Ad/?d/>e.=d-u*=u.?w->v.?w,=u,=u->v->u.v/Bz.@x,Ax-B{+@y,Az,Az+@w-By-@x.Ay/Bz.Ay-@x.@y.C|-B{.C|.C|.C|.C|.C|-Ay/A|.A{0C}0Cz0C{0C{0C{/Bz1D|.Ay-@x/Bz-@x.Ay-@x.Ay/D{.Cz,Ax,Ax-By.Cz.Cz.Cz,Ax-Ay/Bz.Ay.Ay/Bz1D|/C{3F4G2H|2G{3G|4H~3E~0F|0Dx0Dx1Ez1C|0F}0E|0E|0E|/D{1F}2G~1F}0E|3H3H2F~0E|2G~1F}3G~5I}6J5I{5I|7K|6J~8L7K}6J7K}7L8L7K6J6K7L5J6K7M~6L~4J|5K~5L}6L~3H~5I6J6J5J5I6K5L3J4H~5I4H~6H3G|2F{1Ez4H}4G}5I{2Gz5Hz3Fy3I3H~1F{/Dy/Dy/D{.Cx.Cy.C{,Ax,Ax-By-Bz.Cy,@v,@v/Dy.Cx0Ey.Cy0Cy0Dy/Cx/Bx/Cw-Cw-Cz-By-By,Ay-By,Ax,Ax,Ax/D{0Cx/Cx5E|2Cy0Ez1Ez1Ex/Dz2Ez3G|3G|3G|1Ez0Dy/Cx/Dx/C{/C{0C|2E}1D}4H}1Iy5J}4J|1Gy4J|2Hz1Fx3Gz2Fy4H{4H~2H}2F{4H}6J5I~6J3H|4J{5K}1Gy2Iz5I{5H{4Hy3Gy2Iy3J{3I|5K}4J|5H{5I|6K}7J~2I{2I{2Hy2Hy5K|5K|3Jz4F{3Gz6Ez4H|3G{3Gy7L~:K}:L7I{7I|9K}6Hz9K~8J|8J}9Jy:K|:L{:Kz9Ky8Jw:L{9JxOxLt@MwAOvBOw@Pv?Ov@OvAPwDQ{ET{ET{FV|GW|GU}KZIYLZL]P`RbScScSdP`Q_N]O^O^P_P\P]NZMYO^K[M]M\M\L[L[M[K^L_N[N]Q`Q_M`N`N`OaM_PbOaPbPaPaObNaQcQdPcRfWgUfXjZk[h\i[hZhYfXfWi[lZkYkZl]n^o_p`obrapesdwfyi{i{n{m}n|n}poznznzpl{n}k|l}k|j{k{j{l}k{kmpnl}izl}l}k|j{hzjwjwfxfwevfwdufxevizfxhziygyh{i|k|j|j{k|k|j{k|m~m~m~k}ommmporqrstsvvuvywvvtsvtuutvwuusvwxwy|{}Ɵ ŠġĪɫðĹ˺ͻֽܹڼڽȹǸ̶ȶɸʵǴŵüɽÿ˿ۿ׾ռԷӹѸѺһؽ۽ٵӳճر۰رٰسխӮӮԬӭ׮֪իҭҿޮثاף,6R+6S(7R*8R(6P+8R+7Q-8S,6R,9U+:U-X1A[2>Z4?[2B[2A[3A[3A[2@Z2?Y3D\0@Y4D[2BY3D[2BY3D[5GZ6FX6EY6EW7FZ9E[9E\8D[:F\;G];G];H\;H[J_I_?J`?I_AI]AI^@H]CK`CK`DLbBJbBK`@MaBNdBMdANaCO_CL_CK`DLbBJaCK_CJ_CH^CI_CLaCK`BK_BN_AL^AM_BO`BP`CPcDRfDQcBO`ERcDQbDQbDQbGQcFPbGSfFSgIVgIVgGTdJVgISfLVhLViNUiPXkOWmW_tU]sVauS^pS]pW^qW^rXatWbtXduVduYfwWduWduZgxYfxYgyYfyYg|Zf|[g|[i|[g{[g{[fy[gy[fy[gx[fx]dw]dx]e{]f|]h|Yfz]k~]j|^k|_l|`m}ambocpcperdqdqdqgthwgvgvhxhxkxnzmzl{k|mznzp{ozp|q|q}p|r}o{p}ozsuuq}p|o|m|o{o|o{lykxpxrxz{y~x~u}qxryryqxs{xxv~|}{yz|}{zwwt|u~u}t|w~v}w~v}w}zz|zz|xs{syu{synsio}hn|io}gkzdjyciydjz^fy]dv[bt\cw]dw\bu[bu[btZarZasW_qV^oV_mT]nQ[lOViLTfIPcENcDOcANb?Kb>Jdv+v,=u->v->u,=u+;w*:v-=x,=x.?v.?w.?w.?w,=s,=t.?x.?x/@u2Cy/?u4Ex1Ex/Cv2Fy/Cy1Ez0Dy/Cx0Ez/Dy.Cx1F{/Cx1Ez2F{2F{3G|0Ez0F{4G{7G~6G|6Gz6Gz8H|7H|7G{7I|5I|2F{2E{1G|1F{3G|5I~6J3G|3F|6G|3Dx6Fz2Fy2Fy2Gy2Fy0Dy3G|1E|2F{0Dv2Ex1Av1Au3Cz2Ey0Dy0Ey1Ez0Dy1Ez.Bw/A{-Ay0Cx1Ay.Az0B{/B{2E}0B{.@y0C|1Cz,@u-@y.Ay-@x-Ay.Ay-@x/Bz.Ay.Ay.Ay,Bx+@w+@z+@y,Az*?u-?x.Ay-@x/Bz/Bz/Az-Ax.C}-B{-B|/D}/D},A{-B{-B|0E}0A|/By/B{-@y-@x/Bz/Bz.Ay/Bz,?w/Bz/Bz/Bz.Ay.Az,Ax-By,Bx-By,Ax-By-By.Cz-By-By.Ay.Ay.Ay.Ay1D|/Bz3F~1D|3Cy3E{/Ey2F{0C|0C{0B{/B{/B{.Cz/Cz/D{.Cz1F|/D{2G~1F}2G~1F}2G~0E|1G~3H3H1F}2H~4G3H|5I~5I|8L~4H|4H{5I{4H{5I|7K~8L~9M8K9N6K8M6J6K8N7M6L5K}8N6L~4J{7J~8L~6M6M8M6N7N6L}7M6L}8L4I~5I~5I~5I~4H{3Gz6I|5M~3I|4J{3I|3H}4I~1F~/D|0Ez.Cx.Cy-Bw,Au.Cw-Bx,Bv/Ev.Cv1Gz1F|1Ez1Ez/Cy/Dy/Cv/Du2F|.Bw0Dx/B|-Cy.Cz,Ax,Ax-By,Ax.Cz.Cx1Ez/Dy3Cz/Dv/Cv/Cv.Cu/Cx2F{/Cx0F{0Dy0Dy0Dy.Cx1Aw0Aw0Aw/C|2F{2F{3H}2Iy4K|3I{2Hz1Gy0Fx0Fw3Gz4H{3Gy3G}3H}3G|6J5I~6J3G|3G|3Hz4J|2G}3H}0Fx6I|2Gx2Iy2Hw4J{3I{4I|5H{5I|6K~4H{3Fy2Fy3Iz3Iz3Iz3Iz4L|5Hz6K}6Gy3Hz7L}6J};L~:L~8Jz9Kz8Jz8Jz9K{8Jz7Ix8J{;M}:L|;M}:Mz;M|9Kz=N}>Pz?Q|BO|AP|Q{;OyQ{=Pz=Oz;T|>Oz=Py?Qz=PxX2?Z1>X1@Y/>X2@[3@[3A[2B[2B[1AY1AX1AX7E\2BX1CW5GY6FX5DU8HV6DW:E\;G]:E[;G]7CY8DZI_?I_@G\?G\BJ_?G\?G\@G\?E\F]>F]@H_>F]?G^AG^AH^@H]BI_CK`AM^BN_AK]BN`COaDPaBPaDQaAO`AO_ERcERcDRcEQcGQcFRdERdGTeHUfITfHTfITfIUgNWjOViOWjPXmS[pU^rT_rS`pS]oV\pV^qW^qXatVbsVasXeuYfwYfwXfvZfy[fzXcw[g|Ze{Zf{\g{\h|[fz[bw\dw[buZdvYcv\cv]ex]ez]ez]f{]h|\g{[h{\iz^k|an_l}`m~boerererdqdqiwdsixgvhxgwkyozlyjzm|m{myo{myq}o{nzp|p}o|o|rzxuo{lzlzjylxjwgucsgrgsirltyw|tzryovqxryryu|xxz{{yzwx}y~xt{uu~uu}t{v}w~v}v}yy}~~~yv~tyqwqwntkqin|dguchsagvagx^eubhw_fv_fx]cv\ew]fy^ex^exZatZasY`rY_qV]oT\pU[oRXkRXmNSiLShKSgISgGRfDOeBOf@Md=Lav->v+;t*:u*:u*:v,v->v/@u.?u.?w/@x.?w/@v/@v.@v/Dz/Dy0Dy0Dy/Cx/Cx0Dy/Dy.Cx.Cx.Dy/Dy1Ez1Ez2F{1Ez/Dy1Ez2F{3G}4E{5F{5F|5F{6G{6G{6G{5Fy2Gy3Gy3H}3H}4H}4H}4H}5I~5I6G}7Fz6Fy4Ew4Ey5Fz2Gy4I~4H}1Ez3G}2Gy0Dx2Bv4Ey3D{2E{.Dx1Dy3Dz/Dy0Dy/Bx.Ay0Cz1Ey0Dy/Cw0Dy0Dy/Cx/Dx1Dz2Cx0Bw-Av,@v.Ay-@x.Ay.Ay.Ay+>v-@x-@x.@x-By*?v*?v-Bx(=t*@v-?w-@x/Bz-@x.Ay.Ay0C{.Cz-By,Ay+@w,Ay,Aw-Bz.Cz/Cz.Ay/Bz/Bz3Dy/C{/Bz-@x.Ay/Bz/Bz/Bz-@x,?w-@x.Ay-@x.@x,?w-Cz-Cy-By-By.Cz-By+Aw/Bz.Ay0C{/Bz.Ay1D|0C{1E}3Dy1Bx2Cx0Ey0B{1E|.Ay0C{0C{0C{-Dz.Cz.Dz.B}1D{/E|/D{0D{1F}1F}0F|2D|1F}2G~0E|3I5H5I}6J5I}6J}6J}5I|6J}5I|6I|5I|8L~6J9M7M6K7L6K8M7N9O6L}7M7M:P7M8L7K}7N7M8N:P9O;Q7N7L:J9J4I~7K}7K}6J}5I|9M3J{7M~5K}7N6J6K4I~5J~4I3H~0Dz/Dy1E{/Dz/Cy2G|1Hy3I{3I{3I{2Iz2Fx0Dx4Dx3Cx2Bw.Dv-Bw.Cx/Cx0C{-Ay-By-By+@w-By-By-Cx/Cx0Ew2Cw/Dv0Dw0Dw1Dv.Bu/Cx1Ez.Cx.Dy1Cx/Bw4Dz1Bx2Cy/?u/Cy1Ez2F{2Ez2H|2H}2Hy2Hz1Gy1Gy0Fx2Ey2Fy6J}4H~4I~4G|4H}2F{4H}3G|5I~3H|4J|3G2G~1F|4I|3Gy2Iz2Hz3Iz3I{5Gz3Gz5J}7H|6F{3I{5I|2Iy/Fv5K|4J{3Iz4Gz4Hz4Gz2Gy9J}8Jy9J{9K{:L|8Jz9Kz8Jz7Iy9K{8J{:Ly9Ky8Jx;M|OzR|?R|@S}AT~AT}BU}BV|DV|DVzDV{EW|FW}JY}K[}M]N^P`Q`Q`~M[vO\tO[tVb}Zg\j]lXfWg[i^landscr`oaqcqcsfsfuesbp]k\j[iXfXfTbUbPaO_N]N_P]P^P^P^O]P_P_P^OaM_R`RaQ`R_MaQbNaPcPcQdRdRdPcQcSeUgSeWi[mYl_l]l\o_r^p^p]o^obq_n`o_n_nap`oapbqetdtdsftguhxiwm{h|m|op|o{o|n{o|o|n}n}kzn}qm|n}ph}l~i|iyj{k|nl~p~n}ppn~ivkyhujyjwexfwfwhzjwiujxiwlyhyizizizj|k|izizizj{j|j{i|myj}l|nmonsso~vstssstuuvurswrtutuuvvuwvyxy|}}›ġּֽֿʼǷƲʲȱƱȭDZIJĵƶºżھڿԿѿξԺٷظرҳֱֳٰۯٮبԪԫԫլקӦթئרԪ֩զբ֤ڣ)5O,8R.:T,:T-:T,8R,8R/:T+6P-:S-8S,8Q-8S,7R*7P,8R,8R-9S,9R/:U/:T-9T1=X.:U-9T-8S-:U-;U-;U/:U2=X/=T-=Q.X.?V/@W0AW1AY2B\1AZ1AX2BZ1AY2AW5DY3AW5EX5DV4CV6FY5DX7F[6EZ9F\:F\9DZ9E[;G\9E[:G\:F];G`;G_;G_;G_;G_:F^:F\9E[9E[9E[:F\:F\:EZ8CW8CV9CV:EW8CU9DV:EXJcF^>F]>F]@H_@G_BH`BG]AI^AI^BK_@L]BL_DK^AK^AN_DO`BN`BOaBM_BM_DQbDQbAN_DRcGScGSeGThGSeITfITgJUgLTgLTfMUgLXiPZmRYnV]rS]qS_rQ\mS]pU[nT\oV^qU^qT`rYduXeuYeuXfvYev[fxZfzZdy[bw[bx[dyZey\cy\cx\dw[cv[cvYatZbu[cvZbv\dz\dy]dy_h|[g{\h{[kz^k|^k|an~bo~`mbpcqerfrfsgvdsgvgvk{izjxmxlykzl{kynznzmyo{nzlym{n{lylzsxwo{kxkxixgwjwgtesctfsepholttt~y}yw~qyqwovqxqxszv}xxyyvwv}v}u|u|u}t~t~wx~w~w~w~x{|{~yrzsxnununskqin|jk{gjyekydjx`fs_es`fvahx_fxbi{`gz^ex`gz\cw[bu[buX_rY`sX_rX_rU\pU]oTZnUZrTXqRZmQXkMWjJUhFSeFSdBNc?Lc=Je;Id9Hd9Gc8Ff9Fe6Be6Ce4Bd2Ab0?`.?a+;]-=`+;`,:`+9a,8c+8c+8b+8e)6b)6c(5a)6b)6d(5c(6c(7c%6d#4c$5c#4b$3a%3b%3b$2a$2a$2a%3a$1c!0c#/c$1e$0d$1f"2e /b"2e!1d!1e0d/c0d 1e!0e 0c!1c.b 0c!1d!1d 0d!0d/b"2f/b/c/c0f 1g!/f.e/c.c 0c!1e!1e/d-e 0f/b%1g#0d$0d%/e'4i$2g"2f /f 0f 1g!2h 1g0f!2h0f 0i!1i"2j"3k2j0h0h/g!2j!2j#4k#3j#4m$5n#3k!3k2j 0h%6k!2g%6l'8n$5k#4k$5m#4l%6n&7o$4m%3l%4k%5l%7m&7m%6l'8n(9o'8n'8n'8p%:n$8m%9n#7l$8m%9n%9n%8m'7m'8n'8n$9m&:o$8m$8m#7l$8m$8n&8q%8p$7p%9p%:o#6l$7o%8p&7o%7o%6n'8p&7o%6n&7o%6o&7l(9o&:o#7l$8m$8m"7l#7l':o&6m):p'8n'8n(9p'8p'9p*;s*;s(9q'8p(9o(9o';o*;q*;r):r+u+v,=u/@x+v->u->s.?u2Cy0Aw/Cx.Bw.Bw.Bw-Bw-Bw,Av.Cx/Dy-Bw/Cx.Cx1Ez1Dy0Ey0Ez0Ey4Dz4E{5F|2Cz5F|6Fz6F{6H{3Gz2Fy3Gz4I~4H}4H}6J2F{3H|8I7H}8H{6Fz5G{4Ey6G{5Fz6G{5G~1G|2F{5Ex6H{4Ey4E{5F|5F|5E|2Dy3Dz1Cx/Dy.Az.Ay/Bz.Ay0Dx/Cy/Cx/Cx1Ez.Bw1Ez2By0Av.Cx-@v/B{,?w-?x.Ay-?x,>v.Ay.Ay.@x,By,Ax*?v,Ax+@w*>v.@x-@x-@x.Ay0C{1C|/By/E{-By.Cz,Ax-By-By,Ax*?v-By.@x/Bz2Bz1Bz1Ay.Ay/Bz/Bz/Bz.Ay.Ay.Bz/Bz.Bz.Bz,?w-Ay.Ay.Bz/Ay-Cz-Cz.Cz,Ax.Ay.Bz-@x-@x.Ay.Ay0C{.Ay4E}2Cx2Cy0Bx0Ey0C}0B{.Ax0C{.Ay.Ay/Bz/Bz0C{0C|/Bz1C{/D{/D{1F}0F|2D|2E}0E|1F}/D{1F}3F~4G~2G{5I}6J|5I|4H{5I|4H{5J~5J5I~7K~8L~6K7L8M8M9O8N:P;Q9O9O8N9O:N:N8L;O:Q;Q:Q:P:N9L:J:K~6K~8K5K}6L~6J}8L8N~8N8N8N5K}9O8L8N5L~5L}6L~2Jz4K}5K}3J|6L6L~6L5L~6M7J|6J{6Hz4Fx3Fw3Ew1Bw3Dx1By.Cw/B{,@x,Ax,By+@w-By.C{-Bw.Bx/Du2Av/Cv.Bu/Bu-Du.Aw-Av/Cw,Av,Av-Av/Cx/Cx1Aw3Cy3D{.Cx.Bw/Cx1Fz1F{2G|2J{2I{0Ew0Fx2Hz3Fz1Ex0Dw0Dw2Fy3G|2F{3G|1Ez5H}3H}3I~1G{0G2F~/Dz0Dz0E{2H~5I2G|5J|4F{3H{3Gz5Ez8H}3Cx3Ez1Fw3Fx3I{2Hy4J|3Iw5Jx4Hw7Iy9J{7Iy7Iy9K{9K{8Jz8Jz9Kz9Ky9Ky8Jx9Kz:Lz;NxO{=Nz>O{>O|?P{DQ|CR}DS}@R}@R}AR~BTCU|DV~EWEWCV}FY|GY}H\}JZ|L\~O_PaUcZhapjxo|n{myvp{|}zyvo|p}r~vtsplxlxk{jwjwkxfugu`n^l^k\jZhXeUcRbP`P`QaR`P^O]TaS`QbQaSbPdScTdSdRbUeTdPcVeVfWfWfUeUdXfWgVeTiWk\m\ocpesixfxewbufxdwcvhvgvfufugvdtdsgvixjylzkyk|k|k{m~n}o~q~o|o|p}p|o{n{o{l|o~o~n}m|po~o~o}k~j|k}k}k{l~o~n}o}rlo}jyl{mykzkyjygyhzkwivixjylzixhzhxk|k|lyjxjyl{kym{n}n{n{m{q~k}m~nonrsqtr~tstruvvtvuvsuuttutvtwwv{y{}|}}~~›œԻӽɼƷƶ˴ȰñƮŴdzŴźȻý޿ҽؼټظ׺ֺҺӿԽնַطز԰ӱװٱ۬٫تרצ֧ק֧֥էۥئ֧ץԤץܣڤ+7Q,8R,7Q+9S*7Q-9S+7Q,8R-7Q,9R-9R,8R,9R-9S-9S-9S,8R0:U/8S/9T.8S-9Q.:Q-9P.:R.:R+7O,E[?F\>F]?G^?G_BG_BH]CL`CK`DK_CJ]CJ]DM`CK]DL_DLbBMbDN`DL`COaCP`CQbCPaFSdERcGSfHSgGRfKUiLSeKTfNUiNSkMWkKVjNYmOZnOYnPWmT^rP\oS]qT\oU]pU]pW_rYatYbuXctXet[gwYevYbuZbwX`uZbt\cv[bv[bxYauXas[buZatY`sYat[dw[bvZbu[cw]ez^f{]ez]ez^j|]l|_k|^k{an}`m~cpcpdrfsivdscrfugvfuhxhxiwlxo{mykylwlykwmylxmylyn{mzkxp}vvp|lylylxivixkwdrdraqftgpgpgopr~x}x~t{qwpwlslsovszryt{u|v}xx~w~y~x~s{t{u{w~wzww~y{}~|t|s{nvltkshp}glzfkyeixdjzeky`ftciv`fubiycj{ahzagx_fw\cu`gy`gz[bu[at\ew[bt[btX_rX_sW^qX_sX_qW^qW^oSZoOZnOZkJXgGScEOcAMaANe?Jd=Ib;Hb7F`7Ea6D`4B^2A^2A_1@`/>`/>_/<`/v->w->w.?x0Av.@u-Av/Cx-Av.Bw,Av,Av,Av-Bw/Cx1Dy/Dy.Bw1Aw1Bx0Aw/Av0Bx3Dz2Cy4E{4Dz3Bx3Hz2Fy2Fy2Fy3Gz3G{3G|3G|3G|3G|5I~4H|5F|4E{8H|8H{6Gz6G{5Fz5Fz6G{5Fz3Dx4Fz6G}5F}3E{6G}3Dz2Cy2Cy2Cy2Cx1By.Az-@x.Ay.Ay-@x/Bz0Dx0Dy/Cx/Cx0Cx0Aw2Cy/Av/Cx,@u+?t-@x+>v+Aw,Ax+@w.Ay-@x.Ax-By.Cz-By,Ax-By,By.@y-@x/Bz.Ay.Ay.Cz-Bx.Cz-By.Cz,Ax.Cz+@w*?v+@w,Ax-?x.Bz2C{2C{1C{-@x0Cz.Ay.Ay.Ay,?w0Bz1Bz1Ay0@x2Bz/?w.?w.Az.Ay/Ay/Ay-Cy,Bx-?x-Ax.Ay-@x/Bz.Ay,?w1Bx3Dy0Aw0Aw1Bx-Bw/Cx0Cy-?y0C{.Ay/Bz0C{/Bz.Ay.Bz.Ay0C{0C{/C{0C{2D}2E}0Cz-Cz.Cz.Cz1F}2E}7I6J~7K5I~4H{5I|5I|4H{7G{7H~9I5I|4H{7L4I~7L6K8N8N9O:P:P9O:P8O;M;OQ;O;O;N?NO{>Oz@Q{?P|@Q|?P{@R}AS~BR~DS{FV~FUIXHZGYEX~I\N]QaPaPaTcVfYh]ldsm|t{¯{us}n~n}n{oynzlxjwgwbq^k\l_l\j\jZgVfSdSbRcUbUcS`TdVfVfTdVfZiYiWiXeYgWiXhYiYi[k\l\k]l\k[kYi[k\k^m^sftizm|qpon~n|n~m|m|n}mzjxkxjxm{n|o|m|o}o}o}lzo}o}m~m~n|o~m}o}o~n}n}n}m|n}n}po~n}n}n}m{j}k}ml~l~l}o~o}i|l~mk~mzm|l|l{m|l{l{m|l{kzm|l{jyk|l}j{j}l~j|jxjym|kzm|l{jzmzn{n|k~nl}prtqrrrusurstuxwvwwsuvvuuvuxwxy|y{}}ž̿ҽǼǸƵdzĴ÷dzȸȹɺȾݿξ־ӽվָյַܴڶٶڹٷַطնԾֿտ׷յԳ׵صش֮ԭخکר֧רצԥ֥դեڦإդҤգפۣۤ+7Q+8S(6Q)8R)8R,9S,7Q-8S,6Q-:T,8S-9T/;U.:U.9T,9S,8R-8S,6R09T/:R.;R-9Q-9Q.:R.:R+8P-Y1?Y4?Y1U/AS.>S/?V0@W0@W0@V1DW3BV7BX4@V4@U6BX3?T4@V3>U5@U1BU1AV0?T1@U0>T3>T4?W3@W3BX3CZ3CZ3BY3DZ4DZ4DZ4CY6BX7CY6BX7BY6DX6CU5AT8CW6DU6CT:GX5BT7CS6ET6HV:GW9DZ;G^:G_;G_;GZ;FX=EW@G^=E\?H_Jb>Kc=Jb8F_6Ea3C^4Da3Bb4Dd3Bd3Bd3?c1?e/>e0>f0k0=i/

i-s+=r+@t,Au,@u,@u,Av,Au,Au,Au+@u.Bw-Av/@v/@v.?u/>u0?v/@v/@v0Aw1Bx1Aw2Cy1F{1Ew3Gz2Fy1Ex2Fy2F|2F{1Ez2F{2F|3F{5E|6G}9H|8H|7F}7Fz8H|6G{6G{4Ex6Gz8I|6G}3Dz4Dz5C{5E|3E{3Dz3Dz1Bx1Cy-@x.Ay/Bz0C{,?w/Cz/Cw/Cx0Dy0Cx0Cx3Cy3Cz4E{/Dy-Av.Bw,?w,?w,Ax*@v+@w/Ay.Az-Ax.Cz.Cz.Cz-Bz,Ax,Ax-Cz.Ay/Bz.@y-Cy-By.Cz-By-Cy-Cy,By-By*@v+Aw,Bx,By.Az1Bz1Bz2C{1Bz/C|.B|/By.Ay/Bz.Az/@y1@y1By3D|1Bz0Ay/@x.?w,?w.Ay-@x,@x,@x-@x-?x-@x-@x.Ay-@x.@y.@u2Bx3Cy1Aw0Aw1Bx-Cw.Cx,@t/Cw-@y/Bz/C{3C{/@x0Ay0Ay/Bz0C{0C{.Ay/C{2D|0C{-Cz.Dz.Dz/D{1C{4G3G4I|2F{5I}5I{2Fy4H|8G|5E}7G4Iz6I|5L}5I6L6M~6L~9O9O9P:P8O:P8N;O;PAS>S>R;O;OR;N;Q;Q;QO|?P{>Oz?Q|BS~@Q|BSBTEVGV}IXIXL\N]O_O^P_QbTeVg[j]l`ndsjxn~z~~yyxtrt}qynzjwjvftbr_pan`n_m\iYjZi[kVgZgZhYiYi\k[k[j[k\l^m_lboao`n^n^o^o`pbsdtcscr`p`qdshxfvixortvuttttsr}srqqqpp~p~rqp~o~p~n|n|q~o|l|n}n~po~o~o~o~pn}o~poo|o}p}pj}ml~mk}lo}o~j~k~nj~n|m|jym{o}m{m|l{m|l{l|m{n|j~h{l~i|k~k|jyjyjykzkzkzkzl{n}n|k~k|mnorptssrsusvvuvuwtvuuuttvvwuwxzz|}~“ÚͽмҿɸȴƹƻȸǼ˽̿ԻӺָԷֶֶٵرֱֳٯٱְֱز׶һѺѻѹҺԽ־ӿѼϿҿԼԵӴҳױԲְֲ׬֭ګ٦צԥӥӥԣԤԣԤעӣӢң֣أآ)8O(7N*8P+7N,8P,6P/4Q07S-8R+5N.8R,9Q-9P,8Q,:R.:Q.7Q-7Q-7P/9R/9R.8Q,7P/X1>U2>U1=U0=V/>X/?V-=U.>U.>U0@W.=T0@W2@X1@W1@W3AX1E\=E[?E\CH`>D[AI]BK`CK`CKaAJbBI_CI^DK^CK^AI[CK_CK`DLdGNcDI`EMbFOcCOcEPdFQfHSiHSgHSfFQdKTfMUhMVhKRgOWlKShLWkMXlMWkMUjOWlRYnRZpP[oQ[mT[nT[nT[nU_qVasYatX`sZ`sZatX^rW^qV]pX_rV]pW^rV^oU_oV`qX`qX^qY^rX_sX^rZbtYat[cy[cxZbw]ez\dy`i}[hz]j{`m~^k|_l~anbnboapapbqbqcresaretfrhthtitivjvkwkwkwkxmzkxo|trkvivkxkxivivftevcrgqfqcqdpandqancodlem~fm~gm|txy}y|vzws{ryovovovovovqxu{w|uzx}v}u|t|s}t{w~w~zzy|}}}~~}˽Ⱥżɽĺ{u|sypvko~mq}lr}hn{jp|iozjnylp}ln~oruyw{rvmsjqgofl}fmfmcj|bi{agzahz`gy^ex^ew\cu[bu[`vYasY`sW^qV]pPXkQ[nPXkMThMShJShIRgFQeEPdFQeEPhCMg>Ib;Kb8Ha7Fa9Gc9Ff9Fg8Eh7Df5Be4@f4>e3@g2Ag3@j2>i1>h3Bo0?j1@k-=i*;h+=g)=i*:i,:h+9h*8g(6e'5d(6e&5c%6d%2c$2b#1a#3e$5h$6h!3e#5g"5f"5g!3e 0e0d.b/d.c 0b!1d /c"2f!1e0d 0d 0d"2f"2e 0c!1e 0b 0c /b /b/c!1e 1d"1d#/e#/d%/f$/d#0d$1e$0d%1d#/d#1f!1e1e!0i0e1f1f1g0f 0f 2h/h/g1i2j0h.f/g0h0h0h/g0i!1j1h 1i 1i1f 2h!2g"3i 1j!2j#4m"3k!2l"3j 1i!0g#1i"1h#2i$3j#2h#5j$5l!2k#4l#4l"3l$5l$6k 5j"6k#7l$8m#7l$8m$4k"7l#7l$5k&6l$6l%5k#6k#7l$6k$6k&6l%7o$6n$5n$5m#4l$4l$5m$5n#2k$3l"2j$1k&3m$4l%7n%6n#5m&7l$5k$5k&8m#9k"6i#8k$5k$5k$5k$5l&5l&5l&6l%6l'8n&7m%:n&;p':o&:m%9l$8m(v+>v+>v+@w%:q+@u-Av,?u0Aw->t.=t->u.=t.>t/Aw/@v0Aw,Bv0Dy1Bx0Ey0Dz-Av0Dy0Dw2Fy0Dw0Dw0Dw.Bu0Ew0Dw4Ez6Fz5Fy6Iz6Hz5Fz9J~6G|4E{6G}7H~6G}2Cy3Dz4F{3Cz5F|4E{2Cy3Ez-Bw/B{0C{.Ay.Ay/Bz1D|1Ey/Cx.Cx0F{2F{1Dy.Cw1F{/Cx/Cw0Dz-@y-@x.Ay.@y.@y.Ay0Dx/Cx-Bx-Bw-Bw-Bx/Az.Cz,Ax0B{/Bz/Az.Ay1Bz1Bx0B{0B{0C{-@x-Ax/Bz-@x.Ay0@x2Bz2Bz1Ay1Ay3D|0@x2B{.B}.A|-@x/Cw/Cv/Cw.@y/Cz0@v.?u0Ay,?w,?w-@x.Ay.Ay-Av,?w-Cy,Ay,Bx.Ay/Bz/Az-Bw-Cx-Aw.Bv-Au1Aw.?v0Av-@y/Bz,?w-@y-@x,?x.@x,@x.Ay+?w.Ay/Ay1Ay1Ay1Bz1Bz-@x-@x0B{/Bz1D|3D|3D|2C|3H{4H}5I~2Fx3Gz4H{2Gy2Fy3Gz4G{7K}4G{6J}3I{5K}6L~6M7M8O;N;P=O>P@R@Q@Q=R=Q>R@R@RAS?O=Q9MQ?OAS?Q?T>S>R>S@U>T=S?T?TAR?QBU~?R>P?Q;M}6I{8J|:J4H{2F{3H}0Ey4H}3F{3G|4H|3Cx4Dx1Fx0Dw2Fy-Dv.Dw/Ev/Ew,Av.Cy,Av.Cx-Av-Av.Bw-@u/Az+Ay,A{-B{-B|,Ax.Bw.Cx,Bt,Cu/Ew.Cv/Ev0Gy1Dx2Fy2Fy4Dy2Ex5Fy2Fy0Dw1E{6J2H|2I{3I{2Hz2Hy2Hy3J{6I|5I|6J}5I|2Fy9H}9H~8H}9I~6G{7Hy6Kz4Ix7L{8M|8M|7L|9O{;Kz:Ly9Ky8Jx9Ky:Ly8Jx;Mz9Ky9Ky;M{P{>O{?P|?P|DR}CR~DR}ES}FU~FU~HWJVKYP^P^P^Q_SaVcVbWdWf[j`ofukzpq|~}wwuu~q|l{jzjzhxixmyiyhvevcr]r_sap`o`pbrbvatdwcucqcsdsgwixjziyhxgwjwjxlykzh{h|k{kyg}kptuvwxxwxvutsssttut~rqoq}p~n|n{nk|l|n}q~rpr~qrppn~pp~rrqppoon~k~l~j}o}pon}pn}qn}m|m{j}k}i|mzm|m|n}lzi}j|j}n|m{l{n{n{l{kzkzl{ixl{m|m{l{o}o|j|mlnmqrotsttvvtxuwwuvurtxwxwwx{{z{|||оѾþÿȾѾѻоҽԷѸֶյԳֳױ֯׭׮׬֪ԫثԬճֱԴԴֵԲѴӵӷֶַոѺҽӿӾԺϸϲѵײֱԱ԰ӫӦӧӨ٨רקצԦզӥզբԡӡӡգ֣)9P*:Q(8O,7P,8P.6P/5O/7P.8Q-7P-7P-8Q,8P-9Q,9Q-9Q-8P.7P.8Q/9R.7Q-6P-8P.:S-9Q+7P-9Q0V3?X2?W1=V1>U0@U0?U/>Q0?R0?R1@S1@U2@V4?X3?X4@X2>W5AY5AY4@X5AY5AW4@V5BU5BU6CU5BW6BY5AW6CV6CW7DU8EV7DV7BU8CX8CW8DX;G[F\@G]@F\AH_AG]AI^AI\BJ]BJ_BJ`BI`DJ`BI\CJ]AI\BJ^CKbDLcGMdFLbFLbFNdFOdHSgHSgFQiGRhHSfJUfITfKUfMSfLVkJUiJViKVjLWkMWkOVkPXmNXlMXlP\nR[mS[nU\oU\oT\oT\oU]pZ`sX_sX_rV]pW^qSZmSZmU\oV]oV]nV`qVbsX`qY_rX^qX^sV^sZasZatYawYavZbwZbwZbw[dxZfwZgx\hz]j{^k{_l~anao_o_o_oaqapbqbqesfqgshshtitiukvitiuivjwmzlyo|n{jxdqdqercpcpao`raobmbmbobo^k|boaodndlfn~hogo~sx|x|v{wrzqyovpwpwpwpwpwrzu{txtyszt{t|r~s|u}x~}}{z~}{~}|{}|}z{}}õȶͽȻƹ÷{~zuzuytxrvruptnsovnsnr}nrpsruuxvxvwqylsiplsltlslsgnahz^ew[bt\cu\cuZasY`tZ_vW]sX_rV]pV]pRZmS[nOWjRZlPXlPYpMUiKReIQdIQdIRgDQgDOeCOfDPfCNg?Lf;Je;Ig:Gh7Dg6Ce5Be5Bf8Dk5Dj5Cj3@h4Al2Bm3Bm3Cm4Bm1Ak,=j,k.t/>u,;r,;r/>u0@w/>u-?u/Av-Bv0Aw.?u+@u.Bw.Bw.Bu/Cv0Dw.Cu/Cv/Cv0Dw0Dw2Bv3Ey5Ey4Ey5Fz5Ey5Ey6Gz5E|6G}4E|4E{4E{1Bx1Bx3Dz1Bx3Dz3Dz2Cy1Cx.Bz/Bz0C{0C{/Bz1C|1Ey0Dy0Dy1Ez1Ez1Ez0Dz3Dz0Ey1Ez/Cx.Az.Ay-@x.Ay,?w.Ay/Cw/Cx.Cx-Bw-Bw-Cx/Az-Cx-By/D{0Az.By/Bz.@y0@v-@y-@x-@x.Ay-@x,?w/Bz/Bz-Ay-@x.Ay/C{,@x,?w.@x0Bz.A|1D{.Az-@x-Ay.Ay-@x-?y/@x/@w-Ay/Bz-@x-@x.Ay,?x.Bv.@y-@x+Ax-By-?w-Ay/Cx.Bw-Av.Bw)>s-Av->t->t0Aw-@x-@x)w.Ay.Ay,?w.Ay2Bz1Ay2C{1Ay-Ay-@x,?w0C{1D|2Bz2C|4Dz2G|2F{3G|4H}1Ex3Gz4Gz4Gz5H|5H{4H{5I|5K}3I{4J|6L~:Q6L~6K}:N:M?Q?Q?Q>OAR@P=QAS?QAS>P@RAS@QT=R>S>S?U>T@V@V?UCTCUATCUAS?Q>PP~@Q|=NzARAR~BTDR|ET}ET|GV}GW|GX|KYKY~N\P^P^Q_R_R`WdXeYhZi^mapixqvx~}y{un|n{mzsusrp}nm}iyk{j{iyhxjzklkk}m}poopqsqspqosrssurutxxxywyvvxwvutwvvtutpn}m~n~no~l|l}n~m}qrsstsssrqrrqq~opq~oppj}on}m{poo}pm|n}l{lzk~ll{kzm|n}n}n|j~hzn{m|l{l{nymyjzlzl{m|l{m{l{hzk}j|l~mj~p~n}prrrsstuuvwuwxvuwqwuxx{xxyzz{{z||~ҿϿλؿӽ;ҼԼӺѴԵִѲձӯԭӭ֫ӫ֪Ԭ֪Ү׬լҰԱӯҳձӳղԱնԴӳϷѹѻܸϵӱӱձհӰҬҨԫ֧զ֧֥֦ץգөդբӡԢԢ֣ա,8N,7M-9O+7O,8P+7O-7Q-7P/9R-7P.8Q-7P,8P+7O,8P-9Q-9Q,8P-8R.8Q-8N.:Q.9P.:P.:P-9P-9O0T0?W.=S+R.@S/?T0?T1@U0?T1@U0?U0@U2@U3>U3>U2>U/>Q1?S2AT1AP0@P0?R1@S2@V3?W4@W4@U2>U4@V4@X5AY5AY4@V4@V5AW5BU6CV6CV5BW5BU4AU4AS5BS7DU8CU8DV8BV;CX;DYF]>F]=E\>FZ@G\BG^BH]?GZ@G[AI]AI]@H]@I]AI\AH[CK^CK^DL_DLaEMcDMcFKbHNeFNcGOdFRfEPdFQfHTjFQeITiGReGSfHSgLWkHUiKWlLVjJUiMYlMVkPWlNVkMXlQ\pPZlQYlT[nRZmT\oS[nS[nW^qX_rW^qW^qU\oRYlU[nU[oT[nU]oVaqT`pT\mU\oX_rW^qU^pV]pW^rV^tX`uYavYavZaw[dx[fxZgx[hy]j{\iz^k{^k|`namamanbp_o`p`o`pcrdsap`pbqeseueshugtergtgtererdq`m}_l}`m~_l}an_m~^p_o~am~`m~anbobpbocmcmemfoirjptzzwxs{vt|tzpwqxryqxnupwpwryryszu|szu{t|szuzz~|{||}{{}|{{ywwx{|}~~~óǵͻŷ||x}v|uyx}w{txvzuzpwrwqtqutxtwsvuxvxqxowovkrjqfmbi|`gz]dw\cuZasZasX_qX_qZbtX^sU\qY`rU\oU\oS[nS[mQYlQYnNVkPVkPWlPWjLTgOUhMUgLShFQfDQdBOcBNeAMfALg?Ig>Hg;Gg7De8Eg7Dg8Eh5Cf5Ci4Cj4Bj3Bl6En5Bn4Cn3Cm0@k+=i0Am0>l/=m.t-;r,;r-x,@y.@{1Ay1Bz,?w.@y-Bx,Bx.Ay-@x-@x.Ay,?w.@z*@w.Ay.Ay.Bw.Cw,Av-Bv.>t.>u-=s.?u-?s+?w/@y,@u.Bw-Av,@u.Bv.Aw-?x,?w.Ay-@x.Ay/Bz3Bz1Bz,@x-@x.Ay/Bz/B{0Ew1Ez0Dy1Ez2Ez2F{3G|4Gz4Gz3I{2Hz4J|4J|2Iz5K}4J|2Hz3I{7M7M7M~6M~:N9M=O>P?Q?O@R?Q@R@R?QBQCS@P?N@R=S>T>QN}=O~?S|@T~@P}AR~DUAR~FTFU}IXHX}JZ|JZ|IZ|MY}M[}O]P]Q^Q`TeVf[g]j\l^l`obqgvpvx}yxwvwywutspoon~p}ooprutusuuuwvvqruxywyx{yzxyzyyxzzyywwxwxuvtttqlqoom}n~m{n|o|qsqusssssqqqrp~qpp~q~qoo~porqrqn|p~n|m{m|i}n|m|o~n}n}m|m|m}m{o~l{l{l{l{lzjykzm|m|l{h{j|j}k}k~k}k}p|o}oqrrqssstvvuvwvxvzwvxyxzyzyxzz{{|z|•Ӿ̾ξӻμÿַղ԰ҲҰӴԯխӪӪӫӭժӪҫӮ֮ӬұկֱװկԴײԳѶԲθӻѻнײүүձְүԭӪԪЪөթקԦӦԨ֧Ԥբӡԡӡמӡ,7M)5L*6M,7N,8N+7M+7M-9P-7Q,6P09S-7Q.8Q,9Q,8P,9Q,;R-8O-9P.:P.:O/T.=R-S->R->S,=Q.?T.@S.?Q1@R0?R1@S1@S.=Q1@R0?R0@R0@R0?R0?R1?R0?Q2BQ0AQ0?S2?U3?V4@W4@V2>T3?V2>U2>T4@V4@V4@V4@W4AV4AT6CV4AT6CT4AR4AT5BU6CT7BT7BSG\;BZEX@GZ>GX?IY?HY?GZ?G[@H]AI[AI\CK^DL`CK`DLaDLaDLaGMcGObCK^FObDObFQfFQeIThGRfIThFQeJTiIThGTfJXlFUiJWiKVjLWkLWkMXlMWlMXkOZlPZlQYlQXkPWjQXkQYlSYlRYlQYlQYlQYlPXkPXkQYlQYlRZlSZlR\mQ]mS[mU\nT[nU]pV^qV]pX_sV^sYav[cxY`vZcxWcvYewZhxYfwYfw^k|^k|^k{^k}]j~]j~]j}_l_l`n^m]mZj\l]m]l]m^n`p`oanancpbobo`m~an`m_l~_l}`m~anan`l}am~`m`mboboancocnamblfmeneohooupvqxtzv~u|v~szszszryryqxqxqxqxqxrxtzu|s{u}u{u{y}{{ywyzvvwwvstwuwy~z}Ųɹ}yw|yzy{xyx{x}vzuyrxpwtyvwsvvxrvpwkqipfmbi|ah{_fy\cv[bv\cu\cuY`rW^pW^qX_rY`rX_rW_rW^pV]pT[nQYlS[oQYnQYnRYnQVmOVkQXkOWiMTfNUfMUgKReDQcBMbBMa@Lb?Ld@Ke=Hc:Eb8Fc9Gd7De5Bf5Cf7Ei5Ci4Bg7El4@l5Bk5Bn3@l4Bm/?j-=i0>k.m+9h-;j*8g,9h)6f*8g*8g)8f(9h'7h&8h$6f$6g%7f#5e#5d#5d"4d"4d#5e$6f#5g"4f1c!4e!3e0a0b/b.b.b0d.b 0d 0d/c"2f/c 0d.b"2f!1e#0e#0e#1f#0d"/c#0c#0e".d#1e"2f#3g!1e/c/d 0d/e/f 1j/f 4i1f0e/d/d/d0e.c/d/d/d0e/e0f 1g 1g!2h 1g0f0f 1g/e/e0g 1j 1i.d#2i /f"1h$3j#2i!0g /f"1k/g 1i"3k"3k!1j#2i"3i!2h!2h#4j!2h!3h#2j"3i%6l&6m%6l%6l"3i"3i!5j!1j"3j!2j!2j"3k"3k 1i!2j 1i!2j 2i!1h!0i /h"1j!0i /h!/i#2k"3k#4l$5l$5n&4n#4j!3h$5k!0g$3j$3j#2i#2i%4k%4k%4l&7m&7m#4j#4j"4k!7k&:o"7l$8n&6l'8n&7m'8m&5l'8n%6l&7m%7l&6l&7m&7m#7o(8p&7o%6n&7o&7p&7p%9q(9q$7p%8p':r&9q%8p&9q%9p%:p%:o%:o&;o(t+;s/:u.9s.u/=u.?u->t.?u,=s*>s,@r+?r.Av/@t/@t1Au0Au/Cv/Cv/Dw0Dx3Cz3Cz0Dz1Ez0Dy/Bx3Cy2Cy1Ey3Cy3Cy3Dz2Cy0Aw2Cy.?u1Bx2Cy/@x/Ay-@x.Ax-@y1C{4Dz0Bw.Dx.Bw/Bw2Cx0Aw/Aw.Cx-Av-@y.Ay+>v/Bz,@x.Ay0Ay0Av-Av/Cx/Cx.Bw.Bx.Az.Dx/Dy,Ay/Ay/Bz-@x/Bz.Ay-@x/Bz0C{-@x0C{.Ay.Ay.Ay-@x.Ay-@x/Bz.Ay.Ay,?w-@x1Bz0@x0@x/?x.=z1A|1A|0@{0@x.Ay.Ay-@x+Ax,Bx,>w-@x.>w-Ay-@x.Ay.Ay.Az.Cw.Bw0@v0@v/@w.?u0Av/@u.?u.>u0Az,Bv.Cx,@u.Bw,@u.Bw-Au,?x-@x,?w.Ay-@x.Bz.Ay/Bz/Az0C|.@y/Cw/Cw1Ez0Dy.Bw1Dy1F|3H~1E{1Hy2Hz3I{3I{4J|6L~5K}5K}5K}4J|6L~5K}6L}8N~9L~9M8M=O?Q@R?QAS@R?Q@SCRAQCSDQ@Q?P?QR?UBT>P?Q@R@R?Q?Q@R@RCU@U?TEVCTEVDVDTASDVEWCUBTCUDWEU?P~@O>N=P=O=O;P8L~6J|7K}7K}8L~:J|8J|9J~6K~5I|5I|3Hz1Hy1Gy1Fy0Fx.Ev1Gy1Gy/Ew0Fx0Fy.Du0Fx1Gy2Hz0Gx3G}2Iz2Hz3I{0Hz1Hz1Gz1Gy2Hz2Iz4G{6I|3Gy4H|2Fz5I|6J}4H{5I|6J}8L~6J~7K}7J}7K~6J};L9K}:L|;M|:L}O~>Oz?P|?P|?P}AR~>Q|BTBR~BSCTFTHWHVGWzIZ}J[}KZ}L[|KZ{M\}M\}P_TcSbTcYe^kaneqdsctivkykzn}uz{{{|z~~|}uwrtrro}o}qptqvuuvvwwvvyxvz|{{y{zz|zy{z{{y|yy|zyzvwwuutoqmmurp~qsqtrsrstttrtqrrp~qp~pq~n~n~m}n~pn~mm~ml~mmj}k~n|m|o~n}m|m|kzl{n}m|o~m|m|m|n}jyl{l{l{n}l{j}k}i{j|l~k}lpor~qossrsqtruuvyvxxsxywywxyzxxxxz||{|~̾˾ԿξӻϺ˾¼ԷشկѱӲԲծӯիҫѩҬӨЪ֨ҦҪӬӭүկկְӰҲӷԴҶζлѼѹɺϺϻѳҳհԱձӱӮԫ֫ѩϩҩԨ֥ԥӧ֤ԥԡӢӡӠҠԢ,7L.:N-;N*8K+8K+8L+8L,8O.9O.9P.9O-8O,9O-8O-9O,9O*9O,:O,8K/R0=P0=O.P1>Q,;N/>R->R.?R/@Q.?Q->P1@S0>R.>P0AP0AQ0@Q/=Q/=Q/>Q/>Q/>R0?T.=Q/>R1@R0?S1@V3?W2>V4@X3?U/;R1>S2?R3@T5@X4@W5AX4@W4?V5AV4AT4BS3AQ4BS4AT6CT7BU7AT8@S;CV:@U>CYDZ=E[@H^=EZ>F[@G\>EWCK[?HY=FW>FW>HXAI]>F[?GZBJ]AI]DLbDLaDLaAI_DL`FM`GNaEM`FNaEPbFQdGRfHShHUhGSfIUfHVfGTeIWjETgHViKWjKUjKVjMXlLWjMXjMXjLWiNYkNUhPUiPUiOWjOViNThPWjOWjOWjOXkQYlOWjNViNViRYmSYmQ[kQYjRZkSYlT[oU\oV]pV\oV]qYavW_tV_tXdxWcwYdxWctVctZgxXevYfwZgxYfwZgwZgx[hx\izZg{]j}[gz]i|]k}\i|[hzZgz]j~_k`l`mco_m_l`m`m~^k|am~_k|_l|_l|_l}_l}_l}_m}]j{_l}_l}aoaoanbocoal~cmgnenfohpipnuovquw|ww~u|u|qxqxovpwnwmvovovryowpypxryt|rzu|u{w~xv}wv~r}trtttrr~tsvx{~y}ĵ|{y|wyz{|~yzy|x{w{ryuzsyqxpvlriojpdk|bj|^ex[bv\cv[buZatZat[btZatZasY`rW^qY`sUZqUZqV\qU]nRYlT[nRZmRZnOWnOWmQYnPVlRWmSWkRVjPThPUhNReKSdHObCL^AMbAMb?LdAMh?Ie9Da8Fa8Gd5C`7Ee6Ef6Dg4Bf4Bh5Cj6Ck3@k4Am4Bm1Al1@k1?k.t,=s-=s,>t+Au+?r+@r-@t/?s/@t0Au0@u1Au0Bu-Bu/Bv1Aw1Bx1Ez0Dy/Cx/Bw1Aw1Cy0Dy/Cx/Bw2Bx1Bx2Cy2Cy0Aw1Bx1Bx1Bx0Bz,@x.Ay.Ax/Cw2Cy3Cz1Aw1Aw1Bx1Bx/@v->s+@u,>w.Ay-@x-Ay1Ay.@x/?w/@x.@u+@u,@u.Bw.Bw-Av/B{-Bv.Bv.Az/Bz.Ay,?w.Ay.Ay.Ay-@x.Ay-@x.Ay-@x.Ay/Bz-@x.Ay,?w-@x,?w.Ay.Ay-Ay0@v/?y0Ax/@w/>z/?z0@{1@{0By,?w,?w-@x,?w-@x*=v,?w0Ay/@x/Bz.Ay.Ay,?x-Av/Bw0@v0Bx0Aw.?u0Aw.?x0Ay/@y/Ay/@u.?u.Dx.As-Av.Bw.Bv+>v-@x.Ay,?w-@x.Ay/Bz.@y,@u.Bw.Bv0Dy.Bw0Dy.Bx1E{0Ez-Du0Fw/Fx1Gy3I{4J|4J|4J|6L~6L~4J|7M7M5K}6L~5L}8K}7K}:N=NPBS>S;P>SARAS@R?Q@SAS?Q@R@RCUAS@WDUDVDVFWFVFWEVDVBTCUBTCUCTCTCTAQ@P@O?R@R@R@Q?QP}:P}:O|9O};Q~>P=N}=OOz>O{@P}?P|?P}AR|AO{@Q|ASBS~BT~GUFU~GV}FU|IXKY}KX~KY|M[~K[|L[}N^O_S_TaWcYf]kfvkxlzn|m{rtuvyy{~{}~~|zwtq~rrrqprqtutwxzwxyyzz~}}xz|zzz||z{{{z{vvrsprqposvtqrrsrtp~p~sto}p~p~rp~qqo}n|n|m{o|k{n~ok{on~i}k~k}j|j|mj}j}m{m|o~o~l{l{kzm}m|l|m|m|m|l{m|n}n}n}n|l{m|m|j~j}j|k}loooq~qotqtssptuwuwwxxxwxwxxuyvyz{{{{{~}}~ϽϽл˺ÿַײԲұӱӲְԬӭԫѪѪӪթԧԤҩԫԫѬҩЭӱԱӷ߾ټ˾мѼи˷ͳαѶհ԰հԲӳԯӫլҫѪӪרӨ֧էפԥԢѡѡӡӡҡ-8I,8I+8I-:L+8L-:N+9L+8K,9M,9K,8K+8K+8K,8L-9P-:O*:L,;N.:N-;N-;N-;N.R.?Q/@R.?R.=P0?Q2?P1=O2>O1?O.@O/?P.=P0?Q0?S/>S/>T0?S0?S1>T3>U2>W4@X3?U2>S4@V3?U2@R3?S2?R4BU4AU4AV4@V3@T3?R6AT6@R5AS4?R4@R8CU:CT:BT;BVDX=CY=E[=E\>F\=EZ=EZ@GYBJ[=FWGX=EX@H\CJ_@GZDK^AI]BJ`DLaDLaBJ\DL_FL_EM`EN`DOaFQbFPcDNdERdHTeFSdHUfGTdFSdHUgESgGUhGTgHTgKVjJUhKVhKVhITfJUgJTgLSfLTfKSeLSfMTgMThOUhMUgOUhNViNUhOViPWjPXkSYlRXkPWjPYjT[nRYkSZmT[nQYlU\oV]qX`uU]rV_sS^rS^rT_rV`rTasUbvVcvVcuVctUbrVctWduVctXevXeuZfxZgzZh|Ygz[h{\i|\i|\iz_l~`m_l`m\iz[hy\iz\iz^k|\m|]m|\k{]jz^k{]j{]j}^j~]k}^k{\hz^i~_j}_l}]k|anbl~al~cnfnfofpiqpvszqxv|u|u|u|qxovqxpwovoxmvltoupwoxnwmwnzqzpwryszu{u|rxs{t|r~r}q~tr}q}q}q}suwz~{|}~|{~{~z|y{z|x{wzuyryqwlsovipiodjy_gv`gy`gy^ey^ex\cv\cv[bu[buZatX_rX_rX_sW]sTYoRYnRXnSYpSYoRYkQXkPXjOWjPXkQYlQYlQXmRXnQVlOUjPXjPViOViKSdIPbIQdGObDMc@Kb?Kc>Kd:Gb6D^:Gb:Hd7Db5Cc4Df4Be2@d5Di5Ch4Bj4@k3@l4Am3@l1@k/>i-;h-;i/=l.u0t,=s->t->t/@v+@s+?r*?q,@r0@t0At0At/@s/@s/?s.?s0Ax2Cy/Dx-Bw-Bw.Bw1Bx/Aw.Cw1Ez/Dx/Cx1Bx/@v0Aw/@v2Cx1Bx/@v.?y.@x+>v-Au.Bv/@v1Ax/Av-Aw2Ax0Aw/?v.?u+>v*=u-@x/Az-=x.?w->v.@x->v.?s.?u,Av-Au.Bw/Cx.?x-Bv-Av/Az.By.Ay,?w.Ay,?w,?w+>v,@x-Ay,@x.Ay-Ay-Ay-@x,?w.Ay-@x.Ay/Bz,?w,?v.Bw-Ay/Az/B|-@z,?y/B}/B|0Ay/@x-Az,?w+>v)r->t/@x0Ay+?w-@x-@x,?x,@u.Bw/?u->t/@v.?u->w->v/@x.?w->v->s2Bx.?v,Av.Bw/Cx.Bx,?x,?x/Az.@y-@x.Ay.Az/@y,@u.Bw,@u/Cx0Dy2E{0Cy0Cv1Ex1Ex3Gz2Hz1Gy5K}4Jz5L}3J}4L~6K}7M6L~7M5K~6L~7K}:N9MQ|>Q{@T?R}AQ}@R~AR~?P{AR}CQ}DSCR~BQyCR{FUFT~DT}EU|GT|HV|GUzJX}IW}LZM[~M]}NZ{O\}R`Q_~RaVeXg[jfpoytvsw}}{yz{|{{}~~{zwurrqqrprstxvvw{yz{|yz~~~~|}y}~~}}}wtuststuyzwvwtussqqqo}rp~o}qq~o}n|lzkymzmzm{n|k}n~rm}nl{lzm{n}m}l|l{pn}o~kzn}m|o~m|jznyn~p~m|n}m|m|m}n}n~l|l|m}n~m}n|n}j}k}pooppq~oqqtsuuxsvwxwxwwzxwwxxxyzzzz|}z{}ѻ˼оҸرԲӳҳֲԱԯӯӭխҩԪԩӣѧԨթѪӨϬѩήԮглоѷȴʴȰ̳ӱЯӱծӯѯѬҪӬԬԩѪѩԩө֤֩ҥѣӤӤҤӢԢ.9K/:L-:K.;L,9J,9J-:K-:K.;K.:K,9K.;O,9L-:L.:N/R1>P0=P1>Q.>Q/=S.@T.?Q.?R/>Q/>P1=N2?O0=M2>P0=N3?Q2@Q.=P/>Q/=Q.=Q/>Q0>R1?S1?T2=T2=T1>V2>V3?W3?U4@U3@S3@S4AT5BT5BT6CV5BT4?U6@U4?S4@R6@R6>Q7@Q9CT9BS:CT:DTF[=EY>EXI[AM^?J\CJ`DL`EM_CK^CK_DL_EM`FNaCN`EPbAO_CPcFSeDTcFRcFSdFTeFSdCQbGSeGUhFSfFSfHRgGRfITfITfHSeISeITfJQdKReLSfKQdKReLQfNSgKSeNUhMTgMTgNUhNUhOViNUhOThPXjNWhMUgOVhPWjPYlPXkRYkQXlS[pSZoS[pR]qQ\pS^qS_pP]oQ^qS`sTatTatTarTarVctUbsWduXduYfwWhwXhyWeyZgzZgz[hxZgx\iz`m~]j}^k~[hyZgxZgx[iz\m}Yhx[k{[jz\iy[hz[h|[f}]h|]g|^l~\iz_j~^j}_i{]iz_m~_k|`k}bl}cl}dm~gofpltouqwv}qxpwqxqxqxpwmtiqlupykvlvluktlvnwkvmxkvmxnyqyt|v}t|t}r}s~q~sq}r}s~s~vvy{|{|~~|}~z{|~y{wywyuwsvtwqwntfojpci{`gy`gz^ew]dw]ex\buY`sX_rY`sW^qW^qV]pU\oRZlSYoPVlPVoQWpPVnRXmPVkOUjNTiNTjPXlOWiQYlQYlOWjPWmPWlOWmOWlLThMSeJQdJQdJQdFNcDLbAMc>Jbi/=j-;h.t,=s/@v/@v0@w->r*?r,@s-@v.Av/?u/@u0>v/?v.@v.?v/@v1Bx1Aw2Cy1Aw/@v0Aw1Cx.Bw/Cx.Bw1Dy1Bx0Aw2Cy1Bx1By1Bx/@v.?v/@x/Ay-Cw-Au/?u/Cx/Bw-Av-Av1Bx/?u,=s*>v*=t*=v*=w-=x->v,=u,=v/>w.@t->t,>s+?u/?u/Av,=u+@t+?u+Au+=v+?w,@w.Bz*>v/?w/@x/@x-?w/@x->v,=u/@x+>v-@x-Ay+?w+?w,@x,?w.By+?s,>w+>v-@z+>y)w)?s)>s-=s+;r*;r->v.?w,=u+>v+>v*>s*>s+?t,=s->t.?u+v->v->v,=u.?w.?w.?t-=t+@u,@u,@u.Bw*>t+@t,Au,Au/Cx0Dx,@u0Dy.Bw.Bw.Bw/Cx0Dz/Ev/Ew/Ew1Ex1Ey1Ew1Hy0Fw3Iy6L}4J{4L|5K}5K|6L}8N6L}7M~7M~8L~PASASARAR@RBT@RCSCSERCTBRAP>Q?Q?Q@R?QARASAQBRARAQ@P?RASAS@RASCUCUCUEVDUEVBSASARFXFVCTFWEVGXGYEVEVCUBTDVASASAS?Q?R;QQR9M9M7M5K}6L}6L}9O7M~5K|5N}7L}7M~7M~5I|9M7K}7K}2Gz5Hz6J}7K|7K~6J|9J|:K}:L~7M{7L{7L|6Jz5Jz6Kz6Lz8M|7L|7L|9N{7Ly8M{7MzO/Q/>R/?Q0=N0=O1>M2?P0=N1>O3@R0?R/>S/?P-?N->N->N1AP0@P2?Q1>R2>U3>W2>V2>T2>S3?S4AU2?R3@Q3@Q4AS4BV5@W4@T5=S6>T8@S9BR:CT:CT9BS9BT;DU?GY;DU;CU;DVDZ@FY>FX@JY>FX?HZ?H[AIa?H\=HZBM`AJ`DKaCK_DK_CK^DL]BI]AJ\@M]AM]@M^@M^@M^AN_BTc@O_BO^COaGQcDOaFRcCQbFSfFRfFReGRdGRdHRdHReFQcGRcIOcJPcIPcHPcIQdKQdLReJQdJRdKReLSfLSfLSfMTgNThLRfLSeLUfLTeNTfOViMUhOViQWjPWkPXnPYnP\pT^sO[oR_pT`rQ_rS`tUawR_sTatTarSaqUbsVctUbsTctTetUeuVhvZhyYfwYfw[hxZhx[hy\iz\i{[h{[hyZgxYfw[izWhxYjzVgwXgwYfvZhz[g{Xcy]h~Zf|Zgy[gy]e{]f{\hy[gy]k{\jy]l|al}dl}dm~fogphqiqjqpxszounupwmtlskrlsiqhogoiqiqksjqktivkylykxlyq|t|s{t{u}r~s~p}sq}s~ttyxxz|~~~~Ļ|x~wzy|y|wyxzyzvwsvruptmsjqfobj}ai{`fx^fx[cvYatW^qX_rV]pU[nT[mRYkOVhPWiPWiLRhPVmNTnOUmQWoPUmOUmNTkOUnQVnOWkNVkNVgIQdMVgOXkKWkNXmNVkPXmMUjMTiJRfIOcJPfDLaCL`@Mcf3?e3Af1?g1>h2?l4An3An1Al/>i.u-t->t0Ax.?v/?s/As,@t,Bv0Cx.>u/>u/>t0=u0>v.@v.?u->t/@v/@v/@v/@v.?v0Bx,Au.Bw-Av-Bw/Cx1Bx.?u1Bw1Bu1Bx.?u+t,>t)=u*=v,?y+=x+;v0Ax-=u+;t/=w-?t,=s+t,=s.?u.>w/?t/?t/@u,=u/?x/?x,=u-=v,>v.?x->w0Ay->v+v->v,=u->v1Ay/?w-?w,v*=u)=u)=q(t->t+@t*?t,@u.=t/?u-=s-=s0Aw.Cx.Bw.Cx.Bw,@u0Dx/Bv/Cu.Ev.Dv0Fx1Dv1Ew3Hy2Fw3Iz5L}4J{5K|3K{6L}5L}8N8N7M~8N9N:MP@R@S=S@V?VBTATBQBRFTCSCRCR@R?QAQ@QASBSERERFTESAQ?O@S@RAS@RBTASASCRCSAWAVBWBSDWDUDUDUFWEWIVHVEWEWCUCTDVCTDVARAS@R>T=Q?Q?Q@R?P@Q

R:N8N7M~8N8M9O8M~6L}5O|6L}6L~8N8K~;O6K}O}>O}QR|?R}AQ}AR}@Q}BTFUFUIWFU~GVGVGW}HX~IXJXKYJX}IW|KXzLY|N[}P\Q^~P^~SbR`{Ra|Wa|Ze]h]j_kcpcpbnhqpy{z}~~|{|~~|zvssvstty{tvyyy|~|{|þƸ̸Ž}z{~{{||}{{zvuvwz|~}{yxvuqsq~q~mn~n~m}l}k{mzlzlykzl|k{l|m|m|k~l~l}n}m|kzl{kzl{jyjyn}kzjzl{kzkzl|lyn{l{l}i{l~l{m|o}o}n|o~n|m|m{o|k}n~pl|oo~opprpssrtsrssrsuvwvxwyywwwxz|y||~~~~»ŷƹɾȽĻƺ̾ϾҿijοʹǸȻƼѿһӴϱвӳղϱϰϯҬѭӭѬЩרԨԤӥԤզըҧҩة֪ӱԴҲʼտֹбѫΫҮҬΫͪѦϥөէҩѨѧШЧϤѨҥЧҦѨԤӢ.;G.;H.=J-;J+P/=M.>O.M0=N3@Q1>Q3@T3@S1>R1>U1?U3>U5@V1>T1AR2AQ1>N3?P3@Q3?Q6?Q7?S5=P:@S:>S8?S7?S:DT:CT7@R:CT:CRD]=C[FY>FZBG_AH]@E[AJ^?J\>I\@K\?K\?J\AK]@M_@L^AK^BM`BM_AL^BM_@N_AN_DQbCN`CO`CPbBP`DQbEPbFPbEN`ENaHObFNaIPcFObCOaERcCQaBPaDN`GNaGNaGNaGOaGPdGOdGOaKQcKPcGObHQdJQdKPdJReJSeIQdHPcIQeKShMUiMUhMViOWjPXkQXjRZnQZlQ\mO]lQ]nQ\oR]nSarR_pS`qTarVbtVbsUcsTarS`rWdwYfzXdwYdxYeyXeyZh{\hz\gy[gx[hy[hyZewZewZgx[hyZgxXfwXfwYewXdu[hyYgxXeuZexZdw]dw\dw\ev]cv_fy^fy_fy_iz_iz`jz`l{`m|bm}ckfmiqipjqktitlumtipjqgogpgoishqfoirhqhriqgsgtjwlwovovrzrzt~s~s~usssvusvwyxy{}}~|~~~z}z|xzz{wzorioioimemdlckbj|^fyYatU]pU]rQYnOZnQYkPXkOWlOXmNSjNSjPUlNUlOUjNWlOWkOWmOVnMVlMUlKUjLWmKUlLSkLTiLThMThNWiNUjRYnPXmOWlOWiKSeKSfJRfENbCKaBKbALbh/=g0u.=t.>u-?u->q/@t,@s-@s/At.Az-@x-Au+?t,@u->s0At/@t->t.?u/@v.@u/@v/@u->q.?r-?u.>u-?t.?t/?u/@v0Aw1Bx/@v.?u,=s+=s)=s't*v-=u.>w':r*=u(;s-t,=r+u+>v*?s't->w+v+?w*>v(;s*=u*>v,>v/?w+s,=s,=s+s,Av-Bw,@u,@v-At.Bw,Bz*?w-Bx.Cx/Fv.Cv/Ew2Hz3Gz3Gz3Fy5I{4K|4J{7K~7K~9L~6M}9N}9N}=R;N~S=Q@QASBRAS@RBTCUASAS@RBSAQBT>P?Q@R?R@PERARERESEPERCQ@QASASASCUBTASBVBUCVBTEWDUCUCTCUBTEWGUGTHVJXHUGUGUEUDWCVBUBVBT@R@R?P>P>P?Q>PR:M8P7P7N~8P6M8O8N6L~6L~7M8N9M9M;M~;L;LN}@Q?P~@Q|>N{BT~AR~>Q{?S{BT|AS|CV~CV~ER}GUGVGVET|JWJX}JW|KW{JY|M[LZLZL[|O[|N[}P\}O\}O_~Ra{U_{Ua{Ue[i_lepmslvoxnvnvgrhsnzy}}~{zyxuuruuru~||{x{ļºüưιж׹к|zz~~~}{wx{|{|yxwwtrn~pm}n~p}m{lzm{mzm}jzk{jyk|l}lzm{m{kzo}p|l|n}l{kzjyl{kzkzl{jzo|l{l{kzjzlym{nzo}o}o|o}o}o}o}n}ozq|oyn|m{o|m~opkk~qqprtsuuqrrsurvtuvyuxwxwxxx{z|}~~~}ξ̿ſ±»ƾʸŶóóž͸ϸӳԱүϱѯͯҰҲӱӯӮҫҬӨӦԥեբҦңԪשѩЩөЯұҭ̵ѿ־ѿջԺӯԭЯүѮѩҨҨӨ֩ӧѧѧѧШԩӧϩϨϧЧѣϣϤ2=G0=G0=J-;H.O0=M1>N1>N/O1>O/O/?N1=N2?O2?P0>N1>P2?R1>R1?S3BT2>S4?U3@V3?Q3@P4AP5@Q3?P3>P6AR8@S8?R8>R8?R:BT8AR9BS;DU;DV8APGX>EYJ\>I[@J]BM_AM_AO_AN_?M^AM^BM_@K]CM`@L]@M^@M^@M]BL_AL^AO_AN_@L^AL^EOaDL_BL_DK^DK^DL_EM_BM_BO`AN^AK]CM_CK^DL_FM`EM`DL_DL_DL_GM`INbGObGObHOcHObIPcJReHPcHQdJRgLTiMUhLTgOWjOXkQXkQXkSZmRYlQ[mQ]nQ^pR`rR_pTarQ`pRarR_pTaqTarUbsVcsTasUbuUbuVbvYcwXcwVcvWcxZex[fx\gyZhyZgxYfw\gyZevYexVauWduWevUbrXeuVduVcuYdvZcv[cvYatYbt\dv[bv\cw]dw^fy^ey_fw^gx`j{_k{_k{ai|ai|bj}elgoeodp~gpfoel~iphofodm~hphqhqgpfogpgngshtjwmwnvnvowpxs|p|q}s~uuuxtvz{{z{{||{}|x{z|rwgn}dk{cizbhz_ey]ey]e{ZbvU]oS[mQYnPWlNXlMXlJViKTiMTjMTiMSiNTlOTmNTlNVmQYnOVkOVlOVnMTkMVlKWjJUkOVmNVnMVlMUjLUhOXjNVkOWlNVkNVkNViMUhKSfGPcCJ`AI^AI_?Iad1>i/v->v,t.?u.=s,=s.>u->u+@q-As,@s,?r-Av-Av,@u+?t,@u,?t/@v0Aw.?u,=s-=s-t->u0Av->t->t->t+s.>t->t,=s,=s.>u';s(;s)t*;t*;s):s+s,@u,@u*>s,@u+?t-Av.@x-By,Ax,Av.Bw/Ew1Dw2Fy4H|5J|7L}3Gy4I{6J|6J~7K}5L}7L~;P~;P=RS>T>R@Q@RCUBTASCUBTBTBTASBTASAS@R?Q@R@RBQBQBQBQESFTDRFSAQBR@SBTATBTEWDUASBVBSCUASDUDXBTDTCUGTFTFTHVGUGUHVGVEWEXCVCVBTAS@R@O?R>P>P@R?Q;Q=Q>Q9O5N~6N~7P8Q7L~6L}6M~7M~5K}9L;O;N=O=O;M=N;M:M~;MR|@S}BR~@Q~AR~AR~@Q|?P|AR|@Q}@RzCU}CT{CU|BUyFVzFVzGV}IYGV~KZKZP^P[Q^O]P\L[~O]O]P^Q^}P`{Ra}R`~Sa|SbzTd{]i_ldpixw|}{u}q{t||z{xvvwvuwspxzxy~ý³Į϶Ӻöþ}}|}{z{yutropl|l|lxlzlzkxmzkzl{kzjyhzhzkxlzo}kylxiyl{l{kzkzjyjykzl{jyl{kykzkzjyjyhxkwlyo}n{m{o}p~o|o}o|o~mzn}lzo}o}n|p~m~n}qorutsvuuxwusuvvwwxwuvywx|y{}|}}ʽ˿ÿż´¸ƸȺǾϿʼ˸ʶбЮӯίίѭЬϭϰҮϮӪӪѧԧդդԥҦѨө֪ӪҩҪЮЭаҲѵηѻйϸϵ̭ͯͭЯҮҭѫϪЪөԭ֪ӧѩϧΩШҧѩΩШϨϧͦѧѦ3K1O2=N2=O2=O1=O0P0=M2?P2?P4AT1>Q3@S0=N1>O0=N0=M/O/P3?P2>O2>P2>P2>P3@R4>R3>S2=Q2@R2@P4?N4?Q1=O2>P5?Q6>Q7?R7?R7>Q9AT9BS;DU;DU:CT:CQ>GV9BQ;DT:CT:BU;CW=DV?FYGWK\?L]BO`?K\AL^@K]?K\AN_?M]?J\AM_@K]@J]?J\?J\?J\?J\@J\=K\>J[?J\?K]AI\AI\BJ]DL_CK^DL_DL_EL_HM`GNaGQcFPbEPbIQcKRdJQcLSfLSeNUhOUhNUhQXkPXkNViSZmSZmRYlRYmR]nR^qR_rR`sPaqO`oRaqR_pTarSarS`qS`rVcvUbuUbuUauXbvVcvVcvWcvXbuWbtYdvYdvXduWdvVatVbwWbwWbvVbtUasVasUasVarWbtV`rX`sX`sZat\cu\cuYbs[cu[cw[fx[fxYfv]gx_gy]iy^jy_gzbj}ck~ai|ck~cl}bm{cl{dmfmhofngpfp~bn}epgpdmgnirdofrfsismvmtmvowpxqyrzrzu~uwxz|||yy{{źy{pvnsfm_ex[ctXarV^qU]sT\tT\rT\pQYnPXlNVlLTlNWmLWkLWkLWkKUiOWlNVmOTlLRjMUmOWnLTiIUjLVnKVlLVlNVnJVlKSkNUoPWqPWqNUlMSiNSjLUjNVkPWmOUjPWiNUhNUhIPcDL_BK]?J\?K_;G^i.=h/>h,;f-v/?w+s,=s+s*?t,@u,@v+?s+?u,@u-Av+?t*>s+?t+@u*=v.>v/@w,=r*;q-t,=r)=s(:s*=u*=u)r';p';p':p):p(s+?t+?t,@u,@u,?t+?t-Av+>v,?x-@x,Av.Bx.Du.Cv0Dw5Fz6G{5Gy3Ew4I{6K}4Hz5I|7M~8N|:P>RS>SOAQAQCSEVBTDVBTDVCUCT@RCVBUGWEVFSETGUFTHVGUIWIWGUCTEVDXEXCUAS@S@QAS>P>P~?Q?Q=SO>PP;M=OP~>P~9O}:O};M|=O~=O~=O~@R>O~>OzAR}?Q|BSASARCTASCTBS~ASAT}CU|CV}DV|DW{FWyFUxFWwFWxHXyKZ{M[~M[O`P]Q`Q]P]Q^TaVcWdVcTaUaXcYfYf~Zg~[k`leqjur~x~}{|xxyxwvsstsv{z{xzx|ιѻƽÔ}{{yvvsqqol|l|l|mzkylylzixkzhwjxfzhzm{kylzjxlyjykzgvixixixixixl{m|kzjyl{l{kzixjzlykyk{m|n}m{l{m{n|n|m}n~m}mzo}o}o}p}opm}qqtuxvwwxxxxxywxzwxxzyyz{}}|{~Ƚǻż¿¾ǶŻƵǼȸζε̷̺ǴŶ˵бϯѯѭѩϬϬϫҮԬԧЧӨԧӤդԥѩҩҪըͩЪЭӭЬϭίϳԳѳѴѱΰѮЮҭӭЭѭѬЪҪЪԫѪЩͨΦ˩ΨϪѮάѫЫШϧͨҨ1=C-:@1>F3>H/=I-O0O0M0O0=N/O1>O1>O/?P,=M2BR-=N/@P/@P.?N0@O0>N1=N2Q2P6=Q7?Q8@S8?S6@R5AS8CT9BS9BS9CR9BQ;BR9CR:CR9BQ:CRK\?L]>K\>K\@M^K\=J[=J[>K\?L]>K\@K]>I[=HZ>I[>H[I[=HZ>HZ>I[>I[>HZ?K]L\@K\AI\AI\CK^AI\DL_DL_AI\EM`HNaGLaGNaDPbDRcGQcHPdKQhJQfKReIPcKRePWjQXkOVjS[nQYlSZmRYlS[nS[nT\oP\mQ^pQ^oR_pQ^oT`rS_pT_qVasS`qTatUbuR_rTatS^rT`tVbvVbvU`tVasUasVasWcuWbtXatX`tW_tW`tX`uX_tW_rW^qW_rX`sY`sYatX`sW_rY`sZas[cuWarXetXeuYeuYeuYeuYeu[gw[hx^iz`h{`h{ai|`h|aj}aj{`jycl{dm}cl~em~dm|fo}cn|eqepgofnemfoepgsgrhtitkuksowowpxsyu|t}uwy}z}z{|~{}~{zvwip}bjxYauT]pT\oOWkQYoT\sRZqQYpRZqNVmOWnNVmNVmMUlLXkMXlLWkJThNTjLTlLSkMSlMUlNWmJViJVjLWmLWmJUkLVlMUlNUlOUoPWqOWpNVmOUlOVjOWlNVkQZnPWlRXjQXkMTgIQcFO`CL]>I\?K_;G]v):o,=s,=r*?t*t*>t*>s)=r's+?t+?t*=u);s.>v->w,u+;r+;r-=s.s*?t)?t+?t+@t+>w.Ay-?x-Au.Bw-As1@t3Cw3Dx4Ez4Gx3Ew8J|8I|5J|4Jx9O}7M}9O~:O|=R~S=R>S=R@U?TASASASASASASBT@R@RAS@R>P@R?Q@R?Q@R?T?QAQAQDQCQCPERER@R@QETFWDUDVCU?V@VCSBUCVEUGTFTHVIWGUIWJXHVHVHVEWFVFWCWDWFXEVBS~BSBTAQ>P~?QP~=O}>P?N:L|Q|>Q{>Q|>P{=Q|@S}AT@S~ATCVAR~CTGTEUGWHWHWGWDW}DW~GU}HV~GV}HW|GWzFVxHVxGVwKZzL\yP_}N^P`ScS`R`SaS`UbUcYfWe\k[k[j`j`l`kdpjvmyp{u}Ū~~}{{zzzywxvuutwyzywx{}~˽ʹ¸~|zyxwvqppom}l|jzkylyk|jyiygvjyjyixjxlzlzlzlzmyhxjykzhwhwhwjyixixl{ixjyixixjyixkzixlzjyl{l{l{n}l{m|l|n~m|n~m~o}o}o}p~rqtrtsywvwz{x|{yz|yyzy{z|}{}~}~~ɾǻǼ˿̿ŽĿƻļ˿ɻŻ̾ȸʵϯЯ̯˴ͷ̺ͺ˹ƿͽȸʹήέϮѬҨЩЩѧϩԨѥө֥ԤӤԧҧϨͬέԧͫѪѮҬЬЯӯӱԱ԰ӰӯѰҬӭ׬ӫӪЬЬЭЮѫϮѬЬЬͯЭέϯЮ̭ά̭ҭѭѪϫ0H/=I0>I,M/O2?O2?O1>N0M/O1>O/N1>N1>N0=O1>O0=N1>O/N/?O/?O/@O/@O/?N.@N2@P5?P5=Q3;N3P2=N7>P7?Q6@P8?Q7?R6>Q7?S3@Q4@Q8CT9BS:CT9BS:AR:@Q9@P8AO:DQ;ER:CPDU:ASGX?HY;EU?HZ?GZAI\?HZGYBI\BJ]@H[?GZCK^AI]BJ]@H\CK^GN`GNaHOcCOaDQbGPbKQeJOfJQeLSfJQcMTfNUgQXjQWkPXlPXkS[nSZmSZmU[nT[oU^qQ]nS]oT^qS_pT\oU\oU\oV_qQ]nS]pUbtU`tU_sTatV`tS]rT_rU`qUarZ`tW_sYatYatW`rW_qU]pY`vV^tV^sU]pV^qV^rV^qV^qU]pU\oV]oZat[asZbqWcrXdrYevYevYeuYfv[gv\fu]iz_gx_gz_gz`h|`h{_hyak{`lybkzbkzeo}dm|cl{dm|bl{am{epdm~dmemdldohsitkvjujvmxkuqxpwqxu}vuy{~|~{||y|~|{|sou}rz|{xoqwgo{ciyY`qY`qPYkOWiPXkOWlOWmRZoMUlNVmMUlMUlLTlMWmJVjKVjKVjHSgJUiJRhNUlLRiMVjLUiJViIUhIUhIUiJVmKVkMTmMSnLSnMWqQWqOWmOWnPWnQXnPWlS[pPXnQYnOViRXkMTgJTcIScCOaAL^?J^=I`;G`;Fa6B]3B]4B`4Ab3?b4Ad3@c4Ac2?b.=a.v->v*r)=r)=p*>s+?t*=r(=r(=r(=r(s)>s*?t(=r*?t(;t*v->u+s+@u*?t)>s)>s+>s+?t,@u/Cx-Av.Bw-Av.Au2Bw1Bv2Cw3Cx4Gx5Gy8J|7I{3Ix4Jy8N}6L{7Pz8O{S=R>S=R>SBT@R@R@R@R@RASASBT?Q@RAS?Q?Q@R@R@Q=S@Q?R@P@PCPAOESESESERDTEUEVDUBUCUCUBVDVDVFVGTGUGVFTGUGUGUHUHVIWGUDWDUCTDWEXGWCTDUDUAR~?P~>P~?Q=O~?Q~;R~:O|9O{:O|:O{7Nz;Kz9KyD0>C/>E/=F/>G1?K-N0=M.:J.:J0=M1N1=M3;L4;N5P2?P0=L1>N0=M0=N/N1>M0=M1>N1>N0=N1>O1>O0>O0?P2>P2?P2?P2>N3?P2>N2?O3@P4?P3:N6O9AR:CU9BS7@Q7@Q6@Q7@P9AQ8@Q9BS9BR;AT;BT:AS9@R9AP:DO9CP:CTGX@HY=EX@H[>FY=FY;FX;FX=HZ=HY;FX;GX9EW;HY9FW:GX8EV9FX:FW8FW;HY;GY:EW;GYEYI[:EW9FW:GX=J[=J[;HYKb:F_9E]9E_7C^5C`3Aa5Bb4Ab2?a2@b1>a0?a1?d0>c/=d-s+?u)=r';q(;s)w+>v*?t(=r(=r*?s(s*?t*>t)>t*?t)>t,Aw-Aw/Cu-Au,@s.Bu/Bu/Dw4Dx6Fz4Fx6Hz4Hz4H{3Ix4Jy8N}7M|8O~9Pz;P|=Q~=S?T=R>S>S?T?TAR@S@SAS@S@S@R@R@R@R?Q@R?Q@R?PSAS>P@RAPARDQDRBP~CQESBSDUEVCUASBTASATBUFVEWGVHWHUGUHUFTGVEVEUDUHUFTCVEVHXEVEVFWDVDUCUDT@R@S=O}?Q~O~>P~>P~>P~>P>N|>PS}BS~@Q}BS~ARBS?RBRCTBTASAS~DS~CT|CTEWIXM\M]L[M[M[O[MZN\N]P_Q`Q`P`P`QaPaSaR`UeVfXhXdXeWdXeXeXeZgZg[h[i[i[i_j\gclclgpfojsowrzw~ľȧɩͰгͱί̫ϭԳȩĽÿϹտ±ÿ~{|}~{|{uvuxxvwzwwvwzy|›~Æ}||yyvuurpqm}k{l|jzlyj{hxjzhxjzhwhwhwixhyixmzm{ixjyhwhvgvgvfugvetfufufuixhwdshwhwiwjxixjykzkzjzk{k{m}m}n~m~no|o}qusssuvvuxwxy~|}}~~ĻǽĽſɾƿʾƾǿǸŵIJǴźǾ̷ʮưȯǮdzɷʻɸʹϰʭʮϫ̪ǬʯͮʫɮͫϭʰɵȹкͶϳ֬ΫΤͨӥͦҥЧѦЦЧѥѦҦѦ֨ϧΪͭҬЮҮѫΰ֫ѫҬϬЯϮѮЭЬЮЭҬϭѮүӭѮҮҫάϭЯҮѯабϯͱͲϱΰήͰͱҰѲ5?F3?G0>F0=F0>G2?H1?H1?H4>G3>I29H29G2;G3=J1>L1=L.;K/N/;L/M2=M2>N1=M1>N0>J0>K1?L2=L/=J0>L0=J/;I2=L3N.;L.;K.;K1>N1>N1>O0=M/N0N2>O1>O1>N1>N1>N4AQ3?Q4>Q7>Q8@S7@R8BR6AP8AN7>L7>O8?P8@P6@Q8AR9CR8AP8BP6AR8@R7@Q8?Q:ARFXGX=FX;CV>FY>FY=EX=GY9EV9EW:EW:EW9DV9DV:EX6CT:GX:FW:FX;FX;FX;FX;HX;FY>GYJ[?I[@GZ@H[AH[@H[@I[?K\@L[DM^EN^EL^AM_EL_FL_FM`FOaFQbKReMThOTjOViPWhNUePYePWeRYjU\mT[kU\mS]mQZkQZkT^nS[lP[kP]mP\mP\mQ\lQZmSZnSYlS[nRZmQYlRZmT[nS[nS[nU]pR\oR^oS]oS\nU]oW]oV]pV]pU\oU]pS[oRZmU]pT\oV]qX^qW^pV]oX_qX_rY`qXapYbpZdp[fqVblXdqZfqZfrWdrZes[fp^gw]fw^gx_fz]fy^gw^gx_hy_hx^gvaj{`m|`k{dl~cl{dm|en}fpfpfnhpgohqjqjsgrivivjwjvmzmwnymxp{s~vz||z{ytvqq{r|szt|wz~~~xxtvsunrqrorkp|ik{jlzppztswsoowopvnt~{m|u}z~||xx~wvmnzbgrX_jOXePWfLSeLUfIQdKSgJRhIRgKShKShMUhNVlMTjLUgMUhLThMThLRhLRiNTjPWjPViMVhKTgKTiMTjMSjNVmMVmNYoMZpO[qPZpQXoMWmO[oLWmNZpJUkMWmMUiKSiKShKSfKSeLSfHQeEQeDOcDOdBMb@Ka>Ka=Ib;Fb9D`9Cb6A_8Cb7Ab6Bb2@c4Ac2?b/=`0>d/>b.>c,;c/>e-s*>s*>s&;p's';p'v,=u,=u->v->v*p&s)>s(=r'r(=q)>s(t/>u,=p.>r.>r-=q,

r';p';o(o(=o)=o(>q*@r)@q)?q(?p*?q(?q*@r+@v+Bs-Cu.Cu-Cu/Bu,@s/Cv.Bu0Cw1Dx0Cv1Dv2Fx5I{4Gz4Jy3Ix5Kz8N}7M|9O~

S?P~?P~>T>S>S@U>SASBQAQ@QAQBSBSASBSBS@S?Q@R?Q?P;QQ@R?Q?Q~BR~BR~AR~BSCSCTCTDUHYCTDUFXDVFXGUJYJZIULYL\M\MZM[N[N]Q]T_Q_R_RaRaScQaScUeSdWdVdWeWfYeZgZgXeXeXdXdWdZgYf\h_j_j^i`k`laljppurvuyx}yŽǼȾǫвͭǪƽüƨͮƨǭŬīƮ϶жƬêʱȭŬƲĴ~~||}xyzwyxvwwwxwyzx~¤ÆŃ~~||}}ywuttpn~n~on~l|lyj{jzjzjziyiyhygviyiyjzlylyk|iwfugwfugvfufufufuetdsfufufuhwixiwkyhwixjyk|iyjziyl|k|n~n|o}n|rrqrrtvwwwy{|~½ýƿ¹żźƶɳʯɬƯƳˬɩǨɫɪʭʰɱȳ̲ϩȩ˨˨̩˭ͭͮέͫ˪ͫέͮʯȱȷɷ˲ʯͮЫЫ̥˩ѦѣͥͥЩӦЦѦΧϥΨҨҦЭҫЬЭԫάӪЫѪЩϫϫϬέϮϬЯѫΪͯҰѮҮЬЭѭѯѬϬϮѬϯӱаαͲϯ̯ͱϰΰϰίͰ4>F5>G4G2J2>M/=L/K2@J3?K2=I2?J1?J0J3=J3N/N/M.>M2?O2?O1>N2?O3@P1?O3AQ1?O3Q7@S7?Q6@Q4?L5?I5=J8>O7=N6=M9?P8?P9?P9?P8CR7CR7AR6@Q8?Q9@S;BV;BU;BS;BR9@R;BUJ[?KZCL]BJ[GNaBL_DK_GNaHNaIPcKSfNSgOTiNUiLSfOWgPXdOWdNVcQYfQYhRXjU\lT]kQ[kR\mR[lQYjP[lK\lL\lN]lO[lO[lS]pRZmRZmRYmRYmPXkPXkPXkRYlSZlQYkOZmPZlR\lS[mT[mU\oV\pU]oV_oT\nS[oS[nT]oV`qW^pV]oV]oW^pYarZdrXaoZeq[erYenYfoZgqZfsYerYdp\er\fq[es]fw\fv\dw[dv\ev_hy^hxakyaj|ak{bn~am~dn~en}gpen~fmfnemhognhpiqjsgthulzjyl{lxn{nxnypzq|vuuwwurq~q}p{pzowlvnyryqwrwvx|}}||{z|uwssmlij|dhwbhu`fsadq`cs_ct_bq`ala_hc]jh_gthh~{urktoq~x~y~zw{wu{qr{os{iox\doQZfQYhLVeKTfLSfNUiMUjNViLTgMUhLTjLThNUhKReJRdMTfLRfMRhOTgOWiOViPViNUhPVjQUlOVlMVnOWnOZpLXnMYoNYoNXpNVpKWoHTkKUkHSjIUkHSiKShKShKSiNVkMUiKQdLUhFSgHTgGSgEQcDQeBNe>Kd=Hc>Ie;Fa;Ge;Ff;Ef9Ee6Cd5Bd3Cc3Ad1?e2?d3Ag2@h/?h0?i0?j1?k/=i+8h*8g'9e):f*;g(9e+:g);g%;g%9g%7f%6e&8g#4c&5g%6f&7f(8g)9h'8g'9g&8e&8e&7e%7e%8f$6d%7g%7h$7i!3e#5g$6h$7i%5h%2c$2a%3a%3_&3`&3c&4c$2a$2a$3b$3a%3b%4b'3c%4b"4b#4b 3a!4d!3c!3c$5e"4d2f3f4f3e5f2e"1f0d!2f 0e 1e!2f 1d!0e!3i"3g!2f#4h!2f0d!2g!2i!2j 1h0f%6j$5i$5i"3h$4j%4g$5i!2f#4h$5i#4h$4h&6j&7i$7h$5j$5i%6j&6m%7m&8n%;o&;p):p*;q'8o):m*:m*s)>s)>s+?t*;r+;r)9p):p&7n'7n'8n'8n%6l&7m%6l&7j'8l'8l'8l(9n):p&7m'8p&7k(9o):p(9o&7m):p'8n):p):r(9o'9n%;o$8m#8m$9o$7o&7m'8o(:p(9p&7m'7m(8n&6m(9o):q'8n(:p'9n'8n):p):p):p(9o(9o(9o'7n)9p+;r):p's*?q(>p+>u*>s)>t*?t)=s)=r+>t*>v(=v)>u(>r'q*?q(=r&;o(=r(=r)=r(o(=o)>p*@r+Bs,Bt,Bt,Bt,Bt+As-Cu+Bt+Bs.Dv-Ct-Cu0Ev.Au0Cw0Gx.Fw0Dw1Ex4Hz6J|3J{5K|5Ky8N|5Kz8N}7M|8N~:O~=R?P~?P>P~S?QASAP@PAPARAR@Q~BR@Q~>S@R@R?Q>O?R:P~P?OAPCQCQBP~BSARAS@RASDSDUBSBT~BS~BT~DVDUIXIXGVHWDVBTEVDUHUHWGUEUEUEUEVETHVETDUDUDTBRAS@SBRAR@S@SCVCTCUCUDVFXGXFXHZHZGYHZLZLZMZN]NYN[N[P]MZO\Q^R]P\O[R^T^R`P^Q_S`PaQaRbRbUcSaWeVeYfZg[h[hXeXfWfYgYgZh[h\i`j_j`kfqnvv~yƻ˿¥ɫʮũ ȤʫˮίˬŨɫɪȪɭδаȫɿǽóλɷ÷}}}~}yx}{y{wvvwxxwwx|~~}}{z~~{{zxuvtqpm}k{k|m}l}k{l|jzjzjzhxjzhyhyjzk{j{lyl}hxhxfugwesetescreucseqetetgvfvhtgtiviyiyiyiyiyiyiyj{mzo}n|o|p~srrsqsuvvxx|~ĸȽĿ»ö̾ùǴ˯̭ЭͨƦƪʨ˨ʨɨ˨Ǭʭ˪̬ͧ˨̧Ψͧ˫̬ˮͬ˪ɪʫʫʮͫͭͭ˭ɬɪͩЩϦϥͧΦң͢ΦЧѩЩϩЩͧШЧѪЪϫϪЫҫЫѬҩϫѪѪͫѮϫͭϰѪϯҮѬЮϯѯҮѬϮҭѬϭҫЬЭѮίϱ̲ϯΰβаϰͰ̱Ͱ6>I6>I5>G6?H3G4>G5=I4K0J3;I39K3K3>J4>J7=K4;I6>J29G4;L5K0=K/>L1>O1=L3?L1=M4>O5?O4>N4>O5?P4R;AU:AS8>O7=O8@M7AL6AL7AL9CO7AM9CN9CN8@Q9?Q8?O9@Q;BR:AQ8?Q;BU:@S;@S:@R:BP9BQEX:BU=EX:CW8DU9FU9CU:DY;GX:FX8EV:GX:FX:GX7DV>EX:DV;FY9DY;EY;FW:EWFY>EX?GZ>GY?EYAH[DK^@JZEM^BK\CL]CL]DM^FO`EO_FO`IRbKTeLTdKRdMTfNUeMTcOWdMTcLS`PVdOVcO[fS\jU[lT[kSYiT[kTZkQZiO\hP]jMXgNZjM[kKYhO[mKWiN[jNXiOWkQYlPXkRXkRXlQZkQYjSYkSZlSZlRZmQ[kU^oT]oV^pV_pU^nT[lU\lU\lW]oW]oYaqY_r[btZas[cr]eq\dn\cn]fp[epXblXdnXdpYeqXft\et^ey_fy^fz_gy^gxbk|`ixdkz`kx`m|_k|am{`m{alyem}dlfnhqfoeqfrgugtfthuivivlylxkwlvjvmymzp|prs~stsrn{kxkyjwhthugrktmuksmsnpqrrsmojkbg|`ew^dw^bw\bs[bs`btZ]nY\n]at[^o^`qabsddrghqts{{{~|~~lmqcbjgesmkwjlsopxut}rqy|~ux_al\coT^oPYkN[mLXiMXjPXkNViLSfMSgIRfFSfHTgJUiHSgIRfIQfMShNUgSZmOViNUiMSiOXmNUjOVjMUjLXlMXnLWmMXnITjGTiGTlITlJUkJTjLSjKShLTjLTkLTkLUiJWjHSgJTiLShMUjJRgLUfKQdGPdEQeEOgDNjCMhDMiCKk?Ih>Jhi.=h.=h.=h.>i+r.>r-v+q)>q)@q)@r)@r*@r(?q&=o(>s'=r)>s(=s*?t)=r';p(r):p+q+?r)=p*>q*>q*=p(=o+?q+>r+>r+Bs-Cu-Cu,Bs.Bu,@s*>q,@s.Dv-Cv.Dv-Du-Dv1Gy3Ex3Fx5Iz3Gz6J|6I|4K{5K}3M|4I{6L}7M|7M|9N{9N{O}>Q~AR?Q=S?R@R?P?Q>P>P?Q@R=S>P?Q@R?Q:O:O:Q9P=Q>Q?Q@R?Q?Q?Q>P?Q@Q@RAS@R@RASBTATBUDTBSCQ~DRFSGVFUETFUGVFUCUCUFWFXCVBUDVCVDXCVDXEWDUFWCWCWEXEUDUBSCTBS~DUETHVIVFUJYKZLZJZJYM\L[N]N]M]L[N\N]M\P^O]O]O]P^Q]Q\Q^S`R`P^O]Q_S`UbQ`TbUdUdWgXdZgXe\f\eXeWeZh\fZh[h]j`l^lanhso{s{~~ż¹¹æϭϬ̧ͨɬǪ§̿¨ŪèèŪǮƭǬ®Ŀ~}|{~~|zyxyywyyy{z|z|{~~~~||}}~|~}|z{xvuquqpn~np|n{lzk|k{m}iyk{hxiyhykyjwjylzjzk{iyfveydwatduetdsfsetdtdsetetfufuhwgvjwkzkykyhyk{jzn~n}n{n|m~portrvuvxxvz|~ÿþųľ·DZʫǨȥɧ˧Ǩǩɪ̪˧ȩɪ˨ʬͩ˩ͦɧʣ̤ͩ˦ɪ̯ѭЬΩ̩ͪͬЪͪϩ̧̨ʩͧͥΦΦΥͦϣͥ͡ΥΦΧ̧̩ͥϨ̨ͨͦʩ̬έѩЩϩϥ̬ѩ̫ͪΫͮЮѯΰЮίϭϲԮЬͮϯͬͬΪͫͬͬЭЭϮααϲʲ˱˱̳̱Я̮7@I6@I3;D5F5>H6?H7?K7J5>K5?K4>J3=I2M3@M0?K0>L2AN2?L2@N3?L5AM3M0M.:K0L1>L2>L3@N1>L2>M3>N4O2;M5>N6?N5=M7@O8@P8AQ:CT:CR7@O8AN:CQ8AP8BP8CN6@K7BM;DQ8AQ6>M8?O;CR:BO9@O7>N7>P:AR9?Q9?Q:BT;BS9CP:CS:DUFY:BU;BU;EU;GW=DX;DW:DW:GX:GX9FW:GX9FW;FW:EW8EW9FW8EV8FW:FX9FW8FU:GV;FW>DX=EX>FY?GZ@HZBH[?GZBI]BK[AJ[@IZBK\CK\CJ^CK^EM`GPaHQbIRbJSbLSdLSeNUeLTbMUbNUbMSaOUcQWeRZhT[hR[hRYjSZjT[kY^nV^lQ\gQZgNYgLYgMYhLYjN[kM[kNZkO[jPXjPXkQYmPXjOYjPYkRXhPWjPWiRYkU[mRYkR[lS]mT^nT]nT]mU\lU\lT\kW^nX^nZbqYaq[bt[bt\cr\dq\eo]ep]epZcoYcl[ep[eq\fs^gw_gx`gy_fy]fwai{_hxaiybixbkwblydn~`m|anzbozbnzem~fogpemgofqeqgtfthuivgtfsgtivivkujvlylyo}n}qq}r}r~p~lyjwhugtfsdqcobmcncmelek~ch|eh}eg|ag{_cw[_rZ_r[`r[btX_rX^pU[kV\lW]mSXhSYjY^l]`p^`pdfrdfr`bmhgohgrcep^`n_an`amehtno{onyrqztu~knyhl{bj{ZduX`rT\oQYmPWkNVjLTgMTgKSgJRhHSgJUiJUiIThLUiLShLRgOViNTgMThLShMVkJUiITgITiHSiGRhGRhFRhDPfGSjHSkHTiHRiJRiJRgKShKSiKShMUjPWmMVkJUiMTiLTiQVmPViNUfLUhKSfJRiIRiFRiDOiFOjFLhBLiBLhh+>i*=h+>i+>i*=h)=h(=h)l0>l/@n*;i*:k*;l*9k)9m(;m(;k%7g%7g%4e&3d&4b'4c)7f&8f&7e%6d%2b&5c&5c%3b'5d'5d'5d'4c$4a$5c#4b%7e%7e%7e6b5b 5e4c4f2d3f"6i"6h3e#5g"4f"4g!2g"1f!2f"3i"3f#4h#4h"3g!2f!2f!2g"3j 1e"3g!2f!2e#3h#4h$5i$5i#4h#4h$5h%8i&8j'9k'8k%5j%6j%6j'8l&7k'8l(9p*;q*:q+;q,s*=r,=t.?v-?r.>r.?s/@t->r.?s+r->r-=r+r-=q*9m+w0Ay,=r+q+?r)=p(?p(>p(=o(?q(=o&s)=r(s)=r)=r*>s)>s(=r)>s(=r)=p*?r't.?u/@v,>q+As+?r*?q,At,At*>p-As-Bt.Bu,@s+Bt,Cu-Cu,Cu0Dw.Bu0Dw.Au.Dv.Dv.Dw2Hz0Fw1Gx2Gx2Ex4H{5H{6I|5K}5K}5J|6K~5K|7M{9N~6L|:O|;P}=R=Q=R;P~@P>P~?P~?P>S>P?Q>P>PAS>P>P=PP9N;Q7P:P9PP>P@R?Q?Q@R>Q?Q@R?Q?QBTCUCUBT@S}ARAS~DS~DS~GTGVGVFUGVGVGVCUCTCUCTBTCWFYBVBUCUCVGXGTDUBVBUEXFUDVDTDUEVEVGVHWHXJYL[IWKZJYKZKZL[L[M\M\N]N]M\P^O]P^P^N\Q_Q]Q]Q]P\Q_R`P^R`SaRaS_UaScTdXdWdXeXfXeZdYfYf[f^i]hYh^k_l`neslvp{nvsyrzw||~üùŽžâ˦̧̬ѲŨĹżĻƼŻéȫʯ~||}y{zxxwyzy{xzy|{}}|{}y{{~}|||{zyx{utsstppn~l|ol|m}m}l|jzk{k{hygwkykyjxlzk{jziyhwdxdygveufvcsgterfudseugsgvgvfuhwjxm{mzmzj|m}l|jzm{n~o}oprstuuuwxxxy~~}}¿·¿ǰªб¨èɥǣǠţƧǦħɦǦɧ˦Ȩʧɧ˧ɠƤͣˡʤˡɨ˧˪̪̩ͩϩΩΪ̩̪Ψ̦ͩͨʧ̦Ϥ̤̤̦ͥ͢ϣ̨Уɧͤɧ̦ͨΩЩ̪Ω̪ͨΨϨϧΪΪ̪ͪͭϭ̬̭̫˭ͮέͬͬЬϫ̭̬ͭͭάϫϬЪάϯѯήϯβͱ̱̲̱̱Яͮ7@H8AI6=F8>G:=F9@I5=G6>G5?F5>F9?J8@J8@J8?J8?I6?I4?I4>I4>H3=H5=K5=K4I4>I5?J5@M2>M3?M1>K3BO0?L1?M4@N2>I4>J5>J6>K4;I5;J5=J4O7>R3N6@P3?M1=K2>L0=J1>M2=N5>O3>O1>N1=L4I0=J2>L2=K4=K4=L4>L6?N6AM7AM6BN6AN6BM6BM6BN5BM5BI6BP5@N8@O9BR7@P7AP:AR7>N7>M;AO:@M8AM:BN8@N:AQ:BR:CU9?Q7AQFY:DW8DU9CV9DV8CU:EX8CU9ET6CT8EV7EV6DU7DU:HX9GW:GYFY>FY?GZ?GZ@GZBI]BJ]@L^AK]AI\AJZBK[AK[AI\CK^GPbGQbGPaIRaKSbKRbLSdNUfKRbOWcQXeSYgTXgRWeSZfSZhSZhTZgTZgW]l[bo]drS\iOWcMVcOXhOXhKYfNYhP\lN[jO[kQZkRZlQXkPWiOYhNWgOVgQXgOVfQXkSZlQXkR[lS[mSZmT[mU\mV]mU]mW]mV\lV\lW]mY_pYas\ct[br[bp^dq^eo^fq^fq\ep]hq[eq\fs\et\et_ev]dt]gv`ix`hxdlydlzbkwep}hpdm|dm{gp|dn{fofpdpepfpepepfqepcobodqfsgtgtiujuitjwmzmzkzo|o|o|mxlymzhuhuerbo^k|[iz\hy[gyYgw\fx^ex^ex^cwY_rY`sZas[at]duX_qW^pU\nT[lSXgPWgPVfPWfPWdSYfU[fV\jX[k\^nY\l]^nhizjl{bev`cs\^n_aoegtght^`m[]i]`p[ar]ct`gw\btU^oS\nQZkR[mNWhOViLUhMUiKVjJVjHVhHUhHTgGRfHRfLSfNUhLUjJUiJThHSfGRgFQhGQgGRhFQgGRhDPfGSiFRgFPfLShIQfIQfLTiLTiMUjOWlNVkMUjOWlOVlOTjQWmPVlQWjOViMThIQgJRgKSiJQiHPfJQiHQjEOj@Mf>Hd;Gc:Hb9Gd8Gg6Ef6Dg6Df5Df4Dj3Bk3Cm4Co4Cn3Cn3Dp2Cn/@k-@k-@k-@j-@j.Ak.Al.Al.Al.Al,@k,@k-@l.@k,=i.?k/@l1Bn->k0Ap->m-;m.=n+r)=r)>s*=s+r/@t/@t/@t0Au1Bv->q,>q.@q,>p,?p.@r,=r+r,=q+=q,=p,=q+q/@t->r,=q+r*:o,;o-=q-;t(9o*;q):p+t+p'=o)>p)@r*>t)>s's*?t,@v,@v+@u*?t*>s,r->p/?t->r.?r,=u.?u,=s.?r-=q-=r/@t.>s-?q0Bt2Cv2Dv1Cu1Ew0Dw1Dw2Fx/Cw.Bu0Cw0Dw/Cv0Dw/Fv0Fw1Gy3I{1Gy3Iz3Ix3Jx3Ix3Hw4Kz6L{6Lz4Mz9O~5K{7M}6M|:Q};P}S?P~?Q>P~=O=O=O~P?P>P=O?Q?Q:N9P9O7N~;N:NP?Q>P@R@RBSATAS~CSCTGTCR}DT~ETGVFUETGVIXDVBSESBTBSCTBVDTDTDUBUDUFSEUDUCXEVBTFWFXFWDUFWGUIWLYIXHWJYKZJYJYIX~JYJYKZKZKZL[N\O]KYM[O]N\P^P[R^Q]R^Q_O]R`Q_SaQaT`TbUcVcVcWdUbYfXeXeVc[i]g_j^i]h`j^l^ncrfqgrhpktqwtxtzy|{~~~ûžľĿƩǧƥɥͮ˪ý¼»üſýſƬƪ¦Į˵ҿƸ}~}||}z{xxzyzywzx{z|~~z{{{|zz{z|||{||zyvurrsvvqppm}m}l|k{m}k{l|l|l|hxiylzjxjxkxl|hxgwgvdygvdtbrfvbrfsfudsfugwgugugyfviyjzk{k{j{mzm{n}o~rn}o}qtttuyxvvyy~y|z{}ʾû¿û˶īŻϸͭèèȤƢĢŢƢĪ˭ͦɣƢĢȣƣɡȟǡɡǡ̡ˢ͢ʢˡƢɩ̧˥˦ȧ˩ͫάͪϪΩͧ˦ʤΦΤ̥̤ͦΤͥϤ̪ͦͪΩ˦ͨʩͫϬШʫҩШϩ̫ΫϬϪ̮ͬͪѬ̬̮ϭͫ˭̫ͫ˪̬ͪϫˬ̮ѫϩ̩̫ͩͨέϮͯͱ̲ϲαβϯίή̬9AG:AH9?E=BI;?I<@I:=G5=E7=D8?G:?H;?HJ7@J5?H4>G6@K3=K2?M2>K1>L2>L2?J3?K6?K6@L5=J6>K4L5L4=M3;L6=M6?N3J4AN3>L5>N6?N5=N9AP5@J4>J9CL6DK6CK6CL7BO7CN7CN7CN6CN5BR6AO8@P8AP9BP:AQ9@Q;AQ;AQ:@P8@O:BR7>M=EU:CT;DU;DU:CT;DUFY:BUG[>I[>H[@K]@K]?I[@H\DK^FM`CK^DL`HPbFO_GP^LTdMSeLScMRbMVdLTaMUaOTcQVeUXgTWfTZfW]iY]jYZkXZjY\j]al^cqY`nW\lS\iSZhPYeS]iNXhNYhQYiPZiRZiQXhOVfPYeQYgQXePXfRYjRXiRYiRYhPWgRYjSYjSZjT[kT[kU\lU[kW]mU[kV\lY_pY`pY`rZas\cs]dt^dr`et]dq^fp^ep]fq\fs\fr\cr\cq^fr^ft_iu`jwdlzckwckuajtfnygp~kqhpep|dn{iq~fpdqeofpfqepfqfqepfrfsgtfsgtfrisjvjxjwkxixkyjwivgsftdrcpbp_m~\izZhxXfxUeuUdtWcuWevZdwY`sX`sY`rX`qV_qV`pW`qU^oV]oT[mQXiQXkQYjNXhPXgOUeQYeOXdS\hT[lY_rY^r[^sceyjllogm}fhycgwhixdfs_an\^kX[lVYi[^mZ_oW^oSZjR\kS\jQZhQYjQXkOViLUhKWkITgESfGTgFSfEPdGQeEPdFPeFQeFTfGSgEQhEQgEQgEQgFRhEQgGRhEOeFQgIPgIQeJQgLShNUhLTgLTiMUjNVjMUjLTiPUkPVkOViPWiOWiOViNUhMTgKReMTgHQcJRfGPfHPeHQeEQgCMgAMg?Jf>Hd8F`7Gc9Gf9Fi6Eh5Cg4Cf3Bi5Dm3Bl4Cl2Ck1Cl2Bn2Dn0Dn0Dn1Dn2Eo0Cm/Bl1Do1Do0Cn.Cm-Cl0Cm/Ak0Am2Co2Co2Co0An2Cr0An/?o1>p0?p.>o,l,=k*;i):h+p';o';n(q-=q+r/@t0Au1Bv1Cv3Fw3Ew3Ew2Dv1Du.@r0Bs/Ap/At2Dv/As/As/?t0Au.>r.=p0@r0@s2Cu0@t0@t/?t/?s.?s,=q/@t,=q+q0?t/?s/@u,=q+q*;o*;q):p'q';r&;p+>t)=s';n+?r)>p)=p';n'r.>r.>r,=q0@t,=q->q->r/?t0Av.@t1Ct/Bs/As/@r.?t/@t/@u.?t/@t0Au/@t/@u/@t0Bt3Ew1Cv4Fv2Du0Bs/Ft1Fu1Gv/Cv0Ew0Dw0Dv/Dv1Fx2Fy1Ex4K{4Jy8N}6L{9N}6Nz7M}7M}9M}7M}7L|7N|6O|8N{6Ny8O{:R}:N|P~>Q=O}P>O>P=O;MP=OH7?I6>F7>I5>J2=I8AO4=M6?O4>K5>I7@L4>K6>K6>K4J5=J4K7?L7?L5=K4>J5@K7AN5@K6?L5?K4=K4;L6=N5I5=G7AK7AK6@K5?M6@K:CO9@M;CP8?K5?H9CN9BO7@M;DP9CN8@N6>M8?O7AQ7DT5@O:DR9BN9@O:AQ;BS9@Q9?O9?P;AT;AT=EW:BU:BU:DU8EU:EU;CT8AP8BREX=GY>JY?J[>JZ=JZ=GYBI]EL_EM`EL`HQcGP`HQaHQ`IQ^LSaNUeNTdMTcOUcMS`NT`RWbVYfWZiW\gY^i[`k\`l]bm[_l^bo_bn[_jW\hW^jU]hQZeQYfQ[hMXcNXcQXfQYgQZgQYeQYcNV`S[fOXeQYeSYhPWhOVgPWhRYhPXdRZhT[lSZjT[kV]mV\lX^nZ`oY_n[aq[br]dt]dt]cr^dq[bp\eq^fs]er^fpaht`gv[dp_gs^fp_gsaisaitbjtdlwekwhnxhn{lrqvmtenydlxiq~ipdpdrfreqeqdocndobnererfserhufsgtjwftfueudrdqal`k}\hy[hy\gzZfx_j}\fyUasUbsQbqVeuXewWctXcuXbuW^rZatUbqWbrX`qW`qS\mS\mTZlPWjNVhNViNVjNWhOYjQYiPYfSZiY`q^ew`cwafz^cwci{lshn~ko{mo~ln{gkugktaco\`n_aqaco_dqX]lU\lRYjQ[jOYgPXhQWiPWjLViLUjIUiIThFSfBPbDOcAOa@M`BPcDReDQcEReGRiEQgDPfEQgEQgEPfGRhEPfGRgGOdLTiLRhMTgOViLTgMUhLSiLTjLTiNTjMTfPWjOVhQUjOUiMTgMTgMTgMTgMTgLSfIQcJPdHNeGOgGNiALgAKh?Kg=Hg;Fd7Ga9Gd8Ee8Df7Eg4Cf5Bh2Bh5Ck3Ci5Cl4Dm4Fo4Eq1Am3Eo1Eo0Dn1En0Dm/Cn2Ep1Do2Eo3Fp2En1Cn3Dp3Dp3Dp3Dp3Dp1Bp.?m0Ao1?m2@p/?q.>p0?p/@n/@n+=k.?m1Bp.?m1Bp/@n.?m,=k,=k,:i)7f'5d(6h&7g&6k'7j$7g&8g&8i(:g&9g$:g%6e$6d$6d%7d%7e#5f#5f#5e$6f#5e"4d$6f$6g$6i%7i%7j#4h%6j!2f#4h"3g$5i'8l&8k%7j&8j%7i%7f%7g%7i&8j%7i'8k'9k(:l'9k%7i&8j&8j'9k&8j);m&7k'8l'9l*=m,>p)o,?q+=o)>p(r.?s/@t/@t0Au3Dy3Ev4Fx5Gy3Ew1Cu3Ew1Cu2Dv1Cu4Fx3Ew2Dv2Cu2Cv3Dx1Ar0@t2Bv1Au.>q0@s0?s1Bu/@t/@t/@t.?s.?s,>r.?s/@t->u+r+r,=r*;p*;q):p(:p(=r)=r(r,>r/@r-?q,>p.@r0Bt/Ap.Aq+?s,>q+?q)@p)?p(?p*?q*@q+As(>p)?q'>o(=p)=p*>p)>q*>q*?r)>q)=p(=p)=p';o'p(=p)=o';l)=o's%9n&:o&:o(r->r.?s-=r/@t.?t->r.@s/Bs3Ew0Cu1Bt1Cu0Bt1Cu0Bt/As/As/Bs0@u/@t/@t1Ct1Cv2Du3Et2Dt3Eu4Fs5Gv5Fu8Jy3Ft6Hy6Hy6Hx4Fy7I{5Gv8Jy5Lz7L|8Ly8N{:O}8Nz;O{;P}9O|7Mz7Mz9O|7N{8O{:O|9N|;P}:O};P};Q~:P|=R=RS@QAR>O~=SQ|=N~;Mz;M|P>P>P@R?QCVCSBSBS}@Q|DR}ES~@R~CTAS}BT~CS~ARBS~BTDRCTCTAR~CTBSAR~BTBUDTFSDRBSCTAR~HUFUFUHVFVHVFUFU~HWHWJYJYIXIXHW}IXJYJYJYJYKZKXLZKYLZ~M[N\M[O]O]Q^P^P^R`R`P^Q_Q_SaTaUbUaUbUbVcUbWdXeWdYfZg\e]h[g]h]h_jbmbm`kcnbmhpfokpmukqiokpjomppupttyw}z~Ľǿ©ĩʭԷӶЯˬȭƫȬ̯ϱвϱϲҶҷǰƮϵչεDZʿòǵŰȾįô}}~|||}~{y|z{zywz{}||}z|~z{y{zyz}}y|~xxuvususqppsopm}m}m}k{jziyjzn{lzlyjxjxkyjxhyhxjzhxgwgwdtdtgweugthvdteviwiwjxkyizjzl|l|l|m}qqqqqqpqrttwzzzyyzx{z}}}~½´ǿº ¢¢ħŬȧã£ơãŝŞɝƛșșșȜ˚əəʛȞʝˡȟȠɠȡʣ̧Υ˩̪Χ˫Σ˩ͧˤ˥ˤ˦̣ʤ˥ϧʪͩˬ̭̫ͮͭͫ˪ΫϫЫҩЪЬͫέϬϭ̬̭ͬͮɭʭɮͬ˫ʬʭ˪ͮͫ˯Ϭɫʫ̫̪̩Ϋ̭̰ͬϭ˯ήаίίέήήά?GJ@HJAEJ?CG;@D;AIJ7@N6>L6>J6>I5>J4K6>J5>H4=G4=G6>I5=H6>J5>H5?H4=H4K4K:BO:BO:BN:AL:?N9?MO:>R:AR8AR9BO8CO8AN9AO9@O:AS;BR:BR:CUFW;DVGX;DU;EV=FV=FX;CV;DW;BV:DTJZ@JZDM^FN^DK]DM_EN_DM[FP\IQ^LS`NTcLRcNTcPVdOUbMT_QWbVYeW]hY\hZ[hZ\i\`l\`lY]i_cncgq]al]amZ^jY^jW]jTZfSZfT[gSZeP[cS[eU[gU\hRYeQWcPWbQXcRXeNWdMUbQYfPXeQYfQYfS[hU]jPWeU\lU\lU\lW]nW_nY`mY^kZ`o[br\cs]dt]cs^dt_fr]er_gt^eq\ep^gq`hubjx`ht`hrbjuckvckvdlwgmygmzho|lspwqxqxio{gmzhq~ipfpen}fo~em}gpgqdodococqcperdqerererer^l\l\k~XgzZfy[fx[fxZbuZat[cuZct[du]gx[cuVbrSaqVdtVbsZdxVaqX`rY_rW_pU^oV_pU^oV_pQZkOXiR[kPYjOWlNVlNVkQYlT[oT]oW^p[bqY`o\crciyfk|acucgwmompnq|oq~rq~pqypryos{kozjlykm|dhtZ`m]erV^kOWcMWcQ[hNWiMVjNTiKRhJQfEOdDPcEPdBMaAL`ANaBObDQdDPeCOfCPfDPfEQgEQgEQgFQgEQgFPfJQfINeKQfJQdNUhNViLThMVkLVjNUiJSeJReJQdKSeMRfMUhLSfLSfKReKReKReJQdJQdIQcJPeIOeENfDMiAKg?Jg=He=Gg;Fd8Fb8Ge7Ee7Dc8Ee6Cf8Fi5Ci6Dj5Ch5Ci3Ck4Ck3Fo4Fq5Fr3Do2Dn2Do2Cn2Cn2Co2Do3Eq0Do1Dp3Eq3Dp3Dp3Dp2Co/Bn0Ao0Ao.?m0@n1?m/@o->l/@m.?m.?m.@n/Ao.?m3Dr1Cp0Bp/@n0Ao/n,>p*=n+=o->p,?q(=p)>q,@s.?s,=q.?s/@t/@t0Au/@t0Au2Cw4Ey4Fy4Gx5Gy5Gy6Hz4Gx5Gy4Fw4Fw4Fx4Fx6Hz4Fx4Gy4Ct3Du4Du1As3Cv0At1Bu4Cv1Au2Bu2Bu2Bv0?s-=q/?s,q,=r,=q,=q-@r/?t-=r/>r/?s/@t.?s,=q->r-?p*s)=r(p.@r.@r1Cu2Cv/Bt/As.@r,>q.@r.@o,Bq/Cv+?r-As0Bt/Bt/As0As0Cu0Bt0?s.>q->r.?s->s.>q-@q,>p*=o*r,As,@s)>p*?t'p'=o'=o)q*>q,=o,>p,>p-?q.@r/As-@q0Bt1Cu0Bs1Du3Ew3Ew4Fx3Ew2Dv1Cu1Cu0Bt0Bt2Dw0Cu2Dv1Cu1Bw0Au1Cu2Dw3Et1Cs2Dr3Et7Iw8Ju6It9Lv6Ju6Hv6Iv7Iw7Iz9Kz:I{9K|;M{Q|=Q|:Qz;Q{@R~P~>O}?QO~?R|?S}AT@R}>Q|>Q|>Q|;M};M{:Lz:L};M};M9K}:LP?Q@Q~AR?P~@Q}@Q|@Q}@P|AQ|AQ}BS~AQ~CS?S}?S~@RBSDR@R~AR~CSATBR~BR~BS?S~AR~DQ~DQ~AR~BR~AR~FTEUEUCTDUCTGVHWGVGVIXIXHWIXHWJYL[JYKZL[MZJWKYKYLZM[N\M[N\O]N\O]N\O]P^R`P^Q_SaVbTaUbVcUbUbVbVcWdXeWdXeYg\dZd[f[f\g]g_i`l`kcnbmdlfpflgnelflfjfkgklqnupys|w~z·Įɱȯ˯ͯʪɪ˫ϯѰ̮ɯǮɲʳDzůìưƴ̿~}{}~||}}z}}|zx}}~zyzxzz~}{zz{{zz{}~||vxxuutrrutrrqopon~l|l|k{k{kylylziwjxkyjxhvhxhxhxgwgxctfvevfveudqdrevhujxjxiwlzmzm}l|n~l|m}rrrqsstttsww{{zwyyz||}~ǿſ¹ʮşâ¡¬Ȧèê¤ĠÛĜǞ˙ǙțʙȘȚɗʚ˙ʙɜțȝʞ˝ʞ̡ȣ̤ʦʪΪΤʥ̧ʩ̤ˢɥ˦̨˨ʨ̦ʪͭͬʫʬʬɭ̭ͬΫͪέЩΪϬάΫ˫̭άɬʫʬˬɫɪȫȫ˪ʫ̪ʬ̬ɭ̬ʭʭȬʫ˭ͭˬʫʬʬ̯̭̯ͭήϭ̫ͮͫ?GIBHKCHL?HK@GJ=DG=HI=FI>CJAFK@EIAFJ>CG@EKAFL>DJI4=J7@L9AN5=J8>H7=I5;J5;I6=I6J6=I7=I5=G5=F5>H4I:BM6@J7AL6@J9CM8?K9AN8@M8AM9AM:AN9?M:@N:@O:@M9?M;AR;?T:AR7@O8CN8BL8@L:BNGX;DU;DU;DU:BSEW=FWHYIdl/@n0Ao.?m->l/?m.q+p,>p,=r-@s-At.?s/@t0Au/@t.?s0Au0Au.?s2Cw3Dy3Ex4Fx5Gy5Gy6Hz4Gx4Ex5Fy5Ey5Gy6Hy5Gv3Eu5Gw8Fw7Du7Ev8Ew6Dx5Bw6Cw8Ey3Dw4Ew4Dw2Cv1Bu1Au0Au1@t0@u0Au/As0Bs/As/@t1Au2Bv2Bv/?s0At2Du0At0Bt.@r0Cu0@s0@t-?s/?t->r)q,?s->r-=r->q.>r/@t1Bv2Bv2Bv2At3Cv4Fx3Ew3Eu4Fv3Eu4Fv4Fv5Hw3Hw4Hw2Gv0Dv3Gy3Gy2Fx1Ew/Ct/Cu2Du4Ey5Fz4Fy6Hz5Gy5Gy7I{6Hz5Gy5Gy3Ew3Ew3Et3Et3Et4Fv3Ev0Bs2Dt2Ds4Fv4Gw5Ky7Hx7Iy6Gz3Dv1Eu5Fw5Gw4Dv4Dx4Fy4Ew2Dv4Dw4Ew1Du1Br1Cv2Dw2Cw0Dw2Du2Cv2Bv->s->u/@v.?v0@t*?r*>q,@s.Bt.Bt-Bt.?s*;p,=p(9m,=q(9m,p+=p-?q-?q.@r-@r*>p,@r-@t*=p)>q+?r*>q*?q/?s/@t/As.@r.@r0Bt0Bt1Cu1Cu0Bt0Bt1Cu2Dw4Fx4Fy4Fy5Gy7I{2Dv2Dv3Ew5Gy5Gw4Fu3Eu2Dw2Dv2Bw4Fx3Ew4Fu3Ev5Gu7Iw8Ku;Ny;Nw;Ny>Nz=Nz>O{>O{O}R|>Pz?Pz=Py?RzBUAR~?S|=O{=P{;Q|?R|?Q{?R~?R}?R}?P?Q@Q;R|?Q|?R|>Q|?S~ATAT@S~@S~?R}@S~>Q{=O};M{QAR?S|?S}?Q?Q@Q@R@R}@Q}BRBU@S~@T~@R}AQ~?S~BR~DQ~CRASATBR~CP~FUGUCTDVCTDVEWHVGVHVIWHWIXIXIXFU}JYIYJVJWKYJX~JX|KY~KYKXL[O]N\LZN\M[N\~O]P^P^SaP^R_R_R`TbS`R`SaUaUbWdUdXdXf[dYcXbZc[f\e]g_i^i^i^idkbkbjai_h`idkdjbihnjqlrqxtztz{~}}ȺȰˮ˫̮ΰдγ˶ɽɿżƼ÷ƽ}~~}|~}~|}{}}|||yzyzx{|~~~}}}||zzy{{~ztvvvuvvsvrrsrppppn~om}ol|jzlykzlzkzkykyjxhvgxfvfvgwgtgufsftdrfsasetevgtjxhvjxlzm|l}l|m}l|n~prrpssvtvwvv|{{z{w{{{}}ƿ¼ŹźŻ¿Ļýüſɷ¦šŞǵ˧ƠěƝʜɗȚɘɘɖƚʘəəəʛǜɛȜʛȝʞȡɡʨ˦ʦʣʣɨ̧˦˦ʨ̦˧ɨ˪Χˬ̬̭ɮˮɬˮɭ̬ͬϬάά̫ͭͭͪ˫ɭʪȭǭʭȬ˭ˬʪȬ̬̮ͫ˫̭ˮȮɭǮƭŮɭʬɬɬǬ˭̯ͬά̮̫ͭͭͬЬEIMDJM>HJ@HK>GJ;FHAKM=EJ?DLAEK@DIBGK@EHBGLAEK?DJ=ENJ9@M8>L7H6=H5;I7=I8=J8=J8@I9@K9@K:?J:=H8?I5=F5>G4=E4=F6?I5=H4:E8>G9?I8>H9AK:CN6AM:DN8@J:BM9@L8@N9BP9?P:?Q:CO9AK:BM7?J7?J8@O;BSEQ>ES>EVHWm1?n1>m.o*s,=q.?s0Bu2Dv/Aq0Bu1Cv2Dv1Cu2Ev0Dv.Bv.Cv.Br.Bu.Cv2Bv3Dx3Fw3Ew3Ex2Dv2Dv5Fy4Fx5Gy6Hz1Cu4Fx5Gu4Fv6Hy7Iy7Iy6Hz4Fy5Hz7J|8K|5Gw6Hx5Gw5Gz3Ew6G{5Gx7Iy7Hx9Kz;Nz:Mx:Ny;Ny>Qy=Qy?Q|@Q|@Q}?O|>R}>Q}AP~>P~?P~@Q|@P}@Q}>Oz@P{?RzAT}DW~CU~@R}@Q|>Q{>P{AQ|?S}?R}@T?R~@S@R@R@T~@S~?S~>Q}AP~BS~@S}CVBUATCV?R~?Q=O}9L|;M}9K}:L~:L;L7M:L~;M~:L~;L8J}8J|9K}7J|8J}8J|8K~6J}7K}:N9MP~?Q>P?Q>P~Q?O{AQ}?S}AT~AT~AT@S~?R}?S~@Q}DQFSAS~BUBSBSFUASCTCTCTCTEVGYIWGUGUIWIXGUIWIXIXJUIVIVHUIX~JX}KZJVJWJWJYKYLZLZM[M[M[L[O]O]N\P[P\O`O_N^Q^O]S`T`TbTbVdXeYhZcYcYcYdZdZd\f\h\h\h]h]f[g[f_h]e_h^gaiemjppvtxtzu{v}|{ɱΰΰв˳ȲƱɽ~~}}~~||~~~~zzxxzzy{z{|{|}}|{{y{z{x|v{wwvvututstrrttppqsp}o{m~ol|l|lxm}kzl{kyjxjxiwgxgwevfwhuftesdrdrerdtdufteriwguiwjykzl{l}l|k|n~o~qqqquttuvwy{z{|xwyy{|~~´Źʸ폽ùŻǽƹź½ú»ƶŸǶåßÛƞ̛șʚʙɘǙǘȘȘȘȚɛʛǛǛț̛ʜǢʣ˦ȧʥȤˤʧɨ̦ʨ̨Ω̧˫̨̫ͩ˫ƬǭƫɫȫǬʫ̫ˬϫʫʫʭ̫ͫʫɫʬǮʫǮʬȫɪȬɬ̬ͫͫ˯˭˯ɮǭűƯŰɭƭƮƬɭ̬ͭίέˮʭά̬ЫϭDLOGLPEINDJNCGLAHK?II?IK=GI?HKBIL@EKBGMCHN@FL@GMCGMBGM?DI?CH@EI?DH@DH@EH>CI=BG=BG:?E:@G9@H:AJ;AK;>J:@J>AL=@L;?J8?K8?K6=I7=I9?L9BL7@I9BK:@KE5>F8@I6>G6>H5=H6>K7@M8AN:BN7?I:CM8CL;DN9ALL7?L9AN>FT9BQ8CP;CM;CM:DL:EM9DN;EP;FR:FP=FP:FP9EQ8DR:GUET=ER=DS>EW?EW=DS=ET?FVBHY=DT>EV@FW>GW=HSGW;DV:CT;DU:CT;DUEXDX=DX>DX:FW9HW:FV:DW9DVm1>n.@m.n0>o0=l1?m2?k/=k1Ap/@n/?m0Ao.?m-?m+p)=m+=o+=o*n*r.=r-=p0Cu4Au1?r1=q0@s.?r/>r/>s.?s0Au/@u/@t3Dx1Cw3Ew1@s3Bu2At1Au2Bv0@t1Bv1Av1Bv3Dy4Fx3Ew0Ew1Ew3Et3Eu3Ct2Bt2Bv3Cw5Ex5Av6Cw5Bv6Cw7Dw7Eu6Cx7Dx9Fz9F{6Cx7Cy5Dx5Hx3Eu3Et3Eu4Fx5Fx4Dx4Ex1Bw3Ev4Fx5Gy3Ew3Ew5Fy4Fz3Cx3Ev4Fx3Ew4Fx2Cv1Du2Dv0Au3Cw3Cx4Ey4Dx4Ez4Ey1Bw2Cw1Bv2Dw4Ew5Ex7Fz9Fz:Fz;Iy9Gy7Gy8Hx5Hx6Hx6Hx7Iz5H{4I{5Jy7Iy6Hw9K|9K~8J|7Ix9K{9Kz8Jz8Kz8Jz;M9L~;K|:K{:J{;J|9Jz9J{7Gx:I{:J{:J{8Gy9Iz:J{;K|Pz;Kw?P|?P{BT{>PuCRyCQyDR{CS}@R~AR}?P}?Q|AR~@R}BT~BU|>QxBSzDRzES{FVzGV|ETzER{@Pz@Pz@Rz@S{AT{ARAS~BS@T}CV@S~>Q{=Pz?Rz=Ry@Sz@S~AT~CVBUATBVCVAR~?P|>O|;M~P?O}ARBN}@Q?Q?Q?Q>P~?Q?Q?R=O~@R=O~=O}@R?QAT~>Q|@S~?R}@T~AS?P|AR~BSDRESAS~AR~AR~DUCTBS~CTDTFUFUGVDVDVFYEXDWHVGVGVJVHTIW}IW~IU}IVIVIUHUIUKXKYKZLYLYKYM[M[LZLZKZ~LZ|LY}LZN\P]P_P_P_S`VcS`XaXbV`ZdZa[bZc\c]f]e\e\d\e[d\d[dYfYd\g]jeqgvkwovpvsyyzzɸǵƲ}~~~}}}}}~}}~|{yyxwvvxwy{~~{z||zzyyzz{zyyywxxuwststtqqrqpon~ol|m}n~l|l|k|lzl{kyjxkzjyhvguhugugufthugsesdrfsdrcqeshvftixixl{l|n|k{n~n}p~p~qtrsswtwuywxyxzxy{{|µ¾ƾƽŶƶĸ´¿ĺ½ƶȺƺźķøſDZƵĞȜǙƚəʙɚʙɘəȚ˙˙ɚ̘ʙ˚ʗțȞʚȝ̠Ƞɢ˦̧ΩϤˤ˧˨˩˩̪ʩɪ̪̬ˬ̪ȬȫĬƬɪȪȫ˪ʬϭЭέˬ˭˭ɬȫǬȬȯȬ˭˭˭ΪȫʫɭɫƬȮȭůƲǰƯʱȮȯ˯Ȭ̭ˬ˭ǯɫʬɬȭͭ˭FKPINSHLPGKODJMBHL?HJBKMAKM?HJ@GKCGLDIPEJPDIODHOCFMAFLBGJDIMAFJ?CHAEJ@EJ@EK>CI?DJ=AG;AG9AI8?G8@J:@J=AL?CN>BM<@K;AK:BL:AK=AN9@J;BL:BM9AJ;BI>BJ;>G8>C7?I6=I3=G4>E6?G9AK8>I7AI8@K7?K9?M9?M8AN:BM>FQ;CN:BM9AL;CNET>FV?FV>EU?HT?GT>FS=ESAIUFX;FW9FV:GX:FWIYAIZAHZCK\CM]BL]CL\GP]HR]BJWFN\HN[HNZHNZMR^TXdSXdSWdRVcPT`VYdZ\h[^jZ_k[^kY^j\`l_coaeqdgoadmbelbelbfmhkolmqprsqpqqqppqqjkpbfm_bk]bm]`l\`kZ^j[_jZ^j[_k\akX_jV\gV^kU^jW`jW`mV_lXanYam[co]fr\dq]er\dq]er\erZan]es_du_et^gsaiwdkxflwekwdlwfnzgn|hn{emyfnyiq|jq}jo{io{jp|jp{os~rutxpulsmtjq|hpzelxbkv`kx_it[er]fu[euXeuWdtVcsUbsUbtWduUcsTcsT^qUarUarVctUbsTbrW`r[bs\ct[atY^p[apZ`n]cn^dp^dsZ`nV\mSZkQXkRYjQXhRZiOVfRXkRYkOVhOVhNUgPWiPWjQXjT[mSZlSZlV]oU\nV]oZat^eu[aq]ctadt]`o^apadsfjyjmyhlwfivjlxjlxikxfhuabq[]lW^k[]lX[i[]mZ^oZapW_oS[nQXlMThKTgJReJPeIOfINeGMdHNfJPhINfIOgGOcGOdEMcGPdCN`CN_CM_GNaFNaFNaIQdJReKRhNVlIUiP\nMZmNZmMYjMXjKYiKYiLWiLVhMThMTgLTfHPbJQdIPcEL_GNaFN`HObGMcGLbFOcCObBMc>Ka>Kd@Jf?Gdl,m0l->l+p,m*p/@s2=r2?s/?r.>q.>q.?r.?s.?s.?s/@t/@t1Cw1Av0@s2Bu1At2Bv0?s0Au1Au0At0@t1Bv1Av0Bu/As1Du3Ew2Du2Dr2Ar1As1At2At5Au4Au5Bv3@t4Au7Cx9Ey6Bv6Bv7Cx7Cx8Ey9Dy4Cv2Dt2Dt3Eu4Fw4Fx3Ew3Cx3Cw4Fw4Fx2Dv4Fx5Gy6Hz3Fw6Hz5Fz7Iz5Gy3Ex6Hw4Fy3Dw5Gy4Ez5Ey3Ex5Gy6Iz6Hz4Fy5Gy4Ey3Dx5Fz6Gz6Fy7Fz9Fz9F{;Iz:Hy7Hy8Gy7Hx7Iy7Iy8Jz5K|6Kz8Iy8Jz7Iz6Ix8Jz9K}9K{9K|7Jz8Gy:K|:M|9Jz9JzL|;M{;Mz;Lz;Ky;Lz9Jx;M{N;K|PxARz?R{BUzCQyCRyARuDT|ES}CR~@R~DS}DS~DR~DR|DS|BQyET{EVyET{EVzHY}HY}FWzEUzBQyAPwDS{CR|CR|CR|ET|GV~AR}AR}AQ~?R}?R{=Pz?Ry>Q|@S}BUATBUCVBUCVDUCTAR?QQ>P~P~=O}=O}?P~@R=O~@R>P~P>R{>R|?R|@R~?R}?S~AQ~BSBSBR~BRCSCTCSBR~AR~CTCSDTBSBT~CTCTDTDUCU}EXHVGVHWHWFT~GU{GU|FT{KWHUGTHT~HU~HUIVIVHUKXJWLYJX}HV}IW~HV|IW|JX}GUzJX}IW|J[~K[L]~P]R_P]SaV_V_U`U_W^YaZa[b\d^f^f[d[dZcYbZcZe[f`leqiuivnupvqww}z|~}~~}}}}~~||{{|yzxvwyuwwxyzxyyzyz{yywzzxyxwuutuvsvrssqrpon~m}ol|oom}m~m|jyjym{jwhwhvkyftguhvguhvguftguguftcqesdrgueskxfvl{l{pm|n}n}oo|qpprq}tsusyxxwwxxwyz{}̾ȶȸʽʶɹƻŻ¿ĽɽʺǺʲžάƚǚȗǘșɘǙɗȘɘșɘ˚ȘʙɚʚɛȝƠǜ̞ǞǡǦʥɧ˧˧˨ʩ̧˩ͩɩɪʪͩɫ˪ɬʬǫǫĩɬʪȨɬ̪ͫ˫˫ɬʫʫǬȫǬȭɭȭȫȫǬʭ˭̫ƭǬǮȯDZɲȰűűȱʭƮȰ˭ǭ̫ʭƮȫʫɭͪʬˬJOQLQTLOQILQEINFLODJNAJMCKNELOEINDIMEKQFKQEJPFKQEHOCFLBHKDIMBHL>GJ?GJ>DHAEK@DJ?DJ:BG;BI:AJ:AI=DM;CM:@K<@L>BM=AL;?J<@L?BN>CN:AL;BL;BK;AI@CL?CM?BJ:AG9AH8@J6?H7AI7@H9BL8BL8AJ;@L9?J9?L:@OHUEWDQ=DQ=DS=CS?EUBHWAGU?FS=FP=FP=FQ=FQ>FS>FSFW>GY>EX=EW>GX>FX6DS7GTIZ=GXJd;Ebi-Ak.Al-@k-Al+@l.Bp.?m,>l*;i*l*;i,=k*;i+q0?r0@s-=p,>p.@r->r,=q->r.?s.?s0@t2Bv/?t2Bv1At1Au->s/?v,Au+?t+?t-At.Bu0@u0@u1Bu/Bs2Ct2Au0At1Aq1Au0@s3?t2?s2?s3@t3@t5Cv2Cw3Cv4Dv3Cv2Cv3Dw4Cv3Du2Et3Et3Eu4Fv2Dw2Dv4Cw4Ex1Cu1Cu5Gy2Dv3Ew6Hz5Gy5Gx6Hz4Fx3Ex3Et3Eu2Dt3Eu2Dw4Fx5Hz6Hz6Hy6Fy7Gz5Ex6Fy7G{6Ey5Ex4Ew5Ex6Fy:G{9G{:Hx7Gw6Gw7Gx7Hx7Iy8Jz9L{5Kz8Jz9K{8Jw8Jy8Jz7Iy8Jy8Jx;Ky:Ky:J|8Iz7Fy:J|:J|:J|L}>K}=L}Lz>L{9Ky:Ly:Ky=Jy;Ix=Lz>Lx>L{;N|=N|Mz;N|;L{;Ly:Ky;Ly:Ky>M;L|:Ky;Kz:I{9K{8Jy8Jz6Hx3Hw6Gz4Fx4Fx3Fw5Fz7G{2Gz7J}6Hy3Gy4Hz2Fx2Fx6I{8J|4Fy4Ey6Hz6Hz8J}5Gv6Ix7Jz9Hz7Hx7Jz7Jy7Iz9K~8J|7I{8J{6G|8K|8K|7I{7I{:J}8Hy:Iz;N}9K{;M~;M7M{:K{:L{:L|9K|:L|9K|9K|:L};M};N~;J|:Ly;MzOy@RzASyASyBQxCQyCTxBSwERwESxCSzCR|CQ}BQ{CS{BR|CR{CRyFV}EUyHX|JW}IWyIWzJX|HVyGUyDRxBPuCRvETzGU|ERzDQ|GT~FTDS@Q}?P|=Qz=Pz>Q{@S}AT~ATBUBUBUATCVDUCTAR~@P~=N|;K}P~>P~>P~=O}?Q?Q@R>P~P~=P~?P?Q>R|>Q|?R}AT?R}BR~AR~BR~?S~AS~AUCVBUAUBUDWBRBS~BRBR}BS~BR~BU|DV~GT~EU}FU~DT|GR}FU{GU|ESyFSGTHUHUGTGTFS}FS}FSHUGT~IVJVHUHW{HV}FTyESxGU}ES{HV{IV{IWzIWzJX{KX{LY|P]O\P]R[V_T^U_T^S^WaYbXcXcYb[d[d[c[g^jbneqcohqkrpupuszw~x~{}{~~}~~~}~}}}~~}}~||{{xxyywvwzxz{{|xwwwwxzxxvvtvstrttrsqrqqqppooopl|n}o~l{m|kzl{ixjwhvhviwjxivgujxguguesdrftesesesiwhvhvhxkzjyo~o~n}pn}o}o~oq}tp}q~q~qq~usssuvvxwyz~{|{}ʺɸ˸̼˻͹ǶǼº¿ȼɽͼξþƸżŹŧƛəƚȚ˙ɖȗȗȘɖȘɘǚəƗƘǗěǞǝęƞǟŠǥǤŨʧ˦ɧ˧̬ʫ˪ʪʪʪ˪ʫ̪ȫ˫ǫǬȭ˭ʪǪȫɫȫȬʫǭɬȭɬȬȬȭɬŭŬūȬȬȬɭȭȬǭűɯƳǰĵıưȳƲȬƱ̬ʪɭƭʫɫ̭ͪʭͪJORJORHLPJMRGIOFINFKOGKOFKOGLPDJNEJMFHMGJOGJRJLSKKTEIOEHMCHLCIMAILBJM@IM?FM=DJ>DK;BIBJ=AJ>BJ<@I>BJ>BK9AI:AI8?GAK>AJACJCOHS;DQDP=CO=CR>ET?FS?ES@FR>CP?DQ=CO@ER=CQDU>EU>FV:GS9HS;HU;HT;FSL\?J[BK\BI[BI[EL_EL]HO_HN_HO^IN[HO[KQ]LP[OS^QU`VZeTXcSWcSWbWYdYZf]^j`cobepehqbembfmbejiinhilfghgiiikmijkonmoomrqjvupstorroqrqjmmhmofjodjndipeiseirdhrbfqaep^dn\dn]fn]doagq_eq_eqagsags`eq]ep^hu`iu\gp^hr[co]er]fr[cp`huaiv_guajwcjxdlydlyckxckxdlydlwckvbjuaitahsbitahu_draetegugkwgkwimyimyhlxhlwekveluemxckv`jw\hv]kx[ivVctZgwYfvXeuWevUbtXawY`vY_tW_rYasYarYap]crZboX^oZ`q[`n^bm`do_co^co\bpY_nU[iSYhTZhPWePUcRXgQXgOVfOVgRYlRYkRYkQXjT[mSZlT[mX_qT[mRYkU\nU\nT[lTZhV\jY^kY[kY\mW]mZap\bpU[iSWgQSdQTeOQbPRcSUfPTgSWjPUhPUhMSfNSfMTfMUiNWiNUgMTfJQdJQdKPeIOeGMcHNdHNgHNgFLdHNbIPbGOaGQaGOaEPbEPaGNaFNaFNaKSfMTgLUhLWkMWkLYlP]pP]qR[pV]rS[oN[mP[oNYmKVgHSfIThHSgGOeINeGMcFLaFLbDI`FKaEJaDLbBM`@Ka>J`>Kc:Ga=Ic=Hci/Am->j/@l-=i->j-?k.Ak.Al-Ak,Am.@o+=l-=l+l,=k+p+=o,>p->p->r->r->r.?s.@t0?s.>r0@t.>r.>r->t.?u+?t,@u,@u+?s*>q,@s/?t->r/@t1Bv0Bs1@s2?s2@s0@s1At3@t2?s2?s2?s1>r1>r1Bv1Au2Bv3Cv2Bu2Bu1At2Bu1Cu2Dv3Et3Eu3Ew3Ew4Cv2Cv2Dv3Ew5Gy3Ew2Dv4Fx3Ew3Ew3Ew2Dv3Et1Cs4Fv2Dt2Dt2Du4Fv5Gw5Fw6Ey5Ex5Ex7Fy7Dx7G{5Ey6Fy5Ex7Gz7Fz:F{9F{:G{9Dz7Fy7Gx6Fw6Iy5Gw7Iy6Iy7Ix8Jx8Jx8Jx8Jy8J{7Iy8Iw9Jx9JxL{=Kz;Ix=Kz:HxL{>L{?M|=Lz>Kx>Ly=Jw=KyN};M{=N}>N}=N|;L{O}=O{=Nz?P{BT}@RyCU{ETxFVyDTxFSxESxDRxCQvDRwCQyBQyESzET{ESyES{ESzDRvDRxGUwFTwHWxIXzM\}K[|HWxESvESvGTxFRuHSyGSzGS}FSDP{DT}AS>O|>O{=P{>Q|@S~@S}@S~BU@S~ATBUCVCSBS~AR?Q?Q~=M|=L~:J{8Jz7Iy:L8J|6Hz8J|6Hz7Hz6G{7G|5Fz8J|9K}8J|6K}7Lz9N}9N}7L{8M|9M|6M|:O}:O{9N{P~=O}:LzP~>O~?R|>Q|>Q|>Q|>Q|?R}?Q}>Q|?R}@S~?R}BU@S~?R}@S~@S~BUCW?R~@S}AQ}AQ}@SzAS{AT|FU~DS}CR|DTyESzGU|FT{DQ~ERFSFSFSFR~ER~GSER~HUGTIVGT~HUCQ{FS}HV|FT{FT{DRyFTxCQxFTwESvESvFTwFSvGTwIVyJWzKX{LZ|PY}QZ~R[S\T^T^VaT_V]|V_~ZbXd\g_jamdqfohqkrptnspusxswv}y}~~~}}}}}|{}}}}{|}||}|||~}~~~~}~}~}}~}|yzvuuutxuvwywxzxyyxyxxwvvttttsssqsqrsqqqoropn~o}o}m|kzkzl{jyl{iwiwgujxi{iwiwiwftiwivfwerdrguftivgwiwl{kzo~m|ppn}ppp|q~p~qssrp}sssutqutuwvxyyzy~}~}||}}~~ɹǴ˷лϼ͹ƷĽ¼ĶȶȻ̿ÿØÝƚǗǗƕŗȖǗȖǘɘɘǛʚǛǚƝƝÝÞƠǠǤťŦŦŦĨǨɨɩɫ˫̫ȨǫɩǫȪɬȬȬȬɬǬʭʬɫȭȭȭɪƪƩƫǭɭʬȫȯɬȯʫŭȭȪǫǯʬŰȱǴƶǶƵĴijƳűƲȭǯɬˬƭʫɫ̭ͪʫˬLQTINQJMPLOSLOTKNSGLPEJNJLRJMRHKPGJOFINJMRJMQJLRLLTKLQHKPEJOCJNDLOAJL@HM@HM>FI?HJ>DH@EKBFN?BL=BKCI>CH=BG=DM=DN;DM:DL;DK=FP=DO>EO=DNDQEO?FP?FQ>DQ?EQ>DP=CP>DPFQ=EP=DO>FQ?GS>FS?GT=ER?GT=ERFQ=ER>CR=DT?CS>BS=BVAFY?DV>DR=CN>DN=CQ>CRBDT@DR=CO>DO@FP=EN?FO;BM=CP:AOEV>EV>FVIS=GQ@JTGQ?HR?HRHRLY@JZBJZEL[BHXCIYGM]EK\FL\GM]HO]JP]NQ]NR^MQ[NR]PT`RVbVZfWZfWYfZ[i^]jaamccnghshltdfneflginghmijojkpnlrmmpnmnnmmnnkqojtsnuwquvovvqrtqtvsorsquwkosfjqfiqfirfiteitdgsaepbgpbfpaisagrdjvagsciubht_gr_ir^is`it^fq^fq]er]er^fs^fs_gu`hu`hsaitaiwahvahu_gt\dr^fq_gr_gr`hs]ep\cr[bq[bp^dr[`m`coehtko{mp}nr~quz}qu}ms{goxfmxbnz`o|]kxWesXguXeuUbrVdsTarV^rX_rW^rYasWaq[cs[aq\cp[bpZ_pZ_o[]l]bm]al_co\bnY`nX_kSZfW]iSYdMS`MSbPVePWgQXgPWhT[mSZlT[mU\nSZlQXjU\nSZlRYkRYkRYkRYkRXiSXfSYgV\jUZjSZiNSdOThOVfIP_LRbMRcLReNSfLQdMRdPTgNVhMTfOVhNVhNUhOViMUhLTgNUfLSdKReJQdIPdIOfIOeINeFKcFLbGLdDJ`EM`FOaEN_EP`CNaGNbHPcJRdJRdKSgNVlNVkKVkM[mN[oN[oO\oS\rV]tSZrRZpPYmIUiIUiIUiIThHSfFRfIPeENbCJ`CK`EMbCLcELcDLbCKb?Ka?Kd?Kf;Jd;Ic;Ga=Icr):n):o+r*;o+r,=s-=s*?t+?t-Av*>s*>q,>r.>r.>r.?s->s.?t/?r1=r1?s.?r0?r1=q1>r1>r1>r1>r2>s/?s0@t0@t1At0@r2Bu1At3Cu1Du3Ew3Ew1Cu1Cv1Cu1At.?r/Bs1Cu1Cu1Cu2Dv3Ew2Dv4Fx4Fx2Dw2Dt3Eu3Eu3Eu1Cs2Dt3Eu1Ds7Gw4Du6Fv5Ex4Dw6Ex6Fy5Ex5Ex5Dw7Gz5Ex7Fy8Dy7Dx8Ey9Hx6Fw7Gw6Hx5Gx7Ix7Iw7Iw5Gu8Jx6Hv7Iw7Iv5Gu8Iw7Gv=Ky;IxL{>L{?M|>Lz>Ly?Ly>Mz>Lx;My;Lx;Ly;LyLzOz=Q|>Q|?O{?P|=Nz;Lx=O{>N{>MyAP{=QyATyASzFUyFVzEVyGTyGUzFTwDRvFTvCQwDRxFTyESyESyESwDRwCQwDRwDRvESvETuHWxHWvJYyMYyJVwIVxGTvGUwDSuGUxIWzFTyDT|DT}ES|DS~?P|>P{?R}>Q|>R|?R}>Q|>Q|@S~@S~@S@T}BRBTARBPAO~=O|?P~=M~9L{:L|9K{8J}8K|6Gy6F{3Gz5Ez5Fz4Dx4Fx:L~9K}4Hz5J|8M{8M|7M|6L{5L{8M}7N}9Mz7Ly8O{:Ky=O}P~>O~>Q{?R}>Q|>Q|=P{=P{>Q|@S~>Q|>Q|?R}AT?R}@S~BU@S~@S~AT@S~>Q{?Q|ASz?Qy@R{ATyFT|DS{DSzCSyFT|DRyDQxCP{DP|FR}FS}FRDTFUDTESFTGV~FQ|ER}FR}GS~ESzES{CQxESzBPxDRwESxDRuDRuBPtESvDSvERuERuFSvFSvGTwHUwIVyIUyKX|NVzQ[~R]}S^~U`T_|VbWb[f[f_jbmfohqmsnuospuotpunvsxx|~z}z~v|vzt|t{ywz|{z}~~}~~}~~~~~~~~zyxyzxy|{|{~{|||{z{{|}}{~}}}~}~||zxzwvtvuttvtuxvwwtwywvuvvustrsrqrssrpsqqqpoon~po|p~o~m|m|m|kzixjxkyhvkyhzkxhvjxguiwmzevgvftgvhwgvhwhwjym|n}m|l{o~n}o~o~o~qr~ustttttvstsuswuuuxvyu{z|{yz{{||}{~|}~µƴɶнҽнǻ޾¿ļµ˾ǽ»¾×ǖǗƕƗȖǖǖǗɚșɜƜśŝĞĤɡšƣçȪƦè§ħŨǫɨƩǩǪǩȩǨƫʩŭȬȯʭȬƬǪŬǭȪƭīǬƬǭǬǬƭȫūˮɬǮʭɯˮʭɫƮDzǯŸǶƶĸƷǴĶıŵɰƯʭȭǭʫɬʫ̫˩ʪNSVLPSNRUMQTKOSJNRKNRHMRJNQJNQHLOHMPINPKORKNSKMRJLQMPUNSUINPFKNEKMFJOCHKCGKCHKBGK?EHAFJAFL?DL@DL?EK>FL?FM@GN@GMAGMAEL>CIEL=EMFJ?EJ>DN>EO>EN;BIEO=DN=DN;BKDO=BN>EO>DO>DO?EPAHRAHR@GQ>EP>EP@GQ@GQ@GQ@GQ?HQ?IR?GS?HU@HU>GT?GS>FQ=FP:AM>DQ>DR>DR>DS>CS>DTDO=EN?FQ>DQ;@M=DO=EO=EQGS>GT>GS=GQ?FR@GU@FTAHU@HV;ERGQ@GP@FQ@ER?EQ>FQ=FQ=DO>FP@HUBMYBLYAIWAHWAKW?KVCMYBIWAFUBIWDJYEKZIO^IP]JP^NR]OS^OS_OT^PT_RU`QTaUYeW[hVXe[[j__l``lcboeelhhpikohiohhlffjigmmlpllnnmpnkornornmtpnvsnwunwwpwvlxxmzypxynyyrz{vuxtoqsjlpkltijthlvhjvfjubfqdgpfktehtekuekwdjvdjvcjtbjubkvaju_gr]fq]fp`gs_gu^ht`hs]epZbm[cn\dq[cqYdoZbo\dqZcoZen[eoYcnZanY`nZbnYan[cp[`o^cp^dogmxjp{suz}y}kr{fpyfqz_ly^kyZguZfsXdsUbsTaqWcsXcsXasV]oX_rY`pY`o`gu]dpZbnYaoZ`o\bo]al[_j]am\_m]aoW[gY^jU\hOUaMS_SYeQYgSZlSZlRYkQXjT[mU\nT[mT[nQXkSZmOViMTgNUhNUhNUiNUgLSbMScSYiRXhSWkNVgKRdKQdJQdNTgLReMQeMQeNShNUhMTgPWjPWjMUgNUhNUhMSfLSfMTgNVhJQdKReKReKReHOcIOfFLbDMbCK^CL_EM`DL_DM_DM^EM_FObJReIQcJRfMSjOXlNUjOWlLWkLZlMZlP]nP^qP[oR[rT[tRZqNVmMTiJRfGPeGShFRhFPfHPgFNeCKbBJ_DLdDLcCKcBHcCLcALb?IcAMg?KdAMg?Ke=Hb>GcIe=HeHg=Ihq1At2>s2>r2>r1>r.>q.>r/>r/>r1At.>r0@s0?r2Bt0Bu1Bv1Du0Bt0Bs/@r1Au1Au0Bu.Ar1Ct2Dv1Cu1Du1Ct2Cw2Dv3Ew3Ev0Br1Cr2Ds0Bs1Bu4Ct3Ct4Du4Du3Cu4Cw4Dw6Fy6Fy3Cv1@t3Ew3Ew6Ey6Fy5Dy7Dx7Ev9Gx5Fw6Fx6Fx5Hu5Gv7Iv7Iw4Fv5Gw6Hx6Hx5Hu6Gu8Iv:Jx:HvKz=Lz>L{>L{>L{=Kw>Ly?Ly=Lx>Ly;Mx;Lx;MxKy>Ly;My:Lw;Lx=M|8Ky8Jw9Ky9Ky7Iz8J{7Iy5Gw4Gw4H{3Gy0Gx3Gv1Fv2Fy0Dv3Gx5Fx0At6Gz6Hz4Fx3Ex6Fv6Fw7Hy9Fv9Hw:GvP{?P{@O{@OzCR{CQ{DSyDTxEUyFSwHV{HV{FTyHVxGUxGUxESwDRvCQtFTvDRvDRxFTzFTxESvDRuDRuETuEUuHXvLXxLYyHVvIVuFSvETrETvHWxGUyGV{EVzFU}AT{CR~AP{>P{=Oz?R}@Q@T}>Q|?R}>Q|>Q|@S}@RBSAR@RBO~@O}?P}=MQ|?R}=P{=P{>Q|Q|@S~?R}@S~>Q|?R|?R|?Qy@Rz@Rz@RwBQxAPxCRyDS{DQyDRvCQwCQvBPxDRxAOvBNzBQ{BQzCR~CR~ET|CS|CS|BQzGT{CQxDRyDRxBPuBPvAOtAOtBPtAOrAPs@OqDPrANpBOqBOqDQsERuDQtERuCPrHTwHUwKXzM[{PY}OZyOZzN[{S\}T^|WbWb}Ze\g_jbkbjbkgqiokrkrmsjsoupvrww{vzw{x{sytytyqvpwrynwowr}s{vturv{y||}~}{~|}|}}}}}}{zyxwwwwzzxyz}yz{{xxyxz}}{}}}|}~||||xwwurssrqsuvuwwvvvxuvtsstsrrrprqsrrqrpn~oopo}o}o}n}n~o~kzl{kzkykxlylzjyjykzl{jzm|jyjyfuetfueugvgvgvhwgvixjym|m|n}o~n}pn~rqr~suttuuuustuxtuwwzwxywzzwwwxxxzzyz{y~~~ŷ˼ͼʸ˾Žº˻Ƽȿ«¥ėǖŕƗȗȖǗȗǛɛƞƞơŢġţǣĥæŨůɭũ©éȨƧŧŨƨɪʩƩǩǨìȭƫŮȭŮʮȮɭȫƪŬǫǫǫǭȬǭȫǭǬɯˬűǯƯƭƮʪïǰƵĺʺǸǺƹɸƸǴµȳǮȯȬǭȪʫɬʫɩȩNVXNSVMSVLPSMQRKPQKQQJPPMPRKPQINRJOSMRULQULQSMPTMRUMPTKORLQTKPSHMPGMPDIMCHLDIMCHLCHLEJNDIOBDKBEMAFLAGKAILBJM?IL?FJ@HK?HL=DJ;BIEP>EN>EL;BG>FK?GMDO>EN=DL>EN>DM>EN=DN=DN>EO;BMCO>CP?DP>DP>EO?EO=DN>HP>GP@HQ@GQ?FP@GQ?FPAHR?FP@GQ@GQ@GQ>EO@FS@GUAGU@GTAFS@FR@ER?DQAGS>DS?DS?ETET>ES>EV?FV?DR>EP>EN>ESBGTBFRAEQAFQ=EN?EPAER;BM?GR=FP=EP>FQEQ?ES>DR=FS>EQ=FPAGT@FT?DSAGU=FP>FQAJSCKTCJTEJUDJV@GSAITCLUAJSAJSAITCJVENXFMYENXELWBJUDLWHMXILYHLXGLXGMZJO[NQ]OS`QUaOS_PTaQUaPT`PS`QS`UWdWYfVXeZ\h]]ha`iedjedighiiijhhjkkljjjljnlilkkiqolsomytqwsnxuk{wl}ymxvj{vg{k{i~zj|nzm}yqyzsvwszzzqrvkmtmpzjnweircgpfisdhsdhudjwekwekwbitbitcjtcjsahq]cn[am\bnZamZdnYalYamYalX`kYamZcpXboZbnW`lS^iU^iU_hWblYcoYcoZdp\cq]dq_ds_eragvgn|glyos{{{}pvgp|fp|bkxblx`iw\ivWerUcoTbpVdsVaoW_oW]mX^n[bsY`p^es[bo\do[bpY_m[`n\`k]alaeo`dq^ap_cnX\hW[gTXdOUaPVbPVfRYlRYkSZlRYkSZlSZlNUgOVhNUhQXkMTgJQdKReIPcGNaHObFM`IPaLSfMReQYjRYkOWhMVgNWhNWhMTfNUgOUhOViMTgKRfOUhNUhNUhPWjNUhLSfMTgJPeKQgJQfLSeJQdHObHObFM`HNbEL`DK_EL_EM`DL_CL^FNaEL`FNaJRgKShMSiLRhOXmPXoNVlMXlNYlLYjP]nO]qQ\rQZtQ[sPXoLTkJRjGOfGOgGNeENdENeFNeDMdCKbCKaEMdCKbELgCJcDLcCJe?JeALg?Jf@Le?KeHc?Jd>Je>If?Jf?Jf>Ie?Jf?KgBLj@Lj=Li=Ij;Hi:Ig7Fc7Fg8Gf7Fe7Fh5Fg7Gh5Eg3Df5Ei6Fi4Dh4Ch5Di3Dh4Dh5Fj3Ek6Dl6El5Ck7Fl4Em2En3Dm5Dm4Cj4Cj4Bj1@h1?h2Ah2Aj1@l1@l2Am1@k1@l1@k/=k0>j.;g-p.?s-?q/?r/=p0=q.@q.>q0@s/?s.?q.>q/?r0@t,=r->r->r/@t/@t.?s,=q.?s/@t0Au0@u1Bw.?s0?s2Av3Cw2Cw0@u0Au.Ar/As3Dx1Bw1Av2Cw2Ev3Ew0Bt0Br2Dv0Bt0Cu2Br2Ct5Ev5Du3Cu4Fx4Fx4Fx3Cv1Cu3Ew3Ew5Gy4Gx5Hy6E{8Fv7Ev9Hy7Gx6Gu7Hv6Hw6Hv7Iv7Ix5Gw6Hx5Gw6Gw6Hu7Hv6Fw8Iz8FwIy=KyL{=Kz>Kx?Ly>Ly=JxKx=Ky>Lx>Ly=O{>O~O>R|Q|Oz@Q|@Q|BQ}CR|BQyBQ{CRyETzDT{FWzFS{FTyGUxFTzIW|FTyHVxGUxGUxFUvESvESvDRuDRuCQvDStDRvCQtETuFRuJWwKXxM[{MWwNXxHTxFTuGTvIVwFSwDSvESxDUyBRyDP{BQzCR}>O{@Q}?Q?R|>Q|=P{=P{=Q{?Q|=O~?P~CT?P~AN>L}>N:J{:L|8Jz8JzQ|=P{Q|=P{Q{=PzQ{Q{=P|=P{R|@Qy@Rz>Pv>QvBRuBRvCSwASvDQvCRvDRwBPuBPvBPuBPuCPyBQxBQzBQ{DS}BQzBQ{CRyCRxDQyCQxBPuAOtBPtBPt@NqBPvANt@MpAOqBNq@Mo@MoDQrBOqBOqERtFSuGTvDTuHWxHWxLY{KXzN[zLZyQ\|O]}P]}P^zP^|Q_|V_{U_|YcZb^i]hbjbkbkekgofpgnisqvovqxoupunvmunvnvltksjtmypyr|q}q{m{n{p}stwxz{|{}|{}|z||}||{}yzzywwxwywwuywxw{zxxz{xz|{{|{|{|zz{|zyyuuusrqprsrsvqwvvwtttstrrsqrqssqsrrsqqqnpp~p~qo}qol{n}m|l{kzkyiwjzkzm|kzk{kzjyjyhwfugvhwgvgvgwhwiwl{ixl{l{o~n}n}o}n}n}srrrsrtsttustvvvuyyxzzzxzxxvwwvwvwvxwzy{||~~¹º¹Ǿ½ýÿúǹøŽɿȾɼ˾μͩŸ—ÕÕƕǖǖǗȖƗȚÝÝ¢£¤ťĥĥħéŪèǧŧŧħȨȨɧĨǨĪƬŭƮƱʭŭɮɬǭǬǫƬĬĬĭȬǭǬǮɪĮƭưıƲȮȮŭDZƱƷƴúȽɺȺʷƶĴõųȮȮƯʯʪƫɫɪȩǩOVXPUXNRTPSUNRSNSSMUQJPONQSNRSKRSJQQLRTLRULQTNRUOSVMRVKQRLSRLRSLQTJMPIMQINRFKPDJNDHLFIPGKREHOEIOEKNDIMAJLAMNAKLAJKBJKCKP@HO>EL>EN?FQ?FP?FOFL>EN>FQ>GP@GQ?FN=CK>DN>DO@FR>FQ?FR>EOAGR?GQ>GP@IRDNVCLUCJSAHRBISCJTCJTBISCJUAHR@GPAISDJWCIWEKYAGTAGR?EQ?ER@ER?EQ?EQ@GU>GT=CT=ES?HU?FT=FU?GS?DQ@DQ>CP@FSCGRCGRBHS>EP@FR=DP=FSFQ>DP=CP>DPAFS>EO?GPDIVCHUAGRAGSBHS@GR@IS?IQDNVAJSBKUCKVCLWEMXENXFOXFPXDMVGPXFOXHQZKU]ISYGPZJQZJNYLP[MQ\KQ[IPZKP[PT_PT_OT^NS]PT_QU`PUaRWcTVcVWcUWcWZdZ\b]\dcbhihkkjhkjijlgkkgonklkionlllhomgtplvqk|vnyoym{i}l~o~llproo~yh{|q~u}rrsopuorxmqwglsfirdirdhschrchtcju`grbitcit`hq_gq]en]cn\am_bn[bnZcmW^iV_hW_iU\hU]hU]iT]lX`nU]iS^iXcoXblWboXcoYcm\dp[cp_gt]er_gt`guclxbgvjl{xz}sw~ouen|bkz`gubjwbkw`jyWdqXftUcpTcpVcn[cpW`nX^oW_nXanX_m[bpYbmX`nZ`nY^l[^i[_jaepaer`bs^anZ^kVZfUYeRYeQWdRXgNUgOVhRYkSZkSZlPWkNUhNUhJQdKReHObGOaGN`GOaFN`FM`JQdGN`HPbJQcMTfOWhOYiMWgMVgKTdMVhMThOViOViJQdKQeLQhLSfLSfNUhMTgKReLSfIOcJOgKQgKQfGNaHObJPcFM`FM_AO]BP^EQaDM^CK^EN`FOaFNaIQcLSgKPgNSjLSjOWoOVnOXoMXmO[oM[kMZkO\pNZqOZuPYrOVmIQhGNeGOeFNdGMeGMdFMeDLbEMdEMcEMcFNdDLdFMfEKdEKcBIc?If?Jf?IeAMf>Kc?Kc>Hc>Hc?Je>Kd>Ie@KgAKg@Jg@IfBLhBLj@Lj=Li;Ig;Igi1>j.>i,=j,>l,>l*;j*;i);i);i*o+=n-=p/>q.=q-?p->p,q/?r-@q.@r,>p,=q+

  • Mj@Nj@Jf=Hd>IfCKi@Lh>Ie>IfBLiALiBMiBLiALhALhCNjCNkCOiERlGUpGUoDUoESnEToDRlJVpKWqNYsO[uP\vN[uMYsMYsMZtMYqO[sM]rLZpP[rQ^qP]pR_rS^rT_sX`uU^rW]pV^tW_uW`wR_tT[pT\rRXnPWoOXoNZoNZpU]p[bsbhwqs~~ztw{vzopokrpnzonxqmu|}~y{~|zz~gmn>LISeZby`b{[RmGWpIf~YQiESiAczQm_vjzqu{qw|x{}^geMZW'+D0/H0-H-XuUNpF2V+JjC6T053$>(&?)";"62;S=.E,1I*6>X0C_6PkD*E$@9Q2HaC8Q-Sn?z]]|7Zz6Sr4)E:X'@[2XLj9Jj5cKZ|BVs4JB(=52/.0,::),"+. #!%/;;-;;.<;-96%321::6BA*;26K
  • u.>r0Au->s/@t/?s0@u1Bv0Bv/@t.@s.?s.?s0@t0Au/@t/As1Cu0Bt0Bt1Cu0Bt1Cu4Ar5Ct3Ct2Bs3Ew3Ew1Dv2Ew2Bv2Dv3Ev2Dt3Ex4Fx6I{6Ey9Fw8Ew:Hx:Hw6Gu9Ky5Iv7Ju6Gw8Jw7Iz6Hy6Gw7Fx7Hu6Gv8Hz7Fw:Hy9Fx;HxL};I{Ly=Jx@M{=Lx=O{Ky=Ky=Mx>Lw?Mz:KxO};Kz9Lz9Jy9Jy8Iw9Jy5Hy7Hz2Hw2Gw1It1Gw2Hw1Gv0Gu1Ew5Fx4Ex4Fx3Eu5Du5Fv6Gt9Gu8Fu:Hw:Hw8Fu:Gw6Gx7Gx7Gz8J|7Hz7Gx8Iz8Iz:J|8K{8K|8J}:L}:J{9HzR|>Q|@P|@Q}@Q}BP|DS{CQ{CPxCRyET{CRzCSzGT|FT|FTyGUxFTzHV|HV{HVyGUxGUxGVwGTxGUxESvCQuDRvERvESwESwDSvIVxLYxMZzKXxOYyOYyMUzLVyLUyJRuFRvFRvEQwDRwERyEQ|CS|DR~>O{@Q~?Q~>Q|Q|>Q|=Q|>P>O}>P}AR=O|@NR}=PyQ{Q{=P{>Q|=PyFO?FP?EQ@FR?EQ>EP;EP>EP=CN>DP>EQ>DP?EQDPAGR>GQ?GR@HS>FQ?FRIcKf>Lg?JfAIfBKhBMi@Kf@Kg>Lg=Ke?Lgj.?k.>j.>j-?k-@k,>l-?n,=l.@n.@n-?n-?m-?m->l,r-?q,>p,?q+=q->r,?p-?q-?q,>p.@r->s,=q,=q->u';q)=r*?q+>r+>r+?q,>p-@r/?q0@s1Bu/?r0@s/?s.?s.?s+@s,?r->r/?s/As1Cu0Bt0Bt2Dv1Bt2Cr2Cs3Bv4Cv2Dv2Dv1Cu2Dv3Ew2Dv3Ew3Ex6Hz4Gx5Dw4Dw6Gx5Fv7Hu9Hv9Kx7Hv8Ix8Jx6Jt7Iw7Iv7Iv6Hy7Iy5Hu5Gv;I{9Iz6Gw9Jw8KxKzLx>Ly>My=Lw>Mx?Mx?MyMx=Lw?Ly=Lv>NxO|>O{ARzCT|AR{DR{BRzET|DT}DPzESwCRvDSxDRwFTyGUyBPuFTyESvFTvFTwIWzGUxESvFTwFTxGUxGUxHVyETvDTtGUvHTvGTwIVvHUtKXxLYyMZzJWwLYyJXwJXwIVvESuDQsDRvDRuFSzETwCSyDS|ETCQ}APz>NxQ|=P{=PzK|:Iz9Iz:J{:J{7Iy6Hz6Gy2Fz4Gz1Gy1Gy1Gx2Gy7I{5Gz5Gz2Fy6Gw5Gw6Hy5Fw4Jz5Jy3Hx3Hx5Iu3Iv6Gv6Hv6Hw7Hv6It7Ju7Ju:Jy:Kz8Jw6Ku6Hv8Iw8Ix8Jw:MxQ{;Ny;Nz>N{?Qy@OyBQwBPuBQtBOsAOsDRuDRtCQuCQtBPsBRtARt@QrAQq@Qr?PrAOuARuARvBQv?RuBSvARuCPvCPvESxDRwCQvDRxCRtDQtESvGTwHUwGUxGTuFSuHUxIVxKXzKXwLYyLYyN[{OYyOZzP[|P]}P]P]P]Q^~P]}P_zQ^~P]}O[|N[{M[zN[zP^{Q\wT_{T_~WbWb[f[f]h`k_j`kbm`ndqdodpcsfshugthvixiwhwlukvhvkwhwgvhwn}m|n}qvuwvvtvuwuwwx|{zyzxwxrpsstsrrstvvuwvvtwstwywzy{y{{{yzyyxwsrppm|n}ppm}pn}qruwuvsvwrtssrrstrrrssrspsssqqrqqpo~n}onl}n|m|k{m|m|mzmzlyjwkxlyivivjwkxkxivmzjylykxj{lzl{k}l}l{o~n}po~qqqqro~qqqsrsrutuustuvuy{yyxuxvxsxvvwxwwxxyzwwwxyyyzxz{}|·ÿº·üĿŤƝěȗŗȔƕƕƗțɜŞçƫũëĦåħ¥ŧŧĦǩɦƥǧǦƨƫǪĬíıŰŲɯDZǰƱɱȮǯȮȳȭǭǯɭɮòƴ´ŶųƵDZõŴ÷ƶƶĹȹǹȹȸƶƸƴdz´ȱʮƭɬȬ˫ȩȪVY]W\_V[]U[[U[[RXWQUTPTSQUTRVUQUTSVUSUUUUXWWZXX[TWYRWXSXXSYWQWTQUTPSTQRTMRSNSVNQTRSWPQVRSYMPVMPUNQVLOSIMRHNTINTLPVKPSJNSINQHMRFMSFMSHOUHOUIPVISVHOUCJPIPVFLVHKTJOUINTHKSELTDKSDKTCJUDJUEKVCKUEMXELUFMUELTFMVIPYIOZKR[IPZJO\IO[HOXIPYGMUFNVEOUFNVCJTELVFLXFLYDJYBHVAGVDJVAIRBIS?FQ>DP>DP@FR=DO>EO?FP?EP>EO>EO>EO@GQ@GPAGQCFQCGQCHSBHT@FRAGS?EQ>DPAGSBHTAGSAGTCIUBHTBHTBITCKV@HSDLWFNYEMXCJUGMYELXDKWGMYGMYHNZFMYENXFNYGOZGMYHNZGP[HQ\HQ\HPZKP]PT`RUbQUaRT`RWaPT_QWbOVaPV`RV_TV`SV`TVcUWdWXeWYfVXcZ\f^`m`bmaajdekhgmklnurqxtl|s}szmznxn}vmyoxl~xlzpzl~n~l}kjlllpropruxy}~vtvsmqvmqxhktbfpbfp_cn_dnbfq`epaep_cp^fq_bobdoagrcitfiuikqfip_fm\dlVcmUakYblXakYalX`jZbmZbpY`qY`p[cnYalYalZbo[cn[co\dq^fs_gt^fs_dr`ftagtdgsmo}no}oq}flxclybkxchvbhuaguZao\dp[cqYdoY`kX]jY_kZ`lW^iW]iU[gW^j]am^bnaeq`dp\`m[`lZ^j`bncfr`epaepZ_i[_j^bmX]jRXgRXhPWiMTeMTgMTgMTgJQdHObHObFMaEL_FM`EL_EL_ELaHMeHNcGP`HPbIOcJQdNTgNTgLSfJQdKReKReLSfLSfLSfJQdJQdKQdKRdKQdIPcGPbHPcJQdHObGM`FM`IPcGNaHPcFNaGPcHPcFNaGObHPcHObHNaGNaIQcKReKRfLTiOVkOViNWjOViNViOZlO[mO\oN[nP]oMZmKWmKWmIUnHSjFQfCNeEMgEKeEKeDKfBIfAJe@Md>HaCKeDKgDJgCKg@JeAJf@JeAKfAKf?Je?Kf?Jf?Jg?Je>Gc?Ke=Jd@Mg>Ie;IdHd@Jf?Jf?Jf?Kf=Lf=Kf=Lg=Kf>Lh>Mj;Jh;Jj=LlKnLm=Mnk,>j/Al0Am0Am2Co2Co1Bn1Ep0Dm0Cn0Cn0Aq2Dr1Dr2Ds1Cq/@n->l.?n/?p.>o,>n+p,@q,q-=r+r0?s0?s.?s,r*?r*>q+>r.?s->r.@r.@r/As3Ew/As/As2Au1Au1Au2Cu1Cu2Dv0Bt1Cu1Cu3Ew3Ew3Ew4Fx4Fx5Dx3Ct7Gx8Gy:Gv;Ix7Iw7Hv:Ky8Iw6Iw6Hv6Hw7Iy7Ix6Hx8Jy7Jw9Hz8Hx7Gy8Iw7Iv9Jx:GwIy=HyL{=Kz>L{=Kx>LyMx=Lw=Lw?Ny@Oz>Mx?Ny=Lw?NyO{?Q{BQ|BQ|AU{AT|@SzER|ET~CR{CRyESyDRvDRwDRwESxFTvDRxFTyESxHVwGUxESvHVyHVyHVyESvFTwGUxGUyIWyGWwGVwFSuFSuHUxJWvJXxLYyOYyMXxJWwLYyKXxIVvERtCPrDQtCRuESvFTxCQvDUvET{CR|CR{CR{BQy@Oz?O|>P{ML};Iz;K|9Iz:Iz8Jz5Hx3Hv4H{3Fy1Ew0Fw1Fx0Dv3Hv4Ev5Gx0Ft4Iy7Hx5Gx6Hu6Hw3Iu4Iu4Iv4Iv2Gu6It5Ht5Ht6It6It7Ju7Ju7Ju7Ju7Hu9Jv9Ju6Kv8Kw6Iu9Lw8Kv:Mu=Ov=Nu;Mt>Px;Nv:MuPy>PxBQxCSwBPtBPrBQrAQrCStCRtCRtDStBRsBQrAPsAQs?Np@Op@NrBOsANtARsAQsBRtDQwERxESuDRuESwFTwHVxIWzIVyJWzKX{KW{MZ}LY{LY{LY{MZ~LY{MZ|M[zM[{PZzP[{NZyN]yNZ{N[{N[|N[O\|P]}O\|R`P]}O\|N[|P]}P]}P]}O]|S^|S^{P\zT_|U`}VaXcZe[f[eZh[h\j^k^kbndqcodqcpcpdqcqdrgvhuiviviugwgwhxixjym|p~qrsvtsurrtqstvvxxwvuqppqsrppr}q~sr~ttsvtuuttwwvwxxvwzxvxwxwwsqpo~m|m|kzkzl|k{n~ortsuxwrvuttsusssttrqttstsrtqrppqo~pnnn|n}n}o~l{l|myn{lyn{kxlxivkxkwjvn{nznzn|n{kxl|jzjyl{m|n}m|m|o~poo~q~m|n}qpqqrrrrt~s~tr~pprsvuutvwtuwuvuwvwwxwwwtxuuuvvxxvvwxzz{~}~~~ŷ·½ž¾¡ÙǙƗƖǕƖșɜțĝãĦ©§¥ĥĦĤƨǥŧǥɦɥȩȧĬƭųưŰŲDZưƯóŲĴųŲƯïƱIJĶƷƵŵŴöɵĴĶŵķŶŹȸȺɹȷƸǸƴǵDZïɯȮǬǪƭȭɬ\``[``[aaX^^W]]U[[VZYTXXSWVUYXSWVUXWXZY\^][^]]^_[[^XZ\UXWRWVSXWTWXXXZTVXSVXUUWSSWSSXTUYTWZOUWRVYNRVNQVPSXMPWNQYMRWMSVNRWRQWLPTLOTLQXLRXLQUMPUMRUMTYIQWLSYKQWLQWLQWMPVLPUKPXHOXEMUELSELTHOVGNWHNWGNVIPYGNUHOUIPYJQYKRYIPYJP[IPZIPYHOWIPXGOWFPVGPWIPZGOZFLXFLXFLXDJVDJVELWCJTDKUBJTAIR@GQ?FQ?EQ@IS?FP?FP@GQ@GQ>EO?GQ?GP?FQ@FSCJVDJVCIUBHT@FR@FR?EQAGSAGSAGSCIU@FRBHTBHTCJVCKVBJUFNYHP[IP[IO[KQ]JP\JNZLQ]KQ]JP\JQ\IQ\GOZFLXGMYGMYIO[JP\JQ\JQ]HO[KP\PT_RVaRT`QT`QUaPV_OV^QW_SW]UX`UXbY\dY\eY\e\_g]`h[^h`bkcdnddkeejkjkllisplzsj|ujynxmvj{q{q}s~q~p~m|mlokmnommlmnpur~pvwoopoootmkvcfqafkbeo_`m_`l`bl`cl`ck`dl^cl`dlcfoeitfkulourstqrntvuhoo\fi^gmahp`hp\fqYcm[dmZamYaoYanYan[cn[dn[cp]ep\do\dq^fs`hu_htafubhvagtcerfhuikxkn|dkyaivaiu`eragvciw^dr^er[cnWalX`kU[gU[gZ`lX_kY_k[_j[`k\_l^`m`bo^`l\_j\^j[]i_`l`bn_co^_l[]i\^j[_jSWdLSaNTeKSeGN`HOaIPcKReFM`EL_DL_DK]EL^EL^DK^DJ^EL_GOcDL_DO_FM`GObHPcKReLSfIQdIQdJPcJPcJQdJQdJPcJQdHOaGOcGNcGOdHOeHPeFNcFMbBJ`BJ^BJ]FNaEM`GNaFM`FM`HOaKRdJReFNaHPcGM`IOcIPcJQdMUiNVkLUgMTgOWjOWjMViMYjOZlP\sNYoNYpKWnJVlFRhFRhEQgEOhDNfAKdAKe@IeDJeCIe@Ie>Kd=GbAKf?IdAKfAIf?He>HcAKfBMgAKf=Gd>IeIe=Ic;GaJe:Id=KfIfMnNon.>p->o.@p,=m/=n.>o-=o,n*=l+;l+;n*p,>q,>q,=p+=o*;p'q,?q,>p->r-=q)>p*>p)=p*=q,K{Lx=Kx>My=Lw>Mx>Mx>Mx=Lx?Nx=Lw>Mx>Mx>Mx;Lx;Lx=Nz;LxKz9Ky9Jx9Jx:M{9Ky:Lz;N|=M{Lz=Oz:LwAP{AP{@P{AQ|DS~BR}DS~DR{DT}FU~CRyERyET{ESzESwESxDRxFTvFTwFTwESvFTzESuGUxESvGUxFTwGVyGUxHVyFTxGUyHVyIVxGTvHUxHUwIWwKXxNYyMXxMWwLWwLXxKVvGUuFSvERtERtFSuDQsBQtESwGUxGUxDRvET|CRyAPxBQy@P{AN|>P{?O{=M|:M{Mo=Lp?Os>Nr>Os@Pt=Mr>Ns?Kr?Ns?MsAPu@QuAQv@PtAPu>QuBRvCSuBRtDRtETtETsBRrGTtHVvHUvHUvFTsDRsCQrBOqAPpAQoBQsBRsERuCRuDQuFVvKX|LZ}KY}KY|JX{LY|MZ}MZ}O[~MZ}MZ}MZ}N[~MZ|MZ|LY{LY}LZ{M[}OZyOZzOZzP[{OZzL[wM[xN[|O\|MZzN[{P]}P]}N[{O\|P]}P]|O\{O\|P]}Q^S\zT_}T^{T_U`U`U`WbUbVcWdWeYf]iYe\i]j^jam`m`lbobqbpanbodqfserhugtiufugvhwjxlzo}o}qqsqrpqprpsqsssqqn~m}n}qo~qrn}n}m|qo~po~psrspsttuwvwxuuvvtuttttttppm|m|m|n|izk{ol|n|qvuuwwwvwwyywvtsrsutrsrtrrqqqptproqo~o~m}n}l|nzo|n{n|o|lxm}iyixkzjykzjzjykykymxm{lxjykzl{l{m|n}m|o~o}p~p~o}qp~qqrrq~r}r~tts~r}s}s}putuuuwtstsstrwwwvwvvwuwuuutuvwvuvvyyzz}}||~~~~½ȷʿĽ­ğЦŢĠśŚśǘǖǘțɘșȜŝĢĤ§ª¥æĥ¦ǦŤħǦǦˣǨ˧ƨêªijôı´ŴųĵƴõĶĺȺǴ¶ĴöĺǺǻǺǷƴôĶƵŷǶƷǷƷŷźɸǷƷȵƶƴƳŭǯȭƯȬįǯȯZ^`]acZ_`W]^U[\Z^^Z^]W[YW[ZX]\WZY[]\]_^_a`\^]\^]\^\^`_\]\WZYZ[[XY[UVZVVXWZXVWWUUXUVXVW\SUZRWYRVWRVYRUYPSXNQVPTXPSXPRXPRWSTYRSXNQVPRWORWMPUOQVMPULQUNTWMUZMSZNTZMRWMSWNQWNTZMRXKOVMRZIPYKR[JQYIPVKRXHPVJPVMQWMRXMQWNRZIQYJQ\JQZKRZKRZHOWHOXGOVHNWGNYGNXIOZHNZHNZGMYFLYFMXDKUELUFITGMWAIS@GQAHRAHS>EO>EO?GP>EOADPBEPBEPAFQBIUEKWBHTCIUDJVBHTDJVAGSBHTBHTAGSEKWCIUEKWEKWFMZGOZFNYHP[FNYHNZHNZIO[JP\KO[NR^NR^MT`KR^JR]HP[ELWHNZFLXHNZFLXHO[KP\NQ]NR]PT_PT_SU`TWaRV`RV_RXaRYaUYcVYbX[dY\dZ^f[^fY]eZ]f]_h``idckigmigjliimifplgwndypg{si|sh}sfvjvjxkylxkxjwixg{h~iijmljhmpqrm|ykttnlmoikqfiuceq_em_cm\`m_an^`l`ck`dhaemadmbeoegsdgtkoxruw{zvsuqskknlpstosvdlq\enZemXahW_jZbp\dp\do]fp_fsahv_hs^fq^gtbjwckxdlycivagragsafrbfqfhuhl{cjxckx`gt^cp\bn^ep_epagr]doYbm[bmZ`l[amZ`l]bn]`l[_kY]iX\iY\i[_k\_j[]i\]jY[gY[g^`l_an]_mZ\hZ]hZ_kSWdKQaIP_KRdIPbIPbHObJQdFM`DK^DL^CL]BJ\CI\DK]DK^ENaFM`DM`EObDPbFOcGObIPcKReIQdFNaIQdHPcGPcFObDL_FObDM`EMaEMbDMbCNbBMaAL`CNbCK`DL`DL_FNaEM`GObGNaGNaKQfKQfIRdJReIQdJPdKRdIScLTfLVgLTgOWjOWjOWjMUhNWjKXiMXmMYoO[pMZoHTjFRhFRhEQgCNeCLgCLhAKg@Ie@Ie@Hf?GeIe?He?He=Fc=Fc?Hd>He>Ie>IeJc=Jc=Ic;Ga:Id;Id:Hc;Id:Hc;Id;JeIe?Je>Ie?KgIi;Ih:Ih;Ji:Ij;Jk;Jk9Ij8Hi?No=LmMn=LmNo=Lnp/?p,=n1m.=o+l->l,=k+o*=m,=n*:k,;m+;m.p);m,>p,>o.@q-?q);m);m(:l*p,>m*q+=o*=o)=o)p*?q-?q-?q-?q0?s.>q/?r/As-?p+=p(=o)=q*>q*>q+>r->r,>p-?q/As-?q0Bt.@r0Bt.@r.As/Bs/As/As0Bt0Bt/As0Bu1Bu1Du0Ds3Eu2Dt4Fu6Fw4Ct8Ev9Gv:Hw:Iw7Iw8Iw6Gu6Iw7Iy7Iy6Hx7Iy7Iy7Iy6Hy7Hx7Hy7Iw8Jx8Jx:Ky9Jx;KzL{=KzKx>Mw>Mx=Lw>Mx>Mu?Nz?Ny>Mx>Mx?Ny9KwLx;M|Ky>Ly?NxAOz?OzBQ|BQ|FR~ER|DR|CS|CR{DS}CSzFT{DRzFTyESxFTyDRwFTyGUwDRuDRuDSuDRuDRuCQtFTwETwFTwHTxIVyHVyIVyIVxIVxIVxFSvHUuGUvJVvNXxJUuOYyNYyLWwJUuJTtJUuESuFSuERtFSuGTwFTvGTvJWxHXwCRuDSwCQvCPwBQwAPyBQ}BP}=O{MqAQt@Pt>Nq=Nq=Mp>Np?Np?Op?Np>Lo?MqAOs>Lo@OpBPr?Op>No@OpAPqBPr@OpCQsBQrBRsCSuDTuGVwGVuKWwN\zM\zS^|T_~R^{T_~P\yKVtISqBOlDSoEStHUvJXxJWzJWzIXzN_R_R^P]MZ~N[~N[~O\N[~MZ}M[~LZ}PY}M[~KX{JWyLYzMZ}MWzOX|P[zP[{P[{OZzOZzKZvO]yO\}LYyMZzN[{LYzM\{Q[{OZzO\|P]P]P]|O\|P]~R`{P\~P]}Q^~Q^~S`S`S`S`UbT`T`ScTcUcWfYiWfZiZiYiZf\i[j^k`mananandqbocpbrbrcqdrftkyn|o}nn~n~n~n~k{l|m}ppqpqon~l|k{jyl{jyl{m|l{m|n}m|o~po~n}n}n}pprttqrtsstvtuwtusstrsspn}n}o~n}m|l{m|n}o~n}rsvvuwwwvvvuvutuuuttsuuusstrsrrqpprppq|oq}mym}oyr|m|lzjzhwl{hwjziykziyn|m{m{m{kykym{lyj{jyl{n{n|p~p~p~n}l{n}o~p~rppt~p{tq|s~s~p{p{r|p{p{s~r}r}s~r}rrtuturqsuswuvurwuutwvwwwvvxyyz|}|}{~~ǹޞ÷ÝŞàÞƢĞƝǙĚŖ×ǘǙșȘșɞǚťä¨çç§æ¥ä¤¦ƥťťĢĤţƥĥťƦʥƧȦëŪij³µó´õµ¶ĺ¾ƾƼżǹúĿżû·µ·öƶǶƶƶƷǶķŹǶŸƵŷǸǵDzį˭ɮƯȮǶι͵Z^]]a_\`_X_]W][X\\W[]VZ[Z_][]\\^]\^][]\[]\[]\Z\[^`_`ba]_^\_^\^^Z\\YY]WWYVXWUXVVXXUVYXY[TVXRWXTXZRVZSWZRVZQTYQTYRUZNQVPSXORWPSXPTYOTXLRVMRVQUYPTXMRVOUYLTWLTVNSWSW[QVZOSWMRWMRXMRXKPVKPVHPVJQVLSYIPVJPVKPVKPVKPVKPVLQWJPWGNVIOXJQZKRYJRXHOVHOWHOWGNXGNXHOYHNYFMWLS]HOYHOZFNXGNXILXGKWCIS@GOCJRBIRAHR@GQAGQCFRDHSDHSDHSCHTBHTDJVCIUCIUEKWCIUBHTBHTBHTCIUFLXEKWGMYFLXIO[ELXJR]IQ\KS^KR]IO[IO[KQ]JNZKNZKO[IP\JP\MT_LT_NVaFMYIO[JP\HNZHNZINZOR^KO[QU`OS[SW_VYbVYbSV_VZcX\eW[dVZcX[d[_hZ\iZ[g\^j\^j[]h`akcbmhgnjhopmopmjrnjvqjwsjvqh|sj{sh{sh}vkyqdvj}vlyth|vl|vizte~xf~wfzgzfyh|k|k|koxtso}|mtsklnlcgndfqaep]dk`dm^bn_bnbepdgocho`fmdhqhjtfhuikyoqzvvx~uxzv~qv~~xsvtcln^hlZcj[cl[cp]eq^fq`gp_erbgschuchuciwgm{io}cjxajt^erags`grbfsehxdgwbhv`gu_gt^fp^coafrags_fr]doags]co_eq[am]co[am\`kUYdVZeUYdW[hX\i\_k\akX\eW[eX\gZ^iZ\iZ\hZ[h[]i[]iX[hPT`JP^JRbLSdIPbIPcEL`FM`EL_BJ]FO`EO_EM^FN_CL]CL_FObEPbBPaCO`BM_FOaGObIQdJReGObFNaGObGObBJ]DL_CK^CK^BJ]BJ`AJ_?K_BNbAMaBLbEMbDLaDL_EM`FNaFObJQdJQdIOfJQfJReHPcKSfMTgJReJSdMVfLUfMThNViNViNVhOWjNUiLUjLWlLYqLXoNZrJVoFRjCOeCOdAMdBNh@Lg=Ib>Id;Fc;Ea:D`;Ea6D_8Ga8Ea;Ea;Fb;FbHe=FcIf>If>If=HfMn;JkMnMn;Kkp2?q1@p0Bo3Cp2?o1?n2@p3Ar2@n-?m.?m.?m+l,>k1>m/=k0>l0>l/=k/>l+>k+=l*=n'=l&n*n(:j);k(:j(:i*p+=p,>q-?q-?o,>n,>n-@p+=m-?o+=m+=l*q,>p,>p-@r-@r-=q/?r.>q-@q.@r.?q+>p*>p*>q+@r+>q-?q-?q-?r.@r/As/As.@r0Bt-?q.@r0Bt/As/As1Cu.@s.@s0Bs1Cs1Cs1Cs1Cs3Eu1Ds6Ew8Fw6Du7Et9Gv9Gv:Hw7Iw8Ix6Hy7Iy6Hx6Hx7Iy7Iy5Gw7Iw7Iy7Jw6Hv6Hv9Kv9Jy8Ix:Kx;Hx;IyJxL{>Lz=JwMx=Lw=Lu=Lu?Nw>MvMx>MxKy>Ox@Pz@Oz?OzBN{BOyCPzER|CP{DQ|ER|FR}FT{DRzFT|ESwESwESxFTyDRwDRuESvDRuFRuERvFSvDSvFTwETwHUxHTxHUwIVxFTvJWyKYzHUwIWxJUtJUuMXxMXxNYyMXxLWwKVvKVvITtITtJUuIWyFSvFSvHUwHUuHUtKXwIVvHVsGUuFSuFVvCQtDRvDQzBP|?L{>Mz@M{;MyM9Jz9Iw9Jx8Jx7Iz8Jz9K{9K|5Lz3Ju/Gs1Iu3Hu3Iw5Gu4Gu4Et3Ev2Du2Ct2Du4Es4Ft6Hv8Iw7Ix6Hw6Iv7Kv6It6Jt6Gr7Is8Kr8Js:Lu;Ns=Mq=Mq?PpDRuCQoBQoDRpESqCQoBQoDQpCPnAOoCOoCOqDQsCPrCQqCQqBOoBOnCRqDRsDSrDTrDSuDSuCRsCRqEUtHVvFUtJVwN\xR^zXc_h]g`i]eV_|OWuIUpKVuLWuO[xT_~T`~R]~Q]~O]}UcUaR_Q^Q]P]~O\N[~N[~M[}PY~PZ}NX|NX}MZ}LY{LY{MZ}PX{NW{O[zP[{P[{OZzR]}M\xN\yMZ{O\|MYzLYyMZyLYyOYyOZzM[{N[~N[~P]|O\|O\|Q^~MZzP]}P]|Q^S`R_R_P_RaSaP_Q`QaRaSbTcVeXgTdXeXeXeXeYfYfYf[h]j`m`m`manao_l^laodrgujxlzjxo}k|l|l|k{l|l|k{k{ol|n~n}l{k{k{kyjyl{jyfuixhwixjyl{l{m|l{l{m|m|pprqo~rprqtsqrrtrsstssqpl{m|m|l{l{l{l{m|n}m|qqsuvuvuvvtuuttusutuxstttrttssrqrqpr~r~o{o|m}nxozoziykyjyjxkykzkylzkym{lzn|kyn|lzlzlzkym{m{lzlzlzm{o}o~s|q{r}q|p|r}s~r}ttq|q|r}q|ozq|q|p{p{q|q|p{r}uo~qtrqruusvsututsurutwwyywyx{{zzz|}{~~¼ŗ»žÜÞáŝ˜™˜ŗĘŖƘǘȘĘƝȞŢġ£åĦĥåãĤǢƦɤǤäãäåæĥå妩°ñ¾ſſĿƻǷŵƴĵŵŶúȸƶŹȶķȸǶȶȯˮȯʬɭǰȲʱ]a`^aa]a`Z_^Z_^Y]\X]^Y\\]_^]_^Z\[Y[ZXZYVXWY[[\^][]\]_\^_\]\Z__^[^][]\XZZXZZY[ZWYXTUWWXZQVWTXYTWXTXYSWXPUXRUXSV[TW\QTYRUZNQVPTYSX\NSVPUXNSWNSWQSYPUYPVZLUWLVXLVXPWZPTXOSXMSVKPUMRXKOUJPVHOUJQWKQYJPXJQVJOULQWMPWLPWLQWLRYJR[IQXHOTJQWLQWKQVKOWINXHOVIPYJQ[IPZIPZIP[IPYJQYJQYIMVHLUHKTFKTBIQBIQDKSCJTCJTCJTCHRDHSHLWGKWDHUDJVCIVDJVFLXFLXGMYHNZEKXFLXHMZHNZHNZIN[IOZIQ\GO[JR]IQ\JS^HNZIO[IO[JP\KQ]JP\KQ]MR^IQ\JR\JR]LT_HOZJP\KQ^JO\JP]JP\NQ^OS`SWbPT]PU^SV_VYbUYbW[dX\eX\eX[eWYdXZf[]i\^k\^j\]j^_kadlfgolkrmkospqupnvqnxsmyulzulwnyo~wjxl}vg~whxj~vi}wj{uh|vgzjxg}vd}xe~wdzg|i}jotpopmsuhnrkknmgjocgkceldgkefjfgkjlpfkodkoelrhkugisggtilxrtzyywwxzwtuypuodml^gn^gq]ep\cn`frbisagsciuciuflzgm|hn}io|dkwdlw`is^fp]coagrgjwfixciv`fr_fq`gqcfsdhtcgsbfrafr`gs\bn\bnZ`lX^jV\h[_iY]hY]hWYfVZfX]e\`jaek^ahW[bX^bZ^e\_f^`i`blacn_anY]iPUaKQ^JQbKRbJQaIPbGN`HOaGN`HPbFO`EL]FL]FM_FO`FN`FObDOaCQbFRcDOaDNaFM`EM`FN`DM^DL_DL_EM`AI\@H[?GZ@H[?GZ@I\AI]BJ]BK]BJ]DL`EM_DL_EM`GObHPcHPcJQeJPgJPfMShKSfIQdJReJReIQdJReJSeHReLTgNViLTgMUhMUkMUjNUlLXnMYrLXpLXpHTlDPhAMe@Md?Kd@Lf>Jd=Id:Hc9Fa9Gb5C^5B]6D_7E`6C^7E`7F`8D`9D`9D`8C_9D`9D`8C_;Fc;FcMn;Jk;KlMn;Kk>Mn=Lm;Jk=Lmm3Aq3Ar2@q/An/@n->l.?m.?n.?m-?m-?l.@n/?n0=l0=l/=l.l,>l+=k-?m,>l+>l->l+m,>n+=n*o0@q-=n-=n.Aq-@o,?o-?p-?p.@p0?p/?p0@r0@t0@s.As-@r-?r-@q,?n-?o-@o.@p/At.@p/Aq0Bq.@p/As/As/As/As0Bt/At1Cv0Bt0Bt1Cs0Br0Cr0Cs1Cs2Dt2Du3Ev3Ct5Ft6Du7Ev:Hv:Hw9Gv:Hw8Jw9Jy8Jz6Hx6Hx7Iy6Hy7Iy5Gt7Iv7Iw7Iw6Iw7Jt7Ku8Iv9Jv:Hx:Hw;IxLyKx=JwMx=Lw>Mw=Lu>Mv=Lu=LuMw;LyMx?My>My>Mx@MzBNxCPzCPzDQ|DQ|DRxFT{FT|DSzDRyGUyFUyETxDRwESxDRwESxESuBQtERtERtFSuFRuETwFTxHUwHUwFTvGTvIWyMVzIWyLUxKTwLWwMXxLWwMXxKVwMXyJUvLWwKVvJUuITtJUuLWxGTtGTuJWxIUvKXxKXxMZ{MZzNYvLWxIVyHUxGUsCRtCQvBOyAMz?Ly=Lx>Ky=N{=Nz;L{:Ky9Lx9Kz9Ky9Kz:Lz9Ky8J{8K{3Iu2Hu4It3It4Ht6Is5Hw6Ft2Es4Ft4Gt4Ft6Hv7Hv6Hu7Hw7Iu7Gt7Is:Jv8Ks8Jr:Ir:Iq>Mq>Mr=NrANsBRsFTsMXuWa~ZeXc|U`{R]vS]wVazV`{X`|S\xOWtLUtMUuKUtKUqKUrJVqIToJVqHWtIVtHVsHUuHUuHUuGVtJXuKZwLXyKYuMWuOYuW_|^e]d]dXbS]zPYvPYvOXuQ]zYa^g[eV_T`~U`VaR`Q^P]N[}P]P\MZ}N[~PY}PZ~OY}NX|PZ~KY{KXzLXzKX|MZ|N[|OXxNYyNYyP[{P[|PZxP[xP[{KYyLZyLYyMZzLYyOZzOYyOZzLZzN[zO\|P]}P]|O\|N[{P]}O\|Q^Q^O\~Q^M\}O^Q`P_N^Q]Q^Q^RaP_~QaTaS`UbR_TaUcXeTaTaVcWdXeZg[h\i^kao_napbpcqftftgukxhxjzk{l|l|m}iyjzjzl{jyjygwhxjxjyhwgvhwhwhwixgvhwfufufuethwhwjym|o~rqppqo~ppo~ppqo~o~rqsstpppo~n}l{kzjyjykzkznzn{o~psqrrusuusututttttqqrssrsusrrqrqssq~m{p{q|nynymykykykykykylzkyjxm{lzkykykylziwjxm{lzkym{m{m{n}n}q|p{r}q|r}q|q|s~us~s}r~s~r}q|r|oyoznynynyozozo|qzp~p~o~pprs~trsrtrtsstrsuuvvxxxwxy{zy|||~ԓ—İžÛĘ×ŗĘŘ×ǙșŘƜǛĞťģĥ¤¤ŤĥɣȤǤǢƢŤĤåŤ¦äĹûü¿¿ŽøķùǴŴĴŷĹǹǹǼʷƹɶŷʳűͭʭʭ̬ʫɮˬ`e`]b_^c_\`_[_^[__X\^Y\\\^][]\Y[ZZ\[Y[YWYVXZW[]ZYZYY[X\^[]\[\]\\^[^_][]ZY[YXZYWYYVVYWYZVZ[W[\SYYUXYUYZTX[UY\TW\SV[ORWPSXOSXQUYQVYSXZQUXMRTOSVORXQUYRX[KUWKUWMVXQW[PTXNRWNSWJOSIOUHOTJQWIPVJQWHPVHPXKPZKNWMPWNQXQTZLQWNRXNSYPU[MRXLPVMRXMRXKQWLPWIQYIQZIP[JQ[JQZIPXGOWKPYLPYLPYJNWIMVFKTEMUFMTFLUGMWFMWDLVFLVGJVFJVHLXHMXGNXGNXHOZIP\HNZKNZKP\HMYKR[KR\JQ[JQ[HR[GPYJT\IR\JR]IQ\KR^KQ]LR^NT`KQ]KQ]LT_KS_IQ\MU`MU`KS^LT_KS^PW`QWbNU^NU_MU^OU^SVaTXbRW_QU^UXaWZcVYbX[dW[dUZaUZbWZdY]f\_h]`j_al^bk`cmbendfnhknlkprnpsnmsnluqkxti{wm}wlxlzl|lzh|k}kzgyhygyh|g{i{e}g}f|h|iikljlnp~kwvktumqrooppqrlwxouunxxqxyqtvsnstgkohiqjiqkktoowstx}{vvtruuspv{}pjqnbloclsbluaitdkucjtbhtekwdjvflyhn|io|gmyekwemxckv_hq]foagsbhtelwdjubhtbhtbepaeqbfrcgs`dp^bn]am]amZ`lZ`lX^jZ^iZ_i[_jY[hY[gX\b[_cefijlohilcdf`be\ad]aeadhadkadladn\`lTWcMS`KS`KRaIP`IP_GN^HO^EL]GN_EM_EL^EL^DK]EM_FOaGNaDQcCP`CO`CL_DL_BK^AJ]CL\CL^BJ]?GZ@H[>FY>FY?GZ?GZ@GZBI\BI\BK^CK^DL_FNaFM`FMaGObFNaGObIQdJQdIOeIOeHOdIRdLSgKSfIQdIRdJQdLTfJUfKReKSfKSgKShLTjMUmOWnNWqLXpJVnGSkCOgBNeAMe?KfJlLml->l.?m/@m1>m1?n1@o1?n.@m.?m-?l/?n0?m->l,=k-?m+=k+=k.?m.>m->l.?l.?m,?m.@q-?p-?n-?m,>o+=m+=m,>o->o/>p,=n.>o-@p-?p.@p-@o.@p-?o.Ap/Aq.@p.@p1Cr1Cs1Cs0Bq2Cp1Dq4Du0@q3Du1Bs2Bs2Bs1Ar.Ap3Cq3Dr0Ao4Dv1Ar2Bs2Bs1Ar1Bs2Bs1Ar0@q0@q-?p0Bs1Dt1Cs1Cs0Br/Ar/Ar/Aq0Br0Bu2Dv2Dw3Ew4Fv2Ds0Bs/Aq3Dt4Du4Du2Eu2Dt3Eu5Ft6Gv7Hv7Ev9Gw;Ix;Ix9Gv9Hw9Jx8Iw7Iw6Hx6Hx7Iy6Hy8Jw6Hv8Jx7Iw7Jx7Ku7Ku8Ju9Iu;Iv;HuKxKx>Lx?Kx=Kx=Kx>My?Nx=Lw>NvMx:LxLy>LyMv@Ox@Px>NxBOxBOyCPzER{CRxFSzFT{FT{GRyFQyEQwGRyFQxFQxGSxGSyDRxESwESuERvFStERtGTvGSvETvFUvGTvGTvHTwJSwKUuJUvLVzKTxKTwLWvKVvMXxNZyMXuLVuMXvLWvJUtLWvMXuMXvLVuIXuHVsIWtJXuM[xMZxM[xLZxOZzMXxNZyIVwHUuDQrESwCPx@MxAMx=Nu>My:KwNp>Nm@OoAOoFVsOZwYb{bhnvpwiofm_fz`h|dllriodj\b|Y`{Y_|W^xV_vU\sR[oNZoNXpP\sR\wQ[xOYxPYyNXwMVtNYxOZxNYyOZzOXwRXuTXwT\wT[wSZwTZwT[xQ[xT]zT]zV__igpakXa}T]}W_S_Q^R_P]O\~O\~N\KY|M[~QY}NY}NX|PZ~NX|KX|LY{J[{LX|KXzMZ|LZzMZzNXxP[{OZ{P[xOZzNYyPZzOX|LZyKXxLYxOXyMYxOZzM[{N[{N[{O\|MZ{P]}N[{P]}Q^~Q^R_P]NZ}M]}O^N^~P]P\~O\~P]N[}N^|O^}O_~Q]}Q]}Q_P^~WaU`VaT^~Q_Q^~R_S_VbTaVcXe[gYh\k\k]l`ncqesesdteufvhxiyl|jzgxjyixhxixfvfvgvgvhwgvgvfufugvethwgvdscrcrcretethwkzm|m|n}po~o~o~n}n}m|n}o}p~o~qprrrspqqm|m|m|kzl{jyiyiukwl|l{m|o~qpqrrssusssrsssqsrpsstsrrsppssp}r|q|p{ozmxmxp{l{m{lzkyjxlzlzkyhvjxlzlziwkyjxjxm{kyjxkylzkznxozr}s~q|ozq|r}p{p{q|po~r|ozp{pzo}lzozozoznynyp|s{r|o|n}l{n}m|o}r}t|qo~s|qsqqpsssssuwyxxuxvwvxwyzy}}}~žš—ŖØƗėØǗǘǗÚǝśŸšǤ¤ģġãĤĥƢäģĦŢÿſÿÿÿþĺ¹øĶĵƶƸŹǺɺǻȺȸǷǵȵȰɭȭǫȭˬˮˬ]c^]c^_c`^aa[_^\`aZ_`[^^]_^[]\Y[[]^]\^[Y[XY[WZ][X\Z[\Y\^[]^X^_Z]^Z^`[[_Z]__Z\[\^]Y[]W[]TZZU[\UZ[UYZTXZUY\VZ^TW]TX]RV[SUZVV\WW[UVZVV[TVYUVYTUZRVZOVYNVYLWYJVXJUVMWYLTWNSWNRUJOTKRXJPWIPVIPWLRYJQWGPYKPYOR[ORXORYQTZNSZPU]NTYPU[NTYNTYMRXNPWMPXNRYKOXLOXLP[MR]OS]OS[MQ[MPYMPZLPYLOYLOYJMVHNSELSDLSFOXFMWFMWGNYFLYKO[LO\KP[FNXIQ[KR\JP[NP]LO[LO\KO\JR[KR\KR\JR[JS\HRZKT]MV_JR]LR^KP]MR_LR^MS_MS_NT`KV_OYcMV`NVaMU`NUaNUaPWbPX`NW_MV_QWaQXbNT_RVbW[eWZdUXbWYbZ]f[^g\_hY\eY]eZ^b[^e^ag\^f]`f]`g]`g\`h\ai_diegkffjljlojjrmlvojyse~wl{siwkzkyi|khg}gjjmklimijijkjjmknqrr}w~uv{vyw{~sqvqjophkqijooptstx{||}zunktwtuzy~wisxentckvchtcjsdkuflxekwekwdjwekyflyciuekwdmveowblt_iqdivciubiudjvbju`fp^bl]am`dp^co\`l\`lY]i\`lY_kZamY^kZ_iZ_i[^j[]i^agbcgfgijjjnmjnnklkjikiilkejkdgibfldgpdgq_doX\gSXdLTaJS`KS`IPaIP`IP`HN_IP`HObFM_GN`FM_GN`GPaFObEQcBN`AL^@J]BJ]CJ\CJ[AIZ@FY>GZ=EX?GZ>FY@H[?HZ@I[BH[AH[CJ]DK^EK^EL_GNaHNbGN`FO`FNbFNaIPcHOcGLcHNdIOdHPcHPcJReIQdJQfJTiHShISgKRhJRhKRhMTjLTjMTlOTpLUoJVoFRjFRjAMfBNg?Ke=Hb>Ic8Ga5C]6F_4D^4C]6F`4D^4E^4B]6E_3C]4D^4D]4B]6C^8B^7B^8C_7B_7Ba8Ca9Db:Ec9Db:Db:Dc9Db9Db8C`:Eb9Db=Hf;Ec;Ed9Ee:Ff;Hho0@q1?p0>p.>n/@m/@o.?n/@n/?m3@o1>m0>m1@n.?m->l->l-?l.>l/@n/@n/Ao/@n.?n0Ap0Ap/Al/@n0Ao0Aq1Cs/An/Bp0Bp-?n.@n/Ap0Bp2Dr2Cq2Bq1Aq2Dq2Bq4Cu2Bs4Cv2Bs2Bs2As0Cs/Ar2Dt1Cs3Fr4Er5Gr4Fr4Dv6Ew7Cu6Dv3Bs2Bt4Ev3Dt4Es4Es4Es3Er4Fs6Fw4Ev4Du3Cu4Du4Dv4Du4Du5Ft6Gu3Et3Ev4Fs3Es4Fs2Dr2Dt1Cs3Ex2Dv3Eu4Fw5Gw4Fv3Eu3Ev3Du4Du6Ev6Fx5Dv6Hu7Hv9Jw:GuKy=Kx=Kx=Iv>KxMy?Mx>Lw=KuMx=LwKx>Ky>Kx>Lz;Lx:Nw:Nx:Nu;Nw;Lx=Ny;Oy;NxKy>Ly;Mw:Lx9Jv8Ix6Ix8Jy7Ix6Hv:Hw7Hv9Jx8It;Lx;Lw7Hs:Kv:Nu;Nv=Ow?Nw>Lv?Nu?Ov=OvEQ}ER|BOyDR|ESzESzES{ERyERtDQtEQwEQwGSyFRxEQyEPzDSwCQvCRtDTtFRuHTvGSvFRtEUvETuGSuFStFSrHSrITtKWxMV{KVyLWwJVvJVvKVwLTsJTsHVtLWtKVtKVtKVtKVtLXvJYuIXqIWrKYsKYvM[xL[wM\yNXvPYzNZ{Q\~MWwIXtFStESv@NtBMu@Nv>Ku>MvLuNsN^?K\?I[@FZ@GZ?FY>FY=EY@H[?GZ@H[@H[?FZAG^CH^BH\BK]CL]DM^BL\DM^FO`GPaEM_FM^GM_HOaGObFM`DL`FNcIPdIQdJReHPcJSeHSeGReHUhHViIUhJUhITgITfJVfKViKVkIUkGSiDPhAMe?Le;Jd9Ga8Fa6D_8F`6D^3C\5E^6F_3C^4D^6C_4E^5D^3C]3C]4D^4B]4B]4B]4A[:D`7B^7C_8B]8A_9Aa:Ec5Da7Fa9Ec9Dd:Ed9De:Ec9Dc8Ee9Ff9Ff8Gg8Gh9Hi8Gh7Gh8Ei9Gj8Gi8Ej7Ek7Fj:Kn9Hm9Gk9Gl7Ej7Ej9Gi8Fj8Ei9Gi8Fi8Fi8Gj:Hn9Gl9Fl9Hl8Fl8Fl8Fi:Hj:Hl:Hl:Hl:Hl:Ik:Ij:Hk9GjIp;Gmn1?n/?o/?m->k0Al->i.?j->j/?k->m.@m.@n,?m.@n.?m0Ao->l,>l0Ao/@m4Am3An0Bn0Bm2Dp1@n0Ao2Cq2Co3Et3Es3Dr5Fs3Ep/Bn1Cr2Ds4Fv4Ft4Er3Dq4Es5Ft6Gv5Ft2Cq4Es6Gv5Fs3Dq3Es2Du3Eu3Et5Gx3Iw3Ft7Hv6Fu6Gu5Cr8Fu8Fu7Fw6Ev8Ft6Hu7Hv6Ev7Gx7Fx9Fv9Hv5Gu4Et5Et4Fs5Et4Ft4Gq2Fq5Gv5Gu5Gu4Ft4Fw3Eu3Eu6Hw5Gu4Ft3Es6Hx5Gv5Ft4Es4Cr5Ew7Gx6Hu6Hv8Jw6Gv8Gy9I{:J{Ly=Kx>Ly>Ly=Lx>Lw=Mx:Lx;MwLx?Lx=Kv>Mx9Jv;Lw9Kw:Lw:Jw:Jv8Jv7Ju8It7It8Ju:Kx:Lx;Lx>Ov?Nu?Mu>Ns>Nr@Pt>NrAQtCQyCQvDRwDRxDRwESxDRxESxERxERxFRxFRxGSyDSxERuCRtCRsCRtDStFUvFVwFUvETuFUvETuFUvEUvGVwHUwKSwFRqLWxLWvLWvKUuLUvMUtKVuKWtMXvLXvMXvKVtLWuMXvMXuMXvPZyNYxKYwLZwLZwL[xNXwMYxP[yPWvOWvOYyP[{MW{GTvFTwDQt@Oq@Ns@Nt@Nt>Lt?Mv@Mw?Lw=Nv;Lu:Lw8JuMt>Mt?Or>Op?Po;Om=PnAQtBQr?Op@NnDSpFTqFToESnGTkNZq]ezptvzw{rumoglbk}dk~gmek~gnhognhjek~ch{afyaizgk~gmfi~ek{ej}\cvV\sW^uV]yV]zT[xU[{PXwOWzNXxOXyQZxPYyNWvPYxPYxPYwNXtOXuR[xU[xU\yY]|[_~Ya~[cU_|PZyS^~T^~P]}P]O\~O\~MZ}MZ{NZ|MZ|MZ|NW|OY}LWzNX|LV{KUwLWyJWyIVxKXzKXzMVyNX{NX{MVyMWvNYuNYxMYxNYxMXwMYvLWuNWxPXzMXxOYyNYyOYyOYyJXzP[{P[{NYyM[}O\~O\~O\LZ~M]|N]~N]~O_O\~O\~MZ|LY|O[{N]yM\xPZxP\yR]{R[zS\zR]{R]zR]zS\}O]~O\|R_P\R]}R]}S]}R]}S_R`S_T`SaWcXh\m_oaq_oaqbrcsgucretcsdpamcrbqetbqdsetgvhwftetcretdsapbqapbqcretfuhwjykzl{l{l{jyhwhwhwixjyjym}n}m|n}o~qqqo~spo~l{l{m|ixkzhwhxiuhuerhuiulxl|n}n}n}pm|oo~qo~pn}n}o~r~p|o~po~rrppqppqn~n}om|m|o~m|n}kzlzlzkylzkykyjxkylzkyk{kykzixkzm{kznxnxnymxnyoyl{mzlzpyozoznyp{p{ozpznznzozp{nym|lzlzm{n{lzoynyp{nyoyl{lzn{n{n|lzn|o|r|qzo~o}n~m~n~n~orqp~quvvssttvxxwxxxzyyzwvyx}|˜–•—ÖƕƗÕØėŗĘŖÙÚƜÜĞġŢĠâ¢ä²´¿ǿǻĿſɿþĽĸǺɷʶƵŸöƸǷ͵ôòưȯȮǮƯɬȬʬ_cc\`^Z_[\a^]b^[`\]_\]_\Z\Y]_\]a^X\[X[Z[]\\^]^`__a`^`\]_\]`Z[a[Z_Y[`YZ^X]^Z\]]Y[]Y]]UZXTXXW[ZVZYSWUWYZZZ\VWXWXZVW\TUYXY[VWYSTWSTYTUYTUYUVZWWYTWXQWWSW[RWZPTWRW[RW[QUZQUYMVYPW[QUZOTXOVYNWYNVZLRYKRYLSYJQWLQYOR\PT\PR\PS\OR[NQ[TW_SV\PSZORYQTYORXQW]QU[QW]PV[SY`UWaUWbSXaMV[OTZNSYPU[NTZOTZNSYNSZJRZKR[KQZNQZKOYLQ\LP\PQ]NQ]JOYOS\PT]PU]LT]LT^MT^OTaMS_LT]MW`NW_OYbNUaRZeP[cPYbOXaOYaQYcSXePVcQWdQYdPXdQWcSZdSYcQXbPWaQXbQYeRYcU\fW^gU\eZ]g\aj\`i^ci[`h^bl`dn]aj^ak_bi]`h\aj]bk^bl]ai]_f]`f^af^bf`eibgl`elhkplmqlmnqnmsqjwwj~ymwgzizh|h~gilljihiiklnpqruoooqsttsrpoqwxxxux{|uwxtvut{zrsssvxvzujrmhmrfjxdkwdkucisbhvekzagsekwciubhw`ftbgraeqafragscgs`dp_co]al`do^bl]alZ_jY[fY[gY[gXZfXZgXZgY\hTZfX[e[]f_^j\^j_^fbbhlfk{sn|n~i}i}h|g~j}r}~uzytrrqmmqijtfisdgn^`hW]hW]hSZfPVbOVdLScIQ`IO`GN`GN`HOaJQbGN`EL^IRdAK^?L];I[;EXj->j->k.?n0Ao-?m-?m1Cq/Ao0Ao0Ao0Ao1Bp2?o4Bo5Cp4Dp1Co3Cp6Et3Es4Eq6Gs7Hv5Ft6Gu7Hv5Fr4Er4Gu4Fu5Gu3Et2Cq4Es5Ft6Gu6Gu7Hv6Gu7Hv7Hv7Hv4Es5Ft3Dr2Eu3Eu5Gw3Fv4Hx7Hv5Ft5Gu7Ds8Fu7Ew8Gx6Ew8Eu9Hw7Hw5Ev6Fv9Iw:Gw8Ft7Hv6Gt5Ft5Ft4Ft5Ft4Gr2Gs4Ft6Hv4Ft4Gt3Ft5Gx4Fv5Gw3Es4Ft5Gx5Gw6Gw6Fv6Gs8Fu7Hy8Hy7Hu7Hv9Jx9Jx6Gx;Iz9J{=JzLy>Ly=Kx>Ky>Mx=Lw?Ny>Nx>Mu>Mt;JrMwMx>Mx:Lx;Lx=NzMx@Mz@P{?Ny=Lw;Mx9Kv=Kx;IvMyMt>Nu?Or>Nr?OsAQuBRvAOtBPuCQvBPuCQvDRwESxESwCNuFRyEQxEQxFRxERuDRuDRuBQsDTtDTtDTuCRsETuFUvEUuETuFUvFUvFUvHUwGTuITsKVwLWwLXxJUuKStMVtJVtKVtKVtLWuLWuLWuLWuMXvOZxP[xOZuNYuKZuM\wKZtPZvOZvPZwS]zQZwOXuPYyR[yNYzJWyHUwAPs@Nq@NqAOq@Nq?Mr@NsANv?Mu?Nu?MwMs=Lr;Nq>Mr>Oq@NpAPqAPoBTqCTsEUrFUsDRnJVpR^w]fXc{S]sXbxemryuzwzvyorjlhmdjembi|bi|gnahzdicgzdi}`ey]bt\atdi}mrinnsms[btW\sT\tU[vV]yV]zU[|RXwOXwPYzOXzOXvOXxOXwPYxNWvNWvPYxOXuQ[xSZvU\xVZxSZwV]zV]zQZwOZyP[{Q[{MZ}O\~N[}N[}N[}LY{MZ|LY{MZ}QY~PY~LU}LVyKUzMUxHUwIVxHUwIWxLUyJTwKUxKUxLUyKZxKUuJTxLVxMVzLVxMWwLVvHTsMXwJTtLWwNYyNYyMXxOZzNYyOZzNYxMZ}MZ|MZ}P]Q^N^~L[|O^O[~MZ|P]N[}O\~KXxIXxOZwP[yOYxOZxPYxPYwMXvOZxR\}O\|O\|P\|P]P]R\|Q\|P[{Q\|R]}O]}O\}O[}Q_{RaUcUeZj\lZjZj\l\mbqap`oap_l^l_nap_n`oapbqcrdsgvfufuetcrapcrapcrbqdsetfufuixkzjyjygvixfuhwixixjyjyjykzl{n}n}qpqrpn}o~n}kzl{kzjyixfuivfthrguhtkwl|l{l{n}m|n}m|n}m|n}n}qpo{o{n~pm|o~n}m|m|n}o~pm|ppo~o~m|n}m|lzn|lzkylzlzjxkykykym{mzk}l|kzkzkxm{k{nymxnymxmxnyozozk{oynylwozmxnynyoznynymxoznxl|kzkylzkzl{pyoynzmxozl{m{lzn|lzn|n|lzr|m{n|n|lzn|o|n}m|pp~qrqrssrrsutvxswxzxwwsswwvzz{|Ĕĕ––×ėėŗĚÛ Ģƣţ¡¤¦§ǿƾƿľſ¾û·ƷǶǶǷƸǶŶǶ͵IJòưɬŭŮȭǬɫɫ`e`^c_]b^[`Y[aZ`d_`b_^`]]_\\^[\^\Y^]Y\[\^][]\\^]\^]Z\Z[][^`\]aZZ`Z[`\Y`[X][X\[X[]UYZVZXUYYUYZX]^V\\V[\WZ[UXYSTVSTVTUXUVWVYWWXXSTVRSVRSXUVYVWYXY[TXZRWZSX[OUXRW[VX^UW\SWZQVYRVYPUXOTXNSXMSWMUYMTZMUXOW\MSZOTZPV\QV\SV]SV_UW`SV]QT\TW^RU]RU[RUZSW\SY`TY`TY_UX`TW^SV^UX`SV\RX^RW]RV]RU]QU\PTZMSYOTZMQXKPUNSZOS]NR[PS\NQZQT^RT`QT\PS\OT]OS\QT]QV`MT^OU`OV`OWaRZdRYcPYbOXaT]fSZdT[eRYdRYcSZdRXcRYcU\gR\eQZcRXcRYcSZdT[eS[dT[eV]gX_hZ`h]bgY^d]ag_bi^ah_bi\_f_biadk_bj]`h`cibek_cj_cj_bi\`f]`g_bi_bgadi`dhcfmgiqkkppnstqrwrqyvp~{nznxi|i~ikj|gzi}gkllolmprvvttsssusuwxwurrwtqtu|w~|r~{tuvpuwrswwwz|wtvnmpqkovkoxhpxglxdjxekxagsekwbht_eq^eq`gs]`l^bn\am\`l\`l^bnX\h[_kX\iZ]j[_jY]iY\hW[gTYeUYdWXdX\gW]jZ]hY\eZ]g]_ibahjfltpl|oldhoskns{|qwvrpqsnotghqael\`hWYfUVcVZeSVcQTcMRaJQbGM^FM^GN^FM]EL_IPbEL^GN`CL^>K[=HX>IYAIZAHZ?IY?JZ?IZ?HY@H\BJ]CK^BI]BJ]CJ]BJ\CL]CM]EO_EO`CL_EL`DL_CJ]CJ]EL_EL_DJ]DL_GObFNaGPdEQdGRdITfIUgKVhJViHViJWjHUhIUiKUjKVhKVhGRfDRdDQfBNg@Ld=Ib:F`5D^6D^5C^3A\6D^8F`6E^5E_4D^4E^6E_4D^4D^5E_4D^3C]3B\4A]5C^3A\4B]6D_6Da6C`7A_7B`6Aa7Cd5Bc7Dc9De7Cf7Df7Ce6Bb6Bc5Cd5Ef7Ff8Hi8Eh6Eg7Eh5Df5Cf9Gj8Fk7Ej7Ej5Cg7Ej6Dk8Di6Ei6Di7Ej6Di7Ej8Ej9Dk6Ek6Ek7Dl8Fl8Ej8Ek7Ej7Eh7Ej7Ej8Fj8Fh7Eh8Fi8Fk8Fj8Dh8Ek:Hm:Hl8Fk8Fk9Gm8Fk:Hm:In8Fk9Gl9Gm:Jn8Hl9Im7Gn9Il7Ej9Gl9Gl8Fm7El8Fm7El8El5Dm:It7Fq7Fq7Fq7Fq6Ep5Do5Do4Cm4Bo5Cp5Cp5Co3Bo2@m1Bo2Cr2Cp0Bp1Ao4Bp2Cr0Ao0Ap1Bn/@l0Am0Bn0Cn/@o/@o/Ao0Bq0Bq2Cr5Fu4Et4Ep7Fr4Bo7Dq6Cp5Dp6Dr5Fr6Cs5Fq6Gs7Ht7Ht6Gv6Gu7Hv6Gs7Hu7Hv5Gu4Ft4Ft4Es4Es4Es6Gu6Gu6Gu5Ft6Gu6Gu7Hv7Hv3Dr3Dr3Du2Fu5Hw3Eu4Ft6Gu5Fs9Gw9Gy9Gy8Fw9Gx7Ew7Et9Gv6Hw6Gx6Hu8Hv:Gw:Hx7Iw8Iw6Gu7Hv6Hv5Iv6Hr6It6Fu7Iw5Ft8Iw5Ft5Fu5Fu7Hv5Gu5Gv5Gw4Fv5Gv5Dv5Fv6Fu6Fx8Hx9Jw9Jx9Jx:Kz:HzLyLy=Kx?Ly@Oz?Ny=Lv>Mv=Lu=Lt=Ms>Nr=Lt:Iq>Mu?Lw=Lx=Lx;Lx;Ly;Lx;LyQy@Mw@Lx?Oy?Nx@Ox>NzAP{?L{;Iv;Iv;IyLy=KwMu@PsAPu@PtBRvAQtDQwCQvCQvCQvDRwESxDRwFTxDQuDQtCPsCPtDRuERuESuCSsFRuFSvFSuHUwGTuFTvGTvDRtFVwFUvFUvETuHUwGTuJSvKVuKVwKVvMWwOWyMUtLWuKVtKVtLWuMXvNYwMXvMXwNYvP[xNZuOZwNYvPZwMXuPZxR[xT[xSZwT[vU\yU\ySZwPZyOZzLXxGUvDQtERuCQsCPrBRsCQuCPt@Nt>Os?Ou?Nu>Nv?Ov?Ou>Ot?OuMp@Oq@QrCRqCPpDRnFRqIVsFUrIWsJZpKWkLXlYbugov}mtci|msv|w|{}uxrvrsjogmekch~ai{bj|ai~`h}`h}dhbi{_gy^ex\au]bvbg{\awch|ek[axX^tX^vV]wV^zV]yW^zRXzNWwOXwLUtOXwNWvMVuLVuNWvNWvNWvPYxQYzPVvSYyUYwRYvS[wMWsPYxP[yNYzNYyKX{LYzJWyJWyN[}LY{N[}MZ{KY{PX}NY{KT}MXzKUyIUxIVwIVwHVxKTwLVyMWzKUxISvISvHVxISvISyKUxJTxISwGUvFTvIRuKUwIQuJUsITtNYyMXxLWxOZzOYyMWwHVxLY{KXzMZ~O\M]}N]~L[|O[~O\~O\~N[}N[}KXwNXyNYzMXuMXuLVtNVxLVvL[vKXyLYyN[{MZzP]~O\O\~O\|OZzOZzP[{P[|S^{N]yO]zO\yM\}Q`UeTdYiYhYi[kXhZh\k[jZjYf\h[j\k_n`oap`ocrbqcrapcrcrbqcrdsbqcretetdshwfufuftfuetcrfugvfugvjyixjyjyjyjym|l{l{n}o~o~n}pppqm|l{l{kzhxivgtishvgujxlzlzk{m|n}o~l{n}po~m|o~n}o|o{m}m|n}m|l{m|n}kzm|n}n}m|n}m|o}m|m|m|lykykykylzjxm{iwkyjxjxkxkyj|jykzlym{kzmxnymxlwoznynymxmymxnymxlwnynymxlwnymxmxnymxnxnzoxlzkykym{jxpzoynxkzm{lzm{m{n|lzn|qym{m{lzl{jzoyo|o|r}m|rp~n|p~qrrsqtttsstttxtusrtuuvwxz}~’Õ•–×ĕؗœęÛŠĢ¢¥å¨¬ĿľýſŽÿƻĻŶƸźƸƷǷƶǵŵķdzŰƯˬǭƬƬŬɬˬ_da]b]]b\^c]_d^\b[^a]`a_]_]Y_[Y^[Y]\Y\[\^][]\]_^\^][]]\^[]_\^a^[]Y\]Z[^[W[YVZYUY[UYZTXWUYXUY[UX[TZZSZYRWWSVWRRUSTVSSUSSUUXVXZYUVWSTVTUWWX[XX]YZ]W\]UY]SWZTWZTX[RWZSWZTY\SX\RVYSWZPUXQVZPUYQV[PW\MVYRX\QV[QVYPTYRU]TW^W[aSY_VZ`SV^VY`VY^UX]TW\TX\UX]UX]VY]WY_Y[`VY_WZ`TW]RX^UY_SV\TW\SV[QT[QS[RV\OU[NTZRW\QT[PT[QS[PSZPS\RU]PS\RU^QT^QU^RV_RV_RWaPXbSZdV]gT\cRZbT[cS[cV\fW\fZ_jY]hU[fS[eSZcSZbU\dR[dR[dV]gV]gX_iZalY_l[amZak[ak^ai]ai^agY]c^ah\]eaaj__g__haajcdkadiadidfkdcmcbkbdj`cjbeladibejfindglfgmijolkppostrryvtzwo{o|o~n~lli~g{f~ijloknopopsomppstwuw{yzz{vxww}wy|wux{xz}zvtwtuyur}w{}rw}kryenvclxaftgjubgs`frY`lZ`l\`lY]i\`l]am\`l[_kY]iY]iX\hZ^jX\hW]iWZfX\hY]iX\hX\iVZdUYdW[fY]g]`h_bkcemhflpkl|spokhl{rh}j|ykxxqtttqrvklrcci[\eW[dSVaWYeRVbOR`LO_JQ_FL[GO\EM[DK[EL]EL\GN^GN^FN^FO_DM\DJ\CJ\AIZ@IZ@J[>K[AJ\CM_BM]AK\CM]CK]DK^CJ]EL_CK]CJ\DK]BI[AK[CL]BH\CI\DK^DK^EK^FNaGObIPcGPbHTfHSeGSeHTeHTfJUiIThJXkIViJXjJWhITfHTfFSfDRdAMa=Ic=Ia:F_8D_6B[3A[7F`8Fa7E_5C]4C\4B\5D_3A\3@\1@Z1A[2B\2B\2B\2B\4A\4B]5C_6Db4B_6Da5Ca3@a4Aa6Cc9Ee8Cb7Bc8Be8Be5Bd5Cd4Ce4Cd3Bc4Be5Dg7Ei7Ek7Ej7Ei4Bg6Di8Fj6Di5Ch8Fk6Dl6Dk6Dk8Ci8Cj5Ch6Dj5Dk8Fk6Dk7El6Bm7Do8En7Fl6Fl7Gj7Gj6Fk6Dk8Ej7Ej7Ej9Gl9Gl8Fk9Fk7Ch9Ek7Fk8Fm8Gl7El8Fm9Hn8Fm:Hm7Ej7Fi7Gj8Il6Gk7Fl6Fl8Gm9Fm9Gk8Fk8Fn8Fm6Dk8Fl8Fm6Eo9Hp:Ir:It7Fq7Fr8Gr7Fq6Ep6Ep5Do5Bp5Cp7Er7Dq5Co3Dp2Cq2Dr4Es4Es3Dr4Ft4Es1Bn1Bn/Am/Bm0Cm1Do1Co1Ep2Ep3Fq2Ep4Gq4Fq5Er7Er7Er7Dq6Bo7Dq8Er7Er8Fs6Dr7Er7It6Gs5Fs6Hv6Gt7Et5Gr6Gs7Hv7Hv7Hv7Hv5Ft4Es5Ft4Es8Iw4Es4Es4Es5Ft7Hv5Ft5Ft3Et4Cu4Eu7Gw4Ft4Gu6Gu8Gv9Gw8Fx8Fw7Ev9Gx7Ew7Et8Fu8Fu8Jx7Hv8Ix:Hw9Gu7Hv8Iv8Iw6Gu6Gu5Gu5Gt4Gr7Hv5Ft8Iw6Gu5Ft7Hu5Ft5Ft3Es4Ft3Ev5Gw4Gv5Gw6Ew6Fw7Gx6Fx9Jx6Gu:Ky8HwMx=Lw?NyLw=Lw>MxLz>Ly>My=Lw>Mw>Nv?Mw?Lt?Mt@Ns;Jn=Kp?Mr>Lp>Lr?Lv=Mv=Mx;MyLv=Lv>Mx>Mx:Kw>O{>Lv=Lu=Px:Mt;NtMt=Ls>Lt?NtPv?Nu?MuAPv?Nw>Mt@OvAPy?Nw>My>My?Mz?Lz>LyAN|?Lx@Lx?Ku?MwANyAQw@Nv@QtAQu?Ps@QuARu@QuDSwDQwDRwBPvESyESvCQtERuERtDQuCPsDQtCPtDQsFStERuERwDQtFSuERtFSuFSuGTvGTvGTvFUvEUuHUwIVxHVxKTwNX{KWvLWwNYyKWvMWuMXvMXvMXvMWvMXvNYwMXuNYtOZvO[vRZxO\uQ]wQ]wR_yU_zU[wUZwTYvRZvSZvSZwU\yS[yQ[{Q[zNZyOYzMXyLWvFTtHUsGWwEUvCRtBQtBPtBPr@Ns?Mr?Mp>LoAPs?Mo=Mn?On@OoBQpDRrFUtCRoGWrHWoOYo[at[at\dwYcwR_rN[lNZj[dtmt{z|tuqwsxsuy|wysuioiohnhlclah}bi~ahbibi_f{_g|ag}[cu[`t\aw[_uY^tX\uX\vX^uV\sX`vU\wW^yY^{W^{T\xNWuNWvMVtMVuOXvNWtPUvLVuNWvNWvNXwPYxPUvRWxS[vRXuSXyOXwRZyP\|O[zMXwHUxJWyKXzJWyLY{MZ~JWzJWzIV}NU}MV~MV~LVzMV{ESvGTwHUwGUvLUxJUxISvJTwKTwKSwJTwISvISvJTyJTxISvHVwFSuFSuFSuGSvGTvJUuKVvLWxMXuMXxMWwIVuHUwIVxJWyKXzLY}LY{KXzMY|M[|N[}O\~N[}LY{OW{OYxNYyNYzLWwLWvKWvMXxIWvIVvKXxLYyN[{N[zLY|N[}N[{PZzNYyNYyNYwR]{O_{O]{O^{RaRaQ`SbTdVgWhXgXgVeXgXgXgYhVeWfWfXgZi\k^m]lap`o`o_n`o_n_n_n`oapcretgvetgvgugwetdrgveueueufuhwjyixixm|kzkzn}m|kzn}l{l{o~l{n}n}o~m|kzl{kzkxhwkugvgvhvixiwjxjxn|m|n}m|n}o~m|o~n}myo|m}n}l{l{m|l{l{l{m|n}m|m|l{l{i|l{kzjykyjxjxmwjyjxhvjxjxkykyiwm{jykykxhwlzixmxlwmxlwmxnynyozoznxlwoznylwmxmxoyozmxozmxnymxmxmwjzkzkyjxlzlzjykzkylzjxm{lzlzn|n|m|lzm{n|pzoznxpyp}r~p}qzpp~rp~p~rqqp~srspststrrrsrtusuwyyz{~}‘””œŸŸà¿½ÿǿþúùŷĸ¹¹ŹǵöŵĴòįǮʬȮʬȫƩŬ˩_a]]c[^c]^c]]b\Z_Y\aZ]b\\`]Z_[Y_[Y]\Y\[Z\[]_^]_^[]\]_^[]]\^\[]Y\^[\^[Y\XW\XVZZVZ\SWVUXWRVWX\^TXZU[ZSYZQWVRVWUYZUYZRVWUXXTUTVWVXZYWYYVWYWXZZ[_YZ]VY[UZ]TX[TX[TX[RVYTX[TYZPVUUYYTXXPSUTX[QVYSX\OUXQVYSX]OTXSX\SW[UW\UX\UY_VZaTY`TY^TZ]W[`X[`WZ_X[`WZ_X[`Z]bX]_Y\_W[^YZ]X]`TY]VY^WZ_X[`VY^VY_SV^SV]QUYRU[SV^RU\SV]RU\RU\TW_SV`TW_SV]WZ`UY_UZaUYbVZeUYdUYbY\eUYbX[eVZdX\eY\fY[hZ\h[`iY\fX\eT[cY`hW_gV_h\cm\cm[bl\cm\cnZakZal[blY`j]ak^`i_bk[^g[_h^`gcagaagbchbchfglfimchjehmffkdekddlfgoehncfkehmfinehmfglghmjjoonrqqpsrozupvkznnnnqqrusrqnikppopopuuvzywvsuz{zzy{}||yyx{~|zz}z{zwuvvtx}~}uy|ntzelubhsafscgr`eqehs]`kZ^iZ^iZ^jY]iZ^j\`l\`lY]iW[gVZfY]iV[gW]iUZfT[gV[gW[gVZgW[fY]hZ]i]_j^bj`ckefmkhkvon{swtrwwzsjjzjzyqvwupqrklpdeh]_eWZbTW`QV_NR^KP[JP]GMZEL[CLWBJUBJWEMZGP\FN\CKZHP_IR_EM[EM[FM]BHY@JYAKZBKZCIZAIYCIYCK[CJYDL[BIYAHXAIZCJYCJYAHYAH[AI[BJ[CK^CJ\CJ^DK_HNbGOcIPdIQeHQdFRdHSeIPeHObHQcGSeJUiJUiKXiLYiLWiITfFQdDPeANa>J_?Ib;H`:F]8D]7C^5C^6C^4B]4B\5D^4C]3A[3AZ3A[3@[3B]2B\1A[3C]2B\2C\4A]3A\4B]5Ca2@^4B^3A_4Ab4Aa4Ba4@`8Ac8Af7Ae4Ac3Ac3Bc3Cd0Ab4Be4Bd5Cg3Af5Ch5Cg6Dk6Cj5Ch5Ch6Dk6Dl6Dk5Cj5Cj6Ci5Ag5Ch5Ch4Bi6Cl7Dn5Bm7Do7Dn5Dm8Gp6En8Fn7Fm7Fl7Gk6El4Cj6Fi8Fk7Ej7Ej7Em8Fm7Fm7El7Ek8Fn7Do8Eo8En8Dn8El8Fl9Ho7Fm7El7El6El6El7Fo7El9Hn:Hp9Gn7El8Fl6Dk7Cn8Eo6En6En7Fo9Hq7Fq9Hr7Fo6Ep:It7Fq7Gq7Fr6Dq8Er6Dq4Bo2Cp2Dr1Dr4Es2Dq3Dr8Fv6Et1Co3Dp2Bn2Do/Cn1Do3Do4Gr1Do4Gr2Ep3Fq3Fr7Fs8Er7Er8Dq7Dq9Fs8Er8Dq6Dq6Dq7Fr5Gr5Gr7Fs9Gv9Fv:Hx8Gu8Gu5Gu5Fu7Hv5Ft7Hv4Es4Es5Gu3Es4Es4Es4Es4Es3Dr4Ft4Fs3Es4Et5Ew5Fw6Hv5Gu6Gu4Es7Et7Eu9Gy9Gx6Du9Fw:Gv9Hv9Gv:Hx8Iw9Jw9Gs:Iv7Ht9Jx6Gu7Hv6Gu6Gu7Iw4Ft8Iw5Ft5Ft5Fu5Fu6Gu7Hw5Ft4Ft5Gu4Fu5Gw3Eu5Gw6Ev6Ew5Ew5Hu7Iw7Hv8Iw7GuMwMx=Lw=Lw>Mx:It=Lw=Kw;IwKy?Nx?My=Mw=Lw?Ny=LwLr>Lq=Kp=Kp>Lq?Mr?MsAOu@Nu?NwLv?Nw=Mu>Ms>Mt?MtLtAOw@NsAOu?OvAPwAPy>NwAMxANz?LyAN{ANz@MxBOyANv@NvAOvBOwCRyERwCQvCPvBPuDRwBPuARwCTxCSwDRtESvBPsBPsFSwFSuFSuDQsDQsCPsFSuGTvFSsERuFSuHUwDQsFSuERuFSuFStFSsGTtGTwHUwHUwIUwKTxKVvLWwMXxLWwMXxMXvKVtP[yNWvKZvPZyLWuNYwNYuR[xRZwRZxR`xQ^wR^xR\wTZwS[vUYvV[xRYuRYtUXvVZwV]{U\yT\zS[xSZ{Q[yNYuLWsN[vN\yKZyKXyIVwJXzGTvFTvEQtFStCQqDRrERrGUtGTtGVsKYvQ]zQ]yR]zTavYcrbirxy{tw{qx~dkv[eqZbojp}z~~vwstpslnmonrnsnrjnkphnfogo`kalalbmbl^j^h{^ez[`v[`wY_uY^uV]vVZuV\tV]tW_vX`vW^yV]wT\wY`~V]zOXvNWvNWvNWvNWuNWtOTtNXvOXwOXwOXwMVuLVtLVtRWwOXwNWvPYzNYyOZyNX{KTxIXyHUwIVwJWzLY{JWzKX{JVzLT{KT|LVzLVzKUyJX{FSvGTwFSvGTwGTwKRuJUxKUxJTwIPuHTsHRvHRuHRuISwJSvGTvHUuGTvFSuFSuFTuJTsLVwKUuLVvKUuGUuFSsGTwGTvIVxJWyJWyJWyIVxLY{KXzLY{JWyKXzKXzMY|LY|LVvNXxMWwJUuKUuLWwHUuHUuJWwJWwLYyKXxLY|LY{LYyNXxKVvKVvMXuOZxP[zO]zO^zN^}P`~RaQ`RaRaTcUdTcWfVeWfXgUdUdVfWgVdVeWfYhZi[j[j[j]l]l\k^m]l_n_n`o`oap`ocrcrctdscseugwevgxetfuhwfugvhwjym|l|kzn}m|m|jyn}m|m|m|l{l{kzjyjzhulwjujujtjukuhwiwjyjxjym|l|pm|l{o~k{m|n{m}l{m|l{kzkzm|l{jyjyl{l{jyjyjyjyiwiykykyjxhvhvguhvguhviwiwkykykykyhviwjxjxkwlwlwjukvjumxjumxp|myo{myo{o{nzo{nxmwpzoynykvoznymxnyozpyk{jxm{jxkzl{kzlzkykzixk{nxkzn|m{oym{r{oynzo{sp|m{m{m{p~o}p~o}o}p~rp~rrsrttuqo~s~t~qstwtwvzyz|‘””ĝàƴſþ¼¼¼ĸøķŷƵĴõǮǩȫǫǬȨīǨȪab]_c]_d^]b\]b\Z_Y\a[Z_[X]YX]YY^ZW[ZX[ZXZYZ\[\^]Z\[\^]\^][]\Z\[[]ZZ\YY\WW\YV[ZVZYTWVVZXRVWUY[TX\SYZTZZTXXTXXUYYSWXUYZTXYUYWUYWTWVZ[ZXZZWXZVWZXY\XY[WY]TY\VZ]TX[SWZTX[TW\SXZUYZUYZVZ^RVYTY\RW\QVZTZ^NTXTX\UW]WZ_X[`Y[`Y]aS[_T[_X\`Y]aW[_TY]TY]W\_Z]aWZ]X\`Z^aZ^aZ^aZ_aZ^aW\aY\aWY_X[aWZ_Y\bWZaUX^TW[RUZVY_VY`VY`TW^UX`UX_SV]VY`WZaVZ`UZ`W\bV[`X\cW[cW[dX]dW\aY_dZ^e]`f\_f\^h\_i`bl\_h^dl\dl_fn]em[dm[cm\cm\cl\ck]dk[bjY`iZalZ`j]aj`cmadm_bk`cl_`hbchcchcdiefkijokmrlmqjjohinfflbckdgncfmbejadiehmdgldgldfkggmllpppqsrsxtrvnwn~pqtusvuxrtrrrssrutwwxuz{ywrrz|{{{yywxxyu~zwttpuxtrvtwxxxzvlqsekpbjsekubfqhivdfsaep^bm[_jY]iY]i[_kZ^jZ^j[_k[_kY]iZ^iZ^iVZfTZgTZeTZfV\hX\h[_kZ^iY^i[^i^ajbenbenhioliixnkpwvtízrjpvrmzwrtspkljhgifdj`agW[`TW]RU\RV_OS\IP\FOXFMWGNXGNXFMVHN[ELXGMYFLZJP^LS^JQ\GLYGMZCLYENZDMYEMZGM\GN]HO]IQ^HP]HM\EKZDJYFLZGM[FMZDLZEK[CJ[DJZCJ[EL\HN_HOaGOaGPbHQbJSdISeJQeJSfJTdISdLTeLSgNWjJWiJWhKVhITgFQcDOaBM_>J]=I^>Ga;F_9E]8D\7C\5BZ5CZ5DZ5C[7C[5A\5A[2?Z1@Z2@Z2A[0@Z1A[4D^3C]3B\3A\4B]5C]3A\4B^4B_2@]4B_4A_2?`5Ab4Aa4Bd4Ac4Ac4Bc4Dd5De1@b3Ad3Ad4Be3Af4Bg4Bj6Dk3Cj4Ck4Bi4Bi4Bi3Ah3Bi3Ah3Ah4Ai5Ci4Bi4Bj5Dm5Cl5Cl4Dl7Go7Fq8Gs6En5Em3Fm3Fm4El6Ek6Fj5Ei5Dh5Di6Dl6Dk6Dk8Fm7El7Dl7Cm6En6En7Fq8Gp8Fo6Eo6En6En7Fo6En7Fo7Fo5Gn7En7Fo7Ek7Ek8Fm8Eo8Eo7Dn8En6En5Dm5Dm8Gp5Do6Ep8Go6Ep6Ep5Do6Ep6Dr6Dr7Fs7Er5Dp3Dq2Bq0Bp1Bp3Cr6Ds6Dp6Cq5Co5Bp2Co5Er3Dp3Fq4Gr6It4Gr4Gr4Gr3Fq3Fq4Fr4Eq5Cp7Er7Dq9Fs7Dq8Dq7Fs7Dr6Dq6Er6Cp6Er8Fu8Gv7Ew7Et7Es6Dq4Er5Fs7Hv8Iw4Es3Et3Ft2Dr5Gu5Ft4Es5Ft6Gu7Er6Ds6Cr6Ds7Ft6Es8Fu7Eu5Ft7Et7Et8Fu7Et9Gv8Fu7Ev9Dt9Eu9Gv;Ix7Iw9Ix:Gt9Hu7Ht6Gs6Gu7Hv8Iw8Iw8Iw6Gu7Gv7Hw8Iu5Fs6Fr6Fr6Gs6Hs6Ht4Ft5Gu5Gu3Ev4Fw4Gv6Hv5Fu5Fu5Iv6Is6Fs8Hx9Fu;Ix:Hu;IvLy=LwMwLq>Lq>Lq?Mr>Lr@Np@Nt>Ls>Mu;Lu;Nv;Nu;Mv>Kt;LoLu>My=Kx>Mw=Lu>Mv@Ov=Ls=Ls=Ls>Mt?Mt?Nu?Mr?Ms?Mr@Ns?MrBPuAOtBPu@Ns>Ps?NvANv?Ms@NwCPyBPzBOy?MtAOvAOvBPwCQxAOtBPuBPuBPuBPuCQvCQvBPtBSuDTvBSuDSuFTwESvBPsDRuDStDPsFSuDQsFSuDQsFSrERsFSuERtERtFStERrFSsFSsDQqFRsFRsGTsGTwHUxIWyLVuKVvIVvKVvLWwLWvOZwNYwMYwKYvJYuOYwNYwPZyPYvQZwT[xR\wPZuU_zT^yS]xTZvS[wTXuU[xQYtSZvU\xU[xU]zX_|[bX_|U[|X_{V_zT^zU^zU`zR^zT^|S^}R^|S^|P\zR\|R\{OZwOZwP[xS_{R]zQ[xWa}Wa}Xb|]c}goqv{ip~gm{psuxtwrtsvrunngmkohmjnmtjqipiqhoeqbm`l`l_k_lan]g}]d{[cwYbvY`xX_vV^tV[vV[sS\rV^uW^uU\vW^xW^xU\xT[yR[xPYyOXwNWvOXxLUsNStKUtKTsMUuMUtMUuLUvLUvMVtNVvJVvNZyOX|LUyGUwIUxHUyHUyHUyJWzJWzJVyJWzHVxLS|KT|ISwJTxLUyGUxGSvESvFTwGSvGTwGTvJSvGQtISvHRuFRqFRqHOtIQuFQsIRvGUtGTsERuHUwHUwERtGTvERrFSrGUuDQqGTtFSvGTvHUwGTvHUwIVxIVxIVxHUwJWyKXzHUwJWyLY{JWyKXzKXxGTtHTtHUuHUuGSsHUtGTtIVuGTtIVuJXvHUyKY{IVvKUuJVvKVvNYyMXuOZwMYvL[{N[{O[}O^zO^zQ_{N]{P_|SbUdVeUdVeVeVeVeTcTdTcUdUdVeVeWfWfYhZiWfXgYhYh\k\k]l^m_n_p`n_m^l_maocqbscthwduguhwfugwiygwjxkwn{jyjykzl{kzjym|m|m|l{l{jykxhukvgrithsititjukvkujzjxm{lym|m{lzm{m{n|n{m|l{kzkzkzl{kzjyjyl{jyjyjxjygvjyjyhwkxkxjxguhviwftiwguhvjxjxhwhvhvjxiwiwiwmxmwlwmxmxjuitmxnykxmzjvmymynznzo{p|p{p|o{p|o|myo{p{ozmxmymymxmyo|lwnymxnxkumwkvnxnynynzjxp{oznzo{o{q}o{p|p|r}m~n~m|n|n|n|p~pn|qp~quvwwvxtvvvyzxzz|”ĐӒ½¾ſýµĶĴõİűǮǫɫƪǪƩŨ©ǫ^_Z]`[[a[Y^XZ_YZ_Z[`\Y^ZY^ZY^ZX]ZW[[Z\[XZYZ\[\^][]Z\^^Y[ZXZYZ\ZZ\XY[VY[VZ\WY[XX\XU\WV[VUZWY]_UY[TX[UZ\TY\VZ]SW[QUYTW[V[\TXXSWXTXYX[ZVZYXX[\\_XX]YZ\Y[_UZ]X\_W[^VZ]VZ]UZ[V[^W[^W[]SWZV[^SX[TY\UY]SV[TW\TV[SV[UX]WZ_WY^W\`U]`V^aV^aS[^T\_T[^W[`Y]b\`a[^`Y]a\`c[_b\`b\`cY]aZ_bZ]aX\_Z^bZ]bY\cZ]dWZaVY`VYaY\dWZaX[_VY_WZ_WZ_X[bZ]dWYaX[bY^dY^dW\bY^dZ^hZ^fZ_d[^e]`g^ah_bi_bh`ck]ah`ek_cmaen\`i\ck]emZcl]em\bj]dk[bjZaj\bj[ci\dk^djaeladkcckadkcfocfl`dfbfjeglhhmijnklrllrhjodgldfldgncfmdgncflbejdgldglfinfinhlqknqqqvttwwtw|wt~xtzs}rwy}zvzxywwyzyywwuxzvvvxvwwzzvwz}|{|ux{xvuplromssuuz{qmpjfkmdhnafodgsegtegtdhtchq`dl`dn^bn\`m^bn]am]am\`l\`lZ]jY[kVZiSZgTZhSYfRXdV\hX\h[_j[_j[_i_aj`cladmehpilppji}qmx{uopsu{|u{owrnopnjjhjjhnmofej^`dYZ^UW\X[bY^dRV^MQ[LQZKRZIPYGOVKPYJNYJNYMQ]OS^PS_LR\JQ[IPZJP\KQ^KP]KQ\OT_PT`PUaSYeQVbRUbRUbQUbQWdOUbKR^GP[GO\GO[HP]HP]GO]HP\FM[HO_GQ`EN`FO`JSdJSdLUfMUgOVhPXiNWhMUhIUfIUeHTdITeGO`BL^=I[]4Ab4Ad3@b3@b2?a4Ac3Cc2Ab2@c2@c4Bf2@f2@g3Ah5Dk2Aj3Bh4Bi3Bh2@g3Bh4Bi3Ah3Ah4Ai4Ak1@j2Ak2Ak2Ck5Cm4Cl6Em4Cn6Ep6Do6Dp3Dp3Eo1Cj3Dh1Dh2Cg5Dh3Ch4Cj5Dk6Cj4Cj6El6Em6En7Fp8Gr8Fq8Fr6Eq6Ep8Go7Fo5Dm6En6Ep7Fq4Fq6Gq7Eo7Cn7Eo8Eo7Dn7Dn6Co7Dp6Fn7Fo6Eo6Eq6Eq8Gr8Gq7Fq7Fq7Fq5Eo8Fs7Er6Dq6Dq6Cp4Er1Bq2Bp2?n3Bq4@p4An4Bo4Bp5Dp0Bn3Dp3Dp4Dq4Gr2Ep3Fq3Fq2Ep3Fq3Fr3Fq4Dp3Cn7Fq8Dr7Dq7Cp6Er5Dq6Eo7Fp8Gr8Es6Dr7Eu8Dt5As6Cq8Ft6Eq2Dp6Gs5Ft6Gv6Ev3Fs4Ft3Es4Gu4Es4Es6Gu6Ft6Dr4Br4Bq8Fu5Cr7Et6Ds6Ds6Hs8Eu5Cr6Ds7Et7Et5Cr8Fu:Fv9Eu9HvMvMx>Mx=LwLo=Kn=Kq>Lq?Mr>Lq?Mo@Nq@Nt=Ko>NrMs=Lv>Mx?Mz>Ku>Nv?Nw@Ou>Mt>Mt>Nu?Mt>Ls@Nu@Nr@Ns?Mr@Ns>Lq@NrDRwBPu?MrAOtAOtDRwAOtBPtAPuBPvCQwCQuCQuBPvDRwDRwBPuCQvBPuCQvCQvDRwCQvESwBStCSuCSuEUwERuDRuCQtDRuETsCRsGSuFSuERtDQrFSrFSsERsHUuDQqERqERrDRqEQrDQrFTpDRoFRsHUtGTsFSsIUuOYyKYyLWwMXxKVvKVtMXvMWuL[xLWuNYwNZxQZwSZwU\yX[yTZvP[vS]xR\wR\wTYuS[wTXvUYvS\yTZwSZwT\yV]zV]zY`}V]zY]~[`~Y`|Y_zY`zX^xV_zV^|T]{XbY`~Za~Y`}Y`|Z`}Y_{W`yYa{Yb|^d\d]e}_d{]dxlnutt|ptnrprknknlrmrntininhngnkqkqjrlsjriqfqcoambm`l_k_k\h}[f|^e|\czXcyVavU`yW_uU]tX`vV]uUavU_yV\vT[uU\yRYvQZwMVwPYxPYxOXwOXwLUtKTsLTtLXuKUsKVtKUtJVvHSqMXuKUvLVzIVxIUxGTvIVxFTwESvDRuESvFTwHVyIWzIWyMT}KUxHRvLUxHUxHTvFUxCQtDRuCQtCRuCPsDQsHQtGQtFPsHOtHPtHPtFNsHOtGRuHRuETtDRtDQsDQsDQsERtDPrFSuDPrDQpDQpDQtFSuGTvGTvGTvERtHUwHUwFSuGTvHUwFSuGTvIVxIVyIVuJWwHUuIUvFSsGSsGTsFSvFSvGTwGTvFTvHRuIUtIRsGTtKUuJUuKVvKVvKVvNYyPYzKYyN[zO]zQ[yQ\zP]{N\yQ_|Q`}SbSbVeUdTcTcRaSbUdTcSbTcTcUdTcTcTcVeVeXgWfVeWfVeVeWfYf\i[h\k]l^m]l^k`m`n`qcsbqdugwfwhuhvkylzmyjzixhwjyjyhwkzm|l{kzm}lxjwlvlshpgsfrgrgrgrgrjuishviwjxn{n|n|lzm{n|m{n}l{m|kzkzm|l{kzjyjyjxj{j{l|m{lzkykyjzgvjyjykyiwiwhvhvgujxjxlvixiwhvhvguhwhtiwkwmxjvkxmylxmyjvkwo{lxlxnznzmylynzo{p{nzq{kunxowp{n{o{mylxnzlxn{o{ozp|n{nznznzmymynykylzixp|o{r}ozp|q}o{q}q~p|r~p|o|sstsvrwwwuyvuyx}‘ĿĸĽ¾ļĵİŮƮȮʬȩŨ©èŧéǧ]_Z[^YY^XY^WY_XY_YZ`\X]YW\XV\XX]YV[[W\[X[[\^^[]Z[]ZZ\YWYY\^^Z\YXZX[[V[\WZ\WZ\WY[YZ\XUZUUZVUYZUX[VVXWW[VW[UW[SXZTXZQWZTY\TX[TXZTXXV[YUYXVYYX\]X[\YX\YZ_W[_W[^VZ]W[]WZ[TXZV[`UY]X\_UY\UY\V\_SX[TX\XZ`SV[VY^TW\WZ_WZ_VY^X\`T\_T]_X`cT\_V^aW`bU^bV]_\cb\aaZ^`]ad]`d\`e[^c\_c[`c[ac\`b]ad^af[^d[^e[^dZ]dY\cY\c[^fZ]bX[`[^bY\aX[b[^eY]cZ_bZ_dZ`f[`f]bi]ak`clcel`cicfj`chcfi`chadj_ci`ek^bj_cmZ_i[cm[dnZcl]fo]dl[ck\dl^fk^fl^ek_jjahkbfkbegdeidgldhlchkaegeiladiaekfhmijokmrhjocfkfiogjqdgodgofiphksilrhkpipsimrlntknsoqurswtsyuvxxwxzwt{ytzu}u}xxyyywstwusuutvy{yzuxwywvxvvz}w~xwwssvtwqzdwuhsriqrhjnicikchobfqcdoferdfsegsdfqchpcgqbfsbfq_dp]amZ^jX\hY\hX\iTZiSYhSYhSYhQXgSXfX\h[_k]al\`k[^i[^g^akaeobgpjknwtqwnyzttxz}uxe{qfvpnnmlhiehighhjhfkaac__b\\a``e`biW[bTU^RX^PW^SZaTX_PT]OS]PT_OS^QUaPU]NR\NU`KR\LS^NVaOT`TWdVXeY\h[_k\_lW[fVZeVZfU[eW^hU^gT\eQXdPXcQYePYfNVaLU^KT]JT^MUcMVfMVfKUfNXiOXiPXiPWjPWiQXiOVhOViLUgHUeHRbIRcFO`@IZ>FZ;FX9EY:F]:E[7CZ7CY6BX4@U5AW3CX3BZ6BZ4@X6BZ6BZ6CY3BX2AY3C[1B[0AY2B[1?Y4A\5C]2@Z5C]4B]2@[4B\2@[2@[3A^4B`3Aa4@b4Ac2?a3@b2?a0@a2Ac1?b1?b4Be3@g3@h1@h4Cm3Bk3Bk1@i1@i1@i2Aj2Ah0?f3Ai3?j3@j2Aj0@h1Dj0Cj0Ci1Ck4Cl2Bl5Dn1Al2Cn2Cn1Bn1Ck0Be/Ae1Cf1Cf4Dh3Bi4Ci4Dj3Bh3Bh5Dj5Dm5Dn5Ep3Do3Ep6Fp6Fp5Eo5Do6Ep7Fq6Fp7Fq5Fq4Ep8Fp7Cm7Cm7Cm6Cm6Cn7Cp8Dq6Em6En5Dn8Gr7Gr7Er7Gq7Fq8Gr7Fq8Fs8Fs4Bo8Fs6Dq4Eq3Do/Ao1Bp1?n6Br4?o3@m5An6Cp5Cp3Bo2Co3Cp5Er4Hs2Ep4Gr3Fq1Do3Fq1Dn2Fp1En3Do4Do6Cp8Er5Cp5Cp6Dp6Ep7Fq6Fq5Cp7Eq5Ds6Ar5As7Cr7Cs7Eq7Er6Gs4Et7Gu7Gx2Dr3Es3Es3Es6Gu6Gu6Gv4Dr7Dq7Dr7Et7Et6Ds5Cr5Cr5Dr4Fq7Dq6Dq7Er6Cs6Ds7Et7Dt9Dt:Ev9Gv:Hw8Fu8Iu8Er8Fs7Ht4Er7Hw6Gu7Hw7Gv7Gv6Hv5Gu4Gr6It6It7Ju5Hs7Ju4Gr5Hr5Hr6It5Fv1Cq5Gt5Gu4Fs3Is2Hr3Hr7Jt5Hs5Er7Gt7Dr:Ht9Gu6Gs7Ht8Iu7Ht8Iu8Hv7Ku6It4Hr8Kv6Ju8Ht7Ht7Ht;Ju:It;Jt;Iu:Hs:Iu;Kv;Iu=Ky=LxMv>Mu>MwMxNxLo?Lr?Mr>Lq@Nr?Mp@Nq@Np>Lo>Os=Mr=Ks;Jr;JqLsMyKtAMw>Mv=Lv>Mt=Lt@Nu@Nv@Nu?Mu@Nr>Lq@Ns?Mr@NsAOtBPt?MsAOu?MrAOs@NpAOrAOq@NqBPsCQwBPuBPuBPuDRwDSwCQvCQvAOuDRwESxDRwCQvFTxDTuEUwFVxFVxDTvEQtCQuCStDStDStDTtGSuFSvFSuFSrFRrFRrFSrDQqDQqFSsERsDSpDSoESpFTqETqERsERsGTtFStKTuFTsLVwLWwKWvKVsMXvLWuJXuOYwOZyT\{PXtU[xRYuUYwTYuS[vS[wS]xS]xS\xU[wV\xRZuPZwR[xSZwU\yV]zV]zY`}U]{X]}Y^|X^yY_wY_w[`{W\zW]yX`|W^zX_{Za~\`|Z_|\b|Y_yZbwZbwX`vZbw[`w\cwcg{bduwuuv}xtpqopkn}ik|ek|ci|ahzdk~kohnkqhqksjskqkqjsktitiufqamcoam`m^j[f|]e|[f{WawVb{Ub{XawW_uS^tR^sS]sR^sQZsU\vU[xTZwPYvNWwPYxMVuMVuMUtKSrJSrKVtJUsLWuJUsITrHStJUrJTsHVvHVyIVxGTvFStERtDQuESvESuDRuFTwFTwGUxIX}JRzJUxJTxJTvHUvHUvEUwESvESvBPsCRuCPsCQsERsEOrGQtFMsHPtIPxHOxJQyGQwGQuGPtGQrDQsCPrCPrCPrBRrDStBRrBOrDQrCPrDQsERtFSuGTvFSuGTvFTvFTvFSuGTvIVxHUwHUwFSrDQrHUuGVuGVsGWsCSrDTrDQsFSuGTvESuIQtHQuHSqGRrESrHRqHSsIUtOVxKWwLWwLWwLZzJXxMXvMXwLWsQ\yM\zP]|M^uQ_}Q`SbSbUdSbTcUdUdRaRa~TcRa~UcTbUdTcUcTcSbSbSbTcUdTcTdT`VcYfZdYd\g_i[j_m^l_oarbp`pcscsivguhvnxiujyixgvixhwjyhwkzjyjyjzjvlylwluirjrirfrgrhshsgqhrbrgufuhvhvlzlzlzlzm{n~m|n}l|l{m}n}l|l{l{l}l|k{l|n{m{mzm{jylzk{jykzlzkyiwkykxjwkxlvmwixiwiwhvhxlwfugufukwkwlxjvkwnzjvmymylxo{lxnzmynznzmyn{pynxoxpzpypypxn{o{nzo{o{nznzmznzp|o{p|pyq}nzkyn{l{n}n}m}qo~q}r~o{r}s}q~q}r~uq~twsyv{||~|³´±ŭĨ¨Ƭ˪ĩ訦ĥæ^_[[^XY^XY_[[_Z\^Y\]Y[\W[]X[]Y[\Z\][\^[\^\]_\\^\]^X[]W[\Z[][\]Z[\W[]XZ\WY[VXZUX[UV\UV[TW]VTZVVZYV[\TXYSWYVZ^W[_VZ^W\_UY]Y]`X\`W[^VZ\W[]W[\W\\V]]Y]^WZ[VZ[VZ]X\_W\]SZZW[]ZZ_WX\Y[^X\`UY]V[^UZ]W[_X[`VY^UW]X[`WZ`Y[a[]cY[`Z^cZ_fW^cW`cW^bX]aZ_b\ac\``]aa^bc^becfjbbfbch^ae`dg`dh`dh^ae_cg_cf]`d]bd`eh\`b]ac\`c]`e[^c[^dZ^b[^b\_d\_e_bf[`a`chbek`dkejp`flbfn`dm`ck`ckcflbem`dnadn`em`ek\ag^di^cj_blahnael`ej`ekaflchmchkejnejqekqeipacjfiobelfhoeglfhmeindgkhikiknlmqomrkmqhkpknsgjoilpjmrknrmnrlmskorlptnqvnqwnqylovorzqvyrtyttwvxxwxxyywzzy{{|{y}{}y}yzzy~yxz{x{zx|z}{z~z~|{||w~}~}z}|xysptxtwno{dup_yrfxthyxmssinrmjppejpcfregsefrhiuaepaemafkaen_`n`co[\h^`l]al[`lSZfV\gQWcRWcW[fY]iW[gZ^jX\hW[hX\hY]hZ]d^ahbengfqjjq|tt}s|{{zmjon~wbtmirlhqmjpkilkfhgcdeaeffkjhecdcdbaacddfffgdc_dc^geddefacfZ_dY^dY]d^_c]\aUY]W\aSY]PV]OU\PT]QU_SS`XXh[^j[_jZ_gY\eZ\cUYbZ]i\^j\_j[`h\_iY_iV\hSZfQYcSXhRXgPWeRXgS[hV^iU\kV]mT[mSYlRYkQXjOVhOUhNUhKReIPcIRdEN^BKYAIX?HX;DVa2@a/>_1?b2@c1?c4Bg3Cg2Cf0@f3Ah2Ai2Aj2Ak1Ak0@i0?g3Bh0@f2Aj2Aj0?h0@j.?j.>j.@k.?j4@l4@n0?k1?l/Al0Al2Cm0Cj.@g.@f0Bg4Ci3Bi3Ak4Aj4Ak1Aj3Bl5Dl2Aj3Bn2Al4Cn6Dp6Bo6Bo7Co8Eq7Dp6Bo9Eq6Do6Ep8Fq6Ep7Eq6Ep7Fp8Go6Cm6Eq7Fp9Hr7Fn7Fo8Gs8Es8Fr9Hs8Gr6Ep6Cq7Dq6Cq6Cq4Co2Co2Co3Bq3@p3Ap1?m5@p4?o5Bn3Bo5Cp5Cp5Cp2Dp1Do3Fp3Do2Fq5Gs1Do0Cn1Co2Cr0Bo3Dp3Dp2Cp5Gs6Er4Bo5Cp5Cp4Ep3Dq6Dq5Bp4?q6Cs6Aq6Ds7Cs5Dr5Dr4Fs4Gt4Gu5Ft3Es2Cq1Cq4Ft3Ft5Es5Ft3Dr5Ft5Fq5Er6Fu2Bp5Cr5Cr4Bq6Dr5Ds6Et2Er4Er3Cq7Ds7Dt8Dt8Fu8Fu9Gv9Gv:Hw8Gu5Fr5Fr6Gr6Gs6Gs7Ht7Kv5Gs7Iu4Gr4Gs5Hs5Hs5Hr7Jt5Hs5Hr4Gq4Gr6It4Ft2Dr4Ft5Gu5Gu4Fs5Gu5Fu5Fu4Es6Jt6It6Hs5Fs7Hv8Ht9Jv8Iu9Jv9Ju8Iu6Gu6Is6It7Ju7Ju6Jt8Hu9Hu:Is8Iu8Hu9Jv7Hs7Hs8HsKvMv=Ku?KuMu>Kt>Ks>Lq=Kp=Nq=MrMp=Np?Np?Mq@Nq?Mp?LpNo>Nq=Mq;JoLs?Nw=Lu>Kx@MvANw@Nt?Ms>Lq?Mq?Mr@NuAOv@Nr@Ns@Ns>Lq=KpANq@NpAOq@NrBPs@Ns@Nr@NsBPuCNq@NqBPrCQtBPsBPvCQvEQwDSxESyFTvDRuFTvESuESzGTyCTuDTvFVxDTvCSuCTuCRsCRsDStDRtDQsFRuDQqERrFRrFTsDTvDQtFRrESsFUqGQpFRoHSqFRpHSqHSqHRqHRqJTrFVsHTsKUwKVuLWwLTvMUwKVtLXuLWuN\wN\wPZwP\{OYyQZxQZwQ[wQ]wQ\vP\vS`yR^xS[wQ\wU[xTZwSZwRYvSZvTYzW[xW\zY^}W_{W]zW^yX_xX`vX`vW`vXawV]yY`|Y`yYa{ZbxZ_x]`zZ`xZ`x[`v[dy[cx]dwah{bj}ek|xu{}wqp~gmfl~dk~cnbk|`h{dldjfkhnflhniqhqhqlslqmtkviueucsgqdqfo`l^k\h[gXd|Ye}YezUbzU`xV_yU^wS`wS_xR\wU\wS\yT]yPYxNWvMVuLUtNWvKUtJVtKUsITrJTsITrJUsKVtKVtJUrKVsLWwKVvKTwERuCQuBPsBQrDRtDRwCRtETsEUuESwHV{ETwGUwHVxGTwGUxESxCQvESxDRtBPsAOrAPsCRuANpERuCPsENrEOrDOrEOsGOtBOrCQsDQrFNsDRuDPr@MpBOqERsCOqDQsBOqCPrCPrANpERtERtERtDRsHQtGRuHRuEStGTvFSuFSuGTvESpDRoCRqDSrETtETuERtDQsCPrDQsERtGTvERtFRuERtFSvCRsFRuFSsJTuITqHSqJUrLWsMWtLWsLWsLWtNXuNZuM\wL[vMZyM[xR`}Qa}Ra~RaTcRaRa~Ra}Ra~Ra~Pc~Qb~RcSdSbTcVfRb}TbRa~Ra~RaTb~Sa}Sb~Q]Q^~TaS`XbXcYdZeYg[i^o^m_o`qbr`pdrdresesgudugvfufugvhwgvixjyjykzjxkykxhuguhugrhsgrhsfrdsdrgqhrhrdsdrguiziyhxkyl|n|n|n|p~rq~qo~o{o}m{m~n~m~m~om}o~m~o|m{k}m~kziyk|hxk{jynzkykyiykxiwjvnzkwlxmxmykwlxlxiuiuivlwkulvkwmylxlxmymymylxmxmxlzlylznyn{nznzmynznznzo|s|n{o{r}m{s|qzn{n}o~q~q|q|tyup~p}p}qq~rsut~ywr{|vyy|ēĒkvz՟ĬĮ©ťĥħũħçƧǥħƥ§^a[]a[Z`[\aZ\a[\]X]^Y]_Z\^Y\^Y]_\^`]]_]]_]^_\]_Z[_ZZ_YZ^X\`[[`YZ_YZ^X[_Z\]Y[]WZ_XZ_XX]VX]WX_VX\ZX\\Y]^Y]^Z^^Y]^W[\Z^_[_`Z^_Z^`Y^a[_b[_`W[\Y]^Z_`Y]^Z^_[_`X\_X\`X\^X\]W[\\]^[\^Y]^X\]X\]Y^^W^]Y^_Z]`Z_aY]`[_b]`d]ad]ad\`e]`eZ]cX^b]ae[ad]be]cb\bcaehaeibfh`dgaeh`dgadhcdhaef`dfaef`de_cd_cd^bd`ee]c`aed_cdadg`cf`dgadiaej`eg`cibdjadh_cfbfibdk_elbgm_ejaem`dncfocfobelbemcgrcipbhnbhm`fk`elaendhpchnchndiodiochndiochochmcindipchndgnfipehoehndgkfinfjnfikijlmnpmnronrppumqvorwjmrmpujmrmotstyrtxotuosvoswpqvmpuqtymqvqvxtx{uy{vxyyy}|}~|{}}}~|~}}~|{{~y~|~{~{{{{}|}|}y~~zuvuuxzuy{sxztvystwtsr}yoypmijljfe~c}huxrpttzsmqqilsimufhsdfs`cn_ck^ch`ck_am`al_bj[^hZ^iW[gX^jW\iV\hW\iY]iX\hW[gX\hW[gVZfY]h]al]ai]`i`dj`bjkkqzut|yoi{exe~veyqalg\edbfeahgfjidjidlkgiifgffkkjihegfba`^cb_nlftpfxuf{wg{visqggi`imenphrofplemmfiie^_aXZaUYaTZ`PVZTX`UWcZ^f^bk\_g\\b`_e^bfabnaam^bk\`i\`iX]hZ]iW\hV]gTZfV\iV\hX^kZ`mYaoV]lU\lS\mQ[kPYkOViNUhMTgLSfHPcFM`DL^AJ[AJZ>GX=FWEY9AT7@S6AV8BX5BY3CZ4BZ5AY5AY5AY6BZ5B[4C]3A[2A[2AZ3A[3A\2@[2@[5C]5C]5C]6D_3A\4B]3A\3A]2@^3A^2?\5@^1@]2A_1>_3@`2?a3@c1?a1?b3Ad4Bd4Ae4Ei2Bf1Ae2Af3Bj1@h/>h2>i1?g0@f1Bf0Af1?i1@g/>h1@i,?g,=i-?j-=h1>j1?l2?l1>l0Bm1Bm.?j0Ak/Ah/Ai1Bj2@i1?h2Aj3Ak4Bl3Bk1@h1@j4Cn4Cn2Al3Bm1Al4Bn3Bl3Cn4Cn3Bm8Hs6Do7Fq6Ep6Do4Cn6Do5Do5Do4Co5Do6Ep5Do5Cn4Cn2Bm4Dp3Eq5Er6Do5Eo4Ep4Ep4Dp4Dq6Cp5Bo1Co2Cp1Ao2@o2@o1?n5Cr5@q3Am3An4Bo4Bo3An4Cp0Cn2Do3Co2Co3Fq0Cn0Cn1Co1Do0Cn2Bn2Cp3Dp4Eq6Er6Dq7Er6Dq2Co6Dq6Dp6Cq6Ar5Cr4Ap5Br4Bq4Bq4Es5Ft3Es1Cq3Es2Dr1Cq1Cq2Dr2Es2Bp2Cq5Fu4Es5Ft7Hu2Dr3Cr4Bq5Cr7Et5Cr5Cr6Ft2Dr2Dq4Ds7Et5Dr7Cs7Et9Gv9Gv7Et8Eu8Fu6Gs7Fs7Gr5Gr4Eq6Gs7Ju5Is4Gr5Is6It3Fq4Hs4Hs4Hq5Hr5Hr6Is5Hs3Fr4Ft4Ft5Gu6Hv5Fu7Hv6Gu5Hv5Gu3Fs8Ku6It5Gs6Gt7Hw7Hv7Ht8Jv8Iu8Iu7Hu9Iu9Lw7Ju8Kv6It7Ju8Ht8It8Ju9Jv7Ht7Iu9Ju7Hs8HtMt;Jq?Mu?LtLs>Lr=Lp?Mr>LrNp?Oq@Nq@Nq@Nq>Lo>Ln?Nn=Kp=Mr=Mq?Ou=Mp;Ko:Jn=MpMt>LuJu?Lw?Mt@Nu@Nr=Kp@Ns@Nu?Mp>Lq>Lq>Lq?Ms?MqBPr?Oo?Lp?Mp?Mp=Kn?MsAOt@Ns@MpBPsBPs@NqCQsCQtDRuCRtDRxESuDRuDRuDRuBPsCPtESxDTvEUwEUwBRtBSuCPsCRsCRsCRsDStFRuERrFSsERrFSrFSsFSvERrERrEQrHRpGRpGRpGRpGRpFRpGRpGSpGSqGRpIUsGVrKUvMXwLVwITtJVsKVtJUsMWvLZuM\wPYvOZvOZvQYvO[vOZuQ]wQ]wR^xO\vQ[vQ[vSZuQXtSYvW]zRXuRXySYyV\|T\wX\zV]zW^yV]xW^wT[uX`vX`vW^uY`|Za}[b{[c|[cyX^v\_x\az\bx[aw_e{\cx`gybi|ci}ej|vv{vtkm{ilek~cm~ao~am}_j|cjfkejflgmfnksltmvjrosltlwkwgwfugrfrfrcobn_k^j[gYe}Zf~Vg}U`wT^wT_wS_wR_xR[vU[xPYvR[wOXwMVuLUtLUtLUtJSrIUsGRpJUsITrGRpITrITrITrHSqKVsLWxKUuHRvCPrAOsBOr@OpANrAOuBPrCRrDStDRuESyESvDRuDRuFTwDRuDRyCQvESxBPsBPs@Nq?Mp@NqBOrCPsAOrFNqCMoCMrENsAOqANpBOrANqCOrCPsCPtDQrBOqCPrGTvDQtDQrBOqANp@MnANoDQrCPrERsDMpGPtESuERtEUuDTuCRsEPsBPnCOoAPoDSqCRsBRsDQsDQtCOqFSuFRuDQsERtFSuFSuFRtCStFRsDQqHSrFQoHSqJUsITpITpITpLWrMXsLWrLWrK[tM[uQZxKZvM[xQ_|O_{P^~P_P_O_zN]zP_|SbQa~TcReQb~TbQa}Rc{Rb|RaO^{P`}Qa}TbUcSa~O^{Q^T^~T_VaVa~XcZdWfVd~WeYg]kZi\k]lcqcqftbpesdqdqcpdrguesfugugvgvixiwiwiwgugufucrjthsgritgvdsgrfqgrcrfuguhviwjxkzl{n|n|o}n|p~p~rrrqqon~oooonppq}np~n}l{m{k|l|k{mzn|n|n|kylzmynzlxlxmylxmykwmyo{lxlxmykwlxmykwjvnzlxnzmylxq}n{myiznyo{nzo{mynznzmynzo{nyp|o{p|p}q{r}p|o{q|q}s}r}tp}qtrsqtrwy|v~uytyptv{~pu}]oaasgtx{~zz֓Ǿýú®®ç¤ǠƤȦǤŦǨŦťĦŦħ`cZ`c\^b[\b\^b]_a\`b]`b]_a\`b^`b`^`]]_\^`[^_[\_ZZ`Z[`Z[`Z\a[Z_Y[`Z[`Z\a\\a]\a]Z`\[`\Z_\]b^Z^[Z`\Z]]Z^]Z__Z^`[`aY]]Y^]Z__Y]_Z_`\_a]]`]]_[]^]`^\_^Z^^[_`Z^_Y]_Y]`X\_Y]^XZ\\^_[_`Z^_W[\W[\[`aZ``[aaY]`Y^a\ad`eg\`a\ab`deaeh_cf^be_dh_dg^ec^ec_ed`fgbfjbfibfjcgkbghcgiaegcfgcggaeeaddbeecggaeebfecggbfedhgcffffjefigglehldhk_bhbelcfmbfjcgjbfi`eh`eibglchobhnbfmdgphkqehoehoehocfmcfldgnfhpeiphkufjrekpdiodjpdiochnbhmbhnbgldglfjnehngjrgjqehmfimfinilqjnqknqpproproputuypswlouoqwoqwmqvkotprwstzqswqtvttwuuyuuzqswsw{psyru{tx{vz}uy}z}}}}|}~}~~}~~}~|~}~|{~{~|zz~{sw|pt}nq|nrzkpujnsopt{nlk~fkkklrvxwtyyw{}vprsgipehpcfpcepdfnacj_ae_^f]^h\`i[_i_doY`i\cmW]gY`jX^hW[fW[gX\iX\hY]iX\gW[fX\gX]h\^i]`g\_fcdjjhjvpmypjukgphdphcrkgrliokikffddabc^eeccbbgfbkkepmgmjfihdjhfmjepjdkg_ie[ok]yschnjzcyu_vt_zwb}wbijysdmiagfe]_dUX_SY_TX]UY^Z`g^cfddfcdelhlnkqhhocdk[_eZ_fZ]hZ\iY[hZ^hX]iX^jW^jZalZ`mY_mW_lU\mSZkQYkOWjLTgJSfJQdKReHObDJ]?GZAHZ>FW>GX=FW=FV;BU;BT:BT7?S9AT8@U6CX7CZ7BZ8E]8D\5AY5AY6BZ6B[3A[3A[5C]1BZ2B[2B\3A\2@[2@[2@Z1?Y4B[5C]4B^1?Z3A\3A[3A\1?Z6?_6A^5B_1@_1?_1?^3@`3@b1?b3Ad3Ad3Ac2Bd3Cg2Bg1Af1@e2@f2@h0>f2@g1@d1@d0Ad1Ae0@d0>g/?g-?g,>g,=i,=i.j-?k.?k/@l/@k.?j.?j0Cj.@h/Ai1Bj3Aj2Aj2Aj2Al1@l3Bm3Bm1@k0?j2Al1@k2Al3Bm2Al3Bm1@k5Do2Al5Eo2Co2Dn2Cn4Ep5Cn5Cn4Cn4Cn4Cn4Cn4Cn2Do2Bm3Do2Co3Dp4Dq3Do2Co2Bo3Dp1Bn2Co2Bn1Bn0An/?m3?o2@o3Ap4Bq4Bp5Cp4Bo3An4An4An2@m2@m3An2Dp2Dp1Eo0Cn0Cn1Do3Fq1Dp2Co4Eq4Eq5Fr5Fr6Dq6Dq4Co2Do6Dq6Dp7Dq5Cr5Bq2Cq3Cq5Bq3Aq3Dq3Dr2Dr2Dr1Bp0Bp2Dr1Cq1Cq2Cq2Cq4Es3Ds3Dr4Es3Dr4Ft5Es5Br6Ds4Bq5Cr5Cr5Cr5Bq6Dr6Ds7Dt6Br7Br6Es7Et7Et8Fu7Et7Et4Gq5Fr7Ep6Dq7Fr6Hs6Hs5Fr6Gs5Fr3Gq6Hs4Eq4Fr5Fr3Gp3Fp4Gq4Gs5Hs4Fr5Gv6Hw6Gu9Ju8Iu4Et4Ft5Hv6Is4Gq5Hs9Lw6Gs8Iw7Hw9Iu:Hu9It7It8It8Is7Ju7Ju8Kv4Gr6It6Hr6Gr8It8It6Gr7Ht9Ju7Hs8HsLs;Iq;IpNr?Lr?Ms?Mr>Lq?MpLp?Lp>Lo>Ln=Mm=Lm=Lo;Lq=Lt=Ls=LsKs>Ls>Ls=Kr>Lq@Ns=Kp@NsAOtAOs@Ns?Mr=Kn?Mp?Mo?MqANrAOrAOr>LoAOr>Lo>Lp?Mq?MpAOrBPsCQtBPsAOrCQuCQtCQtCQtBPsDRuESvESvCQuCQuDRuCTvDTvDTvDSuBOsCRsETuDStDTuERuCQpERrFSsFRsFSsESsERrFStHSpHRpHSqHSqHRqGSqIQpGPoHRpIRqHTrJUsJUtLVwJUvJUsKVtKVtJUsNYwJXsJXrN\vPZwPZwQ\xPZwP\vO\uNZtO[uS_yR\vPZvQWsQXtRYtTZxR\yQZyPZyQ[yUZ{SZzUZ{U\yV]zT[wT[xV]vV]vW^xX^xY`zZazZa{[b}XawZ_x[by]bx[ax`f|`f|_e{_f|agybgzafzmo}}zxswur~kk|il}hj~ci{aj{aj{`n}_k}ckdlfngogoowownvnvltnrmumykxmxfvhsfrhsfsdpam_k^j`l]iYhYe~Wa|T_zR_xOZvPWuTZwR[xQZwNWvLUtLUtKTsLUtLTtIRqHQpJSrFRpGRpHSqHSqHSqGUrKTsJTtFStESuETuERuBRtBQrAOsAOt@NpBQrCRsDSsDRvCRsERvDRuDRuDRtCQwAOtAOtAPrBPsAOrAOr?MpBOrAOqDLq@NqAMpANp@Ls@Ls@Ls@MoBNqBNuAOpAMpBOrCPsBOsBOrCPsCOsBOr@MqBNq?KoANr@MoAMqBNqAOrBOrCStDStAPqBQrBQsBNmBNn?OmAPrAPqBQrDPsCPqCRsDRsDRtFSuERtERtERuEQtBSpDQqDQqHStHSqJUsJUrITpITsJUsKVuKVuLWuKVuJXuKZwLWtKZwM[xO^{L[xN]zN]zP]{P^{O]zO^{P_|Ra~Ra~Qa~RbSa~R`}Q_zQ^yP^yQ_}P^{P^{Sa~Q`}Sb~W`~T_U`U`T_R`}UcTbSa~TbTcTcWf]f]i]gZj^m^laobocobodqdrbpaobpdrdscrftiwjxhviwiwguhvfphshsgqetftfqfqgrhshrfuetjyjxlzlyn|m{o}o}m{n|o}o}n|p~o}nnm|o~m|o~n}on~rm|o~n}pn}n~m}m}m{n|n|m{m{l{oynzlxlxnzmymymylxmylxlxmymylxnzlxmylxmxm{kxpqssn|o|nzo{q}o{q}nzn{o|p|o|o{p~q~q~r}s~stss~s~u}uuvrtvw|}~m{efuZj{]vkuof`_vXf~^mefy`NaLRhVJ`N[m\]o\exdgyhswtWidxjzgĨæè¦ŢŝǣţŤǣĥ俤`cXbeZae]bd^`c]_a\`b]_a\`a]]_Z^`\^`[]_[]_Z^`Z^a[\a[]b\\a[\a[Z_Z`e_\a[]b]]b_]b^]b^_d_\a]]b^\a]]b^[a\\a^^a`^`_`ca_``]`^]`^^```aa_a`\^]^a`]___a`]`_]^^]_^Z^^Z^`Y]_[_`Z^_Y]^Z^_Y]^[_`[_`[_`\`aZ^_^ac^_e^^b]_bbbe`bdcdfbce^acbfgcghbggaffafgaefaedbffbfhcghbfgaefaefgkibefcgeglhfkhfkdglhhmihnjimjhnjiokjnjkmkfihfghfgjggighm`cicfnbeldflbgichkafjbgkafkejobgndiofipfiqehphkqgkoimoilpginjmthktgjtfirgjpehoehodgndgndfnfjrgjpgknilrjmrhkpjmrilqgjojmrknskpsmqsruuttwrswrswnpsnrumrtrvyrrvqswstxuuyvw{twuvwvy|zvwyvwzuwysx{uy}w|uz}uyzx|~{}}~~~}~~~swzot|lqfkxhr|enwhnxlnuyxwytop}i}isoponooptvq{|rrtphopmovgjrfirfiqadj`ch`bh]`e\ah_clZ^hYakZakW^hX_iY_j]al[_jY]hX\hW[fUYeVZcW[eX[gWZeX[e\`hX\b^_cc_d`]bccha_da_ca`bgefffifgicdd^``bbc__aifbolcrobnf[nh^li`toawo_wq]xr]yr[~sYbhhfy^~x]{uYu]gbbzevpdfc`YZ_SW_TXbVZc\ahdgmuuuxupysnwrnnklghl_ag\_g[^gZ\e\[f\]j\_j\al[`j_bn[_lW]jU\kSYjOWgNWiJRfJReJReGObDL_BJ_CJ^?FY>EX>EX=FX;BU8AS7@S:AT8BR8@R9AU7>T7?W7A[5BY5@X4@X6BZ6BZ5AZ4A[1?Y1?Y5D]3C\0@Y1A[2@[1?Z2@[3A\2@[2@Y1?Y3A[1?[4B]3A\3A\4A\6@\4?]7B`5@^5?_4?`2@_3@b1?b5Bf2@c3Ad2Bd2Bd0@b1@b3Ad1?c2@d/=c1?d1?d2@c2?b1?b.?a0?d/?c-@g-?h,=i->h/=i.j/?l/@l,=h->i->i.?i.@g/Ai/Bj.@h/Ai/Ai2Al1@k0?j0?j2Al3Bm1@k/>i0?j1@k1@k2Al1@k0@j1Ak0@k,>j.?k0Am/@m0Al1Al5Cn1Al2Bm2Cn3Cn0Al2Co3Dp2Co3Co2Co3Do1Eo2Dp2Ep0Am2Co1Bn0Am/@m0Ap0Ao1Ap2?n5Bq3Bp1Am1Am3@n3An3@m3?l4Bo4An1@m/Bm0Al/Cm1Do1Do0Cn0Bm2Bn0Am1Bn2Co5Er6Cp6Dq4Bo5Co5Cp6Dq5Dp5Br2Dq3Cq0Bp2Bp3Cr4Es4Ft1Cq0Cq0Cq3Ft1Cq1Cq2Dr0Bp/@n2Cq5Fs4Eq5Ft5Ft5Ft3Dr6Ds6Ds6Dt5Ct5Cq4Bq4Bq5Cr5Ds5Ds6Br6Bs6Ds7Et9Gv8Fu7Et7Et7Et5Fq6Dr6Dq6Dq9Gt7Ft6Hs5Gs4Eq6Gr5Fr5Fr5Fr4Eq4Er4Hs4Gr6It6It3Fq4Gr6Gs7Ht6Gs6Gs6Gs7Hs6Jt5Hs6It7Ju9Jv6Hu8Jx7Hv8Gs;GuMw9Hq:HoLo>Lp>Lp>Lo>Ko>Lo?Mp>Lo=Ln>Ko>Mp?No>Mn?Nn@Op>Lp>Kn=Lm=Lm>Lo=Lr>Mt=Ls;Jq;KqLq?MrLq>Lq?Mq@Ns@Ns@Ns>Lo?Mp=LoAOp?Mp?Mp?MpAOrANr>Nn?No?Mn?MqAOrAOrAOrBRrCRsCSsFUvDQuAOrAOsCQtAOrBPsAOrCQtESvESwCSuCSuBSuERvCRsBQrETsFRsDQrDQqHRrEStDQrFTqHStGRrHRqJTsGRqGRpGSpHSqIRqHQpGPoJSrHQpKUsIUrKVtLWuLVuNYwMXvLWuITsKZtJXtKYrL[uOZuMZsNZtMYsQ^wO[uNZtP\vR[wP[uSZvQXtQXtQXuRYvRYvSYyTYySZzSYyV\|W]|V^yW]{V]xV]xW^zU\yU\xV]xY`yX_yY`{ZbwYby[`yZ`x[ay\cv^fx`e|`f|_e{`g}ch~bg|gi}il{mn|kkykk}ii}fi{gjzbdx_dw^ew_gybj}engofogogoltksowqymuowmukskvlylxhsgsgreuhrcobnam`lbn`lZi\h]fT^xP[xQ\yO[yQZyPYuNWuMVuMVuKTsMUtKTsJUsIRqIRqHQpJRrGSpHSqGRpGRpCSpERrGTtFRrBQsDStCQsCSuETtCQtBPvBPsDStAPq@OoAOsCRrCQrDRuBPsBPsAOrAOtCPvANp@Nq?Mp?Mp?MpAMp@Mp@Lo@Mp@No@Ls@Lr@LrBMs>Mr=Kp?Kq@Ls@MpANq@Mp@Nq@NqAOr@NqAOr>LoBPsANq@NqANr>MpAMpANq?NqBPsBQrBQrCRs?NlANp@PqAPq@Op>Mn@OpBPqCPtBQrBQrCRsFUvERtDQtERqDQpDSrGStDQqGRpHSqITsITqJUrITrJUsITrITrKVtMWuIWtKXvNYwJYvLZwHWtJXuM[xMZwO]zO]zO]zO^{O]zR_|P_|P_|O_|Q_|Q_}Q_yO]xN\wO]wR`~R`}P_{S]{U`~U`}T_~T_}U`~U`Q_|Sa~Sa~R_}U_~VaUaXcVa|Ze[f\g\g^h[k\k]k\j^l_m`ndqanercscrdrdresesguftguftgtjthsgrisdtgqitfqfqfqgqgshrgukzjylzkym{n|o}n|n|n|n|m{kyn|kykzl{l{n|ool}p~nn}o~n}n~n}m~l}o|m{n|o}n|nxn{o{nznznzmyo{nznzmymynznzkwmylxmykwozlycpzfuzqpn~vo~vo{q{p|p}p|q}q~q~q~q~suuvuwvw{{zzyy{}~n|e\lMvaRf?PgBToLE_>GdBC^;MgHH`DI[ECYEJ`JG^GZp[jikkwwl|Tj_=TEɷwēyţÞĞĠǟģ`bUcdZbeZce]ae]be^ad^_b\]`Y]`Z^`[^a\]`[__Zab]_b\]c]\b\\a[^c]\a[_e^^c]]b\^c^^c`]b_\a`_cc_a``b`\_\]b^_b_^`^_a_^a][][^`]aca_a^_a^^`^_a`__`^^]\][^_]]_[\_^W[[Y]]Y]\Y]][_^Y\\^_a[^_X]^Z_`]bc\abZ_`\_a_`e^_f^_eabfbcf`adbbe_ab_cc`dfbceabecdfcffaddaeebgebgeaeeafcdjeekfdjfhmhhmgjoikpjnslkpjkpjinkgkfkmiknkkmjkljkkliiigjheghehlcelcfmdhkdhjeijdilbgldimejnchnhlthksgjqgjpilpkorjnrimpgkphkphkpcfmfiqfipcfndgmdgl`dhfgmghmjlpimonrvmounqvmpuknshkpknsilqknrmrtnssqsurrwoptmosnrupsvrswtuywx|vw{vx}ruwrurwxwyxzyzzx{zx{zz{{x|}w{~v{}x|~{{z|}}~|~}~~~{}wwznrwmqylo{ip|fpygoyflwgmvjoutvyyxuyvvm}ksrmloqkgmrjxzlosmnrpmqsimrjmrhlrejminqchk^ch[`g\_i\ajZbkZakZak^am^al\`kY^iY]hW[fTXcSWaTXaRV`SWcTVbWYd[_fY_eZ]e\\bZ\c]ah]`d]_bdei`aeddeddf`_ba_ea`fa_bmhdwpe~vcu_ulUvpYz_cba~][\_cgdcxYwY|]cgj~aql^___X\fX[i[^k]_iffk~|zs}t|vpmlkghl`dk^`j``jaakaan^an^`l^`l]alZ]jV\hUZjOWhNYgITdIQbGObCK^DL_AI^>F[>FY=DV=DXQ7?Q8@S7?T6@V5@Y5AZ5A[4@W5AY5AY5AX5A[3A[3@Z5C]1B[0@Y3A\1?Z1?Z1?Z1?Z2@Z3AZ3A[2@\2@[4B]4B]3A\2A[4B]5A_7A_4?]5@]5@_7Bb5Aa4Bc3Bd2Ab5De3Ae1Bd0@b2Ad2@c4Bd1?b2@d/=`2@c1?b1?b1?b0?b1Ad0@d0@d/Ag+=f-=i->h,>i+i,=i+i->i,=h/Ai.@h.@h.@h/Ah/@i1@k2Al.=h2Al1@k0?j1@k2Al1@k/>i1@j/>i0?j0>k/=j/>k->j->i+Mo>Mo>Nn>Mo=MmNn>Lp=Kn=Kn=Lo?Mo>Nn?No>Mo@Mo?Lp@Mo?No?Mo?Mq?LrMsLq=Kp=Kp?Mr>Lq?Mu@Nu@Nr@Ns?Ms>Lo>Lo?Nn?No>Lo@Nq@Nq@NqAOq>Nn?Mn?Op@Nr@Nq@NrAPqBQr@PqDStCSsBPsCRtDSsCRrDRt@PqBPsBPsDRuDSuDTvEUwESvDSsDSuCSsDQqEQrGQrFRrGSrEQnFSpGQpGRqGRpFQoGRpIQnJSrIRqIRqHQpIRqGPoIRqIRqKTsJUsLWuKWtITrHSqJVsKVtLWrJYsJXsLZtO[uMYsLWtN\tOZuO[uP\sNZuNYsPZuSYvSZvPWsPWsOWrTXwSWvTZzSYySYyTYyUZzX]}W_zU\yW^|W^{SZvSZvV]zW^yW^xX_yX_yX_yYawZby[`x\bz[av\bx]bx]bx`f|_e{`g|cf}ef|eh|fh|cdzfh~jkkmfj{bf{_dw`gxak|ck~emgoiogohpiqltmuqypxpxpxowkvmwkwhucqftetetfqdpambnam`lZgXb[dW`~T_{PZvNXxQZyMVsOXuMVvLTtMWvLWtJUsITrKSrJSrHQpGOoGSpGRpGRpEPoEPqCPpDPpDSqCRsDStDStCStDStCPtDRxCQtDStCRpBQrAOs?NoBQrBPtDRuCQt@Nq?Ms@MrANp?Mp?MpJq@Lq>Jp=IoKp>Lq>Ip=Io?LnLo?Mp>Lo@Nq?Mp>Lo=Kn>Lo=Kn?Mp?Ms@Nq@NqAMr@Pq@PqAOp@OqANp?No?No>Mo@Op@Op?MqBOr?OoAPqBQrCSsDQsDQuERqBOoCRqEQrDRsHSpGRpKVtHSrJUsKVtITrITrKVtJUsKVtHWtLYvMWuKZvLZwJXuKYvJXuN\yN\yM[xO]zO]zN\yP]zP^{P^{O]zP^{P^{P^xO]xP^yP^yQ_zP^|P_{U^}T_}S_}R]{T_}S^|U_}Sa~R`}R`~Sb|T_{U`U`~VaWa}XcYdYd[f[f^h\iZi[j[j[i\j\l^n`obpaqdresesesguesguesesgqititgrhshsfqhrgrgqgtgsgshtjukuizkyjxlzn|o}n|p~o|n{m{m{lzixjym}jzl|m~m{o}n|o~qp}qo}qn|o}o}l{p{p{p|q}q}o{o{o{myp|nzp|nzo{nzo{nznynzo{n{q|hv[jlWh`Yj]^pbx~yyp}vxyp}usstvv||ZjQse^tQe|Xby[OiIEaCQoP>[=D]?`w[SeM?VARhR\r`?U@2K1LdIazc`xck{Pd^G`PLbPyf{`[ڞƲceXbeYadYce[``Ybc\be]cf]ad[^aYab]ab\`a\`a\ac]aa\`c]^b[]`Y`cZad\`d]]`Zad^^a[^`]aca_a__a`\]Z_a^^_\]_\^`]^`]^`^ab`__[__[ab]_a\]_[^_]``^``]]]\^]\__\bd_`c^Z`\Y^Z[^^[_^[_^^_`]^_Z\^\]_^_a_`b`ad`ac]^a]^c__d]^c_`acdebedbdadfccebcdcceceedffddeadfcegeficgjcjlgjmfjmgloilpiprmjpjinhioikpjglgglhgmheiehjejlgllhlljmmkmmknmnjmlhlnfkmejmhlngklimnkopfjmglphmpinskqtjmshkpikqimpjnogklhllhlneinfhmcfmcflfinfiohkqikofgkijnklqlmqlqtnrvknsnqvmptlotilqhkphkpknskpqnrrquwotvkormqtlpsprvtuystxtuystxuw{txzvuwvwywwv{{y|}z}}{z|zz|zz{{{}|{~z}}~}~}~|}~}swwqtxmpvnq|mr{nqykpyjq|gozcmufmxflxhmvkowprwwwx|xu~u~}orwojqroruumxxgqrirtnvxq|~wwyrkliimjputhnq_ek^cj^bk`dn^bm\`kY\gY^iX\gZ[hY[fTWcRWaRVaOS]PT\PT^SVaTWdVWdWZbX^dX]dX]dY\eY\e_bl^ai``e_`fcbgbaf^^d_^g`_fc`fojh~vjtj~dlpkgdcke[[ab_]~Y]dhkniig_^`f\^h]]h__ieafwohwe{f{kvlolhiikehnbdmfdmggofeqacp]`l\^j\_jZ^iTYePWcNVcGQ_GQbDM^AJ[@H[>FX?GZ>E[;CX;BX8?U4?S7AW6BX5@T5?T4@S5@T6AU6@U4?U4?U6AZ5AY4@W3?X4@X2>W6B\2@Z1?Y1?Y1?Y3@Z3A[3A\1?Z1@Z0>Y0?Z1@Z3A\3A[1?Z4B]2@[3A\3A\1?[2@]3B_2A^2@]5B^6@_6@`5Ab3@c2?a4Ac3Bc1@a4Dd2Ab2Ab3Ac3Bc0>`2Bb0?`0>a0>a2@c1?b1>b3Ad1Ac0@f.Ah-?g->g+h.@h.@i-?h.@h-?h.?h2Am2Al1@k0?j0?j0?j/>i0?j.>h-j,>i*k.?k->j->j->j->m->l/?k-?j->j-?j-@j->j->k-?j2?l0>k->j0Am.?n.?m->l->l-=l-?l+>i,?j/?k/?l2?l1?l1>k1@m0>n0>m0?k.@k/Al/Bm0Do1Am0Am2Cn2Cn1Cn3Co5Cp4Bo5Cp3An3An4Bo3Ao1Dr4Es2Bp/@n0Ao1Bp2Cq1Bp1Cq0Bp2Ap4Br1Cp1Cq1Bp1Cp1Co3Dp4Fq1Co4Eq2Co4Eq4Es6Br6Cr7Ds7Eu3Dr6Cr5Cr5Cr7Ds5Dr6Ds6Fs6Hs6Fs7Et7Et8Fu7Et7Eu7Et7Er5Cs6Dr7Et8Fu8Fr6Dq7Es4Fq5Bp8Fs9Hu5Gr5Fr6Gt4Hs6It5Hs6It6It7Gt6Gs6Ht8Gr8Gt9Gt6Hs7Iu8Jv7Hs8Gs9Gu:Hw9Gu:Hu:Hu;Ku:It;Jt;Ju6Ht8Jv7Ht8Is8Jt7Hs7Hs9Ju;ItKtKn=Km;JkLm=Mm?Nm?MmANn@Mm@Mm@Ln@Mo>Jm=KmANpAMpAOq@No?Ln?Mn@Lo@Lo@Mo?Mp?Mp?Mq>Lr=Kq>MuMt=Jo;Jo:Iq;Jq:JmKn=Kn=Kp>Lq>LqLq>Lq>Lq@Ns@Nu?MtBPt=Kp?Mo?Mp>Mo?No@Mq@Nq?Mp?Mp@Np>Lo?No@Pp?Mp@NqANq@MpAOpCPrCRsBQrCQtCRsBQrCRsBQrAPq?NoESvDRuFTvFTvESvDQtDTtETuEQrEPnFQoFRpHPoGOnGQoJRpGSqIQpHPpFPnGPoIRqFOnGPoHQpFOnHQpIRqGPoIRqKTsHTqJUsKSsIRqJSrKSsMVtKWuMXvJYtMWsMXsMXsLWrMXtLYrNZtO[uO[tP\uQ[vNUqOVrQXtPWsQWsTZwRYwSZwRWyUYyUYzUZzVZzW\|TZzU]xSZxU\wT[wT[xU\xV]xY`zY`zY`zX`yX_yZ`wY_wY^vY_vXax]e|\d{_d}ag_g~ae{`dzae~`d}di~fjhldi|_ez_fyah|elek~ipkrjqhpiqiqksmuqyu}t|s{pwmwnxlxiucrbpbsdsgqcodpco`k[kYgYd~ZdUb{V`}P^yNYvQZyPYuMVsLUuLTsJVsKVtLWuIUsJRrJSrJSrHQpEQoEPnDOlBQnEOpCQqBOo@PoBQrCRsDStFUvGVwDQuDRwCQsCRtDSqCRtCRsAPqAPq@OoDQuAOr@Nq@Nq@Mt?No?No>Lp>LoJo;Ko;In:Im=KoLo=Kn;IlLoLo>Lo@Nq>Mo@NqBOr?Pq@PsAPpAQq>No=Kp=Mm?No?Nm?Nm>Mo?No=Ko>Nn>Mn?OpCOrBOqBOnBOoDQrCSoBOlFTqGQpGRpITrITrITrKVtJUsITrITrITrJUsJXuIWtNWvKYvKYvJXuLZwM[xM[xLZwN\zM[xO]zN\zO]zN\yO]zQ_|Q_|N\zP^xO]xP^yP^yO]xO]xP^{P^{Q_}T^|S]|T_}S^}T^}P`|Q`}Sa|U_{U`{T_|T_~V`Ra{YbYcXbYdZeYeYfYiXhXhZjZjZj[kZj\k^l^laocqdrdreseresetfpgrgrfqgsepeqjviuhtjviuhtiujujukwlwixlylzm{lzm}m}l|n{m{n|n|lzj{k|k|lzkykylyn|n|n|n|n|p~n|o}o~q|q}p|q}q}q}p|p|o{o{q}o{p|p|nzo|p|myo|n|q~yhszaopQbY8N:TkS^u\d{awri}p|y}z|uvwy{s~s]nT@W:>X;B\@5O07O26P34S4*H&7Q13M.?U9-C1%:'8L:-C-/H.BZAhi]mrkx2L8`xeveuNiiNÓ|yÜ÷bbWddWeeZbcYceZbdZbdZbdZbeZce[cd\ab]`b]ab]cb]bb]ab]ab[abZcd\abZcd^`b\_`Z_`Z^_Y_`]ac_`b_]_\`a\`b^^`^\^[_a^]_\_a^``]`a\_a\_a\\^Yac_a`_``_`a^ab]bc^`c\`c\`ea]a^^c`_c^_a^ac``b_^`^_a``b`aba`b`aca`ba_ac`ba^`_`b^bc`dgaficcg_dg_dd^fgafe`feahibgidjkblnflncnpfprgoqiophoohmpijkfjmglnhlnjhifgifikigifjlgjlgmmijihmmjnnloomllilnmklnlloloqkpnjnmimojoqhnoglpkprjosimpimpkoqjnoimmimnjnofjkhlnhloehmhkpgkoekmlospptklnklnlmnllppqvrswnotmpvknviltimtilrjmqmqukoqnrrptvptwnrukormqtpruttxtsxtrxuvztuytwzutxwwxxxx|}z~zz|~y{}x}~{~~}}~~}yx}}swulpuns{mr}kr~mr~kozimyinzgmyiq{ks~inzkq{lszqu}tv~||~{u~{psvoputptvvvxwvm~o}my{kxylvyukor`cl_cl_aj]akZ^iW[fWZeXYeWZeTVbRT`RT`QS_PS^OS^PT]OT]SXbUVcUTbUWaVZ`V[aW[cY\eUY_XZb[ZdYYcZZdZZcZYbZZ`YY`\[d\[`ea`pj_wsiolgdgffgfdada_Z`fijgkwrdffd]^]^^]_^_jdc~sg~fekzlwsjqnphjqfhrdgngkrehrabo__k__k\^jY[fTYdLT_GPZEO\FNaCL]@IZ@HZ=EY=EY9AW6?T5AW6@W5AW6AZ5AX4@U4?V6@U5@U3>T5@V6AW3@X4@X3@X3AY5AY4@X3?W6B\3@Z0>X2@Z2@Z5C]3A[1?Z2@\3=\3=\5?]3>Z1@[3A\1?Z3A\3A\2@[2@[1?Z2@]1?\2@]3A]2@]3@`6Aa6Ab3Ac3Ab3@b4Ac2@b1@a3Ab2Ab3Ab3Bc2Ab1@a0?`1@`2@c/=`/>`/=`1?b2Bd0Ae/Ae/Ag-?e.@h+=e*;f,=h+i.?j.@h/Ah-?e+=c,?e.?f2@j2Aj2Aj1@k1@k/>i/?j1?j.k/@n->l,=k,>i.?k0>k2?l/>j.?k/@m.?m->l.?m-=k->l.@m+@k-@j-?j->j/@l/?l/@k1?l1?m1?n2@o/Ao0An0Al2Co2Cp0Ap/@n2Cq1Bq3Dr6Cr5Cq5Cr1?l3An3Bn0An5Ft4Es-?m/@n0Ao/@n1Bp0Ao1Bp3@o3@o3Ap4Bq4Ap4Ap5Cq7Er6Dp5Cp5Cp6Dp7Er6Cp5Cp2Cp5Fu4Ft3Dr3Es5Ft7Dt7Et7Cs4Bq7Ds8Et6Hs6Gs8Fu8Fu8Fu7Et8Fu7Et7Es5Cr8Fu8Fu7Eu8Fr6Dq8Fs9Hu:Hu:Hu7Er6Dq6Gs7It5Is6It5Hs7Ju7Ht5Fr6Gs:Gt;Ju:Hv9Fs9Gs8Fs8Gt9Ht9Gt;Iw9Gw8Fv:Hu:Gu>Jw;Jw:Gt;Iu:Hu:Hu7Hs8It8It8ItKl>Mo>Lk@Mm@Mm@MlCMkAMm?Mm@Mm@Mn?LnAKn@Lm@MoBOq?Lo?Lo@Lo@Mn@Mn@Ln?Mp?Mp>Lo>Lp=Kq>MrIp:InLpLq>Lq@Nu@Nu@Nu>Lq@Nr>Lo?Lp>Nn>Lp@Nq?MpAOr@Nq>Lo>Ko=Kn@Np@NqCPsENrCMpBPr@PqCRsAPrCRsBQrAPqAPqBQrAPqAPrDQwCQvDRtDRuESvETuCStEQqHSqIQpIRpHRpHQpGPoHRpHQoIRqIRqGPoIRqIRqFOnHQpFOnENmHQpKTsGPoHQpKSrJVsKVtKSsJTsKUtLUtLTtKXuKVtLXuLWsLWuLWuLWtLWrLWsLYrNZtKYuNXuQYvPWtOVsQXuPVsOYvPZuR\vR\xQYvQZyRZyU[{TZ{U[}TZyV\|T\xRYuSZvU\xU\xV]vY`yX_yW^xX_yW^xY`zY_yX_xX_yX_yYcwZby\d{_e}^d{^cy\b{\bz]dy_ey`gybi|`hz_gz^fybh{fmgnkrqvrwovovovnvqys{u}u}t|rzmzlvlyiuetcq`paqbpbndn`l_j[kXfYeZcVc}Ua{O]yNYuQZwOXuOXtMUvKUtJVsITrKVtIUsKSrIRqHRqFRoDOmEOmCQmDRoDOpDNn?Nn@On@OpAPqCRsCRsDTtGUxESxBOrBQrBQoBQpAPrCRsBQrCRsCPtCQtBPs@Nq?Mp?No>Nn>Ko=Mo=Mo?LoLrLp>Lo>KoMoMl=Lm=Mn=Jm>Jm?Ln>Kn?Ln@MoANmANoAOkAQnBPmCQnGQoFQoGRpITrHSqHSqJUsITrHSqJUsKVtHVsKYvMWuJYvJXuJXuLZwLZuL[vQ[wL[vLZuN\xN\xO]xP^zP^yO]xP^{N\zP^xP^yN\wP^yO]xP^yP^yN\wQ_zQ_zO]xT]zT_{S^zS]zT_zS_{T^{T_|U`{T_|Va|Ub}R`|Tb}XeZd[fZb[dWhWhWhWgXhVgXiYj\k]k^l_l^l`n_nbrapcqesgqgrhsjvjvgtjueqhtgshtiujwisiviukxlwjxlykym{lzl}k{k|kym{mxjyn{m{kzlzkzp|p{k|l{mzn|m{n|mzm{n}m}p{o|p}p{q|s~r~sq}r}r~q}q|r~s~s}s~r}r}uq~mzWb`O[TPaSD\B]vUXoLI`=j_ascw{|{v~z|anbRbQPdKViMNgIWnS,D/3L.X<5O20L-=U98P5&?%BVD*?,?Y<>W94M3e|avyq~'C)êi]Qp@}e_F4Q%{uby^TmMmhuk{o|if{w}uvhhvpccWecWgdWcbVeeYfgZbdXbdWceYbe[ab[aa]b`]cb_dbabc^bd]bc]de\ceZbe[cd]ab\ab[bb[ab]bb^bca_a_`a^]a[]b^_a^^`^_a^_`^a`^ccacd^`b\be^^b[cd_`a\`b]ab]aa\ed_]^W`d\Z_X^d]]b]^c_ac`ac__b]ad^ab^bc^cd`bc^bd^bb`ac`cebad_cg`cf_fi`hlafi^eh[hi^llcllcllcklalmcnpcqqeoocoocppdqsfmpekmbjlehkiikgjlhkmjknjlnmmoolnkpqlpqlnpjklimmknnlonmopmoqlnpmlmmopompokpoimphmphooinohnolptnrukorinqkorknojnpmqrfjkfjkimojmrkoqkppmpqqrtoqsoproqsrqtoprqrwnotnptkotiltiksjltjmtkmrmqumqtostotunsvnrtlpslquprvrrvsrwsrwtuyrsxswzuryyyzxxw{}yy|}{z|}|wx}zvxrqttlpvlq|ip|go{ipykpymq|nqlpmr~otmtpznxmuosqtzwxt{yu~{wz{stxupqpmsppopstoqrtvz{rlmobdl^al\`iX\fUYdRU`RT`PR^ST`XWcXXeSUbSUbPQ^OS^PT_OT\TYbYZgXUdVWcTX^UZ]VZ`VX^TVYYY^XX^STXSSXVX]TX\TW[VZ\WZ_XY^_]_je]uiyhyg~ebadecaegc`bdheegif{^m{mqrfhj_fe]ihduph}kmhm{o{ujtoqiirgksdhmdho_cm\^l\Zh[\hY[gY\gSXcOUbJS]EN[AKZ>FW?HX:CU:BU:AT8>S7@U5@V4@W5AZ5AX3?X3?W4@U4@V1>V2>V4@X3?X2>V2?W0@W1@X4?W4@W3>W3?Y3?Y3A[2@[2@[1?Y0>Y2@Z5>\4?^6@^5@^4@]1@Z1?Z1?Z0?Y0>Y/=X1?[/>Y0>[0?[1?[2@]3@`3@`2?_2>_5>b3>a2?a3?a2>a2@b2Ab3Bc2Cd2Ab3Bc2Bc0@`/>`1@c0>b0i->i.@j,>f,>e,>c-@e-@e.@f0>h2Aj/>f/>g/>j.=h-k0k.?m,=l-?m+=k);i*h*?j-@k-@k->j.?k-?k/@m1n.@n/@o1Bo1Bp1Bn1Ap1Ap1Bp0Ap2Bq3@p1?o4Bq2@l4Bo4Bn1Co2Cr5Et3Ap1?o/@n0@n2Cq1Bp2Bp3@p4@o3?o4Bq5Cr3Aq4Cn5Cq3An4Bo6Dq5Cp6Cq7Er4Bo1Co3Dp5Gs3Eq4Es4Fs6Ds7Et6Dt5Cs5Cs4Ft5Gr6Gt8Fu9Gw8Fu6Ds7Et6Ds6Ds7Et7Et9Gu8Et8Er8Er7Er9Gt7Er9Gs8Fr7Eq6It5Fr5Hu5Hs4Gs5Gu6Gu8Hs8Er7Dr:Hs9Er9Fs:Hu7Er9Gt:Gt8Er8Es9HvLsLo?Kn=Jm>Kn>KnKl?Ln@Mp=Kl=Kk=Jj?Lm@MlCLjALiAMl?Mn@MmCLpBKo@Jn>Ln@Mo@Lo@Ml@Ll@Lo@Mn@Mo>Mp>Lo?Mo@Nq=Ko>LoJnJp?Mo>Ls=Kq;Ip>LsLq?Ms?Lu>Lr@Nu?Mq?Mq>Lo?Mo>Mo@NqAOr>Lo@Nq?Mp@Nq@Np?Mt@Ns@NpANqCLqDMqAOpBNqCRsBRsBRsAPqCRsCRsCSsBRrCQsDRxESxFTyESvCQtCSsCPrDRqHPoHQpHQpKPqJPpJPpKQqHRpGPpIQqGPoHQpHQpGPoGQpFOqGPoGPoFOnJRqIRqITrJUsIUrISrLQqNTtISrMVuLUtIUrJUsKUsJUsLVuKWuKVtKVrJUqJWpHXrMXsNVsQWtOUsQXuNWuPZvO[uKYtO\xM[vP[{O[yQ]{RZyU[~TY{UZzS[vV]xSZvT[wU\xV\yW^zX^{Y`zX_yW^xY_yX^yW^{V]yY_yX_xZawYaw[ay[ayYbxYawYay^ew]ev^hw`h{_h}_gz`h{_gzelfmos|}x~szpwryr{u}u}wxt}mymwmwhudscq_o^n`nbmbm^j_kYiZhZe}Yc|Xd|Td}O^wR]yQYvQYwPXvOXwKWtKVtJUsIUsJUsIQqHQoHQpFQoEPnBQmDRn@OkCMnCMm?Lm?Nm=Ln?NoAPqAPpCRsBOs@NqAOrBRrAQnAPoAPr@OpBQrBQrAOrBPsBPsCQtBOs?Oo?No;IlLo;IlLoMm>Ml>Ml=LjMfCJbC.B(klTnR1K3-D-7O:0H1E\G,C07(/C/>S?FZHNdND[=McFAX=^qQ\pSwxƆzq~drpd}pNkTݾRnQDbCLmI8Z0TtGgWϨVpQCZCSmRLfHSnQǶs_ppG\TE^O>YCHdFwknsjfe{z}kdUlIZsM~sbuhnbbeYbdZfe[cdWeeYffZccWcdWceXbcVbbWbaYcdZfd`cb^dc_db_cc_dc[dd\ddZeeZee\bcWdeYcd[ce^ab^ac_ab]^b[_aZ`c\`b]`b]`b`adb_b_bda_b\ce_ac]^`[ac^_a\ab]``[ab]]c]]b]]_]_a__`^`_]_____]cd^dd_ghbbc]cd^bc]ef`cd^dd_cd^ce_ef`gh`ik`ik^lm_kl_ol`sqdpqcoobqoaqpbspcsqcsrdsqdpqcqrdnnckn`moglminnioojnojopjnpjnpkqtlqsloplppqqqprqpqqoproqspoqnkmkkmllnmlonlnmproopmlpllnnkonhmonprppspproqqntpmrqlpplprlqulqsmqrmrtmptqrurrsqqqsrqrrpssstttssrppspqslnqimrhmqhlpjorjotmqspuyqswstwrtynqvmrtnrtosvorvqsyrvyptwrvytxyuuzvvxyyx}|w{}||}zxwz{|~wtzx{|}qutjsusinslpymq}ln|mp~lp}os~owqyqxownwltkq{pryvwx{zuywsxxtzxr}qxwnoquxxkqvttuztprvxljkg`be[^dY\eVZePT`OS_MP\OQ^WZchhnaciUXaQT]MR[MQ]OT`RT`UVbVYdWXbTX`TWaTZ_TY_TX^PU\QS[QT\NQYOS[SV^RU]SV^SV_WY]^_c]^agcge_aja]rh^n\cdbfidegbfddffceh~bu\qr|k|kwg|jnnolmzkxtmplhhhiefk__hY[d[ZgY[hRTaPR]SUaSV`QVbNVaLT`HT\EOY>HS=GR8CQ5BP5BR5?R4?T4AU5AX4@V4@Y5@X5?X5>W3>W3?W3?W2>U3?V3>V3?W1AT1?T/>V1>Y0>Y1>X3@Z4B]2@[1?[1?Z1@Z2@[4B\4C^3@^4?]4@]2?\2=[1=Z2=[1`2Ac1?`1@`1@a2Ab3Bc2Bc0>`2?a1?`2>`3>a1>`0>a0>a1@e1Be2Ae3Dg-?d/Ag/Ai3Aj2@i0?i.=i/@k->j->i-@f,>f-?h,>f-@g->g0>f0>e0?h1@i.=f-g/>i0?j-i+>i->k->j->j-?j.>j0=m.=k0Ap.?m0Ao2Cr0Ao1Cn/Cn/Bm0Cn0Am4Bo2@m1@m4Ao3?o6Aq3Bq2Cq2Cq2Cq2Bq2@o.@n0Cq3Dr2Cq1Bp0Bo0Bo2@o2Bq4Aq6Aq7Cq7Cp6Dq4Bo4Bo5Cp4Bo7Er5Cp7Eq5Cp5Bo6Dq7Eu5Cr6Dq6Dr5Cp6Dq5Gr5Er8Er5Bp7Er8Fs7Er5Cs7Et6Ds8Fu7Et6Et5Fw6Gt6Fu6Ft9Gs9Fs:Gt;Gw7Ft6Et9Gv8Eu6Ft6Fr7Ht7Hv6Gv6Hv6Fr5Gs7Dq9Gs8Es8Fs8Fs9Gt:Gt8Dv8Du8Gw;Ix;Iu7Fs:Ht:ItLv=LuLo=Jl>Km=Jm=Jk=Kl>Kl@Mo@Mo@Ml?Kl@MkBMjBMkBLkCKiBKkAKiBMkALj>Kl>Kk?Ll>Kl@Lo?No=No>No?No?Ln?Ln?Lo?Ko=KnAOr>LoLq>Ln?Mp>Lo?MpAOr?Mp?Mp?Mp?Nq@LoANqANpBNqANpBOq@OqCRsBPrBOqBRrAPqAQrDQuCRuDRvCQsESuESvBPsCQtBPrBQsETrGQoHSqITrEQoGPoGPpISrGPoGRnEPmIRoIRqHQpFOnJNoKRqGQpFOnHPoFRpHSqIUrKSsJSsLQqKTrJSsLVqKTqNWvJSrKTsHTuJXuJXuLWrLWsKVqJUqLWsKVuMUsMVtNYtLVqNXrPZvOXtP\vO\uOZuP\xQ[xQZwRYzT\{QZwQZyR\|U\{U\xU\yU\xT[wU`vWbyU`vU`vX^yW^yX_{V]xV]yX_{W^{VazV`yWazVbwX_wX`wX`wYby[ax\bx^d{af}_f|ckaickaj}flkrzzt|t|rzovryqxv|t{w{u|ovmxjwjvhseqaoaq]l^nan^j^i[fYhVd{Vc~Xd~Vb}S^zS^zR]yP[xPZwQ\wNYtMXtKVuKVtKUtJUqISsFQoEPnDOnCQnCPp?OmAMm@Mm?Mm>MlAPq>Mn=Kn?Mp@PqAOr?Mp@Oq@Mo@Mp?OoCRrBQr?No@QpBPsAOr@Nq@Pq@QrANqAOr?Lp?Mp?LpKmLo=Kn=LmMm:Ik=Ln=Mm=Lm=Lm@Mo?Lm?Ll@Mm@Mn@NjAOlCNlFNkGOlFOnEQnHSqFQoITrHSqKVtITrITrLVuKVtKUtHXtLZxKYsLZuIWrKYtKYtM\vN\uM\uP^xP^xO]yR`|P_zR`zR`{R`{R`{R`{O]xP^yO]xO_yN_yO`yP]yO]xO]xQ_zP^yS]yQ\xR]yR]xT^zT_{Va}T^zSb}U`|T^zU`}Va}Va}XbVd~WeXfYg\jZh[iXgYg[i[i\k^h]k\j^k]o`m_mbpcqaobp_mbqfqdpfrgseqhsctdtiuiufshsitjwlwmynyo{lzo|p}pm}p}sp}ozq}ozo{p~o{pzo{p|q~r}qoo~q~r~mn~p|p~p~r}q{rsqrtwwwwzz}|fvtScZzz=O9L2Jb=jX_}MQn?Mj>a{T}nxiywgwtgxlqsRgRTgQNaK8L4DW+B-": d|aB[>KfHPhE[tSi|ayv~}vc{cpoOgQPiR_x]QoPKjK@^B@]A9V:>]=PpOTsTCcEIdIIbDG`A\vSproq}|k|iwft`srWlCNi;p_umyqqcjy{Y|^iT@U2L`Cff[cdXabVddXffZddXccXcdWbdVacVadWef]dd[aaZba\ca\cb^cb]cb\cc[ddYccYcbYdeYceYcd\cd^be^be``c\_bZbe]_c]`b\`b^ac`_a_]_\`b^^`[_a\^`[]_Z\^Y`a\^_Z`a\^_\_a^_a\`a]cd^aa]^^]^`]`a^bc]ab\de_ef`de_de^hibde_ef`ef`fgbghbjlbknbik`ii^pkbolcllaklaoncnlbolapnbusfureqocrrfqqdnnbnocoqfqqlprlopjqsmqrlsslrtjsvlswntupsqrqqpqqnrrnpronplmoknpnmookmmlnllnkstotupsuqoqnprooqnrtttvvuxwtwtuwsuwutuustuvxzvxwrtstwwrsuoproqpssquwqwxtwvuvvuvuuuuutuuqtvnrtlpukqulqumotorwlorrrvrsvstxqswnsvosvorvorwnqvprworwtxzwz|xy}zz}}{|{~~}{|}yz~zv{{z}|w{{pt{ps~nq~prprsxt{rzmwnvmt~qw|xz}zzyxvwyxvsvxutvqtwwvu~wopwylikf]_bX[aSW_SXdQUaNR^PS_OQ]]^hllqfgoX[bQT^QS_RT`RTaUVbUVaTVbRVaTYcUYaPT]RT]SW_QV\ORZPS\PS\PS[ORYORXRU]VYbSV\Y]_``ea_ea\`d^]mc\qaefbbb^\adfeadc`}[afz`mnpusyvmqsq|pwulnlgffe_ad\_fUXbPS]NQ[MO\PQ^MOZNPZOT_MS^LT`MVaIP[CKV;CN7AL8BN7@Q3?R2>Q3@S3@S3>U3?U2>V2>V3=V1=U1?V1@W2=X1=W.S-\1=[2@\2^0?`/>^.=^1?b1?b0>a1@`1?`3?a2>a1>`2?a1>a/>`/=a0?d0@d1Ad1Bf/Ah1Cj2Bl3Aj2@j3Ak0@i/@h-?h/Ag/Ag.@e-?f+>f->g/=f0?h/>g.=f/>g,;d.=f,;d-i+j,=i.?k->j0=m1?n0?o,>l-?l/@m0@l0Bm.Al/Bl/@l0@l5Bo2@n2Am2@m2>o4@p0?o0Bp0Ap2Cq1Bp2Bq0Bp2Cq1Bq2Cq2Cq0Ao4Bq4Bq2Bq5Bq5@p5Bn5Co6Cp4Bo5Cp6Dq3An5Cp5Cp5Cp5Cp5Cp5Cq5Cr3Aq6Dp5Cp6Dp5Bp3Ep4Dq7Dr6Dq8Fs7Er8Ft8Fu6Ds5Cr7Et7Ds8Fu7Hv7Hv5Gu4Er:Hu8Fs9Ht:Hw9Gv8Fu8Fu7Eu9Gu8Er7Ft6Hv7Iv7Iv5Fr9Gt8Fs5Cq5Cp8Es8Fs8Fs9Gt9Er9Gt8Fs;Iv;Iv8Ft:Iu:Jt;Ju;Ju:It:It:ItMs=Ls=Ko>Lq>Lq=Kp=Kn?Kn?Lo@Mp>Km=Jl?Lm=Jl@Mo@Mo>Kk@Ml@MiBLkBLjBMkCNl@Ki?Jh@KhALl=Kj>Kk@Mm?Ll@Mm=Mm>Mn?No?No?Ln@No?Ln=Jl=Kn=Kn;Il:Hk=Kn;JlMv=Ku=Mv>Mt=Kp>Lq=Kn?Mp=Kn?Mp?Mp@Nq?Nq@NpBNq@Mo?Ln@Mo?LnBOqAQrAPqDPsBQrCRsBQqAPqBOsAOrCQwCQtDRuAOrDRuBPsCQtBRsBQqFTqERoHRpITrITrISqIQpHPoIRnHPnJSoIRrIRqGQpHPpIQpIQpGOnFQoHSqHSqHSqKSsJSrKTsMVuLTtIRrJSrLTsKTsKTsHUuHWsHVsLWrJUpKVrJUqKVqLWuMXtNVsMWrLWrMXsPYtR\xP\vS_yS`yT`yT\zT\yV\yV]zU]zU\yU]yV]zU\yT[yT[xV]vUavX_vX`wYawX`wW]xX_{V^yT[wT[wV]yS^yU_xS]wV^uW_vV_vW`v[`y[ay\bz`f~_e{bg~cl`h}bjbi|cj}krqxy{yv~w~rynuovmtqvqvmvkwjvgsgsfqapbq_n^lak_k^jZg~YiXh~Ud|Wc}Wb}V`}S^zR]yR]yP[wP[wKVrKVrLWsJUuKVtJUpHSpHSqGRpFQoCRoBOpAQoCOpANnANn@On>Mp>Mn@NqAOr?PrANrAOr>LoANrAPq=MnBQrAPq?NoAQq@NqCQtBPsARtBRrAOr?MpAOr>Lo?Mp@Nr=Lo=Km?Ko>KnJl=Jl>Km?Ln=LmKoLo=Ko>Lp>Lo=Kn>Lo?No=Mn?No>MnMo=LnQ;:L68K59P93L5,D.4M1jiKdM6O:$>)6 2HT:>X9tmWiNztnn{}~zyiobfmnfjgo{~v{VpSLhH^zPl^Ok>tiW/M"TqQ9V>>W:?Z:9T05S*Jg;5R$@\37T+wP3>P3>R2=Q1?S2>U3>W1>U0?V0?W1?Y-:U-;U.V0>X1?Y1?Y0>Y1?Y2@[3A[6D^4B]2@[2@[1?Z1?Z0=[2=[0;Z0<[1<\1<\1<\/:Z.;[-:\-:\0<^/<^/;^/<^-<^.<^-=^-<^-=].<^-;^/>`/>a/<`0>a.<_.<_0>a1?b0?`1?`0=`1=`0=`.=_/=a/>c.?c/?c1Be/Ah/Ag.@f-?e/?f2Ah.@g.@i.@i/Ai,>d+=c,?d.@g0?h/>g1@i0?h0?h0?h-g.=f.=g+:d-h+>i);f*;g,=i,=i->j-=j0=m0>m/=j0>k/>k.@k/Am1Bn.Al-@k.?j1Cm0?j2@l1@m1?l3@m4Am1?l1Bn/@l1Bm6Cp2?l1Cr1Cq1Cp3Dr5Bq2@o3Ap5Cr3Dq4Aq5Br6Co4An6Cp3An5Cp3An4Bo5Cp6Dq5Cp5Cp3An5Cq6Ds4Br6Dp6Dq7Er3Bo3Dp4Fq7Ep5Cq7Er5Cp6Ds8Fu7Et7Et8Fu9Gv7Gv6Gu6Gu5Ft5Ft8It9Fs9Gt9Fv9Gv8Fu8Fu6Ds8Ft7Er8Fs9Gv9Fu9Gu9Fs9Gt7Fs:Is:Iu9Hs8Ft;Iv;Iv;Hu=Iw9Gt:IvMv=Lu=Ls=Ls>Lp>Lq=KqKn=Jm=Jl@Mo?Ln@Mo?Ln@Mo>KkANm>Mi@Mk@Mk@MjCMjALiAKj?Lk@Mm?Ll>Kk?LlCNn>Kk=Kl?No?Mn?Ln?KmALn>Km>Km=KoLoMv>Mv=Ms=Kp?Mr>Ln@Nq>Lo=Kn>Lo?Mp?Mp?Mp@Lo>Kn@MnAOqANp@MoAOqBQrCRrCRsCQr?PqARsBRtAQsDQuCQtBPsBPsBPsCQtAOrCRrBPrDPoDPpDRrESoISqHSpGRpHRpKSrIRqJSrKTsJRrIQpHSqHRqFQoGRpFQoFPnGQoGRpJUsKUtLUtLTtKWtJTsFQoGRpIRqJRqIUuIUrIUsKVqJUqKVrJUqLWsJUqMWtLVrMVqNXsPYtLYsO\vP[tP\wQ]wR^xU`zT^zU[ySZwT[vU\yU\yT[xT[yU\xT[vW_yU^tW_vX`wV^uV_uX^yW^xUZxV^zSZvS[vS[wS^vT_xW_uW_vW^vV\tZ`x\bz]bz^d|_g~_g~aj~ch~ciahzfmqxpwv}}|{|w~pxnulslrirmtkvhthteqgscrbq_n_n`l^j^j^j\g[gYe}XdYeUb{S]zS^{S^zQ\xR]yP[wMXtMXtJUqKVuJUqGRoGRqISqGRqDSpERsFRrCPpBOoAOo@Oq@Op>MnAOrAOr>Op?MpAOr@NqBPr@OpBQrAPqAQr@OpDQtBOr?NqBOrBQsDRtESvDRu@NqAOr@NqAOr?Mp?MqAMpBOp?Lo>Kn=Jl=JmKm>JlMn>MnLo?Mp@Nq@Nq@Mq>Nn@Mp@Op@Op?Nm=Lk?Om@MoCPrCPrCPrCOqBOrCPpCPpCPqDRrHSoFQoFQoHPmGPmGPoFRoFRpHSoHSpHSoITsHSqITrJUsKVtKVtNXwIYsKYtLZuM[vM[vN]xQ]wQ]wR^xQ]wT`zXd~Vb|T`zWa~Wa}Wa}XcWb~Wb~Wb~Xb~Ra|Tb}VeSb|U_|U`}T_{T`|T_{T_{T_{Va}U`|U`|Wb~ZeUd}[fZgYcZdZe[fYiZh[i[i`n^l^l]k_mbp^mblbnbmcqbpaobpdresaodresdrdrhtiujvjvjvkwiwhvgreqipfnjrlsjvmxmzo{o{p|rrn{uvxtqrttvvxy|{|{~wjvpJ[N7J7-?*+H\DUiJH^7rbm`;Z0XyH0MHc8Qk8O7K\J5H4@R?0E37O(,B*3I1+F'8Q3E\@PdHSgMevawuh|hzwj}so|on~g{wozluh~pt|klvvrsdceca}_}sKbL^v[MgIPmHrhj^}s{hh}nvgyk_t[cz]WpQWsR{hrXHl+Pq*`pO4)D 'Ab~MbJoP~\yYrQg~IrNkkGpnDyNh~EsPsOhaj_fsu\wrW{oW{hD]1o^q^lWhRwdraylkdkiYljZgeUgfWghZefXdeWegYbgXadWcdZbb[`a\`a\``[bc^`aZ`a[^`YabYbc[abXadX_aXab\cf_bd`ac]`b]ab[`aY_aY_a\^_Z]`[^`]]_\_a^[]Z]_\]_\[][\^Z[\Y[\W]^X]^X__Y^_Z]^Y\]X^_Z_`[_a[`b^ac^ab\efabc^cd_fgbefaegbcf_gh_ef^fg_fg`gg_ji_jj`jjaiiakjdllhghbjkflkgllfllann`nnbpperrirrissksslrsjtukrrkrqnrqmpqlrsirrfrtfqtgpqjpqlqrmprlprlpqkooklpimpjormmpkpsloqkrtnuunxyqz{txzrxzr{|vxztz{uzyuxxtxxtxxtwwuttoxxsvuoxxrxys{|sz{rzzqz{qzzq{zr|}vz~wx|tz|wy|xxzwuwwrsvstxtuztuxtvxtvwtwvvxwtvusvvsxytwxwv{vx|w|x|{}~~}~~|~{~}{~}wzys{|v}x{yyzusuw{y~{yzww{vt|vmwzpynvtzszrxuxqu}prvvww~zu|~zuut}xuyzusqtttpqqrrqvtp}xr{pqqp}omoecgf_bgXZ`QT\OS]LP[PT_OR^ST_SW_SW_VXbSVbTWbUXdUVcRTaOQ^PR_QSaRTaSUbPS_QS_RT`PS^PT^QUaPT_OS^NR\PS\PS[SV^SW`TX`XZbZZa^\bb``liexqkymt~{l~afqopuhdjkg~ekokjhyf}msym~tkvoypjrkbpi_qjatmeqjemjfhjhcde\]`[\`\_d\`jW[dPS[OR[PQ^INYNR^SXdRVaKOZBGT>FQ;CP8?P5?N4=L3?P2=O3?Q3?S1>U/?V.=U.X2A[1>Y0>X/=W1?Z1?Z5C_4B`5C_1?Z1?Z2@[0>Y.=Y.;X2=[0;Z1<\1<\1<\1<\-9Z-;]-:],:\,<\-;\-;\,<]-<]-<\,;\-<],;],:].<_.<^.<^.<_/=_/=a.a0>`0>a/=`-;^.<_.b0@e-=e/Bg.@f.@f-?e,?d0Ah,>d.@f/Ag+?d+=c/@f.e0?f/>h1@i/>g/>g1@i/>g/>g-j/=k/=k0>k0>k.k1?l->j-?k,>j->i3Bm2Al3Bn0>k0?l3@m1?l0>l1Cn1Bn1Am2@m2@m5Cp2Ap1?n3@o3Ap3Ap3Ap4Bq2Cq2Cq5Cr4Bo5Cp5Cp3An4Bo3An5Cp4Bo4Bo5Cp4Bo5Cp3Ao6Ds6Dq5Cp4Bo6Dp5Cp5Gs3Dp6Ep5Do7Er6Dq8Fs7Es7Eu8Fu7Eu8Ev6Hu5Ft5Ft5Ft7Hv5Fr7Gs7Gu7Gv9Fu8Fu:Hw9Gv8Fv8Er7Fs9Gv8Fv9Gu8Fs:Hu9Fs9Is:It:It8Gr8Gs9Ht9Is9Hs:Gt;Iu=JvLp=Kp>Lo=Lo=Jm=Jm=Jm>Kn?Lo@ImAKn=Jl@Mo@Mo@Mn>Kk>Kk>Mi?Nj@Nj=Lh@Lm@Mm?Kl>Kk=Jj=Jj>Kk@Ml@Kk>Kl=Jm>Km=Jl>Km>Km?Il>Km?Km;Jk;Im=KnMuLq=Kp?Mo=Kn?Mp>Lo?Mp>Lo>Lo>Lo@Mp?Lp@MoDMp@NoANp?Ln@PpCPtCRs@Pq=Mn@PpBRtAQs@Oq?OqCOrBPsCQtBPsCQsDStAPqBQoDSrEQrERrFSsERrISpHSrHQpHQpIRqKSrHSrHTqHSqISqHRqHSpDSoDSoFSpITrHSqISrJUsJTrJUsJUsHSqHSqHPpKTsIUuKVtJUsJUqJUqKVrJTqJVrJXsLVrKWsMYrMZtO[tN[tO[vNZqO[tP\vQ\wR[vS^yU[wSZvRYuRYvRYvU\yU[wX\yT[tW]uV^uX`wX`wV^uY^vV\vW]wU[uUYvRZvRYuT[wSZvV]vV]wV^tU[sW]uY_xY_w[ayZby\e|[czaf{_e{ag}dk|krszpwlsvy{xxu}qvmtkriripfrisfsgrhtfrbrbrapbm`l`l^j_k]i\h\hZf[gWc}Vb}Ua{T_{R]yQ\xNYuNYuNYuLWsKVqLWsJUqHSoFQpGRpHSqDRoERsDQrCOo?Pn@OpAPq@Pq?Lp@Nq?PqAOr@Nq@Nq@MqAQqAPqAPqCOqDQsDQuERuBOrBPsDRuCQtESyESuESvCQtCQtBPsAOrAOrBNpANpCPrBOpBOsAMp=Lo?Nq=Ln>MnIk=Jl?Ln@Mo>Jm;Kl>Mn>Mn>Mo>Lo@Nq@Nq@NqAOrAPqBRsBNpAPqBQr@OmBQpCOoDQtDQsDQtDQsERtCPpCPpCPpDQqEQqISqGRpFQoFQoHQpIRqGPoJRrGRnITpHSoHSpHSqITrITrJUsJUsLWuNXsL[vKYtM\wM\wQZwR^wT`yQ]wR^xT`zWc}Vb|Wc}XbXdWb~XcXcYdYdXcZeYdYdXcYdXcXcXcXcYdWa~Wb~YcXcYcYd[g[gZf[g[g]g`jbl]l\j^l`oao`naodscreofqepgrdsesguguguiwhviwkym{iwlxnznzmyq}q|p{o{qyktmvlsnumvlululzo|n{w{urkyx[nizwy{~jtpJWL?Q@1D/5L7SiQ>Q6/C%NgCFa<1$C!tXuKi]:R*nZXrY7z}[zNa}PQpCnd{|XjXbsceygii|Ni@Lg;PiAcZTz@hJba}KZ[ŁzStN^\k=]zQfwAbkxpItJtIxRЙfxD`qCoew{_~ʛ`n?X2Ic<\wQKe>WsMLgDHgCPmP:Z;geXgdWeeWefWdcVdeXbdWbeWafW`dVacZ`d]ab^`b]^`[`b\ab[ab\`a[bc]ab\abYabYbd]_`\_`\_a]]`Z`a[^_Ycc]^_Y^_Z]]Z]][^`]^`]]_\Z\X\^[\]Z[\Y[[YZZX\\Y\\W]_Yac\_`[^_Zbc^`a\bc]bd_`b]`b]bc^bc^bc^bc^de`cd_`b]egabc\de\bc[de\ff\hh^kkakkbkk`jjaklejkfjlejkcoognncppdnnbpperrhppgqqjqqiqogpneppionlrppqrmsskuuhstesthttjuumutmttjqrjttlsslqslrsltunstmtvmsulvvkwwmwwnz{q{}qyzn{{qz{rzzqwwowxsxztxzuuwrvxsvwrzzr{{s}pr}{o}p~}p~r}p}|r|~t{}s|~t}~x{{xxxwwxytuxvw|wxyxxyyyw}~z}~z|~z}~{}~y{x~z}{}~~z|~z{~~~|}|}}~~{||{~~}}}|||}}}|||{{y||z}}}~y{}wxzuyzuxyt|{v~|x{|~vtx|~{wywxtw}vr}~u~u}szsysx~qu|vxz~~|xqux}xuu}u}zvzywvurtuqtvrpprmlshluimwhkrjkqompolmqkhxrvnrpggih^aeW[cRV]PU[QU^PS_SWbPT_RUaSUaTVaXZfUZeTXcTXdPT`NQ^QRaRS`QS`STbRTbRTaQS_TUaTVbSWbRVbRVbOS_OR]OQ]SU^RU^TWaY[d\[c]\c_^`edczwswpjshrxpzeucnrldwYwlPvlSr\vbv^coppr{ivqdtqkvtppkiojkoikupopigqictkfrietmjqkiqkjomkpnoooqhjn`enVZdKOZLQ[NR]HLXJNZJNZJNZHLXDHS@ER@GU9AO6>N7@O5?O3V,:V.X->X0@Y0=X-=U+;R0>Y0>X3A\5C^2@\2@\2@\0>Y/=X.a/>c/>e,?d.@f,>d-?e.c->d,>d-?e.@f.=d.=d0?f0?g.=f.=f0?h-j0=k1>k/=j/=j/=j0>k/=j/=j/@k2@m0?i2Al2Al2@l2@m2@m3@m1?l0@l.?k1Bn2Bo1>k3@m2@m3An0>m/=m3Ap3Ap2@o2Ao0Ao6Cr5Cq3An4Bo4Bo4Bo2@m1?l3An6Cq5Bo4Ao5Bo4Ao6Dq6Ds7Er5Cp4Bo6Dq4Cp3Eq5Fr7Fp6Eq6Dr7Er6Dq8Ft8Fv:Hv7Er7Er5Ft5Ft5Ft6Gu5Ft6Gr6Ht5Gr6Ht9Fv9Gv9Gv6Ds8Gt:Gs9Fs8Fu8Fu:Hv7Er;Gu9Gs8Gr8Gr9Hs8Gr9Hs:It9Hs:Ht:GsKn=Jm=Jn?LoBLo@JmLk>Kl>Km?Lm>Kl>Km@Ii=Jj>Km=Jl=JlJkAJmLp>Lq@Ns=Kp>Lq?Mo?Mp?Mp>Lo?Mp@Ln@Mo?Lo?LoANqBOr@MpAOrBPsAPs>Np?Op?OqAQsBRt?OqAPs@PrCSuBOsBPsCQtDSsAPrCRsBQrDRqDQqERrERrFRsGTsIQpIQqIRrGSqHSqHSqHSrETpGUrETpDRpDRoGTqHSqGRpFRoFQoGRpITrITsITrGRpIQqHRqJUvLWtITsGRnKVrJUqKVrKVqIWrIVrLVrNYuMZsNXuNYuLWsLXsNZtNZtNZtS]xP[vTZvPWsRYtSZwQXuU\ySZvW\uV\vV\tW`vXawU]tW]uW]tV\vSYsSYrRWuRYuT[wT[wTZwU[uV\wU\uW]tW]uX^uX^vZ`xYcyYaw\c{[cw\ey^cycj|ipnujqgnippwrysyqvlqkqkqkrdqgrfreqeqeqdpdpdpcoco`l^j^j^j^jZf~Zf~Ye}Xd|Wc~Wc}Xd}U`|T_{T_{P[wOZvOZvNYuLWsLWsMXtLWsJUuITrITsETqDSoDRpBOpCPrETsBQsAPqBQr?NpBPsAOrAOsAPpBRsAPpAPqAPqCPrERtERtFSvDPsEQtBPtESvDRuCQtFTwCQtESvDRvBQtCQuBQrCOqDQsDQsCPqCQtBPs@NqAQq?No@Op@Op@OpKm>Km>Km?Lo?Ko=Km=Lm>Mn>Nn?No?Mp@Nq@Nr@OpAQqBOsBRqDStDSuETrDPqCPsERtERqERsGTtGTtDQqCPpDQqERsGQnGRpGSpHSqGSpHRqGPoIRrHSoHSoITpGRmHSrITrJUsJUsMUtJWuKVrMYuJZuNXtPZvQ\xP[wR\yQ^wT`zUa{Ua{Wc}U`zYcWb~YdZeYd[f[f\g[fXcXcYdYcYf[hZf]i\h\g\h^j_k\h\h\h\h\h\h^kamamam`kam_kbocoeqepfqfqgrgrdohsitfthvkykyjxkylzn{lzm{o{q}o{p|s~r~u}u}ywu}y~yv~t{qxlyr}o|wp}]kk\kecrerpk~hvxx}uQ]TAOCBVC@S?8Q;>T<6M1MdFJaD)B%%>%4P8OmHQkKE^B@W6AY3VpCiMzXhuoQcSMaK;Q8;S5C^=:T6=X<:R9';)+?,+@.(>/0$$8-':3,$/E;+>7'$*?<0)'7/!1(0&3',"8+ 7)(McQ6G73F3D\aaWaaVabWaaU`aVacWacWbdX`dV^aV_d\cd_`b]`b]^`[]_Z]^Yab]`a]_`[ab\ab[^_V\]W]_Z\^Y\^Y]_[^_Y^`Y[^W[]W\]W]^YZ\Y[]Z\^[[\YZ\[\_]V[X]^]\^][[[ZYX_a^`a\ab]bc]de_bc^bc_bc^ac^bd__a\`a[`a[`a[_`Z`a[ac^`b]bc_ac]cd^cd^fg^ih^jj`jj`jj`kk_llamndlnfnnfoofqqgrrgoocnnbppeppgooenmdnndpofpmepqjpokopkrsnqrjqqfsrfuugwujuvkuwituhvviyvlwwmxyoxyowwnxxnxxmzym{ym{ym{ym|zm}p}o|o}|o|znyynyypz|rwzry{sy{txzp~|r~qqoorstt~qss~r}}rz{swxsxxwxxy|||||yz~z}~}~}}~{|zz~|y{~{|||||{|||}}}~~}{{z{{}zzzzzzzy{yy|yyz{{{~~{|||z|ywyvxzwz{x}|{~}}~}|zyx}xqz}qxpx~u|tx}{~{xwwvxyy|yyztuwoosjmphmnejlffofis`dq]cq\bn_bk`alackkjouotjfi``d]]c^]f[\eUY_U[aRWaPX`PVaNR]PS_OQ^SUbTVcTWcQVbNT_OVaPWaOS^OR_QTaQUaRT`SUaTUaSVbSXcRTdQUcQUaPT_QTaRS^TT_XX`[[a]^da`ebb`mjdsqh}zvwtozrk}ti~uh{shwpe}ukwiteygybsZxoTtiU}r`zfhionnxsmlifhk_bg]]d^^db_ghfljfingirllojgkeekeilghqmowuxkhjXZ^RWcNR\HLWNQ]LO\HLXHKVEITGJUGKVDIUCIWCIW=CQU1=U/?V/>V0@X0@W0?V2>V3?X3?W3@Y1@[3A[1?Z0>Z0>Y-;V+9T,:U+9V+9V,:V1e+=c->d-i+j0=j0>k/=j/=j0>k0>k0>k1@j4Am2Bl3Bm1?l/=j1?l1?l1>l/Am/@l0Al1>l1>k0>k0>k1?l1?m0>m0>m2@o0>m0>m2@o3Ao4Bo3An2@m3An3An1Am0Bn1Co1Co1An3Ep2Bo2Bn4An4Bo4Bp6Dq5Do4Co3Dp4Eq7Fq7Fq7Fq6Dq7Er8Fs7Es9Fu6Eq4Er5Ft6Gu5Ft5Ft7Hv7Gt8Gt9Gs9Gu8Fv9Gv8Ft8Fr7Fs:Fs9Gt8Fv9Gv;Iw8Fr9Gt8Er5Fp7Fp6Ep8Gr8Gr7Fq9Hs:Hs:Gs:Fs:Gs:Gs:Fr9FrJw9Gu9Gt9Gu;Iv;JtKmKm?Hk@JmAKn?Il>Ln>Km@Mo>KmANm@Ml?Kk>Kk?Ll?Lo>Kn>Kl=Jm>Km>Km=Jl>Jm?Lk=Jm=Jl=Jl=Jl>Kl=JlKmLq;In=Kp=Ko;IpLq@Ns>Lo?MpKm@Mp@NqBNrANq?Mp@NqCQtBQt?Pr?Oq@PrAQs?OqAQs?OqAQsBRtAQsCQtANrCSsCRsAPqBQqCStEQtCPrDQpFSsESsGQoITqGSqIUsJUsHSqJUsJUsGUrFTqFTqESpESpHRpGRpJSrIUrHSrHSrHSnHToITsIRqIRqIUuHSqJUsJUpITpITpLWsJUqIXrJXsLVrKVrMXtLWsKVrJUqJVpKWqNZtMZsOYtOYsSYwRYvPXuRYvT[xSZxRYuSZtSZtS[rT[vV\wS\rTYpU[uRXrQWqSWuQVsRWtRZvSZvRYuT[xT[uV]wSYsTYsU[vV\wV\uX^xX`zZb{[czZcz\dx]ezaf|ciag}ag{el~jqjqiqiqhofnemfocmdpdpeqcobndpcobnbn_k_kamam_k^k[g[gYe}Ye|Ye}Wc{Vb|Va|T_|T_{R]yQ\xP[wP[wNYuOZvMXtMXtMXtLWvJUsITqISrITsESqCQnBQqBQpCRuDSt@PqAPrAQrDOrDQsBPrBQrAPqAPqCPsERtFSuGTvGTwFSvEUvEStFSwFTwDRuCQuDRvEQsDRtERtCStBRrEQtCPrEQsBQtBPsDRuCRsDStDSt?No>Mn?No?Nl=LkBOr@Mp@Mm>Kj=JiMo>Nn@Op?OoAPqAPqBQrCQtCQtCRsCRsDTuFRuFSuFSuCQqFSsESsFSsERrFSsFSsESsHSrFQoHRpGQpHSqFQoHTqGOpHSnGRnGRnITrGRpGRpJUsIUsJRqKUtLWvLWrLWsLWsKVrNYwOZuOZvOZvS^{TazUb{T`zXc~Va|Wc|Xd~XcXcZeYd[f\d]e\gXe~\gYe[g]i]i\h]i]i_k]i]i]iZf~]i]i^g_iamam`l_l_k_k`lcocpdofqepfqgrgrfqgretiwguhvhviwkzkzn}tq}rnyo{r~w}{}ww|~VefAPGm|nexdviq}rezXaY5B89H:?Q?4K6#;#)A(D[>H_C2-E/,D0.K5%A'2$;%/F-5L.UpG]xHjLlnl~frhIYFEX>JcE?Y5KfBRmKMhI@W?6N80D2/D3'=.3G<0D9)=55IA,?7/C9!4.%%# -%/&"3)-$"5*"5+. (?22#:O@ARB8R;*2C0=PAU9DX;iyZr~X|~~~uq{~zyni|nmntmby}b{~cxzqe}xYoie}kikxsgaliJaLAXH4IEU=CT9BS:CT7>R5>Q5@R6BU3AS4@W0@U0?T0?U4@W6BX9E[5AX7CY5@Y4AZ/=W/=W-;V,:U,:U-;V-;X,:W.;X/9W/:W/:Z0;[/:Z-8X-9Z-:\+;\,;]+9\*9[*:\+;]+;])9[*:\+;]+:\*:\,;],:^-;a.<`.g.=f-;d-:c-;e-;b-;a-:d-h+;g+j1>k/=j/=j0>j0=j0>j2?l1>l1>l1?l0>k0>l1?k.?k1Bn1Bm2@n3Am0?l1?l/>k0>k0>l1?n0>m0>m2@o0>m2@o1?n1?k4Bo4Bo2?l3An1Bn2Co1Bn0Am1Bn2Co1Bn2Bn5Bp6Dq5Dp5Co6Ep4Do3Dq3Eq7Fq7Fq7Eq7Er7Er6Dq7Er8Fr3Bo3Eq4Eq5Ft5Ft6Gu6Gu8Hv8Fs9Gt8Fs8Ft9Gv8Fv:Ht7Er9Fs8Ft9Gv8Fu9Gv8Fr8Er5Dq5Fr5Eq8Er8Hr8Gr9Hs7Fq7Fq:Hs9Hs9Hs8Gr:It:Iu:Jt:Ht8Fs7Er8Fs9Gt:Js;Js:Iq:Ip:Ip=Lr=MqKm@Jm?Il?IlBKoALo?Lo?Ln=Jl?Km?Lm=Ln>Ln=Lm?Ln>Km=Jl=Jl=Jl=Jl>Km=Jl?Lm=JlKnLs;Kt:Ir:Jr:Js:It:Ir;JsOm>Mm>Mo@Op?NoAPqAPqBQqCOuCPrCPtETuDStEUuFRtDQsISrIUuGRrFTtERrFSsFSsERrITtISrHVsESpCRnGQpFQnFQsFQoITrITrKVtJUsGRpITrITrKRuKTrLWuJUrKVuMXuKVtMXvNYxOZxLWrOZvNYuQ[xR^wVb|Vb|Vb}Wc|ZfUb{Wb~XcZe[c\e[g[gYfZf~Zf}[g[g[g]i]i]i]i^j]i_k]i\h^fakamamamco`lam`lamcocnalepfqfqeqgsitesftgtergviypn~hxpr~rl}r|typ|xdulEUE]q\SgPavRbxSgzV]qLftTsĴnvhCOC$4')=-,A-+B,1K.HaB(A!.E()>',D08!$A)3K62 .@,-A)%=",F"1MWq?^w=Vo6aim|`m]HZCQcGPg?VpHFa9OkCPoKA[==T?%=*/D4;OCH[O%;./#/$0(3*-C<!*%+;4$6*, ).$2G< 4*)9MADYI,A2-B0-A1&?*AWD9L91E00C,BZ>J_;Sf@^pCnPt|\kdxji~qcxs_qrcvz[oqTgfZnndwvDXVI^XK_\DYT_tlFcRa}rIbZWqjLf__umE^QH`QSmRjhToNNiDNiE\uWYpXSlVTmWIdJVnOPjJ]yUg]a~T]vNWqLQkFTnGk]iVp`fUxdzeoV[yD^HcPbPCd24U+RsJ&H)I!7W1_}ZIgFA[PkDa|TTqA]~KX{?lRaLiUMq=Io9ofNdNHi7/M(?Y=8R6DQ>DS>EV@HX@FY=DV;BT@FXW,;V.b-;c-i+j,>j.>k.l1=j1>k0=j1>k1>k1?l1?l0>k1?k1?k/@l/Am/Am2@m0>k1?l0>k1?l1>k0>k1?l0>n1?o1@o1=m1?n0>m3An3An3@m1>k2?m1Bn0Am/@l0Am/@l0Bn2Co3Dp3@m5Cp3An3Cm6Ep3Cn4Fq4Eq8Gr7Fq6Dp9Ft7Er7Er7Er5Cp4Cp4Fq4Fr3Et4Et4Ft5Gu7Gv:Gs7Dq7Dq6Dr8Fu8Fr7Fr8Fs7Dr7Er;Iy:Gw7Dt8Er:Hu6Cq3Ep5Gr6Fs9Gt7Gq8Gr7Fr9Hs:It8Gr:Ju8Gr9Hs:It9Hs9Hs8Ft9Gt8Fs9Gt:Jt;Jr:Ip9Jp7Io:Ip;Lo:Ko:JlKj=Ij>IiAJo@ImBKo@JmBLo?Lo>KnANp?Ln@Mo=Mn>Mn=Lm?Kn=Km=Kl>Lm>Km=Km>Kl;Hj=IkKn;ImIr?Jq=LpLo=KnMnLo?Mp>Or@MsAOp@Nq?Pq@PrAQrBStARsARs@PqARsAQrCRuBOrBQrAPqBPqBQrBPqDPsCPrDQsDPrFOrAPnERqDQqCPmEQnHRpFQoGQpDRoDRoESpESpEOmFRpGOoHQpHSnGRnFQmGRmHSnJSpHQmMRrIStISrJTsHToGSnGRnHSoHSoJUqISpHSoGSnJRpHQmIUpGRqHSpHSqHSqJUtMUrMUqOWsQXtNWtPXuRZwT[xRYvRXtQWqQXoPWtRYrQXrQXrPWqQXrQVtTYvTYvSWuRZuRXuQXtQXtQXsOVpPWpOVoQXtSXuTYvTYvVZxV[wT_uY`xZbx[cwYbwZayZax\cy\dw\ew]e{]h~`i`l]j_k^j\i]i]j\m_j_k_kamam_k_m~^l~^j_l^jak`i_h^k\iZf~[gZf~Xe~Vc|Ua{T_|T_zR]yS^zR]yQ\xOZvQ\wOZuOZxOZxNYwNXvNXsMXsIXuIWtHWsGTuFSsFSsERrFTsHVvFStGTtERrDSrCRpCSrCRqERtFSuHTvEUuGVwHXxEUvFVvGUwGWwITwHUwERtERtDTuDTtETuFRuCQuBPtBPsBRtCRrAPqBQrCRsBQrBQrBQrAPqBOqBOnANnBOo@Mm?Mm@Mm@Ml>Om@Om>No?NoBQrAPq@OoBQr@QuCSxBStBSuETsCRsEQsDQtHSrFPpFPpGTtERrFRsFRsERsHRrDToHWsDSoETpETpGQoFRrFQnFQpFQoITrGRoITqHSqHSpMVxJStIUrJUsJUsKWtJUsMYvLWuOZxOZxNYuOZvOZvO\uNZtO[sR^uVb|Ub|U`{V`~Wb~ZeZeZcWc}[gYe|Zf~Yf~[g~Ye\h\h]i[g\h\h^j\i]i^j`i`j`l`lamam_l^j^j_kamcmepbndofqererhtdudscsgugvn}m|dsev|vsozm}r~v}hssS`V>OBL_LBU@RkP_wVTmIOe>`vN>Q+ixU}mqyiZfX7F;=O>4J79P=6N6:V5TnJ_yWD[>/E.,1L3jm/  +6H5 3kh>Y6;W+So=k@vP~]sRz[kpwwx|}qdp]VdOPcENe@VmAMf;Je:C_4:X3D`C(@,5#0$^tDfHek~Rq^tfg|_ph{rjiWn_Kb]J``CVTSh^Sk_VoaYrdLaUPgY@XI?WGlsTrXek;UE`zn^tkBXOD]PDZK[uZrpA\;e`e_`yZ_w_H`J\xYf`_zXUqJk]Da6WtG[yJLh:`zP6Q(,F{mjWxenYs^oZBa*+J6X$Hj<8-O-4S-:Z3yn_PFh7Ml?TsGPlB!=SnCʗjg}P^uI[vLHb8D`1;[)QuDPBGU?EU@FVEX9ET9EV9GV8GX:HX6CT8BV:CZ5AX6@W4@W1=U2>W2`,<^+;^,:a-=f-;d.;e-:d/=g-:d-:d.;e.:e.;e.:c-:c.;f-:e-9f-;f+;f,;f+:e,;e,;f,;f*9e)7e,:g*7e)8g+9e*9f&7c'7d)7d)7d)7d)9e*8e*7d+8e,9f+8e+9f*8e-;h)7d,:g*8e*8f*8h-;j+j(;i,=h+k,>l,m.k1>k1=j2@m/=j0>k/=k0>l1>m1?l2@l2@m2@m0>k1?l.?k/?l/@k2?m0>l0>k0>l2@m.@n/@m2@m3An3An1?l3@m1?k1Al0An1Bn1Bn0Am3An4Bn3Cn4Bo4Bo3An3An4Bo7Fp6Ep6Fq8Gr7Fq8Gr8Gr6Ep7Er7Er6Dq6Bn8Fs6Cp6Dq5Cp6Dq:Hu9Fs7Er7Er9Fs8Fu8Fv8Ct9Dv8Fu6Gs5Gs5Gr7Hu5Gt4Es5Hr5Fs9Fs8Fr7Er9Gt7Er7Fq8Gr8Gp9Ht9It8Gs:Gr8Gr7Fq9Hs8Ht8Fs7Er8Fs9Gt;IuKn>Kn=JnANp>Km>Km@Mn=Kj=Ki>Lk?KmAIl?Il?Im@Jm=Gk?LoKl>Km=Jl=JkLo=Ln?Kn>Kn=Jm@Mo?Lo>Lo>Ln=LmNr?PtBPuBOtBPu?MrCQv@Ns@Qr@Pr>Oq?Np>Oq@NqAQqDPrBOqBOqDQsEQsCPrBPqBQoAPoAPoANnCRmCRnCPpCQnESpDRoEPnEPnEPnFQoGQpHSqFQpGRpFRoIRpHRpHQpLQqKRnKRoKRoJRoHQnFPmFRnCRmESnHRnGRnEPmEPmGRoHSqFQoFQoHSqEPnGRpHSqJVsKTsMVsNWtOXuOUrPWtKTqNWsOXsPUrPVrPVsOVsPWsPWtRYrRXrRYrQXrRYvRYuRYuPWrPWuPVsPTqOTqPWsRZvRZvU\xR]wT[xT[vV\xW^zY`xYazWcxWaxUawUcuVdwVduZf}[g|Yf]i[g]fZfYeXc~Ze|\h[g]i~]i^j_k\h~^j`k_j`j_k]j]i_j]g]f\hZf~XbX`}Va}U_|U`|T_{R]yT\zR^yQ\{T_}Q\zR]{Q[yQ[yL[xKZwKYvM[xLZwLZwNXvKUtKWtJVxKWwMXxLXxITuERrETsETsFTpGUrDQqFRrETvFUvGVvHVzIUwJWxIVxHUwJWyHTvERtGSvDRtEQtGTvDQsBRsBQrBQrBQsAPpAQqCRs@OpBQrBQrAPqAOpBMpANpCPoCQqFPpFQqCPp@MpBOoANmCPrCPqAPq@OpANr@NqBOrBPsCQtAOsBOs@PpDRtBRsCRqFQrERrERrFSsDRnDRoDRoHQrHSsGQrHRsHSpHSqJTuJQvIUqFQpHSqIRsHQsEPpFQqHSsJSrKTtJSrLUuKTsLUsLUxKSsKVrLWsOZvMXtOZvNYuLWsNZtO[tO[vS_yT^yV^zR`yXeVb|XeYeZg[gZf}[g~\e~\fZf~[gZf~[g[g\h\h~\i`kbl_i]k_kambnanakbk`l_k`l_lalcndogqfoemfngshtfrdrftcs_p~_qw`sqcxuv|m}q~r}z~ivsXg\\lbDVFRbP\nWcv]UkJ?U5OcBQe?o\Wh?^nMobxVbOAO<8G58J:?PA>V@3M/8T5ca@\=O4^uL]wDc>d|>\t5uQsPz^t\vbxhcqVbqXUgPDW<;P2H^:Re@Kf8Kf;E`9Ic?=U8$<&*B1$7,":.(!9+4G;3((,@8%71,B:,&*?VH*B18Q@ 7&3''>1#:-9OC"6+)!1&+8/2@77J7AQAF[KH^M?W?;R3hVjNh8`{Kyec~Rxj}qndh[oeTnN}~Zq`MeWbzj_yc~y|RkTlhZlPP`AXhMXiP_{X^wX/E/J`QAXM5PG5LE4GE6NAG`HFc?^yU?Z6W+cVvelWcjnZtD=V+tpWp}tXNh3D_/ZxGuu^Y~CQs<`R_V^SGe>C`BVH"=0@[O?UE3L@1N<-L.^~PbGx[anqUMg8D^0jT_vGz^fbiuq^fsY9D.;H6tjVjJu_}`N]*嶴֨ޫدowGʐ͔أ|u]^Y_`[^_Z^_Z^`[^aZ]`Y_b[_b[\`X]`Z^a[]`X\_X_aZ\_Y[`Z[_Y\^ZZ[V[\W\]XY[VZ\W\\Z[\Z^`\Y^XX]W[_[]_\Y\X\^Y^`\[]Z[^[[`\Y_ZY^ZZ]Z[]]Y\[W[ZX][X\[Z^]Z^]Y]\[_^^bb^`^]_\``]_`Zab]cd_bc]de_de_egaghbijfgifhifffcef`efbghbhibhhchicgibijdjjfiigihglmhjjemmdllcnmepoeooerrerreqqesufwwivtgxvhwvhtvhwui{xlzxlzxmzvnzwm}zn{ym{xk|xl}o{xk~|o|p}zl}zk}n|m~n}o}omlloprqooqnlloqusssnrt||}~{~z~|}|z{}x~~y}{xx{|}~y|}x}~y}~y}~{{{yxxvxxzyyzxzywxvwvtzyx{z||}{{~~}}~{}|}z{||~z{|z|}}~~~x}tz{u{|vz}z}}}zz{w|{w{zvyz~|quvoinnhmpsvtwts~sqrjjkhmpskotfjo`ejX^eVZcSW`QU_PT^MQ]NR^OS^OS^OS_PS^OS^OS^RVaSWbRVaQUaSVcTWdUXcQV`QXcRZcR[dOXaOWaOV`OU_PV`SWbVZeVZeUXaY\eX[b`aehfjmjjpqknmitqmztn}ks{smlk~zhrrgijdccbddhqnn{vq|qywmiiinjimxobqmfdea``aVX]RU\PO[SR`PS^NQ[MQYMQYOS]MQ\NR^NS_NR^INZHKXJKZNP_LN^JLYIMWGKVEIUAGTBHW@FU@FUAGU>DO@ER?ET?FS=CQ@FT>DU?ES@FTCIWDIYDK[DK\AH[R5BT4AU4BV3CW0?V3@Y/>X-f,>c.>b.>`/?c,j*i*=h*;g,=i->j,=h,=j+;j/=l/=k0=i0>j0=j/=i0>j/=j/=j0>k/=m.l1?k1?k2@m0>k0?l.@k.@l-?k1>l0>k1?l0>k2An3Aq3Ap3Am3An3An3An3An1@m1Cn/@l1Bn0Am1Am5Cp2@m2Am4Bo3An4Bo4Bo4Bn5Dq6Dp7Fq5Eo8Gr8Gr5Ep7Eq7Er6Dq6Dq7Cp6Cp6Bo7Et7Eq7Er7Es8Fs9Hs7Fq9Hs8Hs7Es8Es7Ev7Et8Fr8Fs9Fs7Eq8Fs8Ft7Er8Fs7Er:Hu8Fs7Dr9Hs8Gs7Fq:Ir;Ju;Ju:Gs:Gs8Hr9Hs:It8Hs8Ft8Ft9Gt9Ft9Gu;Jt;Ju:Ir;Jq8Gn:KnKmKm?Ln>Km=Jm>Kn@Im@Im?ImKnKm>Km=Jl=Jl=Jk;Kl;Il:KmLq=KpLn>Jn>Jq>Ln>LoNs=Mq=Mq=Mq=Nq=Kp?Mr?Lr?Mr>Lq?PqBRt>NpNmBNn@NmDPqDRnDRoBPmFPnGQoFQoCNlDRoGQpFPnEQoEPoFOnHQpHQpLPqJOoJRnKQoJQnGPmGOmHSnDSmDRmGQnEPlEPlFQmEPoDOmFQoEPnFQoFQoEOnFQoFQpFRpHPmJSoLUrMVvLUrKTqMVsLVpMWrMVsNVsRXuQWuQXtOVrRYrPWtQXsQXtQXtPWsPWsPWtPWtOUsMSpNUrQXuQXuQXuRYwSYvRXtSZvT[xU\vW]wT`uVavU`vUbtS`uS_tVbxXd|Wc{Xd|Wc~ZcWc}Wc}YeVe~Ye|Ye}[g~]i\h]i}\h~^jakak_h_k^j]j_h_i^kZf~Zg]d[dWcU`|T_{S^zR]yS[yQ]xR]yS^zQ[zR\{O\zO]zN\yM]zL[xL[xLZwM[xO]zN[xPYxL[yNYxMXvLWuKUuIXuEUrGUsHUrHVsHTuEUsHWuETvGVwHXyIXyIWyHVxGVwHWxGVvHWxHWyGSuGSuFRuBQrDStAPqCRr@NqBPsAOrBPsAPqAPpAPqBQrBQrBQrBNpBOoBOoANn@MmBOrDQsCPnFSuCPrDQsAQrCPtBPsAOsAPqBQrBPr@PpAPqBQrBQr@OqBQsCQsCPsERtDQsCPqCQqCQoFOmHRrDQrFSsISqITpJUtITuHSpGRpHSqGRqHRqGRqFQpEPoFQoGQoGRqHSqITsITrJUtIUrKUsKUpKVrLVrNXtNXuNXuMXtO[tP\vP\vP\vS_yS_yS^xU`zUa{Wc}Xd~[gZf[g~[d~Yc{[gZf}Zf}\h[g[g]i~]i`k`kak^j`l`l`lbncm_j^j_k^j`l_k`ldndmcndnbmcmeofrfriufsds}QaaSg`L`Wh{tcuvvqp{xz|~~itk_kdHTL#W28Q16M3'>+"6',#5("8,+?4*>1/C8!5)"6.+"(=63-%:03K@,E7(=2"7+8PC&?2 !%8.(* $3*-<1)<)-C/(=*(A+2J5+E#C\4c}KhQ_yIb|PKd=Ga;Lf?i[_xQPjALeAws?W?9Q9^v]e`siZtN_yQk–ѡ“xzkyfza^sfAXN.E?;PI;MH2KA,D,0J%RmF7P20I4(@(1L32T0WwNp]mDc%Tr2eB`:Hh'Tt3iJnLiH]}>Ur;>[)9V uYomp[z=ev_Df3\|H.M:Ϙ|[pWjoBW,f~LuWowYu@lS[zBSp8Tt;Gf2eT?]6VuMDbCQjQ0G72H<%;4'B:2LC.D:(D:/H<4R8i_@]*i^yHUl:Jf3Vp?Lg:XnDRj;tZ[m?mRoV`oJve_kSFQ@;H5tmCT8dvRx\}]hq`q`|lf^_X]^X]^Y_a\^aZ[^W]`X_a[\^X\^Y]^Y]_Z\\XZ]W\`YZ^XY^XY^XZ]W\]X\^Y\^Y\^Y\_ZZ_[[`\[`[Z_[[`\Y]Y]_\[]YZ\W[]Z\^[W[XY_ZX^ZY^ZX^Z[]Z\^[^_^^__\]][]][``^d`\`\_a^^_\__]ccacc`bb`bb^ab]de`de`egbgfdefddgddfcefcdfbdfbgidficefbhjefhchhfgiehkhjkjjkgkmgmnfmnemoglnfnpgpsgqrgqqhrshttgxvjwuisrfttivthzwkxumvuluulvwlwwluuiwvkwxkwwlxxl{zmz|m|~o}}p||o}{nr~qsnloprrpnpokoppuuvwuotv}{yy}~x{|x}|{{z|{|y|}wz}yvz~{}~y}~y|}x~z|{|xyywyxvzzx}~|~z~{|{|||{{|{}zzz{yy~yz~xyw{~wywz{}}~y}}|~y|zvvysswyy}wvyxxu~|wywsyzvvwxqrwttq||qqrp~nxwnqrnmpqkoqhkpben^ck[_fX[eSWaQUaQU`QU`RVaQU_PT_OS^PT_RVaSWbTXcSWcTXdTWbRUaMU_OV`NV`OYaNW`LU^NW`PWbPVbRVcVYdSXbVXa[]faafkimwrrwsowsl{tkyp~ui}gpxqdouqqwvgklarqi~v|p~ruuo}lnl|g}m~qzqgohcca`_`bYY]TTZUU_VUbYXfOQ^LNZMP\JNZJOZMP]OQ^MQ]LP\KNZJNZLNZLP\LP\JNZGKWDIUBHVAGT?ES@FT>DR>DR@FTAGU>DRAGU>DSAGUAGU?FTBKVBJXCKXDIYCIYBHXBHXEJZDIZBHY@JY=FW>GX=EV>DWT3?Z2>X0e/j,=i+=i*:f.;j/>m->l,=k-m0>m0>m.m/=l/=l0>m1?l1?l1@k-?k0@l/=j0>k0>k1?l1?l2@n2@o2Ap3Am2@m1?l3An4Ao1?m0Bn1Bn2Co0Am1Bm5Cp3Bo3?l3Bo4Bo2@m6Dq3An3An3An5Bp6Dq7Dr7Eq8Fs7Er4Bo6Dp6Er7Et7Ds6Ds6Ds5Cq8Fs7Eq8Fq8Gr8Gr9Hs;Ju8Hr7Fq9Gt9Gt6Dq6Dq8Fs5Cq7Er8Hr:Iu8Gs9Ft9Gt8Gt8Fr8Gr7Fq9Hq8Gq8Gr8Hr:Gs:Gs9Er7Fq7Fq6Fp8Gr7Fq9Gt:Ft:It;Jt;Js:Is:Ip:Jq;Kn:Jl:Jl:Jl;Km:JlKk@Jj@Kk?Jj?Ln=Jl>Km>Km>Km>Km?Ln;Hk=Jm>KnNo;Km>NpNr;Ko:JnNr>No=Mo=Mn?OqAQs@Nq>Lo@MpAPq@Po?OoAMoAMo?OoAPpAPp?NmCNoAOmAPpCPpCQmDSpCQnCPnEOmEPnCQnDRnDRnFQoGQqFOnFOnFOnGPpHQoLRnKQoFPmFOlEQlGQnCSoCQlGRnDOkCNjDOnDOmCNlCMkEPnCNlDOmEPnEOmENmFOnEQlFRpIRrLUtKTsKTpJUqHTpKWpKToKSqLUrMUqNXrNXrOYsPVrPWsPWrRYvOVsQYuOWsNUqJSpHQnMTqOVsQXuQXuQXuNUrPXsRYuSZvU[xV\uR\uS]wR^sQ]sS_tS_wR^vVbyVb|Vb|Ub{U`{Ua{Ua{Tc}VeWdVd}Vb{Xd~Xd|Ye}]i^j\h_k`mam^j]i^g_k]i^i[fWb~YdWb~V`|U`|U`|S^yT\zR^yU_{S]yP_yP^yM[wO]{O^|O^{N]zO^{O\yM[xN\yN\yN\zO\wRZwPZvOYvKZuL[uIXrHXrIWtGVsIWsGVvFUuETrJYzGVxJYzHWxIXyHWxJYzGVwHWwFUvIXyDRtDSuDStETuCRsESvCQtBPsAOrAOrCQtAOrCRsCRsAPp@Op?OqBNnANnBOoAOnBNqBNqERsCPrERtDPsDTtDRvBPs@NqBRrDStAPqCRsAPqAPq@OpCRrAOtAOuAOsCPsBOrDQrCPoCOpDSmDPpDQtDQqETpHQnITpJSrFUrHQoGRqJUqHSpHRoGRoHSoHSqFQoGRpITrHSoHSpGRoHSoGVsHWsHWqHWqHWrHWrKZuHXrKYrM[uOZtQ]wP\uT_yQ_yP^xVb|Xd~Vb|Wc}Wc~Yd~[d\e[g~Zf[g\h[g[g^j^j_k_k[g~bl`o_lbpalbn_k^j_k^k~_l^jamcmdpelclgmgoiqfrdohtcofu{]njHYOHZMlt|~ro|v{~wwS^UGTKEUG)3L8'D/(@++CZD4L3-E/0H24M7AX?0G,C[=3I0(>'3J->V/^yIoVr6Xv9Lh/Xs?]vKf|YUhGMcGJbDSoK[uRGayDY:>X;kbMk:hQUr5f@rJu_Rh?qK`UvVڢnU9R';S-PlG3K.;R;*A5 5-.EFW;DV>HY;CTX2>X0c1>c1?d0>c0>f.;e/>b0>c/=d1>e0>f0;g0=g.;d0>d/=c-;c.;f/j+=i-?k-?j,>i*;g.:g,;h+>l+=k+;i->l.k/=j/>j1>l4?o3@m2An3An3An1?l0Am1Bn2Co1Bn1Bn2Co3Dp6Dq1@m6Bo4An3Bo3An2@m3An4Bo4Bo6Dq5Cp6Dq6Dq6Dq5Cp5Cq6Ds5Cs5Cr6Ds6Ds7Et7Et7Er6Ep7Gr5Fq6Gq6Fq7Fq7Fq5Ep7Er7Er7Er6Dq6Dq6Dq7Eq8Gq9Hp8Hq6Er9Ft9Gt8Fs7Gq7Fq7Fo8Gp7Fq7Dp9Fr9Fr7Dp5Do8Gr9Hs9Hs8GsJjALk?Jj@Ii?JjKm?Lo=Jm>Kn=Jm=Jm=Jm;Hk=Jm=Jm;Hk;Jm:HkLr?Lq=Kp>LqMr>Nq=MrNr;Nn?Op>No>Lo=Ko=Kn>Mo>Mo@Ko>Lo>LoAQq?No?Oo?Oo@Ln?NlAPo@OkDQnBPmAOk@NkBPl@OoBNnCOpEOoEPmFNmENmENlGOnFNoFPoGPmENkFNlCNjDOlCRoDRlEOkCNjCNkCNlDOmDNmBQmDNlALjALjBNlDLlCLkEMmBMkEPnGQoIRqJSqHSqGRnGRnISpKUsKVrJUqJUqJUrIUqKVpLSnOVsPVtRYvQXtOUvOUuLSsISrJTpKSpJSpPWtOVsPWtNUrMSoOUqPVrQXtOYtNXpOYrR\tQ]tQ]uP\sT`{Q\vR_yU`zS^zQ]wP\vN\vO]wO]wP_xQ_zP^yUa{S_yUa|Vb|YeZf[g\h^j]i]i_k[g\gZfWe~YcYdVa}Tc~U_{U`|S_zT_{T_|R_zQ_zQ_zN\wO]xO]wO\xP`}N]zN]zN\yO]zN\yO]zO]zO]xP^xO]wP^xL[uM[vKYtKYtKYuKYuGUsGVtHXtGVvJYyJYxIXzJYzJYzIXyHWxHWxIXyJYzGVwFUvGVwHWxDStDTuDRuDRuCQtCQtBPsBPsBOsBPsCRsDStEStCRqBQpCPpCPpDPpAPqBRrDPsDQsERtDStETuDRwBPsDQuETuDStBQrDStETuCQuCQtCRsBQrBRr@OqBQrCPpCPpCPnDSnAPpCSrEQuCPqESpDRnESnESqESpGTrGRnGSoIToHToHToITpHSrJUsHSrJUsJUrKVqHSnISoHRrGRqJUpHSoKWsKVrOZvNYuLZuN\xN]xQZxS^zN]xP^yR`{U`zWc}Wc}Wc}Wc~Wc}Yc~ZdYeZg\h[g]iamamambnam`k`k}_l|cqbnboalbn]i`l]i^k~^l_kalemdlekfmgmjpiqktowozcquaqjScXJ]NM_P[i\s~x{~~YbYZfYZh[GSJ;K@ASDGYE1I-LdEK]C6I11C/.@+)A(;R6X2h]sexkqdxT}arn[mD[jNK\C=O39M92E1,>-,=0&9+-,)?13J;!8(/3.E/*B,6N87!+9O=&<'"7#-B.+C'9R,Mg;Qj/fIKh1a}Ka|OHa:XlN?Q5I]BF^ARjMRkJA\;C_;;W4@[8.G&1J+;T6'@''<()>/+?4%90!2+9JB+ ! )1E<7KA6IB$83+)-+%!$")83.@90E.,B.=S<:R6/H*B[8D_5@[.D]-Ph9vy_avGgzJbxJ:P!|aqWnhrYp@v\pDqkvyVg9s`\qJDW5J_COgKpo4N3&?$7WsLi"6b{R]xNu~]wQ[w4tK|LSL{MeZ_5h:jtUw^{88WEa)Rp5jF`fdCmM͜rrguRh?evN{^`U}}SX\v`L`,]nD4J"ygL_@(: ""1C0Ư ,C6$<,)F-A^=MmB@Z3Oi@E`3fW`{QLd;G`6ps_^tK\rI|j~zjIY:_mSCP64A)@P6j]WoFlWv^ةrbřu~n^`S]_S]^T[\U]^V]^X\^X[^X\^\]_]]_\_a^[^Z[`ZZa[[aZ\a[Y^XZ_YZ_Y[`Z[`Y[`YZ`ZY^[W\WY^WW\WY^[[_[]^\[^\Z\YZ\Y[]Z[]Z\^[[]Z^`]`a]^`Z__Z`a]`a^^`]_a^^`^_a\[]Yab]bc_a`_aa_aa_`a``a`_`^cbabcaaa_ffdddbddbdeccfcdfbegeegfgiiikijlhhjhhjihjihjjhkhikhijenoimnhmqgloglohnoimmhomhqploohooenodqqfrqessgttkuumvvnvwowyovymuxluxkuwjvylwylyxmzynzzny{lyyl~r|{o|p}p~o~mnptuttuwuwvvxxvvwyvtr~s}v{|u}~xy{vx{uy{w{|xyyvyxw}|x~yx{}yytzzyxxv{|yyyw{{y~~{}z{{{~~~~|{|~~~}{}}~~{~~}{z||~}{z}y{x|y|x{z|vy~x{sv~uzw{w{y{||{~}~~ttwmoslqvjnuknxjnyqt{qtvsuqtuovvqtuprsnqplwvn}|s|xpzxo}}swuqhiicghaeg]ce[_c]`eZ`cW\aW]cUZaVZdTXbRVbRVaTXcTXcSXbTXcSWcQVbSZdNU`QXbPV`RUaPVaOV`NWaNW`MV_KT]JT]JR]MT^PVaVZdZ]f^bjdejmkqwstytq|xo|wlyo|q}mrrtqklforowq}putkqqjihenlg{uryrlpjfjicbbcYY_YW_ST]QT]ST`QS_TUbTVcSTaRTaPR_NS^NR^PT`OS_MP]LP\HNZIO[IO[IO[IO[EKWFLXEJWCEVEHX@GUBHV@FVAGUAGUCIWCIWDJYDJYCIWGLYFLYFMYFMXFMWFMVDJWDJUFLZFL[FLYGL[CHYAHX>GWFWCW8?Q;CV7DW6DW4CV4CU3AU6BX4@W4@Y4@X4@X5AY1=V1=V1=V0c1>d1?e1?d0>d0>c0>c0>f1?d0>c0>c/=a0>c.i+:e.i-i.k*=k);i+=j+=k.;j.k1@l-?j/?i1@l0>k0>k/=j/=j/=k1Jl@Hl?Ik@Kj>Ih=Kj>KkKn=Jm=Jm=Jm=Jm=JmKq;Jn;In9Gl:Hm;In;In:HmLq=KpLo=Kq=Kp>Or9Jm;LoLn?Mo>LoAOq@Np?Mp@NqANq@OpAPm?Nm?Nn>Ml>MlBNoANmAPoAPnAMmBNoFPpFQqEOpFNpEPpEOmGOnFOkFPlDOkCNjFQmBQkAOjDOkCNjCNjCNnBMkCNlCMkALjBMkALjCMkDLkAIiCMlALjEPnDOmEQnEPnGRpEPlEPlESnCQlDRmFQmHSoHSoHSnGRnHRnLUrKTqMVtNVvKTsKTsKSsITrIQpIRqJSpJSpNTqMTqLSpJUoKVpKUpMWqP[uPZuOYtPYuP]wQ]wQ]xQ^wR^xQ\xS]zR]yQ]vR^xN]wN\vM[wN\wN\wP_zUa{S_yT`zT`zS`yWc|YeYe[gZf}Zf~\h\h]iWeWeYcWdWeUc~Tb}Va}T_{U`|Va}Sb}Q`{Q_zQ_zO]xP^yO]yN^{N]zO_|M]zN]zO\yO]zO]zP^zO]{N\yO]zQ_{M[xM[vN\wN\wLZuKYtKYsKYvIXvHWtIYvIXyJYyKZyKZ|IXzIXzIXyFUwGVxGVxFUvDStDStETuFUuEUuESvESvCQtESvEUuDStCRsDRtASqDSrCRqCRrCRqCSrDSuCSsCPrERtDQsDUuDStDStCRsBSuETuDStCRsBQsFUuBPtCQtFTyDTrDSrETuAPqDPrCPqDPpEQrCSpDSqIWtGUpDTkGWmJYpBPkDQlDSmMXqLXnFRhGSjIVmO[uJUpHToKUsHSoKVuMXvMXvJUsLWrJUrLWrJUqLWsLVsOYvNYuLZtM[uQ]vQ]wP\vO[uN]wP^yT_yS_yXd~Wc}Wc}YeXa}ZdZd|Zd}[eYd|\f^h`lbl_k`mbl`l|bn{dq|cq}drdqbm_k`l]jalcn`ldkck~gmfminiojpnsovnwoxxYeb@QFcqfHWKN_S_oj]gmu|xx\g^ERHCRDDQHCSHBQHI[L9O:@V>D[>;R86L5.E/';*3H60G1)B#!=VpKSnEBZ3atNzvqYdvLYn@Mg:kLvQ[d}ARi=PcF@S9I]@ReN4G1->)0A/*=)+=,'9),B1=VB+ 9$.$<'/H3F^D5*#4&2'=';R8@S4?U.AX*Ui7Ph6Qj:Le;PlBLeE@T9=N66I18M5BZ?9P4,F(=W7=U75N1*C&0H-*C*)B,@TE=QE$8,';2' "(!2&,!K_W1E;0C=0,)%'77%%//&% ./*(?TL*"4)+A4$9.8P?#:* 6'$5'&>*%;$9R6IbC>Y3D^5\xJ`{Ec|DnKċcu?cdnܰy]w[ʞ~peawdn{IoQgyLSjA{fe{N,DHd9ndTtNtp=_7WqG[sBmh{`{dze_|ChojIsSb@\x6^{6Yy3]~6lCi?X0oaPd:jX@P/ƢtZjHyiAM8BT8h|`,G$h[xmJY3q[w|vWr]_R\^R\]S\]V[\U]^Y`a[]_Z_a\_a\_a]ac`^`]^a\\b[[aZ[`ZY^XX]WY^XY^XY_YZ^XZ^XZ`ZY_X[`ZZ_YZ_Z\_\_a^\^[[]Z]`]^`]\^[]`]\_\^`\]_Z``[^`Z_`[__^`b^_a\]_Z\^Y[]W]^Z__[_`Zaa^__^__^^`^bdaaa^ccaccaddbddbffdefdegcegcegdegfgihgigfheikiikkikigjfjkikmgiidlkekkcjldkldkmfmnhmoilmgpnkmmhnnfopgoqeqsfrsgrtjutmuumwwpxxpwxnwwmyymxwkwxkzzoxylxykz|nyyk~q|p|q|p}popprruswxwzvuutstuwvvss~rvz|ty{uwyty|w{|uyzuxyuxxwyzwzytzyt|{vz{vxxv||zzzx||{}}{}}{~~{}~y~}|}z~{}}~~{|||}~~~||zzzyzzw{|||{~{{~{|}v{}tw|uxuy}ty{vy}x{vy~w{uzvzw{w|z~{}||z}}wz|}~xy~||~wxzvwxqqvknsiorhpshpvhmvgkvimsjmpststvquxqwxqxurqqlqqoopnnmlonmttsrswdildhl`fh_dg_dg^bg_afY]a[_cY_dY^dW\cWZdVZdY]iZ]hY[hY[fX\dQV^QXaRXaPXbQXbQT_QU`OU_NU_MT]JTZKT]NT^NU_OV`PVaVYbW[dZ^h\_gddkjilmlinniqphuql{tl|twtyvooosuv{}outjllg`a]accgfghfiebedbc``dX[aUX`SV`SUbPTaQTaQT`STaQSaPRaNS_NR^NR^OS_PS_PT_NR^NR^NR^KO[INZJO[EKXELXDKWEKWFJW?FUDJXBHUCIWBHWAGVEKXCIUDJVFLXHNZHOZHPYJOYLQ[IQ[JQ[GOXHNZIOZGLYCIWCIW>HW>HV>GV=FX9GU:HVEX=CW:@W9BV7BW6BY5DV2BU4BW7BY5AX8DZ6B[5AZ4@W4AW6BX3@V1=S4>U5?X3>W1=W2>X2>X0],=]+;]*;\+;\,<]+;]+;\,9\-;^-;^,:],:^.c2@e-;b0>c0>c/?b1Ac2@c/=a2@d3Ac1?c1?e1?e2@f0>a.<_0>b1?d/=c/>d/>b/>b/=b.i,;f.=h.i.k/=j/>i1@k1@k/>i1>k0>k/=j2@m2@m2?l1>l3@m1@k1@l4Ao2@l3Bm/Am1Co0Am3An3@n6Bq4Am5Bo5Bo5Bo2?l3Bo3An2@m4Bo3An4Bo5Cp2@m3Ao4Br6Ds5Cs6Cq6Cq7Dq7Er7Er7Er7Fp6Eq5Cp5Cp7Dq9Gt6Cq6Dq8Fs6Dq9Gs8Fs8Fs8Fs:Jt9Ht6Eo7Go4Ep6Gr4Ep7Hs8Gr7Fr8Go6Eo9Ep8Ep8Ep8Ep8Eo5Cm6Eo7Fo7Fp:Ir9Hr:Ir:Iq:Ir9Hp9Ho9Hn8Il:Jn9JnGj?IlKmNpLo?Mr=MqMu;Jq:Mp=Mq=Mq>Kp=Ko=Kq=Kp=Km=Jp?Mr>Lr?Ms?Ms?Mr>Lq>Lp>Nn>Mo>MnMk>Lk?Oo?No@OpAPqBQoCOpANn@MmBMmDOoDOoCOmEOmEPlCNjCMiCQlCQlAOjFPmDOkCNiCNkCNmBMlCNlBNlCNl@KiBMkAKjALj@LiALjBMkALjCOoCNlCNlFQnDOj@PjAPkAOjEPlFQmEPmFQpITpIQnJSpKTqIRoIRrJSrJTsIRqGQoHQpGPoGPmFOlGPmHQnJRpKToKUpMWrMVtMVtMXtNYuMWtO[uP]vO[tNYuOZvOZwOZvOZvN[tMYsP\uKZuM[vM\vLZuNYuO\uR^wP[wU`|U_{V`}S_yVa{Vb|Ua{Vb}Ye~Xd~ZfUd}Vc~WeWfVdTb}Sa|Xb~S^zVa}T_{Va|V`}Sb|Q`{O]xQ_zO]xP_|P^{R`}Q^|O^{Q^|R`}N]{P\~Q_{P_|O]{O^{N\yN\wO]xO]wO]wP]yM[vLZuM[wJYtJYsIXwHWuIWuGVvKZyJYxJYwFUuHXwEUsEUsFUtGVwETvEUvCRtETuDSuDTuGVwFUvFUvETuCQsAToDSpCRoCRoGStDTrDTrCStGRuFSuCStFUvHWxETuBStDStDRtETuCRsCRqDRrETvDSvESvESuESxETwFTvGUtFSsESoFTpIZnIYlJZgFW_VeiWfjSelQalPaoYgrYgrht^kwfs~[fsT_oXfuQ_pNZpNYsNYuKVrLWtMXvNYuMYuMXtMXuP\vO]vP[uLXrP\vQ^xR^xQ]wP\vR^xT`zUa{T`zUa{Wc}Xd~Vb|YeZcZd\f~[e~Yc}[e}[e\f|\g}^h_k`l_k|YbkVbgcnq^lrdszcqboboeoeodpgogoiqnus{syqtptwzz}nv]fkMYX?MFXe_HTNIVOIWT}|tzsZf]L]SASG=OA?PC:K>UfY?PB-C/,C,3M42G00E.$;&#04A]AWqR1J#:V-C]0Kb2t[kª}sp`I^5Th8h~IJc0Nj5kKpMUt3[v?Ga6E]@4K1OgL8K2'; 4';"': 4H18I60G2#=#@ZA-F.NgOE^D6O50I4'A*0E3.$;!WqROgAJc8Ne6f}KcrJCV-AV0J`/2'-%!6, 0)*#&;/4H<';3&;0'<5&43'85+)/,$51//../-/..'2'(>1"6&.C4,A2"9)'8,+=,7L7:S8?X9?Y3Lg>B_-Yu:kGZtKZjduY{b~yx`irjnkevIw]`mir{`Wn@>R#{̘oeX=`6fdwToDzchOcsGhTl>{cgRRn:\yCYvDGd1Li8`{IWu?fFqPb?ZXwQvRIa+wT\u8cB`{amQvnSjzhdsSm~ZlTZq?pzabsGcer`xQZxT[caȉglm~BXzjsPbu`tTjHX=GXX2>V1=V1=U2>W2>X/Y/=X._/>_-<]+:[-:^,;^.<_1?a1=a0=`0=`/<`2?a1>a3@c3Ad0>a0>b0>a0>a2@c2Ad2>c1?b3Ad3Ae4Bd2@b1@c1>a2@b2>a2?b0=a0=a1>a0=a0>a0>d.i/>i.>h/>i0>j/>h,>j-=j-:h,:g+9g-;h-;i.k2@m2@m1>k2?m2Am2@l0?j3Bn4Bo0>k2Al3Bm3Bn1?l1?l4Cn3@l4Am4Am3@m5Bo5An2@m3An2@m5Cp3An4Bo5Cp3An3An4Bp7Er6Dr4Do4Dq3Dp5Bo5Cp8Fr6Ep6Fp7Fq7Fr4Co6Ep6Dp6Eq8Gr6Eq8Gr7Eq6Dq8Fs6Dq6Eo8Gr7Ep5Fq6Gr3Do5Ep6Ep8Gp;Js7Fo8Fm7El7Ek8Fl6Dk8Fm7Fm7Fl6El8Gn9Hq8Gp9Hq9Hq8Gq8Gn8Gn:Jn8Hl8Il:Gm9Gl9Gl8Fj:JjKm=JlIm?Im;IlJq:Jn;Hn9Iq:Ip8Gn:Ip:Ip9Jn:Ip8Gn8Go9Ip:Jn9Im9Jm:Gm9Gm9Hl:Hm;In:Hm;Io9Gj9Hk9Hk9Ik7Gi9Hk9Hj8Ik;KmLpLq=Kp>Lq>LqLp>LqMn?No?No?NpAPm@OnAPo?MmANnBNoDNlDOmCNjCNjBMiAPkAOjCPkAMfBOiALiCNjCNjCNiDLlCLkCMl@KiALjBMkBLj@Ji@LiBMlBLmBKlBKlBOlDOmDOmBMkCOjCNjEPlEPlEPlEPnDNlFNkFOlGPmFOmIRqHQoHQpGPoFOnFOnENmFOlGOlFOlFQnGTmGSlHTnHRoITpKVrITpLWrITpLXrLVsKVsLWsMXtNYuLWsMXtNWtKWrOZvLWsJYtNXtMXtLWtN[tO[uKVsMXtQ\xN]xR`yP^xT_yS_yS_yS_yVb|Tc|Sa|Sa|Tb}Vc~VdVdTb}Uc~U`|T_{U`|Wb}U`|T`|T_{Rb|Q_zR_zP^yQ_zR`zQ_{Q_zP^zQ_yQ_zP_}Q^|P_{P`yN^yP]xO\wM[vP^xO^xO]xP^xQ_yO]wQ[xO[xK\uIZtHYrHWuIXvHWtIXuIXxKVxHVvIVvEUsFTsDSsGVuFUsGVxFUtFUsETtFUtETtDSsCRqDSpCRpCSpESpERpERsFSsEUtHTwERtGWxHWxFUvGVwCSuCStDRrCRrCRoDTnDSpGVtDSqHVwFTyFUvEUrIYrGWkIZjN]jM_gLadL`^Tg\mk[oTdw_nocxkizs[meWg`gvm[kas}xaon_nsZitR_oR_sO[uLYrOZvPYuQ[vQ[vR\wT^yR\xT]xT^yR`vO[sQZtS]wR\uS]vUbzVb{Ua{Wc}Wc}ZfXd~Xe|Zb|Yc|Yc}Zd}Zd}^h[e^i^i_j~Ziz_j{S^fMVW[faYe_`pjjxwo~etgqdogsiqksmusyu|~ty^ikOZ[KTR?JJHPRO[WEOOu}^igw|s|mvjZe\Zf^EUKTe[DVJ=RD6K;)?,SdRYo[;Q;;T<1J2,E,1F0qs5L;'?*=TZ&Ha66O*2J-2H1:P:/E+D[?`vY>V68N1%> 5L1>U<+G&IdF6T60M2&B)"?'%=(";&2M/:T5C^>PjDId:Fa3`zL;Q&Ma?T>%<&,@.3H4DZD1H//I*5N0,E(3K11/0 0% 4+4*"1+0%-@6'=1*>6 3-!0.+&!//%!"1,(6/-&*=820)''#&!2(0%#9),B2%;,&8),:0@QB'=)*B)2K.6P*YuGQn:_}Brr{RwQe}bZmDxfo~^VeFojz_T`0j]Uc3k{L|^jzI{X{jEyU}[{]1MdZLkI>`>1Q'VqIZuLkW{gQf@Qb=btNWnHQkCRmARoAGd7_}Q`zOPl?v_[wDSn;Fa(Up7rUNh3^yFLb1@[(oWt_lSJ`5q[xdyesd^qTYlQrdhWgnpx[oyaguDwYo}TwZz_}\]my{U{XrIil~_o\n|GxNms|]^zEJe6Jf;PkCKe>vizIcE%< 3J/=S9FaATjDj}\xgj|ejy_YjQH[ESkUSjP0H*tg{iHV9}hUdBT_=QY;ox[{jxi~rbdXadY`bX_`Y^_W_`XaaZ`bY^aX]`X]`Y__[[\W]_Z\^Y]_Z[]X^a\X^XZ_YY_Y\`Z]_Z]^Xab\_`[^`Z]b[]c]^c]_e_bc`cba``_aa___^_a]^`Yab\`a]__]a`^aa_ac_ac]`c]`c\ad]bc]bc]bc]bc]de`cd_bb]ehbdfddfdedcffeedajkfggcghcfjbgjcgiefhffhfikhilgijfhhfijhlnhmmglleqogrmcqpfpoeooerrjopiopknqkprmppkppjppgquhrvhrvivwowxqvvnvvlwwlxxkwxkyzly{lz|n{{m|o~o~pq}otusttsv{|{wtttusrtsqtttuusv~}q|~qz{u{{wvvtttsvutwysvyswzwz}z{~zy{u|{{}}}~~~|~}}}~~||~y|~y{z|{}~y{}z|{{}y{}yzx}y|y|x|~u||v|w|v{sy{sx{sy}sw~uxvywzux|wz~ux}tw}sx}ty~pu|puovkszkr{imtloyimvjmwjkwhivgiuejscmr`ipahpdkseksfjrfjpilsnntopssvsxwtxwvvyyjpqckochldgidegdfjghngjpoquonuomwklujmtjmtimqfinchl]bgZ_e[ag]`i]`i^aj`cmfiq\agS[aPYaRXbUXcWZeYZg\]h[`hW_fW]eY]gW[fUXcUZdRYaT[aT[aV]bX]c[^ca_dceeefgfhhiiijkiqqoronupounpxrpyxwwyvyxpsqjmicolfrokpliutqvusmmoechY[cZ^hW\bX]cTY_PU^NV]OU_NR_NR]MP[NQ\NQ_LP\JQ[JP[KQ]LR^MT`KR_MS]OR^PS`NP]QUaLP\MQ]KOZKOZIMXGMZHMZEKWFMYFLXGNZHMZMR\JPZJNYKMYLOZKOZOR^TXcUWcSVaQVaOT_NV`HN[DLX@HVCKYAH[CJ[?GZAHZ?FX>EX=DW9AS=FW:BT:CV9DW8DV8EW9FU7DU9GZ8GY9EZ7CZ7CY9BX6?V9AX5AR4@R2>R4@U4@U5@V5?Y5AY5AY5AZ4@X2?W2@Z0?X4B]/=W0?Y0?Z1?Z/@Z/=[/<].;[,:[.=\/>].=[.=]-<^/>_.=_0>`2Ac0@a/>_0=^1=^2>a6Ce3Bc3Bd2@c3@d2@c2@c2Ac3Ab2Aa4Bd4Cd3Bc4Be3Ae2@d1?b3Ad4Ad2?b0=`1>`1=_1?`2?`0=_/>`/>_.=_-;`/=b-;a.i/>j/>j0?j/=h+=h,=h/i4@l2?k2>k0>k1?l2@l.@k/@l1@l1@j3Al4Cn5An5Bn4Cn2Al3Am1@k2Al3Bn5An4Am4Am3@l4@m5Ao2@m2@m3@m3@n4Bo3Ao5Cp4Bo3An5Cp5Cq6Dq4Fp3Ep5Eq8Es6Dr6Dq7Fq6Ep8Fq9Gp6En6En6En:Fp7Dn7Fo6Gr4Eq4Eq5Gr5Gr4Fr5Fr5Gq5Gr5Gr4Ep4Ep3Dp7Fn:Ir7Fp8El9Gm8Fl6Dk7Fl8Fm7Fm6El8Gm7Gn7Gp8Gp9Ir:Ir9Hn7Fm7Em7Hk7Gk:Hm:Gm:Hm:Hm:HmKnMn>NnAOp>Mo?Pm>Pl>Nl@Mm@LlCMjCMjBLhALh@Nh?Nh@NiAOhCNhEPjCNhCNjBMiBMiCKkBJiCKkALj?Jh?Jh?NjAOkCNlENpENnCKlBKlDLjCNkDOmDOlALgDOjCNjCNiDOkEPnEPoGOlGPmHQnGPmHQpHQpGPoFOnHQpIRqHPmGOlFPlFPmEQkEQjCRlEPlITpHSoITpITpKVuJUsKVrKVsKUrKVsKVrJUqMXtMYtOWtOZvNYuOYvMXtLWtMXtLXtNZtNZtOZvNYuMXtL[uN\vN]wS_yR^yQ]wR^xS^xP_xO]yM[vO]xN_yTc~Ub}Tb}Uc~V`|Va}U_~V`~Va}Ua}T_{U`{R]zP`zQ_zRa|Q`zSa|R`{Q_zO]xQ`{P]yP^yPazN^xO`yO_xP^vP_wU_zUa{S^yS`zS^yR^xT`zS^xO^xMZwM[vL[vKZtLZxKZwHWtLZwHWtLXyIVvGVuGVvFUtETtGVvGUuETtDTsEUsCSoCRoDSpDSnBSlEUoFRnFTqETqFRtEUsGTtEUtFVuGVuGVvETsCTsFWuETrGWqEUpIZsJZsGWpGWpDSoEUrJZsJZpFWeNbdMb^kvAVIAXK:ONDBRGIaOCYF1G2:P6Ut5?Z [uE7T&Zt;RlI`@I]>AU7AU9:N39L45K1G`F1I.:R7/E13G5.B0%=)*C*6O11M,>W:8N2&;&+?.*?1/C9"6..&"7+5J>:QE9OB!5,.(*'))!")85"1+$1B7&:3!73*(,)0+3)4(#5&1#1#);-+:1-?12H5.D.2K/;U0Kf=>Z+fNlS[xAG`8azRD_4VkEQh@Nb=WmHt^wel{aThN]sXGa@udrZ|ayz\cwIiZk=nUizTfuSh{TnX|ez`wZfwHlQj|QsM[3csMcsLsXZi=bl|@wZUp9@].Gb*R9@U7wgctOctRSa7ѪQW8ŪW[HGMASXPabS``SbcU_bW^bW]cX_d\\`Y^aZ]aZ\`X^`Z\]Y\^Y]_Z]_Z__Z^^[[^ZX^X\`[^_[]_Z_a[_bX_aYad[ad]`a]`a`aabbda^a[`b\ab\^]Y`_]^^[__]``__a`]`__a``bb`b_ab]`a\de]_`Xbc[bc^ceaddbddbdfcceceebgicghcijdhichjchledgahhbjlfjkdghcgjdhjehjgkmhkkeklglmgnnhmmgmnenofpphnpfopiomhopgqqhoohqokqrmorlpslqqjttlqrjvulvvlwwmvvlwwmyxl}{n}{n}|o~}os~qsooqsvvuywwwuvsrusrssqstvtsutts|py{nzzrvxquxrwytzzw{{vy{s||w~{}z|}x~z}~{{~}y~~|~}}|}}|}|{}~}~}y|{}}x~z}~|~}~}}}z{z|x}x|x{w{~v{}w{~wz~uw}w{uyuztzuytw~twsv~svtvptyot{nsyipwipxhoxgnxgnxgnxfnxcis`fq_gpahsckuektahpcjremucireirglvjmwmoworvstxqrvnrvilrcfjcekcflaei`dkafmaelilrhjomnsllshkqjmrjnrhjphkpcfkdgndho`ei^bh_bibeladj^ciZ_dZ^g\al]al`do]bl^bm^bn_cl`dm_dn]cnX_iU^jU\gY`jZ`jZai^fjaegjkgtqlurlytovtpsqmrqnonnljlkjmkioqpvkhphhnegkdeh`_f]\b\Za[\b\]c[^dY]dZ\dUYbSW`SW_QU^SV^OT]LT^LS_HO[IP\JR]JQ\NS_HNZHQ[IP\IO[IPZOS^MQ]MS^MP[NP[QS_PS_JP]NS`LO[OP]JOZFOYIP[HNWKOYLPXNPZPS\MPWMPWLPVLQWMUZNU[OW_NV^NW`PT_NR]OS^PT_LR\GNYEMXBKUBKVBJVCJY@GW@HW>EUT6BW6BW6BX5AW4AW4AX5AW3@W5AW4@X4@X4@X5AY5@Z3AZ4>Z2<[2<[2@]0?\0?]/=].;[0=]0<]1>^1>_2?_1>_1>^0>[1@]5C`4Bb3@a4Bb2>^7De7Cf4Bd3Bc4Bd4Cc4Ca2A_4Aa5Bb6Be5Be5Bd4Ad4Ad2?b2?b2?b3@c4Ae2>c1=d1b/?b->a-;c.:e-9d-9d.;e.e0>d.e0?g0?h0?j/>i0>i-i3Bm2Bl1?l0>l1>n0>k1?l0?k3Bm2Bm0Al0Bl4Cn3Bm4Cn3Cm1@k3Bm1@j5Ak5Bl2Bm3Bm3Bm2@l3An1?l3Ao0Cn4Bm4Dn5Cn5Bp6Dq7Er5Cp8Er5Eo5Do7Ep8Gr6Do6Co9Eq7Eq4Em4Gn8Gp7Fo6En8Fo5Dm9Hp7Go6Fn7Fp6Fp8Gr7Er7Es8Fr6Ep9Hs8Gr5Ep7Fq5Dp9Gs7Gr7Dn7Fo7Fo7Eo8En7El7Dl7Dl7Do8Fp8Do8Eo8Eo:Ir7Fp7En8Hk7Gj7Hk9Ij:Hk9Gj9Gm:Hm;IlHk=Jm=Jk;Hj;Hj:Gi;IjKm:IjLo=OpMm>Ml?Kk>Mi?Mj?Mj>Li@Nj>Lh>LfBPk?Lg@NhBLhAMkALjBLk@Jh@KiBMk@Jh@KiCMnEPp@MnANn@MmANn?Ll>Ji>Ll>KkAKi@KiCNlCNkBMlBMjALkCMlFNkFNkDOkDOmGOoGPoGPoDMlDMlDMlGRpDRoCRoCRmFPmEQlEPlGRnHSoHSoJUqITpKVrJUqJUqKUrLXqKWqIUoKVrMXtMXtMXtNZvN\vM[vO\xNYsO[uPZuNZtP\vMYsMXtMXtMYuNXuQ]vP\tR^vR^xR^xO]xO]xN\wN\vJZtL\vM]wP`zSa|VdSa|Wb}Yd}Xe~Wc}Zb~U_{Vb|S`yUa|XbUa{TayTb{PazQ`{Tb}S_xRayP`yRazS`zSawS`uQatRavTavS_uS_wUayTayUayT`xR^wT`xS_vS^vS_wN\xO]yQ\xR^uN[uNZtJYsHVoJXuIXuHVvGUrIXsFUpFSoEToERpFTqESqDSqHUrDSpESnESoESoFUpHVpETpITpIUsETrHWsGVsFVrGWrFTrEUsJZsL\mQblTfmO`iN^pFXoKZsGVgDUZNcYhig~bXsRYtSPnGFc8a|GhIilgNraczQK`=M_@nbj|_bv[RhWcxlQdY]lgepr[doLVgNYmQ\pP]oQ]sOYsX_yW_wX`vT[qV\rVcqT_oZaw[byZbwX`uVavVcwVb{WcyUbxVbyUawXd|ZcZdVc{[gZd}^i^j\cxdm~]jxN\eJTY;EE9D=1?1DS@K]D]pVntfwsfu{er~gqfqirpvryyy}}s|{dlkjqmdklIRS7BD.8:BMN9)=71*G]F0G,9R7;S91J1'=&.C06J86$$;+,A6)A142M1Ki>=[,Tr@^zEPn5Ji+So.[x4VmHnLUp8:U$7P"D^6B]6WqDWrFRmC+E"6N29R;2 $%C#LiKQmLA\,D,4K4#;$%A&YwXFeF4M3E_K0H6,E33L7:Y;+I-1P4(C+-G/5L38Q4:T7'3I3(=)#8#2F2AZ?2K.D]>2K-8O9JaN#9,/#%;.*@3,@63G>*?54-)&2-$416GC"22-++#);2$6--'/,(:86IF$73 3/#3,.?4.@3.@0*<**>,/B/6H42H2>WKZ>sgarN6EBR&cu@p\Se~IYkRJ`Tq_p+l~F/A@X;GbGKhIKeFIaAZtQZtNMgBbxUTjI\pQH`=F`6Sl=Xs?hOhSHd;If@FS>FW?FX=DU>EV>FV>GW>GW?HW?HZEY;DV;BV:AS9AS8?Q9AS9@R8AS6?Q6?Q7@Q8AU9AU7@T7BT4@S4>R5?T5?S4?S6AW5BX6CX5BW5AY4@Y2>Y4@[5A]0>Y0>Z0?Z0>[0>[1@]6Da1?\6@_3>]3?\6Fb6Da7A_6B_5@_5Bb4Ab7Dd5Bb4Bb3Bb4Cb2Cb4Ba2Ba4Bb4Ab6Cc6Cd4Ad4Ad4Ac3@c4Ac3@b4Ad3@c3@c5Ae4@f0=`1>b0=_0<_.=`/=`,9]-;^,:]+9\+9\,:]+9_+9_+9`+9^-;_,;`-=a/@d/@d0@d->c.;c-;b.=c.b/=b/=c/=d/=a0>c/=b.h0?g/>g0?j.k-h-=h/>i/>i.=h/>i/>i1@k.=h0?j1@k/>g1@l2Al1@k1?l1?l1?m0>k0>l0?k3Bm0?j2Al5Ep3Bm2Al4Cn5Do1@l2Aj2@i3?i2@i4Co4Cn2Al3Bm3@n2@m3Bo3An4Cn5Dn3Bn4Er5Bo4Bo3An6Cp6Eo5Do6Ep7Ep5Bn8Eq9Er8Ep3Fn5Go7Fo5Dm9Hq5Dm8Gp7Fo7Fn7Fo8Go7Fq9Hs6Ep5Ep8Fq7Fq8Gr6Ep8Gr6Ep4Co8Gs8Gs7Fo5Dm6En7En7Cm6Dj9Dm8Ck7Eo9FpKmMm=Mn>Lk>Ml>KhKh?Mj>Li=KeAOj?Mi@MkAKj?Jh@Jh@Ji>If@Kh=Jk>Jk=Jj=Jj=Jm?Lk@Ml?Ll=Kk=Jj=Jj=JjALi@KiALj@Ki@Ki@Ki@Ki@KiCKgCMiBMhDOnEPnENmFPoENmDMlFOnDOmEPnEPnEOlDOkFQmFQmFQmHSoHSoJUqHSoIUpJUqLTrJWqKXqMYsMYsMWtJUqNYuNYuQ]yN]xO^yP^xS^xQ]wS\wO[uO[uO[uOZvNYuOYvP\vO[uP\tO[uNZtNXtL[vP^yN\wN\wN\wM]wL\vN]xO]xP^yS]zT_zS`zUa{Ua{Vc}YfVb}UazT`zVb|Xb|XcyWbxVd|VbzU`wWbwT`vTavT`uW`wR]rU_sT_qT_qT_pU_tU`sT^rU^uS^sU_tVavVatU`rV_tUavWczScxTatV`tS^sR]oQ]nQ^nMZqO[sMYsKZpJYqOZsIUnMWqJTmJUmHUlIUmHTkIUmJWnHTlHTmKWqIUoLWqKUpLWrJVrIVrHVqHVqHVqHWpIYkFXcq~l~q^oeoVhlQcfUhe]qblhh\j\ZwHhWZ{Gv]nPsSOs.]BXvD?Z/VoEH]9ufy^\pTohTfS;PCAVMH\SYichuunx~WakYetP[jQ]lS[oT\rW^uV_pTbjbnuhu{hwxdry`lvdmy^fu[htYcvVcvXexVcuWbzWcxXdyXd{Ye{Yc{Yf|Ygz]h}Yex\esdn|NXcCNVANQ=J>=,794@BAJO7=D4=?VdQMaIDWC9O<7M8:OB,@7,@<#81)<4&3+3L87M;.D.1G0-F.+C*4L5;Q<,!5) +1(&<2"8-3!(@&ZuM>Y-Da16RC](Fb&_|9h~WRr0Qn4Ur@8P)E_:-F'7P26O-B\71L%+E"@X=6N8!8(6! <#1K1C^A $ 3.!5%0E57Q= '-G05!8(%*B14L7";&8")A-'>*'?+(>)"9"(?(7N7AT<8M5?P;/B+8L4=P`oNGY/o|Xs&VcEgvRnWh=\t@Tj:?V(WqDD^4XlIRgGRcC>S2ujsiqgi~b:O3TfMj|[]nOeuVbrT_nQMa@buTyylvl*<vjohxOxnyugϬPV?NUBa`UdcYacXbd[``Ybc]_`Zab^_a\\^Y`b]``[`a[`aZ]_Y^aZ_b\^aZ`bZ`cWbd\be]_c\`b^_b\\_Y]`Z_b\\`Y\`Y\_Y]_Z^`]_a^]]\^]\^^]^^\^^[^^]__]``__b__a_ac``b^^`[_a\bc^bd^bc^efaeebbbaacadebddaddbbb_efahidghcefafhdehdefafhcegahjehjhfkhfihfhehjgikilkgklflmhlmglmgnoilmhlmilmgpphopfpojqpmqsnqsnrtnrsmqrkuvnwxowznyzoyypzzn}}q|}o~~p~rvwvsqprrppqporsqqropqqoppssr~sstutvvwz~xxzzz}|}|||{|~{~~y}yyy{|||~~{~~|~}{}|y{zyy|z}~x}~zy}y}x}w{~vz}tx{rvyqtwsv{quypsxorxru|nqxnqxnqwmrvmrxjqwgntfmvelt`gobircgreitcgrcfrdhsdhsbfqafp]ek\dk^dmdgpaeo_cncgrcgqeirehqbeobflaejaek^ek[cj^dm_dl_bkafn`embeobdlhkrcfl_biadjcfldgnbemadm^bk[_h[_g[_i]aj^bj_ck]aj[^h^al_bm^bm]al^bm]ak]ai\`iZ^iY]hZakZak\bk`di`ejhjoppt{xvzu|{y{wy|w~{}}{rrrggj`ae^_e\_fY\fWZcVYbWYbUYbUWaUZcTZeQWcOUaPS_QUaQU`QU_QVaNU_OV`NT^NT`OUaNU`OUaOUaQU`SXc\`jehqaem_cm[_fTW`RWcOS_LQ[PT]NQWQRUTTUfedpoilljdff_aa]_a^]]ec`rqjwvnpqjmomoqvjmrZ]bTY]TX_TWaSW`PT]RV_UVcRU`MQ\INXGNYHKXFIXDIUDHUDIUBHV@FT?FT>ES>GS?HV?HT@HV?GS:DR:GTDQ>ERGS?HU>ET;CRa1>a1>`1?a-;_,:]-;^-;^,:]-;^,:_+9_,:_,:`,:_,:`.=a.=`/>a2@c1>b0>d/=c.<^/=`/=_/=`/<`-;^/=b.c0>d1?d1?e/=c0=b0>a/=c/=c/=b/=c1?g0?f/>f/>h.=f.=g-i.=i0?j.=h.=h1@k0?j.=i0?j0?j/>i/>j0?j0?h1@i.=i1?k1?m2@l0>l/>i2Al1@k1@k0?j1Ak2Al1Ak1@k1@k4Cn4Co3Bk2Aj4@k3@i1@j4Cn4Do3Bm4Cm3Cn4Ao6Dp4Do1Cm2Cn0Bl1Bm7Dr4Bo5Cp5Do6Ep4Cn4Dn6Co9Fr6Co7Eq8Gr6Fn7Fo5Em:Fq5Cm6Fl5Dk7Fo7Fo7Fp7Fq7Fq7Fq7Fr7Fq6Ep7Fq6Ep6Ep8Hr6En8Gp7Fo7Fo9Hq7Fo5Dm9Fp8Dn6Ek8Em8Do9Fq8Eo8Eo8Eo8Eo:Gq9Gq:Gq:Hn:Hn8Fj9Gm:Hk;Il8Fi:Hk:Hk9Hk:GlKo=Ln=Lm=Lj:Ih:Ig;JhKi>LiAJi?Jh;Jf=Kh;Ii=Jj=Jj>Jk>Km>Km=JlKkKjIg@Ki@Ki@KiCNlALjBMkBMkFMmENmENmBKjCNkCNjCNiALhCNjCNjFQmEPlFQmHSoGRnGRnHSoHSoMWrNWrMWrKXrLXrJVpLWsMXtMXtNYvN]wL[vL[vNYuQ]wR^xS_yP\vQ]wNZuOYwP\uP\vO[uNZtMYtNZsLWtLWsK[vL[vN]wP^yO]xN[wMZvN\wO]xS]yR^zR_xP]vS_yUa{WczVbzXd{Xe{Ze{[dx\cw]ez[dx[cv_fw_ev[ex]ew\euagu]ep[cq\cs\dt]et\ct[aq[brZan\dq]ep[cm\do]eo]doZdpZftZft_hvagt_gn^em^gm^fo\fqYeqV_mUbnU\pU]pS[nSZlRZmQYkOYjMVgJWfLWiMXiMWkNWkNXnNWmQXqPXnQXrOXqMWqLXpP[uLXoTauXhfN`YusjbdxWocOhPC\@F_>wjhUWv@Ig0Y{AkOgIYv:Gg)Bd(>`*Gc7RlHAX6KbA1E)6I0))B),E/'>(>R@.>31&,#)$*%+"1 @XAC[2:S'Oi4A]#Fb(Vq5b~?a}>`AHf,WvC:S+*C"6&>'34,E)6O0;T5G_E/F06$*A16P=QgU 8$-.$#%!4* " 6)7N=- &<0.D8!4()?-(?--B5$8*$:)&8++>+7J61D08J63E2$5!,=*;K80B/+;*9J86H50E1.H0@WC6N;4M7'?'4L1(D*;R:0I0!9$1E6"6,*+#1E<6KA,%0+#74') $"& !**$%**().-+*(%-)#62"51&:4)!%7,:L?@RD1C5.A/6J60D,4L1Fb5N-FaC=U73L.D[;Jb>?V6:S3DX9=O4KbJBY?H_DYkQ`tXL_B?T:BV=L^GYkSQdK`rWhw\ZjQIX?FU?BQ::I3aoNv|eBO,`kIJZ7p]s6D#`pO\mHZnIOd=pWPhGP;EP=FP@ITBLWAJW@HX=DTJc;Gb_1>`0<`.<_-;^-<_0>b.<`-;]-<_-;^-;_.<_.<_0=`1@c3Ad3Ad2@c0>b1?b1?a2@c1?a.>_0?`/>_.;^-;^,:]/=`.<`0=a1>b0>a0>a0>a/>`/>`/=a/=a0=a/=`0?d0>d0>c.=e-f-=f/?h/>g-g0?g.=f0?k/>i.=h-g/>h0>j0?j2?m3An0?j1@k1Ak3Al2?k1=i2?k2?k1?k2Am2Ak1@i3Bk4Cl3?i3Ak2Aj3Bk5Cm4Am6Dp6Dp5Do7Fq5Do0Bm1Bm1Bm1Bm6Dp3Cm5Do4Cn4Dn5Do6Bo5Bo7Dq6Co6Cp4Cn5Em5Dn7Eo7Dm7El6Dk7Em6Fo8Gp6Eq7Fq7Fq8Gq8Gp8Gp8Gr5Do7Fq5Do7Fo6En7Fo8Gp8Gr9Is9Gr7Cm7Dn7Do8Fl9Gn8Gn8Fn7Em8Ep7Do7Dn9Fq9Fp:Gq8Eo8Fm8Fj:Hm8Fi8Fi9Gj;Il9Gk:Hk;Jj;Gj;Hj:GiJk=Jj:Gh:Ki7Gf8Gi:Jj:Jk:IiLi=KhIg>Ig>Ig>Hf?IgBLj?IhBLjAMkEOmEMlBKjCKjBMkBMhALhCNjCNjBMiCNjDOkFQmFQmEPlEPlEPlGRnKUoKUpLUpKXrJVpIUoLWsMXtNXuNYuNYuOZvP[wP\wQ^wR^xO[uNZtO[uP\vO[uO[uMYsMYsNZuMYtMYsLWtMWtMXtMXtOZvM]wO^yO]xM[vO^xQ[xP\xU]zQYvOWuP]wS_wXbyXcy[g{^i}`k~bi|cj|dl}fmck{ho{knxgmznqz{~z{|pqtqrxknxnrzpszorwtswuvyuvy}~uzxuuutwtnqqqvzmrt}{psvlptlrxov|qxlq{jmzim|djw]es\crW]jYbnXanX_oY`qY_qX_r\bu]dwZ`sY_uY^tT\qT\qQ[nS^pQ]i_oiXjZh|_iYVpF^wN`ySl_Jd=A[5?Y3Gb9He86T"Uv?jSW|9Sr4gL;Z$iWo`'BKdE1J,]sXBX;L_GK^Icvcg{gcu_kfVoJm`f\m]wjyqcpdepn]kjZjaWlWa{Xm`az[QjNjhTd\Wfdguy[huZix[e|VazVa{YdxZey\gvYgrXdm~bppS_[coj9HCK[T1F65J7@V?:O4BY:h[YtFUr?c{ORjDd{_f{jjztyaqpjzx]tfPgW1F4.C16L:%:+5F:+8/CQIFQLDPN4?A08>7@E-49,18/9:9CD4?:4C6;L9NcL9P;2H4 7'**>7%:1-D79PC%<,)@40J6-B/+@,,C.3K27P<9Q;+B0.B3('1H-5N8-F02J1,D,'<(-.$#6-0+*$#50!53!0/)+ (#"%"%&(#&$&&')& 0*$!1-$75,%/)2(!6*+ 5(-?02B03I03K->Y1=\*Pl6cDVs;^yEUk=r[oK{lYmNpVyQi-qFh:zKo@f7q>_~0Vu(Sm&a{5rGXs,Vr+Xu"L_wEQd]Nf3Mc>MbE:Q9F^GA[AGdCBa>^zVNjAOlDJf=SmEBa:Ge>WuOLkG>X7C_?MfHA[=AW;?W:TgMFZBCW>UkVVlW8M;H^FG^CE]@9K0?S8-B&ViR6D-TdKcsXarW1?,ESBFTBHVDf|RYnG|hYLd@Ha@ax]vfon}`zAN2jy\VeIguYdp^tpZhVES=j}d@T9DXFW=EXJ]Ja=J`=I]^/;^0=_1>a/<`1>a1>`/<^/<^.<_0>_1=`0=`3@c5Bd3Ab2Bd1?b1?b1?b2@c1?b0>b0?a1@b2Bc2Ac/>`0@a0@a2@a3Bc0?a3?b2?a2?a0?^0?`1@`/>`1@a2?b1?b1?b2@e1@d/=b1=e1=d/>`0>b0>d/=b.e-g.=f0?h0?h0@j0@k2Bm1Al2Al0?j1Al3Am3@l2?k3@l2?l4Am4Ak2Bk3Bk3Bk5Dn3@j3Bl3Bk2Bj3@j4Ak5Bm5Bl4Do4Cn5Do4Co3Dp1Cn2Do6Ep5Do4Dn5Do8Dq7Ep6Bo5Bn6Cl6Cm5Cm5En6Dm7Dn4Ak8Eo9Gm5Cj6Dk6Eo6Eo5Dp7Fq8Gr9Hr7Fn6En8Gr7Fr5Do8Gp6En6En8Gp7Fp:It:Ht:Gs9Gr8Eo9Fp9Gm8Fm8Fn9Gn7El9Gn8Fm9Gm;Iq8Fn8Ep8Ep6Dj8Fk8Fk9Gj;Il8Fi9Gj9Hi:Ij;Jk:Gi:GiHi;Jg;IgOi@Nk?Nj>KkKk=Jl;Gj>KlKk?Lm;Hi@Jk@LkJm;Hj9Ij9Gi;Jk8Gi:Hl:Hk:Hl8Fk9Gj:Hk8Fi8Fj8Fi9Gl9Fl9Hl7Hj:Jl8Hm8Hl7Hl8Im8Im8Hm8Hi7Gi7Gi6Fh6Fh6Fh8Hj9Ik:Jl9Ik9Ik8Hj:Jl:Jl9Ik8Hk:Jk9Il9Hi:Ij:Jk:Jk:Jk:Jk;Jh9Hg:Ih:If;If:If;Ie:Gh9Ih;IhHf=Hf?Jh@JiLi>Li=KhAKiALj@LiAKj@Ji@Ki@KjBMhBMhBMiCNjDOkEPlCNjDOkDOkEPlCOkGQmGQlHSmJWpJVpJWpKVsJUqLWsJXsLZtLWsP[wNYuOZvP]vMYtP\vO[uNZtO[uQ]wLXrNZuMYsNZrO[sNZuNZtLVtLVsKVrJUrNZsO[tN]wP[uNZuQ\wPZuT^yPZxOYsPZuS]uT_uVbt[cw`j{bnem~iojpntmrsxw{~{||}z~nq~km|jm~fm{gn|elzhm|hjzehwlp~im{egxfizafx]ev^eu^ftfoyR`eJ]QWlZc{YD`6Mi&IaHigUkQ0C1UhT^rRSlFZvH^{GnV{_xUuRuY_vL{ssqQdOUkOOhAD_3Mj?=W1nd~xRhLMcLVk[`tlbpu\hr\hyZav\dw\ew\fs]hsYhk[ljN\VBQG5E8\m^XgZ6G;=S?:S;/H.BY<\vWNiDNj>XsEId:E`5ayQd|Xe}`awcWk^NaZJdPAWEH`LCZK0I:*?4$5,1?:;FB+29)354==9C@-;/?P>OcO7N8*C/7'3'!6.$8/+,B3)?02"9#1I34. +# Hd6\zDNl0a?W{;Xy$6M84K:(";%AY?,E':S62K2+C1/$"9-&+.J95#-D/1E.1J52J45M5(@$$ "2#!4'1)(;8-,*+ &'!%&).'.!$++/.+("2+!5/#6/;NK"5.%83 4*/%';/,C3/=Q;-B)8Q0=Z0`L^zFeLTpT>;R?:U?:V99X7/O.NiDGa>GfDMkHKiHHeEA_=SpR5T4KeGMdKF]C9Q6>Q8DWD>Q=9M<=RA=RBF[I/E0SkPAX>kfJ^B\mSNaH^kTQ_HN\Egu]exbAS>7H38J4M_IN`JunRcJnivnBT3u`n}Tlmw_izTt^etNndtPVkGasT\qSez]J`>FZ;HaJc>Ib=Gb^1?^0>\0?^/<]2?_8Cc8Bc7Cc4Aa4Ad3@d3Ad4@b3@b1?b3Ac4@c3Ab1A`/>^0?_2Aa1Aa0?_2A`2A`3@`3@a3?`2@\4B_4Bb3Ba5Dc3A_3@a3?b3@b2?b1>b1?b/<_0=`/<^2?b1>a/>a/=c0>c/>c/>d/=b0>d1?f0>f1?g/=e0=e.e0?f1=i1?j2?i0>j0=j2?k3@l3@l2?k2?k3@l4@k4Aj3Aj2Bk4Cl2Aj4Ak1>h2?i4Ak3@j3@j3@j6Cn6Cm4Aj4Cl5Dm5Dm4Co3Bn5Do6Eo7Dq6Do5Bn7Dp6Cp6Cm5Bl5Bl6Cm4Dm5Cn6Cm6Cn6Cm3@k6Cm7Dn6En7Fo7Fo6Eq8Gr9Hs7Fq8Go6Eo8Go7Fo6En7Fn5Dm6En7Gp8Fq8Eq9Er;Fr9Eo8Fo9Gn9Hn8Fk8Fk7Ek7Ek8Fk7Em9Gn9Go8Ep9Gm9Gn9Gl8Fk8Fi9Gj8Fi:Ij:Ij9Hj:Ii;GjIi;JfKg?Mk=Me@MiAOlAOlANn?Ll;Hh=Jj=JkKi=JkKhIi?Kk>Lh=KgLi=KhAKi@Ki>Mh@JhALjBMkALjBMkBMkALgCNjDOkEPlCNjBMiCNjBMiDOkGQmEPlFTlIUnISpITpGRmJTpIXsJXtKVrKVrMXtLWsNYvQ]vNZtP\vNZtNZtNZtMYsP\sNZrLXoO[rNZsO[vLXsNZuP\uLYsKWqMYsN[uNZtMYtPYtOYtR]xSYwQXtNYrO[pOZoRYmV\nYbs[cs^escfqjlwoq{tw~|}{}y|~}}|t|toyonyo}u}~ls{jmzjmzimyimzimyimyhlyhnxijvglxfjvhj{il{flybkrersUdbJ_OKcHFa9gTNm>TrDMn?eRWwDHh9Mj>B^4PkDHi:Fi7Dj56V*4 61L*5S20M.1J0 7:R;D_D9R69P7H`G8N99J5G[DZrSSlHD]2Oj;Xt?Tp5`}AX6X0e[ZtOTnKNgGJaDbyaqxcsn^llbls^fp_ip^io^jlObZIYN?QC@RB2C2cvaUhU=P=4M5BZB,D*=T:H`E%;.0$4) 6-/+5CA1<:7CC,68+8;19?/8@/8?/8>*46(22?JF.<15H62F3*D/)@/*B4!6+"5-.$:MA:L=4F8@SCW;.2$),("!'(&66$!'97" )$)@6.I<4L;1G: 7*!6,!5,(<0#:,)@/7N:2K53K2,D,4M40H0=U<=V@'=()=*2(:&.B-1H4%?. 9,5"C[G4I31J23K32J1"=_yUj_=UD&<(>Q64H*WpKNi>Uq>Qn9Kg2Mh84O&A[;5K34J4;M9[nZRh?Vm@nPUr6Rn4Jd.D`,7S Ic+Kf-A]$Lh/Mk/Wr6Ca Zv3Wu5A`!a}U=9K5AT@8K83I5,@04I;2G42G4XnXtpWmVqkniYkSbs]=N9SeO_q[NaHytThMViOUfMPaI@U;AV??S:G_FCY>6L0mhPgLCU>FXBbq\cr]%4"GUABS@CTB?P=WiWJ\IQdOOcKMaH'9&4G1lblYxwgapNx[jOjv[s^kesR8F+|sbjTfpXKU=TbJkxek{jj~iomJZIR_WT`[[edWacR\^S__WceR]^WacZcbV__[d`Yb^_ja\f^^h]_h]`h`af^be_egbcgabg`ae_iph^e^_e_`faagcaebac`aeaae^ehbeh`fhaeg`ffbefaac]de]hh`ff]jibhgagicgjchi`jjcgi]hj_hk`jk`klcjj`llckk^ji]ll`mndmmcmmeonhoogongnnfoogoohpphpphsrkqpkrrluulvvjwxlyxmzxnyxk}zo|q~|q}{o}|n|m|n}r}p~o~prrrnopnmlllnonnnkkmmmmmnlnptwwxxzxvwwzyx{{yyxwzyzzzxyxsv{yxyy|}wzy|{xvw~~u~}u|}w||w{|wyzwxywuutssurrtqrtqssrsrsvtuvustwlpsknsknsilsilrgkrhksgkqekpglpilqkmspsyknuhkqehogjpfiphkrjmtjmuknvfipcgmciodin`ekafl]el\ck[chY`fX`gW`gXbkZclZckZciYah[bl]al]bm\`kY^iYbjV_gZal[alZ`m\bn]cm_bm\`lYcmXajX^hV]gSZdSZdQZcPYbQ[cT^fT\fV\hW]iV[g\_j]cl]dl^en]dn`fodgpkmrstxtuyqqtklommqffk\_fV[dW\fRYcSXbST]UW^VY`UX_TYcSZeU]hRZcRYcSYdVZeWZcX[cY]d\`fUZaQW\PS]OS\PT_QU`RWaPXbQXbNU_OYaOX`MW_PXcQWcUZfWZfZ^iZ\hY[fX[d\^e[_f_afgglllmxus{u~s|nm~mo{l|p{p~swu{qynytlpnjdcbeehediabhbck^ciRV]RV_NU_LS]OU_TVaSW`RU_OS_OS_LP[PQ\QT]PT`NR^GNXGMYGMYFLXHMYIMYPT\VW_WW^XX\XW\[Z_^\a\[aXV]VU\WW_XW`WW]\[`_]caaehejjhmjjmkkmlimnlmfeeiifmkiuqmwqnsnjmheliemmihge``]cbdegkcejfhlbcj]_e\]gUW`SU`QXbOU`LS`KR_IP^IQ[IR\HPZFLWGNYFMXFN[EO\CN\AN\?KY@M^BN_@N]BM^AL_?J_?K_@KaBJb?Ja@Kc?Ib@Kd?Kc=Ib>Id>Gb;F`:D_9C^:D_8C^6B^2@[5@\5A]4?]5@_5?^5@]5?\2A[3D\3A^6@_8Ca:Ec9Fc7Fb8Dc7De6Bc5Bc5Bc5Ab4Ab4Ab6Bb5C`5B`6Da3A_5C`4C`5Da5Ca7Aa7C`4C`4C_4B`5C`3A_6C`7A]5A_4C`4B`3@`1>^5@`3>_2@`0=^2?_1>`2?a3@c1>c1>c/=`/=a0>a/=c.=a/=b/>c1?f0>e0>f0>f1?f0>f1?e.Jg;Ig>Jj;HhIfBKhCMgEPgFQgCNeAMf>Jd;IcLh@NjBMk@Ll@KkAMm>Ll=LhLi>Lj=Kh?Jh@Ki>Jg@Ki?JhBMkALk@Jh@JhBJj@Ih@Ji?Jh?Jh:If;Jg9Hg9Hh9Hg9Hh:Hh9Hh9Hh9Hh:Ih:Jk;Jk:Jk:Ih8Gf9Hf9Hh9Hg8Hi8Fh8Eg8Eg6Fh7Eg6Fg8Gh7Hi9Ij8Hh7Gg6Fg8Fg7Di7Eh:Hk:Hk8Hj8Gj7Gi8Hi6Fg9Hj9Gi8Gh8Gh7Fh9Hi7Hf8Ig9Jh8Ig9Hj7Gh8Hi9Ij6Ge7Hf9Gg8Gg9Gg8Gf8Gf:Ih9Hi:Gk;Il;Km9Hm;Ik:Hk9Gj7Fg;Jk=Lm9Hi9Hj:Ih;Ji9Hg;Hh;HhIi=Ii:Ih;Hg;Gg:JfOnCFa9,HEa8Ab5<_,=c1JiE2/I,%?!:W9CaD?X@C[G)@+#:!AZ>F`CCZD2K6cxbG[Bf~aF\?=U3:O'?W);WNm,rIe:Ut2VrG`>F_@+E&PkDMgG.G(SAMbOBWD5L61F23I4G_H8P6TlQ9Q7)A)4L5,E-.H*A[:=Z2B^5SnCQlBF`:G`B)@)2I6;QE%<.-#5.70)>9#64#321==3=?,7;-6;*3;-6=*3:$,2,79,55,734A86F73K8*:,)D5&=0*"&1C7;M>#4'0C5:Q>,@/8I8-?.(@,/F3D[I=SA>UB3" #4&!8$#<Id?Pq?Np<3RUt@C_0>[1(EKgC?Y5)C B\?"=#0H4C\G+/@X>5M2;S83I4%:(*#.$&>-&<,!1)!".=@"43$" %&2..&%=1"7)0%,&$5.3,1%"6+0E52G59O:0G01H2,C-HaK#<%#;%(?+'<)*?-6N83K5)?+2#6))?.4J71G1/E++C*$^vR#9!0"3-!00$$+'!//,/"( #!52-C9(=2$:-3'/*/(.'-#$:.(>2$;+,C2:T<:R6H\?Mb=*CGc6Nk9Tp;D`/7R'/I$BZ=;S9QiOZ-Ea1E`.9V"Fd,On4qStVIg,Us5Xw8a};d:dj7q@zL~[ta4H22H7;T?@\EA]F=[FD]G4M9;T=IfM;W>HbJ?YB,C,8T;7N9+C/CXD:QWl[RhVTeTMaM;M9L^I6N3XkNctRiuWtetw-K\Bdv^PbHetSknaoKSc?NcAQiH;V5F`FYo[:OB:LI:LMVejHV\DSYEU\L_>I`?Ie=GbHe>Iga2?b2?b0>a0>a0d/=b/=b0>c0>d1>f/>d2>f2=h2>h3>j0=j1>k2?l0>j3@l6Al4Ak4@k4@k1Ai3Bl5Bl3?j2?i3@j4Ak5@h5Bj4Ci5Cj5Cj4Ak4Ak5Dm5Dm5Dm6Bo5Bn5Bn5Bn5Bn6Cp5Bn6Cm5Bl7Dn5Cm5Eg3Ch5Dj6Dj5Bl5Bl5En5Dm5Ei5Ek7Fp6Eo6Eq8Gr5Do6Ep5Em4Cl6Dn5Dn5Dp5Cp7Er7Er9Fs9Fq:DqHi?HiIg=Ge>Fc>Hb@JcAKc@Ic?JdLi>Kh@Jf?Je=He?Jh?JhAKi@Hh@IhAJiAJi@HgCGh?Ig?Ki;Jf:Ig;Jg9Kg:Kg9Jh8Ig;Jg:If:If:Ii9Hh;Jj9Ih;Ji:Ih8Ge7Gd8Ge9Hh6Ee9Fg8Ef8Ff9Hg8Gf8Gf:Ij8Ij8Hi:Hj8Gh8Gh9Hi8Fj9Hk8Fi8Ij7Gi9Ik7Gi8Fh6Ed7Fe8Gf6Eg7Fg9Hg7If7Ig7Ig9Jh8Ji9Hj7Gh7Gi6Ge7Hf7If9Hg8Gf9Hf:Ig6Ec:Ij8Fj8Fi6Gl6Gj:Gj:Hk8Fi9Gj9Hi:Ik8Gi:Ij:Ih:Ih9Hg;Gg;Hh=Hh>Ii>Hh;Hi;Gh9Ih:Jf9HeKl@Jj@Kh@Ji=Mj@LjDOjEPlDOkDOkALhALh@Kg@KgCNjEPlCOjEToDSnGUoESqESnHUoFToMWsLVsKTqKVrLWsJYtLXrLXrLXrNZtNZtLXrNZrNYrLYlN\nNYpMYpKWpLXqO\tPZqNZpNZqO[sMYrNXqNYsQWrPWrRVtRYrU[uT\rSZoV]oX]q\`qhitur{~|}{z~}|{|{~}}}}~mst]hgVd^UeThyavgu^t_wf}uxvv|xv||ty}pt}mq{hmxmq~klzkozimximyhkw\fiLXWHYOCXIGXJYoUC^9Ie=MlH@]<.O+Ih@KlE2O*+F!A]74P-Aa7Bf:>e64S3KhKF`Dom5T5.L1)A+DZI*A-4\wZHcE ;"J`L?T=;R78S2;S3;P-=R--D[x@Wv1c:qKNl.7SRoP6M0-E):R78Q56Q/7S)WsEhR]{J\yJgW]tRVmPVkTSiRIaJ;S9AYB5M72G31F3*C,,D.(@&7P1D\>-D*#<% 7$!9%3I_E9V*Nk@OiEG`>/H+5M5JcL@XE2G91&-%.'"72.B@0BA+<:09;-8=(05(18-6='07,7=0:>0:<*440:68G=5F84K8.F89UH%A2 +$"8.8LAK\P!3& !5J=3K;5J;.@1%7)1G80F8'>1*>3)>2FZO3H>%8//E7=S;mcLf;Kh8?\,Qn=F`49S.1K*/G+4K/2$<#5"<'9R@'?,/33J5:Q=3J5-B1-!+ .?2/B4 9(3J9'70(%&)#)/1"!&'.. 60(>22%&6.*:7+<8.),"%/ /D3+@.*>,+=+%9%/D24"*B-0E53I7%8')B,0I2!8$ 3& 4(+@42G4/D-4L0C[B26'.F84I=+&,A:#2*$:.&Kb?1G"0H Mi9Gc2Sm:[uHFb9.H#D]?A[W3:T-A_3[wIBc1;gT)ELh:D`'Xu;Ur1tMb5g4a|-b~1g{>DZ00G,LcO>VA3Q9TD>VG2K50I3E_I=V@5J8:Q>Xo^5K<+@36J;>Q?AWB 5"UiW3N3TlVMdROeSXjT:M70B,BUA?[FC]I1H6G\L@PAN_OMcQRiUP=E\J]scKaR)>1*?2EVK6F:=R9K^Eheqf`pMzitX9H0>M2ey\@R:5J5_t`;Q=i|FajWh&?8S%G`=TmQUmSvvXhZ.),0)8>1AGFU^M_lDTc=MX7GKJZWO_ZM\YETMQbWVfZLZMLZMKWKR^VU`[Ub_VefM]_^orRbeM^`K\]I[[Qa`[fcfqlcjgdnh^h`ckceibdicah`emejrjah_djdglcjofgleghdjlcmoapqbklamnbkm^ik\qpbon`suerqasqayvhusguviqrewvjwuhusgvuixxlwxkwxkzylyzlxylxxlwwlzzm}zm{n}zk|n{k}i~kllnnnnlonlkjigfikknnoomnoptqrsuvwxwuuxvvxywuvy}}x|}zzxuxxy{{{xzx{ywywvtwxqs~qprsrt~p|znzynyyoxys{|vy{r|}s||t|}ty|vtxrmrqhoohmpgjnejnekmeknhmpejohkljkoglobfkdglbfkdhkimqgjnloslqsoptoqtmnqjnpjnqjmqejlcil_ehahkcimbfj]dj]ci]bi\cg[`f[af]bi^bkafnbfo`fo]em_fo]dmZbkX_hU]gX_iU]hQZePXcPVaSWcU[fT[eV]gU_hT^gW`hX_iX_iZal]dn\cnZdl[emZdlZcjZajZbiZ_h`eo[`hV\cTZbRV`QT`OVaNS`MT`NV_MW_QYcQWbQUaSVbSWbSUaOV^MU_PWcTWcUYbWY`VZ]]_dabf`dg_cf\^d]aeZ]d[]cX[bW\aS[dPXaT\dU^fR[dS^gR[eR\dR\eS[eV]eZ^g]bi]ag^af`chfdjjjlqnm{uqxq}twvzpxnwm{t}ytuplokiqmmrpoqomropompmlonkqielgem]`hTWaNR]OS_NTaOUaMT]OW_OWbNT_MS_MR_IO[HN[INZFMVDLVDKWEJVDJXEKYFKWKNYIMWJNZMQZRT`TV`STaRUbTVcQT_NR]OR^QT`TXaVXbVXbZYd[Ze^]gcbidagfdhdceedffcff`ehafhbfnhkplnmlljjmaadXY]YY]]^cabfbchgekddhffkijnefj]^d[Zd^]g`_j`bh\]_abe]`e\ae^biZ`jTZgNWdGQ^GO`EO`FN`EN_CK_CK]CK^CK^CK^AJ_DL_EL`AJ\@L^?J^?K^?J^BIcAHc>F`?G`b1>b1>c/=b1?e1?d1?d1?d1?d1?d2@c3Bf3Ae2@e0>e1?f2@h3@h3Ah4Bi4Cj4Cl2Bj3Bi3Ah2Ah3Af2@e3Ah5Bi5Cj5Bi4Ci4Cj3Bi5Cj4Dn5Cm4Bk5Bl3@j4Bl5Bk5Bk5Bl5Bk3@k5Bl4Ak5Bl4Al4Al5Bk7Do5Bm5Bm6Cn5Cn6Eo5Cn5Bk6Cj6El5Dk5Dk5Dk3El4Fl5Dl5Dm2En2Dl8Gp5Dm6Fo8Dp8Eq8Er8Fr9Fs:Fr8Ep6Fm7Fj8Dg8Eh8Eh7Dg8Eh7Eh9Gl9Gl7Hk8Hl9Ik6Gh7Gi8Gj8Gh9Hi8Gh9Hh8Fg9Fh:Gi8Ee9Fe;Hh9Ih9Hh:Gg=GgHh?HmIi;GgIhHh?Ih>Hh=IeIi?Jg?IhIgKi?Ii>Ij=Hh?Jh=Hf=Hf>IhGf@Ii@Ii?Hh?JgLg:Jc;Je;Ke;HdLj@Ji?Jg?Jf@Je@KgCNjDOkCNjALhALh?JeCNjBMlALgDOkGTmHTnEToFToEVoEUoDTnESlHVqKYsMXrLXrN[uMXuMXtMXtMYsO[uNZqM]sN\qO[qNZqN[mO\pP^pPZoP[pU]qR\oOYnP\sO[pQ[qRYqQXoPVmOVqQXsPZsP\qT[pX`q^bqccohchunnukf~rl|sntnmwsyyv~yu}{}~~|y}}}}~~}y{iqvHTT@SH{g~^d{Q`yM]tLg|Wh{Zprlxuu}vrvwprwopxutmoyntwnv}bkoR^]FRN5E;FVH;P;/H-7P4/I*,J-1P58V<4Q6$A&'D&/N(\~RJoBZR9Z11K0.F.1=V=WqY;U<7N93J8,B)!9>W65N/HaE :F_C]tX3I/=W49U&wXc=rKpMVs7Mj2NiR>:M95N42L2A[@2J4 7$6"3N3ur_{TNjDA[6?X7E_A-E/'?/6) .'.,"50+A9/G=#950.+9=*49"*0#)2*07!*/-;<)79)41)63.855@<#3)@WGAYI$@/ ?,,F61J:*@23&L\Q^nf0A5-=4"1'+""3)%9.0G:2& "$40P`U&=%E\9D^3Hf4Nk<=Z.::V1AZ?'>$"4 5#3F8-#4 1*?V+1I8.E4$:0/E: 4(."%:.$8-%=+5K92M4?Y;Ha;[vKWrE4O"ZuGIe5Hd3Nj?RlHJcG6L1[sU>V1j[[q@E_/Fb3?Z-0J"%?=V2>U52L)AZ71M'!>qf.M$$DC_6Vr;A^%Ki.Sq-d5VO~FK](5H BX;6L94P<8T?AZE'>,4K93I;*=/;OAD[I/F2BXE:P>UI5J=9N=sqmlTgTRiOK]IWnW;R;ZlXvtF^AJdFZrP(;ta|amowd`mSDS=2A*.> i{Pg{RxGf|9`oLO_9DV8DX53G=@WA{wQlI=X1_vRzWfGxpObEi|e(5(&4+x{p=w9\k@[Fb5=V44M0DXChzeVe`($3BD2AF%48:KFj|oK]Jctbqmspgw`y{vn|l4A6;IBP_[L^_DWZHZ^SbhK[_BTYJZZFVUVdcWdd`mjMXV_keaieblh\hcdpl]jealhU^[cmf_jadlg[a^ahcZaWencnvlrzqel`qtikmcprhsujpshprgttiywnwymtwlxynxwmzwk|{nyzkxyl{zk{xkywj{yl}{p|{o|xm|o~zl~{k|k{jlppnnonmnomkjijllmporptsqqrrsrpstsuuuzxwzvwzyz{z|y|{vyyxuxyzz||vuvstustxuwswruqrssqm~oqr~stsvus{{oz{twyspuqpsroqpmnmmqplpnqrqqtpqqoqspnpnmnpkmolmnlnmjllmoomnmmomnnlpoooonmlloporsrnpnmoniljhljimmostpstjlngklgklimoinpeikdhlafiaejbgn]ci`dj_dj]bh[`fY_dV[cQZaQZbSYcW\eW\eW[dU[dV^gYaiZaiYagX_fZaiZbjZbj[bk[bjY`jW_gU\dV]eU\dW^fT[fQXbPW^QX`MU_KR\IP[LS]PV`NU_MW`MU_LR^MT`PVaRXaNU]MS]LS^LR]PS]TW_VY]Z_b_bf`ae^cfbgjeimehnbfk_cg[_b\`dW^dZ_e]cjYagU\cV]fT\eV]eV^gV]eZ^f]ch]bh^ae`dgadgefkkjmspnxsqxsxs|snxr{vo{upywp{xsrqmjijfghhijkjmiikllnmloqotmlqihncah[`gVXcPT`OS_MT`NT`MU^KS^MUbLSaNS`JP\GMYFLXHNZFNYDLVELWDLYCIXEKXGLXJO[ELWHLYMQ\NS^MQ\PQ_QS`QR`PR_MT]MU_PV_SVaTWbWXd[]i\[gWWa[Zb[[a[Z`][a][`ZZ_YV^[W^]Zac`fihmedi^`eY\cVY_VY`\^dcchbbf`ad\^b^^c^_dbbg^]fZYd\\g]\g__ggglghldgiddi_biZ]hUZgQXdKS`JR`HQbDM_FO`EN`CK_EM`DL_EM_DL_BJ]DL_DL_CJ^BK^@L^?K_CJaAG`?E^?F]?E`;D^9D]:F^8D\:F_:F^7CY6BX7BX9C\9B\8C^;G_Kf@Ke?Ie?Je=Hd;Gc:Id;Ig:HeIj=HhIi=Gh;Ff:Fg;Ie;If;Hh;Ii;Gg=Lh:Kd9Ia:Jd;Ic;Id:Ge:He:Hd:Hd;JfFf>Hf;Fd>Ig>He;Je:Hc;IdHg>Ij>Ii=He?Jh?Jh=HeGe>FfLe:HcHhLi;Kh9If:Ie=Ik=JjIg?Je

    WA4"1I9"5*2&*@57%)(! (92*%3/)$+&,?9$70)#$74':60+"$'!) ;PD8Q@'>.&:+%;,%7&':')A,>P=+>++=.+<*,=*2B2 2!%;,!7,)?32$#;'@X>>W:[tT8T.8V/A`6.,B1*C-2K/2K(Nh?^yONi=Oi=s^TrAC_63L,0H..G,0F(8O-5O,@[:*?#0J&$>;+G :4 3O'5Q(Tq:Wr=oRRl)n;y@v:wX;D^D%?()B-6M;0D4:J<4I7C\E@Z@@Z@AXB:Y>?\D6M?1I:1G5)@,2I7,B48S=MdOXnZJ[Ij{k>TBLaRd|kaxcD[K/E:0C6@VAQcOJ]K=N6DW?3G/0E,PcII]B2G,d~^XrPMbAn~\aqK{aiBQ3LYB?L13E#j~[~QrAXxESxFqCU";M"7K!8M+QfO?XE-C/:L>APE4E;7FA-?4QbX-@31C9.=7*;3$4-4E:@QC,?1,A,HbEA[:C^:XpNk`vkqgy`L^DcuZK\M.M%;;Ka"Qd*|KHTzrD`$'A.E)2F5=O:4H)p~j~uvnWkTrqxxmhy}j~kZpytr'9-FWOGWUHWXBPTQ^_:JHl}yHYSL`XN^]EUR]mi;HDXe\?MG>NFRaXYkbYhaUb]@QIL[SQ`\VecaplWf__nh]mgetk`kb^h`Ya[ekhemjfnjjroqtpkpgzyswnz|qzynz{mvykvwkyxm}qr}ym|wk}yn}xm|m~{k~mom}j}jmmnommlnkjjmmnlmnnppvtturrustruvuztywuzx{{{y{{z{yxyzxxxwwz|{xzxvsststsspqtqvqpooonoopqp~q~oprs{o}q||rzyrz{qxxoxxp{{ryypz{r~~t{{q|{r{|s||tuuoxxrzzvusoxvpusmwumutlxumvtlwskwvmxvqxvpvulssistktukwxo{zrxwrzxrxyrxzrvwsrurpspnookmmimoejmgknfjldhjafi_cf]be]bf^ch^bh]`fZ^d\`f^cg^ej^gi`hj]cg]cfaeh]ch]ciYagV\eVZeRZbQXaQYaPW`NW^MV`OXaKT[KT\KR\MT^MT_NU]OV^KU\KU\MT]MS\OV_QT^NT]LT[LT[LS\OU^RU^VY_V[^X\^aaebdgabecdffhiegkdejcdfbceefjgingknfiobglafj^ch\bf[_e\ah`ckbekcfldilhlmjophkmkloppotsqxvqywq{wr{vqwsqtsoqpmppoppnjkmghlffjdfjefiefgfgifhiijndeidej`ag\[dWYbQU^OT`OVbOUaLR^JP]JT`JQ_GO]EM[DLYBITDKVDLXDMYBKWAIVEM[FN[EKYHNYFMXHMZJO\NQ\KOZMQ\LQ]LO\MR^JR]KQ]MT`PUaVZfWZgVXfSUaSV_VXbXZaY\bXX_XW_VU\RS[ST\WV_YYa[[cZ]dY\eTX`VZdWZcY]e^`d^^d]^cY[_Z\aY\aY[bY]dYZgVXeYZh\]jacn[\d\[c[ZbVVbQT`NS_KQ]JQ_IR_GO_FN^HQbFOaEMaFNaEM`FN`FM`EL`FM_EL_CK_DK_BJ^CJ_EKaBH^AG]@F^?E^>F^>E\;E\:F\:F\:EYE]Jd>IdJdJdIgHdIg=HiFe=Hf;Gd:Ic;Id;Id:Hf8FcLjBLjALj@Ki@Ki>LgMgAKg>Ie?Jf>JfBNhAMg@Mg@LfANhCNjFQlBOiALiDNjCPiDOjAPjESoESmFVpESnFTpITnJVpIUoJVpLXrPXtO[sNZrNZsO[tP\sN[oM^sM]oO[nP]oP]qR`oS^pX`s[cv^d{]dvZatY`sYbwX`uYavZbwV_qW[sSZpT\qT\qV^qTYl[aoglxmkrtnpyw|y~ztjl][cedrkixhftwt{}\e]0>4pu>WBE]D?Y:;V4$;%)D.$,G(5P1RmOIbDIc@:U+1NJj6Tx@Ij5Ig4RpAVoH1J*=U9AY?9T7=T?)@,%:*(8N5*LfD+E$MgHF_@1I/)@(=X;MhH8S8+B+":#CWE&;+ 6+#81!91%81%74&$ 0*/+++ .3 ..'65:FE&%'10?KH)947,A4'>-2O9,H8?WI1&3-0, 0("7'5K;(8+0B41A6-B5?VI+ 3/'&## AV@}oSq>Ea24Q%Fb<6Q4(@(.B0&8%':('?*-D/2)A,7P:4P9AYC6M8(A,)B,0F3 7%6&$:. 5((>1#>,2&!3.#!!5. 5+1H?%;/4H?$8/-()&!40-),''%'"*!5'4"*A2&:,1B06H54K31C0(;(.?/,<-.?.-?1->/)=3':0'=16L>0G40J/8Q3C[;@Z8.N%<\2B]3Eb7Ic6YwDNi8rh -(*'**-.#1(+/E8$=.6'$<-6' 5$!2(:$7,>#1& +3:R5.E(+F$9S/3M'@[2Ea6ZvJWsJ/J&=U94K3,D,)B#9T.Je=G`57O%3O&>W0\uQ9S01I*1K-;V.PjCVDh|m7K=L\OI]QK^RD\MBYI2I:&/CSKBSH3C80?5,=7BQPBPL0@;&011=:+720>4-<01C1]t^AZ>H`BHdAAZ6ga-C%YnPcv]4E*M`EM]L6G>(95*=91EZkBWh5_@VTEvN{k~z@VB5L3ayPTo=QjLC\hc[h];I6ƴ6K:J^Pzl}jzzReTorx]rgJ_VRe\h~t:OFH]UUg^[lbKWOGQKT`[XfcYfcV`cWa_]jbjwqdrl^hclslpuly{qw|p|sw|lvyk~qzyl~{n{or~zio}ipomoojmlgkomllklmjnrpqrswvutusuvxtxuxsvwwz|||{|vywvxvwz{|yzzvxxvwxvutuuttstqrroqrqpssqomn~m}m~n~nro|n{mzn}zmzo{n|n|q}q~r~{n}p}onp~{m}p|o~{p~znzp{nzn{n|oymzmznzozozm|xk}yl~zmzn|o|p{n~|p~{p|{qzxqsunrtotuprtpopnlnmkljmnkmomkmlkllknpimoilnlnqmnqknphlnlosglogmmejjdhkdhl^`fZ^cY^bZ]cY]cU[aTZ`SX^QY_OW_OY`MW^LV]OX_PV_PW`PW_OV^OW]PW]PW^PX^MU[SW]QV\NSZOUXOTYQW]UZaTW^Y\a^_aadbdffcdefeeijjllmmmmjjkjjjlklnnnonomoooqsnpsqsuknplmolmqlmqjkokkokmopqsrtsqspqsorsqttruuqvvqywsxxsusrtutqrtmnrhjlgikfgifhmfgmefkbcg_`dcdibchbchabf]^b^_c^]dYZcSWaTXdQVcMS`LS_JQ]IS]GPZGNZBKVBJUCKVCLWCKWALXCNZBLXELYEMXDLWIP]GMXKR]LS]KQ\IMXMQ\LP[MQ]LP\JQ\JQ\KR\OWaSYdQUaPS`TWaRU^SWaTXbSX`PU]PS[QS^PS`PVaTWcUYeSXcSWbTXcVZeVZfW[fY\dX[aX\`WZ_VZ_WZ_VY_WZbUXbSWcRWcRVbTXdRVbPT`QS]OR^NP]NQ_JO_HP^GQ^FS_EP`CO^EPaFN`FOaGObEN_GO`EL_EL^EK^EM]DM^DL_CK^DMbDI`CH_BH^@E\>D[I[DOcDOc@Kc@Kb?Ib?Ka@Mc@LeBNe?KcAMe?Jc@Jc=Jb=Ia@Ld@Ld=Id?Ke@Lf>Ic?Ib>Ib;F_FeHfFf=HfJeKe?Kg=Ie?Le@Lf>Ke?Id>Jb=IaLi>Li@Jh@Ki@Lj@KjBLk?Ji@LjALgBKhBKg@Lg@KgAMiCPjBNhAOiBNi@Kf@LeAMfBNgBNgAMfAMfCOgCOhFSnHTnEQkFRlEQkCRmCRkFTpESnGUoHVpHUpLWqLWrLWrLWqMYnO[pQ]uP\rQ]sQ^sO\oO]pQ^qQ^qP^nR]oQ]nY`sbi|`h{^cv\`tYasW^qW`sX`sX`sYatY`sW]tV^sU]pT]pR[lRZkRYhWWb[Z^|svncdd_e[[gXZk\]pZ]k[Zi`anecljin|{}~XdX1D48O=NjT4O8HdINgLIe7Jb7YpDzbOa5w^~haqRUfOEXE=OBCXK-A5.F58K/N,&F$5R35O4.5%SlZ25R;.56(D&2P.LkARrDTuB<`*aJUx<9T>,H2.G-&@$=X<7R1VqS@\=3L,Ic?8R(;X*Eb1Kl9UuCKg;Hd;-H(6N1=U>=T?>V@?WD"6&6K99P9RlMB[:G`>5N(^uR7O+[rT.H*2K.8R5U@!5*-@7.*1,/(/(0)'"0010(%32'(*-%4/'61;G@(4-0<64@8,=3;KA.=8"-.$/1$0/&7.7M?+C,=ZA2I82#!6041,, 31,&,$&=-5L<*?0BXG?RG%8.)@2!6.#81*&"!*)(!'SoH,9P>*A0'>.'>- 6)"7*4'01I<,%+"&9/&;/#9- 9+2"$9- 5+3.%87+)-++',(-'2(4'-E7(A33J9'>//D2/E/5!8!,@/)>0.@3(;,-@6,@4+>3,@5#6,)>S?7M27N13L-HcA=]7;Y1;Z0@_5=X/?\*Jg4_xO"8!4%-!  2*#8.1&6%(@+3L2+ !YsLcyE;P`G\*Og6^uIb{MY-8S!`{APj%h5{Bz?D|AO~Go;wKUp7ug4N0/N4JdKF]DKfH#@QrFk`1.B79MCAVNVjbTh`+>6*>54)K`X/D;?UKhrCWFKaOdudi{fG[B5L0auWctSseWgK@R9g|_gya@R=MZFHWABSjDYp8`u>cy6k9u>xG`{2}NRd+,9 CV4@Q;5B96C;7H;/B3;L@&;3;KF,:6"209CC%1,4?77G:9K9\rVPiIYuRIf@Fc?A\<_yUPiF[rSG_>WkLbwZ[mY'9)3'*=5'8CT*0B8Li}7[]z/*Gg]]s^6O63N'c~S3R'=[*wXiLc_F>]'Np9aLVw>bHiFf)gGpR[{=Oo1tVxXhmujLaEvpqztz{ryzwu{syz>RBv}ReV7I;CODTaV]kcL[THTOJXSK`WTicQedL^\Ygfjsnjsnkwlq~or}nnyhnvgrxj{~qzzop~mrqpmomilmkmlljllklnnrrqpuuttquwuwxvuvwwywwy|z}|{zxxxvxy|{zyxxwwwwxwwusqsrvsrroooqqsoqpqtpopon}lo~{o~{n|p|p}q|p~p~o|m}o|n}n}n{m|l}l~j|i|kyi{m{nzn|m}k~k|k{jyh{lzk|kyj|l}nop}n}m}o{m{m~zmzuk{xnyxqtumqrmqqjmmgpoitsmrqkqrmrsmrsnqrrrrrrqrtuuqrrsutsvurssoqpmnniknijoghmafi`eh`ceaaf^be\_e[`dZ`cU\aS\`QX^RZ_T[bTY`SY_TZ^W\`V\_W\aX]aY^bW\`VZ^WZ^W\^W\_X\`]`d\_e]`dbbdcfeegdfifjkhhhdiihoonoomlljpomnlisqoqonsrqttrqrpvvuvvuttswwwwwysrurrrsrtuvsuusuxqvxrxzuvwqtuprtpttsrqqtstqrrstupppmmmiijkkmiikiinhflbchcdiabebceabe`afabf^_ba_d\]cWYbRV_QU`NR^NQ]LR^GQZCNVBKTBJUCKUFMWEMWCLVDKWDMXDMXDMWEMXGNYHOYHOYKQZKOXLQZJOWLQYKPXLPZLPZMQZLPZQV_RV`OT]PT]RU`UYbTY_TY_SY`SWaQV_MR\MR[PU^MU\OW_SV`RV_QVaTXdV[fX\hX[eZ]d_bf`bfbch_`d]^cZ]cVZ_SW^TWaSVaSVaQVaPT`OS^LS^LP]GNZIQ\GQ\DS[FS\DQ[CP[BN[CP^FQaHQbGPaFP`FO]FM\CJZDL\BJ[DK^FM_EL`DK_EMaEL_DK_AI\>FZ>GYKa>J`=IaJc>Jc?Kc=Ia?G`>G^;G]a3Ac2?a2?c5Be4Ae3Bg3Ag4Bg4Bg3Af5Di4Bg4Bj4Bi4Bi3@e3?e5Ag4@f4@f5@g4@g5Af5Be4Bd6Cf4Ae5@g7Bi5Ah5@g4Ag4Ag4Ag5Ah6Ci5Ag6Df6Dg4Bf5Bf4Ae3Bg3Bf3Bg4Bg5Cg3Bg4Bg4Bg4Af5Ci4Bh5Bh4Ch6Di6Dg5Bf4Cf6Dg5Cf5Cg5Cg8Dg7Bf9Dh7Eh7Bf8Cg8Af9Dh;Ei;Dh:Fh8Eg7Df7Eg7Eg9Fh9Fg8Gg7Gf7Fe7Fe7Fe7Ff6Eb7Fd:He9Ge:Ed:EdGfJc@LdCNgDNiCJfBKiANj@LgCOi?Kd>Ka?Lb?L_?K_>M^>K^;GZ=J\>K`>Jb@Le=KfLg;Lf;Ke9Ic;Kd9Jc9Ib;IeLh?Kf>Ih@Ki@Kh@KfALiBMiCMjCNi@KgBJhCLiBKhCMhCMiDOhDOeCNeEMeDNfALbCOeCNdDPeDOfBOeCOfCOeCPhFRkHTlHRnIVoHUpHToJVpFUoHUpIYoIYqK[rK[rJZqIYpMYoO[qO[rP\rR^tSasR_rS]oQ_rR_rR\oQ]oV^qX_r]cv^bwZ_rZ_s[`tZatZatW`sW_qW_rY_qV]pV^qT\oT\oR[nU\nRZmV\mTUccakqjr]Y`XXcW[lW\o\^oZ]qY]qY]q]_o[\jb`mfdlvqt~zz}~}~vxqU_RN_ORiWRnY>\A9W>9T<7U88R48T42P-B^:B\7PjFKa;D[1Sg;]rFh~RMb:MaH4H36G:;PE+@4';0&:.(=04K9,C1!7#%;(@YD3P9">(1O6.M*IfFJgGGgCIhC;]79W77P4$:+";-)A2#=&>ZC0K2IgO%B,5+(-* %-%1HA%#"/+#:7!54#21%220A6]oc8J>1@42B6'6+=MB8H=5E?)43*56-+-#4J8=W>?Z? 9%"8*4,,B=&:80+-'2G>+D3D[J:Q@FYN"6*/#.%(<45&/C:!70'&(#!9(>Y=VqH?_2NjB)E"9Q5544 4J6.J02K2-E->V=+E//7&-., (>1,B54'0#!7*2$7%$;.'4J>!7)4K8)C2(A1#;-&>1/C;7MH)(20-+!43/+2)/'8+5(&?0"8+%;-+D1*B,&?(7"2J8 7) 6* 8'(=1'<06( 2(-!+pqB[>8M1.H*/J+6Q1?^;.M'9W0(F!6U)Da2k[/I)YpV2E+  +':O:742M"oYkOvrugd^lHc?f$; =T%4M)5O3@YX51K',F"-G$%>-F&,E$:U3VE4M90F;#6-+>:FYV)).-*=PbPWiYrBf9b~5j7]b=K+;F;9E>4D97G9&9*7J<1C7GXK5E;0?7ARF0@4/?1/@0;Q7B\W=Eb=YyQ/PCc+Ll0Cf'>`"Jm4X~I?f12W"Di1Dg/>a'uZcGTx8fIjJ]}>Rq7qZug[qcm_ںÞ~zrtl~szi||\iOyupg~|^o`dvh}}|aq_~vuopuvasgAXQC^ZOdeQcfKXZjwugwpN^Tareostvcqdirfnwlnti{}owyzk~~ol~josrpomkkknmoppqqsvqstswvwyvuxxwxxyxyuyzyx{|{z{zzytyzxyzwwwxtsrrrtsssrrpsqpqsppnnlqqpmp~p~zl~{l|n}|p~{pzo~zo}q{o~o~p}p}q{p|q{o{n{l}k|h~zh}lyi{jzj|k}l{iygygygxgxhzkzkyk}n|k}m~m{kzjyizl|mym~xkyuixthxultpgwukwtkusgxvkxvlwwnwwnuunvupxxtwurusptuquvqrsprspoomllkiikkkmijlefibegehghhgefebdecfgbfibeg^de\ad\bd\`d]ae_cf^bd`ceaefaef`de^cdaedbddbdd`cd^cc^cc`dedegffighgghcijdhieklgmnhnojqqlnmionjpojtsnuuqsrnvtqvwrwxswytxytxvrwvtwuuttrssrvvuuvruwrxzuxzuwxqxystvqwytttrttrssstttutrqqorrpqqopppnnollkiikjknghkhhjfgighkdegbcfddidcibae]^bZ\dSX]OS\OS[JOXFMTFKSHMTGKTGKTFJTILUIPYHNZGNWHOWLQWKPWKPWQU\QV\RU\QTYSV\TX]UY]UX]RU[QTZTW]V[`UX^SV\SW\TW]X[aY]aY]bY]aVZ_W[`VZ_TX]VZ`W\aU[^UZ_VZ_XZbWZaX]cZ^dZ]b_`dbdhhikkkmkkniikghjbcideg`ae]^c\]dY]dVYaSV^RW^NU]KS[KRZKT]HR\GRYERXEQYGQYGQ[GP[GO\HQ^JP^IP^HNZHN[GLZIO\FM[IN\IO^FL\FM^FM^FL\FL]CI[@EV>EV>EVAHXAHW?ES@GUCIWAGUAHUCIVCJXGO]FN]IP]FN]FN]DK\EL^FM^DM^FO`CN^BN]AL^?K\@K]?K^?K^?K]>J]>I]>I^?H]?I^?H^?E[>D[>EZ?G[=FYV:@Y:@[9@\;A_7@]6AZ5@[7@[6?\5?\5@\2?\1>Z3>Y4>]6A_5@a4?_4?_5@`5@a3@d3Ac4@d5Ae3?c4Ad3Be3Ae5@f6Ai5@g4@f5Ah3?f4@f2@c4Ac4Ad5Ad2?b4Bd6Bd6?b6Ad4Bd4Ae4Ad2@c5Be6@d6?d7@h6Ad7Af4Be5Cf6Cf5Cf4Bd5Ae3Ag3Bg4Ag5Ag3?f4@f4Ag5Ag3Ad4Bd3Ae4Ae5Cf6Dg5Cf4Be4Cd6Df6Ef5De6Be:Dg;EiFdCLh@Lg>Ke>Jb=Ic=Jd;Hc;Fc:Eb;Fe8FcGfId:Hc:Hc:HcKe@JdBIbCKcALcBLbDLcCJb?La@KaBI`AJ_AI^AI[AI]?I[?J^@L`@L`@L`AK`?KaCJa@Ja?H]BJ_BJbEKdGMgINhBLeBLb@KaBL`@L^?J]BM^BM^BK[BKZ?GZ?GX?G[AK`BLcCNfBNg?Kd@LdAMeANhBOi>Mg>Mh>Nh=Je;Ie@Nh?Mg@Lg@JeAJfCLeEMdDL_CL^EN`AL`=Ja=I`>Ja@Le?Lf@Kf@If@KgBMlALkCNi@KhALiBNhDPjFQlEQkGPlDNjDOjFQgIQfIQeLSfMTgHPbIQfHQfGPeENcDNbFRfFRfGShHSjGSiFShMSnNWpNXqNXrNZrMXqMYpNZqNZpMYoO[qO[qP\rR\rP\pO\oP[sR_qTatVauUarVavT_tR_pW_rV^qU\oY`sY_rY_rX_rZatW^qX`sY`rW_qW_tS[qT\qT\nT\nT]pT[nT[nU[oSZnRXjTXgVYgY[hRXjOVjS[nRYlV]oX_rZbuZ`tZ]pZ\i]`mbfoqqxv~}y{ycheT\XGXM1!2#/G6;QD'<2.&%)"#52%86/,/.,& 512++#&'()!8*!>,)E17$9(4#5$)@04'%<,/ 5%(?.0[sW7Q1@Y:1I-;T6A\?4N17U5EeA7Q-=Z3C_6QnE,D%/H*Me@9S(\qIRhA^yN`{JkRNh0cFIe(c~ASn,Mk'`8b=Sr.Xz4_~6wOyR3KMg=NgF9T5/J.+B-&=)UkSWmVOgOSkRYrTTnJPh?Jd7=V-?X.*DLeA:R/7O,9T0X;1K)%>7R/Fb7Gf4Oo4yXUt1Yw1tHHg!Id[v'S]y$~QLk1WtI:Y7MhHD_ACb?QrIHj=Lk@Ni?YyOc\C^@'@-(A47OB'>1/I5(=0'<22,,*?PR-;?.-IXY(;;68I>J\KdueTfX]qd1A8O_ULXP(9./@28J;PeMPgL5M.1J+F]?9S19Q/I`9_tKRg>4I"j~[GZ?Zo_BYHxR~;VhUkXniqcS.C2G)0J-GdD\|ReXfUFj/^Cv[TyAB(N!)O 6Z'Ln=2R 0Q@c*Su9cJ;b#Eh/Qt@ve`Qb}UgXubwOfzS{hm|]}lyamxa~ozjxWr]fuRqizgprZl[okViTprYl^n}uVdZhufo|lzswrizeyuuz)B6Ga]DTR9@S:AU;AV:AX:@X:@Y9?Y9@X9?W9?X8?Y7@Z5?Z5@[5B[3?Z3@Z4?\2>\4?]4?`5@`4?_3>^6Bb2@b3@c3Ad5Ad5Ad4Ad4@c3@c3@c4?f3@f3Ac4Ac4Ac5Bd4Ad5Bd5Bd5Bd5Ac7Aa7Bb4Ab4Aa4Be7@d7Ad8Af7Ae9Bg8?d6@e8Be7Be5Cf5Ce4Bd5Ce4Ad4Ad5Ce6Ce5Ce8Ae8Be5Be5Cf4Cd3Bc4Bd4De6Ef3Bd2Bc5Db5Dc6Dd7Dd7Ce;EeE`=Ea>E`>Eb?Fc=Db>Ea?FbGd>GcHcALe@MeDOfCMcAJdAKe>Hd=GbIe;Id?Kb@IcBIaBH`CH_CI`DLaCI]CK_BK`AI_BJ_BH\BIZEL]DK]BJ\AJ[CK^CJ^DL_BK^CLaEL_CJ^GN`FM_GMaGM`KMaJM`HOaGM_EL\DJ[FM\HO^GO\FKYGLYGLYHL[FJYFM_GNaELbDOeDPeEQgEPfDMgCNgDPkCOiDPkDOkBMiDOiDPjEQlHRjIPjHNhJQgSXlWXj\\l]]oOSeEMaDPeCNdCOeDNgELhBMhCLiCOkDOjDPjDPjDPjCNiEPkDQiFRkIUmISnKRkKSgMSeV]mbhv^aqSVgJReLTfNVhNUhNViLSgJReLTgKSiLTiJRiNSkMToPVqR[pT[sR[rP[qPYnOXmOYmOYnNWmS\pQ]qR]qP^pR`sQ^pPZnU\rU^pV_tX`uX`rX`sV^qV^qV]pZatX_uYarY_sY`sX_rX_rX`uV^sV^rU\qU]rRZoS[nV]pSZmSZmR\mR\mS[mY`rV]pOWkMUiQXkSZlSZmU\oZat]du]ds\dp\eo\ipbjmntsv}vkugmvgoug~t{}{z|xreogU^[\@1K09S6>Y;A\=3P69U@/I26 ;Q=?V;D]8Ib8Fa4r(A#1G0*=1.A9.B9,@4$7,0D7.F2:R=5N:#<'7 .I1;Z;GhFRwJ9]6>^82Q,=a?A_>2O38Q:,H7"=--8Q'>Z@5S4/L.5S3@_=A`7Aa5Hd7Fc3Fc3A^/Hh?7U.3Q,)G&<5R62&C*,H--J,3L//I,F`C7Q4@[=jdFaJdEKbCFa=E_<.1(-)-+'%'$ 30%;91/)$ &'.*>SH8I:5K5@V@*1$ 7*-#' !300* 5-4(8*/#4'+?64)1++$3.6'/ 0'"712+'=27%7Q82L,2M-6R34L/0J-2L2?[B(F+)H05Q:'=+":+ 5)-B7,!"7./,,'' 8KD!5-4(2$'<0)C3)>3(=5')=21H<&%% +"51,?91+,),,-,2-0+*%(")"'&<2*"2&":+&>.;'9) 7(";()B/+B2)@.$;)>T@?VB]wXOkF:S6>Z9+B,'=-7*$=,5M89S<1G64)*'4GG&76&'kxx.=>5HB5K?%91'<40% 1'-?/ 4 @UBH[HDUD';E9':H?0<4+8/MZQ=J@,5G2cvSMb6Vs:Pj-Me$Od#Oc*Ob0Ti;Uk:Qj2Kc%i~@Uj/g|=e|3u?{Ha`o;$+;)BQ>XjIDW4@U/n[Nd9\nC`pBXe3]fs>ozETa1(63E {pOcELk@Qf y9m)x2ԂCX1 7Q,B^<>]34T*0Q"Z{GQs8Jl0hVC5Z1HnERqELk?Mk<9U&;Z'>a)Wz?7["#E$E2T)LlC9V.[vNtexfgTɟYgKS`F~nv`y}|o_vjH]EBYHPgWZp`MdU_slL^[JYV`nexxdrdozimxfsk}vj|cxn\qb6NF4IK=OWDT\JX`HY[YlkEYXI][YnmRffZkm\kpRbd`pncrokyuanggsickbjrfksfpxkoxhuq}ssoovvuwryvsux{yxywxrxztxyussywzyx}|}zyyvvuvuvxywzzyyttstvrttyutsssrrooon~jll~k}i}j{kzjzj{m|l|n}p|p|n|m{j|{m|p}znzwk}yn{o}zn|{m|{m~|o|zm~|p}r|ymzn~xmxk~xjzmznxkzm{pyo|oxh}l{izi~j{g{gzg{g~uc~tdyi{lyi~vh~yk{xm~znyn}zm{xl}zm~|p~{p}zl{ymxvixwiusgspfrpeqqglmekjdhgbigcihdfgaeg^jl`ji`klbmlfiiciidgidijehiefidhidfgefechgdggchfbghdhhfggeghcihbggchidikfhidijejjekihnkhmmeooeoofpphrsmoqlopkrrlttozxsxtm{xrwys{|vxzsxysxzswyswxrzxtxztxyszzuyxuyxvyxvywwxxsvxqvtpwvrvuqywtxxs{yuwvsvuqsuostorsmpqlpqololmoonomppnmmkmnlmmmlkljkljkmjjlggfefdbeeddgdcf`_dbbg_`b`acaada`ebagbbga`d_^c]^c^_c_`eabddddccfddfdegefhefhffhddegggkjkjjkjkjifhkkkhihklkmonnpojmkllmnnnnmnlmmopppoonoonolnpknojrtmtsrtrrrqprrpqsmtuqwurvuqwvpxuqwspurosrqqnorplrqmpomsropqoopnmnlkkimlimlmmjjnjmhgikkkihihhgkniilkhjjjkkjjliijgghgfhhfhgghfegechdcgdagb`fa`f_`f\]c\\b]]b[[`YZ_ZZ^WW\VV\WV\[Z`YY^Z[`Z\_]^b]`e[^c[^c]`e\^c]^b^\cbah_]e\]b\[aZ[bVZ`V[`RW`QWaOV`JU\IRZKR\INZJP[LO\GIWMP]KOZKP[INYDKUGNYIPYGOYEOXGNXDJUDJUDJUBHU?ER?ET>DT>CS>DT>DU>DV>DV?DW=BV]6?_5A`7Bb5?c7Ad3@b3Ac3@b4Ad6Ce5Be2Ac5Be3Ad2?b5Be4Ad5Ad3@c4Ac4Ac4Ac5Bb8Dc4?]4Bb7Cc7Bc9De7Be8Be9Cg7Ae7?e9@f7Bf7Ae8Be8Be8Be7Be5Bb7Ab8Cc8Bc7Dc9Dd9Dd7Dd4Dd3Cd3Ac6Ef6Ef7Dd9Ee5Cc5Db5Dd5Cc;Gg9Db;EcEa@Db>C`?CaAD`@D^?D_>Ec>Eb@Gc@Ga?Ga?F`;E\Ha>Ha?Ga?Ga?E]@F^AF^AFaAFdAFc@Gc@Gd@GdAHcAHd?Ga=Ha=H`=IaGa>Ha=Ga=H`I`?J_BM`CPcCNdBMdAKdALf?Id>IeHd,:5#14 .0+?8&=)@ZB?\AFcH7S:-F,WpRF^>ZD0I1)B-7M82I.F_>\tJt_}k3O/.H0-B5 5+1+(:0)@03I:.I2/J5+G2-H4,H0RqU9Y8AeAEj?(G"4R3+J*!@!.N1;T@8P>;'!;+(?0)B-.F0,F-#@&4'?)+D+7R6(B$3N/2(>2&:1"6,-7M7AZ>3J.9Q4:T3)B#7P3=V77P-9S//I(7P5&?+6&2( 301131,+0-0.,)+(#$ ,-%501$RePPhN=V85N1-E,HaK(=/)91+("02 /2**.?8'9,=RB)@21$,A7 5, %95!512. 3-+&3,2) 4*)<6,?:0+,'/*/&#:1';3( (#+ ";*2J6*D)"=!1J,5O1$> ?\?,I0,K3"C+0.C5*! 4,-',%/B<**"! /) 4+%PoL2Q(8Q5+B(/G--E-0G.(?&)C&0K-7S,XuJB^4;AY4Nk<>YxYXn3Ha)=WTn*Qo+pQNj4Fa1Kf5a~ILl1Yx;fIa=uO{OxLzQl?Kd>4P,RkI5K1AXG7G9?TE,1NgNXqT6N+Ma;@Z23L%G\;>R6DW<-E*7N2)B%(=%4M56 $<' 9"29P4.F)%>AY77Q)=W-?Z0Nj=Yv?Mk.`}AiHfBYy.nCiE[zBVtG2O'4O+2M.;W:7W9(H'1J.7N4;U4PkHD]=BZ?;R?-D2D]HRmSNhM+D//F82-.-"/1''$2.+<80'2J;;RE&=1Sg^J[PXiX/!>OADTHHUKKUO?GA1:8%#$2/6B:1>6!.%&5&2A(ZpF[r=Mg&q>Uj:JTc+J^.FZ/9NGZ$Tg,[m2Rc$[o'Xn#xJzFUnPb32B IY=9J.1A$+<"',+DP5:H'@N*Qb7dsEN^.ar=}X6Gly?nllksr]m?rTE<|(Ks=mu$r"A܀[Vl-$> +9He?/T(2V'9\/=^3Hj:Dd6Ee`KEe4He:8T+;U+Je7UrEYvD_{Irswho~^~mrpX{_l}j\;?^8@]6@]7?^9Bb7Aa5@_4?^3A_5Da4Ba3Aa4Ac7Ae7Be4>b5@a7Ab8Bc8Bd7A`6@`6@_7@_7A`7@a7A`8@`7Aa9Ba8Ba9Bd;Ee7A`9Ba9Bc9Ad:Ad:Bd9Ba:Ba7B`7B^7C`7A`9Bb:Ad;Bd:Af9Bd:Bc8Cb9Cc6Db:Dc;Cc;Ca;C_;F_:Ea;Fb;Ea:E`>Ea=E`=C_@C_?D]AC[@BXBCZBC[@A[AC\FY>FZAJ^@E[@F\BG^AH\AI\BH\@F\CH]BH_@G_AGbAHc@IaBJbAI_?G]?I^=H^=H]>I^I_@H_=F]>G^@Ia>J_=I]?L_>K[AL_AL`DKbBJaBKbAHaDJcCIdBIbCIb?Ja>I`>Ib?Hc>IdFd;Da>Hc;Hb:F`2+D.C_FPmOT?3J5+>).C%5N(Ok<@]2AW?2K42H74J?+A5/E9/G7*B/+@2-@4'6%$>)#<&OiS5O;4J<)C04M92J6"9#)A(9R3Ga:=#21$""5..!!9)"8+-@50E;-B:3+*B:,$3&6(*B4.!."&>XCC`G1K/'?%$<"9T8?^A"@$9!?!!;%#9( +#")(" 31  $ 6H@ 6( 7+%=/7)+@5)@8/C?2.&!!71#66,-.-2EB!42,,%# 43"5,0(0(2(3*4))>6.% 50+##8.&<0)=1-B4$;*%=--B30D4(A+LcMKfKJaG9R7?W;+D,)B*+D.*B+.I1+E,,F,2L0+E#f^/H&0J(@[3.JYKHXHHYIPdLYmOE\5Mb<,B!,@#%/#$-$->+/'9?Q5"4?O/1 gyYWuPKjAB`5Jl:3VpZp\]zJTpBcSn[Hg4Lk8bN?\*UpCo]qa}jkt}dv[pUwqvdɤvme|_VkW;NF6JGHYZ^lm]liaqi_ncmqVgZO`UVg[QcTRcQoime}zpzL^`FV_?PZGXaAQZEVbK[e@QVL^]L_]L]_I\_/BDHX\VgjI]TI^N]u^e|gy|NdO;S;XrSc}\I`EW;CV:@V8@S:BW;CX:?UCdCdE]>D]?D[?EZACYCFXEFZEEYGE[HD[FF^@GZ@FZ?DXCG[CGZDH[CFYCHYBHYCJ[BIZFK\CIZFK\GK]EIZFHZDGZEH\CF\AE[@G\AH\CI]BI\AI\AH[?G[=FY@H[?GZ@H\BG^AH^@I^?G]?K_>I[@K]@K^AL_?J^DK`DLbEKcEKbGMcFKaDJ`DK`CK_BJ_?J_>Ka?Ia?Hb>Hb>GcGa;Fb>IdU8IbBlh6M92H<&96.':PDlsUsYPnOQnP9U79W9]xXTnO[tVLbF%;"+7!C[A7M7,B-0B,:Q3TmI\wN1M$7Q6E]H(A- 7*%>1'?2*@15L<2L:">*47S: <%17"'E.DgI@^@*H,1M5.G4- $+ 3I,'>1)#9-,2*B-%=#9R3SlFC`3bMRo@Ol;XtG6Q);V4?\=A]D8T;/K2!9$'A*.E0'@+":%'>*-G.@ZA1I/*B)/H+:T32L)Jd>5P&TpH>Y60K+,F--F1-C35L;,6%PeOPcEQeH@R:;S7;U19O45L1/E,:P9.F.+D,(A)*C*-B,.C3*=4)$0.20!53/.2/%%$%* !*0!!7# 9"*C.!5*&505ED'& %(+ ..%&'73*>7"7*&=/$;.!4*%60&53'"!5K<;R@8"6%>SD,?5-D84*4-(A8*G:*/%-%"8.4%+E/,E,!9"$<" % $@''D*!=$4/E1,0*$#+*&65%$$$%%$&+85!3)%:.(@/.F8)>1!6-$42 0,'74*<91D?0. ('#2/'<8/*,(%"$30)<9'<75/3."931*-(005/-&%8/0&(,!%9-3$1E7 3$+B/6N6.I/E_E %304!%:& 8#1I3'@*3L45M57Q3-E*)B&-F)*D%LfAGa;ToD#B7U!Qp;7R#Ni=?Y/Ge8=Z.A^3=Y34M+Ic:C^)Hd"^|5Pm(-H6"?Ec-Je6F`6cyTUhH#15C%IW8csMk}WN_6x`Pe8F^00F@R*?Q,2C#0E#(> (A$4(F)+I'+I"Ed:Qr>Np:=_+: +Dc6YvJJg9ZwERq=fOqZ> +$AHd;Sk@lRi}Ox]vrdmlkvl\OhJ;P<.B6?SIBUL?SHMaQIYJWiXShW;OA]qcShYVkYezeby^PfPPdUG\UAUV\nuL\i3DM6HP5FQ?R[FY]I]ZOdZVm^0F=7KJ9MKCYPLcQXqRGa="=D]:Lf@a}TbRqhhmUn>H\8bs^guihumn{rm|pivjhvgr~njwdxmr{gq{jlxfq}lrlr~jr}ivnvp~v}{u~{~{xzr}vr~q}zyxzvyu}xlx|oyiu~jxgu~sn}jxvuws|pzoyxut~tstupznxxwq|s}t|v}t~u||~{~{||x}zy~zw~zx{xx{zvz}uz}oruz|zvzrzzzxzxu~q~|rzwm}zpyxn{{q~{o{p{q~{r{zp{{s||syyrz{uwyr{zu}|uyzqwwo{xqyvnywn}zn{p}q}q|q~zpssspqqvq|ns{o}qyl{ug~wj~vhxrcupawqcsm_sl_tn_tmaql`qmasmbtodupdupdvpapn^tp^sp^pm[tp_rn`rj]ok^qn^nk]mj]nh^lh_mjamiaokbomcklblm`podpmbon`npcoocssfuqfyuhwrgyug{tg~wi~xk}ym{xk{psq}m}o~op~n|o~r~rt|}r}}s||q~{q{yozxoyxovwn{|rxzpyxm{o~zn{p~zo|ym{xnzwoxulwsjttmusprsmstltsltslrrmprmoqmnonnnnlmjkljkklhhiffdffehhiffgdddddcccbeeeeefeefddddddccdcbcecdfceddedddfffhiigghefgeefggehidklgmlilkgjielkhookrrnvvqqqmsrnsnlrpjsoksqlusmxwrzwrvsnwtpvrmurlzwq~|r|n{oym}wkxoyp~xp}xo|xmyvk{vl{umzwo{xp{xpzwmyuk|xm}vo|upxqk{to|uovrjvsjwsjuqhrnfupkupjtpkuqjwpiyqkyoj{rl|rjxohyqjxsktqgpmesojvqktnjvoivpkunirkitlivnksmjqmiqmjokiokjnjgnhfokholgolhkhdkhclhdkkhjjejiejjenjfnkfmhdnfcmhdnidihbhjdhicghecbccaeeacb_a`_`a_a_]_`^_`]a_]```b`^a^[]]\\[\][[][X]\X_ZY^WXYXX[VVZWWXVVZQPVQPWPRVRSXPRVJNRMQWJNUHJSIJTHLTFHUCETBFT=DQ?FT=EV;CT;ASU:@X:@YE`C`>Db=C`>C`>C`>C^E`?Fa@G_?F^F[?G[AG]AH\DJ^FH^FH^CFXEGYGIXKK[JJ\JI\KH^KI^GI]FI]FH\EFZDEXHI[HJYGHWHLXJM[HKXGIWIIWIIWKKZJJXJKZJJZJK[HG[FFZGJ]EFYBGYFJ\DK\CH[DK\AGZAH[AI[BI\CJ]@H[BJ\AG[@I[@H[>KZ>I[?J_@K_CK_BK`EKbDH^EK_GL`CK\CJ\DL]CL]DL^DL`DJ`DIaCJcBIcAHc@Ga=H_=I`I_>Ja?J`@G`AHc?GaIb=Ha=GaJ^@Lb>J`I`;G^@H`@I_@Gb@Fb@G`@I[DK\EL^CK]IObHOaGNaGNaFN`JObKQaIP]JP^KQ]MS^NS_KQ]KQ]KR^MS_NR_MQ^MO]NP^QVbTUcSVbRT`RV^PU\TX^VU^UX_[[`WX]Z[`\\b^]f\]e[^f\]a[\b\\dYYcYXfWYfSWcMRaKQbIQbGNbGPfHOjIPkJQnIQlJPjJRkKSkKSjKPfNTjPWjOUhOViMRcRTeTWgQTeRTiMQjOTmMShMRjJRhIPjFQlEPkFQkFPkIPnGPlFPkHRmHRkJUmKVlKVlNUjRYnRYkagucfq^_kcap\[lVZiPVeOWiPWkVZpUZqSWkOWjPWjPWjPWjQXkPUlOUkPVnSYpSYrTZoSYoRXnRXnU[qV\sV_sU\qU^sV^sU]rT`tT`rR]oS^qS`qVdtW`vR]rR^tT\sV^uU]tUavZay\`xYbxWawWbxU`vZawY`sYavS_rR_rQ^qQ^nPZmNZiP\lSYnOXkSZlY`nW^nZasT[nYfoboxVegVf\UhSZnTK^HAS@:Q?5I94I6M`KG\@SeH^lPN^BI\@J\A.D*F_BSlNIcI.F3'>4$842)=TF3L61O0?Y9B^?(B&IdGAZ=@Y;JaD=S6I`D,E)0H/7M4[qZ 7#9NgDE_:>X5-K(2O0,I1(D/6M?,E8+C62J<.F71I;4".G270(;OC9O?8L92D25G5;N@&9/5)"7-*JbP3J7-E.GcBOlCDa3Bb0Ff4Ed2=]-0O$'D 9#@&3&/I52!."7'2$6L:,G.-G.9 5O48Q42J,1K)8S/7R)Gc/$9.7ID2.#64 42,+*#&!&5F<,"((:-4K:/H4(@.4H=.=:,<< " #)+ .2,-#"4+1'&=17KB2*!1,)$$* 8!!;!.H2>VB5I9,@2%;.#:0%=3/)+*B5,A6((=3"7'@X=3M*/I.15>V:6O44O4$<'1G4I_M, .?9((()$"")( (4/,=14&3K;'=0$5+-'%"$%,;8+(  +.+)%$ 1))91*".=:21$#*+1/43,-(*./,()" 3,,"()2&';.*:4*5$3L60I3,F/0I4,1 2!%:)'<)$9'5 /E08M8.F.2*C.)@+-D++F&5R*RnDA\59U+9U'B^52N'5N)=U54N*Kg?6Q.5O-*E;Y~TZ|HwAON^{/nB]w4`v9ey={SuH~MdvCWxOPh'`{8XK`%G[#I]&Vk7H_,Sl7^w:]v;Un4Tl7jNYu=mPlQ\{>ofqfkp~QcZVsa_hegsfbqnjifzhownpy]erihdog}exnjck\tts{tmhe]hkyinnf^S/E]Q_Ha\^[dfONRPKXedi_^SCO"ed]edo[ge_K\S^h\`fvq0Ww k7~Df C6{.]OiSl)BZ4Li?TqA:V%Ts?wdTq@tcTH:NH?SOAUV@SW8KO4HK*=C>QW2EF:OE[rZzd}`4J:&=-OfSRkOSmGubUuBHf4kXdN@c(Mq2cFc@`upp^@XD\DZC\?DZ?CY?DZ=CZ=CY>C[?D[?EYAFZCI]CI[CJZEK\HK]JL^II[II[JIWJJXKIWKIWIGXJGXMI]KJ\JJ^JH[LJ]IGZHGWKJYJJXMLXNNYKLWNNYLKWJJVMKWNLYMKXNLZOLZKIZOL]IGWJHXJKZFIXGIXEGVDIWHJ[EIZFK\FJ[EJZDJ]BH[CH[CJ]CK^BJ\BK\?HX?J]DKaBI_EK`DI\BG[FH\FJ]BJ\EM^BI[CK\DK\GL_GL`EJ^EKaDJ`DJaBI_?H]AI]=J\?J]AI_BJ_AI^BG`BIaAJ`BIaCLcCKcAI_AI^BJ_BJ_AI^AI`AI`BJ_>J_>J_?K_?J^>J]=H]=DZ=F]=E\=E\F]@H^AF]BH^AI_AI_@H]DJ`CI_BH`BHaAG^DK\EK]GN`HL`EM^IPbGN_GOaIMaKQaJP^MP^NR^PQ]PR^NQ\LR^JP]LT^OR^PS_PQ[TR^VXbPT^TWbVU`WX`VW_YYaWY]XZ_Y[^Z[_[\`\^a]^c]^c__h_`f_`b\^b^\g`]iaam^^m[^jX[gOUcKQ`JQdIPeKQjJRmJQmKRlLSkMVmLQiNTiNTkOUkOViQXkPWjNUgQViNSfOTfPUkNSlNTnMSjMTnNTnJQnJQmJPlJQmHPlJRoGPlHRlITlJVlNXmP[oYcvU`rZew]etcku^ekW\c]bk[^kRXdOWcPWiQXkRXoTYoSYoRZlPXkRYlQXkPWjQWmPVnOTmNVpQWoQWoRXmSYoU[qU[qV\qV^sV_sV_sU]rX`uW_sV_qV_pT_pVasXdvUauS_sT\sU]tT\rT\sQ\sU\s[`|Y`zXb{V`zT`vZbyXcvVawU`uS_tR_sP]pQ^oR\oP\lU`qW_oZdmYbj`im_gnalrJY]UdbJ^SMaNTjNNfHReL;M:9O<>UCUiVEWDL`HPaHSfKDV>DXB>T=-D.LdHE`B=V>%>/"842. 6-F^O2N5;X8QkJ=Y9;T7G_E6N92N7 <& :),#=.:UB*F0-K4%@.3J<6JDEXT+)?,,A0(>)#7%4H=7LA*:0 1*,+(#'$)?9'*'8)"4#)<,hxnFVQ"1/+;2.@5+A40D9#5.$$)'*(/,1-...-(+" 4)*"2)):00($J`L)A)D_AE^G6J72G6*?0!7*3'1)-')D13K;"9+XlUVpKE`41M).H%H`:m`?Y8+F'#9"5G47J7&&4/"# #()$'!#"#'' ,2#,B0,B4/((%!"--   )'%52,&'#4))$8GA*$*&*(&#'&('"""#*'$75 3, 5+0'"3(%6,"3+%!1*(?0 9%)A-7P;.G4-F3"9(5$%:)#8)4")>-.?-1E0$:"+B-.C0+B0(@(&B$Lg@He:Ea:A]5Jf>\yP,H$+C#*C$#>;V,;U.4O%7ShC\~)p3HFYn턡^lsulllkpg`vumrՆosZx>qrxpheicYhLT[\bSLZOvCyEPuAs>OMVYT^jqehWd\XZ`YIIN\R^RPebSVWWPUfNORELKJO]MC`mIO\G1L*D:?COM^VIhUjqigthdiJNeeXgZX0:FR;ITh`UYPOa*9\a:tDLnFTqWuPrDc Hd_Sv'9bt_k9%5 *G7X&YzLPqC-N&)G''F$-K%6U+Hg;&C)B&HaGgeB[>Ic?`|ROk=If6`MSq?dO@\.9U)gTXrEXqE`zMF_67P&A\3RlE6R'dVYtK{igax^tqTnMLgAjatj^wZaycYq]TlU`vaYo`YocCZOIbT;SD5M<.E5LaZ.ABXlo?VTE\VIaOIcC>Z,p]ja6O4\v]UoOGd9dP:] Bb)Ok4?^"fAa$Rr1Oq*@a^Qr!|YdNn^D`8e~c[qc`to`qoTe_`rgkynprm~o_reVi]\nceuk]ocVf]_qh]rhWkcVjbMaYGZXDW\Qcrcumeyi}auj~hzi{h{i~exh{j|polsdylnop_pTd|^p\ocx_t`uas^q\niz\m[nXkdv_ri|ew^q_rduXictcucvl[ln~gv]mhwhxl{]mapfuo|kwam{gp|ir}pykt|ku}gqwmv|qv~nrypv~lszlx|s~ksfn|_ivgqfq~bnzdn{dp{gv~n{n|husyyulzkxmzq}vw{sz{yx~~|w|{z}}x}yrvtptuuzyovsnsruyylqomqoyyzsttsvuuwttwssvqooiuvpssnqsnopjuvoqqhrrgwvhuqewtdtsbuscyxgtrbwugtrdywiuteyvgxte|wi}xh~xfzj}vh{xi|yi~{j|i}k|kortppprroqnoponn~l}lnonno~l~l~lnnj}i~k|jzjziyj~xj{ui{vjzug{vexsbxi|ugws`tpbqobqobolaliamibnjfijdffafebifbjhdjieghbhgcffafebdd_hgbffcdedccbgheigdihegeaggaghdhidpplmmkkjfmlhomiqmjsomxtpwtovsovrqvrpsokvsnwtp{xszxszwq|yqzwqyvo|xo~{q}r~{n}zlyj|n{m{m|n|p{o}xmyn{o|ozk{m{mymxjykykxmyovm~um|rl{skwsgxuhvrgzvlwshwshxujzuj|ujztiwj~vj~vhwk|vjxqhxng|snxrkyrlyrlypkzqiyohzojzpixogypizrkzplwqkxpkwpkwpjwpkxrluoismguljsmjplirmirnjrnjrjgsjgshgukjslhojfnjenkfomgnkflhemiffecihffecfdbedbdcacb_a`_aaa__``^_]]_Z\][\]\\^][_`Z^[W[XVYYX[YZ[UVXSSXQPURQVSRVQRVRSWNOTLLRNMVJJSILVHJVEGUEHX@DS>DR>DU>DV=DV>EWB^=B[=@Z>BXCX=AZ=B[;BZ>C\;AZ;@Z=AZ>DZ?DZBCYBCXBDX@DVBBV@CV@CX@BXACYCFXADXCFYBFWDGXFIXJKYHJWKHXJHWJHVJIWKIWJHVJHVKHYIFWJG[JI[IKZEGVHJYHHXJGXJIWLMXLLWMMYNNYNO[MLZNM[NM[NM\NM[NLZKIXLIZKHYJH[JIYKKZFHUIJUIGVFGTGJVIKYGJYFIXHHZFJ]FH\FI^BI]EK^DJ]BI]CJ]BK^CK_BI]CJ^FJ^FI\HK]EHYEI\FK^FK^FL_EJ^CI]GH^HJ`IK`GJ_CI]CJ]DK^BJ\AJ[?K[CJ^AI[BI]CJ\CJ^BH_CI_FLbCJ`BH^BH\AI[DJ^BH\CH_AG\BI\@H[?GZ?HZ?HZ@H\AI\@GZAGZAG\@F]?E[BH_@H[BI\BI\AJ\DJ^CJ]EJ^EJ^FK_DI]GLaEL_GK`HMaGL`GM`HOaIP_HO_HN^JP`NP_NR^NO[NQ]RQ]OQ]PSbNS^NR]QU^OSZ]`ebejllo^_bWW[YV]\[b^\c`]d_]e__d``e_^c``da`eaaea`f`_fa`fccecbha_hcbmccpccoedp^am[`jOS`HN^KReJPgMRkHNgLSjKQiLQjMSiLRhMSiOVhNUhPWjRYlOViNSfOTgNShNTkOUmOVqNSmNRpNSoNRpLSoMTpJPmJQjGQjFPgFRfIXkQ`rbn~_j{^jy]jycp~^jvcnvbmq`ij`hlX`gU_fRZdT[kRXlUZqQWmQVmU[pRYlQXkPWjRXnTYpRXqPYoTYtSYpQWoU[sSYnV\rU[qT]tW_vW\rX_tV_tZbw[bwY`tYbsXarYbuW_rYavW^sW_wV\tX]vX^vT_vSYrX]wW]vYaxWcxXaxZbzVbxWa{Vc{T`xS_uR`sR`qR`mT_kXdoWei\gefsigtjR`UHVONfOXpUI^GFZG3I8;R@,D32F7AVF@R?BU@>Q>8K8

    -C1:R=HbIC\C7P<%<35431!81a}l[w^LiJIcAE^?QiNBZCE[B7O4?S8J]BXqRLcFC^=;R2[sQH`9?Z0>X/D`8IgGA^B/?=2@<7B;>J?-#;((>+)?,(@.+C2(B,/H04L07O/;U.Ic;Mk;?\19S0;W7*A'%=$6, - 0& +)@(1K,G]C7I42C.4G16H42C03F33E6,B3(=,$9+*!0%,$,#)$($'"+A5?WDUlVG_Fey^HYE3@5#!#HXR:JC->6%"%&*1%+*-'+&26(27$/2"1%4F15H6(:)*:-0H]I9R::U;/E1'<++=,3%."*",&(":)-B6"7)"ReLTlAOjBXrJl][tKBZ9)A$1C-2A0*;)*+&#! ! +&+ +" 1!%8%-?/UhZ!!&& #! *+(;;,+!0-+%"  6E;FTN,&&#((''"!#$$,-21&$% '7/&70 1)*;3$50)($801#+B/3M8$='7"'@.!8&8O> 7&6'!4&+@1*?0"7'-@-);+&;-5J<,.G/E^? <5N/8Q/Gc>Hc@.G&.H*#:'>+F7R'Qm>Tr8_5q;S[TYNBAPO}CG{>Mx?|Dt>t>vAy?x;DF~@z:PbJEL}GSURS_S[ZOa\[^UPH~C~CG~DWMN[[US~Km;xGq=VXL\WTPYTVXRaPQGKDMDBN2NIP:]AKLPNAQGCAz@,*0Q1*=Aaj;NPGPYIy,p~%HY\VTF;G=K14NOT[DPK\bH:OZZz4!@(E Uu*Y{]B>aMk^h"aHgr%x/NZTqtC]Gf5[yO-J(4'C(/J(Ea77T"Gb5*A!2J+8O5gd-E(+D#>W4Ke@SlESmF]wSVoKHbANhDRnFC_UMCYRCYO0J8(E(kaUuDIh8sj4N.*E!ET=CS>CW=DW;BV:AW;AZ:@W9@X:@X8>V9?V;AV:@W;AW;BW;@V9@V:BW;BX9BY:BZ;B]A];?Y<@XCY=BX>CX=BV=BVBYADY@BW@CVBDXAEXACTDCUCDXCEYBDXACVDEXCFYEHZFFXCFTDGUIKWLKWJIVJHVKIWIGUIGUJHVKGYKGYIEWHEVGGVEIVDGVFIXHJYGIVIHVIJUHIUHJVKMYMLZLKYMLZKJZHGTMLZJJZIHYJGYKH[JJ\JIYIJYFHTJHUIIUHHUJIXKJXGFUIGUIGWEFYGGZGH\DH[EG\EI^GL`DJ]CK^BI\EI]DI\EK^FH\HK\EIZDJ^FK^FK^FK_CH]FG]GH^IK`HK]HK^GJ]EI]CK\BI[BK[@JZCI]BI[BJ\BFZAFZDI]CK]BH_CI^CJ\AH[AHZBI\BI\CJ]BG]BI[?GZ?FWBIZ@GZAHZCJ\CJ\CJ]BI[BI\@H[@HZAH[BIZBI[FM`EM`FJ]EJ]GL_GL`FM_FM`GNbFM`DI]EJ_FK_FK^JP_GO\JQaOQbNQ`PT`RS`PR^RQ]RR^PTdQQ_PU_VZ^Y``Zc_krjfkcgldlqkghgdbgcbghelfcljgmjjpkjojfmhdkfbkeajaai`_gcbgedjb`iaalccnbboddpbdp_dnZ]iPWfOTgMUhJPgKQhKQgLRhJPfMSiNTjNUhLSfOViPWjPWjOTiRWjQViOWjOUmNVnPXoLSnOUpOVpOVpOVqPYpR\sGSgETdK[jSfrXju^pzYktapzbo{UcpYgubnz^mvxvo}}XfkP[eU[lRYlQYoS[sRYnSYoP[lRYlRZlRYpRZoRYnRYqQ\rT\sR[rSYqTYrV]rT]qV^uT[rU[qX^sV_tYavYav[cuZbuXatX_rZatV^sX_uV^vVawWawW`wS`tVauV]sW_wU`vV`tX_uZcxVdzXdzT`vVcwUbtWdq[isSbhbpq^lm[jcXiYfv`k}ej}fCUB;PC;P?JaMKcLAYC4J8DZM6J?'@2+?4':04H;9K9ATB9K9:P?9OIcH=W9F^D@X?AYC:R<:O9.?+W=3J21H4.E3%8-2(!5,"5.!7+,C3,D-::U>/L6+C./F5-9$:S:IaK;OC3G>2A>2?@0>@(33/:71;4CQEARAI\HCYFI`J?T?(@+!7%,E3*A,-F*9S0Gd3Hh7Mn=Ba2;Z/(F"2L/,G*%C'9 9"'@,(@-,,D.&>,#:,%<-*5,D'5O-;T.XtG=Y'B_0Hd95P')C)BF`67O 8I#HYA':%QhM8T/?W7J\BFZ@*;$GVA6G40A/.?1*@2'<-#7(';0- 0$"1("2+(910D6-9T6e`pkF[>I[D9F8*30 !-952D97H?*&!!)*&'$$''**)*+3D2D\?OfK\r\0E1 5#[o]NgP]v`@RB(8)'8)puE\M$<,I_SL`U,1F:F\P?RF$nmxnOh@ZtM\vRXoMPhG,B'&=%+=,0&:&%6&.&-%#1/ +##"$*%/;69D:5C3?Q=[nZ:L;1%+*+,(**+-+.+%86.,,*.()".B6RgY-=2+=1,=4,(-,"22,/%+#% .//..,(-#7G=/%.$0() ,"7$/H0. 6'.D4'=-0$->6/@:"$')>0/E26M52I06M24M/,E'&?#&@#0F+/F(0I%@X2Ga2\z?\|L\Gf._)Ryc2e5i7pBvG{Ds?xG_1jQ O,R;B9k|&f{)p9GYMKy9SKNDoBDF3&[m[tp%=BUUOBMCZ_`wmT=ZxT=;ZTJnSwMqTu"6W HiPx9&F}6=[ &`KMkz+G* +0DT>CW=BW=CW;@X;AYE^=D]CX@EX?EX?@UABX@BWABW@CUADWACV?BUAEW=CW=CWCW>BW@AV@CVBCXCDXCEWDFWHFXCAVECXGEZFDZBCXCDXFH[HHYJIZJIYLKYKKWLKWLJXMKYKIWKIXLJXJHWJIWKIXJIWHFUFETFFSFESGFTIHVHHUHFUIHVHHTHGTKJXJIXJIZIHYIHXKIWIHVIEWIGWIGYJFYKIYJJWIHULIVJJUGHTKJVKJVIGTJIVLJXHGWFEUFGYDEWEF[EH\FH\FJ]DK[CIZAGWFI\FJ]DGYGJZDGYDGXFJ[FI]CI[GI]GJ]HJ^HK\HKZFIZFHZDI\CI[EL^BI[BI[CJ]CI\EJ]BGZBGZBG[DH]DI_AG^CJ\@G[BIZBIZBI\AH\BG]BH[CI\CH\CK\CK]DH[CH[CI\EI]EJ^EJ_AGZAGZCH[CI\DI\CK]HMaEJ]HK_FJ^GL\FM^GQ_JP_DK[CH[DI\CH\CI[GN\HN]NQbORbNQ_QS^PR`TVaUVbVVbVTeWYeWZ`ciieofivg{vyqzss|nrvoooofdigcmjgrgepcajdbmfdpa_kb`kc`lb_ka_ja`icaj``h__g]]f__ja`mbaobdo\amY_mW[oOViMTgLSfMTgJPgIOfLReLSgNUhMTgMThMSfPVjUZmSXkTXkPXjQWjOUjPVmRZo[cy_h}[cx]exgs\jxao|pqf}q_tvey|\nsfueuj|Pdc_upcys\loS^gSZiNViP[pP\qQ[rRYoN[mNYmNZnSYmT[pR^qP\pQ\rU[rS\sRZqU^rT\qT[qVbxW`wW\rV^rT\qU]rX`uX`uZavW`qU`rZdyYcwVbuVaxTbwVdw`oZj{TbsVarXdyUatS`qT^rS_qUcqUbqYgv\htXelWgfSc`Xha`obWgWar^gz_\qR]qSTkO@U?@RD7M;7O:AXC6O;5M=&:/%;/'@3&>1(>34I;ARB>Q=;P>4I6OhR/H39Q?5Q>0L84L<%=6"56*)#:5&<6&>.D^J7P7@WB0H3;TA4K8-A/.C10B08L5X0UsGa}TFb=!;.G1;T>/H2-F0&>/1%,B6&93#61*@5%=,6R:IeKKiQ(D.#<(1 1B[CFaG?UA0D6/?9,:;-8:+774@A5A?-;63C81D75G8G^N-C6-A6,A3*?/$9)4#&;* 5#E^F-F)=W53M%?\3Ll:Om:?\(Ih.Nl1džWȁZr5Pg>23K,a{UMf@3K-H[A3@..<)!2,=,0C1(>*&;)7O:*@-'-"3",<0.5L3TnMUoJE]8-"9%!:%(*,D)AB_22M$.F!,C#7/&8$2B/:O8SFO_U"3*,;5$0. +<1KWN3@4?M:EXCLbLK\K-#($!*($(910&)<50(.)( 2'&;+QeR+?/!6'AVE0'2,+' !  !0*.&%('<+(9(/!0"*&4#;6M10G*9R32G&>R17R'3MKh+Sq-Kl Ik^+X&i9h7wGnBi>c6yM{LyJrA|KWQOO\qmYccE};LYFUVU\vzC[^UYP]JQ[QXXYaNGFKFDzAyBLRwE\ZMfSrEUSg3y?I_]{1}6OSZWc_]Zb[TJBt4?ICcz!h+Dz-EQj4C]Pn0aNA^qLTr66N6(>)3K8C]E8V00QvW`+1P$aQRnE1 0K.'E)8V9.M-'E9"?6He*+P q`th=X9[s_]rg4IEFYYJ`\Ka_RhhOfi:NOVmkEYYQed[smI`YKe_WrgctMh`Upiq?W[D\`MclWms]pvat}PdnWkvK_mM_o]mp^{d`~_}^y]xRiVnb~]vYo_tre{byLh[xLgh_v]taxWnJaay[s[sYrRlb}a{RlUlQgWo^y_}SqUpXs]w]y_ya|`zmhe}UlWleyUhVk`u`t_uje|f~WqTkShZn_paqdtmbwbw]uhd|[sUkOeRiXn[rb{tc}gc|dzavcwaxf{ath{^si}ue{\rcybwkeyi{nsk]pcwlauZl~gxm}nhyUg~TgZm^n`m^kaqogveskysp~pn~sn|suup~rt~r}w|sw{v}}yy}{z~w|y~~~{{z{~y}~uxxzzw|uvxrtmqoqonjm~igee~d~h{gwewgxjzlzlxrfxrfyviuqeuqewpfuocqm`ok_uocxqfupepk`pmdsqgroholfqnippessfxvhzvjwtdvrexth{tixl{n|s{wn{wk{yl{ykzyj}{o}{m~{mp~yn}ym}yo|yn}{p}{p|p~zn{lzj|i{h|j~mzg|jyjznyo{szqzo|m}i~i}j{i}l~j|i}i~wc}vfxkyozpynyn}vl|yn{vk}ul|tkyoxmwmymviui|tg~ul|sj|rjvmdzqhzqhzqh|tj~vkyrezqfyqe{tizqg}vk}wlzui|vi|tg{sf{tf}uh}ujzrgyqhypgvngulevmfxofxofumfuogumeyphypjyqlwpkunirkfrkerlgolhqliokhnieniemgdjicif`kfbka_ha_eaba`a```_^\b^\c\Z_\X]\[ZZ[YY[VU[UTZTTYTSXRQURSWQSVLPSKNRILQKNSJMQGKTEIUFITCHR@GS?ET@GW?EU=BU;@U;AX;AW:@V;@V;BUC]CV@CV@CVBDWCCW@ASBCTEDWFCYB?UECXGE\ECZCEZDEZFF[LH\LJ\JIXIIVKLWMLZLJXLJWLJWNLXKIVLJWMKXJKVMMYILXJJUKGVJGUKHVIGUGESIHVIGUIIUKKWKIVKHWJHWKIWJHWLKYLKYJHVLH[LHYKHWMLYKJVJIULJWLJXKIWMNYMMYJGTMKXMJWLLWKJXHGVFIYGIXEGWEHXDHWFJYHK[EHZGJ[FI[DG[FI[GJ[FIYFHXFIZGI[HK\IL^HKZGJYFIXGIYCJZCHYCH\BJ\CJ]BI[BJ\AH[EJ]CI\EJ\EJ^FK_FJ`BJ\BI\BI\CJ\CJ\CJ]AI\BI\CEZCH[CG\EK^EI\CH[BGZEH[DJ\DJ^CI\FL_BG[AFYEJ]DI]EJ_IMaIK_KMaIO`GO_HR]GR\MV]GOZHO]BHYFJ^JL`GJ^IL_KM^JL]KL[HJYKMZOQ]NP]RQ^WTcXXaadgnxpo{lqiznrzoo}gjrehmiaafWT^SP\QO\MLZML[PN^URbTQ`SR`RQ_WVd\Yf]]hZZc\[cZ[cYYcVVbWVdZ]i[`kW]nTXlRZnOVjLSfLRfMSiLTfKReJQdLSfMUhLUfMUfSVkUZnW\oV\pW^qU\mU\mZauksksfmlus|ozsoyqs}xqhwfvasYmw`xxRnfOjaXjlUbiT\jPXlKXoLYqO[pRZqO[mNYmP[pP[kPZmO[oR]pP\pO[qS[rRZqT\qSZoS^rU`tS^uU\pW^sRYoV]sX_tYbwY`v[bwS`sWcwYgz`nbrash|h|ln\l|Zj{YizYjzZgxSaoUckYgp\jsbotYheWg^^q^rkYkPfxZXnKMd?>X71I/1G27G:3G67K8>UC@[H/G7%>-$=,%=1";-,B7:NA9K85K6>T?B[DNhOB\EGcQ0L:;WC:RD/D> 33 33!54/+)?62J:2K66L88P>4M;(@0(?/,A1);*/A,8K35K0,C32I;&?2"72#813+(@2-K20O6.(B/6M>(?.%>(:S=?W>&;'*;0;KG3>@/8=5?<=JF9HC6FA+<22C93D95F9,B."8%3J9 :'6",?U6X4?Z;(B&8 &>*"7$!7%%9)0(.%+;4"7F>[mbQ9BSB7E;#0'7H?L`RQiUZo[eygEYJ_wf"9(1!+C2'=.3J9OfPLeIF`=Fa:H`@AX=:Q6/F/@X@JaJ7M6.B++:,;I:2A/.C,5N41L32K55M66N2LfHVnKSnHE_9]wOlXmWjQYtCD]2;S,A[7b{[\tSOdD:Q5I_I9N?';.2%*91 +)(3C61?04G18M3>S?0C1.$/' 0)"2-)8/(:/'&(&*+*B-2J3(?*'>),B/,":)0#,$;-:QC4M9/I6.C8!7)!5$.D./B-%8$(;))<,2 e7l:O@NNn-xAUgkQ- 5L41K49Q:7P5B^=CbDB\H)?.XpWIbD5M84L45O2B[>AX_|>eVw)cI16S@:U<(B#8R/q\nOmqjJgFDcCDbJH_QAXML`]IYZFYYUjiMd_BXQPd]J_ZSgcbxs?WRFc[RlgJc__xu:RV=UYLdfmTlk4LPZnvJ_icyczcyRjzWtWugVn]vc}\x`y[ti_w^wc{i}ZtTqa|eb|xhmVld|VoVqTpXqfbyYm{eXtMjIhWsJeSkPh^yYuhdXrToQic{Zp`wbxd}`|^yaz_xRi_srcxSgUfdv`s[ndx^tdyLcYp\sRkOhNeG`PjSlNgPhXpWn\qYoSjQjRhZr^sav[s]s[t_vay`yZrf~e|i|ezmbxig~dzZpVjmtj}auXnOdQcPcUh^patfwewav^rdybwndwfw`qj{nmi|mqbvysm~zpzq_pm}kzjwfos~r}iu~ozozs|lu|tz~{|w{{}}z|xvzu}}z{}{|}v{{u~}u}zr}xp~yqyoyo{mwi|sh|vl~{t{zuyyuyzuwwq~}xywtxwsyytyxs{wrywptqkuskvumrqirrkqqiqpjtsoqrppqnmojpsksvoxxrwvnxxm{xjzwm{zq}zrw~|uyzyszwv}rstqq~o~{l}p~rznzn{o{m{j{hzfzgyg|iykvlwlyp}s{o}k{jl~l}j}kln~m~n~ykzk{wh|yj|zn{o}vi|xk}xm{xl|zm~zmznynzmzjyh}uf}vg}ui~xmythxqfzrg}tk{rg}vi|ti|vezrc{rcypa}ufzsdwp`}vg}vf~tf}td{sd|rg}tfxg|tdxl|tj{pf{qf{sgxpd{siyofyphzpjwnhwngvmgzqjyphvmfwmgunfsleuoiqjdokepkgnheieamhdjdaid`id`ea]ea_fb_d`]c^\a\X`]Z^]Z[[ZZZ[ZY[ZW[YW\TSXRQVSRWPQUPRUKNRLOVMPWKNXHKWGKVDHSDHSCHRBDT>CS?GT=DU:ASCW>BWBBYABX?@UAAVCDZBBYABWABVBCWBDU@DU?CV@CV@AW>DY@CX?AVACXABXBDXCDXCCXECVECVFDYFEYDEXDEYCFYFH[HH]HI[GHWJJXLIWML]KKZLJYMKYLJYKHVJHUJHULJXKKYLKXKIWGGTIHXGFTIGUGESMKXKHVKLWNMZPN[MKXNLYMKXLJWOLZOKYLHWOM[MKYLJYOJYOJZMJXNLZMKYNLZMLXMKXJJUIJVLJYOLWRNYLIUKJVKHVJIWJKZHIWIIVJJXJL\HJYKM\HJXJJYGIXHLWIJWGKYEHXIL[IK[KN]GIXGKVEJUGK[BIYAIYAHXAIYCI\FK_DI]CI]HI^FI]EK^DH\DI]DI_BG[AHZAHX@GXBIZ@EYBCZCE\AF\CJ\AI\AHZ@GYAEXGI]HJ]EF\FH`HK`EH[DJ[DI]DI\EJ^KTeKV`M[`MZ`KY^N]_M[]Q_^UcdU`fVF753#95%<4,B99OB/E6>UE;QD-C7/D8*?35K>>UD6M84K2D[?;X19U-WpK\vS-0I49ROD0>:.8;*46,57'230<=)56*94+=5)=20D78O;,E/5N9-G46$?YD@Y=5O/V?3K5=TA7O7>]7bLhNp!`e5`/c4\w>-%7 8M6(=,B"/C*+>-#2!8J70B,AW<1I+QiGKd@%=Vq?uxe-BAU6uko_vefo]e}UVoORiOEYF;K>1@4-B/'<)0E04$;#&(B&2L0B[RmGId@@W9?V:GY?H\CI`E>T8ERWuAaI]}EgRaOVwKJkCEc=;U7-D')A($,,,%5E65F4%7%,@+':%-?'9K53C/.&#0>0/=*(6#2?-*7$%5"0?,,?,5H4>T=>WDZ6=T02J#2D!/C&93F&7E&HV9FU;YkR1@T6AR6:H,;I),:FR0?L*[hF*74A+8jN^5^rC7N3FlWnWQh7[r;ctb[\^fg_gwu}xnoMjgqdaUS{HUXUi/f-]| @CdPY|Kjz;KLICD~;EQcJ[CR~?l-~?K78YIs4IWY\[QvvkJl;bw-Up!b}.`}-Qnh0n4t9l3nf0Fb|?^?aGfZ:Q2t/_r+4MXn\^^tC]F_ Rh4gScMDH^sLcu6Tv(6s*s/Fy=YFc/*C#)A.%9(:S:h`20I-&;!6J+4I/8M65J2I`>Nc>=T.E[.Ne2]yRs8Wu\Z{-j8k$F%C).H/0H#=Z&^~?Rt(KNJj7tGՇQw*)N 1S#7U.*E!2O"cN]|DJm)xMi5`-Fi BgXStGxmRqKSnP[t\=RDBVPM`]D\UKcYE\OUia>SM8LGYnhQjaIdZ7RH-F@D\[E\`=UY=UZE^^c|z:NS,AGDV`M`gPem[nyEYeMboPkvRo|D_nSiz[q[r`we|YqczRj~Pg{Wl]sIct[va|VqTpVpVnTk]t[q[tMeb{d}]quqqZudca~Ys_wb{XsXtk]xzqMgVoTmQi`wWq]yUpXp`yYqYqZof}SjTgSgZnbvZo[qYpTjQi`w^xWq^vKbJcOhVnOgG_MdPhPgOfLfOhPiVlTlSkWoWo^xZsWpQlYq[sj\pie|az\tf~\tXnZoj~ezavYqRgH^~hI^~UiXnYn[q^sWlauYoat_tdwdxex`sWnVldyRg~\sk^rbvfzm\r`v`tcyi}fw`pct_pgxl}brm}p~amkxo|p{mwynxvymyr~xmymynzmxmvs}ox|mtxlqujnpnsrotunsvmsyryqxkpkr}lr{ps}pt~jnuhmpjoqnqsrstuwvvwvuvuknklrnmqonrqimootzpv}t|ry{qxzrvyw{{uus||w~||{~z}|~}}y}~zy{ywvtom~kyg{lzm~orp~r}p|p~s~rss~r~s}|r}{t~}t~t{}q|}v{|v}}wy{txzryxp{zr~zozozn{p{l~xh~wfxi}th~uhyqd{uixl~vivj~vf~vg|ud{tb}te{rcxiyh|vevgvhziwgwi~tftfwgvdzjxj}sg{rgzsfzrf|uh~vl}tk{ri{sh{si|tixqeyqgwoevmdvnetlculcrlepjemhdlgcmhdlgclgchc`e`\gb^hc_gc`ea^c^[b\X`[X_^[\\Z[[\YZ[[X\YX]TSXQPUQQVQRVPQVMQWMPWKNVLOWJNXGLWFJUDHSBFQCET=CR=FR>EV;BUCW=BV?AWBEX@CVBEX@@VABWACXCDY?BT@BUADWADVACW?AU<@UBW>CX@BWACXBDXBCXADX@BWACXAFZBG[EI\FH\EHYFHYGJXGIXGHXFGYHHYJIXIGWIGULJWKIWJIWHGWFIWHFVHGVGIWHJXGHWIFTKIVLJXMKXMKXNKYLIVKIVNKYNKYKJWLKXLIXLK[IHWJHYLIYMKXLIXMJ[LKXNLZOMZOMZLJWMLYOMZPN[QN[MKWLJVLKWMKZNM\NMZNNZLKZMJYLLZJMXHJUMLXNKZJJWGJWFHUJLZKLZNR^JNZIMYHLWHLXFJVFIXFIXEJXBIWEKYBG\AGXEK\DJYIL]GI[CJZDI\DH]CH[?GY@JW@IWAGZ?GXBG[@FZ@FZAH[AI[AJ\BFYEI]EHYAEWEJ^HNdEKcFM`IQcRYlX_rO[nN\gL]cTghObabutpvo}\pmarrRaeSbi`mv_nycp|Q\hJT`BIXBFWCEWEFXHHZIL^HM]KR[cmij{kcyb_y\d}a\uYgerlj{ces``k`S[ZBHOEJWCIVJL]LL_JN`HO]IO]JM]LOaIL^HK^MPaPScKN]OQaQUbPR`QSdQTfMReNUhPWkOViLSgMTgPWjPWjRYlQXkTZoQXlPYlQ[mU\lV\mTZiT\iY`lalyer~zuixg|d}oxgZxuZxwennpi}zUmqQpoOnkEa\cw{]pwO\iQ\nQWnNXoKWpIVnLYlO\oNZlJUgMYhNZgO[gQZeT`lWbndo}al~lxdr[jzfwj|du}Xht]l~`ocsYjdwmt~{zwjdqn_tmQe\Ue[McTH]Jvrj}cOcHYmQauYNgEZsMm_I^9DZ3e}XAY<@ZD,D4,D75MA5K@2I;1F90F9(=4)>78MB,B7*@12I6F]GNgN:R<.G2'B2'B3:+&A2:VD0H:&<2!821*8OICZR/G<4J?3I=9OB/E8'>1,C62H90/*(,'1%.)H/6T<3Q;*E4('C1+C-1J50G45F=*86)45'27"-.%/2 )-%431?<3E=:PE@TH2H7D[J6L>0H74"'>'@X=LfF:T076.0-&7).!-### #$$!>PE.A0;M9 #8JAot8LA.I)!; ]}:rGoB{RsL_+B [mSymtfdv[:M8l~hK\Cjai^j[\uKd~OenThMrYp-Fi[ZuJYtEE_/Jd8?Z0)D.J)8P31G//B,'?%5K16M0>U9H_@GbAiaiba}W^yTHgBKiEc]e]ndTqHeYeZ[tQUnNSjJNeGE^?C\=ScEViHRfC`tPbwP[tK[uLKg9PlCRpFWuHFe6gXZ{I]}K`NiWdUk^aY\Zdc[vXplToTOgOVmWVlVYmXShQSjQd{bgbe`d|[SkKShH[pPg{`l}duexZq_rw~psbocynWnL^vRTmKC\5SlB]vM\{MUsIEc9MlCIgAHf@Ec>KiCHd9OjAKf:Fb6TnBZrEgyNbuI_n?pRlL^-`eugR`4Ud;-;w>T(ooyנʒӛʒËȒ{pz>O"qZc{Ifo`X]_Zhid}RiEgIQr7Kj4@^*Lk2Hf(lEsIoBk>f9c9uLrIsHh9p@{JL{CU~GZMxHceJICDv4j'}688>\u:WMjNn\z&d+e$r/n-c{ z;j-[th/m1t8o.l+r0>bw)p8j|9[o)i8jFf0Bev;Te1L`,Md/Ul4^{8=[7SGd1Ie4;W:WB\LhLGQ<`<6f`'Nqt?VtUkm(H{'sXy:Ka,t/󑘤8E13z2fy u0o6J<4hr8Yz.?[,9N7.@06I0xh,G0G>V$0D/C+<-C}ZVl5!6uKg?2N +sE[z/0L2NRof.NBf$Ae&/T8X$:X!Li/Xx6;Y;ZlDvHc-P$D"?Eb3Oo/;^@eCix=s7}B`!m+c}N=^: YuF&C <8T"/LRt0k@[SEh"Ps9_N\~JIk7Gg8A`9IdGD^KSkWSlVHaKe}j\ueD[OPgZQiYRjYKdRIdSB^Q7QKGa_@X[BV]AV[G\a4GN7ITHZcL`fPch]puOcj@T]f[vyHbfYntYlsOflTlpVnrUlsSjr^u|LdlVmuf}YvyUqyc}@]iQm{Lf|OiVnVmSm^wMeWni}m~p`{^zakazZqf\x\yccji]vVpa{\uhaxc|YrYqXqRjYtXr]tTlSiOeSiQgYoKbPiLdTld~sd~e}D\B[RiLdLcHaKcOgNeKcGaQjukkf~XsWp]v\uVoQjd{f{h{avcye{[r]u_vZp^rg{`xbye|RkczUnmax^sg{dyWk\pNdVmYpYp]tavjdylVqb|\ug]vjd}Yq_u\sUoa{Phbzdzfzk~[obvdxcwWlfyj|fwl|i|i{crk{sn~noh~jcy`wg}dzf{li^tj~buar]nbqct\m^ngw]m^ogvp}myzymx\glkszipwnu{{htxztp|kxjwn}hxgxn}kuntswru}tvsxmzmyhwo}q~kwir|pyykswxv}~yyz~{~|xwxzwx~z||{}~{{}z{}yz~uz|x}|{zu}s~t|{zz{~~zx|u}t{s}yn{nyl}wkymym}wk~vkwk~uhwfwf|sa|saveuewhvgxizkygwfyfyhwhvfuau`veufre|sd{sd~ufwi~ti}sh}sh~wg}te|sd|rctevf}tf{qevmdriatkbsletmhplhlgclgcmhajd_ie`hd_gc_ea^fb^he`e`]`\Y^^ZZ[Z[[[Z[]ZW[WV\VTZSSXRSUSTXQRVPRVNRWLOUJNSKNVIMVFIVAFQCFRCFWADU?FTEX;BUBVADWADWADW@CVAEWACX@AVABW?DX=CV?DW>CW>BUFW=DX>CZ?EZ>DY>CZ?E]>E\?D\>D[?E[?EX?DXBGZAGWAEUEHYDFZDFXEGXEFVCESFGVEFTFGWFFYFFXEGYFHZBEUEHYCDVGHYHFXJHWLJVMKWLKXMKYMKYLLWLKWJJVIKYEFVDEWDDWFGXIGYGFVHEVIFVJFWJFVLJWOM[MKXPN[OM[NN[NOYMMYLMYNNZOP\NO[NO[QO\MLXNNZLMXPP\QQ]QQZNNZLKXLMWILUKNWKOXOS\MR]IOXKO[GKWCGSGKVHLWHKWGKUDHTCJVCHSIOYHOYGMWHOYHNZDKWFL\EM]@GWAK\?IZ?HY@GY@GZ?IXBK]AH[CH[BG[DI]BHVGO[Ydm}AMaLWpjwbpQ^qEVgDWeH[bh}lrrg~{]tnhyd|tdzqrdyv]qqRekThpUhpM`gFU^CMW?GRAGUEHYCEWAJSAKOiuox{az`rrjgUoRhfojqlrm`q_[i_Vc`OY\MU]OT`LO\RVcPU`JPZLO\JL[KN]KM\KM]JM\JP_HN]KPbNSdNSgNShNUgMTgLThLSfKSfLTfNUiRZmT]mVarWbqYft[iwXgu[fr_ht]gpcnw]ktVio[nx`u~r[o}buYoztq[y{RpredQpsWvz_ddh|~sh~QpjUsoCa`OlmUmp]puSblR_oQ\nP[oNZpMYoQ^oXeu]jxbpyXgmYfj]jkgusj}y}zl|Xhv`rpuptka{haxczs`vuRmhGbZLiaUrmmmdzf~pqzcym^uee|gomUjN}uOeCjagz]I]AK_A`wTxfd}Uo_6O(IbA>X>(@0$<24-5/$933HA*@4"9,"7//&'=04J>2I;BYF8R;=U?/K42L:&B3:+:+5PA0G68PC;RJ+B?3LD/F@,C<';55KA7NB$9-#9,2F7&<-.C5/E55J72I3.E,6M0?Y6MgD?Y6(A#4 9$&?-0K;$A1&A36,$;22/54'+,.&;60$+OjXKhT#=,.!0#4&-F43 1!%:1,;;"/3%/4&13#02+,&'%64(91#4+)=1'7/*<1=QB4H7=R@+C) 9:T/cWsfNiG@Y?:P9+@1*:. '/(  ;J9;N8OgClTex[mz ;SL˔krjtSgFSj9F\0G^6WkDYmFH\5XjFRfC[pNc{TUoFFa3[wCeInR{YsXv[u]mWvbk[lWaLhVxfxivjperhzo}s~{socwl`i]lag]qkogialevnuiw{qt}szthlcqdm[m\sbn]o]rxru}rskyp`Wf_ndi_unjbe`klbfNqRMmPQnQYvZOmNYtXlge`okuspkZxRZzToezlj\}pxzoweyuujy_x]}bkQ~fu^vs[zbedzJlQjQRl>YsFXsFPly@w>T^}A{:Ev5I?g-9T)&CX.gFg>nuT!0Q]CjOgK\~FOqCVtQUsM]{PHd;SoLXsXC\DF^HRmRZvY]x^EcJ_}jHgYNjcF]\Pdk@RZ:OW:LW6GT=PYI\bTfiEX[J^bH^ciKecUomPgf\pqPdfPdd`tsXnp\rumQhjZprUiljoor\x{a~QnxE`ob}OhUo]vI`Neeyzx`txYs}VqOlyywpa}fUqd^yeeWoVoTlcz_x[uSnb}f_x`zKdBY}ZqWoQjRjPhE]|Yrda{de}kRjE]H_D]F_Mgg\v@Z~LfNhreio_ya}_z^xfih~exi}\q[s\tVoRkYq\sZog|az]w_xUpYsTo_yrbv[ppYoYpTi\rYq^vQi_wYp[s[s_wb{Zvk_za|jToa}ifPjUp\vWqZrg~ZrYsb{YrWp^v\vUkTiYqWm[odv\oj~f|h~d|a{c|Zsax]u^uXr`|c}c{nbwi~dx]rOeSjbzcyig|evewlwufwYhprj{l}iy]o\n_r^s^sh{ar]mcrhthsal`ncv`s_t`uav_qSgycsn|kyo~j{hyoqewi{m~pgun{o}xpzyzz{|u}u~vuyr~s|zmwpzq{zt~uqzjyk|kzquww}z~}}||zuzzo|yp~zq~{r}t|r{p~xjxh}wexhyizjyhzjzkyjzj|m|h|jxhygzdv^wawdudwgzizivgwfxhtdvdweygxgxgzi{iuf}sixpdvlcsletmgqlfpkgoienickfbkgcidbiddgcbc_\fb_d`\a^Z^^[]\[]Z\\Z]ZX[XWYWWXVVXTTURQTQRVPQUQQVPRUPQULMSLMUFJSFIQCEQ@EQCFVCFT?DUD^=C\=AZ>BX>?TBEX@DV@BUCFY@CV=CVCVD^=C]>E_@F_?D[?EY@HZAGZ>EVAEY@EY?DX?EWBEYCFXBEUBEUDFZCEYAEW?BV@EX=AUCGZEH[EDXKJ[GEVIGTIHVHGWKIXGFWFHUFIVHJWCGWBEXBFYDEZEFZFDWFDVFCWFEWIHYHGVJHXKIXOMYLJVMKWNL[MJXLKWOM[NLXOLWOLXOMZPN]MKWKIWOM[PN\PMZMMVJKQMQUSWYTYWWa][dbOWZLU\MU_OVbIP\IP[IPYGPVFLUDJSHNVIQWOX[S]^S\]S]^U__SZ`LR[HMYIP]@HXBH[@H[BI\CJ\BHZ@KY@FWBF[CH\CIYFOZP]cdsvSfiSdo%<0'=1,C6*@4#:-6N<=UA/K5,G4+I8*E86(!;./J;5M?-E8!72 34,@>(<:%:6%;5/D=-C6T8D^=?Y6WoLD]@+C,/>VI)E6(E75(/&,#3130#44-- 02 207NGBZM0"4&5(!8+ 7'7O;_vc1G7,=5!/0#.2!+.#12(88$44'78(87"2.;LC/?5+;53D97H9'10I):V0If;OpBHh=UvMYxVQoO@]=C_?FaA=XVcI}o}myw{tš~rs\sXgMnTu^|dnUgjiil{bv]iUkVw_rY|^yZ_xWgoUv^iSudlZnZvan]iY_Q`QUvIe[cW`}RhZ]yP]vNeWc}V]yQa}TZzPVyMVyO`Yd_gdc`[~X[~X_[g`f^e[_~UeZoeqfgWtcwgcT{mdYcX^}VX{Pd\TwOXzR__]\BfA9]:;_:GlHCcE5Y:8]A<^CPrVXs[:X>RpQdc\}Xc\b\TuPXxSa\f`sieXnbqdeXrfkycv\{^vXrYgzHf~JoPmOpTz\min~^njbkzquzqbmhj_ikRtGtFzPzO~WwQ_kDW,bsLXoCnTqSXw7Rn5Da'Kh.Lh.Vr8Vs:Qo3Nm3Gg0Us=Dd-;Z(*E)C1J%E\9AZ1A]-/MHj0Lp3Ck,Nv7El/9_%;a'-R8_)5V&:Z-<]/?`3Ab0Ab/@a)Vv:Pn2Hf,Kj.\{APt7;`'Hl4Gk5Po7@^#C\ =XPj'vKaŃwl]ie^`WZTKSXF^JxaLYOSCHZH_TNXVNPPr;p9|@__V?[P_Ocxe[WTMQZQ^wewjbhVWW'KRFz!VSdaOnYd]Z|djem;Nk3'A.E.E1ID^*[q>yl1QqP+PZx6BAPދJa*0CyCVp폊@FEm!FXrp4l`y'Ie]^@Ur1\TVwMs'6Z8Y,3Q%1N7S3M:U%?])5QEe m:g^eVqMIdEKhK\zX8U1;W47V7\{^MnW>]MKd^GZ]5GMZot;OV4HRAT]I^cDY]BW[AU\Rjp`y{Ib`RmhoXpmQhfPcfI[_K]aQehbtyThkH\_QdeYojVpgla{uo`~wyvb}YuYuPlNiIa|h~eyL]fXjnom|z}Jcdmkh]vzzd~]yvdiTou`~]yZx]x\wdKdznLfygUqnb}kWrWrQlc}\u`zb}fWr]v_yb}[ue_xlWoKcKdLe_z`zXrc}c}LgTn[ua{Xr`yZu`{XsZuUn\ul^qdyg}mb|t]vXqWnTiRi\ueZw^yjf[v^xnf~q_w]uXoWoWnUmYq\rUlUlZqYqZrPjUo\v[uc~Oi_yb|iZvXssgaze~iTo_z[vUp\xSm\v[tiUmbui|nZncycx`ve}jay`xi^u]wc}`xYplf|ln_tna{hek]v^sbx[tmini}qlOet_tVkWlNe}TlUnXqVlSgUiZmYmnmeyOhXpi]wUmTmiczmjdz_wnqo\u]vl`w^uh}i|j~l~ixkzlyguo}zfsiwxrpsponurn~nqmyqpi}buj~vni{n{mzr~t~wyww}tyz{~}~}}~z~~yx}s|t{o~r{or}q{pymqmo~m~m{j{e{ejm|i~l}jyfzgzi~m{k}k|k{j|l~m{l{mynxq}xo|uourltqlqnksokrnkojjlkjjhjhfiihjecedbca``ba_b`_a````_]\_]\aZY]YW\XV[XW[UTXRSWQRVQRVRRVQQUOPQMNTNNVHIRFIQCEOBEQACQDFUCCTADX>BW:@S=BV>DW9AR;BT:AS;BT=DVB]=AYD_?C^=A\?D^>F]>BZ?EZ@G[?FY?GY>D[?DZ@DX@EY@DW@EX@EX@EY>CW?DY@GY?EX>EX?FYDH\FH\GF[FEZIFYHFZEDXDEXCEWCFXDFXEHYEEZAEWEF\FF[DDXEEZECWDDUCEWFGYFFXIHYIHWKIWLJWKIWNLZLJYKIVLKWNMWOOYNNXMLYLJXJHULJURP[QP^PP[ONYZ^abmdfpdbo^kyglzk_ohN\^VckR_gS^hVbkT_hT`gM\^P_`XeeYhdXe`Yg_Zi^Zg]amcXc]W``QX[LSYFOXCK[AHY?FV@HXAHXBJYDHYBHXBHYGQ[M_a\tl]woJc_6LRBUhBTnEXr9Ng=NfHZmUnz[s~F]cH_fC[dJbjJbgSkob{{WplXsgo|o~gyMhc[urqe{_zqhsisr{cxlevnexmmrVjWVnUcc\|[lklkc`c^^~YgcuvsyWkcatqRccR^dQZdX]jNT`X\iYZkSVePWbQWcNV_RYcRYcOWcOWcNXfKXiMZkP^oP^qQ^qL[lP^nUcsXgwUeqPclShnThlUloa{}fmhb}_yVsyYt|Zs{Yq~JdwOi|Qk{OjwUszXw{laF`pB\oJfyLg{D_qA]jMjqixge|Omh@`\=]_C`i6O\4PW@Z`U3VnP1J0%>-&=2(<65HE,=="73,A8 6+'=00F:&<1(=4'<14!;1=UH3I<&<02,!5223&:<"77,@=5I@;OCTiW=S>5G47K61B0/@.+?+9L6I`FE\?QiJA[78S/-F)@XA9R?&>17*9.7-'=72/0/*>=.---(1$!  +#*/.A-(SgNThO#7!#3$/(HSQ$101@B)89,-,;;,@>4GB.A8':02F8+A--D+>W9d~[zpuxƝ|{x~gkyc~hl{jykpjtpmkljd[dVlZhTtbr`tcWtFSpSwJ\TOsH\~U[}TZ{UVvSXzXW{SZ}XTvSY{ZZ{]MnQ/P3:[=BbDAcD5S8-O4CdMNpU]zaYuZdillPnLWvRSqJKiBOmG[xU^|T\xRJe=VqGr_weygou_oYqVnPhpw[kdbi|cqignuzztdpk`cw=fAc}>`z8]x5`x5bz9[v1^{6]}4[4_9eBbAbCOo1Kc3:P(;P/NfBQmACc0Gi1?a*Ln7Ef0Df0Hk4\~IUw@XzBFh5Dg4Ff4=^,Pm\+;Y#B[$E[uJtZ\mRY\koWU_VX\UTekcr{`wcotsq}vry|yuɁrcpipryfwcr]`OnW]f`kBJXhzvvgottmumouW<3@kmTd{kmdg?*D,VoAG}/JX[vcSmGwyKCVD86T]P?\P-ajMAX(#9 Yq<>Tpb{`{-pz)9_Sj9Xp4Kb)tDH^HFBV \z->JTFf&3WsHVpBMg7;V'A^-3O!9T'fH}Qm=V|%N+Q~R`5=`Vw3Mm.5W `*C>u3Ck|BAa?]Hhl6c/-L Ii,kLNo)Mn&3U@`#)G8X>c i_?=9a!Su8Y{@bMqaRqH6T1?\I^W=SRPiiXpt[rxF]dC[`BX]?R[E[cE\bAV[NehXqpXutCVEW?FY>DW<@S>BVCWCU?DX=BV>CX?FX>EW;BTA\=A\=AY<@Y=@[=A[>B]>A[D^?F_>D^@D_@C^AE_@F_>E]=C[>DZAG]@F[>D[?F[?EY>EXG\>F[?G\AG[DG\DFYDG[FF\EG[CEZEF\DE[BDYBH\EI]EF\DEZGH]DE[CEYEGZDDYDFYEHYDGZHJ]FEYIFWIGVJHVJHUKHWHETKIUOO[OQZOT]UZbQV^PR_KNUKMQMOVHJTSW`_fj\gck{gxppbjZqj]r^KaYI[]EWYL`cWiqTflSgjXljShd]qi]pcMbVPfWVl[ShW^q_cubap`[h_Wb^P\\HSWENVDMVAIRFNXELYEMXHPZS]eYhkWnhKk^[{mWtoPin3J[AWmK`xGZr=QjX<R6FY8BV3QeBXnMJ_AAY:D\C";'/"%914IC)=6'<40F:7M?8NA3H<-A:3."822I@8OD:SF'?30H>$>75/.+0)=RH6L>%:0$91 13"5500*><$835H=2H4;N97J67H48I71B02B0;N9F^CF]AV=&>+'>.(?250!61%85'%+%+(/*&#!$$#7&2H1=T7ey\RfBJ_Ed-Dc-A`/WtB=[*=Z)C`-ML@UGK?@=[J-E+DL;a`s4p iw(Br.OWsKfe+^}#\MeX7K}*rAIX5L^ljjq2IL`4Xk?rUmIk4Qk'NmEIcl1y7{MGhGgS:\}Kzs5]w"m3^{QRiJh2LCZo.%E;U >[g~1:5>z#xKId'u0q$o&o&o)s(Yx,e_yH%; +6L2`s?yGmn3y@Ej2VzXi{QSmE]|8Qr+*M =nm7\Fi'Rv6Eg(/31O&8X.1R'5U*Ge:OnE4R.KiDXuTHcRCZSDX[Mcg+CI?W]DZ_BW]DY_SZDV?FXEV@DW@CVBDW@BZ@BY>CWBV>DVBYAEY>FX>FWD]=B]>D]?C]=A_>B`>D_>D]>B`=A^>C`?Da?Da?C`@Ea>E_?F]>E]>DZ?F\AG]AG]@G\=EW=DW=DW?FYCW@DX>BW>E[=EZ=EZ>F[;DVCI]@DX@EWCH]CG[CH\BG\BG[CG\BG[DH]BF[BF[DI]AGZ?FYBGZ@EWAGVCHY@EYDFYEF[FF[IG[IEWHFVIGVHGUKLXMQ]QZ`ZejYgmQ_dO[^R][S^YQ\ZT^b[gjXhfZl`tnuhm{eof]Zq]NdX^rlJa^TjjZnrVnn\vohugn]wab}i]weUp[QjUhhidlei|eZoXXjXYi^Tb]U`^R]]T^`UbeWdg`poraxuMia?_VXyo]~zWux2N[@[mE\r>Ri9Ne=QhC]lG]mDXiBThHYnCUkGZiRdsWkuSloUpm\ymk{pzuyus{wpmwuil[y_knknhgqm~sxqkoosvc{g_qe[hdakjozxamm[eg]ej]fmYbg]dg^ghdoooz~qwUgoRdl[muUhrVhs\nyexL]gtBRZ7JMYll]smbyrjzhzXvnf{uuuz|v}~~tnkly@\[5RV>T]5KR7PUVBBZA9R4D]AH_DI[Af{_L^>Pc>Mb>K_?E]=D\>B\BD^K#>00G>-D:6JA+@5:PD5K?+@54-1/#74"44*B<0G>(@8.B;-C=0.10-/)(+?9AVJ0H9(>1%83#20*+/.*:69J@$7(1C32B09J8.?->O@4D28K52K1@Z;AZ;B\8G`;SlK6N3(>++C0+@41&  + 5L;1  (,C1*@+D]C[tVsgsr^o[sr\q^1C66GKkFRrJUtGhWj[tezm{m|qvnmekbuo}svtlviuim]nZs[lVhSp[q^p_n]jZpcYzJTu@gS`Sj]YO[NYNW~J_RaSU{AY~D^JT{@[HcQSxBWyC[|BjSnU^|G\zHZxGdRgVRqFIhv9?Df:w=t7=]^z-Pl}*Ks0OE^KTPntPPm+q<@^ 0JF_UMeZ<Szm17NN>VUIq*^uY=U $aw^@b +`b7Gms3l*MRb)_#j/{FURx13T)J/Q_/wBRs[X9\=`dDGk&Z5`8q@em[~C3VTx@3T NmB@_7)G Ba7Ih=B`=VsY7OFSY=QY@TZLafH]`M`eGZaCV]=LUFWa4JS9TYIfiIacDZ\F``KceBW>CW>DW=DUCVBDV@AVABZ@BY?AW=CW?DX>CX>DX>DX=BVCW?C[ACZ?AW>DW>CU?DW>EW>EXFc?EaAE_@D]@D]AF\BF\DI_BH^?F[?FX?FY?FY?FY=DW?CWAAW>BV>CZFXAGY?EXAHZAGZAGZBF[BFZBFZCH[AFYBI\BI\EL_CJ^HOcDM]HPbHO_DJ[EH]=AVFH]GH]HEZIFXHEVHFTIKXPS^U]dR_dQcgH[`H^^`slu|`rectm^poNb_SkaRk[kfhYnUuUcwrjh~iWnfQgfH_aYsqMh]epqwbgXv^Sr\Wu_Yy`ljg^shunb}\_w[lkw}yq|snz_sl\siWqhMhbB`ZAb\9`ZImmOpy>Zi5Pb6Pb=Ri@WmKhvHap>Te@SgL^tFXnBTgI\lG^iMekTqnUvkkx\flsnsfkdjipsy{yxz~di[xbTqZWu^RqUgdy{}tw|jjj{kuxrvuxjwqnzwhsmnyphrhvttszwbopɿ|~wqzwxv{}_ykHb]8RT*?C0BI/CEThfgsqye~h]u_ZmYlkct`_p_Zj\brcut[pVQfFf|VjkywznjdzUk^we{os_c{S[vMh\rZy`sUnOe|_oRrTks\\rJH`>LeH>V>?Q>6F67L88O8AZ=JcD:P31F*EX?AT6I_7TjFJbAB\;@Y;SoW,J8(C6(>7-D<.F;.H9=SF:PC%901- 432212,*#;53/1+$".-+/18).#$8-F\J=UA(:.&7.!0-+,>OK1@7,?+7H6;K<:K;7H66I35L13L/6O/:S0Gd>BZ:1J/%#$B[BFZAdv]`tZTlNtllaoavmxYwO]{OTrAbKjOmVeMlURpC=Y64N79O@+A5(:-%9-';19M?4K8_N>_O=]H>`CNmM]xUc\`}V[xR\yPcX]zT]|STxTHjGGiG]]XzUNoKUwPUwPT{QRsP^|\?^>4W7||yut|zhhlkeb\nkyWoPa{AsSaxWtPcxAf|GbzA\t8\t8^s9f{EyWpQzVVmCsKb{9sHW^hb_|QYcWW~UkAi@e;pGjBTx/eB[~7X{8Xx5Tr6Sn6Tk;Oj;]zEmPkJvTxRhAhDsNwQlayZqdbtewe_picskYW|K~LS~U[w6Pk.Mk0Im/Gj*Bf%=\He+:VE_*Um;^w>e~A|RzhV]sMuNl}^}]qKc~AUj:N`:;V-6R(pYYt2Z[\AvFf7f~;kCOf'b{~Y`Dc)Qo6Pn5gKFb'Fd0Ii2?_$nA^*UpC-D"?W95P*(DXu9Vt-`}7oJQn4Eb+4S~Xl?Zx.zTQk.Nd/E\&4M>Z#@] Nk*Nk)jDE_ qJr;Y\y8tM]{2`4e3c/n7Mk#6aELZe+N^,Qc.sJsJd$B 5TTs6^z5/'\r-}:j&E9NW;OTH[aFY\XksM_f:MU+?FI]dFYaK`eDX[LacXnq;SV8OS@9MQMeg]utF_^SklOcfRdhbuubwqaunjud}mRpkWuwhe~by\rz[u{MjqLireRptQppqWvpZwpiyuzPpk8U^GdqSn}_|XsgXtGcqMiyQlUo_uUkyMcrUo`yQmMfJdLfMgOhCZLbQhIaKbRhKbNe\s[ua|ToToXrVo]w[ud~d_{\{eVs]xQmUolc}^wgn[vkt]x_xdhcfmb]zeNj|B`qOl~GgvQqLm{[zb_}Je|hmkTnNiYs[t]u`w`xc{Yqb{i]u^xYsMha~RqfZx]zdb~Vp`z\tH`PhOfOfSkJbRiUn`y^xZs\tOfQgk_x^v`yd}QoQna~Zw[xRoVsVoQhWnSl^x^zMkVt\zZwdTq\zTrnvfn}]vNgQhSiWmNfVoMeMfSjPfRiSiOgZr[xSoUqNiLkOk]yid|YsTnYvw`{OjfihljXpVoPjSpbj`^|ega~Wxqme`e^z_|a}_|cihdki]|Xu]ycn_yWpdxlWne}hkTl]we}cy^tYp\rczkh|jzoewfxbt|hziy}bszj|lol}tpmjppoqcznkkmle~le|g|f{[ofy_qYm`rawey[rytsw{csaraqbsg{augyewmjzpuorbuewvnjxXfwYgzXe{P\sKWmNZoJTiDL]BI\?G\?FZ=CW=AV?@U=ASCX;AX;@YBXCZ>BY@BYBD\ABZBE[>CW>CV>DV?FY>CZ=B[?B^Eb>Da>Ea?Fc>Da?Fd?Ea>D^?E\?E[AG]AG]BH^?D[@HZ?FY?FX@GX?EW=DW>EW>EY>F[=EZ>DZ?E[@EX?DY@DXAEYBG[AGZ@IZ>HWBN\DP^HPbCM_EQbCO`DQbFWgH[hQblYkqRdiKYbCNWDIYFHYHG\IF\JF\HI]KR[NZ\VghJacH_fH_gI`eF[\^xkpvqpbyfuVnfKfYjta^mblTbj~zp|xzSmYNj[dqiwepflmpbbXz^Wv``jdm]}__Xpeyoh_b^few|nthsYreXsh]wlVsiTsjWsnGfa:XVHhg>cc=_c@_g8T`6P_3M]>Tg;UfC_kB]l@Zk:Of7KdGXnI[oEXgBYaJdiLjhTqnWxm^ogrTw]OrXdljqmsu{mnvtojdcc|clohq_ziWtdPmZLiUIjP^|^xsryy{x{~vvdqcdoewzas`|y|t}xztu}x{}{xvq|c|^mib|}qiPfILcRAWM?UKNcWk~q`sjWj`QeV|xslyyzuovqqkŞgqqV~dsdvRPjDiW]wFXrBiYc{Vk_c~W\vFazM{dwcbe|ImRye|XqPC[A5I96G:G]H>T>TjRZqU]vY4N/LdDZsQTkFUnINgG;U93O70K86R@ =-5KA-C90G?3G?(>3,?7!5,0+"4031';;1."77$67 /0)*** 03(7=&8<*(-"L^K=N::L9):*)+=1*:/9J=0@39I=L]NFWGCYE$9%:O68O31H+5N.E`:@[4Ie=XrJUoK`zVyo}uqaxft^yblYlYcRdRVyD]NscdSm\p^o]gYRrFDc8MhBKfF3J41G5+C1&A-;UAHaK=V^;QrJX}RW}HaQgVaP]Mn`rependzmxiqcl_cVd]Y~RaX]|ObMnXiTc~O\|K]N_RWyI^~L^NLo=X{HcV]QQqCBc6OuFSyKOuEZN_TW}LZOIp>;a3OuI;^4Cg;NoE\~TZxMOk>VsDUrDXuEYvF_}O[yKcWpd^UVvJRsH\}VnhHgD=\;CcAEdB:Z6^]RwSMsNXzXRuM]X\{U[{U\|Y]|[VuRVuQfbZyUb[QlEUqJOjE_[b^VsY9U@4Q=/L93P9=ZAFfGNmNfZbW^{Qna^zPXwPXwUPoPPnMOnMigb]RxPHlFSxPUxRQpM@_:$D_Zĝ|ti{nyixixevat[pQxUdpObund^eyAxUenG}V~T_hbannbffdYV]ribZc[gZ}TYzWZt7Yr2Yt2qGf@Yw5Vu6oKXv5]x8_hKMi1Kc.Un8_w@bx:|QQk:wH\lsbdennjj`oYd^`cPYgpokdaZcq\fn@VUUvIdu5|N}P|JfVPR}RpNF])Ql6a|HD`-@]*D]+^uJ8P)8P0HaBH`@lY_~BtKoAb}3bx0Ld*fEmz]Oi2Wr;Wq:uW]{CiSiUVqCc}PB]0Nb9@W.A^5Jh;Jf7Qm=5N&5N&7N+C[9?Y4C^3,KuSp<+DY&;Y#Up;oWVp@7L#H]5:S(Nk;He5Pm;XuBb~M2NYsD^|H.LMi;2P 7S:YD^&_}FYq5}Rq@IcqKRf,H[*9I%HX5:J"9MW{8u)}Ib[|'`-Vs(So#LeKcPgNg v3u4F\ g~2g0Qn E^E_{NIcc|)TJYXtZEJ%Cv/5Uf.,EPf3GD+H +@\,{REZl9Th>Hct/P7w|"6CWs^y, :3L"=PoJ9ox*i!__XkK^xF2J5_u(@W }=H~-L16)B,D:S/@Y6*E&AOj6f@HjPq*a?Ih)7U1Nj熕Rc"5WNqOKM|48^ ^-\/Os'5W Rs%a;]g4Ms"#H:_?d"Tw9Fg+]~<:`4Y)M=^$!@[Fm"6^Y>Ko02R#9X05T)@\,Mk:Kh=/H(+C.#;&IcR>XM4MF@TSQehFYX9NFQ\7KTDY[H]YD[VHbX+E7MhWHbN!;(D\ME\OSiYH_OMdUjuH]RATP]mn?QQ@TTG]\NeaKe`XpjDYW?RS_rre{sdzreypczld}oUpg@]ZC]`]u|YowZmyI]ff|H_i?YdToyTowIckA^dcji\zyPmjnkxzep{]{{\|OlwHcpMiv^{a}D`kJfs\yJcqVmyVn{QhvYpazQhNdF]KcIaOeMdH]JaG`C[|I^~QhVnTnXsVrRnZvi]zgZw]yHdq^z]ya|Xs_zZt`zb{l`z]ymyynb|hka}pmhxVsFcuGczB_rEbtIgxFgvMm}So^y[vYu[weWqOhVmUnUn[sPh[sVnVn\u_xYpWqNhifb]|\y]yb}f_yo\vNhPiXnZrQiVqf_x`z_xZqQiSmi]vYua|a{ZxYviUrVs^{YsNgUkNfPiSmRnQnNkRnLhVsWtSo[w`}`~ZxXv_~x|{mnZs[sC\G`KbMeMeUiE[Ngc|gGaiYuPlQodhgQjRnSps]uXp_v[u]xf`}Vs^{YuUrb`]}Yzega^kibWyZ{WwZxYx^{b~fgabfZxZvXua~[wa^y`yc|c|gef\wdhob|[w]w^yiZrdyld{^uUlplcy^t[rbxlggojmrijdc~mmjkb~a|ia{az\u[s^u_vg~heb}b{cz`ti|eybwlk~dw`xUj^scymnh|sruig~gaxf{xatZl}Yl~Wh{WhzL]pO^rZgyXeu\iy[g{MYnNVmKRhHNaDJ^@I\?EZ=DXAG[DZ;BZ:BX=CX;AUCX>CY?EZ>E[=D[;AX:@Z>C^>B_=E`=Da;Dd;CbC`=Da@Fd?Ee=FeG]@G_@G^?F^@E^AH\=EZF[AI]@H]>GZ>DXEX?EX?FZ?EXDJ\>G[BM^=M\Rdo_rNanEUeDUg@QcEYiH[k@Tc@XcWnvVnrYns]rwN^eEOXDKYGKZDGXEHXLQaTbnWioYnqZtyHcnE`hG`dC\Wr{z}srdgYtf`{s6TGr~mn~vug{zhrf_z[`}b{|hllppyVwbRs_GhSZ|^cbupgab^]~[kknsWt`WsdVqdXsjYtkPohHeaJjhHfg?\_4SU9\_6W\=Yc@\h9S`?VdBWhE\kKfsBZj?Vi:Nh9KcGYnEXkL`nJajOhnKgjGed[zuWvl^mfs]}iitcmdlmukqmq{{gifhczhiqdq[ue\xfXt`\xbontz|{vleyzpvntn{t~zurjo}jl~jutrtm}mp}nvshtgjvjvs~uh{izh~rxmvkqdi`k~cf{a`pZaq\o~mqnj|eh}a]tTdzVnXkrznh`[|avROcHvtéy_nXXgV\mZy~q|h~W]sLsfxkfy]cv[WfJfwUvwnwkgn~io\n_q`_zIXrBc|Te~XRkF_sOiXo^eWOhS>BWE9O>@XC=U?B[C=U=ig^w[;T7LeEZrMXsLFa?7S52N8*G47RC-I98, 902IB#:4%93';72/,+++00$67.0$$11#12*+,+'75/0!-&3H9AVBH^EG^D>T?=R?WiUAS@H[HJZIEVG>Q>6I66J56T<>]C:WYxOdY\{ROnCOnDfTTq@XtETqAiYQpANoA\}OStFPpACc8JiASpJ?]7>^8PkGIfASqKTvNSuQWyVgfXyS\~X`~Y[zYZvWVsVQpOQoN[zYQoNRoLIf@SqKi`c\TpRKgN=YB=ZB=UA;T:MkKXvWUrTRmGId=[uO[vMVsIQpHNmHRtOKjF`[[zU>a?;]:KmJOqMA`>4Q.HeBǡrje[mbqcsc|m~lvdr\sZqv^an]dij}WWTVf|OXcfdi\gihrZa`p]\bavOpHYqHZzVpNpIccZhGoOzYknJzWvSuSo}W~WiiVikn`XUXTUR`U[|ESUyEOp@Wc7]{2^w0pCaehbbgmhlfvcgVxWiYVg>wR}]c|J@T)E]4CZ4'A-C;P)FY9OjDB]3B].Lh9.J:S+BW2AT05K!C\24P"AY4Ma?=W1^xPGc93O$:W*>[02N#Mi=OlD*E2M*9S,6R%@Z*E_2_sKC^.Rn5\t:Ha-Ng7Oe7Td>TeJJZ;W `x&Cb5Rjc($IW:>=H +ds7->1AAP*7G%%9mO=W!0INd6d~D2LE_%pFxVtb$v1iY{'Lu9_5WW_y/$@?] +XU:ly$IOq63S Gd0zIl8-K?\+8WpM{YhMpRkIm0QSr>;ZicsJgddKgSf[-0F53J8F`C}e^57ME5JGXWJahThpYkuVhwOaoL`lUjtRkrQlpJghVsviYuu^{zRpqC`bhb{Rpckwdsi{i~GhkA^jMju\xQmyHdpKgsNkvB_jUnx^vOesUj~QfQfH[MaJ]K^OaRfE[Nd\teUpHduOjva}^{hTpLjwPpykjx}QnsXtPkPkZu^ySpvilg`|~UsxVuzYuya{~hb}`|d[x~`~e`~|OnmSptKgt?ZpA\mB_kLjt\i`|UrbRo]wa{WqOhD^xGa|TlPiQjRlNgJd[vRlSnOjToWuTrNmDcwKjKj~NnPo\z`cPmOkVpf\v\v\u[w`~Qnb{YrWq]vTm\vXrXrTrTqXuSp]wb|d{_v`wVnc|[tMfXrXrLfHdXt\zTsdSqYwd```^}_~XubiKcYqUnI`=UH`F^Rkfc]z[x`}B_Jh_~Yu]vUq_|VtWt]yQm]yb~]y_}ea^|Ie\{mdgg_h_do^lh`[{QrQrcedciggYxWuXxfg^lg_|b]zYvRpWs[yZxj\{Vucl_z`|[{WvZyZw[wlmp`zWq]ved~c}fbedc}a|c~enlnoiogZv]yj_{Yv`|\wd[w\v^zdkgfmd|d}k\v[uXpYq_whnnxv\usOhwLcu^usii}rmctctVhzRdwar\kZf{WbuXeyT`xNYqMWnLWmM[nN[qNZoP\pQ[oOYpKTnCLdBMa:DX=FZG_>H_Dd@Fb;Fd=Ge=GhIi>Ig=Ge>If;FdIc;Ga=G`?Hd@Kd@Ja?Ka=I`Ja@Mf@Mc@M^CN_CM`CM_@J[?JZEQaBO^BTdAUbG^kXp|PkwJaoE]mB[lAZjD_pD^oC]jFcnHdmRorZtx[pw^rxZjsKWcLVbHR_MXbVeoQdr[pwWqvMjqPnvSosYusuv{ik\|eSsfTtjLl`p~hlXuZa~ahevuMeNRnXv|x~pvozbmcqTu`AbMFhUHiVFfVMn[Oq\]eUy\glmnmmdffididma}o^znZwoQojLheOmkIdf@\`@]d9V\?^aEcf;W^B]fG^hI_mHYfAVdE[iH]lHZmPeCTi7J\C[fRlsEbfJfiUtr_~xQqcatdugv\}j[zijxc}mb}lrxx~c{ie|kbwk`wjbyif{inpbxfkmg{ch|bdw]]tYe|dijc{gqtmjuygsi^j[mzfsu{izqz}vufpgisimykx~|sqsnvowpytnkr}tfsiandhvh[lZSeJk^c~_bh[\c^XxYssoqh{twr_wahNsxbxd}ki{Zq`vk}Re|LgoW|epWe{M~|jZ}khVkXf}S\rK\rMXrKh\h]Fb9ZuHvbhThRZvElY\wN:U2LeFH^GE[FMbO4K7)@-5M72J5'?(2K37Q9F_FSkIRmHHc@9W9A^H8UC*J::.";5#<8(>< 64#75 52,,04 00/0./((+)*%/-3(+!6*@YG0H50H0.F,@YQ;F]CJaDJaDidTkLFd=TtLTsH`Tj`cXk^eVk\`O_NZ|I]JSwA\KV|GX}JGl:Ei9Hm_2Hg;Kj?JjD5T0JiFD`BA]@B_CMiOFbJ7U:;ZQwHGl=W{LTzKPpCDe9Ac5?^2ZwK]{OMi>]yNXsGSsGStDMm?Jj9NnKk@Fe=b~V_yRYtLKg?[{SVwRTrPSqSSrOd~[[vSa|\RpQNjLGhGSrPStMXxS\|U^{Uh_d^JhFQoMEbD0M/>X?D^E`{\NiIRnLa|Zf|^VnNWoIZuM_zRRnFNlFYxRStK^}WZzU>];OnOHeF>[<5R2OpFg[aSfVkZp]{hlYq]wiw}\`z?v}pmm`avnqyaZX}RWZ[lY{PblnGVbyPmFbv>oLXq8`y<XoIc|>hFd~Be}=~V|RZdDhHyYnKmLnJxMrnVom]axMtGtIxNg;inNd|GZpAOfX1Jb?UmLJb?Me>:V-\wKD_4(D=V/@T/Oe?5J$Le<6Q(k_D`;MeD3M,7N/3M,JdB7O-0I#(BD^7Gd:RrGhaKfESnM?[6\uNEa6AY39L#3HXoDE\6AX':S?X2J/I d4}EVs]IkZ~$d*Jn"7Ke'q`[:Us&kNdD[>Z+,DSf.Ij~bH7RkXn +OX,8Zh#\y&sB[d=9T7T{Ew-LPmSm%[c~/e.]u"h(Ow%P]v!Up-dege{%Vo?_To73L!I\?0FQk5Tn4dD@^Rs.gD)H 8X9Vg@=WB] l,;E]Qo :VC_Zwy8y9p7Ty%St*Wy(](AfQt}NnCZgBbBc+Hd;8T.*F%DPoGVrGWt=Pq0*I*MOt0Nv,/S8\yc;Rs!IOs/v6u8OeOp=|XvLWt@`>fQs58W1CaCceTsPWwQj^Hi7Ei1waBb.*IGi7rZdOn;a{YmnJ^PQcYUhaTi_YndcyjYp_`wiUiaJ`ZLc]sSha\qiQg_bxt=UYEZ`SbkSep\lz`qQcoL_h`tz|c|f~c}HecOmlUtuDafIflRpqUsmPna[zj]{o[zvZ{{GfmUr}MjwHdrMhxJdtD_nQl{To{PluG`lRivWkbu_pUeKYANyHUS_HV}HX{E[yKb|B\mJfns[|uu`;\]X{uvh~n~^krzuauPmkTroa~yko~vtYxlbw|tav]{vflpl|xJha,HLB^e4RV6TQ=\Vq?_^Ecg8VWqMlgQolJgiSptxeujKgk`}_|Pmuq?\gLis@]kUr\{NpzNnv?`fDdm:XbDbnA^nKi{KiyFdtLi}Jh}Gd{Kf~ZuVqiUsVt\{FezUsTqLi\zPmPmTpTpOkUqYuTpWp_vNe[rYpXpazXqWpRkbxXqXt^{XvSr]|aWzQrJiKkRpPoYxdXw`|hfsd~s[y`~gckkghce`d[y[xZxVtTrVsHdRnPmTq[xhai_|\{UwWzX}gX}]_a_bd_^Zcaff_^]a`^[}gaef^fWxiquOqNo`[}Y|bgl^{egX|X{Y|igdh_zSmZuTnZvXv`~dgcYv\yYwajlnihhfkeeckbYz\|Zw]yb_}\x_yi_zgdc~kif_zrksiymn}uk}xc~hhun{ezcyiWmL`uM`uZk~cubsgyetSbvUfzUg{Xg~XgUd|Xf}XdP^xQ`x[j\kNZpFRiBOd?Ka@LaBNeDPgDQiHToHToIVqJWsHUpGToFSmCOj?Mh>Je>Jg?Jh=Ii?Kk=Ji=Hh=If=Ig;Ih;Ih[nA_qCbqIhvNkwKjtEcmFdkPiqMdnVjtSfoPbkQenXkrPhmLfjOjoGej;YbB`gIejb{ixptcht}pkSrjUthRqajqfnVp``{igqWrb~s~a~igom{p|esVvgOo`Lk`QqgRrhWwk`qOp`Mo\crUwaNqSlrgmipkrju`|mXvjRpiPogMjfGedD`bC`cC_e;X]>\]A_`A^aD`eD_dF^c@W]CW[@T]CUaHZjL^oBSgCUiCVh?UeQiuTmsWsv^z|PnoWvrOohWvlctXwh[zkgyhze~wazpd}qfqf}p^rfezne}mmovtetcl~lpozwrpmmhpUpc^ximtquiyou{u|Vaadmokvujunws{euihwvw`qphxwraplmyt|xzs|vz}ows_mikxu_oniyupxhupvhqshrtq{yjwntq{kww}[xSdcxUp}[oaSd8|ahuYnSzbry}lxeuiTmVmTx`oYptr}iyajUVqBgRYtD[vHycq\~mY\qKl]k[mWlUWnFVlG\qOXnKHa>WqJ_{OZvGUsCjXzbmYq_MgA@Y79P47O5[<-I3(D6"?3!<5(@;+B?52 41"60-&'%*',**$$& OeTko`ycYr[ruzzYwUWvSb[ZwURnMWqOOjL@Z>BY?AW>;R7UfSY7OjELlDIjAJj@ZRLpAMqALp@:_/Oo?RtAMo=Hj7Np=@a5Gh=GhKkBIg?QnICcA=\=QoP=[<:Y9C`ACaCOpBYyIlXaNYwFq]eT_NWxI\SZ~LGn<^RZ~PMqFVxLBd9_}S_~TUtJSpCiZeRZuHkYNjOk;^{K^No]RsDOqARqEPoCi_WrIXvIYtIYtHWqHTtKOnJQpM]wZRkIToMVqLVqPF`BGeFHeDStNQqHTtK^~U]}WVwNYzUXuTLjHEcAIgHD^BIdGNfGD_<`zTQkHbz\\uVXrNGab:^~:gAgCWz2kBe9_-[&b1n=e8h?Ss,iEuTe~CCa$Rn4oStZA](Ea-d|L_u=^q;}Uj{=o=Rs=MJRFINOl=uId~=fBXr9]wDVpAUpAJc;LdJf?C^5A\3C^8C\5Jc<=V0GaT $bD"2by?}-EC^Wu/C`"Jg$xENOkyBVF`w&?C"10)jo1F!Pj4tP9Kd)Id"\x*V(E_|#Gn :e*Nm)XsE9R%:S &L|6`90NZl:Vi0I`;SkC]pGasLguWr|Xs~Nepbwz|yt|iv`vm[rukzY|kXzjXyf\{csvwvz|{~~tzdls|~}}luhwv}vwn~~[}iIjYvrw}ŭvy~vqwe}b{jrSslggoz}qm`LjqVtOn~VwWwY{X|}cSrGew>ZoHcTpLiOhPj\wPjSkSjTjMdIbPhNeKcOfPfQgRiOj^{UrTqOnIhBb=^}HeDb=[wSmPkdZuZvr|qq}r]|ah^\~oygckcac\{isbicbg^}Us_}gbimTxSxQxPwKqUyY}U|[RyW}ZMsTyX}SxQsLo^`_WyVyY}Z}V{XPx\X}dhqp~pbjwogbg`}]}`[i_Zy^|gjVrdVq\wNlWua\z[yc[xf\zpjnoqa^}fh`_famceihZxedfla|kog^|bkcqjgi^|a~`dlgacerxyyiqb{i`yUl\qeydwmtx_qSg{Wj[k_naqZkYjZm_rYlWhZjRczWeUd~O_xN]{JYyQbP`RbRbTdScRbO_}L\zP`|K\{O_}QaP`O`~N`}O_~M^|N`~PbL^~N`O`ObQcPbTeXkUgSeTfYkUgRcUfSeRdPcOcQeYni}g|PeuLapCZhKbqTn{NhvE`mIJiuro{xWa`Zafkowmq{W[fU\hHN^Y_oQYk\etWak=DPFOZ]ergowXaheosfsmmzk|rx]v\aj{HtQsg|I[m>fyH[sWikw_m~Yn[ue^vRj[\wMcRiZgW[yAt^iVZtIXrK[uLiZtctedUYwKEb6Ok@ueiUa|LZpE[pJbvV[pM|hiSj@Nd>UjDdwR^sNRiAYrEWrAePjToYZuFOk?l^c~[RiKAW<7M25J417ND-B;.',$-! 2$,?.7I93D4DXG9K9DVBK_F~qmfaFeAVtR[~UNtGHoIj?NoDFg=KmBEcCeBLnLCeA:\6HkDAa9Af:jaNuCX}IeR{cv\v_zb~ddgqZolv`vnVkRnUcHsZjS_{KkTfS\LV}IZ}Mk^\}Mu]dN]zGs]hW_~MYzK`RLr?V|IUzJQwI]}ROnDZzPVrISsHWwNl\cSuc_|N[wFWtG\~NVxI_OPr?aNRrA^{K]zJ_|N^NQpEWwKQqEGd7:V/TqFYwI[uHTnDPlDWuMQoKMlLVpQXpLtfJc;KeBOjGToMMkGUsMYxLmd]}TNnHNmIYxVMkIEcAGfCOiKPjNZtVTlLbyW[rQRlHVqNJdBLfFXrRQmLWuTQoNTrSa]GgCJjFKkFNlIf\wjVwMWwLznpbm]{k~iwcxg_NsbdSlY{efglU`}Gt[rV}_{ZxVvTeEbDfGaEZx>Vu;cIiOcMVv@7Z%;_,UwAiQE`+Pg0e~E}\byAcxZ\wDdNjTLh4Zx@Ic,Qm6Pg4YpXz7Rs1Pv0T|5V}1W}.c7k>Z~.]}2]~3jD^{=Yu;Pi2Qi3Ng1Md/Ph6bzHD\-DZ+\qEMe6}b]q;\p9`p8~Uk{>fu9hy>hy:k{;vKZn1Th/J_*Qf6`x>Yp<\uDVmCF^2VmEKb;I_;Na<;R-JdAF`;SmEGZ3VnFHa9U1VpJA^3C^6@U3HX9EX4BQ0MZ9BR.FW3J]7Oa8FU,=L*3L'?W/6R)8R*?Z4/G&Ha?G[2C^2E`3?X/AZ3:S+S.1F#5N)YsRId@D_@E`?>^9B^:-I!?\4Kf>UpFC[1DZ4TjGSiFLb=AW0I`;K^=G[(PfIkMcQi5ONh:THaWrz1-sZkRl +Re@U-AG^A[Id$Rs(rHSm$6QLq6h7,LlJWyrU;.D`w9Oh$wOx5$<8N%AW YoRk*f8Fc`p36Rf*8l"D"? 49L*J`&wA؃fz#Fh)H`I[w+0K>X'}3F 1FUkG_p8VoMG@&C>x5?Z e3Vp\d}&t9GgO,B=R4IK`)[q6[r)[q$z5oJ|?:V`{@Oq#x4w(JtX,HeH5N Jc)Zw-;.;2QPm-pJWv3;ZMl#g6{>o,Cy3o3Gj;_!=^"Ru*,RHmc-Z|,4UWy(Rws?Z'pA?fKqWz':^,J.H%3N-0J)4P,6R)8T#eIKl+Nn,Lj.Oq6Ry:dBV-RZIoa7a0Gj>aq2Kn$+NV{3XzKHoKqpFWe]~G;[2Bb;ZoJe|=Wq0Jf;QoAWqJaxRi}IcnNfrXmu^qvysrws~vs{vxsqbxcrMqXdlp}Ms_bthyaqMq`LpaX{khueltuyxtnukxszjiSwUPuVNrYIlRglcirullspytqmkm`eVz^hmeeggtv}Z|Zcazrovs{z{}}|zvpprslmon{wriwsmebqp|{uuvvmo~u|lt]eoxkwryt~oyrv}vzciu|StiktrzzhYyMgHbKcE]H^OfNeVo]xRpNlA^A^CaIeC^|=Yq=ZoLg~SnSn^zjd}goWs`~RpWwOpOqMoFgKmRuW{[aUxXydLl~\}dZwb~ka~ldc_}fZySpRrTvNrInChMsJoU{NtY\SvNrKoKpMrPuMqRuUyY|UxSvWzStXzSwW{]SvPuQyZ~][}gch`]`nk`QqZymtyfdmWr[wgj\xb~qflh^|bYwvgkkfbada]|b]|\{i]{_|cbsmerigiswvwhbggjp^~cfZyZy^}\{be`}aagitsd~vouhf~vvrve{\r^rdw^r_tWmWo]uWmSiVjVjSgNa}SdPcMaM_N`TgNbRdQcYkUiThQeMbLbOdPdUiPdPeUkVmPhQhPfOfNdLcRiI`}PeQhTkSjOeOeTiWlShNdPhRjSkawZuUp~D^jEamB^iRpyRnwUs|Feo;VbUq|Rp{A_mHeu=Yl8Tj=[n@_q:YkA^p@]lFbp@_lEcm>\g=YhB^iOjtXs{\xjOjrRntD`gLfrC]k?Wh=Te5MZ>T_>UYJ^TPf[]wiOk]Oh]ftbzl_vjo}zpcrVrgPl`XsjWqkRmhQjiIcbD_[?ZUD`Yj}Zxk@[RB]UNl`Su`Rq\Vr]UnX]ubQg[\sgUndMj]Tnd\vlQhaSie_uq_tr_urd{vbzvYwqld|i|o[qiZll^prWimQbjUgsM_jJ]kH^kF^fF_b?^[SplFdcJheRpnTrn[voUpjUpkSniPjhOge^vti}_vq[meuzw{fuissuym}tfwpXlfH^XXojUkjK``YomYmoexQbkER`^hxcmyVcl^kr]kl_pmbqoMZ]ESZ]jv[gvR_mdpX`pWak_gqA_=Ge@XtLfWgU\rHTmIe}]ld^vRhYhXg}Vj}S]sKh|Vsat_{cWrA\uI`|NVpE_yR[uPMgCZrRYqT]pUWoSUhNcuZ_mTJZ?5E/5G/FXBF[<[sP]uU-3"13 CXFEWAauZezYwmdx[b|[slzpqfb~WXvL[zPleaYMsD]Ol`l^\RnfKlF?Y=7Q68S9?V?F^FKcK7N::N>;O?*?,(<),A-5J6

    >SBBTADWB>^7=]7UsLRqHDb8Dc9Bd96W+Ef9=_1h^Dc;KiEGgC9Y4;^:@d>>b;Ac:Bg:QrEZ{MsfZ}KWyElYj~d}_w]}dex^qYw_}g}gdKtZmSRm8sZx_t[gOp]ZzHcRQtBXyJ[|O\|MTuC_K]~IXyEePVvEOq@Lm?QrDTwCFh6RtDBd7Ba7NiA]zRRkEKkA]{NcT[yJaQLi;ZwHXtFZzKLn?Eg5Bd2^M[{JVrAXsFJf9QnCPmAUtGXvJEc7D_5YtJVsDLh;\vIVqGSoGIe?XsQIbBRjC{m\uMRkCRmHE`;NjBa}TPpCUtJJjATuMPqKGdCIdBKfEKfFRmNXqSPiKC\9XsORkG[vSQmFJf@OnJNkKLjLYrVJeGC^?VrORoIIg@[{Q|nutdsfzopccTMj;nZwdQMj;QoA[xJXuHPnBPo?Ok=c~Md|Ma{LZtEWtBSp@Sn=Sn9Wv>^|BfQ`}LWtEB`2-M :W)Ec67S&?`10O"Ab4wsedSPkMw0bAvNnDZz0_8g=`|4mFsNkIfyDG\+Ob3_rBJ\-H[0I\4K^8h{U>Q.GX9UfFU7,F'GaB[vRG[?DS:CS4RaDIX;TdDPc@Pf?F[4]qKAS.Uf@FZ5I]6G`4WmAIc8C]59Q-G`9WrGToD@Y22M)E\:W/C\2Je8Ie4VsBLh8C^2Ea79S*4O%6R'2M#8T%;T)E[1AW0=S-QhDIb>SlIE^>MgFT0G]:ShH?V1Ka'oWlos/o.Xrd"Z[{ ~;l7 1B|?/D=Ry>0KLg^x/Gab{'Ur->p/PBhu;aj^jd}Ch?uI2K Yq2"7 (<F]*Kc)jCmBVۀi+^}(A^OMIh)F3-)7e-; kz3jz+k~.Viex'v3`z$^z4a{8,1F ]s9zJ_w,1(BF`Pm!w n@USk Fj4Y\MJx&6S 85K F[ Rf.Ti0\p5\r'Xq7na;Z +Xt,is4~7.G9P %#; sH⁏Fm/SOk>1P=[7VGf!SrK3>MU`2BOq9\~@Ei%Tu-i=?_Ll!Uv*^4j>_/Ouc6g8bxMEc+0L'0N.9V43N):W,t^>\ mPgM7W"@a.0SUz8Nt*Ku!zM]3>bQs&o9_$t7\v=Ahl<:_ a+g#I5[)OwQ'I&E)G(-I,*D*-G-+G(!=MhA6R'@\,dIY{6mHhDtWGl2XwE;W,g`PkQTlTXqWTmYD[PJa\Of`Na`YmkUigXljH_[IaaJcfPdimUik[qnSgcNaZbuocwmWk_MbVUka\vlToe[wpVto[xuUrrWsrg~`xtRmjC^d@]j:VkFay;Uo:Rr8Mn:Oo@Tr>Rl/DZUn{QjqH`^iywuoxwuv~^ovd|yMidgz[~lOsa]q_ud{PthMqfNphGhaVviphpUvZZz]dda\[}W`_RvQ\]MrTNsZTxarzX^kpoqpponljkiiipsV{[OuUil[]PtPwtslgbid~z}zzxzwqosqb`kk}`bnnvsh`ndslzu}}hffbXWkn{~Z\becg|_dcjinqswxqsnmzvlhdcx~motttuWz[]~dq}upmvvxdIgoB_m;XjIez@[r?XpA\sHdyMi|Qo~=YmA]rIe|Hd}?[pC_lpxTsvEgoQq|Rt|PoxNkvMksXuyofe\}UvQqJjIiLlQqQr_Zcoond\}EcuYvLk]za|Vq[w[zVwPqbf[}GkFlNrOrOsV{Y}SxX}SwEiNrKoMr\UwIjOpLmNoNoPrTvWxXzSsMoFiGkOsKmQqgfd`eeW}]sihu}y{ykrmcija`^}[}`Yx\zeg`~[zbVt]{Zxoiidbnbmd[xefrikmlou{vmh^~elbc[{digmgglmjsiufrpmkvc~ToYq\u]w^v[sXt_zVqNh}QkPiSjQhSjKbRiNdKbKcKbRhReRePhPgQjPiPgQhMa~NcQjKdNhOiVpWoVoVnTnOjSmPjUnPhSkTmVpOhSlSjMeKdOiPjUn_zPl~D]jGcnTmtGdhC`dFaeKhk\o<[mEewIfuFcr=Zh>[jK`@QjD,F8)E$0H,IbE\uVd{[ayXyqi`YqMj^n_ugxzynofi`sk}v[~SPtIMsDfZeY\P`XSqM=Z:C[A=V>D\DF^FC[C?X@LcO@VE/F2CZEQiT>V>D\G:O<4J:1C4>T<>X:;S7C]?MgF8W2E_=?]9@^=6U49X5<[5QqHCd9Hf:>^/Nn?_QUuFTrG<[1@_7Gg>@e:>a7GlAAg;Im=WxFZ{J_QlZTu@jStX}_{_b_fsXu\|ckpZjQdKiOpVj}dmgOdOhZ_NZ|JOr@fXpa\|K[{I]JY{F^KWyC]JZ|IBd2KnVpD`|OSo?[uF[sEbyKjUVqEZuK\tNVnKXrJZsJYrJXpHOfDKe@azSLh>Ig:PnDKkBRpKNlJFeCIdCPiI\uSWqPHaAG`>F`;HbixBwNZm3Yey|R^x3R\vGvFgz7iw6Sg"zJvHuHPd';OK_&Rd-Th1Xj8K[,Sd6Qa5P_7N[;GR6L_:ZoMBY;X1QkCG_R3=K3FT6L[U+H^5BZ,G^1W0?U/Ri>QhZ,C[2Fb5Mg:Hf7~UKd'0J +Qe*&;!89QG_:V?}AIh^}6i>He ?:v 2<OW5ai>aj3s}'L Jl69Z$*L0N:Ve=Rp,Lk(Gf'Bd W=a>`;]Rt!yM-L$@1{v(D!Eb5hSWx==^$=^*>`1Cc2]yF-NUz7_9=dc84TNp&[~-zJ<^ h,N``/sFk3y9d-BkKq#f1T.J$3M1$=!E^Ac}\Mg@>Y/VqGIf:.K7UVz8Fi!Hm\7Vz:hLtXFe7ZuTQkNVoRYsYNgUK`XL`^J^]DWWI_\PheUkiE_]=VY9QVOgiIbaVmgRjaUibhztm~u_seSh]JaTSk_Zvh]{n`~wKhcNieJd`Qkfc}tSmfMji]tSqiygobh{tn_HjInOsQuIoTxRuWxSsRrQrEkRvLpPqRqKlKmIiFgStStPpMn]~Pp}hLnLkVsYsYu^|jZ}Qw_bfbkilkxqmlprif_\}Vw]}pw|v]~jf\|nmdhrqbXv_Zx`}`Vz^a^Zwe]}cnmnoh_ddc^ymwm}zqlpomimpncgnkei]{sxt_|_|^yeZteYtdq}tYu[wQkKe}PjNgJcJcJbUlShWmOeOfMfNgLdSjReUjYoVlWn[uUo\wXs[v]y[v[xWsWp[vYsZuXrZt_zWqSoSoSmRjTna{IexV^=V\Hbda~}pVr};Wd?\k?Zm9Vi9Vh;Vn>[p:Yk@^pB_oIfs?[i=[h>_lCdoEblB_gPkrZvz_x|RlpAZ_9TV^tpYpkUig~{xavdc|hio_vg\wf\we_zhc~j`|e^ze]xhXreTkaWkgXmkYmkSfeMc_J_X{m|g~vkzn}se|r\}la}knsrvn~rsxfto}pusyzzirj~ʣYhf[om`xvf|bxrp|mwdztZnm]ppUgmTgnVjvOao?TbKckNjnWsrXusTrrB_cHeiLglSkpPikLei]v{Yqx_u}H`fSkpTemN`d`mqR`cN__^nobpr_iomz^jtSakVdpVguObnTfsL^iL^cGYa?Q[GYcCU\N^eYgkUbfZgk[hnRahRdiPcf[hqKXdLXfLXjU]sYaxJRh]czry_ds\co{wSW`BCOUWgchyRVkUZrY_tPZhM]]lld}]sjey\ex^fyg`schxhjxeXgKaoOQe@Qa[=MiLC\C(@,;VD;WAAZ=A]:[wNqeQlEE_>;T9LdMAY>VpLSmBb|SMg@Hb?E^D]=C\?IbD>T89P6;R8AXA6K5MfNJbGE]FAXC4K94N7>WB:TPA1B31G15J3;U7B\?=W;D_@<[8:W38S0=\9:Y:5W7?`=OrJEh@>`41Q"qbWxGQq?Bb1k\8U+B`7Dh9LoBKrCNtEJp@\N_NRs?eUhSpXe{]d}^mr_iMu^kS]{HkSpVktYgLcJhQ[yDZzI\|IW|JRuCSuBZzIPq@TtDbO^~IaO]LeS_LcQXzH`OcSaRj\i\Ol@ZvMYuLVqIgZ_zONhPi:Kd6ZtDId8\wJVrCfPlUlUtZh~OmUf~NlUPh=i}UWqKYrK^wOLe=QhF\vQ^xQOkA^xMWrJQpJNlIDcARlLRjKPjGVqMKe@PiGQkFUoHOkC`{RLh@Mh>SmERkGOjH^wSSjFTh@ybznfhmX[rKVmHYtMNf?ZvIfUZuJ[xLJf=Mg?Id^4Gg>OnEKg?n`wh:U/UqF\xIhQc~JSnZ&mVi~P\p>gwEnJsVYqCPjCa)Eb-XsDGc.jNqQRp,Ci"rQ^=Qz2Z:kErH|KvKk=uBj6Yj`cvi}ypoRgkngi{qن}rtfs|{n[zG}RuJbq:T_.OY1S[8L]4BX1H_<:T07M*E\2F_7Ka:=V/Ha>>S7AY=F`B@[5EX6J[9O_=]jH]lHJ^9H^5QkAIe7=W*Mf=E^4A[1Qh>F\0G]4I^9>T0OiBSnCSpCB_0>\0SnEJcW1AY1F_7H_9G]4I[2]pINb/=ObvELa.=Q!G[#^u.TVj&Td6Ud?`sL5KE\3I],"@Ge(e?j9{Zt2G`G^}=:=`WOH<7oIDv3?k%NfPg%c{8]v/Kd/H)C/HVn#!8KdA[/Gdv3D(0AMDO9H ++Cl PpKg(>; +Lk+;Fg]{"mUNUZ~!!D2Q70O$A 5P Ea?\;#A9;["qP-Q h<*LPn.2N#AZ:/K*3M'0I'/J+)FBc3Eh4,M1R$-L=Z)aHWz:Vz4rJ_~1C` -MHjd72T;`;` v;Ek LoWz#aQ>f ^9Rr-Xy89 FcEA^:RmD'@1L%7T!?^"dF4;Lq+Z3?dNo0Qn3Uu3}[ZxDUsGcX_~SUtQdeOjWC^RBYQ?TMPe[=THMf][un9SP:VUG_bD[\I^\Pe``ton}]mgipRh[Ti^ZrajserOjbNldKhcD^[i~{lzitC\a1K[4Oi2Kj5Mp7Mr2Ii7Ke=NeAUa\pnpzlmpjjdjfhgknnqWnaC]VRliMfeAYWKc]VpiVxqKkg>^_EdiBae?^`9XY;W\>Z`IfiNlm@_ZPoe]}kQpYejeh`bcdooEkFqpadkpZ_EkIMqPglQtTkl[\eehmmt\d^dZ~]rq\}[icppKmMek\bX{^hmstljyynmwxpq{|zxtouskkfhKpPNsUBeNorppiijk{y~vp~u~wpmrsx{tuoj{ymh|uc^zuqpvv~~{{tucin|Lm]Qqcuryy}psqumpy~ipvy~nprtvwz{oyt}ju~xIkYxeuiyfwgwjyx|xovglipnv}tnd|Sxf[omkvutoqssomkje[{VyJpNrRsSsStZ{YzUxLmQqKkLkLmKkOoNoAb]TuTsIjLkWsn]zoqWuKhSrYw[zWyfTvLpfY~ljdcb`jelm_^__`_hUu?_wOp\}[~fkpkgpswn}rqpsqno\|d\|X{UxWy^VxVw^}ojhaallc]gcYxszyui}s|lwwvwssrmuppwznmt}stwvtxunw}snlb~YuVrVqTmPiMcKaNaI]xKb|PhSmTnUoYt[vVrWt\|\|c\}a~\z\y_|YwTsYwZxTsWv^x\w\w`zf[v@]k-HQSkl}r7QW3LUNksNir5P\3LZ0IV9R_8Ud7Ua6U`0NXBaj:Xa?^hHepLiueoSfl=PV8LNbwrvyooqqWuYVr\Wo]\xgYucXscNjYVq`e~n^ueUl\awh[pah{qwxyt~|rf|wVkf\ql}ju_ifvnysq{simjadgvzjsrbmnXiiSgcLg_Vrgb~t\toQki_xu_tv[puM`j`uDYi@WfLblF`ePlnRrsFei?\b4NV=TZQfjNekGagPltMgnOejTijPceL[cQ^hOZdOWbepwt}kufq|UblWfrO]lTasXewUeuTdtXduO\lGUeCSbFX_l~bwoavn{]rj_tnRfeNadJ^cL]eIZeM[hTXmTYrLPkUXvX\whl?D[kpfn~TZhw{`ft_ftY`r_gzT`ldswbvm[q\g`pbZpHbxUohkijlby`FX=dsSP]9jr[wtki|ctXgmwpbzrIeEQpLg`GgD]z^BbEDcKIiR7TB0L=2N?A^JFcQLgU?XF.D36L:OmU9[ADeL9XC'F6>ZKPnZ6Q8B\>]tTXiJ_sRi}[I`C;S:JaI]uSTiBPe?Mc?ThLK`ED\CCZ@AX;AY;PhIMfGG_ARhLAZ;JcC?X:E]@qgm`dV^zNn_iYxi{nxpekakblb}pyjseiYk^nbe]\zR\{SQoFXzMgZi_SzKU|P\Wc]TxMSsHHj;f[Z{O@b7?]7?Z9>V;9O5,C'0G,5*B+,D,/G1-D2/G76L9.F11J15O35M42J4:M>5G91H4@WB4K16N17P2^68W)[{H,K|gmYGg6Cb4OoCIl?Di5Gk9NuALs>V}HW|GcO[|EoV{`d{[h^~\ezYdzBa|BdHdJu[x`s]s\pYbLWt@Hg6XyGdTZ}KWxFJk9o`Hd4VuB[xEoYkT`NRwCRzDPsATuDaPUsEVvGZyMUtJKj@EdWrE\tKPiASp@\zLXxISsDNk;Gc3Ni8^wHHb3[wGu`eRfSgSr[v^xZtYatF_rHh}ScyN_tMj~WXoHShCRg@QjGQiFZuNWqJZuLvgYuN`|T]zRJgATqKNhBa{UufKe>h\j]b{SUnC_zOGe9ToFRmEb}UZsKTk?qUn~p~\d`gczKgzR`xOd{R9S)fWlXXrBLf8E_6G\6UkB_tKOc>h~WVnBSi?axNe|OfW_yO`{Pa}U_zRUqIWtGWtHOkB>Z1Jf>ZvOQkDWqJTmEXpH?[2Pk@^yJd~NQk;Ng8VrBHe7Jf=UrFdQWrBXsC\wGNj;D`3Ol9Zt?Ni3Jf3mS>U$Ha1Sk;Tl7c~JGc4Hj8>_-6Y#Df/Bd,Wu7Pm3?Y%Q+:I*BY/2HH\5NcBPd3HX)CR$Y˄4FIW AQ W[t9AXQf0H[-Ob2qNOf+J_/BV-Lc6AZ(/NIh$1Mk+B{?ed|nxցԃތ܍܏|Qf'Lc$~U +$|aKg*g@Jh%{WtT=Y!3QpAl:`8B^"Ea(Ki'W0RAaWcaw)_o0Yh+\h,[bK[YnQ8}8Og[sKi +b% : `zI%;Ph,i8uD+A[oC(:IF6f0E6*=/nWqA\ jAEY0m~4[mgxn5s3W~hc]P^Y䃁6]vSo ^~sG5h,c2a8WIf@[c~*?{5RNo1RFd ʀ8UNڇ!@tQ7S#2M"0M jQ+L XoH#? (A-F )D.I*C#;T<1L/i^1Q$Fd;Ea5:W(0N4TJm+8Zc<`l5HTzDFiTx#w>Gw=W{LqJt?\*Lp&9^yVTv;Fg2-K"B]70K&XrGXx7Ux*^orEvHPy)`;4V6VRq3Ut8=[%8U&aR`SRqI5R5kpB_I=YDD^I\tZSmRIdLGeRNkaVg8Ql9Qo3Jk6Mn4Jf7J_-?M\oszoton~nesmomfetwemQkbA]XIc`UljQebXoja|Zvv@^`7TZ;X]9UZ6SW7OU@Za:VYIgeKjhJhbPoaNo[ck`j\dqvqtru^_{zgcje^S\RnkY\[ajuahX|Zruwv~~|kmVyY^bbgovikTxPhfCdB``\~]cdst\^fez{||x~ekjs~spzyllssa`X~W|{ttojiehfjim|}^_~}}{vsc`vututvkoacqtt`tFe^MkeTthmv|npooknRuVSwYdg{sv~stqtx|wxkp_jxwdrcrhxctixxzhp|rysu~oyy}~{^mY|lk}gjkdmo~xveZykekkgt^_KmKlOpSuTwZzZzOr`@`IhIi`a_~>\yhnwgb`}XvhijgonodlZ{WxXzb\Ru[`_d]j^ZzOp]}`X}Z}YX{Tv\{\{Qo\ykbjddjnX|iuVxpupqoa~u{nmc\}__kUwVw`~_~a_~d^{lb]}RuYxWvfvndwxkblrsiljiplttwooisttxyxqqs{{}yqxuzptqws~}|vk{sx}c}[uYua|^{_{^yZv]{_~c[~e_}a~Wt\yUrXvXvXuZwc`}ahmmmQjp|~tv{rZwyVorLfk`x}c{FbiA^e?\__{eYvz=Z^3RTHbaUngRg`Ƶkncgccfhlofk\}cYv_MiTYq_Zr`]uchnqvx}xwp~ypyz\gg^kkWffbrrj||i{|ezuh|vozmvgo}m~sZdawsy~nznp{lxv_f^Z_\mxry\mgNe[w^xm\woYrmZtrOhd[ssQelAUc?Uf;Pd;Re?UbC\dGegUsvHdi>U^9KXIZeSekK_fLbkE^eBY^I_bRdgEUU\knamtYbmis|ryenxW^nZctOUgmt`g|X^tW^sX^uhnai|MZiIVeIVc[loyks{huXofXnlZnp[ouM^hO_lJWe[auekY]{MRpFJfloAF_RVmQWkW]oPVfTYgagsmt`juS\iWfoWfkn}Ui]uwtnuiXpKcyTogwrQhLJ`Byuers[{e{o~ZapLuhydtcNhFC`E-I@.H@1M@0M?-I7P3=U7b|Xj_ZwPXvQf`g]f]KiAYwN^~T_T[|QTuIJk@OuHaZkcUwJQqGXsKZtNl^eZIi@XwQXxRVxORvKmdcWcYZRc]]Y\UY{N[|ObS`QfVQoEZyRKfBF_@8O2GaAMfI^wZBYA9Q92J4)@.%9--B1@VC8P9D]EBZA2H3,@00C5+B1-B01G11H+5N0:R3;U96S50P16R3>\:Ba=5X4EhA$EVuEYyGo>_*?`+aOHj8]~ONsBOuA`QTyEV{HSyBbMgOzbiy^h}[~^~ZxUnJdrQmMsTx[qW`zJ\vFc~MlSXtAHe2WvAdQlZucIh:^zMUrA\xH{gcMaL^JMo=Ac1_Qg[\yNTmEXsIWwMRqGXwMNmEOnDNiAZwNp^[vH[xGMi8ZuIZuHYuE_{KQlA]xMa~NB_0No@YyJZwHJf6ZtEmUWrDb{N[wICa2UoBfSf~QK`6P`5_qGqXYnDTi>TiAVkC\qJq]WlFSjCPjCOhAWqJ`zSb|UdWKe@UoJbWQnGTmHYtORkC`uNVpHYvL]yORkCMb:]vNXtI_xR\vNOf=XnAoqs`vOgyDzVmNg{JrWlTlVs^Sj?[uIkUoURh:Vl?WmEZoG[pGo[UmChUUlBPg;[rFLg:UqDb|QNl?C`5Eb5?\.Hf8C`58U)Nk?fWId:Mh?RjC:R*Gb;Je8YrCSl=WpAHe5a|LOl:[xJTrAMk9[xF]xF`{IXtCSo?\wEWs?Ws@\vDMg5Lf2XqCWpBTo>OjMh=QgA`tTMf92M!Jd9Nh=Ph=YpDVl@@V-J_6G]0C[.XnEB\4C\5D^8>X3>S.AX4AX6+E ;T*Pi>CW1:N+;N'g{>Sa,Ua/ep/Xf(LXEWـl~R(6GXf|-l*AUZp([p.zOR}MbWi3@Q+7K$< mGYz LgIOkb {d'X:y*+;#_xC' ;*Mfcjdk|x}rԂZq#Vm+zT*B;U"D_&7S>X$Fb'Rn46Q[u:dd~/^g}:ig|2n:yB_x!azr,f}#J`z4If5&tY΅Pr6?L>c ?^?ZH_ j~B[h'w$,>Rs@]*:vDk~EW6QHdT>`ýLA4tKYG_ng 8[|CV{-Lk(Ok*rJPi#cz-ZuCOYv;[RqJo3.Oh&A*C :%A*GPl1c/K2M@Z+u7R$&>?W@&@((C$6R-VqM]yPNi?>[-5T]AHk(>^vJRfx6c'_Uah(P>c(Qc(l4Ov+O)Ld7Y~5Km2,K$7T03O sSn=i6(My9m.N?iiF(K7W%WvIdZNmIKkJBb>ndUtHcU[TJnK@aHAaP3RI=WT2JIF[YCWTUgc_sk@ULOc[gzsbvjYpaQj[Lg\Hc[D^[?VX3HMASUQfeSikIaeF^l+EZ1Jc4Jg6Kh@UjM`kexzq~QeW`tbnm}w|uvntlojjiekRm`0KEQkgMhcE]Y^tnf~zD`ZD_[9UR7QP6QQ\ttUnm>YU4PLB`\GfbTsnLmdLn`Svagr`kOqX]dgj}runnxqupkfph`Vz|o_wlol7]>Ow][iisch\\rpnntraahlfjbhrvpj˜a]bcjmcgjoTvYbhbjTy_`iy~gldlnvotgj_bRxTddhgxvnnuwMpQowfq\}fmwkux~rtyvwumn]_ei[ajopyOr_8WODc`NliTqlZxgitjrch`c}hlvjqjqv}mpsscc[a^aadrxnvkpinV}dPv`Rxd[~mbsgwcsjyiwkwmwowqwy}mpoqsxqxu|x}|}l|vY{sgJmga}_wm{ve^aec`~c~eTudZ}=`OqV|RuRsƠٟ~YuěΏtk`Tvfbi`dhgqkoUwSu`lLlMnLmLmY{LnBdCfDgLoY}Tu\}MlOnWvVwUwZ}LlVuTqWuRoXuYwaUyZ~\W}l`PtjnVvSr|_}pfmjgpnknmeic^dqjla_}\wZu\{Wxf]|aeVvic_\]__irnj_lljkdldntqlptkixw|{ts~qirwvnqzuotmrzxuyx~nkhgjsnnlfkfjg[xWsYv^z]zdbcgmh}y}}xw}ufvhzpyrqrMkeKgb\ytXsiȲŭvph~rkvawk]s`dgy{egcblnijhjjjjn||yxvwuv]lbs~|ftur}}pu}^fmbjqakrju}iwsh{wavnd}p`|m`|llzpcppjvwcqohzppvtv}yP\Pp~pvzyNjZ[ukVphVpkUqlPjgUlmTkr?Qb;Nd7Kc4Ha:P^MeqE]c?Z[?U[EV`KVepzPajAR\L]iCV`BT[Rain|yn}}pz]foks~bhzbg\_yVXpVXqXZrMOh^_v[[sSTi^auXcl]jtVdki{zkwjrouqxoyTncSheQgkVhpK[gN]kP\mDOaR\rRXuFKhRVrZ_wgjOSj]dw[asU[jlt~eoxYenUdkVdkpbxlYpaE_F_y\icg^VnJ`xVrf|mtszzmqXkr`~qdqff}bHbL(D3-J<7SE0M<8VD:VG6VE1OB-L@&@:%?;.HC-FA&>9+D?-C<'=5#:0(E8/PC.ND&D>*IA.I=#;,E\HDXAM_JKXD^lUM\Eat`7M=,D1EZD[oRa|VB\7$>;W4lfto~|fcOjGsijaOhJIcED\D0F12G7+@33E5ATC>T?Cg:dW_OUvCXyDYzBoYTt=NrX(YsBb~K[wDTp>\xHOj:MiiXPj]wI^{KRn>UrD[yHNi8UqAYvFQl>XqCRl;So?ydD^43N%Ni@WrEWrAKg5Xs@a|LPn=XvEZxBaIhMfNs[ZwD[yHXvD]{GlUmWhPcKLh7Pl;mX]{HGc64P'PmEMi5Ts8C`$@ZQj/Vn'g8Rl"bz1q@ax.RkZt whj1ay-@xw}7HBGFTROOn@pIlCkBfxEm8t>Di1p>tCcr;nz4|DBIYKB^ZVfs^pp̀jxʀ̅Ћxω~pɂՉאԐҐؒ̄ߔzr~W`"HVN`-4IDW/CT0IZ,Pa4GV+I]/ZlAN`9K]5Sf?AS-AU-Ka7D]6:T/@W7E];F\8AP7TcBTf>Nb9G\0@W-Rd9Xm@+: Ѕdpag(Y$MdQWVoWooymud?Uk+Xn7mR=W#.I+E/H?Y'Gb-]wASk3@[Yt1Nf11JD^0Id4^|BzWWu//LZy3vM1L4M Ph&ay2j:Ni"c}0_{&{:g&~;Rn ;je#a*"3 + p-_xC[/C0QnUs)]s5Ԋ_<|4N\r&AO=H¿_XüJ@yA[x-Rjz%,Gx8l.4ZQr/:VGc#Ga ?W^t-xBGVr+e)FcKii,dk0"A)C@Y53M,,F$'@4N E_+/GHa-Vs;fE7S%?3$<% : %@@Z7)C)DkYZzETw:aF!@4U^~3LY?[ p@ZYG>x9RIS~g:[~1l9g/`4Ru=,K'Rv5> @^/Sp9yYjF`:{VMm.9[ >^$bM0yiNp=Qt:YCMu7i\ZVFkIDgJ;[GQm_7OE@UOEYTRec:LLUdMdx+AX1G_>PardvrWi\RhW]q^XkW[oVngmeypngiejmoydu5PIVpjG`\Tlb~~Vqe~pk{7TJIg`MmiOmkGhaTuj]pm|JkZPq\en`hTyZTzYMpS`bjlwyfd\WqhqfÏym{vhlRYBT]GWbguZfveqYev_fTXrYZrRUjrrkl{jr^gvWcmP^f@QVM_dL`aVladynPhWD[GLeJQmL8T49S1j]{yh~dqbyo_RcGk|ci|b?Q7WjSOdN>UD1I<0KB1LF/ID-GA!:13NE/MD-JC)GB+EA/HG$<=9775&<:/EB0EC0JE"@7(G@:595(E?-G=BYJ\o\TdRKYHDUF1?/;I8J\M9M?8P.B73H93H74I74J65M4BYAB[@8U98Y8<[6@a6PuCLq@Mr?vep^XxGXyFSs?^H|Eh0Fj3SxA[~HMp;cOjTw]fez_}gv[v[afavUuUlPiNjLy`[vCmYbOXrGNh:`|K\xFiQiR\wD_yF]vGUoCOk=lZeQNk;ZwGcQWvHYxNYuLSmHQfEYmMe{[QlJQqH[{OKk>PpFJf>RoFd~VQlDKe>I_5[wE[wENlNiBUlI[qOWpLJg?Ie>TnFVqFUmEWpFQk@G`6SlBcyPs]dyQWkA[lAp^n~DW`hzY{W]`m~Jl~Il~Ll~M_qBWl=^uG`tDk~KfwBuVi{Kj~QkTJ`2Qh;jSiR]tFmUc~Ma{K^zIfQXtCGd0Ok9Ie2^uF~dTm7Rn9iPpZHc7?Z2A\1dP_{HQl8Xt>Pn:^|I^|HoRz\fLfLy`gRn\b~Mn\vd\yGgSTq@\zHOm=<[*mZgXqdZtQC]9?[3Ih:WuD=^-Wt?4PNWvFyR\w6vQ|TW}G{Au8bdw-|MlAWOlv5Rw9=x4DQ^S]^jf[LSXRP^U^hrt[[[R~H}FUYiu~scfSwLZi{V_WG_(;R1JYr0B[Ok"Ok7r7~:q#{&O:?E&pAˁQLgRk{9BN7]7Md^v,2J Rko,AC5{,Qlfe9Nge|-gxFPߊM>HUX]z{,?^;-*>򂙩7pt/@]6VBa>Z Ni0Sm-[u6[u6^x0h2M/J!/J$*C'?48P[v[PE*L#DQq:9Yc9azOg0d"k:.N8U -I:V Uv5_:dxNc@a@lIkNaNZ~I;`-;`+|fv^Gs04b$5])1W(LpE2T0QnSJcTLa^S4BU`liy[lrReiQ_fMZelsrwX_tlthoen~dkz^fq`errz_hrZbnmt_dt\]p\^wLMkSYwZ`}LSkQWkbhzfkOSgQWhZckmy}asqm|o}Zwe`}h_|goxb}rOghDX^I[a>MUO\h]hujt~akyiobfz[]qcdvuubbtDK^YbriuXisTgmWkoF[]Oecg}qaxjG^KOhOWoRUmN`zWxi_YvMOhAztt^tjkzX{jfsZtj_nZWiTl~iXmVf~gG^J:RC2LB#<>(;?1EG%89421JF-HD2QH2QI!A9(F>*E<+F;H\P@QB1@37D:>NE4A96B:HXL:N;3C]:e~Yrj[PkCRlIRkMRkOPaOS>.B/-A3)<22C7-?2BUE4K96K<';.';/9MC]~QHo;X~Iqc[}JZxGdSNk:WxEGi1Pr;Ac*Kl8VvDQt=Tu@{f~glSz]xYayctYsXxXvWtUg~InQmRVn:f~J_{Ev_}gUpCNi<]yL]yJQo=]{F`KMi6XtC[wF_yMLf9\xHeRbOqaWuGHe8QlEa|XVnJdwYduU`tPZtNSpGIf8UrDSqBXuIo_^zNJd;G`8G[4E[4>U+[vF^xGUq>`|LjVJa8Pj@fVYtF]wK[rE]uK`uLZqD^yM`zMTnEhVWnDh}V^sM`vJ]sIi~Sh}T^tNOeDN`E2D+=L2HX:Od?H^6Oi?VrGOjBeXa{RRmE[wO^xP[xMKh:\vJTpANg?\sJ[sIXlAjQg{MsVg`}X{OmxDxP^oPtSqNfwC`q;ct>tRqNoM~]]r?jPnQnNn}IhwGj~L`vGlRlUjTXoA[rDXoAf}Md}MTn>Rn=XtCTp>Ok8?]&pXuXy[\vSq8cJiSSq?He5Rn@UqBXxHTtCQq@Jk86V&Hg6]|KQtAPo@Dd8:X4U˃ነ=w\oQ]sy+MJT_ZaMggtKpDdw9{UfzCg{EQf-}PbeDbXXl+h|?bs9m{@s|@W{AZHLNz?St[w3> iEL9ffLgq tQltQl1|Ks"&$-&;K/OAQPCU^]UeN?Ul$'&qH/G*@-E#0 ,C=T):R#Of0Ga'>Y&'@2K#=V/;U.C\2Ke6.I3M:S?X C[%=V`z2d}7Un+Nh[v.0M8S i*@f9EU-LY%kw6ʃO\@LAJK}6KR `khR0ECx:SmNh,N >][z83V@zLc)NJGPJlLo aUBjAfz*M )I:Y2S}KOmLOtNp#Ba~A-HnTcH/N8Za>jJXz8oPdHlYC;`4_S,RFn.mSOx6|gbKcNEk5sk:V?BZUF[`6IR?QY;MUSdhNaaG\ZDXWSigKa`RgkFZb9LW2FR3EOHZc5JPB[^D\cK`k\BaCb>^A`B^GcC^:V?[B`LgGbFcEcOlKgGaD_Hc;VMhFaGbOmJkVyMpTwEhGj?aBcRrRsPrXwStdZzPrV{QtmdY{?b{Gjb^~\Rx[|ZyZxVvhiibZy\~ahlongdsWwPnQoRo]{]}SsQpXv\z`[~_LpLoJmJlQrTvWxY|OqWyhZ}WyY|_ebfbjaV{jxs_avvweXx}l|yzno}mitukgrqghk_]|PqNpUxVv\z^{gmr~}yx}yQkcJg]j|q^~j_jewaqevXqiE]U@WRH_YRfawqij`tiwh{otnde[w`Vr_jqptqnwrvnQoIfcdfoqpsv|gmUp[iq]yddjy|uua_sq[}[\|_jgmgwzur_|g}fIfV[zkq\}pPqlPokOghD][0JAt{Uy[RvWgfiekc~qnfe~d[t^dkSn\Wr`b|p[uk?XQLdeWkmLbgFXb:JX2@P\gwqxP\fQ`fWelES^U`qdo\cy]ahnjpzhp[cv`ewio\arlsir`jspyZ`nbf|WYrQSrQSlSSg]`oikzjn{``r]^odnwhuwi~yb|pdsYxhjualbl_|q_yvH`^DZU]rmVgh_kpluybgu`esehrru]dsVcnHXblkovc}tTj_E\LE\GQiOLdFWrNc}Z`|VTvNg_eY`zUr{umn}hcp]WeSVdT{y^p_\q]UkSb{d9T@4NB/KA)D=6QJ.IE)DC&==*ED.IG#A=$@>!;<78%9<.CF*??2FGEZW=WR;XP;ZQ9XPB`UJi^-H?)C76L?8K@2C8+;22B=,:5-90O^SMbKog|lwggWWrIWqK?V4F^?I_EEWDL[I=N:T25O.3L,>Y9UqQIdH2+A5*=31D:1D91F9+?3#6+#7.2E>2F<+?31F75J;8P:8R;/L3;Y;3T2[{UKl?5W&Bg43X(Ee7]zO\xKLi9?^+Nm7Mk6aJYwD[wGkTw_s\kRayFqljv^rZy]uVrU\rA`yFt]iOd|Ju\mRoUa{KRl=UqE[|LNo=XwDOm;On;Vs@^yKYtFHa4Qk>`}LTqAZwHIf7Gb5Pk?hZ_yRdyV_sPWgEVlEYrIgW\zJhVgUdQNi>Mg>SiC\sKCW4EY5Qg?c{L]wEb~JMh8ToA\vM`wMZqEXoDWoCUk@YnE\qHTk>ZsF[rF\sH\sGYpDcxLaxMe}OhVjV^wOG`;?S4DU=HYAK]?FY9Pe@b{SbzPgVYtKWqJIe9Lj=UuIUuITpDLi:a|L^yITh@i|Rm~UpTjn{p}\\[d`e|`l}RavBewErPuR`q;nKxVar>nNiL_vG}c\o>izJasCk}PpWd|LnWf}OpYjSqZw`ePlWgSTp?ePdL[wDk[r@a{BawA[r;e}JjUUpB?Y/Ie5kT^}D[z?_}DWu?Nl6Pn8Xv?YvBVtBIf5A^/?fL`o0[wku=IXWe:6@{Dp|9jy?gu@Sb-jyIUc8^nDZm9i}?i8by,cy/SSi,[n6\l6fs;wJ^i1hs?P\&lwBly?Tc&Qa#ev8pEK` G[g|5h}8tE_r2_u4f{_oC\n@\o>{XrHiw=Rdn6\vvL|}U]}yMzvEsxrpTRhq;v~LK[0>K%K[2cq@vN~YttF؎Ɋ~ƍˈnsp^g[mYr<%GnQ3!?W#_{ Xok}(<9p;fPnq cB_n6b#c{" i ,FC_.$ Rs#(~A[4@>KA4<8O/8JK6a{ f}0T}Yt'2,BQgE5K&7L%=S-CY3B[0;T$'B*D1I#-E!2G$:P*0G#; =X$Mh2Un57O_w8f5wIkAf8Ni#0M]k4Qe$Ys;Iel+A!@1N/K!'DlMl>'D$?ZwN/N Pp?Aa/Cd*(K wNҁX>^Ih.3R4SjJSt&h(AJpm7\rl0NtzMr(Q8Y,I/L0L k1Jd)]uD`c<2P?_'1R?a)Hi,[_-6W,4V.Ch>6[*9_)Io2Gp2Hq4Hp4kVKr:e\>]APl_G__-CIQ[8KRF]aEZ_I^c6JMTgfM_WO`Teweywi}jf|emkZoX\oX^s`^saYo\OhSXr\hoD`LCaMxIe[;WQ6TRHedIgfhB\VFaYRlaPl]VtaVue2QADcU_nEeT]}qavIi]Ll`LlbRrhTukTvjEf[RuiOudAgSRxaOqZRrZUt[kj[~XddVz_AdV>]ZCbeC_dJdhPmnJicXvk]~nWxePr]JnWfrOq[kujs]~eYzdYzfhq_hcpp8TQ8RR:SVNfk\uz;UTOjhCa\CaXHgWbi_dv{hnlur|fmfjhmr{EhVJi_Xukc}ucs^{m_lfpo{QpbXxoHgbDc[WxiVveftduKocVwlVxmXxkUvfkxs~nuqwnwu}qwlnprnp|szgtX{haqro~`pPraVzk]tazVytNnoMlqSpzNjvUr{UsyDdfa~^~x[|pbw`r}w{isbsdutj~_wY|rfb~pqayoy_{zŶ{huhgirivufrswwlyy{aSsoz}jlmkhy]xX}`BbA_?\:WA\@]CbA`DbC^B]A\F]Ld?[F^OhHaPhGcMjPl@ZHbGa>X?ZC_HdMjLnHjPrUxEfDeA_@^EfHiJkLnOr^_Ej|NtInIkPsWxFeNmUsbeRuX}Yy`~OoY}kkukjehplmn_fc\|Kkx\|fXxNmQsNoSuTv__aRtPrWz]~^QsXzOr]pfUySvaeefgb`^bgnekhihc^ers{ve^}\{abbmY|cnhajl^~`PrWwWxZwZvhp~yvyWpghw{wVthLldTrl\|r`sTsaPo]`~lQn`?XKF\Odyn}yt[~We`mhoh{nkijYu`rxy|tstnrgtl[yV_}]gjejjrgpdna}mb~ma{kan]zfqvlloje^pirkY}Upfratboydukgis]c}XqpGdMQs^bqY~l]r\tEd\Eb\Rmb~ksQuShkY}YW{VZ{Vkbul|rLiQPkU_{e[xb_{eYtay[tiUlhdyxSggL^dER^q{`hvFQ_JVaER]@XG7K>8IC9GC.:1.>2mjmb]|MSrCHf:XsKpe5N0E[ECUB7H85E61C29M=MdP5M9A[EKbMIaL@XBAZA+E0 6&0E7%:+0F9+C5/E<4HA3G>-A8.C:,A8"70.&+(0,2-#7/ 3' 7(3M85Q8,H/$F$/N)Fg<{=b48]0Hf=Ie;Eb4Nl:Gf0^}D\xAb}FhNv]waq[lWzb~fiu]qXpXt[v[Rh7f|Kf~NWnAc}NQi:^vFv\Yq?bzGlXQj=_{KWvHWtDUq@b~M_|KSn@VrFHb8Ng:Id7ZvFeQfR\{HhU[vFv`jVKa;ZpL[lNL`=PhC^yQZuKb}Qd~S`|PNg?TjCFZ7>R0?Q1RfCH\49O!Tl:^vDCZ+;S(WoEWlA`uKXqBYl@\nDbtJkVZoE[oF^vLMd7byLUoC]wJfRo[^yLa|SC]8H_=WoOK_CNaDRfG`tS`wSXrIYtKrdNi>Lg<_|SLg>YvLZvMMi>Rm@hTMc9auHo~Sk{O}a|gjaqvQcyZmxQ^kBcqIN_5Uf`vGw][sB_uHXrCQl=ZqBczLbzKJe8UoBXtFRn?WsBWsBu`\xDKa-Sl8f}H\r?fMlVA[/SoAr\v]]yBQp4Lk3Ur=Xu?]zDVuAXvE:W(\_tCWo;@W*gxOYhDTe@Tf=bqGaj@NT/Y]Y@YIa$iC>W5P&!4"7* S],(/NS!by{+o#[">ot'4u"kM?S wC~GVs'KgNo{I_iG>Y"Vq3d)VN[o5InU} q9PYz@o2]}$0R;\pGfcG6^;!F4-I;V",GNk)8X wEU{-HoLl;?_Mn48Z%gU0R_?cBSx6Pr<<\20P*4T/,M$8[)Fi5De2_P+Q;`*`QJp=kc6W6FaL.GA$=@0FJATXTghM^^GYXK^\ATSH\YI^\Mdd,@E$7A/@M3DP:LU?SZUioAVWI^[_skiqjjk~inkmijfrolkezdk}i\p`Vk]LaQ`vdUnYjmmqciRo\SmdMhb8RRIbfJdes5PIE]YPjcQl^^{gireox[{elz]|pDcWCbXLldQpjWvpFgb=[Y:XX<\WFh`Fl^U{iwlz[x`bbPsQz~Yzh7WRGdhKhkHbe:SYF`cF`bMjhPmgRqhSrg`qBcL\~fenktFgRLm\Mm`Xyi^iNo]Usg)DA;UW=U[PhnE^c=U[;TWPklPmkSrf{ou_c]dalbokruxrselp|A]SOjbSngToh^ypl|alo{Nn_HgaEcbEcaEd^awPpeQqgCeZVwnQnhOmfOmbcrdp`keolwoyqyrtophjgigjku\}k\}krqdvGi_W|uW{vKomVxyIioQoyQo}LhwOjvTqzKjmUvu[~xZ}v\{s_yaxjyun{^~qmpd|poh[~zqjumry~Pvx_acegglwqocqtpruqrdjnfjbZvgvpc~QnVw?^B`>[]@^C`>ZB]BZG]H^>WF^G]G^IcIfNlGcB_E`F_E`=ZEaA^HiMoGiIjVwEcA\A\?[BbFeKlQscalUx<^yTuPrOmWtFbIeNjVrXxWyJnRtQrSuZ}`ebgbagihh^|ebfunhdaTwJjHiUvTv`cZ}JmRuUxXyTvTtVvLladeambadXyhX|hcU`khliqokoipnzY{VwPlxMkuRp{VwgvTvkcglijedTu^|Zyfv~||qoc~xRoeVujgyFh[<]UKkgMnhHh^EfXJhXWs]`zg{²zvnoeg_a`fglklbyaXoXZpa^xjv|\~`lkh^bWiacc\|_lsfmhs`m[zibsc~qPh\Nh\Zuejnfcpimd{vymyiowjTr[zc{a{[chfUjfvy_jV{c^pQvcKq_k_}pNlYVw^RwTb```LrMbbebfakb`{WIeMQmW^yd^ycmpgmhojtVjd^slUigZkpnyrxnscgvLZhK[hGWeS_teo`hUZ|nsuwsy{zht_ny^jwR_oWerSfmWlmdzxu{VdqYcuIVbthru]inQ\eWbkN]aJ^YZtfVvdSva]mjucmVv]Rr_*E8w}ia}otcnphrxuU_jZdmVci_krugwucwu[to^ypZvhYtd^ygYyaYw^NmPGfFLkJ1P/GjI4S58T:IgIMiJfa{cuc`paWlY^n_uvVgVI\H\s]lo:WCC`S.IF1KH,GC$>= 9;!:?*BE(BE#<@!=@69#<@3KQ*=D0325-?@9MK+E@)F@.HC'@<2-%>=0IF%>;;QHLdVKcR:TA6J?APK=JA,<1AW@]yXVuNVvJ\J[}KUtJNhF/G-DYE&'K*fM_vFJd5Ic3c~PYsEWp@]wFWpAPj=PjALe?LhPg:Tl;Lb4c|MRl=Ke8\sF\rDXqBRlAUpEXsHXsGRn?Ie5^{J2NIe/axD{`Xn@^tF>T*8R%n]\uG\yEOl3^}BTt:_}GcJFc,Tr?Ge3UrDA]2C`5>Z0He7<\,9W/1P#Ba55U)5U(NmA<_1Cd9=X0;Z2A`5Ig6hCwm86V~G/^loFdUXhhouUxbYV@yFoy.PZnw?MU*V^;M[6Wh@AU&Ha-Kl&a}>glJevB]o>Vj:[p@ctBdvC_qAZk>YjDZ1@V+AV.Pe4_}YqUpp}+^z A9##zB;95o|C0;0/{*mz(P]JE` 5@]]|4d:$>(?&<2':&8 +='$;!6*F%%@Ic@$<"9,BNe7vWnJ7S7UsR[x5e@Hb"Ys5Uo0Ic%=V3NSo'k:MpxNJc#9(C +5/3MWp#uJ_Nc#G\i:_o$|2@<))M>WOkVWlldy3 />%DW=20B(1C/"4!)=&?N8,|bmC}_uT'-S]&R^~>DYVbAPiy:V\sEUn@YoBd{PNd8Ic5`{KXrCJc5Mg8[uEmWfKfOYuDb}RKe=QkCNfAG`:OjQ6@S6L_=J]9PgWmHUoMRjHWoNSjHNhAYtJUsFD`6Ki9XtIUqG\wP[qLSdAh{aj\bDIO8chS~mabOqu[[`BgkM^cHLV:XfG]mIpWkzK`p>zYqP_tBZn=Wl>Xn>d~LRm;\wF[uEa|LavI`wLf}Qb{P[qEbyKnVZp?axIZtESn>ToCUpCJe;]xN[vLQi>C]1Me8Vp7Sk4bzEatE\rD5L"NiZ/C`1Ol5Ok/Vm7YmS0GW4L^9CT0FW4LZ:ER4S[CAG2MVADE7JK?B>4\TL]OMK::G58mXefQXgSQeRKm\Tr^Xxmo\q~bsdzoNvnJig`DW{d]a(QT4gsw.Pk&p(TtB_Zt x)az pkG.x$.`y 9oih)CR<gz zw 6s +k +H_|e 8qA\b} n,NZseEn~y"D,_fG^fkxz(Y>>xCqmv"r{/NXv*lC?\Dc&+H*F#=63++%7",>)2H.#= /H)-F(/G*0H),B !8 XrA] @\"Gd&If&`|;Lg(>W@Y Tn5Rk1Me'vISs"_,9VPg>R$9avB,D[t&f}1DYOd&j@g{6n4r-[3y"Eb?q"2w(FMJTtH`CX"2$:8J-&5 /"13A(2?#LZ6HR)-9#0*5juDTf"[l(t7\[N]mS@mJr8=T PKeMh3OuEc5RB^e8?[^| Hg`6z2[[|-Df[|+o70PNb+>\NpDh'+I+E( & =!@jh5U-%D5S!9X!<] \;V|5g2W+M>^4=[4:W0UuL ?;'G'G+JsY"?(B ;@^-dJ:]!tZ@aDe#i=^Gh Wy3hBCh!/P!@tD^0R +Cb!Cb(Ko3-RGl-Vz=A &L &G!.K)A^:Ba6(H:[0?`4Cd5(J@-M%<[6.O.(J)IhJJeOF`PYsjLf]@WO5LGPREYYVjlLad0DL3EP;MX:MTDWXUjhLe^Oj]H_Q\nc[l_breOfUbwhNdSYm\PbQUhTgzg\q_^xdD]KUl\OfVG`LTmW]v^^zdKiYD_W9ST0HO5LU.FK>WYrWXXukymwp{Y{l=\W_ROii9OUAZ^QlhZypf~Hg`]~wUunEc]lXxqFf_@^XFf`LlhWtrGdcFbdJfkFcdOmjKkd^uWyl]ol}^nYzfq}ZyghvWwg`w\{uUtmYyoqUviyxCbbB]fWrhIfuA^nEcrIfrXv~TtwMnpQoqUvv_~~]yygczpoV|uR{yU|{fwZxdMpwGisVvNothopsMmtKjoirvsefc^f`T{T|bbfhhg_[}oyrovgjqtiZenknGiaQl:RwH`wdNgLZOWagwzu|]rCX?VI`A]EbIeHdB[GaD^@\PnLmEgGiIhRq;W?Z?[KiIkHjNk_~Rsa^`xlhVw]~MkDdHfRpWuUuMmFf}OpGkyInvRwyQvvV{yY|xTysHmfMoiNqkVzn^r\o[~mbsZ|nexfwarc{aSv@bv@c|IjQs^Y}KmJmQt\__Yy[}QxTxbaPs|DjsYOs|c]~Z|X|TzU{ZZ`bYzWxVy~\\fhhghHdmZuZvYua}Olvbtf`_dqoqqp~~yun}q~[uqJdbKhfFd`MkdRnclycyp]ujn{tG`TNjUś~_xaKfOVuZjnTv[MpXCgP\}lPo`QlaUndLf]_ul[wdfkUpPkfytmjeg_bUvcGfXB_[C`\PkfId_q\to[unVqd^zgkqgkfenfqdaM`HmRqm]_|MgVgT^zGWr=b}DpTlTi\ffDeN\~fjptx\~]TzV^bWy[LmSKmT@cCFg?xhqjVsaeZ[{ZKjJ\{YifkhTtP``deoqhhrw`tjdsq^nnL[_O_fOanftmy_lcso{`hMSpJOlIQmXexXipRgc_ssh{L^m9IYF[eWot>[YIhbC_[Phgdz}G[dPhin~_vmTjdO`cEWaL`lOghb}uUudTucKl\?_Q^l\}g_mlvgmddvsf_plZn`Pb\]pnZrjblcilqmtbdchb~h@]I+H4MjS_{eXs[LdGTrVPoR3T:MnV:[CSHBUKK[R/"!3(+=2:MA>QF;SG8TF;WJ1LC+EA,-.1$7@,228!535-0)+04&+/3)?B#<;5485+@?,BA7ON20 80DZN3H:-A3%7'ATA=O=8G:=M;CT?6J/_yUobugk\YyN\wP`zY2K0^waWmZH_L4H8-C73J=4M?#<*'A+9R<>U@,E0FaDD`?WqL]wRb|XbZdZsjaVl[iWgVvbiYc{Qm_~odR[yId~Q\tFd{Li}NrXu\dzKpXmUb{OgSc}PRjCIb:`{R]vNKeB8P42J41H5+C5/C70E9"7-!43"4411)((%(&'')(($,)2/ 32,)0+0(0$3O^iMmtVbhIekPZdH`nPs_dvKrUgwGZk8Tg5DY*Yk@Pg9^tG_wGMh9D^/Wq@XrCYsDVk>`wHTpCH^0[pBf|K^sBfORl>Lh;Je8Hc6Fa5Rm@0KP&J]3E\,F^/IZ0H[3HW0iCR+=M$Wj=`xIF\//E2CBR.drOn}X>K(@N.QZ@;B-OSA]_RJKCQQIZSONCBZKLgYXqaddOWR>@K;-P@,[K=bN@aM?dPGnZQRF8tgU{p^sbUIpfXum[^[;jy;dl!ck&ju~"x;Wgsw'Jdi8Kj2Krf|%m:F[ЂzA^v&Tk ^s4:P_u0_t.f|2`v$?>q%z*@Sp*hzs'w&Wl B5pu{tZt}Z7J(3C'+ +4@$8D(>H,cw?@aEhW|.Os+,N8Y2Q~OpB\(GYx:Qs4Gk+Rw:|l+Q#[{TNkGd[?a4(IAb6Gg=LlQY3EO-@I4FM:MP7PP?VT>URCXRPd[Vg^QbYM^TSh\[ocMbUVgZQcSXjYctcUjXazgZr_SjXQhWRiV\r^ayc_xbepD^U+DF4KV1GU;OYRfl8PR:QSB\YGbXQmZflQlZPie0HJ@UWMfg;UY/LO/MRA_h9Yc6Tb1LZ(ES=[e@_cB``B`ZGf[[wjVt`t{\~dIjZc~SnsAYb9OZ;N_3GX9M_,BT)@Q8P]4LS?ZWLk]XxfdsSuf5VOFdcLjlIfdTqh`nSsb_ut5MMPilJfjNjnLfn7QYB]bB^aEb`Khe@bS7YKOqbiyOr_[kV~f`qKj`:VSLciG_fMhh^{wSsnKjfOlg6TOdfSsmZzt8WQFedRqqRppPlm:WV4RRDaaJheKkfBc]^vYyo?`UYzmXwj\|lbrRtdQqgQoiZytZvs\zuawi~Wznm]|{Mlt6RbMh~Fby@\rMi~Li{IfqPnsJjnLkoUrxOmsGdgXwwp]|Vzw]RvzVz}fpihRt|j^ml^}[vTow^{}vifabefnY|QtRvWzadoj`fetfmiWzvad^Px|U{eÞeejkLdvlr¤üیtZl>TFa?[=YE_EaYUIb`Phbny}z~TjYuk|`}^qz{|}e}ggjsyXx`XjHjZKj]SqgfzSlgH_YVme_yf`ydXrVc^^}[`__ebkXzlFh_GddE_aH_aCYWNa`XjjNc_iu^yhPnYNkS]|_mjfZ`NqYcsYz`pVjP_yBmPϒy[_lSdYDbCXyYdaVyOf`cajkV}WKqK9]1LE&@= :9#;?26/83<"6<6< 8;::2LKG``AYZ6KO26$;='@?)B?(B?.E@1GA0C9*>2 +dwa;L39M0BV:ZpTXoRIaBvmto^kWQnCQmFd}^sn6M5F\HH`JBZD0D4'>/*?3/G8G`KPhTNfPC]E/G/SlPQlIb|Wj^_yUj\UpHVwNf[jZlZn]gUhVl\n[cOVrBhTfUQj?lVy`jSjPpWv^oX^uIe~UayQQiCMhALfANgD@Y;2J2+B1&_@?a@FhBDi\nKYpIXmHSjDOh@TnC^{MVsAQj>iT`zKVpA_|Kb}OYtF^yLYsF\vILeU8AY;OhATmFBZ2@Y0Lf=Kd;@Z.XsC`{Dc~GYt@UnASkE2J+Ma>QeBI]9:N)H]8UfBHZ4J`9Lc;Kb;SiB`wMMc;Rg=ZsL^sMYoIRg?avO\rLTlE^wPRhA]sKTjCUlIncdyU]rNVlFOgGd|YSkMKeCMgCZtMPjDUoJMfE;U3XpNxWkKrbmux]lsXhmSgmT]eN]dMQXAlu\ZjM{ihtSMY:N\<\kIl~VduI^m@gwHfuD8JL^0_rFkTRg9Pg9MhMk2Kj2@]&Zw@Rn,Qp&:X KiC^Kj"Rr:YDU{GCg3;[yA`:[Pp9X ?Ts1VIsC:8.GK8BsX[E-HGSYmP*iSWZQK jvEoL^aLfػ# (:2F*J[2@N,AN+@N)EU.N]6@O)M_3@S(>Q$AR0AR31A%+<BR-AR,?R--AEZ2G\78I%:J%Wf>[e>8DDO)U^ASX@RUDBA7VVPGCAOFF;23MABO@?C62I:1^Q?5+<2 k^4|kPM;0=*'H:4^RK]RIZLF6,(1( ]WCliErt5a7pxV!(:w1;^p +f|"ZoUn x/Yr g{IC^B\!<{7j_zMg-F8WC/lu--Ak FA'%?%%='3!4 "6!*)%; 5F_<2I%7N%;R#]tEOg2kQ/DAW(Ld/+D '@6O7P=U!:ROi%Pi To"h1DGcPM*>Ug p6\r'[r'Ui'cv7cy6n?o?Qio4z9k}']oZlXiQ\BVlSDJ_'b}5CY1D7H\jA4B BN.8E%-;:H>PwMTk*BUIY$BS:O=PTf,Rc#fz)>F^l"K^Mx7Gy2OCT[}:u$=h(EXuYxEY64P)Fb5eKGg)AaWz6|]ZxD5C_:-H*,F+8)C'0K-2O)*JjT;^Eg*Cc/9T'.HUt=_oO-MfZ)~lzj7T(Fe=JiCWvQEaBC]ENfXF\U:MMH\]6HN/BI3DP0BO9LUGY_ATWFa`0GGZ^S>[R\YPjhLgcRieOmgMnkMnnTsuSptQioQjnFc_Yzmm|fpRycJk\NoeMl_Nr^VzfTw_Vy\dde`wp}tuVs[bhlrTyUY\HkJFhLKmV/P:7WD0N=.N;<\E\BJiN=\A)G09VA>ZK8PD>VFAWF3G8@QIGWT4C?M^WYpcBYLC[M4L@+?9.E?6/ ;5%C=(@@:;'@D.FJ'>B1EM!5=":@15-GI5PQ&DA!=;)>B117PP'A=+EAZ<0H.1H43K= 4*#0-$2- 0-$$*-*/ "$,+21$85$94& ,%( %;0'(@18P=9V=7W;(I+@dDIlMAeA:\3Kl>Hh;Hd6a{QMjAUmEQk@XrDSp?^zJiXZtK`uN[qKYsF[uHgWMf?9M(QeD;N1RoCJh9ToCLf9iTRkF\9AV64M/=T91I-:Q4@X:H_<7O+C\4SmBRn;\xE^{CVq?Rl>7O(\sQJb@E]8OhEkcK_E?T8:K30D'3F)>Q1J]>J]?NbDQdGDV7WiGSc@N_;FY5AU0=R-I_:Nd>BX1>T-Je:azPYqGXoE`tK\pHYpF[sIVjDbvQexRaxUf{WWpLQiBYrKYsMZtPSlJLfCKe>UpHSmFXsOKbBBZ9YqOYoJarRmz`rvaTWF^aNafPFN8ajTlv_{mJX9\pSj{["3;L(Qc>g}Uk}PcuWuUqOoMqSi|NnVG_/>V(Ga2Sl@b~SXtJToGZuM^yPXrDgQ_uF[sAf~Jf}K7R!Nh8Sq=oZ]yH[wGRo>hUGa5ToBUk>cyI_wBnMZq?b{J[vH_|LpXNj6Yt?]|EWt>Rq8Ut7Jj(Xr%=\gjXRo7STw=Pt<>b)a5ibUMIr6Rmy(FU_@PF7SFA]PAN/WZlL9\hcm'XX% *5!.>'6I.AT3?S1>R0 7]t4Nb%GT#?N+>L)HU3esPO_:YhBXjBdtNAQ-.>#(6 1-@!9L(;O,->!RhExIY5:I$Sb:M[/kxMboCLW/*2=B&@@3STJROLI@?C:<=1.-#@8&3-LPńL^pd>0$ME:XOH{tib[O<7!YV3s,,A2>8y/AZ?z%Na\o i{'`w!i$NdALw7:S +Rm&UsQo:U 4N/J Yx#:q(ii~x`yxvIiA[|<2%> 4 '%8%2+&8$6M3U#sRPi.Kd+2LKf'~QRm">ZrC_y%c|bvBgVn+m,TgVi,^n4Sd'I`4R)"CmS;^2Uya$@RW=NZ(:H>PZ@SY9OQ5PN9ON/BBJYYHWVR``BSQn~}BROJ[VVg_Wh__pe[ma]o`NcS_wd^uceziWl[QfTTiVOdQSkTYp\H^SVil$5A>O\5IQ2GL;PO4IG:PP8LKFYXUjf`usqUhi(=?@W\[Vdrqx]~d=_M>\Y;V_+GQ,DP5JY+@O0ET5J]9M`5KYG^hE]a=WTQof]|lQr`QreB`Y7VRGeb>ZZUolC^ZKgaUsn@]^8OU9PY7TZFah?Za9TZSorD`d^y{ZwtLicHeaIg_LkbXvlStgIj]{]~w:UV?V^Z\IdiMhr=YbA\e?ZcIgkRpoCc_UutUvrbzb{_xStmWxqRrle{ayBb^LjjGfhOknToqOmkUqnNmgUzk\ssTrw:ThWw9Rq0Hh1KeB^rGds@^kMivNixSp|d_ImpPswKpvUw~IkuUwMozSu^bUw~\aeTxit|{yTv}mhZ|mb]^cdbma|l`lcbX~GjyQqTscKfo=^fSy]OkuŃxrډ䜹,H`5Qe^{ca|:Tn@[zE`QmGdB`LiMkJfC^@ZG_auqhiRr}Pp}_eVzY~Pt|Su{bxvrhdTr{FfkNkpVsvFdfGeeIgjJkkFif_|PtkWzj_o]npp|fsjvgrlrckmwWz^fpaoU{jX~uLpoMotKouMpwVw|^[}XyFgpXwKi}UuRsJjzNn}RrDetPuKp}DdtJjxMl{RoOoKkWxIjxIjsSuzUvwY{|W}|SwyFjlLmrHelIgiKjhEfaSrlYyxSpsOnoNmq^}iJhe[xwl^{}ZrrWtvOkof}ef`|{Zuw[tsv{m\|KiVmx`bHx^zcqxv~pvjs^ugd|n_xjfq_|mFeV:WI'B6F]SVlclvt}\qbhj|ysrooRoZFeYTrlMje>VX=U[=V]9RT'?;8QJI`S\vZ}zxqufybx[^wOxJh5a,Qia~Ci0y@t;x@Tild?_aiFOp9[PJnCHnAk`dR~amCzKdZtPWxD\K`Pi\i]bYhcur|~vzrvpunkdletlqkinf}vdzujxo~fw~{fq^lvFX]L``F\YG\]J\dBSbBRh?QbH\kBZd,GH9)@9)@:BXT+B;3K?DZMH[Q8HC8HG;HIBTPLbUb{m:(<;%A=(B?'BA*DE%@@+DI'?C59!4:.3+/(,+DI$>@)DE ;SlNYrSUnMVoOXqPXoLczTugfWf}QgQ{dt\u^bwMc{Kc|Lb}Ka|OcUgYQf?Of=WpBa{LhR]yIc~QTnFPlDMiD7P0G_>C\>D^A9Q91G6,C51&&&%&#"+."% !(;;6LJ'>96-3*';30'$;.*A1.F0GaI:X<>]>3V56Y8*N+?a@1W.6Z.JkQiCRm?\wJ[uGMfZnOKZ@CU8NcATnFPh?So@axL\sGTnA[tJUqEHc:PkCIb.A%:N3NbFG\@3K.9P3?W6?W5C[94K*'?6L*G`8UnE^zK>\)E]:Ke=TiK8L1?R<2C/8J38K0DX\wB]xBiMhMc~HNi8Rm:Hd0Da)`~>Vv,k<4G:"TQ$<Wu)Tt+mDPsf5Y6N3[On7TBJB/(({S8H.@IGuK-L8NELcJ\YfSN<,YQ=45:;4='4D(4F !3 3DK^/Kb%^ς]k-IW5HW7AO4YiM%(;;N/7J-(@ %<6K&8J%YlCQa8;L:J[WbdfH:=&=;)YTIoe^TN9dUAI~*KaYeZVQ:lh]VS@C>"ǎpI\a 1hjd{=UN|#fw*@Qq4v91FRju-?TgUlOf&Hc%)FD`H`#+C ">ZwQo{%p-+MpVpRl-H*F 'C-GXt8)D96 wYn2Xi-.Xf%bCSGS&%22C:IGVhs8UHP+8ANmzDCOJT\h'q}?.7+-0-C/#;KcF7O)H]/jBXTj*uM,9S2H+3-DNg3Hb%Uo,oIVr0(FWt*f7nAi@8U yD^O+*[{1.#2AP[l*Wn%Qj2N0%I%0Q.ga+K#?_5Gf9Bb15,LlVmSeK_}GZwIcVJeE*F28QAAWO)=A;NX/AJ.AG4HKBWXCXX2FFDTTGVVIYZK[[QaaIYYO_^arkwVh^asgTeY[paPhWNeTPeTquQfS]r_]s^XnXZq[Qg\Nba(VaDYg>Tb-BQ8M_8N\G_eRjnHb_A[SDaUeuPo`atHg`PojSmq2JOZrvMgkYrv>W]=UaXSFa]KibQogQphDcb4QSBYb?Xd6P]5P^2KZ9P_=TaA[bSkom56?[^LhnIbjA[d9T_;Vc@^eYwwYwuTrsUsqUvjh~]~sJkcZ{uHicJidYxw>\_MjnD_gIeiMimOklVusTslW{nby]||]y(CY/Ie8Qq4Mo9Rr2Ki:Un>Zp?\nDbqC_pUqQrxbIntOt}Tz^LmvMoyek]p]\~c\~\yMkrximzzzPruead[}ekhre~ilimfcFjwMpTtj[xv^~tdTpRk{t}ZrXq|pvyljjloo|Oj|Uo;WvJgRoDaGeLi^^>_Ya|h=`YFieMpqMnrB_cA\_Jheaz^zFhcJidLmic`bOljPnlEba:UVbzwKfgIfgejc{e~a|kxzswvw~~zŨ~l`nbm]nkycyaoyhpjordoa~lSo`Xqebwo^wi]uhe~pYvg\tfgh{vwttsvsUtSHiQk~mgz9SQ7OQ?YYJe_p~{mgmd}p~qvgr^vZtPh<[lWUVJRlHQ^p7`NbUaVcg:dhXaWU|K_Mx[vSX]r_|FHj9NqAaVk`VN\V^Z_]b[a[_~X]}T}vskvnun{rkib~h^xg`zhjvatt_pvbtwbuul{Nd[I`V_vpWjkbuyGWeN`lJ^hPhl4LLLf\Wraq{'C69XT>Z]>W\D\^@YV2MHHfaNlbhx\jFjVLl\Ywm@_WOnc\{m_mYz_ZyU[{R]}Tphkl[|^Zz_EfM>bGAeNOr_FgW/O@/O@/MC6UL3MF7TI3PB0OB=ZHEbR&A3%@42ND7PJ;TP)>;*@?%>:)B=J`Y'BC%>C*BF#;?-FJ6: 7<27,BGMd>QgCG];:S2JbANiBXuFHd6Qm?Oi;UoD;S*Lb;Oe@L^?@O3QaDQeCQgA?Y/d{PYnEjTkWRlDQmELfALc@G\=BX9FZ=3G,>R62F*0D)2F*9Q54K/2K.9R31I(5O/9R2D];%?,H!=Y.:W(Ut?Hf4So>WrFUmFJd=PfC4I*!55H3GZGYlWH\B.A&DW;CW:AT9CW<Oh:Rj:bzI[qB\rCd{LXmA]pGTkEF]9UkFk\e~YH`S5VnKZnOMc=czRc~O\vF]tDNd6[r@^t?h~FpK^u?^wBc}K^zJhTo]pb]yRUpOWoQTmPniSlHPiENgIH#NK'ssDqq;C;Ad=y"2>GwJPO:fdYlv}슡C*P|2o*j11f'T>Q;Q@Y!.H +Wp/Rf1$JabzKr#6v .BPiMg #?9$@/%. ++?sHQ_mv4P]OXOX,7C*;+=7I&MW#V`44ADS(CS),75@_i2R["=E\f8AGHNQX@Iw~Tk-\x&Ww%wE}OkC(D  3-) 4$25L2-F("5'8I],^Ul#pj8Rl#lA5O":-E'AZUq+*ETq+Ro(0Kc}3MkWx-Lk(1N3M d}*B;Sj{,pv7]pUjMjh/5KDVCSKY"Zj'u7fz'YlObE}=@6#--0|L-"6G2A!*5DQi:Jd0Rn.lT Uk&Vm'Tlu3=78{2q-RmHaN?he})QWw l%|05\v9U&.J">Z5Gd;(FGe03Q)G*H7W_I(H;Cb2+LmS:U&,G;X37V,#C#@9T*-D5K*7O.(A#":'>Fc/|];a-R Ms+Sz$Mt'c:.N *I\}Wf4JWMeqCYg4JX5J[9O\I`fTmoYqq:SPD]Wl}SscbsPocFd]RqmB[_:OY2ISAZbB[e9P]AWf:Qb(ANB[eSnrHddKhdZypEe]3PLRnjRojD`\Gd`Da^Fd_OnjWvyLil9T[E]i0KZ3M^7N`G]m@Xd>W^QjmXrpA^ZEabIeh9UWG_e7O\>Yg9V_YvxB`]Eb_Spl\|o[{ooLndTunFec>[^:U[D_i@Ze>YaToxOkoKhhIgeZzsZ{tl_}">P1Je4Kk9Qs2Ik0Hh1Ji>Xv4OjE`yA\qGdu\zbUvKozJpzQwDhpnCdkNs}bqowv`IivQoyplLjoWwyrm{rt{~v{`~~swdrr[vlcvcynhmQu{9^iRsnoah^fNhwGboZt[uNjuQmzRnzXugb}Xs^|Op~Uuwxvq{ut}QoIi~Ee~Ba{Gg|Sq`bY}xcUx{OpyLlyZzahhqcdifxdcRqlOmgl~qzywfyU|iPugSwhSxjduar_p`sLs_NtbW{jY|h_malcqgu\o]r]vqwf}UyleyXyqY|yTvu_TsuGhlUv}Mox?_kHiwJl}RvVwGhyDevJjxUvJltQqwFhjYzs[|x[}}flhVwoeY}tPrlMpj]~Rtu\z}cQnkhnVpnJgeZyt_~xjlt|jm|xvr|{{vnftlnde\kbfWk]xivcwbnsjYw\Wwc[xgYteYrfPi]d{nkujrv~|uxmojl_}^mi_}]hdg_g`RtR@`J:XKBaSB_V1NI_|rztwtsmgj]m[[vEeMfMTq6_~:yOMcR`_ZULSeYUJ[UY\RBQlgv`Z}R\YSvIn[wZyiMEe1Cc5<]3Ei=TyMojgaSzOGlF=_52,DA)B?&?;+DA4LE:OG7KGAST*CC 881IL2KO&>A.3*.06'@C.CH)<@,+.)%91CXM2^t]}mxbOf9oU{ep]sbm\c~Rb}Sn^jXcRugsdpbUqHOjAXtKOj?Lf;Ia9`yQRkDQkFPmFNjCSsOQnLWrQUoOZsQToLMfFTmPRiJOgFVoM_wUlczoxfvbu[~cuXpVu\nTrYmXu^h~OkSfUb}Ul]p^kS\wKa{LSm=a{K^xKQjDLfCMeGVoQHaB?Y<4K22J34L8'<-2'* (8,-<1FXL + 1'--,C0.fc=X=452P5MjKF`BV8I]B5I.?T7@U76I+>Q2CV8K`@=T5Ha>UoHXwLEd87W(9U$\xHdPQk@Mf=UjF2E$CV6I^:`xQ]uK\rLZoGSf=TjCLd=G_AT9HY@AV=:N59M4:N5zfbO5P$>Y0Ld<9R+La?cxYJ^C0C.6I6?Rg|Tl[zidzSThD?O19F/JVF?E>TYTMWNJRIUbQcp_IWC2C,FZ>'<2i|ZFZ7_vK[xFWsAVo?^uH`yJYp=jLoO]uAWp@VqAnZPoDEd:SrJ]vVJeF`xZQjJOhIKeC3L&SnC\vIe~NZuCVp@Sm=WqAPj:Ul>[sEcxNZjFbpPM\>N[>SaBZiGRb>JZ9ZkKI[uG90Q9;l0ATmCGUe@JX')!^iW-LSnNo5ElIM*5`r3tr򓬵hgx4'4>O&!-D?V+Xp,g|7UeFڐےNgg%Y`Bdq#IuU}1]g4>^b1AB,<>0NR-Ɍሄ-3~ *+H6If Tn2ID[7%=wEi6j54b{/[v&Up y>~FD\7J .D 2J6N,!5 h~@Tlc[oRh ~+ct UgAZs;RCZ &= < +8S$*D$.0, raK]:8I"#34A?HMS!S^*[g8JT,4A"34E!DR.CN'9DT_;=I(9H$XiD&4$1KXP-7#*ÕnsC\d6T\.PW+D_x@Ih!?<[]xQk;-3,0 %8$#6*;& + 5I UWo_}Dg1e6Vo4BZ(0JnIk@Mif9~N!Ue53S +8/L0M.JQld~GKP뀐=?D=f&51DCU>J;Icv){<[n!Zq#j-,D*BFl[oS2>m17I.;""3 .;'-F\&2LHb Q)Sq1Pr,Mn#}E:Ri!DQfUk bVOG\L`&K`A=z.DKp`{'=V^ +}7v*IAo({b}:.LJf8Jg?Hc<3Q?\(Nk8@],1NGf.Il5VxETwE]KgL^=-J"<;W1(F8W*"=1K%E[;*>#":3K25L/.E #? +[};In"\71V.S c5pC=]Om/D`#Sr/EcLl"9fEkA^4gjEpLa~acM?b2:65R4-J-%@(4P5>\=GfA6U(]|R*J=\)\|AQp7UuA_~NVvCDh0bPeWkc$<)%:4':?/BJ6IO;JMFXZBUW;NO;MMEWWJYY^lmGWVKZY]ohyL]RUf\SdZ[lbTfZ]obVhY[m\SiUYoZ_v^^v\jkg~o.C> 73C[RZuc\y^ZxVlhxshjNdYJ`ZQdcSffLa`Rhk6NX(?M%[`6TVGeaevQr_-MB&DI-IV7O]8O]=Sb>Sd3IY:O\C[aZsvZnp;RPC]XB]T>[MWwgPoa<]QHh^Qlm9NZ3IV>WbC\fC[fBUi1G^1EY3KX7QWF`aZysPofbwHhbc{NmfQpj^}xRpm/MK@^^9X[Gdh8SZ;Ta2L^=Th;Od@VfYe`ZKk^Jn]\rcxPuiCi^:]RIj\bs`rW}h`phzW~j]rVzm\pl~fvl{\}mZq\wX{t_}ufX|z]~HirKlzDew?bvJj~Hi|Jk{BboDelKnlujtix`hkrhpqWwmRrmLolOllOkiHfaZ|rW|oc{]xAd`@bc9Z`Bcf/BB9KN4CC#12-;>4BD1AE.CC/EB.JF1HE';=1JH-FH/FL#8=,159%>D'>E.AI.0%! ,=-2D0vŤ~drtzd`wKn|kudn^gXdTq`a}NnYu_~i}ia{OZuJ[vIfVZtH]yLWsC_yKb}P]xNTpHKjEOpFVtOQkH_yU\vR[vQUpNWpQbyUayUb{Wma~soalyVu[{_{]uZkNkPrYlUr^u^kTgSe~Uh[b~VfS`zJd{PYqB\uDg~Ne{NZrKUmNf}a5O1@Y:8R51J./H00F20!+2C4H[IQcP);(5F7!7$!7">T=\tXP3CT9L^CJaBMe@Fa9:'F(H:Z)Jl99W'A^.3M">X0OhA@X5XpJGc:OkB[rKUiBQcW;A[;1L%:V+)@]2YsI6R(SlDD]4La@BW87J07K2BU?EXC/F/3J0*<#;S9F]A6J-0D(OcGAS7OaF_uH[qCVkBn][qJSfIXgKQbB_pPO`AQeE=T3TmIWsKOiDNfEVnJawR_tN]pL@Q.`yRXmHFY:P^GFS@gqcSYR]b[FRH[hWes_[iSFW?;M4NbG_tU^rPQdABU2hVfQWs>VsCd~S]yJqYiLiM_vEQk<\yM_}RSrMNkFTqOFcDJcFE^>Gb>LdBVpIWrIgVeSOi:Pj=TnAIc8XrGOiH^9Ok'ph]}GfRn Qo <\Jm 5X+LN(G@+E~1Xq b~ Yw~aXs<9/dC*m88d<]lvDVIB3lOXB#_ndC"4 `%#!9,F&3L,"96-?-(;#-:T)Kf'Ί^s&;Qiw\mNL>q.T;5|6>}5DZ_5CF3S[*[e cGu<5~/Gc|B` Hb aq7Vn1*^w6CXF]!2K?ZVq/6Q9V +@[>X@SJa)Ke33N!6 rN`t3D^7EMa@S QZk 4s3+FJaSf.Pc:#8)@"8' &; # & )ASbEP_74C6@/7,;/::D#=K(7G"AQ)6B}5@;n$r*w4GB^`{.Zx'^~K})Vh"d y:o4n-z.3Fo-]B\xI0GBI&D>Y+$>Li@hYSsALm5waRq@UuE1M"'C,I2QmM7T2O4!&F\|H$A[1;T33&;#0D,/G&9T,7R#(9U@8S:5S2<;\0Cc7;[,aN5R$#?7T)MiQO8KK:JKESUP_]p};JNTabYhdN^VQbYWh^atk\mcPaV]maSeX\r^TjVOePXmYZp[@VAi~ld|kF^Qc}k^zdb~`\|WgaEe?VuRtvmtH^UZmkexw>RRRhm4KW&Qa@Ue.DT9OZB\aMgjPij@YZ2ONw]}lPn^QlbB_XWqnPio(@K5LY>T_G^jVlz3HV.AQ5FZ2DV@Uc0IMIfcVukZxnA^WMkeXxt;YXVtuOnmQpoEcd7U\.IV#=P&=S0G]4Na\WMjkGdlD_lC\lLer?ZeA[cH`gHegOmmWwvCbc/JY*C^2Eh/Dg2Gi5Jj9Qq7Ro>Yv6Rm@\v>Xq\WEd_NneUxh9_JOv`cvFh[CeXKl[X{j^o[~n]pTzfduev]kVyiY{r]}SuzIkwDix@cvRqJiyEfoLnmt~wk|SzdMs^VyfcrTucPocJhaA`[FeeJgh;52 96'@=85%?;/HD9TP1LH85$<<(<@&9=#07'48(7:0AD1EG#78+?@&<:#67(<>*=A&, 3:,1 5;#8>-/,- $/>*bsWƦwz^|]sllUq_zjnp\q_fRiWlYp\ubnov[kTa~MfT`}Qi\UpC]xJ`{Nb}O_|PUqHdWNlEQpKYtSNgGRjJUmLRhHSkH\uRa{XXoNf|Ztgsdq`{hqZ~bx]mUu_iTkXl\r^tajWc|Ra{QJf;Ha:XmIPg@WkBZoDavKWmBSn@WpIXrQOhK/H(AZ=5L0,C*ThO2G.FZBVkPG[@*>#&>##9TkNSlKPiDShC9R,D_9OiFToMD_ANjIJeCJfBY{N:Z1QqFKoCTxJwip_q^B_9F`<9P31D-0A03D23D2=M:@S=8J3GY@@R9J_=B\9VqNHdAh\aVaSGh50:U&riT)B.,E1/H46!!>(ik7T53Q-i^QlI. AY:)8K75G5/E1*?/ 5$4J65L1FZ>AT7GZ<@S7.B&=Q5AY9?W6:P,E[4Nd=\tFMg9Pm;fTbUEf;Cb?JjL5R3JiCC^:A[4XqK\wOTnCRjBG]5SgAJ]:AU39M)&:2"93uNBKL*RjKf *@6K0 0+( #7$8.!1 JZ3DS-( &/$1:H/@O4):@N(4C :I#BP-4>&#3 `uDPMZDOmjq!_e!*/5;29OW/i+B\ UqDyID`=VIhLj3!80%-@)'9@O+2Dj^;Z:X?[k>h4c.Qn\x%zEq@{LrFF`@\JgRq!\9V +݅hV8*{(|3l,F]t!F]H\ 1"S?gxZmfx'ftSh ]QNzF?_(DZw7">R9Y`*댊RCcx@m4^z(To#l6_v#ID=]s^)1O(E+F+G;XFd'`~=Vu6Ts5̍WxD8Y#u]iR1M&?-F%2H+7J*,E@\1+GLh11RY:&J 8[Oq0Yg3St*Ff#]|8Zl;g7PwWf6C9YLn62T @c)`F7Y) +-K-=\<;$;%5PX\:TZD_`ZwpGgVo|4OG9UPJfdSkr+CO\b2N[.G],C\7Mb3M]7R_8R^>Xd3LZ,BP+CPWoyNjo:WZB`b5PX2MW?YaOhk>XXD_[OigSljXtpPpiOqgYyqJhb7SWA]fEamFanD`lFalD_g>W]HehTssy@^d(BV1Fh*;e1En1Dh2Fl3Kp7St;UtE_}:Uo=Wr>XsIc~OiPhVpFdxLkHi|DcsOo{>_kDetKkxWwSrYw^~No{Xx]w^yVp}mlupee}iytg\y}wqppvnpckjmopbzrwy|wwm|s]|Xxzcg_}ygtsRsqaucBdkAajbhnle|hosnqktZ|jWxgau^r^tg~d{rr~yYyvPqoUyuW{uc|mbrgu]}jbqfv<_NAbSXyegnefffcgjrakepcsX}kNteAf]`Hgd?a\PumNuiPwiZre}RrjFfaB`Y[|oWyjm|asi|Jk\DfXQsfIl_Np`SueUzcWcKtUailvSv_SveRtkOrpVy~Hku=`mIhsHioUxxge|W|jhvergq^n\~ndvVuhOoePohOnjDdaFcaDbc?[^DbcC``TupJnhJil5TZ3Q\9WaB`fDacXur_|wewl}xrnz{xnykrbk[l\`SdXrfi]dYmg[ZTyW_a_a`ccf[}]fegbrkniPuONrWUw]ZybTt]lrdi]}b^~`VsV`~bmpciZv_pv]a^_c`b^_Y_U^Qkbea]]X}[v|Tt]]}f`fhdcTcJ|WgX|L]\wKwJj?kD|WkH[;bCtReDtVfFgGnMoLnJmFsK}PXx,YtEh*AC-FE"::/DD4IJ';?*;?+9A!03-??1ED7KJ'=>$:7#75*?A/CE(<<+-"57,@B*>=)%%8-0#WfSĤ~q}_cupx\qYpxek\p^iYhUmZmWjUo]lirXrWlUr]WsEVsGWsKZuId}SYtG\wJZuJUqHWsLMiCHa@D\R47L-7Q0.F'TlKRjGUmFMf>YrNXrQC]>C^@\>-G(2 'HvkRoCLf;2M OlGb>V9$<#7O5XOgOM/MY8,0xs'7<<525[7=][T4dCcIf,oWjJ^D;iw3cm$J[J`u\ZM8_18gzZ`".O_5(& ,Ee$i!'%: 89O: 6%2-I+6eAv?s1Gk}0LLu3[m#H]Nf#Ofc7F;JKZRf4D8B2]l2O|Eu4|1{'x!Zn?k|7yMn q+_y$[q-[*@5K$7N"Rj7Vm7-D 9P#8 5-H1F"6 +2K[2WgE.@9KEV':?P-@cx0Vop3C^ +t@UOf'F`,3I!-D - ')=pZ1A4C# +&2AHT7(508 ."0+;!:I#[fB:F*ES5"-' +HX.Pd.HT q~(-3-Wdp(u~IBNco=TkvFj2FIG{=j-s=qB[w+Zv+[w,l><\Ba :YEc'A]Kf_|)c1e7,G>ZSo#EcxF9Y[x(j><\:YFd",F&>"<Sn!@YYq_v Olt4YmG}#GIB0r)\s I_8LrTnTZsUkcOjF:U3RkLRlOQfPGYHQbUotQfUI]RUkdKba/3$;D0EU*=R$9J-CR7O\IajE^b`~zg|A^V5NO8MTMcf]wqD`UB^QTp^|qLbbGZh3E[0BZ6IZE\fOfk>T\:QXD]aSnj`}pm}`{t'BA;WZ_y1HU;P]H\eGYd/AM0AT3C[8H_8G_>SaG^fKei:YW-KIKhfJfi7RW7QX@Y^A\`NhkJfj>[^+FP7F'@P-FT6R[;V_?[a>Z`(AO+DW.C\&:Q4K^3JY'@HJdc6PQ?WVh~sd~xE]YD_[UrnSrlY{sfFdc=X]:U_FbmEcm@^j]{AoPhe@kg:No Vb{`vqfEdE=[B:X?@aFGiHSvX?_HEeOA`KD`M:WDHdQB^IFcNEfNKkS.O46S6)A?6QM,GA$B6%D1#B2$A5'D=*FB$A?:<97$>:%?:84!:811"86.,2424$7<"3:3AH0@F*;=.A?0DC&<:&=;&?:)B=4IH1FF0DB.B= 4,* &/C2}g^a_hfeu[lh~QVpC]xMe|RiTnZgQpYs\xbnsYx]xbhSgTWoE[uMTmFZpI_wNYrH^yKYuJ[uO\vS\wXRfKbwWSiGNdAqfrgphmhukynbmdaxVXpM\tPeWkWtbn\n\tcmY_sN_sNnZd|RSmCl\sbc|RLa;H]9Pc>Oc>G[5Qg@\sHF`88T/=X4Hc@E^=OhHI_CI^?@U6?U3EZ9TjDPfDZsQWoLJcDKdDUnMQkI>W:5M1.E,.H+.H(:W0A]3Mj=Pq@VrBa{K`zHfQp\h}X[nP>U9OgL0B.,=+4D82B56G87H74E2CSB8I5BU>IaEGaD2M37 ! .TsQHc@2K)6Q+OkCc}MVqE[wNRnFGb<_{R8V07R-0I+2K+9Q17O.H]??S9;N84H47J7*<.(91(94&621+%82!5/ 5&1!* 1H;1I<.:$;%,H17P755".!8%AZD;S:*B*A\AKfKOgQ/1%(5%#9%5L3=P5BU<0C,3D/1B-3E-3F/3G02J./G+9R09L,@U2E[6BX1DY2Lc6Vn>I]4HX5JZ=GU8SaBSeA^rRPcEFX;PaEL`DXlROhKQjKTlKNbAN_B{p`xZct_N\L;G:;F6mxhzok{^_sTfyW_sP]qI^uMShBHb:TlDOh>WmGJ`9ZsKSmD@[0]yJPl;Ie3Un?c}LQn=E`5Mg>E`9RjED^80N!@Z+1H;NrF_rAL^+GW7HT;KS?NVEQWFAI9NUFMWIKXB>I1LW=EM5V[DBE1DF6PQDKL?TUFEC6OK@WUD78ig/Dd{+R=] 7X #CGj't_}HiLlLpA2kYem}?M9Kj.4h|$\n!A|+FWmL~+f\H9w/NN8|+e:EZyF:nIg:D\1Wog`sOY)4grW񌓛Dp{(n=a%SaGMx&H]ѐMW Ȉ'9$w}1G.G\&/ 31,9.h;d;Jaas4du8Qa ?J CT +:P Ng(Qj)ydxAPc0Ui2aw5DbތĀq5 0 +"83 ?Q)meu4%3nE[/HzIr;@\Vs!g3Un(]x5Qj1(@+@ 6 2C9K'L37B+$/ 2;CJ62=&?I0>G0OS;5>M[ MZZght&Ar!Qc Llz<#2jzC5MD_?XB_ Tr1Qm+f;yQ=V+ 8M#) 4L9P?T\o3:N~HTRi~$ZPf#.?[j/o8o/p2k-:U/I+d|8sH@Us_}u,d {?n5l1ENSmEGG]K_}GB?y'.^OYum#A}2y1lz,BDb7T{Qf,[w$HA`8Wg-zDb8'E<5N* <'DiT3N$Ha;@[0\xGBb(aDPn4Ca(6VKn3'KpVMp1Hj0mXMg9*CmOKl%>]4Q2M-LVw<,-J%A,JsM~_"@ ,H,J<[?_Ee @c7Tx1Ko*}\g?@eiGBa1#%C 5R44)D-.I-UqMbUdX9W/>[/XuGUqF7Q.2K,9R7F^@Ib8Og3_{;qJLl)@^!Mi2So8uVpE+E b{ZKcEZoZ9MCARP=MLN^]JXVHWSN^WPbXUf[Vf[m~sL]PLaP^u`QiTLeOUoVJdJggghxyKdN=VAuu{vqf{qvmXlQ|vgw`xsRiQqts=WP+CDAX`2GU2HVWVE_Yi{[vgel`|jK]d5GW7H[[YTrpCac2MQ/GO7NW=U\>TYQhmf~~XwrE`dD]en^w}VosYsvXuwPoqPmu4Mb+@Z*>[,BZ=QfaxlzfvKeXf}ozMg_VqkDb]HgbNnhqNklLelQjtHdoB_gMltGem=Y^?YZ_ztdww[xx;Vc";R/De4Gn.Bm0Go1Ih4Jk>Us=Ur:RnAVsMbbV9]R@cUUwfaoRt\muitdqTvchxY}hfwVxiUxdVxaWy\|yogoci\lcztiipwDgSJndQurWzwUxqHkcX{uHlbQrfoapUu]hr@`M[zhhwriwgvJjZ@`QBaRbq>\M\|gaiFfMKlW*H=.MI1OPDcaOmgGe\:VGA[NB\Q'B4;UEC^KKkTkqdgPmT6S>/I87QDI`QLiQVqY_EccCbAQpP@^B>ZA>[AUqZVnXd}nfzx_pwitO[mS]u\f~albns^i}OYncnalTaso{`merhsZf{am_m{g}_yoayeVoVIcFVqRhcb~^LiP8S:)E@!>97340#=9'B:70!:2/()$-)54!58!48%5>.508#6;)=>%:;)?<6PM0JE/HB1GC+?9%<0EZKFZGOcI˫qufcaympSx]p[axNXpG`zLuckUoXu]lj|byat[d}KfQgRa{Pd}U^wObxQYoIPfC`wOd|Q`zMbzP]wRQjKD\?=T7AU6\rNg~Xqepelcaz[k`naf|XUkHc|[bzXYsNd~VlZfVjW[vIazQbwRl[`uN]qINe;czPp_gVTmEK`9Of@Ma>M`?DX7E\4Pf?PjABZ5KeBLgFPiJJdEF]@CZK`W:-F*>W;.F1/G1(?)%>&4O2Ea=RnCPn@Qn=XrAmQ`y^XlB#7% 2F.5G75F22B15H5=P;4J2)B+'=, "LfVmv3N:*H14!;#,F+PkLVrE9T/D^@_=0K,&A"5M-?W9DW:BW=>P;;K:7J4$6(#3+&51%40+&*$ "$6,!2(&+"0E>3K?-E7;+6Q? 9&6%&<0/&%';.G_LAXC7P9'E,B]G)@/3)"2)(;.)<)>P;7L10C.%9%,=,&7&(9'&8%&9&,C.8O42I,Q3@U:I^CVkMLa@VkKcyU_vRVjHUgIcx\lictf-;.XcXCN?xm{aZkKZnLh{VlXczLZqAgSYsFLg:Hc6Ia8D_6IeMi8Vs>Xm@Vo?Lh9UqHSoIG`>QhD4 nʇfx2iv/|o9G 4=(1 BE)JK8LL^zH=Y''B$>+FFa5Jg5*H6MU6PX:R]DaeA^\Ih^Tri@Z[4KSDXbShkIb`Mgc_ymOgV>WGiu!6122-43DO+;KCSa;NT6IN6KJBZU2MDOjc?[TSno,GS%>L4GW:NXASZDW]AS]1CP8LX=Q\SYAUZ@SYUoj]xne{AZZG`aQkhXsoTmi?]XOlmOjp,CR1G^/B]DUlK^nUnpcva|umzq{jr_yjjy]ysHfaSrmSsqBac/IMBZcH_jNiqEafDbf;X[5QPt~pvouhsoE`j":Q1Eh2En/Ej/He6KlBVw:Mp=Rr@Ut:Nn>St?WxB]wKf~DavTob~HdyId{eXuMjzRr_@_qJj_~Vs\xa|msjZx{\zq`~bqnugpZ{Qp~d`|^yXtjd^yIdrUq~Sn|PlxIdoMivMjrLmoVtuRqmMleYxnl{uizptnh{}noygt^~icplwRx^hvfuW{tEjjCddZ{rUuiStnNpkUuqPqlCfa=`ZEi_>bV^rcs]ejt`oPubX}lY}oGiYAcV?^TAbRFfVFeQPqXVw^ZzbNpYLmXPq^GhV8WFEePgofjafJiR7VCBaPCdK\~Z[SV{J\Q`][[QuUcn_o;]N2RHQrfhxnOudJo^Uyeyvzijtznu^j\}hUvbY{fGjSckhncf`]^V`TdTfWgWqc`Y__Vy_NscNqiJnhX{r@c[MoeHj]WzjYydfopuvtw{_}cXz_ejnqtwchkrQmWa}g\yZ_~W}{udeows}`jkslq|xrtoqo{xxoh^P{LQyPOxVNuXPtWFiICeBXxNkVswhFhAHkM@bJDdOJgSSoYSp\ZxaXy]^^fbicearpnk}{~ypg\`SeTkQlTnXzW|N\~Ub]xuDaKGbLEaKSoYTm\avpqal|^efmflOYrP]wrcqZeOZrZgzXgwarct^n[k|Vgt`r{k~^yia}gZxYxvnZuRb|Wh]VrP~xOlP=ZB4R?$B66.'D@,F@&@;6'mrngiehl1Q@9-<4.LGPnfLk`0L=&?2-H;[wbNk[0L=:185&;?8KS,=H'3,2&9=(??0FC/EB,D<2H?%<.*A1[r]}qf}^ucjv\uWy[js_c{Qd|QlZq[lRmTjlz_x_pWt[u_dQ]wM`wQf|WWmIXnJPcDVkKbxUf{TeWRkCayT@Y90J-4N/bzXn`f|UiYf|X`uUez\e{[h|YWjHBY5\rOUmLZrMh[[xMbSYuHXsGgWhWm[kY\sIdzP|kVoGD^7Lb;PgBViHBU8;N2?Q5VkHRhA_uNLeARjJIbE4M1;U8E]>8Q4C[B4L41H32J65L:+B/)@. 7%8Q9F`@Hd=If:b~Mv]i{Y~bo_qU9K6>P9%6$=M@0B50B1/@/6G75F6/@-(9'0C0:Q<5L60"8)2' !8''E0!<+'>/(?05#5M8Jd@=W9;W?/K4;W>-L04S41R27U6&@#*C&CZ>CW>EYB8I76F3/B.'9*/('##-( .) /+//"1/%51 /.)%)#1)(>5)A62J;,/"1%*!'/&.D5,C2NgT5!*B10F: 2))&.@3EXE:M7Wh4_xZR_CmEYa}SR4MH6WSHLMDIJCJKGQSNLMGNLDEB9RKDd^WF?9D;9?83?80MFY^.~;0Q恇P;Z"AAa$+L +.MEd"Gg$#BFd']{<1P p9Z|[t{adq$zg~uJ[Rv4f'o*gl/es/Cn2u:GBt3w6Iq+:;Y$>eXUIZuST\Y@ha?Lisf; 6"l{6un{+Q]NZs+DYy%+$r!"6F|43?ev8%00AXXv('BE\L`$Na1>P@R b쐐IAS H];OCV._q3 7(! '1+,Dy%<CdXc.EezPiBMk%P[u5d~>`z=^tO2F. , .(# ")*7" " . goA~NZa>\mBO[,8F X1/K/J/K_mCHc%H`0BT%0D +4L'?!7$;,C +Oe#vAYq@Y;XvRpm*H@9)}QToc~m!glr%Xu@MlWwd40Tt7Gf "> !; f~OPj:t`\xN'A+E#>7,IgSCc(Ru6iSBe30ROp5]{>Pn73-HyW_Uz,%Gd7UBdh3Q70H"'B}S9UMh:E]94K*, 8Q.9T)Oo8Jh0Fc-9T#\vJ#:":%>3J$F^/Sk2\x;Sr4cIEb1Li8?['[z8uJa>[#0K%SjRAVIHWVScbM]^ERWWegP`]K]VVg[SeQUgTReSLbNayb\t]|}ilqvvzc|fwzcxdbxdroqjsgpev|tavj|vnqhuvjwyx[ug'B91KK2LO@Z\PjjLgbgzLcb1EM8JR>RUG^]?XV\rkiqZrbaxfosy0A?$6<+RSTle^yl]wn[vpIc^Nid`{sUpkGa]PkiUoq1IQ*@N5I]Nbr@S]Yqrb~w\wpb|r^yhepxId^@^YDd_SssEcgE^gKbk@XaEafGbf8TUWvqfyuusteoo{}g8G1Fa,Ab1Q]s6Li7Kk?StDXzEX}GZ>St?XvD^yNhKg}Kg}OlDc{9TmD_uqjWxQrIh{Jf|TqWsc|byc}not[xbkkuqpfdJjwRr~hqJhrRoyRmz]zMjxQm}JfwHevQn}A^kFanhNosYxxr^~ri|ww~ub}\xTunWxnexduiup~LoZX~efso|[lYzii}Dhc<]^:]_JijA__KknHinA_c4TW2UW6XYIlk?b\KofLobWyhRtdKnaAeYJk_MndSujEf_>]WKj`Kj\FeS>^GIiQSr[IiR@bO@aPJhY7VFQp\Vw_UxYQtUgq<]F6XAPqThb}mr`q_vibY`[nqovmxJlZStaajisju`iX{_dfbc\~\gkszenZ{dMpXmxlvxzssec\U\UV}L[Oh[ZI[Le[d^^aRt\QsaCdTHjXIiX@bKJlUZ}^UwWgfuqc]^~Yolmiunjb|uojvwzmxraRW|O_Xg_cZi^x{wny}zqlvzytHq?Dk@HoHGkDOsHwgkhM{axfFe=<]>x~bgRmVLhQVu^FeLKlOUwT_Zg`b]gfcaJmFUuNPoENo?WwEvdeMgOkSydp_hZ\~Om`QpM{pmYvapf\HgH@\LE`OD^NRoYHhIaX~mx`cJhVYyMQqKMlJFfAOpFZ{PbVZxR|fi_zdgkuyA[I9QCG^TC[N7P@QmWZwZRpLxkfXFb1z[[]wPKn1HlEXyXGdL)F19WE6RB8TG?[N>ZJB^OA[M!;.0J>D^P,F7;VELjRSqUMpMYy^+G27QB1L>6R>Tp\NkW5Q?4PC;XJ;XI=YK:VEA^HKiOUs[6S=8UA7S?FcNPnWTq\LeW_rr\hvNXnV\zKRpHRk?Kfdsao[jFVgVeuVhuXnu`v}f|\rxVlnQifRl`a|n\x_ZwZid]yPMhAUoHh\ztlcYvVA_F0L<;ZQA[W8TN#>3:/MjVklStNeaQsR9\B0O@#B6OndEcYTrf,G7;WFTq]OoU@`HLkX8VI+&35!4<)J]C?T:AY<@V::R82K14K60H6/F4$<*(@-.C5+@3*?/5K7:R64N)Ni@^yDfk~Ki{FxZSg=_sUO`HTD 6* 8( :()D3:+6*$9/+@41J8"=!+D10I7&C0*G3#?(-I3"?(;S=PiO.G-@W?5G0+>*+>)'7&)<1+;3,&(#&"(!%61 0.000./++&(%*'0*!7-/'3%/F81H:!5,,"+#+% /$+B7"8-+!1*-)*')/ 3F16I38K8*;,#3'$4*8E87G:(:+/A.CYC5M14L/>R4-@&,@%7P1NgD9T+C[4I^;PcEN\EGW;WbDb{C;S#Ld9UjEKa8Of:Wl?Pf9Mh9Kd:c|URj?]v=YhpDWgao΋kg>LE6PHE?>9PPHNMJUUQLIDOIEKCAPEEQIETIG]TO6-(A90LI;?A%R],`F`TpDa 9U5S&E 0P3O-I1L3P5S'E + )+HiJ*9[uMpLacb{'LG~DyBe+C\t'y2z;x=k4d}0Orj,C=u5?O9$J^`oaJElt%4FG^.?T(=P2*?"!6": !*Sl4D[+'70 ͐ &_}0wIl1d-b3\|2<[;WKf"{Vp1(B %>7p6I(:>N36D/2@*-%+ .%:K@L %dj3QR\oxAYh,h{=l8}E!-,8>M^ux 5eu-z/8do_kRm+kE^}9"@ "= .I>['Ol3&D +@W"Rj1C\*C 6M\uW".G>W"Zt/Lio>c:&? + 7!4/ +;P"E]*[sBH^/E[$zgZXpE`d}c}y-~3r#Sczcx>aw&bw.!5Tg0CV!Xn44Se.O^+AN*7Wf.nFZp1;RKSK8w)UNb/0AQhNb:O ]q)MaK^\n.^r+v:f~,g*{9FCWr^zw*/,>0m>zA{[+<[Ag@c$>\!Jh*BdNp!ڔ5P][|3f7Y|&h5wFuIuMVl7%<A[+hXC^2Ol:n[wCa|HoY^xKJd7UpBA[-I`+[t9UGTsdLk]?\UIc_VkjYlmXkk^sqLc]]tkbzmWqjVpjKfa>ZT]xsWolRjjUlld}zs=S[7MZBYg\[[x{LhoA\eGbiGbg]h4T__gQruAbb=_]Fea;[XCcbFfe?__HieNlh@_ZA`[mThs[`IkQ^{GkVXwEs`TwCTpA^{Kp\hR[wG|mLkEMlMTq]JeQOkVKgQKjL]W|n[xH[NRvJMnJNlNOnPHeGDbCJhHIfFkjyzvzbj]zgnyhr@YM=SK;SMCZV4JB1I:>[EHeJ]{ULiETpEfN\{>`E[|GCf=:^?6VXL:UL1KCC^SB^R7!@4Mh[dm>\D-K.@^DBcHKmT2RC5)3QG8VM@]QMl[Ytf;XF;[BMmUDdPZzj=VP3KJ,12712*>:2G=5%1);%ur`nVj{Mehe|^drufez\~`hpVh|Of}Nr[nV`wImUw_fg\qCf{Of{Rq]YnF\qKRfAbxPh}VWnIZoO_wWay]`y[WoQTnLRlHQiFWoJ[sPnd_wOiYtcrag|XiyYbrRjy\euX_rPdxTe{V\qKazShXjYa}MfSePq\eQeQcNkX\wIKf;PjBH`>DW;2D,/%;++<2+=.+=)*@ /H!8Q#Kh.iIrQwVe~I;T&=V2:N04F/8I82C85K:;Q:MdG2H,)>P61F+;U9HbC:U7/H-7M<4)*>25'*B49PE!5/$8/(!7'.H4#;*,D6+C4\tc9R?,C4#;+2#9$*C-6K82E10C0,?,);*$:.$9/*;0 3&/"#6', 8I?*@3)%-""-(=PJ#7/ .'3',!,%0''&+%)%1C@!'&()*+%31"2)))/B+2E0.>-$4'/?22?20@59L<;M:2I1/F**A$"6(; >V:(@%&A!)EPjEC\8Lb>RdE\pLZoFOe9Hc1z_bDiHbNe0Pa9zgm|\drWHSAXcTq}imyfgt^eqX\kMgwSUjDPgARo=ZvFbMUq@WsDNj9;W%b~HhtגrK7KPb:NaPH\,N`6->DW(F\k{tQTd]IDuTKHOG4B=9C?713 ()B@3IF?QLJTJLOEEUHIF;:I@;H>9GB8WV>`yQNf$a~68V3R5UTt5Ii*<\=Z$8T$: =SLj:v7PmvAyFb-a_Yx8Or8Qie~.KjE}77s+u([`HXGg;Xe/'}~pJ@;s`/:1*65[FN]X,8Ef} |)Wqi|'J^y=[(91 , (<5J 9P"D^'nJ'AKe%Qk)^y:Ic$1K,G6OLgi9Pp"Hg&+#6#5DW?4K$]uGC[-D[-G_,3=R Ul~g&>wd|B1<07Jr0gIh|8$8CV=T,A=Ob`m;IT&tUboAi}E]s5e{6h~6ez*hv0{*D6T2Ni\oM`ct.\l/gzA[l1EXs8_x#Lf u0w4@C\s0C;~%z!QUw:[Hb?X c}f?Nx>wBNq@b ^ 8eLA`a2,F\x@2R",G!=X38S)Kg4*G -ILi,Kp,0S4V%C1Qn5yT4P@_,Hd6-M9]d BC_+C_(6W@b7'B.D$/H"D_-@\#e="A >_"nEaQw&AehLnR;Z0O uGk5Ui5Vw&i6KíZr3Lf2XrEUpFTo?Ni7Xr@q[iNqUA^"Ol3eEYu6Lj'lGX{;Kn31N5Q"VsCcDf@|T:W[wK7O5;MC?NMM\]FSTJYUFULPeXb{iH`PC[KTo_dokulubyimue~jZs]f~iinWkZI^J`q^pmmk`x_\w\ooqomgh`lfyv_x]pkqhuftfykxpiuuWr^Uo^MeT]sbs|G]Rr|iqVm`J`Pjhriyr}xv|prcs{{xwqvfivpxiuwmzv^}]kmno~lkHfQD_U7PFMb^ZokH]YQf_Qi_Mf[czqQlcXqmE`]A[Xazz9QSAW[Qeg`vsmXprBXd:OaDZi?U_F[_BW]Shkh{`woXoeazotHb_HdfGdiB_gKfoEagB^cTqpsnk}cpp{hqnzitr|dkek\vpSlh}xl}:T[6Q]9R_,GVF`vGaxA[sHb{C_wC_zHhOlRnB_tAauGfyVtUp\~Zhiechcl`wp[^It]urpooxyqlucs^xcy]lQsYl|fp]n^RsAUwDYzJRwJm^o|bx[e{^w_q_iVOo=xmmi`[]~Ulbni_}\gfmndblfzre]e\^~S^|PWsEux^a{^py^qV[uDgSXuElwarsYhS_yH_zLc~YBbAJiMNlQKgOJdK\x_XuWb]_zSZvMHlBJmEFgEEeEHgHGcHD]FA]GOlUXvaZwb`}j]ziQp^anNk\-G>.FA6JJ>TS9NL0G=MdTTpXHhD;X5\{Rl[dNqaGkGHkMIlMHgP7QC6QIB[S5OI6RL1LH:2+F=5RD?ZLE`QGbS[N^{n%B8+EA,FB.FC7PK>WMB^OE`MOjWOl[&C1GdRUu`LpWUy_Xvac|s^qxKZjHVkYd{^e{iuevvd{kLdI=S=;QD=VP=XTJe`RpkQmc_|j]{cMiMDaD@_;YxRsfa~Xldc}^KdG_v[VoSE_H9UE&A;!<8#=6*!%B:Ssc:VG7RA4NA6Q@9VBHeP@]LGdX,JB/LDDbWA_P1L>8TE<]F7VD2Q@2N?2NE5NK!6645$:3AUJ>SA:N6ϯz}hj}Rv]dordawriuV~^ktYm|Q}cqVuZlQqWhtYiybkUzde|QaxO\rL`sQ[nLYnHYiEViA[pGZnI^vQ\uUYrTVrSPkLTnOSlMRkHWoJ`zSiXnXydvdo_fzZdxWasRfxYfx\l_iYc|ShYj[c|TlYfRhSa|Lb~JdNgPXzD`MKkV9=U:>T;3J78O;+C.+B.(<))>+;M::L=3H9-?3/@31A1FX?n_XoAcGpN^y=vRlKoR`~JLg9'?5E0/B1.D5>SAB[>4N*VoO:Q4;M02J*LeEQoNFeD1N2&%<,)!  +"  3*4%:R=4'":,2%(@2BZH(B.4J;%;0)?0%;*4I8+?.2C03E2.A/(;(#9)1!.A,8L3YmQ*?#&=&-  @WAK`N !.'+>;"61$$$!%-"%:1"2-(##0++)!/0#&$($'"$%%.( 0%9K8,?)3F/5F63D41A51>2>O>4G5,?,,D+.E)8P0ld!5-D(6/K..I):U1;R.BW2Lb8f}Qb|K^yDjNa}B\x9_|8jHTl9crNvfoaYgL9F0hnarkkwdmzbiwZhxT_qIdyO^xKYuCYuBgPUq@Eb0Lh6Nk3Kh-fnh.{?RPb6BR.CS*}IFV(7vTGQK:`LZem*pT][.'D=5%%ybaKWSGNIEVMMh]aG;=H;=SIE<2-LF;@C%R^,:O9U&E 7)F-MGg2Pp3Nk.5R1K"? ?[-%>Rn0勂2p>3qJm]8c4w.:X{?p<`/_.m4Jp7Zxp6ka{@c+RmR:hOUZF;wX=K^LN_xnLfCEo,34UL>9P9 :hZXc @R+Wn1=HNl"Pi Tg ;s&$5.Bw"x8X?Vm[px-/lkB0:Aa쀚D\%DUXi(8KEW%E\&g{dmv"{-Xh0J8Vc;iBb>kJB`@^"3O3KH[.>R!BU&Rf7;P()?Mc8:S0M f:g:Qp&,H B^C`7S.LE_..-BQf=!7 K`/Ph0%=Yq3;S;SSl(h?FaKd%=Wf67U$.)*<&"5!9"<&=14I7N7OB[fIJX*uMQd)CWI_i+Qm >.)Hi v12|6SdUf-Qb/BT`u0n2h*f|%u0B{39z0p::.6Yv$CQa:N];O]J^fLaeWlo^ssYolXlf^sglvD]WA]\Idj?Zc?X_F`bg]{ui~]}rUvhVvgizJi[Vughxmw`}hfjxz{uha~:Y^A_kQmD`y@ZwA]zD_~9TsA[yA\xC`xTsTnB]{>WxD[|J_yM`yTiz^t|az}id^|`]^_olMo{Ii|DbwVvl[y~mndl{|_\LqpZ}}IimEelJkuBcmDglPrr\|xRrq:Y]A`cJhk^~}GecVvsa|Jkcaxby_uEj_Bf\QunJmj9ZZ>]`@`d?ah9Yd8Yi+J]0Oc.K`.J^5Pe-H_0K]1M^:WeKpOlpgjbc9[;7S8YwZSpQGd@MhAylZveygn[wPQkLlkb|^YqQ[uV`|]TrUXv[Tu[NpSVx[RuZkoLjQjpswJpOAfHPyVLvSRwURwTGkCIn>~ntauahTwcjPhPpUtY~ekSfPiUiZiXYH[JZLdV]ThaOmKaa^}\lgf\vjtjt}|{qsgsdoapam^q`qarccUdQkVhQxabLjTt]iShUW~IthXxLQtCaQhx`i}imYshwoe_[~Wh`ognge]>`;?b>@aCJkMXvXHfDPnKumpnqsZx\uY_}C~blmSbIbIkRs[t[d~za`}KJi:_~T_yTXvTWuW[uZax`c}d]|]_^jaYuMFf@aGEfRFgRDfU6TI,IB+DB(A> 96,AB$8;86=WP3OD.M??]N,G<3OD;QH6*NiYTq\JgPZya<]EFgT"D6=6'FA3QP)HD(D@(FB/MH8QJ.H>;VFC^NJhTHfP@]HRq[UyZNqRWy]hr\toJ\dN`oP`rXerk{u|lokT}kq?[@5Q@EdZRqgUve\}bRpQMjJTpHh`og]wT^yUb{[KdHUmRZsZ:S>3K;.H=5PG&@75KBJbW:WH2O@*D83MB.H?1KC2KB=UM>XQRhf5JI.GC,F=>VN+A< 42# 1D.xqvlhsZpUglei{[dnsvY}`sW}btuYrWy`n~RqVi~KnSlw^uIdPl\[vOUpLZrQMeBWlHThFXkI\pJXpIWqF]xOYtL[vTToORkMEaB?^4F53E3(?*.E32I6(?,+B2(<./D6%6+/E57H=&5,0=/AS:E[4ZtCeHXs7iKTr5Jh+Vu6cEt[Sm@+>$.B.'8+%7&6J039MB2F:;RA2&&<.'>.+B-9N6-?*4G1'< 4(B$9S/Pk?zh 8j_xmZtOWrO ))!(24"):,@SB0B5#!&")),.#'"!$&'$%)(,%2EY=NcC(<1C.3A1:G8,;+%6)(;*,@+3J0*B"'?tb6=[f9S]0bbH40%G>8dYV^RT^PT[RQJ@7pl[rwW^j<͓To7?\(1O#6S&9W+2O"7S"5R7T ;U*'A%@(C'E 'pBX/0)].w(`{eLRh$z4hs>oTq,sHSt$Jh b&o9j6i7h3f~(UmOg5erg2>:$t!TV 9)eu^DSfF4GYB'Tb,'D3>+F +AeQO>~nn(Da Un+FO2%+4[i.s%Hlqq(K1. Ncjy2 j $5_KV+81A:MG[!dt5wHcm-{:v0dv`siDXBWZo:_rAIZ03J!"63H#*@!74?V83,0!5 &? Tk0QWmELf%Hc!Zt-Vw*c.Xp.:R/GDY$uVMd,#>:TBZAYPm c+|Bby/$+-?.> +R`&Ud"aq'dv%K] +h~v"x ?N{.jx iw(U]n,Ud0%EV:G`papWeft%fr#7:foA;c}I4Q=$E5TQo,Da=Y3P6P6MJ`,7N0E$7%:.FF_(:WZy/Pp#Vu)Ro%[vHuE_~4`?74L$@T21 -Zq@3L%;>S0E'?.GG9R[v$h*Xr{,1`xm1m9b}-Ml7h8@X;M8G\_iSATFYM^`u.mYo|<`xe~D5u(1N&0N^k;'@2P +^Gj h.]y:W1P Cb"Rq3Ss1Lm'"C >Ff b>F#C Lo:we*F?Y+&@6SeBDcUw,|PDd%!?4O%Nj83PeTs;%E1R!Cf08[$Dd-Xw\v;Xr0Xt0uMhFTs30O?\,8RhIKe'^|:Tr5\yBxE_DMcM=QCJYUAQPK_Yt~y~gmoz=YLm}UqhPjdKf_Yumdt`}op}[ud`xgOgUmpnqru]v`Yr]gjd}h^y`[x\gfe^jdSmOc}`lgg_e~Xh[ymibQqOYyXjjikwy~~{znljits~~`y`bybrniz``vYoklggb]w[SnTZw_QoZQm[QlWnqjk[t[bzbHdG[xXHiIIkLOmNOmKicWuRa^WxRmb]|MhWr|r__uuotC[M]ri^sj~NbWH]W_srOdfI]av2GJ\iC`sHd{PkC^|F^~>Vu?Xu@[vHdz8Uk-G^2Le@YpPj|E\lQiuge~ng_}SpzXw~_bf_BboIhxHfvNlypbfeuxZkNvtQ{uFlgMsrAdi>^hCenBdn@cmUw{=\^Xvy7V^9Wa>\eUuzCadMjjMkiPpkSumayY~tEic3VT8Y\Bdi=[f5S_Khs<\i:Vj0Ka+I_'F\)H^3Le>Uo2Lb&BW6RdB_m7Ub;Xe4O`,HZ(DX/LZB`k0OZB`l1PY:Y\Aaa=]Y<^REhV@eOPr^Oq^BbP/O;:YCpuOqTEiJ.Q6@bJCeMEdMFhLHlNLrQKqPBgF=]DPlWLhT6S76T4w̗Đ|{ƛvIcADb@StRLlJ\{ZnkmkTsVQrTRtXQt[Qs\@dLX{a=aCHjN^]sl]Q_NwfdSaQs_p[^EdNRy@]MkZW|GTzEbSYKV|GaRVxEWyILm?5S2WuTvu{vwrqoggpoke]~]Wx[MnOedupiaX~P`Ze[RyINuBWK\M]MaQWFgUxelYm]uiaNpJB`?GeD^[[TcWpd}qmcZxQSrOYxZRuPX{Q`Wtmuqrmkk[|^3Q4<[<4R5GdFa~_gZeXrat_{dqYoYnXtZzd~io{gPnr\]zClU|hpwf~qnb^|[\vWXpSXrU[y[OpN_XfZaWvlrgLlFBb?NlPKhOPnWa}k`zgipiro|[zhhw^}mPt]s3NCAWR-D>)A8Rn`4QB0M:;YC;W@7T85Q6DbJ6WDFgW;ZL1QC?^S3OK-FE-EH,DF.CE)<@'=;73@\S6UHDaUFaV)D:(B9#=5,G:F`QTq[RoZ;YE;[IUtg&C=43$@B::+GE3PK.IF)@<+B;1I?E`Q@\LPlX0K7NjTSuZ]cQrWZwb^xnTiiTiq\nyQcgSgU|iqQz[rUu_zk`IeJFcQGfUGdMKhIhc_zV`}Sri`{Xb{Y_wX^wYTlR[r\NjS6R=4N@6PG.GA)B:BXPLcV@[M-B<3+$=:)((0)8,GREvyjc~cg}mgatXivkoRhyLx\x]gz_izMtYnRcvHpTchw`c|PRlARoEYuNGc?E`BJcDf}]]sQ[nJ^rNWnHYsIeU]zM[wOVrN`{ZC\<4P-Ea=WsNVrLk]ziykn}Xr\q]dtUpdg{`YqR_xW\wSulf]MgE\uQhZjZo\fRbNQm;Mj9PnANjBEa:D`9JiA=X5?[;F_AJ_ADY7D[8D[>9P57H67I70B0)>-7&1!6M@*=1$8-2C6.@33F8);0(7.+<*&9XpETo`zL2G'.A),>,2D2.B'.G$0K'?Z:=X7@[:c~^_z^+B4"5/(&$MdT,D-@WAD^H9T>AZG.D6-A63D<;KD/"%9.!8*D[L.E2)@)-F*?Y;8O.PdE/H";U3IdC<[0UuGMi7C]5?Y17Q(@]4/N(EcB ( &* -#2C3 +\o[0!/D9.)! !"!&& #"&&%("9I=2F0G\=k^!6?Q:.;,+8.):/:J?*:/$6$&=#Le?a}LA]*7S#8 xm5O):T.=Y.RlD-GB]1Qn:[u;Yu:Lh.Xt;kJdC_}Bw[g}PXhCUcFju\djVrsdrpt`doVky]PdBTiCezRbyNcPYtHdRLd3Qj9Tk>Zre\S[OLE98WMHXSGWW@emKQ_3'< +2L)F:X,3 :6,G/J8(C+E 9'C0O- @b$h\.5A]@\xe+Gq4Vk7h5@\9VPm,B_taky@k6^z/}Kb[p~6QQSKS[D`v8T47IKzsLNvJoXFf2RSPOA/#<+P=2(Qs)fb~'e/j5t?7U[{0[yT$7P/HD^"Sm/E_f;Tq%V1M Rj)BYWOi i0h2Nd7I$0 2**:=M2B,<[pm$b}77z(u*}7)(DVXhy7#2LX]kcq#jx*IU?L@MQ\ Iq{$HRy08 `|Bc?:Y:Hf"Gc!@]Qn,:VMh,Uo27S0H3J2IB[)Gc*C`j>`~2KkZz.FeUue/n8`4/L+/ + +*@+BH_*9Q\t:yOl=[x%7S;U D_IgqDB`#.B!1,?(1(&,.3 )Rg6+@ 5M +G`t;F\c}+g*|:y.<:f&k=;U j2[.ILb$6J`nBHT'uTIS)EP(]$>]%b?|Pd8\{1b: ;V.3M+UmHt_Lc27N3KUn;pQ`{=lFnHtPpNHh-Pn9\C?[@`{[\xWLiJTsRlf_}ThYrcqfb\d^QiTKaS`vjCYJ5JKJ^eCVaFZch|@TY=QYTdlYmo]svHacD[d9L_:K_L]oEYfE[hKajG[_PeeXmkh}uvpyLibRpo5RY>XcBZaE_bGddTroLlhSsqEcfGchC^cNjl^{xKe`Vtgr~lt{gm^zcptvx{zoq`zcmwcrp`KjrC`oWtB\s7Pm@TsOeE`s@]jB^k1MXZsa}TuxdNkoOlm]zx`oXx}Wu{Z{UvzXz|fbJjtTs~HdoTryWy~ebqZQxMtwV~QxvNwsGol@fgCdoEep;\g:]h<^hLlu3QXKgq@\j6Td4S`HeoEciKhkD`eIgiB`aAa^Vzt:\[3TY;]f=Zi7Rd;Uh7Pc>[k4Mc;Uo1Kh*F`-H`,E_,E`(BY+F\.I\4Pc3N_4Pb4Ob+E[/I_6Qc6Qc5Q`2N\1MX;Y^1OQ:[XKldDeX@bP?`NGgUOoX9X=mlrn`\mkVxXciHhOKhO7W;4V7FiI[~_RuXNnWJgTC_MD^F\wUn}}d{eozjlx~g{fy~tgrdxjXxOPqMFhJ5V@@_QCdR6XD;]G>`GBeFQuPMtGU}MSzHdWZN]QfVn]]OIo>Jo@]Rg^qfRvG\NvfoqYxIjcgbpl]|[QpRBcHFgQbnJiR=\KA`RCbTVu`\~bmnhg_\Be?MrIJpEQvJBj9ZNeXaTBj4SzCdV[Oida`8Z;9YDXwbMjTFcIJjIVvR_|^VtTYzYAbC5V6@b=\XmiTvQ^[kcieXxXwulkDaG]|^xpg^tgjZmZ{ikWjV`N`Mpa_~QYyHql[dTVuEyxheSjYeTq]l[f\SmHTmMXrRWuQ[{S]}TnbcVYzLnd]}SZ{Sfeae~foclpya~n]~m`omydpai}^zj6PE;QJ@ZR9SI/L?2N@5OB9TE-H5>YGA^N8WJ7WM'F;7UL8VP1NJ.HG(?B%=?"6;#49--614PI9WM(F;0JB?ZS>XN)C;'B6E`Q@\J(E33P?:WJD`W1LG9:":> 79%=;-CD)>=';91FB)>8@VM@YL1I<8SC=XHA_HRpYEbLIgSIcVMd^VkjRfd^th|lwVoMhIhKmUn\naYrUMhMLfGrjul\vSg[c~[VoPXqS`z[VoRE\EGaL@[E8RB)D7";5$=:(B>@VQ8*A=+A=30*A>6PI@ZP,E>.CA!;8&?F_?;T76O1=V8?X:KcH1J30G2-B1-B46G8?R>>R=GZC9J2:L2M^CVkJUkFJa?NfCC]::S4.D*/E16H46F40D6,A4!6+0%%9.);0"4#$6%0B4&7*,-=(2G(Lf;Ie6Jg67Q)/J!:V.5Q'3N#0K=X.C[2+C#:6I1_sYB[;,E"7O00L-%=9R4,E)24%?$-() &">RO-C70 6%(B/#<,,B5)=2G[R 0+!1)*>5)) :$8*D!#=;V.n_kX>Z0B^8Ba:Ef;@c2Rr?If;1K$'A.K$1O(KkDOoI=Y6HcA#:6K8McK:L7?S>0B1+@65IE     &)/!2,#=P92F+14I(h~]J_E$ +%/;3(90/#-@-)A$8P*oZa~HuXyceT@Z31L!>X-VtD`}M[wFYv@jLE`&Kg-ZvFLGH=7Hb|,_nv/OR$QO4MF6D;0QH=\[KknWakH6F!.D*E%A0m2 +>W1Qi?:$@:3'B""?##EKk)MP/n*j*j0vA:X j<\x.U @u"g:Ifx (X(I| +5$OOnBKQ X_GM U\ }Dmz7as3Ob'DZ$"5Ne36L?T/"600$ +/F-(A'' % +!23 +:S&*@1H@Y!A[Tn/Ie!=X8S}RFbw6P Sm+Fb_y9}WlGaw7[v.`})l0\u%I] ar3v[jx$ft&w6s,]rE_u"i|D/:HUV`%".L\Te M]P_7F=LKVAL ?NMWWa^j"u4K]ghs,4P&EPo+Vv.Db]z9yVQl+rJe?=[~]pP:+EQl9Ea&sHLn"ǁJf/-G/G#-E _wIvoZq_CZ-Vo;nNMk)[y6\{7mJYy;_}C[u<\uX9D\Dmu^ukWqiMi]MmaKibOmg4QQ2MOB\`F_dKae4JIAWUAVUKb^Md\NfZbzjmuPhZGaSYteWr`MgUNiShl^x^qn{xgbfcpnpk{t{ppj|qtVsZ^|dZwa]~fnwJdO=UBLcQaxeK_PAXFOcRYk[QgWd|lLdUIbTLdWUo`B^OZD@YANfLWqTUsTLjMB^@SoLTpIdVf[`UTqLsmLfLJbPio^vanx*@>?T\,?ODVgas5FQZG>[ELeKwnjy[z[wYqrjZ[uQtkla|kzehq|n|tu~jpd]~XA^D-I:3PBJj\EeUFhSGhRRvXElFKrJSzPOvIMrG;^7RwKY~RW|QJnGDhDHlGUxS^UTyNRwHcYk^jYyhl]ToFD`9?^5YzPPpLHjO=_K?`P?_SDc\;WUA]]KiaRq`agdeNpPEfGHjGDiAe`mf}W|Ob[e[pbh\\S]ZjmKnQ9XDDcOVr_\ydPlSWs[^zb\yanubhHjI}z}s_WMmHIiD\|VYySgcuplg^~[A`BUtU[yUogod|qqbi\vgqbe\kbneh`jag]WwMofnfxm}qseWpbyhsqdl^b{TvjshQqGf\e\]~R]}Pqcmbc[KjH.J/]{dTs]c~kd~pZwi[{kp~^~jHkQLmTOnVYvc@\NAZP=VL7TG-I=0JA:TL5OF1KA8UI1LB2PJ5RN0MH4QL:WS+HE0IJ(BD69#9;,@D:OP5NL:UQ@[W5PI7RN3NHG`[@ZR-D;5L@E]O9SF0K@7RHA\V%>)=9;PJ;RIF]T4OBFbS?YI:UDA\K4N@MfYJbTE^K]w]ToHbGzY{ZZvl^sbtbp]vdYnMgzZ{nsemctmd}``y\MdIhc}vc}ZbzUo`jYcT[uMZuLa|P_zPYsIYsMB[>3J26M68O:8N72H0,D-0G33I52H7:OA*>0>OA*=)-@+=P;7K3FY@ShJWlLH`@HbAKdD=V9AW?(>*/A-2C/.A1,>0"4*4*.>6/?5#5''6*FYL-?14F3/C*ZqO9T,6P'?[3$@0K+2N-0K):U.;V.>Z1B\5ToHA[8H^?9P1/G%JeBE`@D_AQjL@Y>2J095( % : (@)+D5CZT!!+'@VM5('=1&7->Y;|mEb3Li>_zQA\.A\(B_2<\3A_:LkCHh>Cd7Kf>D`8!:*F!;[2Cb8UvJSsHvfUpE@X98Q3CV:8L1,.?4#32+.   $((!&>T=" ' (>6L&[rK9N10;J?4C4(#%'@7 9U"Eb+Vs8kO(BGc8.HD`4XwETs@Up=Ok6Jg/Tq8dFnkNLj.b}>ZwS^GES:=L0L\=H\9H]4VkDUjBVj?Rg;Pb2wOb{-v3w4JPSa?]7P^C@Ja_/e9;:LRkm^]v[J]lvՊϏaWZ/vrU\RAeiTotZFR0 0,@&70 puR#<>T"&@ 847$? 'B%0 > +Mqe$Rm&Kf!Qn)eAHf(Xv:Pm3A\$Ec)iJvPOo'mC1L7OOf)LSo7Rko:k4[r gt-LZo R[[}%XH4'1ObRFLEf:[r_Iddlc\5?XN>=.;SUpNi%0I 6N3Q;YSt4Ll+)H#BHd%9U1M,G,G1N$B 3T&D!@8S Zp)3h{cz}*l}H,6$#~ ~3'Oda{~wz(kp!NUV\Ecn(bp-4E@RZo8@S .#9- !9' "),A-4#! # KX60 +5 6N%> )B A[Hc#_{7GbB][x,xFOj ^}2Xv+Mhi@[@\Qn-Wt3FdFc>]Hh!j>Tu%Yx+7X'E7SE`"qL_~7c;Wu0Xv4(CC]=Vj?B]Pj!Ic+FYx+Nl)3I$)$8''8&' @Q!E\-/ BW1>S"dz>Skg,v9[}Co1p,8~*S`w92HKbTp h-_h52 ;Bb&oLeHHe.;XVu2Yy8]~7:YUv@LMt!Pv1>^)!=?\0\xFYu9~Xa~WIPk[`ziLgSntc|fTmTyujg]xYJeFSmPF^B]vYjb|qc]hglpLlTLhUMiYFcSPm[WucPm[1M=-G7KbSqzI_Q]sc\pb,@2Vl_Zse^uk:QHF^SKdVSpcMi`3MFC[VE^X=XN;VM6PG6PCHdVPk\D]NG`MAXAG_FlkcfD`HD`EjgZsQ^yRyl\xSZwPf`QlQSkW^xed}iZsbJcY>SV(;JEWi;L^6GX>O_QdoJ]bE[d?Td2D\7Ec8FbBQi@RfBZg=S_QfjMbeRhgZnfv|LhaCa`TpwQlwG_hb|VrtJhfYxuFdd?Z`A]fE_f=VZGda9VQi]vn]xi]zghp_|eeksuvwcz`~lmnvgrq~xsroHfg;X`Vwnra~Psmhu[|qPphZyslZxU}uTzu]qfX|qwm^XU{RwOrcMsPw=eoFouEkp.S[2Rb(F\+G^/Nd;[o/Mc>ZqB[r9Tj-J_/L^:WgIhr5R]7R]A^gB^fFce;_ZEgfPnv;Xd4P_/J[0IY_y@]k ;O0Jb2Mg=Zq)E\%>W*B]0Ib9Qh8Qc,FS/IU2MV1KR0LP#AC8UY>Z_1NU0MT1MR0MQ6TTA`\Gf_8WO>\Q)G8[wcYvYrng[sgeWi^}v~UqNmkvsJkG]}[uvSqU4R<@]DUoOqpY~isdrjg`SnQmfutds`}ihv`fTwh{pziwex~l^|`0L;2MCGcZVveEgSIkTJkQW{\VyXKoLPtPHiKFhH=`?Y|ZLpSAcKCeL<_A9`7Ag?X}TmiOsHJmCWzMrd~spabV_[5V;'H2OpeLjf@\\_MIkRomnfxmi_cXQqILkJBb?QqK\{Rka[{QHhB\zY/L*Eb?ibkb\}R_VuilbZ|VY{Y[}[\{[VuTHfD>][ID_PB]N&B3-H:7QI4LI+DA0IC1LF0KF1LI(C@2MI=ZU.KF0KF4NK+FE*BB-EH-EG4IL2KK6NN0IF+EB(C> <67OH8OIBVP9NCJ_S;SF+A8&?71*'C@-FI&>A$;=12(<>23!23'9:-?A0BC.@?6JG0HA(>50H<7RC5P@1L=;VELfSC\DXsVmeeU]{GiJ~bmQ|an|dwtdwpgaz\XoWG]HCXEE]KSjVHaIE]EJbJ;T?*G9*E9)B<$>;65')33.EE"6737$:<$9;!76+E?OjaUD2B/CP:p~astrgx\{^jminitpXw_yiZgMXbGr~]ex\uW`uVwXwYwXvXtYnX^vJ_zN`yPToH?\8:U6MhJ@Z?3M3A[?HaDJdAOiB^tLcyOcyR`{Q[vMWrIOjC]wQKdBNgFj]l]n]s_m}\^oP`tXoghz]_rRj_]wWJaDTjNOdEg|\`yV_yQe~TgR]wJ]xKLh=VqHVqERmA@Z1PC1C54G47J75H5@T?NaGVnQOjJGbC@Z=;S:3I4+B-6I3/@*!6$6!$6'0*+<2,?4);/+X4F^@:W84L/-E)893O+;U,Mg?B[46P.5M.9R1.G'*C !<6 ;32 8(A&2]yXmhMhI;X>%:01G9>Y>S.RfDZpJF^/Qg4:Qe{1@焕LIcR:np'l-k#5HGDVOq*@VW9$H\@f]R^KlWFQawU|YYaC;B#4B"-@$000 (@*@TvB)!71bzaB\3- '?#$>8nUv"n<=\"Ge,Ie+ZwAIf15R :V&0K;Z&Zw=qNEcuCRj8/I]Zn$cy0j3p8LU@x5g!>7Zz,;MP13@e{"-0PAQI=ek/lwk?gB\P)6"2Vawm%@WD_-HXs@ 8 *F/L4Q3P/L>[$+F'B2M 1)B#= "> B`) +1L'HUo#[s t/=Kx&63(E1,L#QKIOHU^T`Ydhr$PAQ AQa^sXle=31`nR_NZs~7ak1)9ET ?R IXAP@NO^%IW MY$BN=J5BAP:J EVCc 7V,HRm.A[#78PB[)Ni63O,H*G-IMi9ZuD:VGe)Pm/Mh,C` 5*H +Ca#Yy2Y|+Bfi@d@'8V^}?_};EeGgPp%)G3M3L.E`x=B[c{9Zt-2ONn!Ih$3&)IY5}^rB|Q8Q-A%4I %:@TrA}I:S Rar-t#8v]x*x4Ki x4{4o*Y "EWar?.B4$<::(E 2O#?-9Ux(6VvPor 07o!\zv2Wn +m7S"1t$x-4/+~El2i,8[~GOl;U6L5I/G vLOo *^x(L]{!v6g!ToB4O$@rSoj0+HkXKf;5M(5L/QeJAU2Ia6[vCnPQm/hFIe)iIb~UAKcMUlWSmUosLgNH_Gc{aKeFibnh`{Zd~_jhZs^Wp]ayfkt7KK&:G5E\4DZ?Oa=O`Ser?QX9M\6K_2B]5DcBQn7Ia>PfAUc?T_LagWloTihYnhPeY~QleKee=V^9R^BXd[syKgjMkmC`f:W\:UZ;SY5OUSnqA]^LigSpiOjaVsg]yjktek~ii{zxu{vqclt~yrzo{yw{t^{mqY{rzsJjeipkizj~ylMulLulGpm]V~Ns{HnvNsxQw|ZdPuPrRs[}JoInHnBiuBhrQu~=]m3Rg*F^.Ma9Ym.Mc-Ha,F_,G_-J_,I^?\k=Zh[`?_bCai6T_,JW-J[5Oc?Ym?[l@]n8Rg1Jb5Qe5Qe&AS2C":J$=H+DJ3MR)EH*GE)GB:VQtl~@_V1QLTsq6UO*IEKkf9XS=[T5TJDdWfxfub~korGcF}tzvj`ukvlvzsxtowt7U8}|~}ozhnrvgodldrif`SnNd^VwMkYs_ns\Ij6ZzJ^UUvMaWsfn^p\fNta}u5S93Q@@^P:YG:ZI5WC@bKDdLAcFJkMHiKNoUFgKDfKFiO:\E:[H:YGEgNGlJdhY}\GmIBg@=a7c\yk{f{z}|qgqs9ZG7VJ9WN&C::YJJjRFgJIkKPrOihqoxuebY|TnfskZ}UBe+D@,FB(A>/HH2MI/JD3OJ,GB.ID+DB(@A7:)AB1GL&;@1535-,3NJ0KE-B?-B<7KE6J?;NB=SE";/$>3Jh`3PL%C@#<;'A>!97";8'?<-@?)<=,>@4FH)<<.CA0HC3JB$>4E`SB[N,G9+F6?[Dnkiae[a|TbRcMmVoZjUpYlXppbi][vU[rXAYB8N:FYNMaWEYN0G:TlYg~ic|fJcP8SJ.H@!:712&:>#;>%=="=;#;;!9915+/.,*D?OjbSpb5OCDZS"6-+?3LaNj{e|§wslhp|c|eynrrzg{gr_r[v_hvX:C+clTmx{^o~QwXxZuS|[xZ{]w[u^gVVlEo_]uQ@Z8AZ;SlOUnQ:S6D]>E_=MgC^vO^tNh~UsaiXbyR`yR\uQ^wTTmJMaARhF[pNZoM\oLjyXcsQcwYk~d{vdwY]qPSjFCX8WmL]qQtegzVhWo[kTd|MTk7R4>V;6K7/E2FXE:N3ThK^sQ&9(?(,2G80D5+V<%?!45/G-9Q:QgO,C+9[uS@]5Fc7;X,1K#%@"*D%4M/E^@]uXB[<2M/;U7&>&+7P:fgAY  $" 41,)3-1)3,*>52&*@3"9*2!.I(5Q'Ec4Hf80L$'B4P+?Z31O/"<1.3K23MfBIdW0D\9BW4* DY:@Q96E.?K7!+!!! (""$)-201(#9%=W8Mf:kTKb2/ 1I{c`uM6M ?X'iT]wKe~T:C^3Hc7Hd5Un>+E`wL:S,A^5=Z->_,?[*A].YvEiSQl6qRgGdEYx;qUMj7WtDNg>buMrWt|WJU2LQ7TWA;=/QTFMSCEO9?L3I[>J]@CZ17QE`$Oh#uftXG^gImhqFw-hY_PM+b<.F^7e;E:CIeSdYwjpry@7A8F7G%%:$ 1G+, Ph;|FjL"3/|4 4d{U/IWu2Up4Ol9Da.?\)Tp?5Q"@Z.=V+7Q()E/Ka|CZy1閠b݅WH_{Iez0vUFF[6FeF:LXOVZluQ:=T'kZ[u-K@}ZfAO:Q C['!;,2 +%>'A4 Hc34 +6Q!7,3#< /F*B8R#`}JWu;2O uLf[rl3Xo!c{)PjLi KIb'Pr EF-4 69epP:w-es+/A[p.DX(<1 &<1 7 ''<8N)- L_3GX'P]);FnxDIS"CO&!5g0j72LB[>V)D[u*\x*Qh ly3w0o)_vx1KJ-=y's)t1;Qp)u-m4s/09'0>G +7C*5+9[k*La;M FY3E 7I2? ;H7EDRQa'G[?SYx7He%*D +ayDawI/3 +#< 4 +@Z.Nh@":2-F"!=Mj6Jg.A^#Tp3Vs3Xu6,JDc Tu,:^ :]Qs"Kj)4B^&?[$Mk.Ih#Bc_/Vv)Kf&0Jc*#;,DRk*]x.^}*St$!? #@ +Xn;G^.,DYf+g.F_"0 5J';O*F[04-DJa&E^#1G 8NDp%m6)Ot Ch#=[q,Me-h;Un5>U&,'C9 < 0L#@/J"< z^/N k@Gi7ZLx,SG\}!CbMby,;PZo@%~.Rb|<{+^x"6{)T=]2Rz?~&A&<{Sq^cj9\1QXu<*G=\&5SEf$`6V{+Ux5Pn4,K'D93L+7O3+B/.9T8'B(0*J",L*IIi*St.c7zE]Sr1A\.;X&\y=~ajMTu@A]1PjF;Q42G'RkDE_2\wAeE@]Nk+tPUp1pG{PlEqRlTqa;Y*3O"=Mk4Ca&kRWsKTU0II>YX/KG2JD=XN8QI;RJ?VLD[MVmYPgT`h?_iCfpOtMsMr[~VyLpAeAfDjAhw2Yb4U^/OY$BN-KV0Q]/R^/La7Rk0JeB]v8Tj9O=Xk@]k6S`6Sa4O\3PX;Y`GepIgqCalA^k'ER.KY7TcC`nA]i2LZ"8H4;20xgyIdX.J<]xj|\{i\|g\|fgr_~nTrgTrgUtfsCbS9WK-I=6QDTqcA`QJh[A\K:WA=[Arp{wkglivssoge_ZNnFYyObYkeXwUjfwqh_viqcrenfRpP^|ZVvPxr^_EiEJpFg[eRu_qkWQpBUtMTtQOoHPqF^~Pm[nXs\zn*J*GhP?`I3S??_K6TDEeQPnXGgO[z^NmR7V==]A8Z>@`HAaK:\G3S??_H[}bRtZW{]QsUSvQxyliW]HjWX{GbQ[}KdVlcqmv|>^G+J9IhWvegceihbcbd|}fgOmNihghmp]c=^DFgMXy^HjOVz`3S?*H9a@NoPTuWQpU'F4'E8)C>#;9,DC+CB,AB3KK.HF1KH.IC4OI(C>(A@&7MG3IA(=22J94N>KfW.L>*I>+FA1LH.IE#>9#;73LH6OK3LG2GE.AA0AD,>@,A?1HE>VN;VI5OFE^T4NB1L>MiS{vycZshyi[dSp^raXqH]yMXsHNiA[uSBZ?G_KBZGG^NEYP@SM;NIG[TEZO%:;'<@";:%?>$>;(C?(A?*AC0GG";8D`Y@^RBcRB\N5L=)9*@O9wjm}`~boueu^l~Wk~Xip[XkDbwPlZt`}dppWdF_kRxwerXd|^z]wXvWqpUht`f|Wf|Vn_QhERhEMcBPgHTlK`zWdZb|Uh[j]h}W]rLYjGQa@WiHexXG[=;P1E[<@R4M`@N_B]mM_sPtbf|Yf}\axY]tWZrQSjG_uPo`i}[tcavOZqFf{P`vJkU[pDZqFYnEQkBOk@ZuJVrFOjB@Y;.E--A/%;)0A2-B4:Q@.D2(A-3M95L$"<)7(&<1,) %752./*1*2I?2K<'?/+D2@XEZuTC^;5Q..H%"<3+F(LiK8V<;$)B-*@-9Q;%=!b~W:(I )H,K!:W2Da3H60/RjCsZnU$; XsCa}EsShJFd+43RmANi?-HMhqZF[4Mf;@Z0A].Ec2Up=Ni:c|NazKKg3]xAb}EhMQp7_}F_xHRm?E_6XlAfvKq~RjsTQW=MN>LH?IG>DE:HN:JV@M_?FX7Mc8swj`JCQQkFrW\BGg#}2Z`KL5xsBRoDWl:]bK4pEJAQUrEe*.G!#<) fNXmB$B +bPAW$ $6Q%XtK6*)d|NA\%He0;W%Nk;?Y+Lf:?U/:P)1G!>V+A[5Sp2C`n4izn:F]Vm&`z$VQtl)Bjq3Xod|(^u!l+t/n$7L0TY\bIa^A3LfKaVZWO$wTiVoNP99I2G(? !;$=2K#P%ctBZk1_l*?JQ[ZW_'ft=yN-Ah.v?Xt%p@C^Pkr?XOdH`;Vi?Ll@\ +UnQh^t%PhdxFt/AVl^w o)K^|2^ni| n(]r AVb{ w5p#Z3@BLR\+>H`k0p~CL]!!:U+7S$4R!Ie.8SHd!Tr*Nm$DdEeVy+LoZ|&zLMm*7W-K&D Nm)p?JnH}FC_" "Qj17NG`m;[y#d0YDKjI`v=3K`x7{@m.A[%; /F$:)=r3( 6 4L>TUm"c{"=TME'Hm!=Z[w$TrZR'BHb,4P6 +7 fW.JC_-E`*3ML]kG7t+u1@5IE=E;\~PDaFdYvm}H6N0L'Ex5v,:mN<_(K9%C0Pct:\ +(F+I4P}cQq0oJ8_qk?oGZy:/K,F+E'%=%7"LdKGbB%?!+F*$C">[75O$Li42Q!A<Qp&7 )>+AUF-D,Gc7Tq?6]y=Ro'QYs)Uk$i2jD_},]g77X4RYuF_{R/J#4P+.H!0M[z>pTIj2-Ke[RlIMf@Kd:eOMi0uSRn+Un.Rj-^v7E\F_mB;YQs<]~H>=Y,Qo7Ed&Lk/SpD5N:La_7KS0DL/BK@T`-DR0EU.@OATb+?L,@H'@D^vrbztJd\UnfE^VJc]Pg`Ni^HeVRn_@]KMgTH_MKbOsqWqSb}\fafea|^knUu_YydgsMl\DbT@ZP=VN,@?RceYnk;UPF`XHdXZviLiZUqaLhZB[QCZSIb[C[W:PL8KJ.@@G\[9PN.HD.FCVXAXaDVf=O\/FNE_dC^b@YZ(C<mtQq_NncUujYwhFbX=XO>ZO]{ktyuq|t~wnyptk}s{~|troqpxgrY{l\{y_EgjFglPorXvvle{}UvpOoj`z[{tX}r@dYSwpJljGjj;^d?biOsxUu{IjoLkvNhxEaq?arZg[OuHl?c;a@dKpDgqDek-NSMknec.PW&EU,G`:Rq6Qj0Le*B[.I^7Sd/L\6Sc:We1MWC`j4QZ4RW4SW%CF%CH3RX.KV6UZ2NO7:$<;vv|ys~py|tsnRpPGeE]~_lqStX^~cKkTDcT;ZLDdTJkWt|NoW5UA^xjXucA^KA^MEdU;YI+G7dchgsumqOpRhiQoP8W8UrRlcj_j`lbphhe]|]_}[\zWlfe_b_LnNXzYPrOpnUuVCfIAjKPvRJmEolsYrKj=^H:WA?^JPp[>^G3T:StUmfufm[~n~pMnAnaZyQEd@]|XyydhwoxUu\behncf``fggiStVVx]RrXQnUZz^b_kncl]gFfPGgQGfOPrX^e4(F6LkY>ZDdjvyoqxqd\aZVzRVwVZ|Ya^SxLY{Ukl[}]^]KlJXwVXwYVuX]{_MkR;YB?\HC`O>YK@[LMhZB\P2L@KgY@]MUta`nVtcekLjSOpUy|_}_CaB^|]|okzu~vyjl}kyhgYi[l^mcd[UzLRvHrhlbkdzssobbJiME`GFbGcbZzV]ZQuMSxPBeAAbB_|a;\G1OB(EA$=<,EG*?B"57) 7=6=!3;)4.4.61FG-HD)D?/JE4MH:SIA\MOjWhn[}gaoA]Q*E<2MG=WP.GBLe\@YP:SM5PF*B>.CB+?@%9:%88>TQB\SPj\MgZ8UI(B93OBSaE[jMWgLUgKK^Afx]ThHViK\jMp`l]_vObzSWpKRkIJdC:T4SmH^tOm]sce{TmXjSnVfwNoW`qIexP]sLVpIRkDPkB^yPUpKHaD3K4(=-(=,3H8)<34&$=,&?.-F5-C6#7-$5*0B4,>..?*9K86I5CU?K^CD\?HaC@V:-C)(;^rT2H#axME_3XqE(A[vK`{R_yS('? NhCMgC3P*NmEA`8Ga@BZ@)$5*3D6+-G')D6S*A\9:V5A`=GbA1K/)4# "($<+,,0&)(0"$<,!7+-E7'@1'<1.B> +())88*(-' 70%=0%<, 7&.G17P9)D%$>$7 2)A%B\?IbE*H.?(,)D3"8(,*?[2Oo>4O( ?c[96W2>]:=]=,L,-6!0 (;)6G2 3\'%C *J)Fd3d)_yL+F6\{&r9w3Psk6Xr%Ro $*= +BR(5ks*?<{?Zd(GRVh*tCOiq=m9a}+e/^y)Ia=V ?S(?Rk-\|6_z0u>[tJv7W[#Qn00P Ji#]}3Qq&FfMo"}Q7Z @dh=Qq-'E:X=\zOp>k1Fq:r;ogBVr)A]DbKhi.COl_{&yEXs.Mhcy/Q-E>V3-ED[/WmC8L' CT&xI^u8u0AY Leiv!|/CD\@YRna$7+F:U):Y)1R!(H9 +(C&=Tl<{K#FQrBcj&?^PSrD^Mc!DY KH{&`vKd Yte+@Wp y'z,&0G.'>"'@"4'B!2N+=X/2O#4P%&C/Md>}T8'->/(9// D\AVrHJi4nM~TmA@YkK{NJi,F Il"l\{:Ji1%E^P7T)6)A'EPq=Ns3`H4 )B$NcCylFa7B^/Jf/Tr5jHdA`};Nl&uHk@VuN3RGf0~ewa:^'Ad&uVheT/J12JF9MT-BJ-CR4JX/EP6JU-@O2DS):I.AO:OWQji[unTqdDaU?ZPF`XKg^F`W>YPMf[6NBD\MLdQ]v_]w]\uXXsTZuVkiZx]Yv]YwcTufQpbHf[EdZNpd@_Vy-G@@YUSmi/JEOkcHd]E`XC^U>WN:PIG\WF[VATS;ON6JK&;=-CB4KJE[[2GI6JL.ECD^XWteVvf,H<0I?2HD8II3DDASP6J@ATJH\QBXGK_NSjWLcNMcPMgRPiSRkQPkPMhMTqUmnQlTD\Gfmr~2FI0BU)`A=^F3T>%I0BeD{tqT{>`IrZzJOm@bUQrEk_qbeUhUZ}Kh\]~Twqf^EgG=]D6R=1L3>W=VpSA_@icxtkEe?4R4VtV(F*OlPPnQ?\AEbHHeGqizikVim[NoDc\SvR_\yx>`?bblpaddkWw^Uu]HhP[{cRq[GdO@^GIjRFhOBcK8YDHdQMjWelck:[DKgTLiVA^HMnTglV~\uzkprvUvYRtSSvTss^[c][T_[JpLNpSVtYKiKQpQZyXhhKmMZ|^=]E=XF=[J?^LEaOIeW8SH2MG!<6Ee[A`QIgY2LD2KC2MC]|npYziZzh[wbXw]`cnmwuc`qosmvopgvjq~nm]p_te{o]eSonsdbSrbj^QrF{qzsUuP1O1>]=dZ{gbEjBCgBGmJRuT9]<:ZA.M=#@9"<; 5;,?D)>@&:>747PL5PM2OM&B@:<'?D-DJ6:-15:)AD561JG9ROKe`8RN9PKRi_IbQ@]H>\FQtYSt_'B8.(2-(=:8OK/IC$=69QK-H@-B@/CC.AE"67+A?;RL2I>-B7?WJ5MC3JC/F:4L=@YDFaESnOXsTSmOZuUne\wQPjBWsI[wNYrKsk\rYLaNF[LFZMOcX7HA8HE.B>:NMJ_Z?VP6MD1KD3HG3IF)<<$8:44152KL+HF3QH/JF52(>? 95:SI?ZIAZD?T8~qt{Zg~bz`}akj{bjlxbs]o[l|Xm~WfjjpVqW|b}ntap_mZTk?d|P|ciizPne{TgzX[oOVhLogQaDarRfvScuPVhC]rOp`Md@[rM_xQXqNVmMWkNWfL]jQR^DP_FbsZXjOK]CN]CGWLcG>T>5I72G6+@2':0$8.6)2&&<0)=2"3&-=28E8,<-*<*7G6-@+2I3>T;JbG?X:TkL[sS3G)"74L*D]2Uq?q_PmT82;T7>Y72Q03M2'. &:1!9/3J>2N:&>00E<)<4." #$:.':3(;32)"4-(;5-*#"! )!!5-!8/!;,)D11M65T;-I.AZ<2L3'@+3-1O1?]>0J/. ;*?ZJ0K; '">#Da=XwQ0P-?JiFQnKLgG0M09!,5#,B4.B7#6$*='-<%RbP):,1=1ERJ%1/%5'_t`*@.2'!3)% ZsKfQ7R'Mi:Zv?^}>Hd+Nm2Sq:{h:V,Fa:QhB@F,Q[6z“/?\l;ct8Xn&MMBdv#3GSku0_i,j.n'/RbA58mzD^F0;7?8;ir2_{*UrGG\IUo@Z0L,G /P$%DFf)Zz@\}DfXo\WWq/cy3Fpe;\??aVRG/y3Kk>2X3MENtO1_cVV=N>u}6I`9R@X"Si;2H$(>&90D%$< +$)F wIs?Vy$IlPp!Us/_Ge 8C` ?\Zy+Ji B{APjwD[z-[|)d:26+D>Y~HNH@ay'\s&Wl!^_t dxYs _yIgp&:W`~2;U(=]s8_[vN@T5PuEx2y'chl+Jk+K/O .M6V[}i,Nra.-Nf08W{;Fe<1LF`+B`$1;*I=]*Ih16U?_m+?("'+"&"=Wu@b?+JpFKg,51H w@s:0OLl.Vu7F;OX.@O%8G>P_%7B7KR@[VA[TLh[Kg[Mi]Rld8TKA\SF_VJ`X3I>D]NPkW]ycMjRKgNSnRQjPPkRZvb^zgTseIk`OnfUB3K6G_IRjTVmUB[>TnR[w[UsUkjfgQiU^xfb~o9QO-AL.DW2H[6L\7JY3GS0EQAS_8KY>OcBSh4FY?T_3IQ8OUQgmCW\J_aTih[slYsiZtdu}B[Nd}t?\Nr}ovzbi~wg_c`Zy[?]Ex~^ze]{gdmdkhjtsppbeeic~fd}fkjmk[yYlhxttm}|slWyVmtOo]ewe}GhfAaaNmkPniQqh^~th}g~nkOpjMmi[{wCbc6V\6Ub;[l*KWHhoLkn[y|OnqWy{GjpUyKoHlFkTz7[x1ToMorMy|eRuvOopUyswCgk@P/Ld+F\0J`4Oc5Qb3Q]>_i.OY$BK:W^9VYVtqgTumKmbVviyZxhhsXz`svaVRpAPmZ^8LnKOqOll@bB^|^`c]~bWx^BbIRq\Tt`HfU@^OB_MB_N:ZFNpY=_I?^JZR6VK3QG1LF4LH3LERqc[}lGiXQr`djXv\ab__b`tsgfdaqlea_XYzOXzN]~PqcredT_Mucm[zjqa[|PIi?YxNYxOtjh\i_tloiTtMKlHRrQDgGKlM@dD<_?EfF>_?,M8!?5#<;15*=A?UX!8;-AB5OM,GF)GD:;;;1IN#9@036:)BD@Z[B][1JE.GDHa\/GD@VP6MC9QD7QC3N?HgVCaR,GB2.!33(<;,DA)B?*C?/HC/JE/EC/AF+=D)<@/EF-@>2FC6HB2G@3JD8OH4LA3K=Vn\VpZ_}d_y^VoTE`@PjJh]WrJhZVsL]wVXqSh}i5J;@TJRg]F[QGVS>PM)<;)=;Uli 627OIBXU2KG-B@3GH(;<-@D34!::,FE9WQ-GC%;:314H@NdSBX?vj|]cpkxwjsZly`}djzSqYgxTjngv[rXv]{bpTy`pYh}Tt`f|Uyh|jxdq[ua^oJWiHZmQL`EQ`IGdCRmLQkJVnN`ySiWbwMmYt`csOThD^sM`tP[nLZlL^qSJ_@X9:S50I0-A-

    C\-Gd)Hc(6O\qHCU1taiV1ITi@^sJazJnRd}FBV ~`]vA\w=gHieIoO`y:nEjD|WzVtRtPcQi/yW~W]TeUCL>NIWJ4D<>?6766:.960,*/9=Q ,>2A1> R]|6;046259636176>886:><86GTc[\`]Z^]Y][Vb`ac`c[]^W_]\W]aaba\aWUY]bfjb]\XYhdd\baYbcd]f^ZXZZ`Y^a\``heeeahfic_b[TSVbdbcchm|@|Ea&Rq-tOeAXz4a:]7Wx.Ac\{4ZGh'Ln3Mm7;X"7STp2_y5Rn%oAh7d1tAt@l;mCx3M6L PeC`*Zv=ɚx²ϴȷͰ٦ǤsrVliThd_qiJ[K=Q8cUmU[[jzy,ETlFo|kB\A@VG;RECXO>PK4GD/C?;PL?TMAXN>WI>XK>UH6NA;PE>SG;QDBXME[ODXO?+<@.?B,>@,>?2CC2FE/GA0F@7LB:NA:O@:O?7M<=TC:W?;W@@\CPnNSpQPmNHfGJgHNiJ[uYJcK@XF8OD5IF.AB/BC-@@4DF,;?+9=-=@.@B%77#75 3/#20-,'&"0.:IDeuogxqw~_mhu6FF4DA-=9'30/:8HQSKTUFNPWabKUY>JK0>?%24*7;0:AgqxvAKR/=@6DF\jkhrtvR_XM^RtvWdTVcSVdRGWGGXKL`Q1H59Q=F^GBZ?TmSG`HI`JJaM;O<5G5->,;K9HWCEV?@S6PdDIc?VrI^zP]zP\~S^S_RbRfSxcs]kQnOwXrUsVz_dLhOUx?:a*4\"ZtEVpC`xO_zLkWt]v_v_wc|hvfb|USmFNgCh~Z`wSj~XlYgXKdC9R8'@,(A+54,E0AZD>U>1K*5P$\yAsPIj$eB`A^~;Zf}Ft=RhmDoJSr2[yU$4Q`}3r>Pm3RgAXp@ 1" 2%. , %4'%;#, +1"5&9!,Ga#NfBZl*9=PAXE_6R'C =Y 0LV^w6\z VuPn2`!c!p ~3Ur=Ys*n)n0m$@Gb-L7U6U +A`JiJiTq)Fa:Y1PHf.N(ArSr+*A(2 !8 & #4. 61$>+Yx8Gh:Z;,Gg~,N.s"PRK`ht$`k&W5O9T-E /I9O^u0dv.H[Th]o\ns0h*cz/,B-? 7N&A: %@: ; +868S2L7 < 5N:S"%&9!62F!2"+% +  %3 !1 -')6#1 9'C$B +$+?$'2E-1E/'9"%+*&*8#-A)/#6 2H1"8"3HaD1C/-?' .-;$$ %%))7+)!11?7F'IU5B/-&5!+,*'.(7%/;)$. +,:!0=#(76J,.%4,>=K/+9 .<&'(%8 +8I1.'),'73C$GX7,< $ .2F'-@H['Sa*yG>R o{PYm0  %5##'##;!% #% (^-3Z5[ Ed:X Mn [}1Aa'  $&37LiDZu'PnGf&A +$='A9R2 ""7F\7 3 +4IJe*]z9Gd"iQh7axK\rF[uLSjGQfCeyU^uHSk;nnNRf5rVXq:Lg/gLKf/qSu>VlIf~BuQ2J_w:e}AYq7Ph/Pg/]u3{GL5HCSE;=RTBBWF;5/9./%-883+(<~-M`?NivCI;8116,:559338+95<>94888;=67:?>AIEJC:2LMJMOTOI_PSUYWMQVVXUUUQ^VU[UURMSPSRRMUYTWVZ[g`e_W[Y\W]ZYY]S[]]d]_X]^_[^Z`QRP\]`]]_Z[X[]]\cb^]_ebeiZ^d8]Z`\^\[Ycc_``_d`gcdh`b]bfled\eaeid`]\[__Rb[ldjfcbdbjmr`\bd\ecbdhgwւz:m.t8}DIh5Sh"BYZs3Sl,Lf(?VC_'Db*[zB\|BGe,Jf.He.=Y Yu:zW`=Z{7nK^:lGPq)d;YrLBc=^!3SB`+Sp9Zw=`y9iBSo)^y1m=Yv&Or?k;c:Jd,2G?T/Pd?[nECV)Xj:nHdx7T}SD^"C[)?S&fzD[t;fFw[Ok7Lh6So<]yGSm=Lh/`nJiFuTf~NWo@Qi4fE`xX{[^w=pOsPtP\xSzXmOhLw\dH_|GmWgN^yFRl9v[n|]z\~^b~@@\ :V?Z&Ok7_|D[r=ι˧ɐĖuQgeMcbPdbWlbcwgcu]ujkeke_b~Ga)bmKgp7R,AYC;QF@UJBVN>RI8LE4IA>RIIbSLdT?\KAZGUFEYMI]QDXNBWO:OH6JD4HC.C@)=<-CA/ED(;:+><*==*<='991BC0AB,?>.E:2J=9OB9N>@UC:O<>R?BYE8QWA@XI5LC8MH-A@->?.>>&58$35"13*,*("63-><%43(88'76"12!10 *( + + %"()(88(89.>=4DC8IGESR_mmitu{~Yde;GG4BCWffGWW)79JX\r~`np ./3AAGUSr}zmyyvdppZie^nd^nb]k];F:5A29F8>M>AQEdtgyz{{}zvtxyawdi{j\nasvWiX\mZduciueqhRfHWmIc{We[jaa|\IgC_|W]~SQvHLp@RxEeUiTiUq`kX[|KbO{hlX`MMw9Hu4gPeOaM~ote8^*TzFbQ;_)Os;Bd(Im/hOpYlYSqFQoNQnQLgO]zaQlTD[C@Y9=X/s_gQiOUs9tWlMZw;eIhNfMa}KXtB\xKxigYjYyhezTp]{iqrvykmZlW~fq[e{P[pLQdCqej`]rVWlP^rWgy]{jjYg~ZLdF?W>>UC1F9":,!;&0*A0,D.6M0Id4jI|VUv1zXjXy4k@OOOHOxKj?CdlnKKg*@^$Rq-yNf3Wz&HkFhPp(Vz/Hn"Z|0Hi?aGf Ee!<[Il!tK]2Lo"_6On(Jf%Nl'Op&_~3<\ Np}HUz Ei6X(J"B$* ! (B+1L1'@%!=!%@*%A+*G5!?,/ +&. :*/,"9,*>2 +# 4'*% $&,A0*'?*H`K-F.+G/,H14,$;*!;'#9+1% # %>..H5(C/ <) 5'7* 8)&?/ 9+! %( 3/!!00 %!=QlF-I!-JPl;7 2M$(B+D?W3:U//I$;..F/6P3'@"?X::S60B+7K-9P*6M'3J#&>4J)%9Jc.{k2r.ICT;NrI^}9\{8A\ \y?4P(E,KLk0P Mj2Vr!Zv$[z$\~'m9k;OhSkhf+Rqn8b57U?\#6T A`Nj^x4Zt3/2 $;65*GIc5PkA+E!, Kf37S0KqRMg&>Z +Ez;g&h{'^nKXO`\mRa]ko}jt#\g+7)A'9+@Gqy7hk ^bty'_aY^5n{(6H=RUj35iA;Q1E0(ev.Uckx-\o w9JI_Xn#5M 4$%>8 ;#>"= 9 9T#!< +/Kd)c}@6$: #:/':5G(0$. +10+)7)!4/!4%O`A%>0I"4 8#?5+H$BYtE* 2-@ =Q03B#.=#$7&9#8K62I1,>'0$7#"5!$5!"3 0) ' .0B,3 51()@(2H1()2/<'*8 =H0" %3*;!#3':)9(9%  +%(&4#'#  $ //)5!)7KY?'8-6K+4C&DS5'87E+:H.&5B,9M36K/6K25J1CW>!44C..+ /#3%5(6!$4):QWOHR82--1:23894584:484<:;9:==>A>=A@I=IGJ;46WNMVXPUWS^e^VRWW\VQUV^LRUU]W[YS^UXUTR`UWVSTV\VRPXZWX[SVTVTV_\^X]VUU^\U]`ibcRW^a\\^e\ab^Z]`\\g`bb\acgkab\`ZdaaYWZdcbdffhcbceaa\aacb`bbchd_tkkd`glb\NZ`fbd`baeegebY]aiRTtkl)DQw=^t+Qg#^oFHcGcSq({Te>Uu1Db#9V;x_[x?C`$_~AXy7Qr1Hg'Sq1iG^}:vR_7a9rNmKBd'2SaKZwAF`*Nf,Zr9qNnFl@Wt)`/`-g4`4A]5 4F#9I+,;>N+CU.Sd6at;sKmDZt8Ld/g~JYo8BZ!Wo5Qg5eήٳȗruz|bxuTjfQe`H^QVkVyg~j`}F`}=f=+@;-C9.D81F:.A5,B2%:+3H79N;9Q;AZAG_DUmPVpQUnNLdFMfHRkLXqUJbN>WE9QC:QFDXQ5KF5GG9JKAPTVeiBQVbruZmm,>=,;:'68$36$25%48%38*::&68*7:(591?@2@@;HH8FC6FD1A?2BA2BB,?>CUSO]]?LLHSSDNN9FF:IHRa`fvuz9IJ.;=DRVHUY(690>?LZZ/>=P_[yo||rm{{_nirxqv[h[doctsEPBP_OTcV]mb.>15G7EWFcueu|_th|y|_k^o|jƭl_znnfmhE_B`z[PnMUtPb]KnIOpKRqPgcTrNRnJQnFJi=Oo>]KUzCiT~h`J\Bw\ht^qaJs;Ai4@f3.U"Ho9`KTziHZzY|-]2qf>Tr-Jh+Qp,_4i:Ux$=`Qr'AbQv.Pp*Hi"BdAab?9W<[Cd@aJl"Ik e>4TFb%\z>hHKl)EdSr*Yy)i7Su j8yJ3R/M,G8S /LJg-4S>\"7T6R:YBbFj(Pq7(HJf89U(; /$GQu8Io.Gl(Fi'Vy9@_!6SE_,+D.H!AZ7Fb@C^A%?'&?*5 .H.7T9'C,&C,#>,7'3$&A1+C20# " !+#%(4#+?.0G5(<2#6,!5K<1I2/H0 9 2N4 ="8!8R<3!/)#9-1H<"9,!8()B/">* <+5NA.F;=SJ9QH4(4(Z2!>(F^}J6R"-H(A&>7'A!*C#,G$?Z85P0-H*'A$10)@.";"4!;5M. , uMe6(Ng/˅HfJU}=TrVsp/l2g -'&(;-*?0!6#4,C*'=#dyUQi5!8 (?&?d@]>byDY Nh\Lj$On'Sq/(E8UHb'"<)E+B67;!; =Y Da (AE^!Wn6Mc-:T:SC_EaMiQl#F`.G +/K0M =[9WMi(Gd%Xt2Sp-:ZQm+Ge4O So)?[F^#Og/Nf)D\ &A`z;Ib!Ih#Qo(Rp%g:8X?] ^y(HKg2Ok>W `x,_v+NJhe%l.Y|Xz%i5uCvBJgEbo@,I + %+EXs?Fa*+H*H+I9 8PhF'A+GRp+j> +5ROm!A\ $@NfYmJ\P_J`BS ^o%n|)\jGV +~Gdv0.B'(3v~DAKFMJQV_it&9D6ao%6)>!2EX`r9(7(IU JWPr~+iuSb $? -H)C5 .'/F@X-2BZ$Pi.7O.CMb</* !5)4/ +@* .BQ4: /*G%A %YqL&<2#30@&*"5" +*39K7(;(3.!))8$1@,' + +!4/ #5! ! $ )?(";" # .D)?R:J[@*( -+:.$61/*!.),+9()'!&&(1?-:I78F2=L64D+.6G+4D(AQ5;K/)6,<%5$%90&: "7+)*'%+.)8"@Q6&6/@"=O/)88Ehw@sBMcAW\_t.  2%+& +>$**+442#/M 8S4MD`l=Pk$wQiWwuAo9~LR]1;Or,AbSt06Y5 ;  1E1.B$/1IFb_z,[v3.+F6M-+E$:T/7O,;T-Tj;\t5]v2d?x]ZuBOh9?V-\vPAZ6PeB'<`tPWoFp[[qBSf9@R#Mb0vYL\IOxCq?|Lo?GFCFHMJRNTLRLNRRW[VlXWYMgXI\cZVTSUWVYX[WXWXWRV\WXTS]XUXZICXYQXSZ`Z]cYX_VZa`]U]\^]]\UZZb_[ZXWZ[a_ZZZW`^aU[^U][c^Yhpcdejad[_a_ade\gdhlkidad\dggddoh^bgd[[ea]gX`O^][^Y\`cmlVYa_mtiQB]a`s>hce{4h?e}=h=\}MOuCuBXV^{A6SRo9hKtT^|>?]%Ec+^{BDa)7SKi+Yw9Ss2^}:\|8oO0R2P3O?Y&Sk8H_,D]%C\#Sm.f>a}3qB[y)i6Wi8O2 +:5B#-<2C8HmMjGYr7lLf~HI_,bzEekc@['Uo@Qk;Mf7]tFRk9iMz\uXTk:Nd7VkCI`5hPRm6Yt=Mg1Xr=Qh5f}ImL\w6d>~\e`}FWs@Nj9Qn>UsBa~NaO^~K\zEZyBqVbdFkM}`jL\x>Rm5Je/_yFoWg7ŦӛosowiuTkb_vmSkb>ULH[S_oih|qXl`Ui^@TGl}o{Xi`E[TDZSI^VNdYF^LAX?uiqbWsEcNbzChBkC|S|TrJ~Vc}4`ekM;T.KbKI^LJ]LPcTFZLGYLN`QShUQjTRkSQhO]s\]w`RhTXo]L`TUf\Ue\QfXH]PF[MI]QBWKBWMBVP?TM9NJ.D>4KF2F@SibVgclxJ_W5J@!06%4:.3+-%89/>A(6:'6<3BH.=D.>CDVZJ\a)7@#/7-;>1?@:HG+;7->7,=9/??4ED1EB/CA/A@7FE/<>1?A>MM7GGDVTPcaarq-==.==2AB/@@8HGM]\]lkCTP?OK:JGK[WRabbroZjcQ`Y:J?'4*-9-?K?CPCbocQbWIYO5D:FVMN`TN^TMaWWh^N]TLXOHVJO]LVePFU>[gZ|wvuiretarZex]qlQmN[xYSrRA`B1N3@\C:S?b+RvBm_h[|p|psp_t_aJ`MaTNmJ=W>E^J@WEWmJh~YavPe{S[qGShB^pP`n[WgQK\GK]DatY_qT]oRZmM_xQZsIeWc}XLdJ6&'=40D>*B;3JC!806%IcFo_Rq5Zz6yWEf#eBjrIc6f:`2maNo(:[:YIg)Db$^|??]0PSt/Ae;_2T$C8W2Q1P<[3S6X>`JlFkEe5,J +3P8T A^'Vua )L;]*'F6R%+HEe6n[gPQu6>a#Fh+Hh--L2O6Q!@Z+9S(;W.(?-(@+(C(8R87T;2N5)H.7.I4(A//H8-B6/"4)3&)@0(@,%>.#<,) 1+/)%')@4%/+ ) NdRPiN626,G5Q 1M7R#/J$?5 9#<2K,)E&&A+G(9R78!+1!&)3%<E\U Ti|4Zr=V Xt,e;e61L-HD`:Z?_Mn$c?Jb(6O5K-A.B?SJc!i@2G[Li%Pp1%CLf'>WJc$^w92E^$Kc*;TBZ@YTn.\w3IcEea}1wCWs!4K)A?V Tm!Pho=p@c0k3o3e%s2n-t6`%e+Zy!Yw\w"q>/iC]:Z DeJiHdPiVn ;P6I1JCW32C ;N 2E4ISigx7jx7kv9]i)HSNYAO ;J 7NViViVR#7Te[j (8DFN]g*_j'CNHTEP L\CW%8-56G =PD[2M@Z)2M . - +2(>$:(?C\&*B.E8M$: 03/- +4 B]80G!0G#" + +(<& ++ .)?+A"; :&=(A3 /H 6 $>)A,, *@@W/!5 "((5!,?(/%%8% &!*0?,'*$3 #2!.+>) $:%8N9))<(AZD7(>'! +"4);!.>!+;,( ++$3!)$-#'& ! *) '+!/*:'&4 DS>SaH&3&7'&6%-=5E! 0 #/*9 $  +#",%32D'4F@P)*:5@AO-> I_Sk%sG$7#! ,@'. ,;#$6# 5( ( 3- -D$: vwu]w,wHkcsEl:VZSZ}1BhMr)@anT> -G%4I4   +1F(%>8SLj"Gd1J C[&.9Q.)B 8R-1K'0G>W%jEqI=Q1cyVgPwPc5oP}agL@['jjsS[s6uLoC|MsC|f}>]t7Wl0Sh(~OVp$g4Q}Ev@`|0wIUvD[HhKHQ>00-2..0-!(/.+-65289@=.@41.22;7:;;;<9;?@=HGHBHUHTKXS^RgYNYZXgdV^P`fZRWYV_YhZRW\[PPX]XQVSV[RVMPTTZYRSKIYHRVPS\^SZZ[ak\^^^]^^\X\W_\bY\ZZ]_\\b]Y]]_am`kefdVZ`\UUfggj_ba`e`^Zcÿfi¾hga]gi][i[^imjflYP]fWb[f`_gjlopfgksacdXPLFLNR_aopQg剟[d{+Xn&]t0bz4KcIci4z>~Bq7b)m5?],J&C Vq:Rn6E_)Gb/B\.Hc4BZ,>U'Oi9a}FVt:Qq5Db&Aa$4S8V Id4?Y(Pe6Lc37P!H`-G`(Vp3YlB`6`~3_~0d7brNIa-6J0A7G!6H!.buGOf5AZ&Jd2Wm=g|N\wCmQvYAY"Rk9F^1D^1Ic5G^0TlZu@]v>kIrN^a~FQmB2@D+9<#14*9<-<=$26!04#28!16&8;$69 15!/6.2%36-;0>?/@@.>>*:9)97.>=(97.A?9LJM_[DWS2C=Wf`N\TVcZeog~IXM+;3+:5M\WGXP?PH;LDK_UN_URbXHUICQBFTBHXDQ`MTeRJ\F:L1AS7@P4IX?XfOT`MJ[CLaFhbXuWKhIPmPC]F:S>:R@D[GHaI7Q5kdYxO[|K~mEf:-K&A_;MjCXwHZyG\}MHg9Ff7Gg;;\10R'7Y,QsCCg0fScSVuLNhL>VB8P?3H8$9&6N5b~[]yQVqBTn=Uo>lU]wG_wHUlBPjBJc>Le>YtL\vK[vLYsI^{MeSfPGa/^yFc|La}LVnE[pKrejZgXo][rH^sKbxSI[=ObCSgGZpMd{YjZudyzjgSMjfDuOvMe9c8e~Y\nIRp.Ww1[1_1GmJoJn Fl7[>`4UA`?\He+Qm4B`$:XDf%)M3WFi$?a/Q <;7U2RIj'Hi AeCh{CxK3S0L;X Pp6)HVt;fAYx6]zX)3L:Q$/H1L%:.,63"=!+I/- +)-6* :-)$ -( %3!%<'*)A/#:%.%/1I/#=& 9#<%+H01N6,E0*!8(+ &1%+>2H^L2I7 7', "(/%,) ,,+$*% OhJ7P4(A!%?&@Fb3=Y(*G:T'"=$:702L/3O/,G),F)"=""9"1I4+C/ 5$2F55(? [tMhU(CTq8nKXv0`4Pq$Df7V>_WyWSJy@1 +&,$)0"!6"E]?Mg:Jb2:Sq7Fφ(&BZYH]YqXd4C^\u,MjaA\/JVr+Ki$Tr/Aa8T6N4IVi:?S"-A GaHbPn0^x7;T/1:U:W;WGeQn)Ga>X:SDa'6R <Ie)Rm18XKh+>]!,I/G :Q>V+(A +CY!2KMg/)B &<.F 5M Rm*;V9U +Rm B`5OSm`y.j;7P ?X[v(Yr*Yt'`}+k1ISqC\}"l2a~&<[Yug.{CKJjXu.Hb) #+14O$1 )A0J!2 $.H?Z!,J9WEh6T Ba;W Qj A\3I:Q(?.B-E3I0F 3 +# 4F]74J#! "!/ 2!.%8"(9",>%-?*#1$!$,!/+=)$)<'3 /14  -+  +&:##2.)9/?"&5" !)%"  ! $  + +$$! $'$3!&4" /( +.2A&/AHY1arF,;8H ):u\Y&Up=Nj6dh|SzJd{4{MtI>V^r4mBZo/Xp+V{Nm@gBqKzSw[RzGXSI4*-5)/1/'340013./;9:95<5,&-?@A;;:71=I;BHC;H?<=>CHHMGKSDGVh>AED\Q]`X^XkGOIdR]ldd^b\^YSRXKUZNV\[VRPQQQTSQPPVXYTR]ZU^][XY^]Z[_cRY`WYfYV][bcek^^cb_dgchcb^eVS^`gkgkefdffih[b\defbnltpy{yjhg]fd\_ejbWThgb\]XZeigde[fh]leZl]HHOD@R``_XOy+vre{*lA^t1[q([p&~FVm,\wd~%uPhBXmIOfASmDJf;^{K`IlRcFjNjuXuX{ZzWxRf@Xw3d>fAqGĆɘӠԥ̞ڳˬߝк}skrlnl[q[EXDAX=E[@LbF\tYH^FH^JXl[[p`KbSF[MNcRNfRE]Fkhg_UrJG_9>W0Qh;w[Ym8Lb*Nf,Tl2WZJTЇtSLd9XnKYpN\pRYkP\lRfw\`qVez\]pQauU`vW[sUZqWJaL>SBO`TOaSJ^KJ]JI\HGZHK]MEWI6IC2E@0C=3FA1D=7H@3F;;R@3J8=RDEZLDZK;SB7Q>7R>;U=+::-<<(6:$36%78+>=+>=#32#34*::/>>3BD0??0AA(88&65(65(55'66#33"55"5535!48!68!66*@=,?>1CB0>>.<+>:(75)73*&6A>?HC5B;->84D>,;96DB3C?7HB->5*>5(;0HZLDTEDQAHUDBN@GTF~ϵkfRmM\zYOjMD\ADYC;Q<=S?7W,TuFm^PqCA_:HbID\L=QF9K>)<)SgNC[>6P-U-=V/D]:MgBB[4=T0;R1RlJmfUpKUqIj^Mg?ZwJkYiSrZfN\xFdR]xMhYTjDRhCYrG`yLuao^xeqe~Qs}cOZuE}h^yIa~Or^XwDWvHSoO,I3)D9!82+.*'0D:+?.BZ;\wJ]{AwURq0nMlJoJeAhD]:Rs/kCwOaQx"ChLr#X~.]6jCBb!4Q'D 'D*E ,K@a \Ux9Db'<[ 2R7Z9\@dS{%Jp!Fg!;[6T9X B`-<[)/L6Q&?/K0M6W%9Y':X%/L:W';\)Pt8Kn..Q1N5Q!5P)C2JK`.:R!0I4/$%2'?'66<%;'- $," ;/8*(!,' +$%7,*?/ 7$,.%:)( "(2J33K5+D.7!"<%.&0E6%9+/%/&"3*+A2$)2&/$, % +-A.-B.#9%!6&"6- +.$':5 ' 2G/!93$=F^72M5Q#2M#(B!5$<*D'8/J+$? %?" 92!9!+!6#-/)@Oj>y_Ba(Sp7wVf>HhVx3'I +'G oGKp-JfQEL#4  1-$*-0I(x|\E]"BZYm*s3by6q;F]ZJcSoHdI`dz.B]*D=ULf&3K9VD_#86S/G&; 6I.B 2H3LLh$BZB\k>Ka1FG\Qi!|ITm 3JC[`w+j9R`x]uFd|%GYgT5j$SrYx\z#o9Xr-3L *B4M5O#= +'D )G 0M?]((C +0HIa,%:!/?U"Vq$D_GeA^ i2Ljf0b0Wx$c,r9HcQmg.h.So!\t1 "0EUl+&@&@ *D69#<1 )5P 1lpFvGNjKg2Rk,DZ6L %71'> 0I4M,A5F>P*=8I4D $72BBRi}6L_Ld?R@Q EW}0ELX9C8@YaBLT^`m---@ +[l*L_J_B\Qh Zo)7I +FZ!2H$< -#3 0- *?'= 6)6 )A 4K$72B )8*;H[0+ #(+)  ( +dxR)/C$3+ 203%<%;3/2B#AP3 +   '!/'& 2AS9YkO# (&!(*<)-+'1$6$"2!&)"YkGRc5HZ'~NZbr@Tg1Vd.fsAl%'FR37I[r>cr.n}:Wi2_p7nA=RH_Sj*Yj02? %( 54,-!.5G,q9aCc8xNf=_=O1    +:M.9T(5O /HiKEX&:ONg2":4>U-H`/D]%=X5?Y?V(axS]uME]3A\/?Y)D_.Ph9d}Nt`.J>V)Kb:G[0h{KqUc{FoTIe2]yEZsA7PrQoMzRZf};}U=QlGY}QuKyRnJc^tOoH^Xcv:oDWLt-ZC?+5*-%+-8.;/402/=3;7;=7?499298<@:<9@C=DJWKWYW]gVb\WXQVTWHYYYVXVXZWX]a[[_a\`X]ZVYfjgg_[ccdghdecffh_UJ^^dfegh_aodlmihle]aYdR\chg_NV_bbXpda]c^f^c\W[]^_`R^cnj^XY_ZT[Q2SF.=E5EEG2n!|.Aw6Xn_u0i~:k7Wf:WET?Png3wLpJZw;,H0J *B[&Tn?Le9ZtE[uFZuBgOxaRlvBcԋƀ·ψЍˉБ|Ŋ‰ɘȟͬæ|ּצkzrx{sevmŮVmVQhEYqJIb?b|[h_ZvTXsPC\7:R+CY.f}NcyHgBY%ZrBb}SbxQl[iyWftSlyYj{Yl}Zl~XwfzpXmTJ]IFYFJ[LN_OK]JPbNNaKZmYQdPGYIFXKCUH>MD9NF0F;7J=5J;5J97M89N7H_DAY>=V:6O2:S5@Y8Kf>Oj@SlCNhBVnMRjJVlMbv[K`O9PF7NE=WK=YI6OB2I;2H<1E?0C?(78*9<)79(68-;<.=>*9=.=@)<>'9:'77,=<,;;(77,9:0>?/=?2@@-==0@?6FE)=;+=;*=<':8%88"5522(::(;:':;$981ED6MJ3ID3FA.@;0?=,<98KJ9LJ9KJ/C@*<=&8:+>?*<=$75'76,<91@<>MH5E@0CB)<:'959LG7LD5IA:OE.A9+@6EYIQdOCU?L\IJZFJ\F?P::O/5L,E^>@V98O4E\?JbC=V4'ALi>lZGa<*B%4J22K,ZuN>X/1J%OeHWkVau`5K3CZ>Ib@^{Sxja~U4S0.H,7M:8N>(C]6xaoPjJZv6rNgEmK_`?_;Y4Q5U=a"?c#:_Np*Ceb<}X$D + = >]0)ILd?%>-B)D0M"+L*N=b,7Y$3WNs7+O+O:\%-N6T&@[+A\1=W)?Y(3O=Z*.J".3!"7!'48&D&)H/5!0 -I:2&1(' !'+?./F4-A2 2#.G57# *A12!$;+"8,/"/"*B2'@,5N8(@)!8",5#1H68%"8)3'!4(.")'=04&.';.(=,6.G*XrR+E#?Z6HbBF_G11&*>1 /!PgRBX;//';!1.5K.$A(":$2"2"+*$; 6Q'yf>\Roe38Q 9*?Rg0@U">U>UBZ ?WBY 4M": 8OIe5B[*]sD3 '#)?Nf0Me,H`$G` '>+A5J;PSj+Rl08Q>YiGEa#85K3J-D*C#>$?)G96Q;V">Z!4M$9bsAXjVAZa}58WCb9X NLiFfJf%Us2g>f;Ut(@]Pk Vq$Jc\u-Mg!7R A^`~/l5Tqp3q;.F:Qg{=6J Yr)Mr?&4D%7!1**@ %Ql+D[,&@;WRm(9RF]>R;O%;/F4LCV$+= 7I)8 08E +FT6G,?8N9MDX\n+.DH]DZ;PYkdt+Zh)GW9E L\*#7`>S?TOc;P2G +;SF^H_!0)?:PC\,A[-#93J$%:(1 +8R& 9-1 2F"0nM%+.%(,!2/&+""   +"%.<'" *8JY-KW(DR"2> >N,71> )(P\.MYn6Vh[p'fz5GX:LNd"Mc\q,\N`!:KJZ:CX54$<)#$ *2 Lc6D\,c{EiuOoI|kvjlDHa Rm,mEMXSY}MnCq?\y)s   "0C+/$8:KCXMg);U!90Fb{oCRn*lMhIQm1qLzPlAvPmFUh*Wg@q0'24(%0&.&0-.49300-7=8:5886>=49@??BAA>C=I@EHPO??HGPM9mz!M\@Q .3@MZ%jw9ZP\O_Y_ZabcWUbujbdVZXJU3>[IQF>cOJRRYFH| <`RURQUOA[]USROa\]NTW]XaZZ]Ze[]_]SSaSPXcaWX[a]b\Xbccdab]bc`Z[^[SOc_^XOUWp_djefaqv`YbZ_Wiobcam^TNQUYaZ[cegcmV[RS]Y?|.3?<74E{(86Du%F?__x"Ofg~3j2t5@4JDOYgRqVv(Cb 4R3O :U+@W2?T/0F H_6,D>U+/F5J$+@4O&;T,Ic57Q$=W'>T'Lb62I7M6M=U Tp9Uq;Oj0@WPh'`{5]x3tM[w6^{9oJkElGAY!1F1C9J'4G'K`?Je@7S,=Y-[xDiMi5A^.^xM[tHMg6cMSm=WqBv_qSk?LfpLjEhD]{7gDpL^PWqӃw}zzσ~Іц}̈́zywȢ߿ຨΕ{s˔Ǝ~}ЮqdsYjjRQm@RlCWtLFb=E`;UpI7O&>W+\vGMg7Ke5D`0ZvOgE8P0:R3AZ7D]:Lg@Ni>Uq@[uJZtMQhJPgIUlN]pY2G9(>9,D=:RFJbS:TF;RF9NB3G?3FD/??-<=0=?0>>3AB,<;-=>.?@,>?#56)::0@@0?@1>>/==6DE0>>3AA.>=+;:+:9-><+;:-==->=,<=(89$54,;;2CB*;<,==3GF5JG7MG4IC5HA3D=.A>3FEA$67*;<5DD5EDAQM5F@4EE0A@,A<3G@8MD4I@0E<-B9)>5AVESfOMaHK_FPcIObENcDMg@ZtMOiCHc=RmITmP\vX3G';M3\sSeWTnFRiKPbOhyjrs`s`H[ATmKg[He=6R.-F,C]D>UC-A3'9+,>.:N:CY>?V;3J02I/'>%16Q2YuT[zUNnLFeCOmJSnJ[wOZuM=[77U2Fc>@^6Bb8Fg:9\,<`-ZzIeQZvHZsI\tLa|RQnB3O%1L$@[7RrJQqGIi>Ih;Ih<6X+Bf7MsBLrE@e:=a:GiE6T65R:6M@&;5!1/* 4J6Gg+Qp5Rq8]zDMk5Mj5]zDLi0Ol3iFtNY~1i>_1Pw&['1K4: +97V<\Dh#:_2WDb(A`'1L1MFb14TKn4.QAa#No+Yz6Uu5<;+K$6#<6&<1J$"@"B,R!-TAh-?b(Hl2?b(bN`M< -J1L5P'2M!/N8W#Gc2Fc85P+1!:*D%:%D"+H*">$%D-$C.%C3.! &)#:';R.3"/"-3%#:,5(3%$<,.G61J76 0-!5%)>,%>* :'* 2'."1%0$#8,/&,;R6'?C_83N%.I!+E%=V;5 # 6"1)0G*6O+G`;9O+2E*#;/5M059T,>Y/0L"cVJf>$A8S2=W:'B&#=%5 +-%.=V6Mi@@\,Oh97TeIAd(8\ >\&3P97U0Ll@@ZQm^l*k*q5m@~b +* *6 ':,@(?9PSo(Oju4|8f~"Wte-mUs"MkPf4F`Ec!C]Lg$>X8U1L7WxV_Ea8UdD\w?+C 6[u1Vo..HhA6PPi(Sk)H^fz7i~9@U/Ee>Tp't9u7o2ax$Bi{%P^t&jlx1]y%[u$F_,D5N H`%BU"&8 6"98O)@ 0G2K #+B(C#; ((*:.A)?3LF_*=TLd'6MDW @T30F +2J9SA[4P!?+C 1F%8&<+C"<'C,H(C 7OCY&Sn5cyE%8 "7 >OH])#:(@ +AOh2=U?W:R:UVq(yF_~+Om"D_Tp%IdXu4Db Uv,Np'd:6RF__u-BZ9R8P8Sh?D`KgWq!!<I`7M5K )A4K;UKd^t&';$2#L]1Rh0@Y'= + !&@Vp+:O8L5G'= 8P;O3+< (8CPN]"^l5R`(BU8L8L#9.A >N3G?V3KVl'z?.=*9;L#7f{?k?Hb:VZr%qAH[.E /6O(A,E 3L4M0H#<7N"'<- +/0 )@>W,-IGa0) $Jc+`z84N9SJ`&DZAUM`+!3% cyO. .E5O'B7R%5O(Oj;BY-(#3*:!/)  ! '  #0=-" - #$!2 .-:) 2B/2&;$0@Q?,#*3J3/*.A/*?%3D+  +" #-(#**1 vek>5>sZ$5 "wK'ryM+5$1,% "6 :M5"6+*8.& $   ! $ ( &4B(7Q\46DHS)cnC4?BN/OX5`m7iu3o2z=Wl%qGCSPe$@Tl;oA}PK\ Td/GO+~Me3/  svzW_pM_x:Xt6zWh>rF]va{:Ng&d~;sE[q)dy/f}K~Km&AXt6~Da(Qn6Jg.b~ImWqXI\/mOpPqQUn9Jb1`wGUl]r;j>CA:@;GGEAI=C=FZL3GOCJGGO;m}&BR2A3=GP2/9cp[*9U+3L%AV1BY/G^3K`6K`7D[2?U1$:)?/DJa85O#5O 7N BX+G^0AY(E]+Un9^zGC_,Nh4E[$Rj/Mh(Wr.Lh%Li&Ki(\y8h@}Uu2J)=3D"2B%,B 2K&>V0Hb6Eb0w_Kg5;T)Mf?E\2YpCYtGD[00HAV/3G"F\4RhBQk=^yJ]wDNh3]x@Pl2wYUs8]z>oH]}8oMbJMh>H`BH_EE]@LdAB\9C^9NhAhXlXqZjQ]wBmVvpdCpKoJjFOo+\|9fDzQl<[r#Z؆րw|ytut}txtoqƀˍʐˑȎyylwpycÀ~~Zho_|֯uhbafJcLQnBD_9Db=LjD{nrA]2LhR3Mc?Kd>C[7>V2I^:Ib;Rm@YtDYuEZtJ^uSWoRMeK^t\FYG-B8&;6,B=3K@D\L8P@:RE4I>3F>4FA3B@-=<1@?4CA5EB/@>&:8#86,><0B?0?<-<;.;<5CC2@=4BC-=<-=<+;:3CB5ED/?>*::)88)99(89)9;-;<1AA,<<5GG2FC1C?9HE7IB3F@3FC3FB1D@6IC8LH*<<+:>+>@,>@%79-??1CA6EEFVS7FC5CC/?=6GC7JD;PF8MD6KB-B9+@51F5SeONaHFZ?PgJJaAVoK[vMPlCLhAa{Y[tV[sVNgM:P92C1FWC?SPC;N;CWB?V?9O72K0IcDSnNWtT6U25V/3N)3N.@\=PiJHcAFb;=[5C`>)I&8W2Fg<@c6Io>se[{J?^/8W-<\1@^6?]4LmC>`8RpLQmLKgBFb?FaBA^<0P'FjQx$Ov%9^?b@`=Z!+F&A2NJg6Jd6F]1+D)CkXXuDUt90S:^9^=^%5S 5R%&@:3#D9Z$8V=] 2Q'DId7.K% (=$?)A%='@.L'*J"/Q"Jm;Rs>2SBd,Ab+(E"?3P!?\0+F+H*H7T#Ca.>^,Ba4/M$#>%?/J(.K'6U0,J*'D($@*6#, +%-2&3K<+D0:R=*0#0%':0 7*-$;/&**,!-!,!.!)0"1"#:*$:+*A/1-0+?1+!%("6+!8*+C2(>,7"8N29Q2'?/G"(@+B$-D).D/  2G0hcVpNMfB/G$#850!85-!:,F$*F9?Z3]2O 6T 0N +H/L3O^zA7S'D 4Q.JKf15P;S?YF_ 9PRl1*C Nh,1J 9Q@X,B9Q=USl-3M +C[cy+2FG^by,CY3H3HM2p/p4Qn'A=XE]BZ.A1"5 2 +Kb5)?"8 5!8/ + &*B(DU 9K"54/F1G-DG](Lb,@W Yp4Rk-Yr7B["'D2O+D '= ++A#3 -*=4J$.EE^,E^.#< $: ":5K)?3/3HDX&AUNc07K0ET;Q eu.Nh?S/C+E,C8QIe0-I)DCY,1I)')$':. )4M!3P9U"C^/4 8 (#3+:  %2%  +##  +,-)'=*  )+(3%(!/0*!3"" + +@WB#<#*&0C0! '4%*"*'    #-7"0^n0ukv*=u6= #6, +(&6-"/!.- /> p5E+ '   +"3AT`;1<6CIV+[g:huAJW^o)k~1l4>SQf+7J uL^r4Rf(Xl-Oa%Pb,K[+gsDږ^cNg0!4 `oI˗bo[r/b|;eBqOiJpQSp/We~=e>:S\t6oExJSq@Kb{EWHf1aw-4lsY':'3.;=M) " + + +&0AOe!|DkC]zn%w(}%|%=~5Di2qBh=a>mOvZeFZy=B;G?GCBAH?>@BFSCIN:IFCTQOFD]pBK]$1 ( '5=YYwCXZ^`[Lv;IN{B^aQWS_flNIEbgPJ{6DQ@DMYV\NDcdYb`gRX[UaT]]^eb\W_[Z]cU^__gagZc\Z^\d`edeZGc_d\Wa[^WWYX[PGY[SQ[VLYXXW`dae[df[_XVW\Z^_XTXTjab^igjecbbX`fdP15M7:KIv)}18EHAGKCUT|15K:>4jfJb;s'l!AdZt5C}>IhNn,:Z "@9V)/K4JG^2E[-Si<6K")=7K),B#)=-C1G-F!; 4N:T#;T$:S"5LF`/Mf6B[+7N3HK^+sP\Id"}V`z;Zu5Lf$b~-H9T$2K Mc>?T-ayMH^5tdCX58J,EV7:O-UjB]tJ@\-To?6QD`([x=hIlM[y;Sq0]z:zZJf4*E/H*6M0C\<@Z5JdA@Z8^wR`{QcPu^^{EUp=mUQl7~`ySvPuOiCf?]{8^|:b8{Lv>\ebtsjplm|tvjrosy~u{yponff\Y^a{YzZi}WW]azQjr|mYR|H^vgWuDLh?:Z1Hg?WtNLhBD`:=Y4A^7Hd<\xHYtO=L]HI\CG_@E\9Kd;SkBC\4B[2Og>Lf9Vr>_{J`}L^yRibD[BL^L`sa6H=-A;*==-B>1F=J`R@XH7OA7LA5H?3G@2B>0A;4D@9JE5E@0D=+@9,@=';9&:8$864GE0B>/>>/@=6DA3B?7EC0>?.?=,<;.<<1A@/??+;;#33+;;$453AE+99/>?+<<,=>-@>-@=1D?6HD1D>8LE2GC1C=:MG7JF7GG/>A%79);<-??2DD6IF3FC9HE7FC2??4C@CUO9JC9NE6KB3H?.B;1B;0B5?R>SjOLcFSjLIbBVpJZwNXsL]wVG^A0H/,C,5L62H24B1@Q>FXD?O=:L:.@.&6('7)5F38H65H2^qXk}a\qMp^xzjxxUkVI`FJ`EVoP^wZTkS?XAE\IAVH4D;CRI6F<9I=?RB;NW8;T7BZ?Gm=<_<7Z?/N9,H3&?-."!5.'7-AW>D`6UrBZyGSt?Y{?dELh3Fb/E`0Sm=Lf4Hc.Ok9Lh4Jk5Ll9Ff5Ff5RsBOl;;Y%a}Dd?f@_6\2GnEmOr)?dJi.B^-$:(?8R+/H'A)>"7- 2*%B6VDf)=a#3T.K4Q".I"8Nj=Lh7*GLk2)H =Y%Mh:=W/[-?\/B_00Q!)J=\26V,2P)'D0N)+J'5S0,J*0M39$, !1 1L;4#30 +! *!   "(",!/&+!%%'0&,!$=00#2!*A-*C- 9#&<,(-%(,$%:-)3.$<3L,2K')C 9$ +! #<?X<6S.?]89V10J'.J+&>$3 /E45!-+#: =U;/C\;8P-9S%C`/Gc06S*H8T!9T!6QTq2ǀMl!"<Yr2A\\|*e)[}Nrq]x2baz:`}2oD]# &Ni.i]y76R JeNj*Ge%*H 7U,L 9WDb!Mk-Ol2Om,Yy1΂oI@\ Id-?X'(E$A : +.H.FAY'C]$/IG\#+C %> +%= ayB=W.IJe Qk$Zs01J ?YMb#=Sn;Vj%8LXn {;AT[pJa^{JhMie;Ga Id&&=%8 &8$6$6-A5LMc4814 +6O!5L'<DX;M2F':!5&<&; 5L1J5?V%Jb+6N+D %=1JsUhIBX&3F$ /. 2$9'=(>&;2,>[Wv*g5FbB]c2;W\v19RSm-Q{>e{&j|#l)J]TeJ^4JQk&H^Tk"r>Qa-$9JF[0D6I;MJ\,)2-9KAQ;J+0*?*?4I(>#6 ,<!"8*?2I +[q)XhVg(4B 1?T(Oj.A[2M B[CV!&9 #: +,D>V(6*AAY+&;)- . 1 )%5 Ec,;Y#"? ?Z.$>7SQp(Yy2^z5HgGb(?1IC[$.J6QNn.@\$Fa1SoEB]3(D&B?\,-E';4(7(9%4%5# + ' /    *""6"&9%(&#/!&-$2 %%';P<'>). +  '  dl\gs;RZ)UpxCX`#}D{GP[c2>,5QA"%Zbw3tI +**-;7Giw=jxFNY7 +0>'&4# ""0 7B]j>HU(FS#P\.Ua0Zf.1AJ\L` FZH^$.A_q9H[#Wj1?U6F9J4DMGvN}LvYRzLSZaMh+vXnO^{B|\vSg@[`Mf)by:u_g6MlmTY{H^iEpJzqlDZ0'TkLyF]`z$J/XD68/;/8///+35<=7=A@@DNEGDEBGESI5Jy/SIJPP\~93A#* *\i2a[tDbU|H{IWYP[h[^YMTGBXO?Fad?hs5GE>}3@hHKF[igWX\[iebcb[Z[V\Y^hd[^WYRY[`\Za\Zcabgmsld`b\UUb^^_]_^^_^]dhZ^`U[^klmqgmhd_hbgZ[^Z]Z_m]edc_M\vAKcWWccgbfe`R=C@:5V>z.`uLU=~0>Ht+Hu-j$g7A5Fz&}*/u%v+|2z3x4o(PjMZb~%Om!Kj)Zy>(EDc2+G-E8N @V(@R(4G :Q%Qi<*B8HHZ-ay@Tm1Nh(jDVp0Tm/jBpJ[x7nP}f6I%-A?T/CY3.FSm<`yJ?Z(8R#-D(>Of?&=/E.C5G)>N2>N19J(F[3Ld9Ql?A[,@['Xs;_|ATo4dCmMaAtWfJJf5E]7/ +?W/-F;V,C^6>W2WrIRkBRm=_{HdLZuC^yFmXq8qL~Wb~;rJ~Uf@kDb}7n>vByBUhahjjqqasqsz҂v{uweׇVTeXwR}^zZuVsUhsUxW{W~[{WqKbbtMnFvMpBn8UtCpCnJLi3B_3tNjBFa:LeC>V8;V3MgEC]8Lg9Qn;Xs8rGvDdliλ±ȼè}|ixfyw{ysmj|g^r\\s\RiPUkRPbHThMQkKIc?OjAQl?Pl=Qn>Tp>]xG[wC_zJl[UmKjf2G>/D=4JE/C>+?=)=;9LJ2E@3BB2B>;JG4CA6FC1?=.?=,<;.<<.=<.==*:;)992BB/??,<=(870AA$77&98*>;%97,A;2FA3F@7KD3HA4FA6JA6HC4CB.>>&7:-?A2CC2FD3GF4HF8HF5ED3>>/>::ID:LC>RG5I?3H?/D;3C>->7+@2E\H^w]^xZRkNOhIYsRNfI&:!.A-3F41B15G58I7BPAN`MXhV?P=:J90@4(3+&6,,>11A11B2?P==O7AV9G[9ZnKYnKh|[gz\WmNLdBc{^?UW6e_\zZjgutObPWl\\q\RlOEa>IhBY}Qd]MsE\PEl:Fm;Di4W30R5)G1.I31J6"8*)&8"skQl@Sr>Qr@Jk8Po8Sr:Ig5Kf94O$E^4Jb7B\/Fb2Ge3Aa.<\*Cc5Ki>\yKKj7Lj6]z@[~8\6Qy)W~-\2Mq%Ko)1QOk5*C!6"54I*#=62G 6J&$8#7$<(8U!:\3W0O;X&-K7P*'BLg:78Ee+7S/JD]57;T7%?$9.67 ><-H#6S)6T#2O 7R&,G,G7S'#>9D`3Fb45R$/L,I%A0M%6S+8X2 >/L-0J-/I-2L2,G3 8',5%-7$&B1 <)($<-/F<%   $" "$/( )#.(%-%6$0H86&(@,!;%+)  #+&/$-8"4L3/ +%B\:-H) :$>!@W=2 "$9'(?$3#510>/%9"11H.5L19P47+C$+E%*G%.L)&E#-H(*C' 8#-,-* ! &*-F).G)+C.KQm:2N8V C`-Lh52MZt?6Vn>i5}M%B3Og=Jh/>bl1Wv'/ qRe6DdwJUr>Qn4Yw8Mj-Tp5^yZ'7U#8TFe!~TIf$'C 4O+D%?#<[ +C`Hc!C\7P":)<6F!$5#5 -?$7 +Wo?/(>:R"5M.FK`G^qF.D #6 0GF]*G_+$>5 $; ,D!9 +5M8P0%; +CZ*/?'#/$3"3*$6(;'9!4%5%75J%&%*;$4 +!00@N_*0A)>Nc+uNQm!o6Mk'CE_8HbTm*Ja(=TB] H`%Yq35J Mc%CXI^$J`&Tn-Pn+;[Uu*KfQnUr ;VJcF^5M +ay2Wn"Wo#(>G]VkThAUM`D]/H Ia#1G 7L$7 " 4:N +7L +3D 3D;N .-? 0B7H#5&6 ++:&:# 55I 6 (%(< *> 2AW-C?U?N k +8R(@ /C +? /E +!-A%:(<,'=' + E[2;P(3K!?W.7 6 &F )F )G11Pm15Gf#^}3CcD`E]#:QMg2:W (F Wt9*F7 1 +#;66 &  0@%+#$4&3#1, ")4B+%&!. -*>(*"&!    ,&%  #/0/'   !) +8O`"k|-s1SBXlz*AQRa@Ojx=^i3P>RE\#OHZ Qa+P&?P%Te;vZSj8|`mQG^-?T)I\/L_,avC[s@ZrCMdC?EF?>HCFF>Ro/CROHKVe[iHVxN|ʅؑהf{OZjQJBR_O\YYTVVWTV[ZXTWX`_]^f][[`__h[acffVOSZdZ^Z[`]]YUZWUed^gaZa^^bmjhededcbggeidbaf_^b^c_WYUWXZaQ`je_^gdfjgZH4AP7J0B:t x$-62>y4Cx6ABCDs8GfOm/Kj0[zBkR:W!5L@X(FY-O`9>P+.A*?.B6I%7M%=T(A[*Rn9kR_|DdJ.E/EBV.5L!=T(9P#.>AS)4K;SRl-Rm+Ke$Ic#]w6Mh&iCUq5z^0F+A;Q(4K!pZE`/Gb2iU5N$0M8R$B\0oZhMrTjQ^zH>Z)Fa5Hd4Mj:RnthCkDa{9Pi(Si(jEhBc{:~QzHl9l\q΁Մsudktt́upruvxYac}TtSwYbKcN^GhOgP|bdnQmJwUoNoLqLd@qMsPb={NZIsCtGWbB]zEKg7hXm`KdAVnMMfDCX6YqKQm@Sn?b}FnD`]h̴ʹŮ~rnrmjdMfHVmOWoPYrR^yRYuJ[vHTq@YuD_yHZuC[vFk\ncUhPnl8J:@QH>NH:KECUO;MG1B=;OFI^OF]K@WC?VE?UF9NA9LA4H=6J@3G?/C:5IB1G?/D@+@=)=;-@>-@>,<;0@@5EC>.><*::*::0@@0@@%77(<:*@>&<:&<9!64$;6+@:4G@6IE5IB=QH?SI4FA5FF/??/>@,>?4GF/DB.C>1C@:HI3CB*88-=91A;BSI=QE;OE9J88I:-;0&5,*<2.>35E9-A50@7(91*82)81DTIL[RCSDEVD@T`75\,3X)0S#OtEGl?<^:>`z/keMF`5Lg;;Y)@^18U+Ba9:X385O77Q<1I3(@( 7/EQkATpB_}LKl;Gc5ZvFSo?@]-C^3/I"0I"9S+[,Bb3Cc6Lk@Kj@B^39V'Nj9Gf.Sv0]8Z2DkDlZ}5Hl(Jh+-I$=#6%8&;&>9O(K`85L!+?3F!*>-EJf3Tt=7X!)F+H:63$?"?8S"Jh1Ok> 7/C&324%=&#;$728:8&A+E!+G*E+F+E"=0K!(D8U(!?3S$=Z.C_62M,26,F&3N/#?"$=%8"'?*2 + + 1 8(!9&'C/&>/";,1%0%0()&    +,&$!#".$$9+- 1"7'-) !' %-(=W9>W44N-'B$40(C(1L2 $<%E]D",&+211F*-E(2J,$>%A5O.+J(/M+/J+)C& 84!.),MdQ+1(A'9W7!;WqJa~H`GA`'Hh/0M.J7P?["Nm(f5GeXv-D` Hf"Sr'UyOt8JMk%@[Yy1Su#uFZx92O:W1NHe,:W-K2O/LDa%pT?[+%A,G*E =<Ol.fC.J3M3O".H 19R(@V+U 5OIb )#5,> /@0C !3,=.* #8&>U$-C2H,B +4HE[;QwC_u)f|/@V cz3W^x0\v.Mi#>9R!8;R&= /5%23G/ -?5K"%<9S%$>&?'@/J"<Jh1Y$,E-J<Nl3@bA` Hc)3M38 (E)F.LEa07 %'>Sk=BZ'TiC ++; & +,%)(5 $ +/:!- +1;$/.<(5 0 ->K_/FZ%G[,;L CV';KK\%_r6fx?J\!}N}P[o/Xm,sJuJj@wJ^}XiIAXTm3Tm4Xt;Ha)C["]vwkJHa'vQ~VsFnf΂`U}LvDWt(_{0Rm%pD^v<\{z7Fh#y3j!y-~/w'rfq/v%z-~$.x/f-h8Wv/^}:Da&Jf-Wq>]t?`wAJc,e~L^wISi?BW0Nh@PhABY09O)>U.M`;HW3Rd55;;@DG929>>?B84K;z/;q4B0B3B!%8CX3J^90F7N#/I@[*:W"A^&Gd.Kf37M",A?R,:P'Qg:)@;N&4GKb,Rl/Yt1Up.Ng&Y`y9Rm,c=\x9Hc)Qh6\tCIa3,Fwac}OC]3RkDE^7>W0LdV(8R"Wp@VqS_PyD_hmgeshqlruzrx[j~7~WuX]JgYYyM^Q_On[jXhUoXrZ{^nLtRoNXx8Ss3Xx7Lk*jEzKJP{FPzJrEXt5Jf/Hb1VqG>W2SkHSjFpaLb<@Z1E_3`xF~YxJUWʡϴʵ±}tqhbxXg\l`l]o]lXeRlYc|Pe|Qf{UyonkUhVz}QbVOH8J@@QGE[LPfTAXDAXE>UDAVH9MA:ND9ME2F=2E?.A<)=8(<;,@?'<;3EE(86&65-<=2CB2A?3B>2A<0?:4CA.<<.=:.<:2@@-=;$97*?;.>;5IEBUN;PF?SJ2E>4GE0BC1@D1DC/CB5JF/D?1C?;KI2?@+99/@;4EOA5G7;L9FWCOaKDV>9K5>O<>P@.@1,@21F72G72G6>T?7O51I.0I/4K02L-?Y6=Y3AZ7AY83H+-@*1E1.C40H7+B2+B2,B54H<4K>;PD;MC2C<*:02B8%6F:HYG1B/:N8:N4KbFMaHMaI8J7#2'IXMEUF:M:C[?VpP\yV[zVMmEStJ\{RZ{RQrLOmNYqXVlV3K1&;6L)Ei4p.t%>Wg,^Rl,c~CB]$-E d~DGa(.I-7R0=U@9R8G_7vYUGd'\yBZxE@_*+K7T C_,?\/?Z2/I#3K)B[51L"8U)\{O;Y/=\3?^7LgB1M&4O$6Q#Lg2Y{8Pv0Io"Uz,Cj<_Op.Nm1-H7):"5.A"3J#&<>R)9P$C[05J$-BH^3=Z*6T" @.H(E!=82 (D>Y-Gc2C_*,D#" "+ 5)?*424*E+)F+4)C#)A*D.H"$>2N'/L#.J =[.:X+2S#1P!$@62/5 "7!*A+$:& + $2&%()!1"(?/5P? 7'0"+" !   +  ,)!2, .%.$ $,"$5+  &4&7( +!"&+$ #9L6@X9AZ8!;#;0&=%%@).G10K1)E+&/'2"4%(1G23L1,F+"> ;4 +=W)dI=\"9XKj0/J2M?\&?["Cb!Ec{GzEd9Wt12N +JhVu'v?{=h.K%G +K Ih#tPgEA^#.K3OGb.0L6QH`+Hb)?Y:R1K!:6!;3P#-I>[#=Z 6S*E#>!<6+F^uH(+> Yl=Qb9'& O`?1 $9"8 +!81H9P?WUl([s&e},v=|CIXkp.QJ[r\rb{#Ic +Ysd08T 0JsN]w<25F- //& "21 (';*Cd|FIa&2K uC]k>mK7H!4%< 4^vB7M$6 *0 %8(;%7 #5 -?*&.%5/#)+ +'6 +(63B!' (7-?$5AS+1D-,> \F[!jE:Paz1f-o5l;Ys&Us Ws(AY!(@ -C/F.E"9'?0C Ka#;P.C7Q :SSo'=Y;U Ok`|(k2n6p2g&z=j87(A6.,@9M\r2-"9dx:[q-EXSf)@T:KqL9L!39OI_"Pa,&7 +. +)!*/"8 G`)c|AFZf}>BW%<EY?T-F3O&A&BSpb*LTr#:Wq-\u;F]%g6M3.!91O^y>?[0: -H8S$@$C8W%B +Ga-8/-I4S!/Jg7/I.3Md39PMg-7L*# +$4 + +&, !1,?&;,C"3D%. DU0?Q0-=!" ' +  % + +$'.$cy$/ JZ3}d.?(7j4hw+nz)lx&bt Sf^q"XK1BO`GfDS +&.: J\w11܅ +$rBat$Zj/+0<' +  =G5$."+%2(6-5F@S(FX+=P"=Q$:KFV)ARSd->O=N{MsHWc|>Sl0c|=yPWp2Zo8Uk4F[!`oOf)Of/H`*C\#oMdB[",GranzPl>SSMX`5Xx,c9h=rHk3+K*){%-xz%)z'6Fm>Zy5Ts6Vr:{agNWp=[r\jAkApGqHpIwRkHrOtP|X]dVt)|TSSyCITSaa{?x@t=v=Dq3;}4KY9?PHFU]RMVNNTUYc_[Z\^\]abZTWQXXQSRMOW\W\`[XUWSVWRSWUVPYNVW_`]Y]^Y[[[[\`b`ZX^Yb_ibU]ek[aeW]\]Z]YYhZOTSTVf^VOS|.APl:W爎@;0{,HF:z8u5Xpj1Phg)};C>s$;=.y*}9^x%k1HX\hk&e.\Nl2:ZxLe2Lh5Fb.C_)A['nPiJ?YkLdGKh0[vB\vCGc'}Ta.m<[d@]y@Ws@gQZtD[vCXu?rWg}JyXobv;j|Ci}DkJG`(Rk2Zs9sNkBwJh}=jA[q+zEcbi}mVdgpfpmrvoifEUu?XyLXwPVuNRsLi_^QaR]|PLk>_}RdQdLza[y@dGcqNb;j?p>{Dt);:.A>)<=&:9!33-==)97'57+9?/>?2CB3B>0?:>MH4CA2@@,;8)754DB0@@3BA/?>SH6JA0D=/A@0BC1@A*<<&<82JC4IB.@=5DB/==+:9,=6,@76&<0,@6(<6);9)<81E<8H>8G=3D47J7AVC4I6:J8?Q;I[CCU;;P5HZDI]H5J7-C23I86J81F2?XA>V?,E--E--J.7O41I*;U2>Z4?Z8AY99M1,>*$5'#7.*?6&;2$90+?60D=+@60E<8KB9HA0?6-=2*9/8H;3F2.A-0E/E[DE\E~~aranhldMfFGbCRoQIgJYuW]xWWtSRpP[tZVlYEWE?S;0C"ĎHew8}4q'|+10?o4t@pKH`se9/I7N4-E'6Q]Rd1Ed#Ij+6W]{6Tp/?Z :W+7P*6O-7O.;T.?Z2Bb5De8.M%+I$.I(-H(0K(5O(,EJg<,H0K2M!'D5R"!?0N0NEc0/K4/!. 0>0+;-!4#.)21F/5)E-7(?!*A ,F!;U03O)&B1N%2N$)F4Q#@b23R##>";'/$'*(;', $,>3$5*)#6(@ZI1 ,D4. ,&%$ + $'! #$&# $"%)&$!" "-!1#%&*-#/(0(%6(/C*1I)4M)6J( 84K00(#;&":$*B)&A'!;!(B')C*,F,12,(,>'WkPTlM-F%$@!;.,F"/H)#: #;%2".-"5&$;$0>V:0K,#Cd[`N2O:YFe*Cb&Jh2>Z'9U"Nk6Kh1Mi0Fc!?\)F'DiBa5n@O{;Bq4t:J|L5R;[#*I 9XMi9/H%;*?&,)1 %h~M1H*<"1%4!1.?0A'8 2 , 1 +% & ,=.=-<8G(--$3!/ (:1D+@HY(CV!DYI^!E];P]u+Xn!RiRk![w&e.]y/37MG]&,CBY$?UJb(>U;S*C;PRk%SWl.#Rd_r:z@u@X,)=.=:M)=@TQh'Vn&Xl%I]/@Tby8G]7M$8,%%()!8 +7%@*<6K:O': '9?T{SEb.LJhe1_(Xz#Rs c8>_Nj(=XLi.:TE_*Id/A\%E\)Tm8/1(ABW3!70 34QHc(Qm.5RE`*XsIV/Yj6rLQis/9Tc\n$^q')=p.bpAN IX +`p|1GZ zGh~!@>J -+9 NM7F  +',8#$      %.7B(,67D-,<6E]G\&-#5 +0 %9 -@3DGV+Ue49IEW"Sd0e|8iAYq5Wq4iEIb%[s8axCYp=Zo;Ym7sOuR>RCY!Xn4ĈnK[s<%> [tL=V37O0@U6ZoQ@W3E[4G]7Od>VjGG[8;L+7K'H[8VkBiTmVVmWBH/(7;55237;<<|YpLvS|Zc@`=qMhCoHuJtGyIV[~HW\Mo7^c,r84N9Qi$e}+o7f}*<\Uk.pO6P"(CVr:[z>Li,dDb|I+>(85D"9I(=P(Ja5?W,2L1K7R 5RU(0F;P%:P"Xw5a=aqCsAq<{DP`W\{3pJ\z?7T Lh8Ic5E\0czPXpDE\.Ui9qQclȯʸϿǴĭsrcv}ʼevlbtjXk_\l_XkYReRE[GTkXNcPCXEDZFG\JCUE@WF>UG9NE9LI1DC1DB/@?/?>.?>&89+<=0?@.<=*;;,<=4DE6BB9HC5FA0@>:MJ1DB-@>6IH2FF1DD'<;@VS;PK0B@':5->94D?4D>;PH0D8%:/.B7->1+<00D;+?5%;.0E9,@9.B>,?<6JB7H>9J<7I6DZEHaJ>V>FXCDW@OcJH\@AU8@V:=U=0H21H4.E26L;;P?=S@:P92H10G.5P68Q57R1?[3Ge9Ie7 60&:5,@8/F8?UI>TF2E:;ND2D97G8>R>9N85F42F1.B*&;9N/=N4=P7;O7@T;5F.6I-5I(9S09U30N.1N15R0;U2D_T=0B''< uC>fen)n(u$.x$9x3l*ju(r!:F/6K*7J,+i>[*:W-=Z5;V42O&.I :U.Gc=4R.?Y6[!9V : #8# +"0 ".$"3& 2" 2".05 6 9!#A"#> +F#2M&&A9V/9T.'A:T-;X0&B.K!D`7Eb9)D$)%!/)0#&!!2+%6.$# '*@4 !      ""%#'"%"& &$&), )! 3&%;,(>1#:,4'/!*( +$7!0H*+D$0F($:,@)/'!<#%>&'?)#;%$<&6'A%73 9! 6 5#9$=6*C%'@#!8 3!9/H+#;!"8#121 #10I/3K/.I)2S)Dd6<](9YMm+Tt47V9X$>Y+>[*.ISp:2PDa(Lj-d@^d?B`@_C\#@MjXZyZ|"^45R: %@,GMh<8T$7S =[# <2/G 5N,D7M"%>;T-/I!; Nh6So9=X#fQ7S#,IC`,@[):V$)D%= +@'7* & +) +0 %7Na4%9 ,<#CR)7Jf{,t7SiQf^u&ax)J_3G K8N!/FYt*Ur>Z GdB\Lb$?T-?(9 ););'=)A,AFX,;M!7I0D/I4J(= 2)7N+C2 '?+9P!Wh:/ 3C*- +/ 7G#%3#21B%, %9. )(8/-+:)6&3%3N\1''. *=)=); +@EZ!/F AWXq14M c}7PeMe MdSk Mgn>ay9,F +8Q&?1IXp8D_Mg'Tp,e;=YA\i:PgTm#[u+@[Mi?\(D[y#e,w>@\)A2L +Ri$\TQfDZYp'$:4Icz0&:6I AUgzA2G6K"9 >TAV0E#84K8P0EBX5F3D7H*:$#'=+. 3,> $7(>/ 7&AIf#Vs.Ts)FhOqUw!GhWy-Nm'Qh3Tl5 =(:( !6%A!=Mi.Ea&=X5O8R"!<:U)A\.:9 Gc1Sq@.K5V+Q)6E#* +?O-9K (9+IWG     & +DU' Ua&l^k"Xb0vH@K  H]$Odx(gy-5IEW _s)r8}?D8FB;R:<~/@G4+5%2".$4K]/[zP *;IY2.?"3 +->3D8H;L =N /Ai~;Wn2Ld*\w9\t5;SJ`+Zn?tWPd0mM[o=Sg7]r?Kc,{Vn3Sk4\t;hfbuRvOrK|OxEWWvCa}/p?Ѓf5p;N<65,{+1q"d64sx$w$rv#nt)p*~=Ij=cCPk6B]*`zHMf6Sl8tX`yDH`/YoACX.Mb;Ma<7N'mXTn>-E.AFY8DV6GV5L`6}WpF#<?S4>R2.B 0F"?Y1D[6K`<>Q/XjIViG.Ak[xe:S#d}KUl;EZ)pN|WrIptPaJLD?679<::8?@8@::H>BAH:;vFzQW`8pKc>iEnIOm+a>iFcARr0Tt3jKhG]{>dAoK}Uf:b5xHvGmVX^mZyEWw&KkWv%f/l0@|;k(l(~9~8WA3FQNOQXUO^[U]SVVXMaYYQMUU`TRPV`SUZ[NQJMVTR\WZXf`la_WWQTUQMU^]TUZNP^VVYORZ`ab_cbee_X@EXWPMYmuhdhcAAObcenYR2LRwz:l+z;k+u9k0o2]r"|@[r j0v?tX$B\*)C1H7L+?/@^r:nGd9j@6UA`Yv3hE,J Ni1Lg2qUPh3BZ(H_35O).F)0F'#54C)@Q72C'7G*BV3`xLXrDZxGiUlIb3G]0H_1UnW(Un@@X*;T&;R$fOMi1Sp7Gd-Lg2b~CSq,Ebj9`|.b}3lH.E-D=T(^uGToBeOtZhJsUoS?]&v\Zr9F](Tk7I_)^s9mEWm,az8rHoCa[|S|XwW}_axWfuX]hrbr=h~=bDZxIWvQJhKWtYCaFT'Md5Nb1wW^l<}ۿŸƮӫ~xtyj{nducZnZCXD^v`SgUWjWwvonJ]JH^KBYH9QD7KG1DC5HD7EC6FC*;:':9+=<.<<2@@+99)890@A6FF?PH6G@5I@/B<3FA;NI:MJ5IG5JI2FE(<:0E?5JD0D<-A61@76E;SJ8MF/B@2DD+@?)<:,@?1DD-@?8MH2C>5DB.>;1?;.?82C:BVL5H=,=3.@72B;4E?0@=/C?2GA)>4)>52G>+@9,@:-@80D8HZJBXELcMMfLG`GI^JGZGCX@L^DBV;?S7?W;4L42J4*A-2G57L:(?+;T=4K4;S9=W=2L07R2D`6Lj;OkAC\;4L2-B2)A0-C7*B9(>;-C@'<7*?7BYLC[JBYG>SB=QB6K;DXFD[C;R88K26G0>P2=S.:P*CX7DW:EX?Z;>Z;D_]#Lj.Ih-Ql66O>&A!=8$<"<!:1L*&A"<"=7,E(- !% .(+<1  '(' !  &*$,)"!'&  )##!"+!2&" ,0$-!)%!  .!6M=)@2:,)B4(=11% 1#-@-/D/(?$)B%2K-,B+"3 =P<'?( 7"=#-*4660K0(@' 5!21$:44#<31!8 95M/,E(/F/#:%4 +B. 9"*B,)A)4N1C^@&B; Bc-3UjFRr34S2M2M 7R'/L2N%B(E2N4QA_Pm*f:[v1E^dNr3q2i6=[)F B`)(D5P(D]57Q'.I4OMi2aQj2Jb-6M)@$=!:54JYp?/L5P0J4+F;X"(C ,HKe)Wp91!+"0' ++3 8N*?>S!buGAS)bsGqEQd[p$J^NcQ+>b,ES̈́1E +0E!!;B^>\xD`z69M1D+=,>6H!/ *>-D50B=Q$1Ia1$<"9&;/ +,2K|R3M0I?W&:P 4=N 0"1 -=),;)8!0 -=/@&6$7$4   !(*Sb5n/?*:(9,?3D,=[r1Si+#;Jc#WJb\t*_z.oA[z0b7Wq%HfXvWtMj0IVp'Ri @V l=;O3I 68Vj11F bx:f{?=R>RAU=P>RNa$8L 7M=R8P0G.DAU@S"#8%8 *:1A')<5M :S%)?,%'/ +%(CUs29W_~86V +Kl`2>`.N 'F -K$B &B99*?-)@1Zt@^{F+B ! Xv7D`%/J%@"=)D%?#<- &?)E%@5 + $7,J 7)C"2So=8UOk2340D!D\7?T0 ../F",C$+() ++ +0 '&UdH   # -@&ftEQh6C +Xd"KWHO!+3 )&69Mex'Pcv+fx)M^?NN_p~+hz'x1ath|cw?QLdl}2u9]p!|BI[vE/=!-is;Ō"(&$+&)4$ ) $ $+")#0!*mC3,@ asEUf:9I#2 +8H#8H#,<7H3D[q/Qh(WWp0~W_CX'Se6L_.I\*FZ'EW(DY*Ti;Pf6srN;V9Vz[pRqOmKpKxQW}NLclPm;yHh9uE~Jr9{;|6|2r(v.q,q*r'x*?|'{(<5z/x0w4z=Zi7h>qNGc*iQB^,_{KfQHc2ZsD[rFF\4J_8WkECX3)@gWTnA5O#>T.3H&VkHBT1cxPmWxe(>3G!8L&\qJf}U0H >V1?V3PcE2CW6cwRSj>e|NYpAXn=f{K;B;AA?MT3r=#:AU>Qo0HLKLN@A>L`lq>ay1|Fv>g,ay,uBL`w%EQyFVr-_~;`<^};yU{[zZbA[y;bBPn1hcDWv9]{?Ts6eEbAdoeBFJDZJWWc=Xi4cux0e|$n5Uqn8[v%t=M[Tjn7n4s:e,m5a(zAi-\y g+r4?w1Je c|#LcNeZqu5Bx8][v+a|5To$/L@^ \w,-K[{8;V#0H BU06G$;N*Me8'B Ws;jL@^"[{?Mk5uU1QLl-tRfI~aC^*B\(cIUpO/mXZtFZuFRm8V#+H>Z'Kh3Qn3lETZy,k:Pn"~X[v=d}NzcE\0WnBlVv]c~HmQeIsXcgGUp7BZ%Qg1Uj1f{?rGYk?SyO_f~HhydaxMUnBQh9e_vqa`uuqEoPbP=\74U9EeNCbL8U<@^GJfOdiHcJjimgOjD*F^yOmZMl4[z?_}AQm0e?e;h,?=-=<6DD,:;)78*::1A@7HBIYO:LA9LA@SI;ODRI.C<0F:1D9-B39I>8G=:LA;PF9NF/D?1DB+?>+@>'<:(>=&;<3HG5JF-B<1D<2D>4C=4F=9MA8K?1C7,<31A(>4+A5/D<$93*?72F;#8*AVDH]JQiSIaJ?YB@TB>P<@RB[A>V?&=(:R<;R=-E0@YA?X>4M24M35N1%A'CZuC:V$:T* :1J+7P1%B"&C$+H*)B(%?$'?#.F&)B8S%,K7WCf'Be'Ba'=[$?^)0K#<.C"1C'!3/%;(>-H@U0. 7%(D31" # 4&,-4'=$64+*0 6!33/!6$!5!4-F+$>"53.,C(!9)B$$=8!;#:+B'9S20K,!96.32I52H47!'B'&@#A[?5P2@\5TrM[f%;J 6Imal;PkE`_u56J 4)-"32H0H$< 5KEV.(:?R#-E#= $:!51"bz-B;S#;Q"AZ&'= .(9=M#&8 1 4C '7&6#47K'GZ:):&6  + XhA%Zm3&9!%8 ,??P$$6CV!8P c|:c|:]w6?ZURm#xJZr2Ia#5H1E Ia(44J,D;S4J2JD\)0JTp2@]?]FeXq*BXpCHc >8V?Z/O\}&Stf,d.sAnT:R0H"8#(= 1H$7 "4 !0 )#5 '$:2!8ZqB5L)Jf,Db$=,J 1N@ae@^92R:!? +P'sXFV-*8 +   \dB}CI-2|ډl}*x1ZjQamKT3=MV+'009 ;Jb_s$m~6ct/ew.Sf$/?Ggs*ZkAv7r1T:K "0j}5L`>S ZjM_=N :L `AK(bm/;F^g,09!  $',,/ " !1=O(m~RVj4Xl8(5[t-g;Vq)i;g<[r6G]+9JJZ-i}Mf|JlQhySi9yYifpSj|_agGWy4c=Ur-tGzJUgӃg8Wc}Nm:VYDn+h'\`"r3m(x.r!5z)~4|7|>LzGl?_y4^v3e~=Ql.[wU$Qh7Tk7\q?qQZn9pNtP|SyJVQQLD=JHE@@>746;==><@(h6[r,Yo(TbbrEYw6Yw;z\y\mPPm6nSeI[y@eI`~EhKQn4cEdG_}BbCRo3mc>`}>fAYq3kAWxGa|IuEhAsLCe\|0wEg0u8q5q6m0b#m-Gl!WTPQ^XTUNMT_V\TW^bZhX^[\VL[XWY^[YVR^W]SZ\PRTOVNXX^PRQQTX[T\gQ\VcV`V[]fI^mdqjitec`Nu1K~fUYQG;9ECl'}5E>dRD=X/:XL8p.h0b~-OSo"n:c},Tm!\s*cz0{GOkb*g0xAq:Yz!]|&[x#b})Eu4z8Wsh&Tm{8@^x i+{@{<~dQi*Z;Z@['6%:/B 0 bzLJf/Mk0hLkMVv7pRlOlLuT\|8Xy8Xv:@\#Gb-5V8Y{\EeHdi>ZEE`OLfSYw^klA^BKhLE`BPkGiXUs@Sp<]}ChGb?kDm@zGxBv>xMK2B?;KH5HE0CA/>>.;;2@?2@B,==/><5FAG[OI\P:NDEWO@RL:LF7JF1E@1G=0D;2I<2F::L?;L>:I=9KA6KB3HB0DA/BA,A?)@<*B;'=9"866LG5IA4I?4I>-B;/AB=OL:OC4I;2E71B6.=7*:3/C7.B82G<2I<-B5)@3.C;*@7&>/9OB:Q@@WD?VA5M7:R<4J7:M:>N:DS:K[>RgGF`@>Y:BYA1I07O68P84L39S76Q45P3;S6AZ=>Y6Ie<$98%98)=;I[Q@VEWmWPgMRiPQjPTnSGaEE_>WoK_uM\sFUl>UnARgAVjFWhJP_IBP@;I;:K:EXG@VD;Q@0G3E[DmjbvY\qRZqLiY^vIb;k6k'l#Cg(Fi=_ \} ~9hlde [Z hr5X{}8H=XFb=q#|*v'kXzA_$?%?2e7U(C!;!:'@&%A$'D&2O0 Z(>^$Kk/Pp6?])0O-I,A;Q/,?'93H".G,J9W!Ei*Cf(Wy<>_&+M.MEd(Fe)9Y<[=^&Ac.i[ '*# "5!6 :!">(#A( ? -K++H)668(@$#<84(=*4 15%2F90$/% 1(%"$" 2*%0#6)1*#'"#" + +*   &((A,+B/$<)4!%  #%5& :)5!8%#?, ) 6"!9%\q_!6$$+A,=U=)A*0G2&;).5 4L5&>(0/6 +D)8Q4&@"6+&:)=W<%@$#= 41/'="1J.1L-8R6)C)/H227"(?+6".4M597&@&)E#Ki56V>\^|@ *E3Q1N5O+G3K0F/E/F/FSj0?Z]x80K +rSSi9#8*'7!;:U'/J.Ph/fG+Wp3h~AE]ax%`v"SiRim4Y:M\m *:-?F^Nh#6R1ICY?T+? *<+<!2 ,=4 64M4K(&6 %9 +F\-0511 %|Op@+B 3G@V)0I+E,G>T&AQ(?O%;O#%;/>!1!2+2) ' +#. 9L&{ZYn5=SF]AV:N./.?#5,?G^=W(BB\Ke(3NNh&:V1H0G2G&< &;2 +9N#(>,? +>U"4M%< *F$AA]b<\{6Yu1=V,C,F =>[G`Xw*m=GhMi^{)v@h/A] +2J1H [q+Sj!~X/E* &QgA;R)EX*;M &68E9E4F/D +=R-E " 5Md6&=9N".?#5"2- ("**A]([v:3QKi*Mk+8U"A)H >^!.M"B#A*H8 ; #?5 . - 2.+ + &(C";#;.G&AJe*Ol+.K*F6P#*C6O()A3)B$<*B .42H,5  $ 2M#+E^xM;P*'; 3% +0>( +    + "!2;H.)1B'%6$5%7(   +  dy=RR|.o~%gsp*LQs@;S@W}HZl"A\9OFv7?T +^pCK\LV +ufoT] 37(- $"#* &.  LV/Xk0dwAEV*'9 #88L.D(>&66G%-?*"2.:!/$0]X_.]|-Vt(:X=Th /~,5x4l1QZy.d>Sn3a{?^v9Sl0qN]x:Pl1Ur.G!BZ7>U2AV5PcDRgCu]`yK\uFfOu_5K#;T/@Z2Ng@?T4*>!6I-9N*Qg8A879=7;3:7C@FLu-/G1,2,CQe1CXTit2?g[IUu@Zq)\w-}P?]{IZm9Xp$[q+Paf7YRp-fIOl5>[#Jg0]zB_|HQl;?\(Fa/Mj49U!Uq`Hv2v4e":Cz6{;g)^}$PI|:v19t)y*F4DQpu*l*s8g3Yt(Up&\y)Yw&c3e7Nh!_z1Tn#_}-Y{%X{ g0s>Xw"Mi`z)VoIy8m+z6KFx-dSm[ut4b!EML;^0L@\%7P)!8Oc@?S/\sFt[Ig15SLj3A_)Po8Cb+Ql7Rp4{XPn.Ni-Tl5Lf3Qi75PKd,CZ"7PsLh@`{6_z8Vo/!<Sq0Pk1Lg3Sj<:P&T/7I/1B,$5 CT:]qP9M((>BZ0[sFjSXo@rLb3BX*2IOg8C^.:T#Pm[+Gd47V$Om;t`ZwE{dJg/9TOl+Uv-nBhlVUpDsarafT\wHd~NlQzXoNi_\sŅrSbGq8U&XwJb[Y|RWvU0L5D_MYue=ZG9WAF`LMiX?ZF@Y??[85Q'A\/Tq?Vu;aDd@jAyJtAn8w?zAo:i9_1h;vIi;i>e?Qf.dt>jv?\tθvš_tg8OB6KD4FD8MH6HE8GESba4GE/AA1@@5BC.<=-=>)9:.?>:NHOa[=QJ?RL:NEG[PAVI=PD>NF>OE:KD3D>ASI:OD:ND7MB9MC?SGHZNASE2E@-C:+C:-B;+A7)@5BYL:RA9PC4JC1FB/F>:QE=TE/H52I71E5,>1/C4:P>,A3(?1-C6*@2)?2-B73I>%=/C[KF]K:Q=8O;2J3;S?AXD5M::N;>O:AP7VeH[qNId?JeBMfJ0I,:S65N14P1:U62Q08U5>U6:T4B^;Gc;RnD?Z60K/,E5(;6(<9(;;#78#67"45#77(=;M^S@SC^u^by\PjLQjMLgLWpTOkIPhFWmF[rHZtJqcYnKM^A5E/.=,5A7-<65C>.@8*<5,@7)=5+?3*<,-@,?P;EX!9#,(&)-%<(/,+(A,$=% & 8.H*04*4907;U76P2)A#.G)5R2.H+1K00H3 9$$>(-!8$5!;T<$=$8!*1 dK\};2ORp57S!= 6Q#9T*9T'2N2O0L;YMl%k>e7DauMB\e7>ZRn&Qk&If(*E 3N83O:P . +?Q$=O#;L#-A3K-E/>V+F"=If+Jh,4Q0L>T%GZ,+> 7I/%< 9Q@Y)B ASJ\.&: 0Jb1'=&04Mj5E^, 0 *7 -I;V)4&;/?8O%%:#7?O00@/E!/ #.& & +)-Kd$KeP)CQj&Yp75J,>CT)/@dw?]x4Je!";55O/I Nh*\u:/E#8 5) 5!3*<"5);4IE[*Kb/!< Uo81M1M1KG_$>U/)A=Y&.G1J`{87R ^{0g7?[Xs%Qlj3t7x".&)?8N)$;'?1 !Gb/Gd'$@ 4": #93 0I&.B$Uj>hLUj3oJaOb+Vh3nLhzCXzP_[]^nVROIF<6@<:BC=>E>FEE@4@SY-/!*A1E"#7Yn7+@/EyCI|De1_Sk=wHuJXw+Yr)Zu*Q~Kb|4uGzPa|:Us3Gc(Fb+Je0Sp;Vs=Rp:Fc17S"3O @\,\xGb~MEa0Fb0C_.Ea1Nj8^zAm/KJf*Vo5Mf-B["?W>Ua{9k=c4nj=Yc8mAFfVt(Fe;Z +Y|(Vw!n9I\z#s7g'DB>K^^_ZYZPOMMRPJIKVu(P]^y)}C|Ae+t6Dh)@Nh)b'b~+Nlc}*VrXvg)?G7>[MDx-^~a#OoWt(Xv*`}/Yx)h9_x2b:THb_{1Yx(^'Uv Ww$c4Sn%Pi_w)Tg,b|$x5U'2FpR7PRk2lHYs0g;Vq+[u3eANl-Da%`zD6N1GG]6EY2[rFe~Q9R+1I*7J/FZ?;O17L(9L(CX1Pf=BW.WlA8L!H^38N#K`5Vl@e}OG_0D^-nYVsBMj:9U$\xGUrA2OJg6Ro;dTp7Fb$Vs0_~8Pp)a;eDhJc~HF_0Kb5`yLF`3Qk))D2>WEIbL8R6A[9D^9Lg=D`0Uq=Wu9Kk'Sr)PuAzDyFpBRr&]|1i=qCpC{NjASj1Vf3}RaeƨcwkEYO@UL7JE8MF=8HFDXS-@<4HE4G@1E>1E?/C9,@23+B-E!9N6TA9K7?R<@Q6`tSWqJLf@NhBE_<>Y69R/=V45O.:V3>Y78V2Z:2J6+A6,<=&66$65"15$46%79'99,>>9HA=OA`s_XpULcHay_OgMToTRlOTmNh|\\rOLbD8P7+>)DUA2?0(4+-<:0??3CB:JI8HH4DD2EC0B?(917H?3C42D/-@"YmDZLhVtBdJl^"Lk'@ *fC]&C $?246 "?%4Q4=56":&!9"(?&$91J#8T&A^,;W'-I0K*E6Q 5N!+D,A5I,.-$5!61D+5G,%83I&;U-1L"Nl>6T+I+G%A%@(6 .&(:.%;-7$02#;''B+'D*%B'1K.+E*";"/-.7! 88-,E'*C%3L.+F)-G*$>%8"/+ ##:&'=*.D1#9$)@*2+eK9[7T9VEb,.J(B&A4"? +<Ef"FgMn ?a_19Y Ro'Ol#Sq&Li"9U;W,G$? 4QhI?Z#H_*K`-=R!:P"+<%7,GYq8azChH8UPk4<[=[0N3M;P#=N"O`/&:xMA[5L-D,-G^1[pJ/ ),C(@4#;CY%;bx-Oa~`y=VNfu7y<,Fp2t+IW6P5R8T&>6M:P(? 2G%; '> (< ++> Qh:Ul7B[!8L;M1D.@\=X &?HbUq&m:sF3' $<!;6P&F_7%961H#7$8* (;@T0)4'9+   1 ':B]Id:YOkHa;R$8$6 +$.AVEa8R 0J+F[t1]x6tP_x< 5 1 )#8.*;$4)94C>O'@V%Me/1NA\)2J8O">W+B 2/*BAY)4L"9(A 3K *CTk%WAX8S +Kej2|CRkTi#uM?V&_pI #1]mC=Q@WLd*@TBX.? +.2)@,D6!63 ,"+,' 03L)E +Po,5USq1:Y2NZz>Da*,/I%@)B#=,F2 6 *A/-.&;-1+(07- / +;R*9P$<+?%9AV18L)1 1/1%"=,DYlO+=$    +  +# +% !: +$7,& 2    + &5$ +!.,/?#-%    + +GW< z=v'LTdYhgw2_r,Rh ;R9O>M4B "1 +bt+\icn)2@:CESJZVf#P`Q`AJTbCSG\Un wB 54HCWBXn4u,8gy/IzL1,Dag7Wt&]z.^x5jF2L=T'8O&Ja2e_|GYvBjU^|DMk5{aoYXuDYtCmVYu@dIUt8Sq3Qp4^}>mLuҎrS;XQm-fC~XuKj=yJQPm`~*a+PrDl+r,9B{6d(\|+{RPp,fH=Z$4MDZ*Sj9Ne-~Y[u9Hc-:U'^wM7O'4J$7K*M_@FX7L`9G\0_tH0EH^7'?G]3oU[n;=N1B2BEY*BZ({[gczGVr=AX-;P-4F%8I+BR3UeBZmAWm:gyDRd/HZ%Wi5ewDgyHdvB]qMrLd\LY_NTIWHAH?FDDPAKJLHG?NNF\uH_w9-E,'.eTl/8QszGWr)?YPn/Rm-n@vHQq&\Ph%Xo+[pBsJF` Vo4B^$Zv?Ke1E]*6PMh2To9YvAVtBPj>Jf9Kf9zhIc6TnAq]D^1Ga2Gb1]xB{\Le+:TRk1b{@Pj.4MSm/Oi(Oi$vJTr*\y2nCxNY[}1k?Ut(xLrCPXr=Pqf/]|l*L=eRY\YTV]Y_ZSSLR^W`meSSTQOS[\WQ\_WZZ]V[QIDPO@NL@NKORMHARYVOTTZYRVQXW[YTZWiet;p:PSq;KjQme0k7{F|GKJv9Bg)QqUr6RwCb|(WsZv"g,w9h*p0l+~W/Ea8-G*F;?Y33K$1F 2GE[+mNNl2\wC>U(1E:I!9JAW#?T"I`(wOrFUq'[u06SKh,8Ua|E)B/EAW1ZoH>U*iUQhB/E%8L04H--B#>R/EY3>S.g}VjWDY2K`9VkDLa:G\5g|RPfjTOj=Id8Jc5fPpSB[#sV_{?uRVl:.DBV+La5Pf1eCuXv\Sn?ToD\wMfS^zL`}OjX{hvc_xN^vO^wQ\tMeUl\slWiPyZ~^yVmPdhSvDIlBMlGb~[:V14O/?Y=/H2.F19Q:Tv,c;e:g;`}4[v/{PdxBDT(KW%wIY[lbEZMFZO:NB@VN?SJATLJ]W:OK/D@4FC3B?3C@4DC5EF8HE4E?8JE1A@1AB?NIBRJGXMCSFEXIEWK@RG;P?@VC1A23D63F;7K@1B0=R-^[xA[2H$8N77O;9P#,?+';/0D;/F:4I<3K:6N?5M?8Q@9R>6M86M:=R>:O<:N;=Q=5H5?T>X4?\7>[4:U.=X1=Y2C_6Gb?:R8&;/(:7'56 .1 01!03-0!03'88*;:1B;=OBVhVMdOI_IZsZXpX\u\XrWRkMTgLH[E5F7,?5/@50@4(4.+;7-:<;KLJYXM]\?OO3BE5JH+=:2B>4D>3C9>O>3F+4I%^x9Yx)Cf +Tuy@IKk6T9V?_ r3:ZAgOt DiRwemPtl"c l-f%l*l'^j6h*Mm!"@On+]}31Q &C%C'A$5,F-'D*)G)"A!,I,!>#,F-.D-0 45L.1J#;U,+F"=;T*1K!9S%,G2L 7P&)=-1 11!4%8/D&CX8/H$)C6 %A :(B=X2)C ;V1-G!$>7_wX6N1%"4#+&.4")A+#A%; "@$$B'-J.&D% >*G)*H)+F$:!</I+(C$1I,.  )$)'&(0#,-4'/#.&4/" &",#, %4(/$)+D.EaJ<$    ".#   "?ZB,I.8 5!. 1!2G3&>#*'@# 959Q88P84&$&,03#:'+C/&B*7 $>' 9#!9#&<(0F48M>(@+1#9#6L4I`F 97O3(D%+D'763*B-$=(#;&&%8#+>..@1$8"(AhTfJSx7Ml-X!.H6Q3M:V4P8W*?4J1F((9 8F=L)--C#:'@+%<&:)A1GBU%{`1E]*5@UD[AWG\-C +&=G^?W?WMg!j:4J 1J +Ha8PQd2! EV%BYc|=az?7L/B 6G(: ,>':8K -@!4 '81 1 *?1A 2 &7&% $ Ja43N-K7U:V 5O-G$A : 4 +"8"5.?T-BY0gN8 $/ * 0. /F$*B'=5H'%6) ,-, 6 &=/7G*/+ +1J'6P*8!;-$  +!!0   % ** NdE06J-)+6!-& &5$$1E(0A% &80  + + + +_rD+GW"v?i:RZoPd\r&7LkOa}D`n/K['o:jy-p+j{N] 6ER bp/GW!3)EVft,TcKYVd(cs'\nW6D@H7@ju#2?M^ w%KXhGEgqf0_@|NER$[Mf{C1> +ANASL`"K]!Tg&k6%;J_4F>K"csG(8* )"-# .:"1<$Ha Lf!A[dy8?T7L1CH_2jVrZlUdNMm:Kg9]xIHb2?Z*Ie4VrAYtDB[,Ni6[wCvkJ\x=‡>]]|C_%Zu_pPz]-GdzQLa3Xo;mKtQb{FNi;Qk@Rl>DX38L(Pc@AQ.Tg>H^0h}R6I%4H$EZ4Ja3Zq8^aq5Td)rL_s8Zo5rMeAU%bxLDY1CW3!2N]<9H'EU2evJK\/^r@Qc0h{HUg7Te9_pDbtFl}LuR|Z[SOVmYO\JJEJ*4>AEE29?8:BBCAB\xX*?Z)Ic-Vo5fDpL+C +Xq6b|=qPVo0[v4Ec]a7So+[x6Kl%Hk Vx-hV2,>4F""5.A#>P0ViF4H$2F"K`;#78M(-@7K(@U1J_6Lb8:Q$Og8=X(Uo@6P"Ng;&;7O#8P#C]*Tp6zVHe"Gc [r4Pi)Qn/Oo/Wv6bBa}C[tAD[-Ja3\vLFb78S'Ia3`wEjNOk0oMuRRj6bwM@V.F[1mTwZrXKf5QkC]uRwm\vPQkBTpH[tNC\6WqJh\]uSKeDQjHZrO]uSXqOToGq_w_ghfrToU}h{gudmXtBNk;0K&3M0'>(.E0/E11G15L3AV6H^7Id4mOoJjAqCZy,^~4e>Uv/Y{2}SyN`}4d7wIRc5;I&HR*pwGufwlQeVQfWJ`SMeVK`T5I>PdY?TK3HB4IC5G@7H?9ID?OJ:KD9IC8HC;JH3BA;JE@OEEUJFWFHXFGYIEWIDVJ:KABTHF\KMcMMeICZ?0E&|LdG2K+D]76NA-F73K;5N=2J74J7;P>;P==P=?R>:N9>U]1Ba6=[2D`7Ga>-D,!4*"34$03"/4(38,7<#/3$34*89-==,;50B6DWG[m]=TANcQTkULdKHcGB[=EW@FYI):3(86*861=;-;;/==,<=2BA=PNH[YFYZ=OP7JD5GB/@;3E=5D:AQ?AS8CX1oEu?PqDx99] *G +9:Ue#Ei +Yzt+y'cz6FV{Bg NpKpHo W~b^Qsc3l;No 2Tq6Pl@00)B% ?"$A#:<1N2!9!2+)%8 +C$+D 4*C$<.G 1L".G0J9S$;Q&)?.+'&27L0.D%,D".H$+E ,D%.H(#:3& +//F)*C%+ " +!,'9$*@*02-&@';=="(F* <":9*F)(E&-H'$?"<";:75M6%8,&% ! 3'.,3&!7+ 5( 9(3%8,2$ $ ! ++% 5)6(#( "2"-.K3KiRIcN.A1$%&?.'B.9V?9P< 5#%@+2P6$E&/P5$E/&B4#;(5 1H.E\?&A"2*C&E]A'@%7 # * .('D--H34)5!;R@4%(0!"")1%-+B).F,5P1%A !;"<"7+43J5-A/-!4*($3J$Xv,G-F"85&<+A :)B-H6)D.I?Z"Da&He)9XUl9@R%0?K\(DXmHTo);Q+@\t>Lb38>VLf+Nf,6M9T4e8Ib#/F!8&%:&<3 8!:4O%,C7O',?%2 (=3 3K"< -D5H%/ ): #;AZ=YPmYv.F^Uk*a0CD\"Vp)E`_|)l2yAUl"]w8"8'>1 + "'>W_u3xLhzA1?->%5!6%;3,C 5 1E%; u[0D6M/G;T'? '<5Jbu-H]m5Uk%/74 +0J '?)>/G F^G`dy?% NZ9M_2av8Wr.D_9NAT />%8#7/D-?*;&8, "2 '#4'(/$5 12A$)@+E#A 75R 3 +. ; 21+' 19P>T'%:+- 3 9/J!#= 5 !% +-+ .+A/# +#'    +%7&) )C -H%+/ $ +    +' "6#7# -?$BV7:S1 +#4I*K\9DW0nd*-! 232.AQ1 3E3A ( " H\8Zp7_s34Di|.v.Cgv bt&Uf!J`L\H[IY-9ET 90rSa-feR_ et->LmCPd+TBT?S FU JV) EIU%00-FR77AP=kxy0/6GQ al$zC&KZv(;$'2)CU8M!0)<Cg;`s"fCR(>K!%'0)'3>-&=T_z6Ic"CXEY#Oa1?O#G]1q\r`Nn:cNRkEZ56H+6E)`qJexCTf0GW,HZ3DU+oHas-iafJlzPQd.pRM_3v^/E+@I^7cvR=O)IV3BR,IY4Rc:jzM\m?Xi>Xk>oUw]Zk<_rAkNmJ|TwGXba_LA[?QJ63G9>674.~./09;6}3n(p/Mu<`b{6c|7ul6Xt)A\E]$Ka0Ia+f|:f5Xq)7OE\#Ti0kDdy9Ph&Kd'7S4N9O"Ia-sUuXSo5Qk5]yF4N@[-@[/>[06R);W.1M#5P&9Q(5H ?T'6LC["Sm2xTr3oJ}WcnEnBTs(Lj [y4[z6uOOn-8V.K FeKi ^}4?^?^BaSt"r?Xz%}Gw@k.Rl 7UM\ZhbSU`ZVTaWKPKUTRaQQLTMKSVNRUW[`X]X_maMH[bE_ge^clieaMWWkeepoXOl=DZekXqZx'^1KmNo E`j4j9d3h8Pk">XXt-h>WIbaz4oAZs1,Mg6d}OWsBC_,Ea(Vt8Hg&|U6PJc)Mf,]vQjHcz^^wXIbAMgDXsS]yWC^=MhGslZsVSmPPhOPfNI`FQgLpkUnMUpJb}QPlVv7eDeC[x;aDnJqKOq)Yz4b=RFRGS#0HhGm{@Wjgg=!85J88KB9O?9P>7O?-D8.E80H77M<9L8:N:>N<=O<;M8;N;DZEBZ>Ga:D_4VpFA\1?Z0A[.@\,Kf8NkBHc?;V3B[5Hf^6He@LeH)@-$90%87'::*:;$22(66'12(34.:9.=;/B94E;>NBdsf=NB=O@DZF@XAHaJOgNAV@OcV-=9)9:,;<2BC.=>.>=/?=3AA;LH;LI5IEMa\McUAXKNeZAXNMbTRfNuiŚuRzLi4U}w>u??`8W.LF7t'79cHg]`dTw_#`+Ps;[ `3[|*Tv Lq~C`%g*k.a'X| 8[nPr59 /2$='1-K+5S1+H(!9,%7)+0D/$<)C!1M&*F1J#&@.K8T&Rn=B[,1J(AHa( ;*#:-+ 5 NeJ5M/+C%&>! 8!/12K40I42K7(D0&B/7S?0I6//+$ +"+!$* 0$9&*A,+A,(@'.!<%<#!; .1N7545L65L6!6H4/Hd>6S3P3OKi18U$3P1M(C9 /J7V1OSs"8ZLkk7]w(5QLgMk#@^;X@]%2N,H8T"0NOl1^{E'B&?7. +#6';/L#%@7 *D/J-,B#8 7K&;j:E^QjI_Mc"8Ka+?V>Wb|<,H2K9Rb{8Hb": %1NhUn&)= 4 2&;0J'AYt5Qh*22DaVu,>]2R0P4R3RLi-*C;U-G8Q3JRl&DX#4F 1 . 2 6OrKPp"MnIhEa-G%<3+Nd95J'2 2*@,B)='<!6.#<3!:CZ*,E5M1- 7G`(8QC^_x/Ke!;T:U(?0H=V5L)DD^Nd3G %:'= 4%> $0G{YUn*Uj(bw;4F%/+>1F&=/I`25L( 8 +(A0D$: "9 .D i=^v.@X KaE] +]v {?_^u2 7.Ib$B\68S=WH](8J"9I"rL?STf)(97IAT5G,<$4&6 #3 (  $ +!) 5 "7. $. D\.0L-?[*"<,. ,/G!1I$7  ! 3).AK_0 +!# +$:,&:!*?+ +"4* -B'%:(,&:+ "  +)?$0J)# &4K/AU8 + + 8K2,B'$ 5I17E./&4+<%+?+0 +# + ,#3*   DTM\'GWL^%HY,4D'6 DP#&1+&Zo0Uj)!'l~.=K HY 7Dp{+8<V0Oww1|'am\iky'`lSbSaN[-59C S +Yk2bjx9( Yk2a(<+AMWp\z1h{&HQ_$_m2|\$, 4@-!. \t1jCKd$Kc'Mb-7H?Q&jGa4B`0eULg8@X+E]2k\xfVsA>X(Ib5CZ.;Q%Ga3\yB}aeIrTWu5hyWSr0]hC}[eCeGVn5xRLh$sHjM+hxJbgM`/I\/Qb2wUv2ct!cq#Rc|R]q:Yl=Ym?YpD\rI2G"DY3>Q-?O-?M,5E#VfDZlD_oC^nCN`3Uh;dvIIZ.[k>Ud9Sf6]n{C\NUYQio3|>r28IKDAB98?9A?FKCAIIINLXw>]!B^'E_,@X'BZ*.J/K0K "<7R+=W-D^18N!2F?T#nOoNKe&h{WXu2@ZoIXrmXv.Zx3Wv3Gf"Gd(A\"7R\w4kAEaC^Xr29Xb4Z|*Qr Ts":TZu!{8A92@QHQT\NR\WYWTXKaONHNHUDFEIJXY][\XXZQTJK=EJIGLUZ|/U\VTOG8?PUSSW^@q7\n#Wjks5Iv@[|-Dfd;oCSm$|OUt+`~8Up-Vp/Up1Up3>]7UB^C\FaGd]}/{KE]Jbr@a~1<[ tDi7\x*Xq)i8Yr#`|*w?~Dj53TnKC`',F ?XSl/C]Ja%USj%^u/FaMf 5IG^uCPoBa}!y6dOgcRs3'?%<&;!86O+D_79T+3O&*B/H";T)RlBId7:S$9Q"8M>P$?P%5F,@gKMf,Oi, ,C,A#8$<)B/D ';BV5/B%&92G(4I'-@:J(I[8g~VXmGg|ThSoZ.$37F./C'8L+5I#La7bvLLc5_xFVr?iTiQkRwZkJOm.hDA[8PBY!Sj0g~DTk2Mf.axDWr=`|IJf2|cr^:X(Hc3G_0}`vUBZ$lQFX-@Q*J_4Kb2_vDmRD\,=V,9Q.JaCD\?@Z>WoTVnSLcJwu`w]PgOG^GI_JOeS]r^J_KI_HLbK@X?HaDRnHj]dTmZpYaGgLzYpKb|Mi^z@8T*,E)5M6@V?;Q4;S.TiDZtGHd.>]!Gf.Fb-?\*Fd,bCjFb?St29WUo-i>uJhzECH#<=cºɹv{mqG\MQfUYn^=RCBVM=QEJ^P@SH9KE7HG;KF=KDCTG>QCLaOH]JK^KJZIBVAFZEGYH?R8D_6Gb2^xLId7Ke6Lh7Rn1#82%75#66%65/=>)77(55'44,<<+;83G:6G::K2G8cxhCZFTkVUhT=OC*94.<<0>=0?<4EC.==0>?6CFBQPEVO8N?ShVYp\g~pVmc\qk^tf}xkkEkAV~'Ag[(vFGi5U SHE@t,s-+F9R)4M#.D+A)B *B#*C'8N02F)1A&.=!5F)-A'0C(#8*0"#%$+&:(-.47$=%#>%7!32#;($<'%A&*D+#>"+F*,G,(D(,G**C&-G)3L/+E''?",E( 76 (,--,'B,!>&;#9 ";&":((@15* -%+%'!)$ !   #2%) #,+A-.F1 8$#?(.K3<%5!>'2N7!=&)46! 0$#%)1D[>3J,..6%7#1-G1/I4'C0!9()2$2#$?+. * *!'2%2J55 ! " ! !:!(A&+E)1I/,E*!<# 9$!;%0%>'%:&)<$h_-J"=eYSs@7T!-I-I /I9U%4Q #=0J (C'@ |Lh;Kkk<;V +Og~GVq Kf7 &A%?0 !3%8">)D8R#AZ+F_06N#?V$8M@V(+@6J"'Qh'_y'CJ4GPhg|30GTl=Rj:0G1IJ_0AW(%; 9MBX`w-cz-Vncy%q4@VTkXsE6mG`{6XxR]s8L]1 /_p78J DPIY(3C%89N.? #4,=(" !1"2/, /'9- "K`9VnC@Y(5P %? $). $+@"SiG>U2BQ5;P.hyR7J#- )Re4}\nAXt1@[5 %82+ *?& % $2 +! +  + + *C!,+-'; +!#7,A ($ 0,;!.#  +   +3@(' (-%  "GY,tNPSQa /.>Wh81@cn7,7HYn)\p1fy@BSDXSf{>>Rc L^ FV%4HVW` ;Gs"@tZ<1@bo)O]%Th(7JHZS57K08L0ZiMKZ=8H#apAix=Tfv91CJ_-at?tD{@s1^DT wD[p/Ka#Nb-yL`4Nd;TkE:M'BT/CT0AP,6F#>N,O`;XiB_pEEV,K\0hyMizNhyKtUL\-qRgvCjx?cxGV`YYTq5Hah%w+VEB=DANPMJTLIZMRMINPcg5Pl#Ur*uF_z0b}9\u7Nc-eyD\p4RyO]p8:L@ROa-{WPf-Xp5F`%=W#'A@\&hJFd*6R7P 9O"\|5Qp*Nk%iB\z83N6N4K9PLd0E^)=X!:U 9Q4LUo0[u1Qj%4JH\rJd7C_DaJgHdSm$Wp$UlJfo7IM|Q=ZJg*0M5QY'Um<|aJ`/7N.A@V/Qf>F[/Qi:To8tYRm:Nh:LeAAX?5J83H75G7ARC1B4FXJ1D7EWIEWJEZLLbQL`MK`MWmXVpXGbFHcAj`k]fUdR\JbNgM_~AoJmDuLkFTo<%<$;!:Q58P1?W0NdKe+I`%oFoFo99 þ˿ZmXplyw=S?CXIF]NKbOD[K9O@FZIH]L^pd>NF9HBEVN>PEBSG9M=SjZJaOM`LScSJZLOaMI^@G]1Tg8EDA_Jz&w(IVID6+<;PdeOr#T8=Upr%w8P7L)R7MeFJe>Oj)=9+=20D5;O>DYEZo`HYNAPGFWN=Q@WjWYiZCTH*:3/><-=:9IE6EB0>@2@C,995G=MaN.E)YqSWpU>VEK^WTd`TfY|mqOk>Xz-Ms&Ch6[ j_Ffu@Uw=^ Km<` DlRyPxm1f*[n2_,St/2N,C(,C.( ' +B\:5P,"=2% %-A)(#,=&(9 '7'4)8&4&8,@!!7"9*)(/./06!5+6N5!9 1%?(3+/!7 56'@$'@$%>"7!:$> '>"%<%= 2I..H*0I+3K07P90H3":$4)B+)E+&@&$?$#="&>(!8$&<+-  *+  #.#(  (7,&<)!7&":'2  6#/%6!,C.2+1* /(<*3(?',>+*%:,.D6"7'$7)4'/!. (3'+F55&*"3',##2)(+21I6F[I7L:3I6$=&&?' ;"155(A,8#7 2NdL ">Z.C`1Kl?,N "C9U&X&Lf7-G :024^w7g5>Fd?[D^I`^t+=W :W Oj']w$:uG_x u+x'Xpm)Ja$< 5\u7;V4MCb!2 0Dc|0 1H#"81H4K;R$-D,C 7 :N !8 3GjI>TNe XnJbCX +:PEY 61F_s3=XIb$6O+BF\#Se0 Xb?IZ!cu8->3B2D7IDW+3F-@ 2+ !' ! #6%7 !5 6 3 ++A. ( + + %6$6%;K&0C'93 ^Omr8*Vk?2 -"83#  &3%&    "#%=Q1%   '7M++> +Lat*%5G^-K]nhy8 8Kfy6gw4Uh/Yl3]p;Wl0Qe&H] =QD[7F ++t+Le>N .0? RZDRv*Qc_phz'NE3?3B L]?VRcu!n&I\#?> 8+9YJO_jhs8MY gv*>P ^l2_n*Zm/Se|0M5Gl#Vq5|/l$8J+<B_$kMDa"yV[h>;VNg,~_f}Fh}Yx5`;ȇUq/Z}Oo=OWr%\w*x@]Lm-o4q=mAzU4ND\*\tFF]24(;9K1FU:+9"8G,:I#|U^Ni}:_o/rHuNJ_$+@&:4G^sD[nDCU+P_68I0A5G#AU1BV1I]6O`8I[2;M"j{ON_1L].{\gvGll{DcbQr?Z}FFdF}@z8FNIBCLBCKKWKBGLIORSQDp5yImH{VLh$oEg>d~>I_(EX&sNtEZL`*3DQa3Re5DY%F]&:RB\">Z!>^%^|AWv:4S]zH&A)?G`/sTw\Ea0q[8Rn8kQRo5Nl-q4Q~[zW1K pM^z9yOlEd|C@Z0J1N6Sf@\!%? +*A1H5J1CBT8M>WOh([w0d7LgYs&\u&]w"l(IKv%l@<< Kc,hEE^5L Md%B\a}2Tq#GbMh#Sn)f6c~0Ys&t@Si7i;d;Jh'Tr1\y:Ca#8UId+8R;SVm2?W;T;SF_ Yr0GaRm&Ni(?Y@U]s5F[Ul,Hb ^w4]w5Qk'e{6g}:n9f~.Un?XCar;f,g-l5r@bB4 +(?):"#7,?$(?!%=.I!->W1QhD+ +*=(<-A8M&E[.K],La,E]#Wn37O>XQl-Ro1_}^zFwYoN~cPgB%8$4H=5D>V/&>7O11K'SkCCY.uOgzAnzNTX;<;&~mɯslMaLQhTE^HH`G;S;:ROFO`W@RFI[ML`Tqzt{K_Mct^EV8AW.[s@Uo0t=p%5-1.@?{+38RC}+-=8>=:+=T`8p;Up+h;R6MH_.%> *@0C-5I43F54E75H4?R=K`CZoPDY8QfFNeCXqJSnDSoBXsD^yKQk=Pk=Je9Jd=Jb@QiH0H+);+#4-':..B4,D2&>-%;.$9232"45$55#32+9:*;;$64';:&:8&;8*9:0E<=SC@VDOeUHYN3C:,814C6UcWcpe5C89F?6D>-;6IVR,+A-)0F2)0#(!9&)?,3)<')<(#5#*=. &   +* %:.,2%&;.#7+(#% 5)1 / %'/#7(3!!7$3!6!31-.(>C[:2K':U'=Y+@]46T**F*F$?6 7&Ga+^u;3K _v3=YB^0Lc|9H_AVF]A\=X?YC[!1I';3C!(8,!1/+>';,@, 2%9$@.G 6+G(E+I9U!$<+)AMf+;U5O b/a"Jfp&}2H0JlCkCUq-B^k4L(> 7NG])9PJ`*+C6MUo4D`eB5O ' 'Od+Mk);U-E :O-B (?0%=;R &=1)B#<-G/I Uq@3K>Z')C9T"Ia.2H$&&;.&A(F*H;W#E\+3F-BKb3,A'<%:0:N;PXm48K4F_nB$4 #2'CT5 +$ $ * "6,3'=!7,6 !:/E.E2 +B0G9O.D6J%:AUbw-j7Wm#Xl*Pg J`CY6O 9V 1M)E -3HSd18H\k1j}EM_!*=/E@W!9N+@(* +'5#-=DR/=N&^oL):'4F(M]B!20E#,C'>-6H",<$2+% . / CS03';-# +C^)F^&=7P,'= !#8 +*  + "DP7uACj`uSRo.\WzoF\u4c{>xTfFXt9|Yv:Ur5Xt4qJyLQpAYLMuo-u8Ze1j=[v2rNVo<;R$L`;CV:%7=O3"3#36Idv:g}6r?{N]s4by:cy9[p+eu.ft/et4yPN`*cu@`t:ZR8H\k.Zl8-; br:{SoO7K"Nd>BW2GY4N_;[mDy`O`3oRnevHsUguER`)tIZk,pDTyHxCe}-i2b{(MHcHHGCJD@OJ@:@HBC@FFAGa|)>]Nn._}@Pi-Tm0`z9Kd$H^'?S"6K.C +Nc+N`-;LYw7E`&A[ Qi0Rk2Sm2B\fEWq;Nf3@X(F`-A](8S5O3KOd42D2CJ[.L[.?Q!4IqJoG^w4Rk$Rj e|.Snf#r)PlYx'g4{K.NRr0Zy:Xw7Cc"Sr1@]"0M7Q5ME^$H`#6N4LE\Pj&GcTp+_x92JOd-Pd+AV4Nh&Kd!d~7Tp'Pl#]s0K`o;F]^y5Vu-c3Eb_0`6vS *9Q+/?)-<'7H*-A#3G,:S0:T0*B%>#"3#3'80B?R%:OMe,Kc%f=iB>XLf(hFsNXr58JDU*/?9N Oi1\y>Ur:2"7$:QhD|nZrI^wM9O)1F#2D&CR76E+0 :O)k['86E.+/?%GW:M^A5F*W Ph8mT8PNd1Vl:AV'G[-8K!Nd:@V+Sj;gLnMoLyVp8Q$DY0EZ48M$`uMr^?S-L`<-@!7J(;T&Yr=rR}d=Q..?/1A9:IELZTGUR9HGFVU@PO6IG:MI.C<7M@Yp`ShY>SB[s^WpVOlKcZ]~Ol_QsD\~QLmBMk>Pma@nEtFpGYs@8R+7Q-D_8PjAF^2O==P>DX=I_=_uOVlFYrKWpHXqIXsK\wO]wPShD?V0H`6BX2J_?0G0AT@3F5&70%46%5/(:4(<4&;1%:50.10%79&98#86%66(;9%86-=UGD]Nrz8D>3=9,80Zh[\kX*9 8H,0HX:^nR*>$1D+d|]QlCfOw`t]mYo]rb{mDW64K"Mi6qR[|8Y|1>`Rt/9\Nr,Uz2Hi@bAcDdgk9V{=s(w){-Sl'Jk h(i,wa?bHja,p@Qr%Ge$A\(Ic7Ka7G_3Mc4iSH_0Ib2_vGWn?^sGkPg:Mf7E_1A\-Ql>?Y.D]4Lg=XuGeRMg8_zKOi:b|O0I 0I;T..G 4K$Lc<9P)?V.E]44O&1L*4P0=X92K00I.2M/1J,%=1K+;U41I):R/9P,-B#'=!6EX42G!RfALb9_wMmYH`4RjAMi?@[/1KJe9Lg;Ic7H`8\tLp^9R)Id:Lg;@Z38S+Id3L,/H(*@&!73' !!" +%&-.A+%7$*'9'->-!&  , -)&@)-H24 4M8#:&-41G20"5!0&!"!0!%-"0#-/!'#"!3'+@4(<0 4(-"%+!*:0/A5%7+.(?)#8#.261K%0J3N#?X0Z*6S"7 5QnEiB_x;D\#u2KAY,C(lEQ6RJeF_Oe!H\F^@X*E +9Q=T!EW*#."08I*/1E$5I(CW8;O07 CY3J`8Nh8Lh54RGc2"</c|KZu;Lk+,I .M +8W 6U9[h%'ERn\x4Ig"Fd>)F)D 2NHf 2NSps!0/=->$9&@*HOh.7K7Q2K +5N az.Gd+F1N;V>\6Q3K$<>U"5K'@!9 73M:5UYx5>X$;K`$4O:S#;3GAVH_(Wm?),D/G'A2 9 &38 +6 1L9 +Mh87N &;!0,$4K%)CA^,&C2L9P#!6 +1G-,B )2CI[+/:N$7.=0A3C+: +&,  % ( ''>6M#1 3 3 !6U6M%9!0#4&5 /0/B + +& 4*I]8Yr6m[v1E_`-D*EC\ k?CY! 3E + + ! 7>.f:4PgTp +BnQg  ASA*=!;+F"+B#Ng?:P&2D]%Us0I"-%.Q`.6F*: ++-A +Pa~QPWGzu/) !2Q}6oYk]siO`:F FR IZ;MSQd Oa l&dzD??MGZko~+ix&XfHWdu-rX1@]4He<XnJBU1Pd?L`7Oa5WhUu0`C'B +V)Th;3E)8 ;MMd,Mh'Zz1Rs'1Ol4~CSNl=TOjRn"GfVw"`,Xy*4V0Q=\"<["=[Ec&Ts7C_&8E`(D]#3K?Y=VC^D_m>D`QI^*Qh0:Q>VMg#Nh#Wq+_y6Zt1=VU.H .H^|:IiLjLggB7R 5.$4;I08K-6J.&;0G*3K+)A#(<$)9#/=$(7(9'7Pa=FX-K`/Pg0Ia$Oi'Zv3B\>W[t8B[Xr1EZ$^pB1A+@F_*eJJg/=T,&7':+@!I_<^yQ`zRi]C\9&9I[;(8#4/ +DZ45G*9F1".1?&>O45E(&69I+0L[7Td9i{JfCrHc(4K9Q u]}a=S$-C@U(XhA>P*QbP0,?0FNd;H]8*=5D*CW:8O(:P#>U'Rf@7I1(7,/:41?9'85'649HHAQQG[X9LL"555HFPg_;OGUi]CUGI]KKcIKeEeWgUHg7Aa1cVWwMXxLKj:eQeI`:U+5Q(E`8C\2Jc3^zEXv?Mh5Qh9e{RGY=IY<=J1N\?EU1`sF_sATe3\k7Q]4zc~޼skLdFXrRTnOXqVTlUorUiW_s_M_EFV>]nWWiWrwqr5H*_rFe{DBu7EKz3o,q0o.o-h*h+[}j,l+Opt'n gz+ny#4P79:ECO@5K4NFRs|L*ABV1BW?EXBG\>Od?[sHZqHTnCUnFNf?RjGOhIBU:8N-0D 0G0G;O+(;*L]R8I?*97&58+:9)86+>9&<74210!351353#76$97,><0??,<9,:85EB/A9&:.0F9bym[jdALE3?6;K?xhvOg7o7kQb~:b|Cu^cMUu;cCbEbF\wGVo?lQgjLqTmNUu3A`0O Jk'fCSw1hELl*Mm,On*Ki&Gd Nm'uJ_*z;~8v-7?=w7a"g*\z*K^|+d78VKk)AdPp%Ut#Y{*Uv*Lj'C_$@[%9T!]u=pNnMxV{WgDkHZq5lHlJsStQYs9Zs:Yr;Vo9[u>Yr?Qm<^yGXs?bzG`yDb{ISk9Up>s\[wDZvCUr?\wF\xDeLfPTqETqD\yNDa6D_5Mh=C\1Sm@VpBKc6f~ORh:Lb3H_2AX*CZ-I_,axCyZpQhHkNmPa{E[w?`~FqXcJcIcHgO^zEgMhNaH^{Fb~L^yGr[]zGYuIYsHNi=Fa7@[3MgAMfC:T0NiG3N+-F'0H+1J,2J-2M.&@"9R3U8(% $:&U/GY5B^(Hh):X2N=V ;Q"4 .4I" 5>U)C^*^|9Ji Fc :RI`&Fb!6N$82E8MH`&F^%;SKd-H`0oZ>X/(AQk?.I!Jd:ShB@V/1G!"8/E. ' +JU?.@!, +*AC\2Pj>9*"8 7PNf-\r=;Q,B/E3J637I '95D !@O't^"  +$ +-.D!/+2#; ,ABV2&: 5#:'<*E146NkKE[#-B Xm41Wn7?ZF_#B[3M `{5h;D`Ki&If%Nl(e>1(<8L=QDW6J/B)<3H"*@G\.;S *C 6 3 $'3F]m?=SXo/Nd)>Vaz>2J%?8 ; 6,Rk83-%"5!$/ =S..*>^rMPg?Pd?l>U(>8N9N+=3">Qn 5t$2%   +(" %7GX2Yt1a{8Sq!;. ?BUӐIS<  &6"=630FA["yB+Mu.TSm8-D!8=Q65G-DW;L_;Lc2az==W]v3`x5mBg?g~AEAHGKJHCMKFJD[b},3R +Vt5D_&Ke,Og-H_"Mc#{RRf.cx=X`x8nH,Ad}CqMaYUr2Up6@^':X"6SY,0I"+DG\3C^1Mh;If8Ba1]L_HKk1aEXw;Ed)Ol2Jc0)?Qg9t[*"9 +=S!:QI^)Xo>E[,6MVo7"<4M;SI^(5I5F5D8H(<,B8N 0H7N &=8P 2H>Q$=S"*A +9V`4QDcJ_|'\y$h1[x'd0MjSr%Ro!\|%b)nV[t7Ia#`y"/?!7E'CQ.N]1N`/f{Cd|AkH>W!TlvUlqTMf65H%,:!hyVpYk~U>P++5B+O_DZoJf|TAV0&9"8G?+65>IG6D@#33$42-=:Rc`?PO0@A%5; 47/EC>SNM^WBQIL]NDZA[sSTnFlWs^Sp?WtDPnAWsHYwJPp@Vu@Sr5hEmHjFQp7kWF_9H`=KaE0><,;9*947F?/A42B7/@7IZSXjaBR86K)x[^\?oM~[zTUoZzLacUsExIvJpD~QxNpEZ|SkDkIoKrLuNVpHkEwNiAoHpKc|=e~@pJmJiEdDUr6hK]y?jK\vMn;Ts@ZyHTpBVsFYvJSpDUrGPmATqDEd:?\1Gc9Da9Hc=D^8QlDUnFOhBTnIMhBDb<;Y4:Z3;[0;\2>b4Di>7\0HjA=]6BT0 2@#%2*$"5.5K|7 .B/%5( !6+87$<,&>jJRf//B@U(@V$3JC[!Xr7/G 'A'@Ia'3K-Ef}HOh,[jAXp*(=I]&Rg7.D6J (<-B,B=W"8)@2.1 !;O;Rez8^y56M"< +%,D=V*5P +D[s=1H*A1 +#4:N,9M('<-B#$5 3+ Wes+':AR' )2d=9H>K^jr3l|.z2);eu#u._tat9Mq50q#N_Vix(\mT_Taw8U;Hr|/u1Xh?S Rg 13H.B%7,?#6-DFa47Q(4K%*Bc|Vrg@^5(C:T*8T'?Z,Oj:Oj<>Y+C_/]yFOh4)@ =V w]Vs;Ie.Tp6lIi[Vr%Qp%lEbLj-Ut6He2)CB](VqQm~8R=Bjw"6H;[Ki!46H*BS5Od=buMXk=dtG]m@N^2fuMP`4N^.hzD\uI]t0e|7{JyHf~2l9Rp3z6NLRE=7CDAOFF;NNRIRCIMNuEe>Ol,?]2KE[wK]q1-A[Wo(E_lCAYjBMk(Oo&6UIh$[x:.J7 (D2M")B*A6L)RhA6M%5N&9T*$AVtDTs=bJ'D +UAT8JRd6=NOc,Yn3AWXn/DXE[DZUm"Yu!q4j*|;_y$Yq"Wn"Yq$l3NdVj}U&Mg6.G56J!Td=(7#4J[3Ob7E[+{b)C(A-HKh5vaF\-F^)Tn.k@{Jt@l8d2MjVs!i2Kh_~/Zx+d0k/d&O3Q 'B /H/J*E2N5T:YNj4(D7P(%<">T8,>$)&!3!20?%"06G#CU-GZ,2G:Pc{AC[ 3IAY%Tk6ex?zQIfWn55J-D)AJd5UmCH]<"8G+:I+/=Td7Yj9av?`x?_w>pRRj8Ke5^wGJa2^uJ.D3+;I26G#]nDoUctN6D(5B*>M1[oF / /"%05+6>1??/=:@NP.<<8FEP`\@PO,0-8#3=)>B$87APNQ:9K03D!I]%JNk721A93D;/?6*:5&:4+>8+A<(<800#65"45$47%22'66$33->=(<7);5)=62D=4E=);6/@=GVSQaUDW;jUpSuXWJA2)(0;FSnVr._{:Lf)Oj5bxGd}JlRlQ`~BkJpKuIpHb=;Z\y:h8W;Z5Q9V6S>[$Ed%Pp,Hn%Ov(c3zFv=Qu`(h5IhGc@]NnTvU,J(EKk#g=nAyG|L{KV[^[XYYUvDLIR[uBN~HLxDQPPQX}JQyAPGHNp>}L~OxJ{NyLwKpCXuHyJyKS[_UYvFRyE|I|HP|Kl>xI{MvGpAlA\{3rIf=nElBmBlDnElCc<`~9iBhBeA\|8bAeCfEnKjIpO[{;`AgIX{:uX]BTv9_DSq8Vu?WuAMk8Ig4Qo:If4>\,Jh7>[.:W)D_/E`1Hg2B_/Ge2Ki6Ml3+3 :U/J.GNk&@a4Y>Vu+[{ ^w/$ !2 BZ#SkRm HbSoIe>Y?\4S"@*E29O#0A$+ MZ<& - )E](Oi"C[/I /I +A^;V@W[o8K`']r7?V)B 9U%=V$%) .- 0 !4 4)8"3+) )<&;"-* -.)%+E&@"= +1KHa'Nd,5L51HTm6Pf75K CW5 #+= ++6"DT,,<( -0 2 %;?U$[u:+, */-&';.J +E*A-2+B;P G[*AS!9L9M.3K(>/E 5IJ^## .N9LRg.0D ?SOao!,Lt(sYlq03>KUb;H_P{ q}kwXv }gpV_w !.|}M'3JV +YeKVFT dt!JWjcu#Xk]rx/j~&Xlj|&H>v+2KhwM]q'gx =KALhq#K]jEY}@%7 &9E[8(=.B!I_9azOE`1.I]xQ?Y2/J!Hc7@\057M%Pk>iYHe6ucZzIlWKg1opV0G5MEaHVv:Nj.|UxeNo"rOeiPB^(Vp?Rl@Sn?5Oc{FXp7Jd%zPeB_B][n?jSt%^{4e8YiQm#Uo+Ib*6M!-B%-='2D*2F#=U'?U#F]&mF]q.Rh"i~?mIWo9Xo9Lc*_w=Ja)iGZp7]q8CTfw-E{4v2{2Ar4bn8,VfFf{ZUlEXkBZj@Vf:Xh=Uf:vW_o@]l9Y{OXl,jAe|p6RCCE9Yu5Wq1Oi)qLVs6B`&2R<[(3%<&;$9@V29R*@[18R)3N"Je5=Y)VsDfQ[{Az]Db%C]$v[CW(CV),@cyKCY*`clG6O:QI`&p;UAZI`&;OFZ"GY%i{I9JO_-4EPc*^u5]w2c}3@WI_~Hc)Yu s<@] 6N^t,\r*F[Ukt7r/~1>MWPeU^Y]XXURSOVJSY[IQMLOKOOLOFIJQJQOPVTQXXWWJOdb5Gd <A\%E`)5O9S&W)B]07Q"3LC])>U&/C';4E&`xK)AiP2MC_.7R"7U"2OA_.C].5OZv9If#Us*\z.rDk:B^[y$b,Gfm@a{0Vq^z#`#uA@_9T%$?6(>4K.H*D8R">Y(1J=UGe'Hd$Uo.,Ec0zDRq%0L '@ .E5I:3G=9NC 3(4F86G62C,>R3@O,CQ*?T$E_&z\u]8G+'3! -9G.6F&3B?N*?O+8G Qb5l~JoHXp2_c~ED[+)?`uK.B.B6K".C=Q(>T+Lb67J ((AJ@%3ap[VdT)$)/!.6=LN&431>@+89,795@C(48)/'0:)6=9LN/@>+;83C@DSL7H;J]CcwPjQ_vFfN`zJvb[wKGb;3N'6Q$VsAgLUv3Ln)hLKc8,=5C)TaGJY=K\IPQk3Mg@QhDh~Wi}SnTgOYrFNg>2K'-&9..?8*=8-C=&@8#83#65*:9#02%24(69#12'56'642B>3DC3DA4D=\!?b Bdi?vAh2IVs#'CXs(g4QmJkLlXx*oArDQzHs@r?PKIKW{GMK^\LKNMPNLUWNFHVTOZRMZU\_KWTMRUORSWZZQ^SNOTSv<|ALKO{DKQ{E~KvCq?wBwDp>uDn>|KvDqAoAj>qEpClAj@hAd>c;`9Zy2iEhBYz4b>a>Tz4Y~8`ATv7Rr6Ur:Qn5Mi3Li5Kh3Mj4Jf1Ok6Yt@Ro8Tq:Om7Nl6Li3Nl7Ll5Ln7Os:Io4[DYCKo3\E\HUuD/P,J'=* 4#9%5 &'9*,+. ! +  ( "2)#4*( $7+1%8$)C/#=('@*3K5C[E-F/3L6'F' >"7 #<%11>Y4?[33P*:X0&AA^$k>y>i2Ys*Vq"e)g,IdWo)pD*F^$>YGd1NRm2?]!5J/F)B +3K;TFa&*E*C5J%8"6%- #7.=+:/ +# ' +-D,C2 +&:"40C(3B( /)( 1 ">$A =Z%E^)@X#+A +F](>S&/);$62C`tOBW/#2 y[cu=*;/>&$2"1 3E)=G_.&: !#5&9(986O+208N"*?Qe47K1D->DU#;Ofz>}V;Q"7QbWi(Xi3=L$Wn6mC\w*[s4?U!78 6OĆX?^ρ"+xu9U.B0AQ):DY!/ETks*y#w Xh +[i w:bo +$ ?L\f-C\_G8E pCfy?Pe*CS6HYg#IGV +k~bsu(gm5AP]]k>JMZS`es\lP`Od WkXmv,]pex%bo!O[ {8VdHX ~/m}}-:;Tlz!U'AX&Mf-Lf)=WKf%Kg%8Rc|6\z.tIuN>]$AXv,xOVr.A^Qj4F^33F(+:N,E[/D])b{GE\)?Rk|C\q2Wm1Pd.Oc2Pe3Xl8aw?Rh0Md&ez;au4XYbs!@n-crFOOIr:*? J[:CW4Mf?L_5O_4Sa8`qDnhyFGW!bs;wLwJlBkCoDvFW|@XD[UPM@=>BD@=@@D?A?@ENSG^CDGGHO@HGGLLF?LI3SOeR%E7WMk.>Z!A^(8S"4Q!9 Rn@:U(,I8S"D\*9P"1G#7,B3J?X'Xq?/J7R!;W$)G;Z!+G.J(B2ONl*Zw31N +Ro(oA;V +g2}Hd1Qn"Hcf1c,g-_,+J5Q!*F2K 1E -D-D)C1K@Y*/F?Y$Eb'(Oe=.C!38Q0LgD;U55L-/D) 41E)MaD=R2J_AVkN9N->O/BS1, .B8L&->.?L`3\q>:RLb*3J:Q3J`x8oI0M ?Z?[ C^*Mi-<:H6IEb(2B!$ ' (8O)H[4.D@U.AV.Ul?:O!/B8O!_vEXr8vTyYGb.DY5#4(4#)2%*6'MXJ$$%!,(),&/'08CI'36+9<'48*496?E$, )0"*1%061@A?OL6F@1A>9HCHVLN]GbrPeyKYn@czKjQTn=Ke7@Z4;U/Ha7VqATr:`~@dF`{I7J%;G/=G7CL;FO>SaLN]H,;)-7++2*(/(%,&!)!7A/FV4ZkBXk>>Q(JY6csU|vjεʅы֝¦̭ըݥćƃ䦜zhC|Ii/d,s:p6j.o0s4}>y:b'Kq3BC|5?2:H>@y*fu(cC+p9u1q.z6s*t 5BUg0kMe.Tq2XrDayMz`d|clShQjUjUTlD`wQBW-2IUj%n({*h)0D fy`EZ?5I3+=3/D8.E;,C<#;6#543CB&44&45)7;'58,:;"21.=<1DB2DD6F?M\SM]Pxulg>d/d.d"Uveko5n}!0Dj%\y-oLWs;Kh2Lg4To<_vCgI_y=nIjC^}6a}7Zx4]|9dCWw<-L9W")D)H.L)F)G.K$B)H Qq/]*St vASr%/IvE~JPlkVZ{HuAMMy?NQOJMp6r9~Cz?w8BRMBFNGI@JLGPKNTLKSYSYEZMO[JRTJKNNFVPJ[MUMRV[PISMJQONXIQRKKNKrAQ~M~LPzJoBc8nBoB`5mBgg?^8Z~6\|9\|:a~AXs9Zu:Xv<^z@dFZv;Xt;\x?dI]|CWt=Yw?^{CPm6_}DYx=\~CSw:W|=Rw9_F_FTs;\{HZyGTqC&>,C$6(?)4'9' 1!"/ '&3(#8," & 5(* +&+ '*0 -G3)C-!8#+C+#9 .24$?!0.0=X:+E#9*E =W1/K&*D(D#A,J 0N 9TUs0Sp&Up%A[-D:P1H;R^wD8P.D(>*A0/K%A 5-G60H)A0E "6  $*-4E^<4J(* + + 3A-6C1 "!! +&9.G0KIg8+H\y=jA1QmT+G\/Jb)DXH]"BW(0 $ /+8YiC 30 +(=)% (:! 5+ .!7.F! ;. 9O%j|L2EAY 3J "6AQ:K 5H BYxD;O dvr-y=s?[p69Q/I;VDY0I`xEC[,3K?X!:T5N7Pe:8T Oj'To*&@1F[p/C^.G DZ&9QCWSf00A-<EX/.A);% "(9(80B%.D9P-(';$ 6H$Ц[gB'8 +): GV21? $DX/k|9:Sg4LBdr#BJ  -&7%KWpa{v{rI"Hq$ E1H %:t'vOd$+?( L[$Td8ET+9L@R >Q9Q Y,f iz8]l2Ubesa3?(q{s!DOz;WEUOb%0E 7JGZ!@NWb(?J BL.4br\m]lfrO'/*^qN97> &cmdnIV' rs:Xkv~9CR ]lHXiwx(Ygdu]n^r`sVkn*i{)bo coan#Me7}aUq6(D 7O7L5FJ]+;QPe.3.B?R#J])BWRi#i6m9gz0I{@y=t9l|*R`77\`:QCB;:>;??8E?CdCFc'=ZIa&Ia%Ia%Ro4Qo49TD_+8P;P"CW*0CPe8Jc4@Z+.I&=(>"4"6avTKc<6P(C_5>]0a~P]zK3P"!?(FqT;ZUs<[xCKe39P .ELc4Qi9G_,H`'xU~ZeoQTp6=Y6OMSGGA[J@IHKNHOQJGN@LMB +C^+C_(D`%0M8U;W>W`z1wG|H_*g7HcTo$d2Oe06T,I:V&&?+?6M"/G3N7Q"7Q!2J1IXk9g~D}[Sk1>WTk/d=|UwSZx:He+Vr>Mi<[xRfc8L73J55K6/#8&)AU:L_A)>Vf=>N"H^*iJUp5H_,6J;P*2F!>R-Ti@La3CY*Tj9-B;L#iyLcu@sQqR9Q:M"'93-A(;0E BW/=R*YmE7K!DY)YpAQi6bEiI[x>|fC[6'9%4B5 +%".)$$##$!*'/$+'#/%0&28+6:'58)69"-3(38 )0$-4$.5/:?;EG?KK2?<>KIAKL7B;ISF^mR^pJi|UezP]tCjMXrA[vHZsKAX1Nej*_S`Ie'j*w1lr"y,),{.x0Ytt&LKr"o&|:r1[{b|-?I4~$t$INH`z8iL{_|_nrq[_uHc{EmOg~P\s>ScKc q.OatD2IAX([sGnd6H92H92H;0E>,A=#55-?A(69)78(7:'39&48->>*=;/B@8KJ=LMO`WJ\L]zCUx.Ej_+B6_gqGknp'@a~$Vq,@[!Ia.Rh;bxIRi6]t>]w;_y:c~$,G+'A&-!6?[6,I"1L&;T,(C*D %@3OsVSp6To35R)E -Li>>W9P7N:P"$: )@9P >W!/I|_[z<@]%Oh=!!< [r[m>\|.Nh"2MAX$?%>/2jC1MF`}EF7Ke +U LW93DWo1Wo-B]Lj!6T9U9U7R*?BV*:L%*"$5Vh9>OL`.^v=Qi-Tn-C]B[.G +/G 'C0JRm7Fa)@[&92#;2, %5%:DY1CY14 ("408 )C.G(*"6%7$8"5(1J"Kf7,EC['Gb),=, 3.- ( 3I5M% $0-APc3E]!Mh&SYRcQb0Bbu+L^ImI_!_w.[s4+A %=Ne3F_&8R/I +8O Zr. &bzFa}Aa~;3Q2M1F5EBS(>QbuB=ACCS,5M&'>4)<!42E'J_:?W0;U.@\1A_38T(SpD7T%Eb2xa\y=gI5S$@ Ni9ZsE4K?V)Gb3Wp@Rk2@Z Vs5Qo/Ml+rTqR!=#: GY*9J&8 #7[o84G3DJ\&H]Ri!k4Yqc{(k/e*PoPm>XWr':S [s%Jk-i}#|7p*y-v(RJKLTEWFAOAMMHANG?CNG:BCD:;F?@PT@LNIXGIIWI8Jz:Xp([w4B_ Y06Q'6Q$1L6Q#(A2K%"8;P+9N(?T-)>3IPj8x]^{?B_%5R*EMg5Je1Up;'B 5QTo2Oi(Lf!Qm!Tu Zy%a~,MhUs(k7l2|ERp0-3O#+C@U-1IG_11J4J5L;R"/EF](+B [n:O`39J+> +Lc%Zp1%=8PC['AW$bvAH]%La&@T4F1DAVlM7M VkBDZ(=T:M$!3%6,$7.B!@V3-FB_0n[He3,G@X0Ha97O)]tOLa::N'3H"9O'3I"9N'8L%2C5D(; FZ$Uj1AW ay@Kb(Pi(Zy4mH_}>Tq7So;Lf$J`D?U9:R6-D(1E'QgE3G!F^4Ma4?V#H`*Zw;Ql1h~I5HUlB;P'AW-Of:Xn>Yp>G],Tj<:L"Zl>Wl6Yq9mOQi:K^9/A*>4H$H\7+@:O$+@EZ0La4CY(@U%Kc0Tr7dHSp8Pn>5P**/( -$&#($.'0"+&$)%02'67+.(.(/(2;",4&/8(29/:?MVX?II>HF9BC?HJ1;9EOGDO>iy^esURa>Tg=auE`uFSkM(L]8=M-3A'?L3P\B_iJu{aƳΑ\Nlнˍ}ΣН֤grqAxP^<]Lj"b5vDI|@On-TvAu7u:\&c0m6d&q,CA,)H=~;g'Z?WEx0_~k,d(l-q'EE3nDER|DsKv[mȾӬn}cdfwxMt:nt-Wt3BAt;xAX&H\>0F/4J72F:-A8/C?!34(:;&55*793@D%4;&5:.>>';9.A@)=WMd.Ja4GZ1Nc7Ri6>VC] Ok,Jf%Lh&Pi'pH^qMvUuUuVZz<^CeQq2cAYx5h{TXR{Kj8a/MHp6{=z=|ABI}=@G8N|7<}7C:CBQOKRLKJFKNSD?FHGGPQWJSOMNEDGLOWUUSPGLHGIDEMFEFIIKRSEM{CQMGFLyC{Dw@|F{Ep?no@sEpApBoErHnGjEiH[w:Wu4b?nK\y;Yu8[x!#< 64.G)2K,%A>Z2.J$0N(1K#(A.I)6O/5P)&@8^yE:*ETn-_y7Xp3S0@R#:Og;Ogf'CKu:fw-TeYfy2dye}d|"j3*[r,k=QgYn.)@=T'9P/GNh+Vn1?TKa!g}?Ia#Me*1F BW.A +5MBY$:P#9 0H/B!3 0?* +%% + ' + +4& +<$8.=$3=T=QMdzD5ShIeE +[JZBT!AOUwKX1t1U@;]fis&E$*n};7R[iXh(|%HK@5DTahy.ao"={7]hKW `o~>Ggz#Mf~ZF^8@Y2ׯLe;te5L$?U.s2E-A2I9R"Ib07OAW"9O;Ph~BmEa|8A^YtN3PdEcB_;Ww4lR42ZpJUl@;U2J)>L]4cuAi~X-(B2L#0E 8H(&4.=!0->$84J$#8'7'7);0B"9N)E\68R+1L#>[/8T*&B8T)If4Fb/`~DGd*Db*2P0K5P":S))B,BMe::Q!;T`}A 7I1F-F D[AXTi)Ti"5Ki7^v,Yp&g1YqKo7q9q18;@L26GPFM@CHM=@LIIYE4=2:C>~=~%@"=8R)4M%%='?,A2E".B+?8M$9R"6O0M3K%CW/;S%@X(Jb3,CMc6Yo?BW&Oe5Xl:2E;L$/?!/ CV"Od.D[)8N5JT8O1 &ub\qCRc=(2@'" *)<#5*ACa1Pm[(Fd6-I#$</ER9AO6'5  !% &&.(/"(%)&(()#13*9=!(#*,3= )'0&-KUYFPQCPO@KK@KKEPP2;<9D@7@;ITEFS@;H1TaAZjCVh>ViA^pLP`CVdJVeL>P7AS:CQ;;G5,3$06(AG7dkYZcKdpXLZB6B1* $,9/^n_uon}avdcrUyhܪ۫jxY>TSҸ~{礼ɞƝduE򽐤eՍVIk"BdEe&B`!_}3zLJx:OOHf'Ko=i8}OzIHgGI7w%h}5l#_~RjNd/K_A4L/nm>RC1E=1D@':8#65"63.==*:;)8=,;<3GC)<9,B=0DA7ED3E9YqUwhgKf@Rw&FoT|` ex+DT| JqOsSyU{Ovz&{-IgG_#Oe:G]3Ka2Lg2Tq7Vt6wU}Z}YrJmDixSjixVtQ[uO|RZ_xKuHl>g7h6p:MLDH~@DGHI>B<=?<=B>=@@EG>GGIHQPN[aXLLNHKMG|@GIGHLOKWVVFPSWMJPUHDUJNMNLG|@YLFIJF{DJ{BI~FzDvB~JyGuGjjBvOkCjCrLlEsNlHa|?hHjFjGhE]v=Xp8`vA^vCHa0G[09O*9L,/=#$/!.+6#$0+(..$5##1**. 2"%!1)5D0%3;Q90D+5G,2E(2F(/C(,C($>!6'>"(A$IcAB^81M$0L%*I&,E"$>5/52L&8 B],$=XKc&%9Qe/I[+8J(: +avC7MRn8=[0P!? E`.+D#6 1- &   "(7%&    +  +"   ! '4"6B0&#1-* %Gb*Gd"Sn&D`c{:5M%>Ni*g":*>.CSi4Sj/Jb$6R)F )G :X7U6S3OLj.@_"$B'B8 +A6M#(B(A3(?!7((?.F$<Ja<6M( ! +#"=$='@5 +$>'Ib;8M%I`3$:&#5'=5&2M4M8THd&@^Hf(Tp56SD^)&@ ,GLf27P&= yP2L +%<q/rA '(!   +*>0E/E0 2I"&=&'I]'6K *??V*AKah~+Pe@U[mGW/BWn]uVpx;_v(o:Tk*;N^ Pa-@S"CW',A '<6KRd(Nc(@W%;7M"9+> +/A0D*A/E)?1H0A(9=K,-$ (>Q6.B!6M#9L*3D#45A"'47D/]u.$<Zo qvO\s,_v x8 ?Vjh ^wV;ezg_v `o'Zng{8* >WYs MhtL$ P]\fTZ?B=*>EAHR[.nqot929=YR+n|ry"pyN{!p9Viry+7ELfu';Hp|)`jny-iu*K^ e{$[pXker%:Gy8KZa{8FY#+@/ 3A'8E,6E+3G+6L'~nMf>>W/B[4b|TVpI1J#AY2K`:Qg@6L&F]7'@G`9A\3F^36O!T)CV,GX.JY.{^Te8^q?gxQf|>nEl~WwOg?d|8zM{Jh2Im1GNNGJCFLBGLKOHHKOHVL}Bd8Ok+Xt:?X!H_'8O6QSqH_8?Z1=X.XuI5N)@X43M&C\0Id3C^*fSp:/J1L*D/F8O%D\3>T+Of99R y[hF{Vd?Kj'kL2I;Pi}E`oFA[Oi&^v/m4s:Lej:Ng"B]o9h/a}*UoSlQi\s$i.e~$l*[ql*=K7;:=u5;>4LFPJG@Fs(h&_y,Hd!(D (DB]-Vo>Ja,Yo<:Q%-C(>8N*:P+1I%!9%=)@2K# +4N9SIe$e>nC]x*o:Zy%HbqOa79M5K"5I0A:MTi5EZ[s-T9RSk0T./E+?DZ-@Y(BZ%C_$dB7O+?3D#>L-%5P`;DV,+>+?5HGZ/AS*GY*CT"]s9mKYs80+?/C>V!-DPgCY(1I@Z&5N8PLe0Ys:Oh1Gc,=X).J0J$BV4M\8AJ&dlEnyV"!$%*%+%(! $('*&46",2%-$0(%.'/@IM%/0?JL9EEANN/<;0<@%-(3<4BKK[@O7=J6,9(.=+3B/6D20=-*5&+3$,5#>G2HS8':7%63&75)99,<;-=;/?86H>-B7/Cp`|XmIsMvL{Ri?qHnFtLwOoG\wJl>l9{HuCj8m:v?K\]NFGKUPHJJG@AGDHC:AIICHLJBD>H@ACAG@@>=A;<;|;~>LJEFLO~Ft9y=q1C~?~@BAxc{L)FR2GP3HX1L^9]pLMc?=S0ZlM;N.5E&>N5!1AP79G/HW;M\AM\>EU7NcCL^@Nb@QdA 84021 +C^0xTm1La(;P6F9J4E6IeZr4Wu7<[[y@;V&:P+!8 .!(%   '  +  )&' +  -( +, %*;BY2 $3N4P:TAZ3K/G1KI`%BZ H_%'":$;6*Db{*A?iYlK-? /5MC\,G:U/K,HD^06S$Oh9 8 )'>5#: BZ=Y>[8V6S5,KGf,.I.N7R.2)@0 + +$7(=2H!$;0!7"73D):O)34L).H#(>1 2 +7S')C'A6O&@Z,'@ D^$pk}? +5J*@ $+.Nf/.Hf-&C ,J&C 8U 9U,HC\'7L#wIazl']"*19'~r & #31 1 5I!&<3F&,A &8Vj%DX:M5I =TEZ3H1FVm#CYDX 9JO,-<!0$ + '.*7#3&5  ($h;E@e|Un9f-i+C0E AYcWs]v%m ^u`uWpWrNhEdu)@Ga2ITl*Ea p$x)t1Sn@Vbv9.!w +t~ ,# +XYqLe c}$;FUW 9 @Xa (djw +n3grw7VcLX +XdUa@KVaFQl|.G5LRdt7{@ju,RXV[>@VY+?.D 2..<"5E*2E'F`<7R)W;PBU"CT$ASZn,q>wFH]DX`t;F[`w4h~6bx.p@FXUf)`p3aq/`r+n1KT]b{EWn?Vk>h|MK]-[m=Yj;`few=pGrIdMf'fXmCrGwFaOh^|>FAA;C=QLPfKf&Pg.5K3L!;3Lf?X$;W&CY+,=(=9N(3H$*@,<1A$&51?%&3"0-='7(9(8"2 2/C#BX2G_6:S*=W,nGG` Pj.)AqULa+dz?tLuKd8N7O 7Q#,C-C7N&B\/8R!0KE]/Jb1Id(:SSl)Pj!m:h0t?Ys"o7w>}A~H2+C%;EZ1H`/,D?W%Le5,C!5 +#6 *@/C/C2>!2@")98I&8N&O!tN]mVp.Tk/Qg(^%<3JkUYtAJe-cFLi2Ib,Qj0E_#C]#Sl5Ja02M3J"9L**6^RW2o!#./'*')!,;6"2-"!!-:;"-2%. +"%1#..6@4=D +,ALMM32@3~ળܷsqݢvKkzHh3s?Nd9[{4Ki'Hb%Ml&_}6Yy,\|+`,`*j3l5h4]~,k`Ljak9HtW6Uq?Kj)Km EjMpX"EiLmLp^v46\n4q3RvKp l#@HO>@FF}8@G?L:?GKz8FI~E~EIw?Is=wBm>yIyMtGl@m@o@nAg8sF{LsCo@rCwGo?m@h;m?f9f8l>wKk>f;g:f9h~;i>kA[s7Zt7]s;cyB\o=_s@Zk:Zl:Rc3fxG`t?cyC]tAMe2G^.Xp@Qg8H^0Pb8Tf=Qf;U)4J6M0F09L=N#Ob/4I7VWt6?]"-H(C 5$8/+ 0".     3@."      &!0%    "1 +33 0D!%=9 +6R Hc1?[!/H ,C 0C$: AW*2H1D"5&9,A:TD^&A[#,I(F=Z#.K,I;Mf91H 50-B=V.1K#":E[60D". 0 0B"!)=!687 4(.3O#&=5  0Oj'm)hm(=S"''?3d}E-C6 +-G2M ;V(7 .F3*]rHUk7-FH`e})7   (6>+(!"(79H(0  2Bo0L>5H)@3HDX0FJa9T E[EZ$<Unf h"{:xAH]6LI[ez5=RQf):NCW*="3*'? (?%=,D(=0 +>L&0C%, *> Nhj-1K@WF`Pj7R:RRg8N [r13q`Kc|"31VkPg{!JbczNf +CY1HfLcAQkt,l@X<9#(+d{ BSUUo =YOiPh_`pO^SayW7h^ 94:T1!<^zVVrK?X13 0EQg>F]2:S&5P#lSOf2gLEb(b|ARh-Sh/Rj1|oQFc3=W1W(;R$0DF]#Le'Mg*Vn8Sh7EX(IY'EWlARg%Pf$H`[t2AZC\f}6dKcdz.BUTb&p}AXg&[n&n,Ac|e/7QAX(Rg7adwE\n{PpCRyEz@l0y:T{27;x2u*{.:32U)"8 +B4I@V/3H!%:04IAU%?S >SH\'G]%Pe)}AZ_v@8O Ka2F\)8Od~Bx7x2@?~7Dy9Fn{0@A@BHOQ={;v;_y(_|+d5\z5D_%5P!+G-I .K 8U(+F1L.H8=T2/F#6,D#0G*0,E!.F ?S2..$3):2#6&7, AS+6L 7O$E]46L#(UkDY%E]&I`$-> ,< fzV3 1F&0D$8N*3I ;O%>P%CT+Ma4J_,Tn/[3PF^#cxC.A#2-;$&4.>4G'/>%!#"1$3!3!37J,@T1;P,/D$=Q6"7=T,WqCC_0/I>V-(@-:P&6J hDZ'>U"Nd70D6F"HZ4/A;MXo6E_ f@y[Sj9XmG1@&DT78J'8K$4J Tg;$<'@39U"Jf.Vu9gJ=Z jHWq/Ph*\t:Yp@,656@=4=<@IK3:=5;8;A;EH@TUGOR>DK77?-,8).?0,,!!/!=ORd5HdTp'Wu)Uq$~Es7m.Qmo0`!n1{ABc]z1i=i<]~.Rq!Tp!k5\w!l-;y/FWKN~LRэÉb͝_Ui1rvNYs*j6Kt4B^ +wA^#d(H|9ZiU\}JgKk9FcB1K9&91.;92<;"55#7/0B45H45G37J82D97IA+@6(=,F_>b~MDb#?_=]Il>e Eh]*Vxm3LSwSwA`%LpUzj&k.{IbmJ]|:gFUv3nKd;d9rAr@zIzHuCO~Lj8g5i4}AKETJNHOKBHPPQXGMPJEGDM@DA@CMEFMONKPEIKOFOCG@EIG~;=9JG@@DA|<};?DIDJ~=@FJCGCGPEDMHCGEFGLH?=@:::w2??@<}:|8~;<:z9q3l1t9}Bp5we9l?sEj7j7s>h5^~+onBlDkE]v5oKg~CjGf{@kBlCg~=i@d~_{>\x=gEe|Ee|C^t=iGe|D]v@f~H`wAPh1Rl5Tl8\s?Yo>Wo?Md85N&&@9*&?!/H,7/H)2K**C"66#<$@!77#4";$=^tLJa3&>^rD_tD6M(>*B@^(4R/L>X%,D4I.B"1,)+-<"  !' ,&  %&, +*  +.2 52E]5dE`)A]&:"= 8S%< +F\,0B+= :Q Nd82!4-  6AXr?D]3B]t F\ E[@ZMg qE6O D^$=U!2M8 $A%?*B#9 /A)9 1 %2:P4'> ,G:U"29T#2O-#94 5 +A8S*&?0 &!' #2-< ,=#'. #2 9(E*C0K!3).}6a{q^wG^Vm73:S@Y^v=bxH-D,C. +50( -.>%" !\KqgsB&.9C+# +"+"+ 0DSk;IL?Sh=P 2E H^Rm":R 5TmSpl'^}VtNe.FI^RiUl!QiGaVo)c|:$87JLd2@[(%@'B9P$=S).?-=/ )$71A*<' vBr5kFY( +Wr1Ad{CM[$ $ %7$42EF_ QoHd Ec >Y|:b}&3KFY)AZt_zbyVj ;M^j}'!rYp}&b<&r ToRlDWo~Ku# } jPhlq[uWt ed~ G*ER Q`x$5>val:AJR iq#M\ N[8?PZBPRd +dyt=_m?IRd Zo 6K.N`p =p0?o`p4uXcFR~2EeniqPQ^N[Vi*KNMOBF8_^w}ho~qPh8Ib/Kc/Rj1G`"/IB_oFxOd;i%DpF[{6Wv93'?1F.C.D>S'9R#A[,9T5N)A 1E7GGY,Xl9Od+I^$Mb)@U:NNc$~MTj G_h*U^aq.EUr=gG]VHs/xE_yt>OjRm$Lf9P f}2Yp!_w$t6y:={8u1f~%d{#o-l)g m/]x%p=b}5\w2`{7Mh%>X!B\/1J!/IA^.A^.;W'TnAC^26P"6P$1K#6N+%<5I,CX9)>&<,@!*>(< !20E&*?!!3'7&5>N+9N'0F)>-A-BI_6E^41L 8,):8LawCKe&Kg!IdLh?[Xt)}Jn9j6``'B[-F 5OJd0.F*B7M):I[3Pa<,A):(8):)<-A#4J*#82E"2F"2B/@BR(9I+?.D"J_>^wLLh6Da,9W Vu;Uq?2L'>^uF}[Ie'3O9P"#4% 1;frKIZ)Uk2kGQk/[s?/C$3.\ Sm3C[ Nf+[s7Ha'Ka)Sf4hwKR]6.6?C#v7<($0+%:&BXD-D.1LhH;U99V9-"#.0%*&. +"%1+*.: *$-375<;@FCIOMM+DV-IZ02I-BWnAQi4Uo7Ie,Fc*Kg/Ib,Yn=nTdyHUiv7u3O9r)E_e_}J][wKj8o>ŀȈkDuL~OVo6l2r7f,s;{Cl3X~AG|Bv?YXzNy^!@#@"'?*);+->3*:5-@2,@(teiV7K*8I97H>?TAn>yK|MTP{K{IVZRKREFZJG@?C9HATLNKUVPMLUMOMNDDGMEMFJKNBGNUPOLLSHK>F@JB??>?|1;@=@<<@C{=u6z4>={6EB>}A??{8x4}:y7y5=:@9|5G=|6|8u6m.z7Ar7l0l1h-q7o8k5b1a1V+^2c2a0f4l8g6tDqArBl=vFq=roIf~@]u7kGd@`{>gE`|>[w9Xr4Vr4Vo4Zr9Ws;Rl7Wr?WtAC_/]yK3O&%A,E$8Q31J-;S4.J' ;#= 'A%6 &2.#;"( '=mVE`)7T;W&@[/2N6Q 8P-E\+*@3%;|9Le{RgZsF] Um)CZPh G`B\N&32E"9 &> .H+G0 1 -D.-"76L$#  20* +&8!,)5-? +BWXk 4ZWh BTCZ+D7R xAs4?X Smp&^zKel;Wq'b}/Vs"?[ +yH6V ;[A[!iK4K,G 9 +AY,-F/ +(9- %4,< 0 ,B*>9K.-=0^uCj0Ig ='yPI|1fz-V + tF^^r7:Pr9q(b%Ac7M6I~ I_wq l4OYs=YK }+;h{uy/LUsyvy!.PU Q[Yd!4@gr,_l#\nf{$z'Ep\kmM994dvZiCTB^om{ jxQ/ZdBNLVSYt{4EN\ffoDM dga>-ByD\:PfA!4G\5;Q)AY-4K 5L I`6F[/Ha)rO_ps[=X%.HRl8kU1G %,>,?BU1'<?U./D?V+:Q#.FPh9pSMf.Ys4Sm*\w1pBnCvNYw3_~:0Q&HOn2.H"8 -B-B5I5M5Q#3M8Q!0E9J!?P%M_34I7Li|J=Q5G^p9lBJ_Lbt>H\ +GC/BLJf`x"Jg"D=Y Ke&azIkU6LOd5Ym?cuJCT(Te7fvAqG^}YjHmLxSuL]yHtBko>q=n=?NIJMFFNFTQNPLPZj-Uj Yi&dt0HY%7j~;Zm-Zp,Zo)I_Nc!dx:K[[n-tI\ 6"2-:*%2+S0D2G5K"g}T/F'>hOx]B_%C]'1E9K3GZn0C\Xr#f1B]>X h:Uk'Qh!Wl!d{)]u e~%a{"b}!i'Skay&o/y6k$}3w,8LGNOST}6bO8q2n~4{EKu=v;_v&t;l3w@`o'p|7s}:q{8u:Bx6>z4z6Ax6{"%: 63H(#8.,)8H*(9'<2$50BCX0AY.."< K^2?O%3C.FId%oIE_Xr1>XA[4NA\6R^z-QUhXv.6SA]$+E):2B'%3'75H,(;#7%7/3D"9H$>O'8H=N#Se4:O9M^tH9N(&;)=!"6 42C'!2&*(:%.3'?(>,A2G(3D*'6.$8!8Gb/Nk3Qo3Vv9eLcO 1HoNXv3d?7R*:! ).X`@7FL^-]u;Rk1@Y$(=8J$.<.<*:-?1E3G#5H&N_;4F Rh>Md4Sl70LJf+Sl7F^-Si9qY@X"D_$To2Gb%.I@Y"uWas?La,5LLe'c}8j:c0SGf]x18#8 -BXn=Kc/+E AZ#Ia+Zq:?W5MDW Ra2O[0itJT[CHL8(-/8&)9"(>#(@#2J.2K,>Z79V0:W3;W5,- '!"$ &,%*2 $/($%-'038499>D?AGAPUR:?>BDAFF>IIM15E+3B+=K4DT:Gd4KiLi{Dl3KYr;p5p@[z`bP`|WLcH.C/5E;7J72J%jP{]Ib5>P:]AbMl!>`1Q4V 0Q(I<] 5XLng9wHLY}IuAxFSVYX\SXz>CKOTPNMIQCABJEEHHIHORGGLHMJMHMJFK;;z5BMBt8y=FNPJ?A=BOQOG=GJ?B=7::=?@7B;JH;DH}7;AAABC~>:}7|4|6>@:|8p.x5o,z5r-w2{6CBx4v2u1;y3~9>|4B;{3;B=@?y6x;~@y;x:~@Hr9m9d2Sw$Qs#_/}JwAzFo:vAu@uAwCs=v>p6s8y?t=vAp@k+D$+F'&A")D%#.-Eo 4+@ [o3))7( +0 4'B!9 95"7&,APf&12K^u==W9SoNwX*(:-@ &2"  )% lvNj{0$qbw4*@Sc6)2"'2%0# 0 +*,&: $8FV'(&NDX Sk 8=V bz,Ys|9Ysm)ay-:SGca}3OjWw+;[ \|1/O/%/EMd1-D$:2,"2$/ .<2@+<(?%;0 0A \u1;X 8VTtx+Pj r.=Uv 6[suDHa-COv;[to/BZ.G;p&kBYm43HQi[vXtPi<6fL 2I -9cq;(6 $%6=KMYb)VY +33 *$:5J(8L+2F&>W7@X<$: 1!3-# :K1JZ?RbF7I+6I#YnCLc5Jb34KKb9Lb6nQb}=~Tb_}>";6Nq6 +?1C&.$;/D!?U/G^67M"=T(jSbzLVq>Kg.iG[e8WXv+Zx1uNzVqM^:fG4Q1I1H5J0FV+\sIKd;G_8)@1/G!'?>W-@Y/:S)>U-1HRi;e}INe1:Tc~_~WrJknKI^05K+?+@3J3HJ`6axH"< 1H.@&6 >ONb(tGPf;U +q@Yt*@X]s7Qg%Ul$Xp$_w%b|&n1d}']v%Xo Ujl/}9z/BA9M@HBIS@O@@{r=}Hp;r?m:f|4Xm&Rd!Qa!bo1lx7Hw={>L{:w8v8>NLuCyNd>Nk*>ZKg-B]%E`+Ql8Ol9Hd2Hd2Id9(A)@/HNh8VqAUo>D_-$>1F 53H*)< $9219M.8M+4I%-A#* / 1;O.&; 3!1*97K!C[+Jc/Ja-L^+9K:N #*#12@&$3"5+=$,<"'63C&6E#=M(>O%7H9KFZ)BV);P()>.B%.B&AU:@T94G,)<"-@).@+$8#73I(QgD3L'4L( 7!3)9%4"5-Pl9\yADa&=Z 6SD\.7M#4IMf,f@oJc|F/> AI1?N*=O#.AKa/ya*>-==K+>O*4H!*>0E9M($7EV6,<%:H^4Vn>AY$ZsW%kNhJ:V.IXp,"=C9M[E@T7>V32I$@Y7;S24L(@;:=5,0*450??;STIOQBIL9/6#(36B-O\EEU8L[L^6Qb;>P,;N/AS6FV;@R1>O,BT)J]/I`/Kc0Tl6Ie-Ng/[r8Yr1[t3Xq1[s9]u=sXrYqUuUʺ績wӌs(9;[X):N~ 3AAwy:PƊ~S7KF]} IgTu$MnIi`1[y/Oo$Xy,[}/nAaOn&HdRp$e5^|.c3g8gn5MiXt}Du?j8g5Yv'd3^}+NSd/u=g.^}"s2O[u5o4Ii:f:ɭ‹ߡvAGazKbo:g/x=x>s;tDB:==A>>F?~89=t2>BAEAA>I?x8r4n1|:>v3m+s.r+8s)}58@C{5H;?:4=E9>tFpAn?vIpFqHqHiAjBiAg?oEf@nGc@b@\z9eAa}>_{;Wu2\y9Zv6[w9[y;^y?Uq4]x=_w=[x*6L7;P94. +()?QkVj< "A^7WJg$Zv85NE^%.G'?&? +Pe7,D#8Tj(qf{73)= s>`x";GZCTp/[q"Xn!Pg$AZ9S/H-F6N3L2J/G>Y3M +Oh%Wo5 3$7 +#3 +(96J"=T$9O 3K 2 &;3 +1;O,3H"9N$$9)  )# 6F&)86B+iGj>bw;!6/ $!; 3 , '"$#84M0F0F5CZ(>X 4M@S"*. +4E.0C*/B',9!' &&6$4CS+haydxmD[]a(4#"02(: 46H. G8Wg$W7EWl8P Ql#F^7Pd|%RjNdUHb"=W;TB`Kh#EeVu.g)-#,?,+*- +'JT2MU) - +'ET->T9Q-Kq0\~_ )] p +hcv3COb v1TQm?ZD\v&A5N6Or,Nj\r*T>VLcUm KNhr"Pnd)GGh(!QfBU z59BY=H^4Lb?SMc%@~ASsZx\y4H`JgWv1rsy |s7AWDXZub~UqxzGduZlK_$,cns| +[ahp +;cmcp$y4X8D,US~>f C_`}r vr~atJ[PbW8BNS^LU/<5A (5@ 2> +Yd,*>IANFT *2<]hCC"3P`k@DS]k#g{,Yq<.+?=3diT#1&3 ",!" 4?'4C(FU6F\5)>,> * [sN5M'Kd=G_5\vH7O!Kc4E]/C]+Zv?5POj%qErC}Q}T5>YnZ{9oR*D*A/C6I2F7N!.FOf>5I$BT5,;)82D%;+@/D#6 *??S$EW$4HL`'Oc&G]tA_yGF\Yp\pQQ_z,kAc{@F\(:O!=P$6J,>1BXh:HY)Sb0]_G]%}^_afZl.nBnB`u3o?SC>\;ARHNIJKJGBURJW\bXRUKPR[@q-`Skjh0p@A]Q*Ug<-@(8 6C*8 +=;M5J ~MSG_":2F*@Sj%IaSla{'b~%^y#PiNej0e{'t15z,6433p%y1w3q2x7j{(~>s4p4cw-gz5cv2fy6^q2dw7dy8j~?^o3\j/o|EvHv@u?t;l/o2q3CDSkYs'kBOj.Zv?_{CVt8^yC1.F)C.G*D9S)";)>!5)=-(1%70B$2F!Jb;;P/) /"3J]>?T5//=%)8;M#7L:ROe.=P;M9P4M=Y Vm:I]/.A0A9O!5NJc':UtD?_ Wx!`+q>Qr'!7G)@Q00>#,)8 '5#2(5"%2)7!-<"/>"6E$@P,:I!8G1B?Q%FX11 /D$8M1$:9O4@V:0F*-C(LcHI_F"6&:!4:P0:S01I%X&Ws>Gc*9TA[*c}P+@7L[u:Vt/Wv.Qm3 +29(7E*%4.@I]1p].B*:8G&,A;Q(=T*5J#,@( +1@%.=");4F]2Og8:RXo=@V'.C6N#9Q"C])Un95MAZ&9Q?W(T15L&;R+H_7XpEPe?ScHCS?>J>/85'-./66+0137:KLNABG7889;89;6/0*/1*./+>>8CC9HI:AG2DO7AM68G.8F+?N/EV1K\1Sg7Wl:H^1H^70C!9I,@Q.@R+@Q&M`1Jb.G`'Oi0Pm4]w:_v/[q Ymdx6ax@Wm:ig]~YؚōW5#G'!Y&hE  7ca/XڌZ}/m w [;XvPmLjSo#Ro$On&j?m@\~.tIZ|0Ut+Qp'`~3ch=`3{ONl"Us+Xu0e9a5zKh7b}-FbMj]}(n9^Ul:Ww$Xy%o9f1yGzI^}-CeKDEx>l4g8vJpKǐ紃^d~3Ro]|,vHqAh4k5b+g0l6`*JPHfFdoCmc1NyBu>VyNf;g>Mn)dEMf8A[0G`3Xt?^~;Vw+h`Deo:q>p9p{tfldSHo3WOHO[^H4B=CI?[LGND87BJIH=>>J@<<9:A59B=>~8~9x4}:BKGCC>;~dX%5N=V8Q*B "<=U.)A#7+*).   &3!"   +(   !(%4$ +Fe}EVp-@\B^Rm*Ys.Wu1&<);"7&6Te;H[]O^IZPg=ZOjKb o7zG'<3J [r7"<">B_6C3ACXnwv:tv#{(J%*6@_OuOh5Rh@7M(3H&;R-ayQOh@Nh=5P 8S!Jc2.@X&Kc.>Wh{RMj MmcwQ.J>ZGd,_zFRk; 2 +&6Vo?qX[vAGb-+EJb,^|AHf)lJpʼnoH]sE)<$6*8&8'99KUd7CQ"*@2E(!4#5 /)$65D 0@4FPh.wSSo'Yy)vD[|*EfzNnG:U+A+)8$2#1#0/? '4 .0<()0<'*6#%1&3,:!5D':J(:I$5D7E;M!AS*9K%*>)> )@#&='>*B/G$/H(*A#' "5 2#4$50F&A[71H&1F'DT7;J-)70E:R#.HJe09U"Pj:[rDJ`53HG^%Yv2kB_z@/? )38F,$4&8 .'5.>%;6L%.E:P*6I+.ScI0>'&6"3(='=5 +9N'/C*?.D0E=S%]qCBV%.D7N0G=V C_ Sq0Jf.5 +';=M&8JG["F`GecuHQj-[pAGX2&:8I$brJCU)L^-Qc3LZ-V_8kEN2ks\svnz\`sMI^5Rf?MaS/Pc>DV0\oHN^8SaFAM9FNE:?;*//+/0.24(+-)*+&&)()+()&6818:3661@A=?A9BC8LOA@E0@L3IX;?P38G*>M,BT,Na1F]&Md/H_0>S+.B 3D$>O*@R+J]/K`/Sk3Um3Nk/Xu9d|6`udScq%VctC}ђ_aϫٹ͜qߖ<.6O'=k|2"+ ,< +@BFAu-BI=@QALNJGX^R<>HHJEC:A;9/89><:=@79B;:BFFIIAD@98|5J@?@<=H=;28?AEGOF~AG@CzCM|LyKzKqCtFVTuHoDuKqInGd=]z7e?mHc~=gAc~CXt8So2cCZx;Ki-Ut7Zw<]x=Ws9Hd,Lh0Mh4Rn9Sn:Pj8Qj8E],F^,E]+D])Mj6;W$=Z&6Q!-I.J!%?(A$+D(&>$4(!&$,-&<))-@, 4+D!Jc:4M!7R$.G'@5oQ_{>5Q,9 ++D)B3G(%9- ,:(  &'4#)6$ +!0.  #  "'<)B4*)Rk8Pg02L,E7U9UC`Wo/5PJb$1I5MHa'.IMg!G_"Rg+Ti(Vn%Kd 4M&8]t"Og|/vE\ ey0;Q3N=X@Z7NAX4MD\!Vn3,DvC_|$IiYA\I_3I Nc.4 0/ 20.2"7"6 !(+0D"8)7& & IZ5Qg/bv<:M+<1C,/6H"!#/ 30!: ":':+@Vl0\q2(C 86MVk?/>"(!    )9(6q;ET  +"6O2O+F7Ts&FZgZk($2&; + 7%; *?:P EZ6N3dk)L[F[@Y:R UnRg6H';J`"%<3L.J6TTr.Zy/}OD](4 +.E(< WKa NSmXyy.\}#<>\?ZMSk +.-CLf j#)?k|r#e}7OWrn.A^D`V^zf|"=j R;8A$<Vlv!GFSY.>9B>Fnz);YVLH 24"&Izw$jhX\3&;H/j{OeR_r:M-KDY]5G*%*--<@R03I%,CT8'3  4='(04B&"5FZ4Ne:Mf6X-2NpYfK|_wXT(>V&9Q@V$Qg7=Q&/AFW.?P!Oc)cw7Pe!*>)<Ma&F\Tm"Zu'4N(A9G drL2 +=$3, +-?(9 M_/1AM`&j|C6H J\5G +/C CYQg(F\@WVn#bz)f*k.g)u3CIP|8?ANKFLy9ANKKuD[o+Rg#au0Y[q(bw1_u1Ma#L\&L_&`u;Zl5]n7Wi4Tc/Ta.BOn{Abr1j|5G~B|@k2~OUo*Un5$+*=!CV52G!5M#.C,B-@3*;(:WZv1c6^~.Zz,c6If.H '?.C +*9 !/!."1#3 .!.-9&(4 *6"'3 %%3'4CR3JZ6,<7F!AO*>O(DV1VjE&:*>"$:5L.)B D\8 9.G%)A"3.'$1#!/!5.C&&= 1E(8J.+:+<J^94K G^/;T%9R$AX*8O#@W)La3Qh2jCRq(jK'9&07+%"&3"0%3'6-!..;"4H&/C';2F%!3)%3!()%5+:K$->S#9S8QLh*Lf16K! (5.<O_5Vm7Wr0yNc~>Mc/&*9>O2'5DQ0BQ)P`5We:^k?[fAQZ9]fFfmQBG*W[?nwXES1XhE^nKZhIGV9FW7BR-FW1K[9HV8@I1"("( $%'%&)%%&',.-040 #!/2/%*$26/59002,13-;?6;=1DF7EM5N\;DS/=O+.?6G"AT)Of1Kc*Pg4F[-CX15F$=L,DU1JZ4Pa5Xl;Vm8]t<^x?]y=\s(nd?LZ`{~oƫϦޫߨݦۨsMOg]*;gryi ") kVCXT:p(g*Yv HdPo Wv*Kh Li'\xXLf(_y9qI\v5Je"E_Wo-]t-i5NE^"nEb1e5[Qn(Om&e:`3`2Tn>_-vC[e0f3EcHe`}+tLv>~FW~OtFd6UYuFe4i:yKVVzFRSY[aXROE>};x8s3n/y:DIAJPHNE/<@A?IKDGGF@CB>?Ix*s&;7NMJC>?}/@@IEBBQX]~>PDLIA:68>8BHJ?827:;8}03=~4>4?=?B;A@CBGFRODEHBDEB7};==BF~3DC@RA8;A;>C@H@FBC=~8z5{7}9~=BGA=A}=~@xAyCtBOvH}NrCuGpAoBoBtHnCi?Zw1Zx1b^|;[y:Vu6Lk+Ts2Lj.Xv9Qm0Qm4Rm4Um6Jc,Nh3Id/Je1Xo=Yq?Sj7Nf3G_,AZ)?Z+5P@]//H3L%-E"'? ,E(0G,5+(&4'*$# +  8Q-0I!1J"9R(=V,iX;U?Z /J!=6RB\,)D8%330(5$ +"  +$1.!1%( 1-@&/ -8 ;- + +1HIe'Kg$>Y2O$>6N:R/H 6O5N=WWHaLb =T=TD^1#7l2`zu)e}"CZ +.ENd#7P>WD]*U!KdJg^AbOnr,CJdM?U!,->. +'( 3,,@"5>S*sTTl.$9 ! [oC*@ =UH]Ma&Rc0- "2(: 5":/%Dd1Mwt>k5~>G+H/J6P ,AnUXcI  `k{N0}PBhJ&?pGHJf k NGTf 0,@ c/F[n8f/=U +A[/G4J5K'7CV7Lj7o8Me NeVk+/,AE\Sl/@ZOk(9TJf*z,FMc'?VRh~9_sQeSh^ok}_p`su,Uf /B`GfJQ'> jD`_}[Fw.:_y |#\m=MQhu3^u`z-Oa?&4UbIDS6E[7D]5j\q_[wGJe6Tp@g{[_mzisP~:WHd21J ;R-;Q-Qg@4K#6P%D_0Ie0Xs>_iGSk1+E?Ya|8e8[x/X]{8:-JbQg74JYmA*;$7 R\q2Vm%g2fJd$KeHbUn%Zb*<FY*=L$@P)=M">N"L\1Qb2Vi2i|D\p7ey>vLTkyL~SVn*Un%r>[s-WrA9V%4Q!a}MJf56R Kf2Ic0D\'D_%If)So4:UHa.Zp@&:GY1$87L"2C("4 /C,D3F%:;P&;Xl/Qb$HZ]YF\Ld]v+j2KRl^z\ys.6Bs-q,EAu3HD|AS}>DHn9sCqFYp0_u5m?Xm)Wl+Ui+L]'eu?[m5Vi4HY$Wi3JZ)O`.AOBOR`)Rd%Xk&p=d|.i1h5E`E_>W3K.E:M)(=';+C7M*8N,' &8"2.?DX1J`1@V*/FSka{IC[,>V**@>T/0 )>,A1H2J +A0 >R,/@(9L\;0@!6E$3B!.=5F:P"8M1B;NBY'?W%D].;P$->:I$@P&6L8S9U?[Xs+Tp%^z0Xt,Vq21L/G-B+ #)(9#1?(3C'(*+!-#0'$0+7 +9 4D$1?0@L\81?+;+?)< 4%9 01F*?T5@V6XnNQfGI]A8L1) '3'%0&*4H0"7+=#12D')<,B1G!7M$Ka5G^1Lc6I`3:P"9O!DZ&`|:sJjM0  7=/(&+&5+(4##$0,:%*<)9'7'7&#3 )+8(,+,:N00C& 3#44D*+?!):1E IY26I0FG]+Vn8G`&Tp4Ur4;WLe4,. '5(>M"cwBzZ/D + .:%.9 ;D,U$Ia*Le+BZ&G[/J[6Ut*CaQl"h8q@Ws'/Kh8h6g3J~Ir>Ur Yv#_})a|-Q}>m6f1Ux _+RaRMLJ{ASKWUX==:;>E?aUBOFBB@?:3;D6CHERK?499<991485FMAJGD=9@CCLKPKB:>=F=AHJLH6<4;8o?pAl>tEl;sDqD^~1Zz-a7_4h?a9^}8e[t6c}?^w:iFD_!Le([s7Ri-Sg-Ia'=U>W!:V"Hd0!:2 / 2D*  +  @R8"3  +*/( (>#*<"& .2E*( !D]34P$,D+ @X)6S)H8S4P2K2I 7O=RRg,mJ5KE]H\AXCXOf AW1 q:Mfe!Tn0KAZ=VKc%Eg1%B)C)7L!.  h{=0FLf[z,Ys&IZumi`}2L)BdnIMAVnm0^rHJO+?+@%9/D^s+%;6MNfe(|>s6f}.LUj#%<F`3L 5Q ?Xf:b~-KdNhax+Qg^s!YkG\i~Zn i|pjXll}Sd ->?Vo,z1`x"y(@>Z[#D`,)0F4J,B#8 3JpNA\Pk!Sq%Kl#Ii&Ol3>P ZjCO_8?P#*< +7HGY"j~BZn4Sg*Pc$kAd`Xg={QVe~5d{)s2}5AIJQLM@G?H@B>IELBAC@IE?>Am,F`1O:r*?>x9z>t=m3s9BNx;ay,e6So*Pk)Tn-Um,]s2Qe)Tf2BR!Ug1Zl8Wg6M^,Qa2Ue6O_0IY(Oc+Wj2Xk+Rh%_u/{Kf7l&:0C!2A%$41-CbxQXkG#3) -=K*,A+/EG]6,F2L:U D_'Tl5`xDPg7>U)#:F\4CZ./F;R(@W1@U0?R-DV01C(8"1);0?!"79L&)?4LCZ(=Y Hd)Fa'Db#Ii)kLIa+#6 (74:( )",'29F)Q_=jyV_nM}yngo]LUA\cO>G4;B2fn__iVOWGKVF`j[2>-1;*:D5DP@FRAAH8:A,AM*IZ-I\+L].HY*La.Kc,Ph.Pi0Wn;5K)Egf)B`7TGe,Ts4eESq43N9TE`-4P9TH_)Ul8Og00HLd+Ib)Je.2K6F8KG[&Zo6Uk29O7K=R8RHe"wU=ZYq37P/KVt8[{7`;Gf#Kh*Ea%E]#F`#6PC[rDUo8p9xFHiKf")AtJuDWSi8tB\|(Vq#mt?E`9UuFOmPozF}Ki7\[Sn F`c}3x@lj7Vw!JOe3{H^VOUXSYBF@PF?MLGR]_`GFKBFt"t"{+::H;}09@>A<.340+).,;:25=7{+~033:/:D:B74MHVSUP\ZVSMKFPIHLQR\`YO]ZRSZ[UBGLJG@<>EHHFVTKFEIGEOG>LFLSSHC>EDCADD=EJD@:GKBDGDDEMEA;CCAHFBLBAA=AC8T:*D,5%? !  ( 2G)*? "6*1&>74  !% .&A4( $ +,B9U&!= :#<D\"-E +5N[o34I4I9M`|.8V:0d2@]TtMi =fXsmLc !5%#99T(5Q !/4LB[3M|8[/v3Im`u"3G*= +5F$  6E+1H "2ie?\B8SKeV?8U Lh3Mi7lJZo1}c&1   (k905Ql7U9]W~G +gj$\d[d4>F\ c|Kc ,CnoL%&;K#$5 /Q`.6B3<-6]h)tes;`p2o{8.:HQ0|~mn>j]f BK ?G9BU_"9<pp*jn(;B<{bF2[huTj{9=QR28K+TlGA[66O,5O*jZyd>X(qYiQ^{BdeG)D/ jWXu@9W"!=Kc)iDTk-]s4e~=d=c}Xn@uX1P>[&EMh,Rk0|XGY$Tf0Uf-EXSj%Si(H]2GvBaj0*CKai2vD;=CJ@E=TQK=Y =ZDb.Kk:;Z(A^*-I:T(?X,:U*6P#/G;Q*2D"'9&94#5#28I,(;%83I%,B'>>V)2LLe5F]+0IFa18Q Ok8Gc.Ge.0L(A*E)B(;:J&2B:J(6G$7H$/?$51GCY(DZ(CZ(BW&@T#?T#'8 )98H!?Q":M?XMi.D[ CZMg"zNxRC^&+B%<%>6O&XsG?Z/+B:T- 8:P0+.8#+7!,8!0;$"/ ."1!0#4&7&+6E)->!*: !4+<'1C.(;#DU>"1&58G0'6!$1A,%( +'%%7#&9>R2K_>\8UPp,@]5QTm6Zo>1E#7 +8I#O^:(86D'1@(1>)Xa>OY8T^Wd4_}-s>c/Z{(Npb/OX[QPUKSJRF>>MKB:DAPFMSLOSIBA-4E:2x(t"5=;=<>FD?A>I==33@PNTDDPZURMVQWOAJOXUIIHDHEKBTDJOQPU\XONSQZQTRJT9?5@F?y3y6z:GKAo7u<|GzFyA}Cx8}=j.w8k,x9wW3K3G 8I-=,?(;$6Ob&?SDTWk*Tg!0En2z;7T/LLf&<*E&@Ja3(? QIg m2Rr =>Knr2v/Sqn/_y"8R^x^yCZ'=(%!: eBgMj,D]#7Q=WAY g4Xp&BYX#Srn[Tk fFX"%" \s1o/GeW3[n%@,D8P.G:Sc|C1 MZ8 3A'Odm"-u*S{>b07kZu pRm!?b~;|$M`~ A41/EXtz'>Nd#LeMfk&g&bx(n2e}0_w.8P2I@Tr<]s"f{&Yk_te{#Ti :MFZ\qLc 1G})yYqGc]w)B9YLj"<^yn +rui)-D6Yn*;u 5$<F^+AYp\q)BXVo?V=x'-GIb :XSpHdGeQoav#a +TvjA-I<&a~t$";@Db .NTt&h bxdtCW:Pe i#Vt"u"Gc]x>@Yq3{$iUA\Nli[ ,y 5J3A=K9IO]2)&/CO'P_&wyNf D\s(dy 3OL/>i>M!)7/@ ':6E.: $0$!.m{@BT,&1TeiO[\H'.1BM/=U_+]dl,W\WYabt{A&x5\j8JG]&>g$YsH_ ?Wn&-Zj +,&]mEWpCPl;F`0t[ij(HxcQh@#5,1J]>;M.J_;5P)@X3.B,?gxN(>Pk;cMUq7gFqPm()%6ctS53G,:O43G+ezYOd=/Kd7Qo;r[Uq:So0Xq0Ia!8LI`'b1ILd)8SEb((E 4N=U"Wp97M1G Ke"Kh!l?uHTt+Hj&[z5Ga BZ AU%/I^)Rk.Zx4Xu3Ba!Ec$5Pf}C2F Wj3,=nCe{9I_aw:\r0yDi1pf|5g~.o4Lx=l3c0rF{SiGPg-`w3KE]"Mf&Kd!Ni"IgGa/GRj'Xm/Od)I[#@OHW&Wd2@MR`.JZ,*8 2>O\.M^)K[(@O @LSe0M_&Yn2Md&d}=P07G*);3-B 0G '<&;"6&%8!4.-A ?T1+A1G 7O&7Q%\&3O4N%,F=U*-C3B.>0B!5G$0A4F"4H]4X/WpI1E$$#/9B,3>'!.&4!/5D%*:1C!):&7%7->"1(9 /$7!&8!4C-'5!9F22>*.;)(! ,;&*8#HUC&+!#+9*!0!*9&/#5)< CV9(9G-'0;B9SWSOSP^`WV$i'?\t;D\ Jd(7Oh1o:Wv(Ti]~/Yy+Xw+Ro&Mh"=U[t2`z1Kin9b.Jl\~+`+i1c)d~$]u}6;DRAOV6NSCGPK9A82/6=DE?@G@JD?0.+>$?0I $?%>'@3J"(?"9. + +3$ + " 1 140B](5Q\u9C\8N8JYh3EX%=O6Ify@[o5AU?PI\8LWn!\v ?\A\kC0I!= #:+C,Mh(]~%,M\}&XzMpbm([xSmb|'q16l!GPAZXu2TGhA`4M.I +;U5'=,#:"6:UmC?\Eb,w /XJ\=Q +%7G]0Vo7:R'!'0D8M 2-3_y=(>%: ;T#2LYoANa,}@I%&! s:Sl1.b4L/Km Ny$7QHa *\Sl\{9a~Wayv&:U7Qp#(>;RF_AXMh[tCZMgXq Qjf.Xp&>WOdYmAUUhDWauZoQdVk{7ThF\ h~'G*vG^:SYs!#=:t2B]\x F*hZn F_d k +2CF0Yq +cyawENe|9v<=VKa5FWp`*E`>XuX-Xs/*E5a/N1Ic=Xg6Vq(e?^ 5SIeHh6V0R)HE=Kf]@;O)=`{#b&@W(|%Tp9@[w)c5M\gaiY`Z\=*IAK S^1LU*3< 2=DNGL+3}*t@i|dw +bzTj 4K:Q5KFb b|"!6L^9HY3>U(1MB]&Rl1fAjBnFf}B%>-CG_(9S6R/K-E8O8PVm5Ka';U4PQq%Hg2Q:VMj*D_%0IoWGZ(7I5J zTkF9WOk.2LUi4cw?2C0AJ\dy1|@n.MYwn0D^p\w2gBH`&kJ=TIa&UNxDABJXKC@Z&0L8Q!2KB\3%@'B>Y-8R'2J"3G'K_>DZ4&=.C $:$:2G!Pe>(<2F!>R,@T,9K"0C(8*;3B2B-=4D7J1F6RFc&>W>VWr'Xw)Ji$-I%>28$>$>)C/K=X+=W+%<(>VgF*/=!5B'#2!06E$DU2. +>O*9J%HZ4.?8I(?R2,>"-(9#//>))5"2@-%2 &2$!.,:'=L6)7 (4 /ASC9I9(('3"-CR5?P1.? 2B$9H'?O*BU)h{Kf~J5N-F 0LdCLd/:O$CS2/>%,'4!&3!&"-,7)&1#'4�"". (#7D4'3%--:)7A2!-'51?%5D''7"1(5/1A%=O/6H#>Q)6F;L"CT&Nc,mILd*G\$CY ?RMb"m@ZL[/CN.?J-1>&/:'1:"28/8 /5+0>E%OV4GP,+5LV6%A @]"Qp21M)D/I8P7L;PTo.Yu.m@n<==A?BANB??JC9G>A@A88H94CL?BIP>MB4=OYHs1MW]9R# 9+D(!5+*($91LIf%Rp(5S?X(A +Ea(@[&0H"9 ,C&+ Yr54 5N"8 +1 4 "+&+"6*> 4 +(>4.3O>WB\3L @UH[!Vj33D5IMa+W^x@\TneD^+Bc}eZu$'@ k25Q +=W &#<%D`1+H>8)H>`Ps +<`4Rg/bBE+5v' "XpkG`e{ (Sj?Ww_rL[*$45G?M}GO */cfx|!Gz(aj-57Bn{5QanbrAR +8KZn +i}9>U.*;,:#0A'"5-="HZ73G9N$QdgB`wG. #1':I4-=)-<#/=$9I)1 2CAX,c|IOh1B[f;zPxN_x;&YmG_tIBZ#2M)F 4-!; 1H$:Vk3Qk,EaNn c4Zw-3N /M7T/K*@G]-9J>QD^oK7R/J!9 !55G8J5F-? "64GA9B@:^Ax>]5PC`'(C#>!=+F,F$<-&BsZ.,(#0*:0AG[,Ne1G_(0H8P0H/F2H%=@X8P4O:WSl+_y7Wt.Sr+Z{2Dc8R3K 7NI] [r4Pg'i7q9o0j"@Bo+WtU(2 -0%6117K.3H)65M,0H&.'8'6.=&=M6+;$5G-5I-!5+@CV7,@"21E#$8-A .!3.B!1E$$4+-: 0>$*;0F")<2E%!4.>!1&5*8&5,<*;2CCU+>T%BY(Lg/Ea#E_oBk9Ut$qJ4C$7F(,<0@6E 0A-?9K$#5 .?/A$6/D$!4&8 1@+"0!!"**'3&EQA(5 @N8)7#4C2,'5(3?0"-%*%'63C&&4(64B(0=D89?2;B2/7#:B+;G*FW-HY.I[)Pd+Of'Lf$Mg$Wr-Oi!`y7Tl.F]&I`-F\,E[-F\/Sg7Xi6\tn^}́smT#+^Uv\Q`KmyumղzcΈc\^ek#^h#{AA?=9@?94998=5=6:?;D@FJFFCHHE?NDTHLEEDEECSQYTEPCKEC6EFGFF@JC+783<>EDIDDCQGJI@D@@EBGCBBDAFLJD;-1CDA28?AF@SKDD>JE?.8698:89=;@A777-78A>;C343+5,5@@??E2SM^LLKIINM?EA@?EKNHCJJGKQVOGMAFDFBA=@;A@B@C;:L?{8Ax5WQ@Aa .QIkMph/h2#;,*+FPn/? 3Wm# , Uqb$-nSg I[x+l3#70H8Fd,Pl0Qk0pQ. CV*':4 9Kc5 !5 8'=#5Ug;'4#0l8Di)9B?*EE^uIbNf2J";Wqp*4 AW};`v'by&Wj7LOb2DVh?U_qk~.I\ +]o"[o.EEyWqQkQl/L72TLoL@`36]=P)4K\r .":V:V#H5VJ[pcyVl ,H0M8S\x})8PTmRkWq8QXrKbp"@[LhMj<3M.I?\6U,L ,LEd'4T7X +b5'Is5hOtfn/B7S?[5g HEQ4c{?jH\{R*C0I"; -6 ,?2CSXn,Uk&vE\s'[u!|CyF=XPc/K],*;H\!j<]t(d{%l*C~8p0[ys2i(COla{UVm4lbvNvLhiWdWFIDBLJFCLNCC>98;@=Yp5e}4oCIc!Ni.5Q 9V&6R(#>&@1L%4M#*C2L$0J!.F3I$)> ;O/?S0SgDG[7;O*G]6=U-`yPUnHIbD^.Mj6\yDZu>3OB_'F`/J^4%2> &5.>8KJ_/d{IKc0": 1G/F:Q#AX#Xp4d|>Zs28Q2L 8OIa"A\Nk'Nk(C`^x6Wp0Jc#?UEZCXRi#`y(p1m(s'q*a| y:t4e'Vpg~/ez1xIyKYi/_p:Te0EW%G[(CU'?P%GX.N0=/= -> +:NUj0H[%I[(J])c{@Wo/6PJd'A\"E^&Ib)?XJd,#> !9 .D!0&7)$/"65":#;6%8 '-&&(-2.>#0%5)"EV:5E+ ;J/&7)9%2&-*70*B)=3 2 /"1!-&4+:0@2D9K 0DS+2C ,=$20>=N)GZ.$+8M(2BBR%5DCS+?M%6A:I.?3@ .92>3A 3@$,8-7".GP,NW3[e;doD]j?vYjxPo}VozWjrv~fyjqygv~kclYjub~ftb`j_dr`era9F6( + !)+6(5?0(0"1:)5>(0:6D#BQ*J\-N`.Mb*Od(Pg&Tm+So+c~:Uo-Vp1Oh.G_*Qi5Md1d|GNf1Ri3du=T[_Zǃޙ}┼C 0VTf۹ڥXgfYcx@bnśZyDYUlu.S糟grKl}-lr@Pn +Zy [v+@ZJ}@Mi4Q^~3j y 51iu#~_}%@i Ie jf3Rt!`3nmK&E ;XcEPk43,);,;(9 E^*[x=Ig*Fd a~7MinBd8oD]}2rG]}4Nm%Vt/Ok%Wr,Up+.H:R+D +1I>VC]A\HdWv.uJWw,EdQo'F`Vn,Uq)d~7Yu+Wv(^}-c/c.Qo0YDXZ\u9q4d$q-{/HNJLCUGICD=G=5><;F@HFP0MKDA7;AECB;@::?BBEB<;;@?;>9>?=8AID>@>BIJ?B=BCHE6DZZGESA=@DTHF:AGABHUJ;EECM==QF:A8GC7:97>?l4Ys$Pl f8?W1H9O 2F#6J) 83 *D0I!6O%0K#%?,F4X>X;T9S6QJb'EX!>TF[#]q$?mDNV}>~=OltA+<fs4dڝDY"E*A.E$@ >%C(E Tm13KyJ]d'v=Z/F @U&732&8 FX*Ue9Ul}$UWjiF03)Zw[~)v+aRf!6P7QJfghi1FcdoPi +pz7C+i B~$]rjmNbI?T=U(=0B*>@U M`PjgzUhBRj},PfPh -I_z B[o1=Z |(k(>aXSo.,-EEaKKk3U9X;5V6WSz4W|*c Dw/y-Of ;U6Ql]vf~ +{ +Vv +0$;uTefy * 5}z)F\:Na{w *>{&pH_ 86pby'I_Qj#;@W(?.Gj-TpZvWu?^Vv w-'HG_HfwSs]EY$$->P0DqN1I D\"/H &;."FV3B-9NXP]MWjvY_[a 9=@fYAYs5O);P?Tk`z%=Qf ew`uN7HTi Ph_w?R",]r"iDZ/RjCVpH@\,Sn?7P":P*#%1+(CQ=!.).;!@T2AW0Pg;dyN;L$g|M[r?Lg,D_$[u1Tn-Yt7+E- *=")8$2,8 )7FU46E Wi>9N!?U%?V]`v6^w3~SuJJe+8PvRpIhmFOn+eFE`$3/E,?3D+> >Vr@d~7\u7`u?oMa(\o6{\KbIJ?=@@<;<>I;=A>CEGJJc|83L5P3M>Z,?Y34P+ <,G%6'@.GE^7U.D\55N(+EHb6Da0Kg7Kg8ZtFF\0Mh9,H^wH5NHd,Pm5To;ZqAL`6*@R'4JAV$-D-D4M&=;Q)BU,G[.:OOg.[t5Mh'5M8O=Q7K,E^x5e=iBc{N53E(CV2D[.H`.Lf5Ne9+?iy](#&0(%/%)5( **4&.5(!'-1(*/*"+0,282"!$+.2$,2"'0 ,3#3=(=H/8C*5>(5@*=F00;#)3EN2?H+@I,:B&BL/LY7KZ55C.<5C?K(4, 6GEQ-itSkxQm}S`pJWeC`kGbogtTbvVfwHhyLZj=z]~grxu{nMYE~wytm{jvu6=5")5(*.;/$/(#+)&+(,.(,2(/:0.8,/7),5#0:#2? :J&BR*Nb.Ma,Rh.Si.Yp/_x6az8[u6Uo2H`&Jc,Xq;Tm9[t=_x?gFVl5bt:rBgUEƇт~G ;A>Zl̹jCBFceYGIot&y2oۯʋpK}趵ߴؠix!bv t&pGeMjHfyBWc~&Lj:o!Xw]z$%| +'# fy/ jLgLg%Ia)I`04L3I'=.B3IAZ&@\pHVh?^|6h@Nm'FfKm$Jl%a>Tq68UWt3}WuLh +Mc,Vk2Qj+Uo0Kg&mFa6a7Nl%b|6IbPk&D_Gd Wu+\{.Wv%]|,Tt"o8r9f(r5k0e+h0f,PuZNIIE8GLGIGNGJNBALGFJEEJHFCA@FILFHHJFFG^􃬹;_/E=@:0R:3AOBA:-7?@:57EA?:GEFGFEODDK@@BLCRSOPOFWDPYQVEHYVKDPQYU@VKPQKMWPXZOFFLTY@QVKLLVPKUOKKGBC?MIF;JER?8/BD?I9=;=<>@IW\EOO=C>F(15z+y.y&w&-~)z&x&w'x+u-o(u.=~9t0h$j%e%m/q9m8[})`2d7f8l/I)C#? .I'B,E-F,E ";,E"<3 1 , ( F[711DXi@ kC-@.A,hF>X$</E +E )A*C 4/2EBT"8JCW"EY&6J>P"*=,?7K)9 7LVl.f;BZ"86M4LkLj=Wv#DfDdGd4S =]@aQqQtHiXx%]|3=[%A8Wt@Giw@^!X|Xz +Oe4GiA_j[x Rm }8TtPpOksZv'GW1A9LmG,D#=5!@ &C6 >YWr$Cd-d~$g#4,8I)(<#5 +Wm+7M |Cc~h:n p $>+F1*fm*j.@V +Iel"D`i72}:BPm_ |Yr0{ &@Tzw +'Na+Okc-g$@P]W\Kbm cI\s0U2%62D';]tCZPf ;P\t?X =\5VDf Pq%@bZ}+8W#CWxhv*3Aw",n?B^Jic"bWi\0Fbw.^|5M=[n)C_v(?Nd PivLfAYGa;Uf>_~ Fa +u-MGc (I?_.M*FJiEjcGiTr{'Oj5Py"D@=55%4s z 0L^dw#s 3Wy@T ``oD$AUQeqh}x:Ui^sg| *Ag{@l;:T'</HKdPk#9,cLnd6UpPos#KmWOf1 !04D'9 #71 7Qm*Lc!`w-8E3#(ox8 (;GL@X@}*j}/Zr ]tMh_uUhp^pEU/Di`uI`}(/YoJ_xdzUn>$9VlJ]wOmZlW$>@R,+ ,6$!+(4"%'+6 I\;8N%Uk?P,2Cx_c{D8RLa$Kc*H_+,C!5"1(8$29E(BP0;I%GV+GX+AU(-D,C /E Vk2|T]u3d~oJe=fDe!DJh-4O5M/F.>/?FW)J_(,D=XYu2@^Ok68)@-C0F&=G\*Mf&Yr+T\u6-B.0@3B7H/@0A2E4H.E +C\Sm+JdD_Oj#?^^|5rL-B 8JATMc)]u4Vr)Xr#^w%r6k.t;Ngb~,i.a"t5t:Xt&;S\p9kmJ]n:_r;rF[b`KAE=EBC>KHH3?C>?@{=b|4Pj+A]%>Z&B\-+E+F#(A##<- +:T0;U.H^6-D$:6J%7K&*;,X,A\-3NE_45L$H_6Qi?>W-3J 9P#G`1C_*Pm7[sA;T"Fa-A\(hI^v>D\$lP/F2G6G,>.A/DAX"Tl2D]"4Xp9@V 8L_u6Rl)Wr-62Un,D]D\DZI`Qh"Yq$h.c|$p-u3Vrj2g)n2u;{?xCSe$Sd(mD_q8l}FCU!EW&(%+!#"')6$)6#+*)# &64E)4G,#5!0('6'81?&)6)83G$Hb92K%/G!'<%7%50>%%38H';M)EW1#6*=2GK_-:Q!9C\Vo+Wt'n--D 1&!0+;#'6 37M%2K1K)A7M&-B&8."+)%0"&"&1"!,.8,!,3+ %%,%%",5&*4$.;)7E/7E-)8;L58E1,+-<%6E'EW0G]1;R*;O-   %&-&&.&#*!.7+?H:+1$+/$$&-/&,,(#&"352585KOH9>3,. 46)69+-2$+0!.5%.7 /6")0-5$+3 .7 6?&4= 6>!1:8C&;F(KW6HS1AL)kvLQ\%/<[~Zl Va)IR%>HUe6sW}cfvYbpEtTqc}\|akipszd~r|s;GQF$!'*$)-(&0(&2' + &0+)1049412'.3",7#4<&08#.6;D(8F%X +gDu$6W34Xr8 ze%"*:_z2=V,D/G4J.?2E1C!7Ng3Mj-`~9sIUlEiAOn'XTu+Kj%_|:zZWw6[z:c?Ll'Rr,oHOo&Qs'Mo%Ba~RqEWr+Fa ?Y 9ULe*AZ"Rh83C+=!2&,JFe'Kk)Ml(kEYu1Ur0Kj&[z6Fe$:XFc)>Z Sk3ay?E\#D_%Kc)-F ?V9P=SKa*Ne,C["5O8Uf>oE`~4D^k?`|6Eb =YKi!Zz.Qp"g9Pq"Ilh1Fc&X{f+r7k1OsDPD<7k}5HEU=@@A;D;QGIF;AGAMHGFPJKFRMLKLTzHEI[:78ECJB@7:;?HCTE>B@HGLE>82JE@CN>JDLH5=ELEQMD@KEJEEINIULOJOROG]aM[VPU\PLVNZKPMOMMKSMJIFJSLOLSCOIPF6:F=D;DIC3ACF;-1?BF9?6,3FCHEB>:SNEP?GY+D7/:8y'~,u$p!v'w*79v)|,4z/w*l!h l(e$b&[|$e/b0c}1s>o;_y0_y1\y0Yu/`|4c~8[w5Un/]t8Zo1jB[u7Xs6Uq8Mh4Hb1Me7Qk*@->1B!(2I/ "0Tg>1J]$~S{I?R#5]nAK\.BV%:NE\%7N)B )B 2K;S1F@R,= +3E9LH]+6I*=,@ 2++=

    YHd6Rf0\|"=`Cbu&vy'k*c~?`|$m$pp +x`*LhZxaBa\x-I-0;Zi#tKQ=[Ok <kx(tPld~i?OWt9I1F:0Om Ppg=YZXay +ND/6+>SJ9CljF`OfZQm_}\xQHd7TAiVr t%Nj Q"g8J0Qfg%sRuy $U:ixI#Te0r q dnmy o 4Khz;QI]F[0FH_ 653AZ-C 9RD]$6.F ,H *Yv.VyDJg Mk Vx Z|z;' / )%4 .4F&: =QOf>u/N[`Bڊ\DZeU` +199@ȊsZfuRhA\_zr*]sXoI_ Me|$4WDTd +Tj49=~#Xh.?WZto$Pj:>Q0%6PiEn^kH_1+=2B".;#2:&'0"#"&&0NaEV"WkA 1*8$2AO.O"Rg4d{KYq=Rj4K`.L`*Sh0Qg*Qj-_xTKe$g[Z}9Tx>dO6 +.F"6(:,:1BFY#Sj-?XVn./J ,-D5H7J1F1F8KSh28QF` A\Hb Oj(Wu4Jj&iCXm/Zm4Na*G["D[MeKar9v9KaMcE[e|3c~+g)q/VkD[EY!I[)=NHY(i{GwM`{JYDS>8>?;==>95C\IJ}4CNh#Jf,Da)B_&Gc,Hb62K$&>9Q.j\3L$Ia9!7+A.@%71C 1B":J."32E,1C(;K-9J'CS/@T0@U22$;m\dQD_2Jd;)/D!6D[6/D/DPh>9T$A])Wr@Rn:sZiLxYKg*mIMc-2F%6 +4C0@4D%9 Wl88PAY'?[$5M>TBVMd$Oj%Id^v2Kd#Nh(BZE]_u5cx8Tk$m;]u%_y y7l.l5_{(n2n4Yu!FTh"h{:Xi0CTASFY";NHZ)>N".?*:7G"*:0@6NB^!D]Qj#PjXr&BZ!-?):Ma3Xm< 8B[*)E5P;UD\%=W>Y >X#7Q#(0%6%%5# 1!21C/%-",+9''5#".)3&""*+7'%4&5%4B+2@)5E+,<3AT97G/ ,'5/@%!0&4+L^@Md=7 -G7P)?U0-@, *8 8G,!29J%*:%8(:0CNb.DZ%EY#=TdF*@ +  ('"1-BV1UlD%>6&<!60B&6F,0>&3A&+;1B#2A,<;JJECE:98,')"$+, "/4%27&6;)MRCBI93=(:C-;C,R[@BK/P\=AM,?M,GR3KS6CN,PW+bFAw)dn%em8YzYSa4_k>\j;Pa3O]3wYxZsTipsuyrxkjpg382#!"& )0)).(+0)-1*&,&%**5-&.**1*),17Xb;#+"'3;IGX+CU%H_)Yo8g|DMb)iDRh/Zq:iJ[v=Us7Yt;ZsHg!Nh&4S nGTt+b9Sr.Jo)Qt/Hh&Kk*Lk*;Y_~:AMULHNKIINIIKIHDCGDSILBOWUgeX_eWSYNUQTXIIPQNLNLOKTDHDEIGNDD:>DDAEG65@;:@M9FC()/?z"76I36G7@1;38}4s0s5Bv8v7j/n7l8d5e5c}5a{3g?c}=sKh~=uHqFmDb>bFXt;[wN+.#,BC\(3!9 -E2M497 : :U!3+D%>*C4!:8P.5& 6&:DU,G\(Oe$Nex9hz3HW.,<8G DT(@P"4L0H9Q ,B6MXj5AR6H7I.?5H3G(:$5 +$5"4 ,=-@0G-D:P;R))? D^:ZMj<]Cbr;[u$D^MiVsIe-F*B2JOgt<]x$?[@] Fc4R8{3..JIef#z(jij&B\#`u1,BXpG^Wh2@W1' $#92 *@v5u'~*j"azu7<)-lA~07XVuQp&cFd9H~aGh,p,Hg_x7Qt +&Aue Ws JgUp\n\v]wVq +Z^y(@\Qz#=n<6Y Jhg'?[c .Tj2(%PgD_Td}qE6v=Y!;3L0H*B2Lab"HhMj*E|:e%0L7o/kU|.MlDa|yv8 :m#GVid} b{9V@]Fd]w[wi!Qh3KLbNKcAZ$d|CnPPj1=UpJd?{SUw-b?bI: !8%50=7F/?8OLe&@[Ca!0J&> #5 -=IY*$8;MYn=Qf2+@yTMb'

    cp:HZ Xm,q@h6q;Qx7B=C:A;p'@|5848;-8@`z)Sr)]{:cDRj1F`)/J/H+D"@W6%>-F5N"0S2BX4:R.7Q+6jZ>T,3"!52G#CX1D\.Hc0:W"7VVH]+Md0#;$; Si;&?+C+D 5L5J8MJ^'DY8R>ZMh#Nh#Un+Kg$Ys1Rm+Mh#Qk!NfPge|1{?Tmd})b{+Tn`z&c&Fu;Nhg>\u6^t6I_%F\$G]$Zs8Qe33C$28F#;I%-Me.5R0KE`7TUp//+<,;1C4IF\-/D'<0DMb5`wBAa#=[ 1M.)  +$5%;9K7- + +&5$!/+9' /#0!+'%-:#2A'.<$$4-="& +.1@$'80,>''4!/-$ ))83B'4G$'?2J &=*?2D!+ +#1.=%6);(: 3 ';2E2F?S7OC\A[Qk#_AU1"!##0*; &9,@5L'?U1'9, -+7$*7!'2%2&4&6.<.;7E"BQ-;K&,>&8 +7'!!+# )!",#! $#489* "1"+#(!#%)*$ &(#$(  ZbTAM; 4>0")"#*+("" )*$!$ )0)+1).2&.1&780770==8..)772GGDqqpTUScf`_bXOQDkn^]`RNPEVYJGM6?D.@D3BF8BE5CK5JS;mt`9A(3<'1.6,,0+474""# "%)'#(&$,%%.%(0%+3()1&9=7R\S5A8$2)/9205+#\b-u@GengrSeA`ʇЕmx`u^JU^syجVe6ix@mw=ȕįŨQ],^i2ڜdmw;ikg7}V@nj+Ws&C^Ztg~*-#-r*M nr%4O&C 36P!7P0G5G,>(<,DXp;fG=YVt1e;Vq,iAWs1Us2Pl-?ZCa$Ll.4PUr8Pk0If,9U?^e>[{1Ll"e9Qn$te7;Tb|:;WD`#Wq6A\)@[&Jd'^y8_{9];Vv5^@dE/M0NKh)[z8Gg#Ml/5Q0$=&; G^+Mc-Je.Db&Ki,?[#%> +-@@S#EY*DZ*H`,C\!Kh'B_e8a4Tr$pBLl"7U Rq'xH`/BcGiv?i.L]d%|>u:t:h+x5y1HI95Yt^x!D87JKNNCPULIKOJDG<6DINVQWPQHNT=KFFC78202)51;KH2UBI'*/4M\5?;90?01-1A76S>?6(87:8?y/r+r0v3r0k*j/g0p;l;h6c3_}5_z5mDk=cw1h7i>_y8^{>`|>_{AcATs4Tr6Ee-Sr;Yu:Wt:=\.L2N2N-G2M0I-E/ (7 2-3J1G41+#;4 +5:V"Qm8!; 0*E&=. +.- 2I&EX16I.@NUfRe \p/K[&"/4@7C 7 WCc| `| Tp=YE_ .KCdx(?[,I=]+8V8UXv\|;2NImDc(%L67L?Y&B=M1LQOe%94WrUMk=XXtHdHh?r +!:p *Q*GaJdiB^QiFXTn } U*Sy/Uu"BaWv.Mi2w8\w46#=-LDe +b"6=^fa|_tJa6Lg3:}XqZRg4H"<;f2?ZAY'@8MDY 4Nd `{"Zr6P 5RLm Fh =_6VXz?^ .I!7:TF`6N/Ij9J(D/HGaA` Nr /K`y \}5XqPh*~"(Lc&=?TCXI^`w%}&j "e +jmyNZ`qli|5?MQe'2)A7R$>1'>E\%%>A^'%D4P$=5N9N$;*B5OQnpf$Yi6/):H\l0S};I/Aj{&i|l"_n3DK[ k{*fs &}|4 +dpDEQ+?4*?3Ju=,CR +`oewObofyFPa >TEZ(i~#4by4K2MJa E\3RhDq/ :Q*C[3_yP:T+2H!$'2-4%%.9F.;J/K\V%fNA\';W!F`)Lh/E`'^y;jFuM(J [|=,I-)>4F2B7H6I3MFc&Sq0Yu60K4.@+:JY+3DBT&DV(,?!4D>P 1E%66F .>3E4E:J"CS.'8)=?WC]"So2Tp4C_$Ea$^x9Uo1fCRk.Tm1F\$Zn8*?H^ UNg&E`[s3IcPhOfXp?k(t1JӅ-BOaXOj1v;Js6~=m&DB77u+j#y4;<85:38w5e-|MA\Ic"[u6vT1J-E(?.E#;/InV#; 7G!5E"O_9Re;DY1&7 057K-La@8L*4H%-@9J&"% .A&/D'"7+A l^o\0E$/&81C!3H!?U'A^*cHOm2He*Pk2{Y@[Oj$D^C^?ZGa!Jf$B]HcZs(Qg"Qg Sk^v)[u&Yq"{B`z'}Bn3d*a}/Rn&Ic?YXr11H 9OKc'[p8GX)-<*8)0Sk6Kf$Ys2So)9Y'EJd)8I1A:M"Wn>I`0/F-4 .'A Lm/4P!: ,(=$)9#6%7#&,'()8%"!%1 " *,9%3*98J.;K., #2/> 7G,. ."03A&%4 (6:H-5D&/A5K":P( 6*@-@&5(82AIY0I[14F?S&I`2@V'8MKb*E_`y2e4S2I+ + (!-+--(;5I+.$((#!-%21@&6D+)7AN0HV7>N,:I%HZ78J1$0##+#&" !& + -9,+:)"0%& --)-,)--**+&!(/&2:2"$+!06/'*&&'$&'"')#&+&$) )+ .2#/1'-/%+-#.0%()" ,1&"&*.$)##.66<(/2"/5 EM4KU8U_DOX5.6*'6/=KU2r|Zv~SBM +U*4T,k~4bxojs\~jFW9HZ>8J03C.4?3%  #(%"'!$*%, %-#'0('&/%(/"/6,KOHLWH;I7@M?[%:W ,GC^,Id-+F+G@\ Vt0?_Nn$\x/Wu)tF]x-3O=XLj%Jg$hEvUb=e]y7c=Ww2Xv7Lj.C`%Nl0Wu7\z;fEAd!4S3O+E5K!-B:O2K>Y#,J?^%Mj5.D'85F->5II_.(? 4MDb!b9Ut(Ss%b1`2AaJjNm}K_~.LmVv zC`Om1w=i2[|%`&l0w4FiVGf~$q3f~#s)EO[TFB:;;/C4810;L:L0<E90;:xxw z$u$y*o$w-s(z0@|:XW}T1(A%>!< \.L<[[z.Jb|Y&&%Xq@\0LOd^x)Bl!5\.zj.79~0n Xq!Hb YtVn):Xj/4H9S7WLo'?SjhYmc YrMdav _w#NbAUbz &=9l~c{ RITAU_yf|GY9P6I,B41!>"8)?1J8Ut=kP^zA8S,DG`H^-FWs!42-^x9ob<Ս9Y3P7 #4LpG~OqF,H Fb(Qm1]z;Hc)*?/@Y&=/E5K2JwS[2AS$M]3+>3H!%:%;"8+C2I&/G!1I$1F#;N+@P06F)4C*0$ +*>",%8):-/QcB>S,BW)]xBeVu:9U@W$/E,B8J$H]4XqBv^,E:S$F_1Q\s4-F m@Lg D_=X=U>XGbIdKge8D[Ka#Kb!AZKdH^cy.]v&r;a+a+^|-j:m=8RJbwFPe#Qe%AV';!5"5!2 BR-(?5O?YUo-Ok&pIjH3I>P&DY/F[0/GE_/Ie1Ie/Ol53P5QB]-83 8R.8Q0.B'*7J5&7#  ++*+'+(4#+9"/>#) )910*;?P/,;&' *$27E*=M->N*AP,!*0?&9=N,J^8?T-0F*>,@1@&6\lDCW*); BV&G].4J:Q>UG_"Mf$a{3\t-AY! '%&-!2:.". !+0>&;I0(-) +&1(4$0+9F-@L5%1(&59H*7F&GV7" + +"',&#! &!#$"$&&  %1!6D.K[EFQ@8=4#%)*% !()$-.)+.)%(&(,'%+(#(# %  !%&*','9@-5;)=B3IQ@U^JJ='1'#-$07+/7-062:C4SRS}mϲ[V@ +(JA(lߢp|Odn7JZCs:Բp_k5h`m-;9)BzUq RoSo=Y j&tClF[qn(@R):Dwi"o=Ge'*H5R!A\,7P E])F[,0FH`/Hb,Ol-CaiDhDZy6Xu3Jf(0L-H"=+F7R.J4Rl;Mh5+C'@B\#Rn.Kk#a7Ww,FcLlLj_{/Pk^Mk*A^Wu38VUr2eFrQ\z=Ge*Ba&5W6S5Q'A&=,?V'7O>Y'7S B`+!= ;P%2C4D+;I\18J !7 +hM8T>[^{3_~2tBa~3Tq%?\Vs$sBXw'Mlo>@`FfOo](_+Zy%m9JKKw6r-I_ZTUT][aZa/}#8O_S]WJdQMNLMNCIKLTNP[ORNILHFIIGKQFMIKDBMEE?D>VIMDKGHENUOG\YLWOEDEF59A446).$&9042FB<<8>?DG=2G?>A9<5AL=2692+.*043-#-/|$t 2:655B::u-m'm(Ay9o5m4v=t=yC}GNGu?k8j9`y3[t0\w3`6]{1uKg@tNb?b~C0L1MNi1@X&AX).E4 /.) #6#:$&6M&#:$:%;3'=%=%<4 %?I`5?W+3I$I_9E[3GZ/EV*CU Wg+xEETev5FX7N^3ozQ3C,>Uh6FY%bs@EW#7G=I3@1<U`4Yg;=J'8 ;M]o:K`%$=)HsBZv*Xt*8TIdl@<].i;>[8PNiJoxPo Ki?] +c"8Z&GDe<[>]2M Wq&Kh ^{ x$`._~Ki:O/DfL<8R)?%@?ZGdB_ ~DG` AZ ;Q;!:1K7=Z Kj4N6=a|"Yp-Tl(?Sj^u |5Ib9RJhOfCX Xs~F2T+Iw1Nh +>>#82;25I_u5eE;"2:n|!9s1bWIX?S4M:A\#1"7 +.D'?'; )D (`7Q9S ;V!<Nj Ww u=Hd1Im$#_FW8H6Gjz;9G!%/):M-Ak|SHWWgvsb  * "N\"Qt1F)On)Of +*1W'TFRAMMe J]s"Qc3H +Wl#EZ'5&==T.*?,B!0H%3K&'@NhA]wO$8!0 0<&3@&SfJ0C!,=N[9GX.K].Pf3(>) -9 %".7F+;K/%43A$@O,gvP"26E HW3,=%94J"!8;Q)0HNi9Sm=Gb,Rm7B\nDZy1oEJl!lE^_{9Vq65OYs&[w%Oi=W'C +Fb%az@9R:M;K$.>BR)(tZ+8 (apJewIL`4. +AR,*:&,>=N$.?BS)4I`wB@W!2N:UE_'Ng/>W!Kb)H`'AZKg'Wp.Pi+CX$I\-EW,M/%5"5'<$+%7&7*:!3(;?R2>R+Wn>?Y!oOeCVo6:PTh6fyLH[1(:+:H[73O ?X&A\(+G8R%5 .B5GZ;V;U?ZRn$OkD_@Y5L5JJ_)F_!HaIad{3c{2IdXw'Vu$On_}-j7l5c|,j5`u+@U Xo&l=jBA['%8 )@)B/G +D4O3Q%B-B 2F"6 +@.FVqBa~J(E9U"Ql=%>/2(Aa|R(C,"2 ,9($'(,#/!) %1@&4D'CT5;O2- .>#5E'2A$+:"0+"0,;%8I!Pa5-?ZjC/@/? "&?Q.3G !63G 0A&5->7I\q?awC9O.EAX#Mc,I_$HaXs+j>>VRg:(6)(1# &#*7'+*6%$$ ( )&/ )4 4?+0;$/9$3@,0=),9%(6-:".>4C)".#!!& # # %$%(127*,-,0(1;-1>);G47@1 % ""' !!$'$'.1%.2#8<(FK6EN2FP0GQ05? NX9?L,FS4FS4MZ7LY4pqVWe=R`:Rb5dq?e>K!DQ"}[kxNoPao8V󜔢Js1YޓMNK-!:Wl|)/3<+1='*5 ,4(+4(3?/JXE7C3+7!6<+#* FOHMWNCNA1<+X(5O@W(5LNd0Ha$Lk&Qp,Uu.Ee`~4CaJguFf}JY|(Yf,<5E *!3*@@Y+!:4S{T=[5adZYRdMMWKCVI+BTSU;7?E1=F952AH.92:3?C,C6372DBA:M>CBB29A>RG9:B@YC7=As3q4x;l1r9vv;s:Jn:pA^x0^y1nBtE\~.Ef:W1N >[Jf)_{@2N;Q,D/F$0@%!0  ,0;O,&<AT=MYi<^m?_Re1BR?P5F=OQa2Q]2Q^5=H#=G3?X,F #>(A/J|6JJgG{kg5a,tcVxuY}Nl5U-J0NWx -=W]z7*ao/H1@]Dbr!yOu,j|-1[xVu ;Y8-3K D^NgGbEa6SGf)F'DKlJm&Ej/?X.He{f .PXw=;K8JUn  8X ,NcA:W&C-Jff}~ zc| 6N ]w"SMh?^iIgI]w]wd{Yuo_} 7UD_ +6Q@YOi'lLh3+F:X +Df;=EgKl!<-O;>U(?Vz')CfAW7_}Qn5N(?&@Hh!c&9Q6O6O$?E`5RZu#Oj,r8<[6QOgRd|2G BQ0'71E;R5K +!:Oj-D2J5N 5PUvr'R|x#Trz#3N]wp@r%1m\y +{s"oTvV01/>zat.}}}/;pO/?)=U*627t8B]!3!8$?(@,7Ln%A^CT&Rf$1CG[cu/L`,=0>%!.5HJ] *>#ur.o<^BDGP)  4@?MJ[FXYkFV_tbyrm} iy~H'WfG`UlO&)8 N_/@7G, ';8N.3.F$?W5/G%*Cod'<2B, .#0:H42B(TeJ/C$:H%AP+u[uRqQ,D $ *+9!=M2"3#72%4xMnG@_Wu/HePk'Zu/Vq `y$q?/I +6P8Q3K9Q" 4&7#2+ &9G$(5 +( 9E!.?,>/ '8'7+;J[1Q%7KH`+2JAZ%>W >W?X#H`,AX",A 1IXt6Wq/Yr4Wn86K=O$k~Ncu?Mc'[r2b|7SKcd|,q5F{8s:Qp Sr)Tq([w(x?Gq-OB{5@CIGTbx:Ld#F`C\Uk-jVi3EY-Mh=C_4Gd2Gd2ZwH\yIOj=(B6K#8J'?O0BQ2+ %6-@%201B$+; $ +:N1CV6auN9P!C\&xU{V\u82CT&:K AS.0 #XlHjXgMC`&Rq8 ? ; /IEZ&DW$R!=UA[Ld#Md sDOj Uq)Z{/Mn \z+]y)Ys$f}/i0Nc4Il5]{(Rt"b8A`&2 81IF]$22M?\D`$:R0D-?"6* -4O"\vK/! $" +(70?!):28I0&' +;!)9)8% 5C.+!1- 1C>Q!Kc08N-C *!$,)8%$2!%3!,:(2>.#$#$+2&%,'/,4!2:'5@,3=-,8('4!`pEzR@RJHh^x(OG3+ZAMi|B17vM7E&KYB.;($0 0;&/9;EXb's{>=E"- 8@*+7#BP7;I+!- HQ3ZgR/;/2?12># +i= "&!K.>k}$i|1HV骩طx«®Ƴ貺čۧӣtrOFBS9L A7C{oV|cr-B$7 *ALe.Wt7Gd$Lj)Tr1@_ 5S:XVs8Sn56Q)EB],'CPn:,H$?D^+>V&2IDY(8RXv8Nj.cAXv4\x5,Jd:h=n@rDi;]/i>lF`?If(Zs7mJWr3]y;Pm/A^&=X(]z%a|&IUpm-~;@YG9PSSM8HI*C]@FJPUQ&HGRQP@FMLLTQUKKNMTMJFKNLKLKQMYUQPLONLLLNUJF<=?F:1@14QPOWZd\O`\XYWQE]XOLVVLAT>B@BANA<D:>AEG?BDG4ALGF:B;7@>5:IB7MSTD=w>y@j4m5r>Ys)[w,c6d3St"vEJfj@'DyYTp6'@&> ++D,@+ -J\&FUds4kw5Ex@]l-EX>NOTd-_s;Td.DU9IET$AQ#:IP[4EQ*CN#:EYf=IV)=M9K6K/EEa9U 2H8O50Icd.f68R +Ur'@\LhVr%Lg8S B^;XRoMi3N 7RE_$0H%B +(B4M Ga lQq]{y-#g|Wx[x}Uu|Ss\zLhz%}2?^[{ mZz81i iPp_mMmHb0>0B")kZ}w#@y'Ki7V1N3Yq/4N *D\ If&F.M$@:VOp#\{=%3OGbTq a}'x<| x"2I_Uq,Rs*GkNm6Pn'H_ Ke!t3XsWqu'8v K[tQlsh 2Np0)VQh63?DP(-#<5M83QLmUvIie5Rm"3@Z 7N +.DZvNq}5Xt 0IHd,H$@=Z0K'@$@EcRlYv>QiNdVp+n8Tm!Qj,u| Rs +Tq zJ2;Sx"?YOjTxU@UJb;VrBKfj$Wt"?WZp!@R#5AS=U3#;25M-%7+((,>N35F,1B%/@Tb=M\2yWgEw\/0?&*9 )= 11I&-C#4);;L)@P,5F#CT2,>j(d'HgTt&:X Yu&d+Ccz"Gx7@|=~?Hv9|Ao4l3o8{Ci0rZ-C_0Ql=5P#9P'/D&%5/? - 8N)(@61"54F)!4*= 9L,5K%7O!F_)Pi.hEzU#60?IY4CT0 1 3B_{&e'WvQob0B\$>8O=S6OE_E_Ne&Oe%Pi%b9Z{/Qp%HfHd@[G`cz.Ul!RjSo^(c/LpSu*tLJh%.HOg/,C 1J?YD\#+B +7KF[)4H';1F&- ..B*'8$$   *62$< 5 &$!  7>0 , +(88H0,$%*$3%4,9I0M_@4F$:L#FY*/C!3 * (,9K*=Q,UjC/C),>FV05E!LV7JU2DN-CL0OW=\fHS]8Vc8gwGWg:Yi5C05C.AN-EOWbDq .DM+:C-3CDY*G`)/ID_!Mj._}@8U>\ Cb#Ji-A_">Z"0M>Z(Ge3>\)@\-3O>Y&1I4J2J@Y",J,0 !5)33%<QkD-G '= 0 &3$54D!4C!$64J!E[,:SLe+D_"8RBZ1I 8O=VOi'>\=[Fe"e=Tp-Pn*Ro->Y9U>YA_k_0.LKi ]}1f2m7HEb(Wu"e1Zw'RMZm9|Hn=_y'^y!l+;@;257D>H?DHHI]FIJG?CVJHPMGGKH8AEK!3A"0 *5-88EeIW#I[I^BW]v-3KCZPd#1/FVr#:V=X=XQk#OkEaHdtGEcMmWwGe \x-@[&@&@Jb/6R8R>W)EK79Xsy`6|g Q4rwm` zM?| KtcgF|)l%A`0O.An_ +IB[k5-Ms5Nj8P 261m?Vu.=Z)G1O [y/>%B +0Lg'HhQp6t06z(ds+%61DBY IexqC6lw)@_} +{ SoLf.a|Ic+Ardgm Ooxy ]ys[zwx^u +Kbd}9N.D,x5=ZumSnUqXt"2Q .K *Tp&SH`^w!@X YvCdLl B_ +0Ig Pm+E3M +Ga@];W :Sl%*Yq%Tm.2 Vp$Vq${>02Jn e ]~ Ka}c{ #687P?Ga 1MsGU$ wCOghTk`3G,>&/@ .pI,@h}+w?,>#2 &7 +1,&; +oALbM_ UhS;JSa /O_;J ;Q 6M9O,B^t1qN[x$LcuZqutc{\t&}1kbu)bQc'#5u2s("2M^A';.C&=Q25L)cyW(>&8'# ,<(#2 0=N2&7=L-W[v3>WF_q=Xt#~zT2K#8 8J#(:,<=L,(6$3.=8G'& +( .=:M"duN$ 3B", +WfD.=0C%8 :MDY&6ND[%8RTm0Ld'Qh07J:MpLEZMf+Rk/D[#2IbxBXk;K\.IY/ , @N&L^.H[&7KEZAV[q!Er1Us?^ ]{+n:f+p1Ah(A~?z;Ku6t9v<~Fn8Kr9w>o9R\w0:RI_$DS,Tg.EX&=PK_ F]JeUq{?y;MjPl>Y=X+CH_F_A]Jd#Jb"F]Ha b~8hU+C/?Y#C[#Uk74HQi90F*?8K1+   %  !"..$=4J/"8"  +  ,3(+6$,9' "!0/>(-':0B'% );&:!61ETi69M%8h{V8I-'*&:9O$5K!+>/>2@ 3B!:J&*<=R.%:#:*AWo?4J:RD\%J-JY9KY6DT/`nJ]jD]hAkwPq|YOY8MX6gsJ_gxCgenUh{Ol~VPb;`rLasN`oKixUdqGds8QnKesԀBP.;5A+6 +6A$L)2jD !+ /<5$q+2C?N5:H36C+9Hjw1TUk0>>G *181:GxQ$9FHWm,I"! ,+PU8mqҞsflpN͝ɦ̤Trh=G>V5,%O=NNSbZOqܦͷпߡ⯶y&0A?UȤȡadz9E]s0k~+CXGZao-lhrv^甸kxWK\cR13=@9DO-sRP92Ji&DA`>^@_ 8U/H+@(; "20CPh.:S_x>^w>Rm2Kh*Kg-D`%;XWt:Jg/C^!@Z9TIc"[x5Jg^}2Vu)IgLj#_~5Xw(f3RrNlUp$Uq&Nj!g=b7Ww,EbYr2@[^w5oAD_3NWr o1t1;\H2/b(97:>\M'~>PJEH[GA@B=:;?:9>>=A26BJGIE?GFOC>F;:DVNKLD9:IUYf[YOW[e^YRI@B@MPKKJWLFHMCDB?;?<63---9*;4:+;B<.DH@>B/?=6;@:78AE1C4q$v0n,t3@s-q+x/|0=19t-t0u1Fh~(o3l0t6h/{HuBg3Tp'Ke$/G.G=U%/E.C0 ( +*?N6Ob>K`9'?3 1 +%<,F,E/61 (:Q'$;(>0/Ot?K[p:zCWep3bn1my;_j,gs5[bp<[k8O])?MRa,9K(:'86HEW$Qc32K/J 0FAXF^-H&B\z$9U^x>A_ =X&?C\ 6NZqG_Slj"P{50G7O1I9,E@W k.?U*@Zr8R/Gay$A,C-FVVk3)>3"<Jj %Ap1&?C[Tr Qw)KkQl )CCZBG-E/WXvOjb}r Dcn#=;YJj7Tm+D;dXmL` +-CFVRe=M %AT.C`u$*<+4I,> !3!2DUct-CX $8=Q}1y.)%s{=!.2D0E9Mt5RGi]p Yho&d{/"58v'VnK3C ^o%=P 7r%+:/? 6J.DY;8M->S06L(?S4(9 )%3# $7/A"+;1A>O*>S*C\+3N48N,6J'. $59N*3L%4L&':,?!20E!8K&.A,?!47K+5L$2JBX-BW-5IDZ*[t>wV]w:tLg;d6Tt+Gf#6Q?Y@Zg99U pCd0$GY*F"8);/@%6 1."42D!$5% + )8/@.A+ +ES22B+=;O BU%1ECW!H_'>YG` m1m2x@Ri!F[AVOe,J`!f{:g~>!<0O6C[_w1o;uDd~6Mi+)C 6/D"J_9Ha4Ib8)A0+?$@V6Kb>/;R.TiI( *-IUsACb0)E(A!7(<0D$&;3H#CZ/C[*Zt<@Y Hh'mN;S 9:RF]) ?S02I*@AW+Vm8>ULd/La< & +4H&YpG2J-D(<, '7* #21A$-!5#80F.F.D>W)Lf7=U!ezK#2+1*-4+#*),%')$&')+,-&'*$!$",,"<>029)2=))4"#-'$   ".5"! ;?)*.FL6LV;*5KT8LR4PX5\g>_mAjxN\k@etLHW/P^7aqIZjCjzUYiE`pKmY{e\mDqftKz]}a}fNZ6O\3gvCizDVe5i{O~j}hwdn|}]kPthKU9_k9V`/]|#EJU!)3N19F/?N1=N|?5T&_nHR`FR>gjx +fu%u0h;!9 ++"AFT6H]ؿ׺űߧx{[Zz攷Ćrssgnc 9A86;3=16L[_O껷ʾ߾ݻ;wqӠ~_GH7%ޭُuex>l@YmI\bqAy~2jx"\j{}~f\VZLDFBIKA?=MM52(357I126H]Tv)IhY8VA]"/D0D5G"5J_ Si.F^%Ia(G`%Ok.Lh/Lh-@[!9V;)G0Q9W':.H&B,F3M#9R-;T,*B@W$:P DZ+.G]uA=VE_%Hc&Da&5S%B +6R>Z#:U!Zr=Jb04O2L,F3NRm9E`.F`1+C- 62+' !1/@$.#4- 1'6. UG`!GfnC]{/Ki Nl(Ro-Qo)Ih]}1HfVt,k@Pk(>[Jh('FJd%\v9D=9FAF<57JHBA?=C661)=LNH>CI@BEMOMH@KDDOJE>JSSWVOK?EAUHBHJMB??K?CBLKH:9>B>8.=?;C7>CKEFFIFMSQNLPbaO=RAGB=LG?:G>=3,=7mmC+evw,r-r.7K82OOKN2Ow*|3~6Iv/v3s2c{)F`Wu#@\7Q/G#< (?*?4 2 #3%5DU03K* 4(<(A#<:22Da/_xG-H,EKa1K^2`s@dw2ex*H[ET P[U^g,CNXc)mw:S`(IV"Q_*{SVc,}UfuC\n:Qc2$5:N6K1C@S)N_3Zk=Sd4_o&@,C +Xt!]|`Nj_t-(FV m.Yr)A #9<@Lm+Tm_xE.4DT5B&jxw0uv#0p +AMe +bZ{VxHfYy{'vz0Sn4LPg`x?G9AZ3N4QPk#Ph,/[s*0F0HF__{)%ACa'7Q?Gba(niG^>U'<9M Qf_wez&4BYZr$j:4r;Un'>^u)?YZuNl A` hc|bJ[ ( (7+D1J WwJh+KeMgy.(F4THg +7Pmx&H1I$9WodQmn !D\hOke*HkBBtxMaKc ^yKcEEX1D>WV~ANe"5$9 + 69?]5 ҁm"+6I ."1> ft08Mo$ayRg'0Dew8lI^i+T,3E=NO^- -p;Sh wBQa[mZj +,@dpmSXe ;Qg!9Od~@lz$-FU=0:J:N(q)+7u8:q/I^Qc+:+:"$3>R4.B$':4>O.# ! 9I(1 Qd8':FU2'6&3ID^/=Z&2M.F$<:Q,';7K*4J%"8":,D 2H$;Q+2G .D;P(4L&-E,F7O%@W(.E3IMd4czFjIUl3b{;b:i=c6Yz-e:^{4KjvCJiHg0PKlUv*!6,B(:1E. +, , ++ + . %4# !.AN.Yi@9I"!/$1$14ABQ+zDf~+^y!_{"_| Hdi(DOj,j2k4l8k4j0i0g~0h8Nf#H`"Kb*F\%;RKa%Tm0/J-KGd-9?XYs3Ol*@\E^,+@%83D!1F$:P*/F)-6]hMT_A`mNMW9jw}\djJGT4MW9`hK3<BK)N[2`izLbsFizLRb5;KYlA`qHfxMbqKXfE}iVg@Ug>J]/Pa5?M%KX.kyMlxPw]y\hk{dxfXlODX +" #29;AH614,?P'qA-F1RE]|,B_$1I-B.fՏXl-.ECY2IKd)Ia%>Y"9U?[&,I5SBb,'F9V$)FEa5-H/%<6M'9O*-AQg4AX$;R!CW'9M:QhFkLEb--J.KA]-,G4M5KAZ*Nj:D`//J'C[wEF`14N!1 ) #3,+9",:")$ +--7J+!6#8.':':(?+A4K5L&?Rl.Lf)Ia)=S7M9QNh,F^&;X5SrQRr.bGIh,1L1IMe06M3FHLPTN:EEAEPCGR49:FWUFBH17:=> KG=CG44?4HS=_VR4GNCfECFHOSYC;GH`v(Lk E`1K&< *",+ () *9 +-+ /3F/E'>!> ">B^.1NMl63Q,I ,H 6 !#3&Mb'Yn&^o%XhUam+LV_k2sG`q2GWETN_(M^(Pa+fsAQ^-AQ>Q;MI\(J_-4H,A2FTf8(; '; .>,0C=OqIJ\"H])?1G;TX=XTo&B^Kid5Le"=(F:U&@4L8PE_#ZMoKkQnH!m w|mNpIkey.DFFh Rs!>\4r\yj"LmW]y i`Sf>T +'<2Fl[pWj m^q(Zv8lCp"Tr Mj8P\w-LAa4UD^z&168 /K27mP7OF^'(@55M %Yr%l)o"G`z,}&@(BCa +D30?U E_ 8R=ZNp X|W{Lm Yy7^Jj +dZt6Sj<\u#<,G0J .K0J3JH\DQh /D\tOh&?7-I3"8>VD$v6|BR*>T 5M QlPjl2Um"466Q B\r2(>*AGb}0 <Ql Vo agy$9H&) +2QkE`~B_q.%I`Ilbld]y Sm c~=UG^9QbLii +Ur$gnjv SANtTdFy#%HUI[#9ey$u'4B%7E07G0*>!1D(0C' 4#7&+* 5K!Kd.Xq:9J!*KY/>R$Md4A\'qVTq;5 37 '>-F /F -B'?#;#:*@+@+@(A9S+n_5P$8Q#9O!8O!Vv+yP/ : 5#88K$#6#5&.=DS/N\;3A&(6D#.=.>"0 ,<- %IV0W:R;RBZ!:S6O,I2P+F$?#= ">Uq8A\*!:%:;L)?P,9L%VlC[tL(B  + )6#)9!+$ 3I+1G$.FvDp:EaVs%B\Up)\w1Xt)>Y >W Sl&`y6Vn-4L 3K>UKd$Jg"Mf(@W=S3IE[!:RF_AVqAf7DdBaQp(\y2[y5/L0K?V'*?0 #3.>9H"=Q#7N"#;6O( 4-*& 05H1## /2/ -"!  !#$%%(&$/!DQ<"4$99K37I/-*? 9N*0EN1%61E#'<2H1GK_/m~QRa8l{XgvXJYA  +,:"*8;7?>7QRI45*>?59:1891 ",-%$#65,24)18)2;)=H3IU?FR<\eNclRueFO:hnXskpUemNP\7_nIAO+J[6]mF\lD`kGW_=hcoNR\?hrUluUy_\g>sUp{dnR\k?rY{XmFFW.bpKCP.BO,n~[oZk}T|cuydo|jp_mRx{vh{lj~qMaW'"%" =DE/21 !"&nw@bm$,8y#\g !~((Hu|fM~bfn8' -3;Awy)Q`!LY8E;T+3C*KXYC '0.">9+WD`ЦϬyEyDWbosʆ[[fjXLk?;$@bPQ<,GJWuʂ㵺ֺ׻ܻܺۻݸܺ}^䙱uT9Td,!C7<:C,.<&.%+*3/9 pa| o,"aMkUy._,2Q///Cbx.i9:Q$<D\BZ Of+Sj0@[$2J>W(8S#,ITs>Vr@B^-2N,G$>+C2 "7&:*>7M:P!#8:M1BNe1gI?]#-I2L7R$*C#; 4J-?3I/J.I/I2MA[,Kb5AY0+. ($ .'4 & =K3!';$97L8N"/E5 +7 8 -F:TW5N+CJc#[v57S.IQm8?\![y;Nn0Yv<6Q=S :P8O8O?XLe'Ie&Gd%7SB_#B^#Ph0-C6MCY&;V *F/K;U".G$= Ia0&= 2HPi*o@qA}Ka2e9m=Yz&m7KgOkHec+a})tAAF9<;9A;@BF=BEF@?@HaShQXWRI>9HL8/q#ay#^Po'3O3J/D  &,)</B+ - '<0F:R.2?^'b9<[Tt AaLa~Zv]zD4'YpF]h}-}/4G)=8Ws;T a|!e=WKgVr Kf}*Rm'hCc ; @^Nn&0L%@+)":Ka+uK>V2:QWq*Mei28O3M[|,6Xgc|XxK\2oE[g R|(n6[^T m;bs60/;X(_}F'H(D oD^{$6k$)z\O08 +N^a2oIiGag6[fb#/#03E 2D :H/9W`/Y"rQ8DMZ,`l >@I%"24I+8L,2F))>1 -B!4I^8.DC\&%=!8(<1C9MVl;6N>ZWt8Tq2Wt60M:V$,F?W1&<2H&4I).D& 71H#>V26L(+B9O)[rJ'>D[.F]0.E6N f~J|BY!_vO)L]7')8-!0]nFUe:$5 *=Qd74D/@9IDU*FX,*@ ?VXo3Vl.Tl-Ld&=T/D@VNh(b|:}U1J+CWq-rA`{1`y:/5D 4>&2=M!g?lXB["BZ"W- 9:Q,6I&3E J_7)B?Y2?W5&8! #+. /,>'.$VmFYpICY3ayR8P--D#&</C&avTG]3Ph9=S&#')3!!*%.# , . '9Mb7Tj;b{Gw\*DU'*=0:LL^)K`"I`j8Xs$=WJfC^-F -ELf#1K>Xb|@0F0Eh}G3IAYXp5Ga&)C +*@ 0B/@8L=O?RDYG_0J +A]9U?[Fa7Q*C .G3 +:J($3, -9"YdO$/ * %0"(2 ,/7G+=M/5D)FP;SXI?B7HI?56/&&!,,()*%;<8$$    + $#) *  %  +8A'WaB\hDiyQm|Ol~Qx_x_fvOKY7)8{hXaG:D+ov~dR\Re/|7y!9<DO+ml +7x 4#$*/K.krlE%(&A6A+#@+# !*297+0.KJf~չw᎞bp쀥3BoǀʞyQi~,MHtq-,>MO2)wr&_UAxாλӽںܸظַ׹ںx]ڛZNXFB!<^pzubb_hHNC?YHPWVSJ,)-D6>68=--7A<2>DKFO5omr:A_Jn'\{(%B/H=RX.C%;Xp(PE_0IHb!B[C_-+B0G$>%A#B0L2L#= : *B4G6H :I#%6%@*C4M"0HJa5(=#7. 3 + *)-;")+=GZ5>S*-AI`4Rl>!;8 (D]zBTo2Yu5Mj)Rk04OPn*;YLk'Vt9; 1M7R =Z"[y:Sq58T:Q8PHa*Ph0:T8ULj)Nj-Qm1Rn45 7*@;L!0E(>(?-C+C0C/A(> D\(8NRk-Oj$GbYs.a;5UAaVu1~WRo'IgFdPl e.yAj*=W]LA~>4"GPED@D?DDMB<58626@>?BHDQHJC>F?=A06:>EEJC@@FJHIER@FMPWOID;EHLJKRNQVWJP\WVSU\QRNRQYKCKJCGMI7=JILEKKDCPLFZTY[YXMKPXRX\\RMUe[WTSQYWPNFQMFS?4=DE`WNSWYPGLLJ0PQHJGK@CcTM3HF@HC*OH%-;=+63337YmG^Ic6S WGa#?Ye~>/K"=8S E`8P;XV,G-Gc"$Da9Q7M7P Rn}@Xx[wMjazpC`Nl }C8\=`4W1P_5ZX|;[Fj6WGhAc3Pbh FWq a{j12KSjcy!bw 5I.Bgy)Od Tj+A$<KTR:?*Jq3`To!$; ]v7QozAUp:(A .F^!i8Ql>Z W,!7uhz Lh k#q Zq|lXniD\y9y(XRmu-N06Q +5O/'@Nf\r_nFUBR [nFZt*I2TiWs 6O\u!K'^ss9.DBX4NIa Vp7P-D?X +[xOmGb}?1H?W +?W7Q 1Gm.QhD["5!*,E + 6lB:eNgYt Gex.FbNjLfjUo@Z3Qj,DOetb~g cd^Tq +>a|[oCZ_rj3oL`x'ISQ^H[Rc 3o+4I +\u-BY'.CvZtD]F.ex }*Xe FP*%quz;-.)9T^^d%-8E2<+3ht_l7D 7K I1/"'co )U@8*8nD& %2-0E$9N.6M-:O-"8#:1F '; 6 kL^w=Rk(4K 4L1HG])Md.?Y7U?aEi!Mo-9V -G3'>-B"2G(+B">V2)B- +*A!6$;1G!0H +' "0$4O`7$6M_2:N7J-A,?/A1C,@Ng'/G]tAR"5F?UH_MeVp$5Oh*F]%4-D`v?Og.5PF^+'9 &7 8J`uF*A /H!9*?);7H1@1A=MEV!$2+8-@>X'pcxgevReuUTaHcqWymuixiyXRa?Wg?Vf8jzOv\]mGz|v{odmX^gR_hQ{esyTcB$1:D%HV:XgMsjvjxicrP3B&gv`pk ",0& =AAEWYqXk} +"N)7|/#$,0-9H֦Ĺʟ޿Ιen*K)RmȎ_y/6ELS@+~<,iZNGQ6VNOQ҃ؐ۳Źչٸڷ׶׷׸һƖڮĔ٤xJ6=A>kb_hdb`[Q>XV_MQRLGG8*:H9C6CNZXWIEHDDCX_;%TjLe4v%B\Vr=Y _z4AYTi/K_"EY4Jk8PhRl#JdJg#:V>Y'*C$=+E+I2P'B,E"9 )A)=+<(7!1 "46M#0JA[(3J=S Qg6B['8S;T")@)@=P+;L&&7$5*<Ga6=U,'@.F5 7M#%* +RhB2G#"84G!VjC$8' +& ,07H%7N$7L" 8 "=%@7T&5S!>[#=Z!8U5P9RIf$yR/PSs0gK4)D <,GC_$A\&=U#Og6,D,EHd)Hd'?\Mi.[x=9VD_)H_-'= ,B"6 + 5 +#70D2F?Q$=N!=M @T"EY'D\$Sn2Pl+*GZs7Lc+'D +1L%@ 6-ILh+A^A\Xp)]w,h.LDk|-}4u*9UE9QPDDELN@::55:/?:>?AEFFFDNHAFE?LGCKMJONIB@FK?NA?JHTQLE?Sk>Lb:N.%9 #;'?"9 +AAV!,-E3L #<\x2d:e66/LIhA^ sD]58.K*Acy,4LOiQm ;:{6)#Fd%8^[)n@h=Gj[~Ag6\1WDhRtNn?` Xw*Zw#m$Vq0J>Y9Q;TMe OgD]KfPi$;Qk?Vd|$9H_D,=i})7JObG] 9QLb=U 2#=`z Ql\wNi9R+B>X5M BZ^w#;T$<=S +)B!=G,D ._c.@Md +4>OkO:FVkD^5k,|Qk)O8R_xoq >5VmVQl_| /SHA6Amu `\o0)*?. $;%; "w!2. G_-|OKfd{![o=ptZ:(kh}bDB.!rt!szvuz%[]GRGNQdnT&.MoG` 4K%/F.?/@->"7*2LhH^'Lj+Bd Uy4Bd$.K2 '?4#84H)0G#=W,Jd95M$36K*+?. >S.U"C[&Ld3BU&Ue8*.>7K4LI]2' .WfFDS/(8%2E9L5J2F0D=Q nQTi4$8)=G^!F_ 9SRk*^w6Vo.Uo/0F.B@TWn0l9Xo%vKXl66E3?6A#P\7>OVj+j{=qDWdy7i~>Zp/AYwJrBo@OkzKKeIcUo%l5Sqf0nF+H,G 7Q=Y"@\$/J1K/F0F>S(3I)@.D$=0H"200!5(<&:-C! 5/D%,?7J$2H D[4%:' %!  *5+*4&-9(+$1)#1,!2*;.> %63G("8--'5!(H]%70BAZ+7TAa#6Z9[]}>'I &G)F?Z5;U6( /1*  + ,!!,%2>7 $# !(""("""#& /!!%)5 !-S_G$"$) %'*CF;&) !$&(/ CH606;A'5:$AG.>D*U[DQXG-5)(%"- $*+9#+*5 ' " ! +   + ! -5#+*5]hGozW]kK`oNziOa?UgC`vN[nK}q}o[eMBN9`nUkyayrcalMVbBtfsX;I/GT=/;&$+!'0#5>.LUB@L1DQ3BP1Q_B_nQ-9CK33>(5E1,:$VeK7E,=L:ʽ"'-80),)IMP*>D!.1l}jzdut!/*=G>OXL28+bo8_N^s%:=;$, '?=Sg~ox*%)&z7.@75 <'U1m/ 08&" #,x/AK <3TszʃТwYl?-SQ*yCIQ@k,TMvGD`&3^턾^,Ca}i팼`oܐ̷̽׸ײԶٹظ̽οv̓Ljǀq|qnh^bdY`rdfognieb]WQVOPMLSEU;WU[Z[EK?:9;QL^AFGiu2|9j#sOgy7[t!pA`x5/D_s8EW-BVn$]u*Wq(IeZx5Ur5:V$)D*F:W$A_)=X 7K GW.5D&6,9JVj9@Y%"> &A*A(>@Y(Pj89U/M+F(A(>1E=R*)0 +)D=X./I!6N#13-D1G"A\2/I_{L8R"@Y*I`7 "!1#2-"2+=+@0GTnC,G5 Sp@Y!Mi.Rl1Eb"tO]};9X<: ;4O!A[,E`/'> 7AY'Ph4+F5Q?\ -J,G,E>W#>W!axBJa*3J%= +(@ 7N3K.CNc55J:L=O*? E]'Ql16S)E +22I,C)@)?0H+B:Q:Q>TG\Hbj7E=k&az s8E`n XI9F=?BCLEMIJHKNRQZbMLTQVMGOZW[\XVUUTGGEKHKHJJNZ^XOJNSTLUOJCAFLPSYHX\a`X^]VUXWTXab^hcZ]ZgeYO\ZSaPHK]MYMPXR8?P8AFS<\M? , ' +-Wh7CXF[Ma'+>G]9J) %3 2>R]//:1;IX+Vc6MZ/8F@P_p4Oba]v33K8KgHcn@pAn=j6Us0M?YIbQk!;U5M2%= Tl9=T$!;PCU$EV $;C[7Q .E2M 1w5TuZvjOi,SlyK$k3CDdu.;1JD\)C8Qn83M@\.IMi @\5O/=W:U Qce{/''Wq&VlH\Pfrv#$>6QJfl%?C]UFaLe +]v%2I%?7Q2J(BzLb,?(:IaB]FbMm#Mj[z,/MYxQpNjIduq}1RpId-I4$8o@%9TgC_Yv Pp8T9P Ic Gc Ib.HOht i}}*-IZ[n1GJ^ 8O4L)C9S^y"Mhl'UBf-G-F1MA^Ys$?Z G` +[s%y=NN*!B#|%2K:UZv 3M.GZt"m`} b}q Ph<1_z4-@8O96Xo:T/`|Smz vp-_VX /(:)@.30Q6@X3-D!"74):3D9I$:L!5K=TNk),HB`Ca9.'="8 .K@]!,M -M6S"0%<(<"5*>CY29U&Mf7Ri<6L&57K))<-B4I6HDX,FZ,nSe|JRi6e|Ehg0J 9 7K9N-DI]-H\*0B>PTk1Id#Sl+3M +;TTm-.C /D4G;PBXNf!^v1@YMd+>O!"- +'0DO-\j>Q`/ANUf0Uf.CU`t9vOWn-`w4Ph#/JyLC^/IE_C_`|.wAPo"9/M)C7Q *B,E*B$=-C)>2$3/ 52)<&:- 0!3/B$!5"6(:%7>O-(<'"7):""2-1@.'2$ %*7#)6$ .+% /:J0.-H[-+;"2, * #4$5:L&3E +='70@@O).>+<3DRj57N8P1J =STh-.C9McwF>Q$>P$!5 /C@U,6&;3K@Cg',O 4VHj(#E&F 1Hg8.K#0 5 950&?L@Yf^Vd\PcT#5$'8*)&1)!)!$%%+$4?4,2A0JYE +#-'2,&#.$+ #*!(.$(, ),#57.)*#)*"47, rwb?C-9='9>(5=%KT='0"(!.7E+*98E*)4BI9DJ< "(4=$HR:HR8VbFGP5=G/@G0LS@:F.4@(+q~`sbm{XaqKL[6Vc>JY4Sa=zdQb<}ibuOG[8RbA9E'RZAVbK@N8Q^J!/ +  &$%0!-)+7(5'47B,KT@&/Q_ON]M8G3ft^=L9Q_O+"$,%>ED #$uwۉ@S[CRWCOWCQUvLUK=E3(nQDNz [vkqw)."0DH9yl} Pqp%q*->$6H~.28C)-4_t1}NJcSn,Ts37V+I%C3O4REf-jRDc*7S'@-*<(8 (La/Md+?ZKh-F`,2 7 3Eb,Ji-rTNk36"92 +7L!9N#8O$'A*C#=!;ZsH:T&+C9S*,,H,I4Q?[)"<4& +"%30* +(2 ;U,78(D:W$C`)?Y%1LA\!Fa%9Y.L%< 7M 9N *@ 2IF]$?TGbk=Yy$Pm_{!Km4i,78#F;2BFIF@A;AINE>=?6A=IE@?@>>?A2*(# "=,Kc]A`Li-0L'A-H,E3&B9T#$B(EC`(=Y;Y8UUs1A^[s8D[&~a`t:Wo,8R >V +IW 2 L\(\m5@MQ]0R^1EV*:K.;Q^-O]'[j+O]p;Wr*"<4L +?[6P(G=Z&E9W +Uq:Y +F_]w09S @YXr1Od"G^\v24N!.C 1C 5F%;>X;T=V*C&AGdH8Wtg 5MEaRm3n"Vle3O7RZx&|jYu HbIg1O0O?aDg X{X|'<`4X=_Qp !<.I {N(FEc6V;[!</H :UG\Of&.F /ILgE^-G3H 4Rk%8R*DRo`*n1LSnLe0FO\=xX*D'BB^ RC]0I=XVo*+G_25NC^1M&?9S[>SRf-J^)?/2JQy&\ju* R#&1ZUknX^(H}"cwj *pHZt$ezm!E2-%m|F,m}et/8G!2*frFT$4#5*>OgBF];0?""2 1 3C @P'5Ld@lF%DEc#.JPi7=S'&9PgN;Y"B1N8S#$<%:)=0D -BEZ3/HRj>XnE*>Pd?i}Y=R,1D0B=N#=O"7H>S!qS^uB`wA'<7PiA@X7N g5\yp5E`1F 5E(8 3E8J?Q3GKb/:PX ,GD^\x.1O 'E 'B!<7(=#9#8*?'<,>+%  1/$ !,% -(8// 2,"1( 0B 2$7%4! 1B''4"'$%(5"$2&4)8"0*,<##(5!7D/6C0'!,KRD#-)2'3*70ui=H4 + !"+&" )&40>$+7$1+72@ 3CIZ0=N$*;,>{]Qf1&9 +"5 #6.-?#-9", WeH+!#3#2 0.%5$39H$#.@3H!0E "37M";1I5M/F 5M'>Re59N.C$;.G.K,G%A$B +1T0U2Y?d0T2T2V9\2T-O.N.MA[01G(=(6 $1$&2+=J@%7#%9""5/?.(%0"$.$/5.#)!%#&-&(5$-+7&4;,#)*3#('0"$,!#*!%,"'-#(-#*,%!$'15'05##+6=&/7 1;#&0(%%!/,6E+>N.>L+2@*6#+)1 +% 2@!6F#%6"3?L(JW5anM]kGN]9ZhEcrO@O*bpLvcorsv`p^v}m|Ĭ\gS~~FVG-7+,5*"&& '&*2&)( )(4#"/&3/<'3@,2A/,7%NVF,5(T^Sww0@-`pZZ$ %.F;V$3PHj+eF3P"; -C-B*@)?5I7N& 7(A0I"F`6+C#<)B-IOi:YtD0K;U%4J 2F#+(*'77G*$8"8&<7#=4-I.J2M)DId*So2;W>Y Pk84L+C.C)?%92J)D9UFb%Gc&3O@X&(@0F0F8J6I,C2IH`&Ha&`z>E_$E]$-D D[%a{C?.3-78@?>=ABKEBAEIKHHLG@AGDASMPTY]bVG9)(-4//<7?J?8=I;C=?<;;EMH?8;?BEHGPOVPKLJPFMFSFIMQWUVNGG84=;T42HJBGDBNOIOQVRVWYQT^VY[XSVSON]VLXTYQ@@J?7/(=<;?GJVC@G5387FGO;73 ##0\k:S9R9T 4S6S"= !;4N!(E *H 9Qo.C_$ < + 7 +*@6)0 #?'F:X#75Q*F3f<;WQn!E`Lc(/C ,DTo&^C\Dt50]p5knBmzFR`0S_,IX+R^0ER@KZg)Yi&Qa;M2KOgUn)D`1$A1M2PHf!;ZJeLg=U6P F`5L NhSvHLeJblF;P;PPe)=VXt'Tm&0IB^IfTtz0g4Nl"=XB[YqFc g4'D8PpQ`z.W7V4RVp.>XA\1+)Jg&)G#=2K2I(C -I%B'C2|6_|)EZw rm!\+Ys.*D5PNiGa"TnXsm97P 4 3f|F&>14Pe26)@-C=P(*=6E\ +D#DNhT[s!i#Vs!B_Dc9~5;WTnSn @Y 8.BLd |)|&F\ZnAV2AXazfTlUjPav\N` +@S &<1IKf9TrH|Q5L#;:S+D.G 9RIg9S +%@(?0HLgWp~>q<:S>XJa d{is,OdwU`1F)@(A7#RmC`PmaOnwx"|;,1 68` o!F\xr&y:ih,3 ;J_hx&,: JV *1M[JS +)) =A ry)R\ LWIS 05&%1114CLgv!IW7Cpzij}Gnn WS9;]e " m~`r +*[p1YdZj]kiz`uf|[o; "m#l{j{OaVg u)ELHR_hS\ch PYju;L"30[rQ@Y2$<# $0-=,@;J'/Am1 >-A *0@Q`-BSGV#9KQg5Wn=0G)>3F!6G"*-*:/@7G%$2 S_=3B8H1C:O /F>U&3H,= 9JOf/Pl-Ws/Ys07OSh+,@\q4by7CYCZYr2Ib E]!Rp)}X3J8HBP&@Q# 1 7F#+8&5AN,1@2C/@:L;Nf{>C[QhMXqaz)e6Qo'3S"?.I! 963$6&6%74I#*# $$  %! *7! -'.$5 1!/+.!2, (!0,+6*%#'4D+/%6&6 #!&2! + 'EJ= #5<(HR<! %-"! !"!&#'%4(&5%2% #+ (8$) +/>"gyL/ #5 ->'9FW2!* . =L*%*@ 60I"";,E6 *@+A4K-D AX @U2E5H6NGa*:VFe+<^#:Z.N8^>h e7\1Y8_-TOv5Jp28^!Df,9W$:V)';CN94?41<4/>0(<",A$(="4C,%1%. ($',&#)"$+'-$/4.0;.-9*'," $"%!$)#""+."#'!)-6%/#+# $"&$ -=L*2@Q`==K.9E*xsd+:v_sLK_4\qBk}SarHO^4Ud7}_mxv^lJow7D'\hDmwb0>0?"8G/SeMMaJ&(\l^>QEL^TFTN9C='0%#' (#+!!)#+ !()!-7/ ,"(0<*3A05D2'4#&;tY5PSQYkIE'(1E.L<)ACQKMEBChߝ{qnVZX_cb_][]koqgnsXVJQJTSHIIMMSUKLLBHPQGVJOTK=ESZe6MgQOi+>'5Rf,Xo,Vk+,$7?RGY%/A3G!3$6?QWj21G 9P7R:X +I+E#<)BIc4C`)(D 0HH`.4S\Fa)7N! 5 ,0C+@ 5 +#3%:(;604 3 !733J-DD[-0*!('# +.?$$4$5 2/,B2 6(B7+3?[!#5!4 +!76M5N,C 7O3J/FDZ%by@D]";T?]"13J4J2H0H)DhS=V $>Ng15LLc/C[%7N>ULc*F`%=[<[?\[v/OlSr_z%o+q!f|[^QVNIGEFPFOG@CD:9A@@FAC=DICBGGIGFBMKQNPQMOLJPV[MB:30-,743A>=-6;BOJ=@1-04='16>845,9<=7>=PNRXP@FQKB?MH?HPOQULQQE;;3C+562;6DGA?0<53AGEGN?18&:\kds'E_:7"@+F?X.8A\)'EJg,@^&9V$= ,1 +,,26A^--KUtB5R!+GV=W;UA]9T :T8TG^:S7QtDh +L;` c)6Vp&/C.@3E>S#8P "<Kf2!:2>V*'A ;Z>];YC\0J ;&D6U Fb7T4PcVuLi[}8:V!9[D]Tq9TA[ 2M 1K2R8Me!h[v haw8kOlhs p-`{c~g _w u&]{Hf9S*%kRh;T EaGf$B-JWBZ GVo +PjeVp S 5K7No:j00I&;Zx2dPs-/MRa,@lmFL_l8D]R.D/H4K,B->/A1B BT4HW;$37H)"60 + + *6N\4,<:J 0A2 DV-BU(9L6H/DD\%>ZB\3M 7OAWF[QhXp)6O3Lcj8C^Nk Sl-5I=P$FW0.=/>.9>J(FR,DR(coEP_3\m!.* )18- GI;#'&-P[K  #'1$ $++: #43C!)9, $4%5' )#1*:5D!/?'7':+@;P$K^5" ) ($0% ,5C'+$1,<1CVlE5 2M#/$=#<0K6MC[$:TFb$E_#Lf'Tq3Ff&Jj.Bf(Ko1Nu1T{2W}4=gKu'P{+Cm7`>e>d@d!;`"Uz=Ik09Z ;W$^sF=O+AM8AK@&,))!*6F-+3<.6:-knd7:1') 03+#('.$#+!(-6,*3)%   &*##,6"AKCE=@E  %-.3'',$*-uP^ddu{"17ScbUc`/<5JXMEPC#/CQ-tG=gp)`Jon(bJju-SlUl +m#V6"5py K"PmOlUq bv7 +)#%; !n h|} -J$V!#$/%lS>%W)6Fikqc6$$CQE~6*%::`XA;9=,=X]YZOKMQYdY^\kr{wseUd_iSCWabcede_WfqyhmZZFDEB;GHIHDJCAGG>=D@=F>@NADJHQMuBq*v=4E -j~CY5C;M5J@T (< 12D'7/Zl5nH3J4K?Y*C_+)G'E6Q"0LFa49S&$=7LE[,Le00H/"8 4 &</E5K8H"&:dLNj3Kh-oRAa$Yx;Li0(D$=3-f~GMd3:Q%$, %+:(%2 #;0G2N"> 7P!/F. +*( +(8 (% 1-% +%7"9Tn9sREd#?],I5SOl)Je$,E / *01*?1G2K/G0I-E9S0I'D So6Uq5Wt9Og2@W)@W,5L %=7QHe+Zw? 8=UC\!@Y F`)7Q?W=V?[Yu(f*PKCXY:XU\VNTSKHLNFF9;JBGEMKMJLGEH?GH@BEKEEABQMSLLJQJL;;-70,36)4,=:250$,&2(-32E31(0.23?9.330%+017=A:IF?D8HI>CFOQ]]]PBGLILLEA0<9>;?0;77003.&'(+CGAB?KE>961=,31+;979?==7.5:>A=C<@A)4 FV6To2L 3P?]!:T% :TB] @_ @]#83 0)3 Hf/'A+F"<) ! 7QG`Ie$;V/IB[0K :]x%UnzCVl$g~6b{1s?Pb%9G;HDL*3Q\|Du<_r&by)LeD]Sk ^t-Nh 2LHc2N*F:#@6RJg A[Id!e~:Id?Y3M/LLk&>\;V C\2K >WIdT`3\Rq ZvYtKkC)KDcA_ ==Pn#9S 6N0D >V7Q7#<5LL^47M$: -B*/* &*BD^7Qc|9 8+C=Y-G 1J 7OToB_Db&DDb^{Fft+:WWq\wVq^?Ya{0Gec 6]xPk4N;V ,IZy,'C?].I[zEd 8i9Ss0g4_|Rjo&SlA\YvUpOi +Sl C[ +Hd (E3(?X`w)@G\ +:TORpoc#"#*!)(&3I\&225Q -`w>Tl)Tj&-Ec},[r Fcj-ay#[Qk|+FZrSj?WCpZqG]3I &>a3C 2G9T $BSp(2`3KgKk ^ 6C2Ip .ip JcX ."-DG/&G2>@>24_a)o:=GJbg%6Jlb +B<45V#$;Ie+%F +%B02.0 (:(Qj1Sq1^}8B_Kf#\w8Lf-+@:O-'9"3BQ3$2 , +*72A,<.?9J!K]48K,>/ASf5Wn;9R7OC[[s1H^Qg p=3J8OkAJcLdi4Yt(E_+B /. 9G'IW7.;CO-@L!=J=JLZ.M]/GX!Xj*Nc"I`;Hm92+!= :76/)!"!+ #*,);(.A.!1  "!  $+'*'$ 1;#+-!2))*'3B/;L3(<%!5?S6("/(,%""%&58)$-9( )"'' + &!,))0);:J%, 0.?!/ +&3+c!@e%>g$Kt-Gq&V0Oy)Kw$It!V0Aj@f-P @c#2Q2PA_)?]&Jd2CV-ET4)3,1+psvpvsZbXZdTX`T).%cb^}}uwX[P47..5)(".&!5<2$ (+   $:@.,2 3;(*1BL25C$6F">N*S`;^kF0@M\3L[2dsJFT0LW3U`=kwPfvLesMOY5YbK?t{2C:0&?NFr|IVXeswCUR%1+%/('2&%,7+5@4!-!,8+". '3%!*!,;F5($/;F76A16@06B/145t}~hw{esy6DIN]W6B;0=4S_SZfW09&IV4Ym/}Be%TzEi OqWyFi 0Qm#~/mzKQj{ rt~EAr^z _zYv cZwE;H" +*3lbx|"(H<,81%u'EJ>|$3/:*2E'$/(DQRKcp?+;1"$&&47I|[lV\pfunVORRZ_JJQKFQVVWY_YMTY`TQcZdb_e_ZYamaslMJC?1:FJIH?U_"Zrt%Udjw..%9 HV_p7G[#Uj0+6J(9 +(9@V!E\#b{?-E+D8S%=X'2O1M_|KRnA(C4 $:**@2J)?7N#'=#:%96K"3G, '4L9T Kh/8V8VB]&/I+E3L1LHd*yX+w`, '! /"8L'(A:U&H^.dN43N Oc==M-,>$5,#1(# + $+C[+B[#c;Yz-Zz1kAlRq&Xw0d@Qk3"< >X(qSiEwN^z8CbTt-f?Gh!`|={Y;Tay@Xm?$': HZ&9K*? +&> :WRk1E^&C[&=U#3O.iOiI@\!'?%< 0 8 5 5O=[#-J7T2O;Y;U:T8O9UBZ&Kb.#9&>2J 9SD`Lh^w`z7=tgQIPJNJKQJB@;FEIHJ=J=DBAG59DECH;ABD?@GKGHOFJCDSKHDD;;A9EG@<=IB=A3@G(9?6B94'./,"*2,AHH9+,$)$#+0,17<.594E:DCGNOWRJEI>BMPC1/40.D:IDPB543,68-+%17X)B1G Je#$>._|*C[y\8deD{3}-Oj`yF9R;SscKeJcD^Y.e{ BZ$(Mje)n)?_Gi 4Sp(B ,&=B\rA\y.2:%?2K +X{,No$6SWt1/L0N Rr)?c+LtOuIVww6_Nl +Pj>]Hg<] 2R6Vu,"@877P.8N6O0 9*  5K0H0FJ`6 ("3*6Oj2L GaTj!H]/E^"Ld)BYup#Uq Lls#p%?`Gf +A^=YKfh&j#B[@Y +l?]}1BQk+D8R Vr'0A`Lf"B^%=Ec7Nkc(FRo\ Gg1L7M)@8SUo`s&Zr_u*7K(B6O #<dA\RkF_ CZXq ]zLnQnt$r)AK,5 +$Zj=$:lH.J%<,E (;3IOf zB'GtC*A#8`-#Hf}&ez5&-}::Q$Yq>P/GWs$-8j=V+7|!`es@:} +'+G-oB& ! &4+'ow +ii"D+[*CI,rx59syMƾBR@:?C8D]l"Wf4l?Iim!W_ C\hFY'{$un~'W`_gesCR$6L^ +@? u rnxVeZgYiTg"6K8K2E6KDV ->du8Wr;K*(7&4$2,- -(7J(1F -E/E45N]yD'C/B[*.C.,B%; >UgAEcYt-Yt-@[D^=S=K`oH.<2=&"  6>$%/:G"%4 P`5Ti6Qg.\q5Nc)Pb-bqA(6DV 3E;LFW#0C 5IQg.1FJ\0!/ 1A1A4G)?+>'6!-#0'6*7+8 ) &:!5' +"00= >I(2=7D0?!1L^3*;&7 +>/A/A2D2B;O La-1HG` Me"lM"jxP]lDrW~fOZ=$+$  ,  % 4?GN0JP439(3%1&1#+)107%/4#8=/=B7P[SDOLHYN7H>CTKo{&54^mlu*7/(5+$.$2=/#.!,6,7A8#.#%2% + '*#0!(+7&).:&)5!(4;G0BN68E+7E,EQP".(=HCDOG-803?53@/+9"/;#6C"DZ m=_)RwTu!=^ RrFie!X{ x#0`y\q`rv|'KexSiOktr h9'u$ +* 3(`u7w.O &B&'?4 w A RI5y XCAG@*SKibXWA8LRNONIWSjggjUPeVHXKLULYQG@IORMGSMVkVg[g^YZ`ba]]WSYZab^[dSR?@OLCNJNH9@B@9?>A@LVHHFGPE?6?Kj,9AWv?K_$1? "3CWXn4!9Sk(2Kc!He+<:=A7=;;MJKTMI>CGBE=??@?FNOPMD;98:5;3($'%$'-+084)1**(.!+79/33/./*7:@ACHAFI>=FC=AGF?.?B8;96*:;=:MsfwFC\ 6Om(Sq)A]B`!@] ;Z=Z*F8 /Kf4\{;>^;\5T 6Yu:Cb!Ts.Cd[RJe%Ys6mLKi)Mg g8^y0:T%DY 0E g~=Lb Se&0? +4?v?Rabr+?R %8KaRk#AZ,C?XE_Ke"?Z\y5_}8zTLh'0K:V#7*F +AY$2I=R=W@W9SB^ 5P1G),'@c}'@GWv_ 1Rq35Ok$>eF>Xv5^~ j-?Ii]wJcA]QXz!$D9X:V5)H Su!Gh 0N4SnI Sr1Hk$D0:@bHVtXxUp;W +5P1:YIkb1*H 1 )" +"56 1,D (?6 +'>&>!8 "# &&79N 8MgTn,"<*?%252N>egt(XrUlC^MhOl!<]zQk_zz)Qp 9T=WZok"JU'*5'"*@*C1J 6/ *@ 60J%;!80#5$6J`(?3 vWp"6K +7Mc|%f%,@YG_^*@Z OmA]p-B[;QrFYq8I~28Oakm XrmC]wDVO_  +}&*% =~jv +[bw{cf``78\a##N(*Vmd@8QT7BJX @MS_)z X>H[j3]jv"an Tc$H-HOOZiun}(r +:P/,BE|$Ua_l!7G?O AQ P;ONd)@U5HTg-;L9M%9:J`p;5F6H-@ ,=$4%3)8,/.%!"!-&5"2&5 0.?"'="/&11;8B"#0?N)3CK\33C8I8IL_0:L% -;H[+7LRj+&@A]McOc"@PXg)Rg!o<^v,1I.C FX':J"(7?M,6C!9H"FU)\kALZ/IW-6F8Hdw9CY5F^ F_#.FRk/!*. -(   "  !   +(%,7'")$"(  !$-# &+;) 06E1&4 /=)$4!   +" ' ! !!!$0!'.*$1 # !# $/6C*0?# $3>M+CP/( />$#1$ $*",IU81=:GVf:=TMh$Nh%Um0=R%..!52/D%. (?8Q"0I%>3M=W(/K@\*A`*1RGg.?^"9XBa%Mk.zdYgIL[:UbDixZDR6U_B[eC>I%T`=frP]kItYeBimlgg{MgxLjp|OyvVkxK_mDhwMYh>sW@O'/>AN*IW0fqKmvXJPBMSKdneto+$$+%,2:#3;" 4;'lu&x +-)'1ny8YC;30.>LPLXRG@II9:H-Z5apK^;M.=.?^Ux.iFXzY/LUr4Gb$/K Tp0:S(@0GWn4`y;sK;TVp3@Z!&>&> 74 +Tk=C\*?Z%a~EUo85*>-?#: 9R 5P1&A: 0MJf0%? 6IFKFGBEDDLMOXTUOHE<<9=E=<<@F?3.2),2.4)!,0*+*,322+'##$17?9=30-'$286;A?@>:.6877C8DQJ9854=@>8><&(*,)*/))8-.-(,,3,+).0!-;#-9:53;=:-+696>$8Qf,Rf)Mb"hy<6E[k/e|1Zo%\q)?T4I C\?YRn%m>Tm(;W:T-J =#A2F;S@Z 8,C}Ei7eRlOhm#)teu*>_A\'BUs Dz'Ut\u+b/LnEjd>LjKf ~4v 5DN>`A`Ig B`w6Z|cm*b%Jhg8\z$'CB^If!C`'@^3%kFEe7'E(F^y;5Fd&DhBb%EEdKj# >,K_|%Jg:T +Ke&!:FfRtZ$C1Ff.jXs.641,G)?$="< 60*@!EY0- &'|M+&@#:!7/E#=-D +.E/I2P <SoYu]{7R`y/Rl!Or!ELX=M$$8 2,? 0%= 5"9G_(E_zEsO+sG.B9SyZ +$-3N1Ix>u@";0K +IeTn%49TKg +Ge:O\/$rj\we~\tj!Y3ORnIf%UpWs_,r]j5/fvl|v bikqMZak)%F:!fo/t|_kV`KROTgfD5B756'43B[hx,9Q[5?/n#,Jz,z +*afw~8doN-3NP^d PZ :IN]3DO_@W +(;OdBW +h~33D>SNf +Ql$3%2%1 $0#(' ..@ .B^nE.?6G *>0"9.F-G2L#: )?6I &?-I.K8TRo-Ie(8P9L 4F:J!!'&4>%!* %24@IU-HZ,+= +J]*DZ 1E=SzHS*>;NhxG[l2AS#=O!4F0%4)8+% &)' !".)7 &7(+'5*:"4 # +& )!-'40?-<):?O#_qDO`4J[0$GS*6EatMIX@O5IfU]p8K\/,.>"2>M)5E+<OqEIa!AZ,C*@"8 6.!9$;0, ) (&  ! % (%)0#!!"#($+   (7&+  ,+.+6;6! *,'*&#))5&#.8# .!0."1/=*$1#0(#("%.9A'7B6F:LAZKf\"@^$28.K2OH$+LV9NYC5A):F1)7#8D15A/IUGYc\T_[%-;#(62A!6E%BQ0?N,?L-AP/?M(L[2Qa:1@!&&)0"*1"/7%FP;.:"=L3IV?5@/9C65=2+2(+4+bmex|>J@?K9AP3#5 fzEgAFeTs(]}2Gf?^c1d"Kk;we +vn=No|)|-7@sRRjolFaD],o EO\G_mEQ@ 2D'4s'V@qmeWw  ' %(924U^*'LSBGWXUMDLC>621D::115:5.39HOFL\B;ID;MYNRFFGXYbahdbdf]aZJKNKNLRKPQQQH[OVYSUSPXZHKIDGI56;9IM @B:@CI4=B>N/Q=P03C1CL`,6J:QPi05L=UBY9R=Y$$@ Vs;Mi7:U&'B,IFb.5P:U#2H0F4$;P(Pe>"70C!. !2* 0D!-D/I,E Mk*Jg*:S9P#5(> >V 8Q":.H 4L,F Oi.Eb$b>Pm,lJa{B,C+C 9RA[B\">Y(AH`.)@2 --D@V'Kb/?\$Lh.Mc+Sf/3GJ`0Kd0)D.9S'48 Vt<0J1.C2I3 )@-D1H./G5O ,.568EDCHA@1+79-95642.3&',+14+35$+5641-,6122)7=',626A=7942<98345:*E.F6O>X +Ws$D`?\Fe!Ih&$D > 8)D1L/K4O!%?5R= @c"Eg&.NCa'4O3L+EIc48223#7-  "9J&Pc33E5HFW$eyAxS[p4.B ;LK]!S`%CTPdKaE[Ka!Og)>U;U4PG_ Ib .G4!>,I,I4R*G*E !; *H*F ?Y9R+C6P j55N.E\vf,"d-G^!8y4CsNk.L ;IgLj Mj"Ig ?[5 ?$H@] j#Yzn'9^@b ;\ +/K[x,^t0(? +%#;$>+H.I#@\v4Ib*AZ(3"6St*W{#8Y FfEeDc %?/E_144- <0Q.O10Q ++OGiߑ 8.*'B .-%?#=%= 5*.+%(a}5Kgh0Pk =V 3L#?5 &2J(@";4N (A_x++H*I<\Nr&D)CBaNk_}&Jh 9Fk,/9L 5#:(B +E_5O:R -B+D6*J +f6mUl &Jd,;&Md3P *H!>2/H +j;7O "<;XF`0N $@EB\:X-GZt,E\h{+/C[n#8H +&5'-%8 -B 473To+3|~5-C'<&@49PMa&7L%+F +(E.H)D5P/I,G` j'pIy^v~C#>T6MEaWsk7Kg#8U6Ws >VQq2L: FS[e*tk{rjsu|bhS]mxbm !BbhFJ0r6BDMu!XdV&^e\ckrheDI@=Agu-L\J6]FQR\XgAPQdq +w#}mzm{U- OQgk~do;P3[dLX'48J HX/Bo-XkUh:L5HDXXjH]Zo#/$#+#+*2&-7%-;&!1(- +#37E%5F 2 - 9 -H$?*C0F / +/H5Q5PVq65S6T8S1E1DFX/1@""(1)4#2 2?2>1?AP&>Q"0C5I7KMd$b}6b}83.DV.. "3 '74B. ++ +:)6+8! %#$ $'+9$)6"$0+6"1(7   )$!,*&3,:/=+:&1BBP':FWd8;HQb+2H Kf!i>C[:M 1BVh)+,=,=.1C9J HY/+:ET20@9H#Ug;L]3O^46G(7CUm@Tk+65+@.C%;*BC\55N'233%7$     #   &   !#$-"-""/,7)%"%)  !!!"",#! #( !#)3()87G(7F'!.'!+(#)"-$/&-.63@2E:P\8VA^=X>XOj,Wv3Vu0Kl#Ij":Z?\@] :V=X8R+C)?@O*.=)9;L!Xh>*7$+$$!- --%3BO76D"mw8[T + P[:GQ=1<,2>+'3!>L=]hhWhf2C<3@5(8+(7(.<-KWI/;-(1!LVE=J:%2 (5"/<+*6',7()#.*&2!!.'4#,9%!-$2(7 /=$-<9H&7H#K-IV;>I5;E76>54<47A9;F>DOH?K>:H2@P-8LmHNj#Po"Rs#LmEf/P$APo 3+gJf~i[nl|.idmhs 5!($wsJHf 2Lx#pT+#pM`-@Vj' ?|6} J""/{)5#J,@?64.NXVNNLOE<@L_R`VQOPCI)&06AB8/6*8EC0=8DHTV\LIIWXSKECGRWWXb[MROSRJNHG@CDHGTRPMISJSYYQPTV]VNKJRNDFIE)S7a2~1 RT%:PRi*2H5H Yj5*=L_(La+-E#90)= ':9NF^(Mh27U8U-J9V%:WfKeH5P/I+D;S)"9)=(<#) #21D"&=1L9V"4RpSEb*2NF`.5PJd.*E1MC[*%=BZ4& &#1!/( + !//K4L%&6F$# 6H%4H%)<-!1A&?P1/.$./G['7NAY X]v1a}-Pke"cv,G\0-QH@JKQQOSJOP[TUYd]V[ZVXSX_UOJGHXMULNKIGODI@IBHIHJI73,5980)/3266)$)%+!!!-) !(1/55-*1-.50:;7667795:>98@?B@6'*+,03**""$/:;()++&!'&-%02-*144:'8498@922/18/DE 4FZFc@_Ca?]Kh(B_0P If*5. '44+88 +)B_&Fj',M Ag%&G4?^+$C +;W#0@W+,@AP-8F� + +N\?%2 8Jj}AOb$cu6\n2Tg,K\"Oa$La#Ti+E[F\"1J-D 1M.H Le'.G1J Jd'Sn/3O/KCa&>WGc(Eb*.L 8TD_B^ 9%L(b~:SOf[+ '*z,-K_Y{x-,$&+;\|:ZGh(G>\Np^.C8N/c}4F9J$#/  &"/.=#4 +;.>2CBS(0B=O$.A6Lg>$= +%$7(7) +) & %1$1'5#1)(3#$# $.$  #/ #".+ +$' '%) ,.;"0+9&6@P'"*fuJ6EEW Vi0AY4( *8$ +"&!/        $% "  $($,$*! "#&&& !!# + +(*  8?2 (.& #%!)%(3A&&3",%*2"&0 +$."-(2 )1.53=GX+AS$7N?Z"\v<1LC[!AX6MF`Po)Qq)Uu.Ll%Ki%Je#C^Og+Ia,EY+1C:K"=O%EY,I[,:Q E\+\t?Mf0Ka1La1?R$+=5JSg=izUhx^.< ?K0t}i(h]l?5DdtF9H8HDS0W_DT[DPU@y}rzipwg}vi|pny`dnUtgt}knzXoU_9[eBwaUd@l}X{hɥڳwaw~wxoūdtO4I';BQ+'  * /8+3-6%2, &4)90>1A@Q%CT(,=,7$0+7&$(5'#2"/.;3B7H.IDUnAO=H&PZ@IS6@N1]jUP[SS`c]kou`rlDPF("/$2)7-=4C!6F">N'8I#=K);I)6C#:H$=LK^-M`3/?!0*87E%5E!.>GU.CQ.GT4AL66B20:.3=39D9>J?ZgZRaMCW3x_5NjAg;Vu&4WUx#Ab>\c,~YZtOpv)r'CXv=G(QUi Se -~J>(%]M3F:)J244$V 4^XPSOM86QeapbGBC?>B@=85>J?ONCAC@?HHB=@?<7A?IOQ^YOQXX]YKIBD=FIMMNFGFQJJKCICAGFJHGNNEFOVTGHDJNOONRQTPKWOJOZG`+o)]scrNZM8EO,8i)6J +&7+>Vj0?UZq<66 +/1GKa*Jf,?[$Nl7Jf3!7F*( $/B3- + #6/C Ld"Ib D[;O3F=P/FHZ)$4 GU*+>:N 7N 8R 5N":'= +Le.b}C`}A*H ')B[-Kd5:QQi5*C(>3(>G\6?W0@Y28P'1F#: 9SId)a~B>Y! 8;R$G^13 +#95 $? ?\#Qk3#9-- (>DY-,D,G>Y!C^>[Sp)@^9V|9J{,J`q$=NIJEHCF9>JKLHW_QQICHRSTKIIJ=87?EDBOIKHD<;=;9BB=AEC;.-420*%))."%$'''*((% #! +'-(1077.A49759:3588>17892ECE2+/!%)/&! "#" " (+'*4&34(.01.#+;5,*)&7I>+5M'@,KsCSq%+FIc'9W6Po2?],5 'A$>)E830Ca&2RFk%Nq.,K7U&)G!= 3POi;CZ0;P%&7%3* +%4+; "0% +(rz]Yg5Ve/ap72A@ON`(CTRg*:L:M,A.C ;U1IAZNg&6LgWr#Mjo> 97BZJd%3QNn)5T(E6Qs0g -mb~ Le.Ga} +ke>q*@` <\Do%BA_ Db +=^ +=^ Opv6m3d+4X(GJg8S;Y+HRJh3P6,Zt65.1 #C_Hi +$o5E`@Z3*@ ': ):( , + &9%#/C/ +%< (1Ss(Jj;V$;'; 0 >Q#+? 9R=Z_&t0Vvu%  #9,I 3N :403KC_Xw0.L,K(E^Jh Ljh-)EZy%>Z3K3KNe6! *( !<j05T0K-I8Zv"q/GIb ]uF_\vG^4-E7O'B[v44N 8N$< 0$= *J979R0D"2BFZ'!82P^y.^|65Q$: *A "8(?B\G/C#a{0Fo"0K/$4 +4F ):"5*=*>/:PMfF^ !8-Og4Gg(y0@E[ N*; 1/CU)(> .8Qs?az%XN\ ?MXeOcURoLdhtWwPq Z{NoEcPpfC\k cz T3FVrOg^q~$M-?Q\%et eq +DMX_kp/14 &* + 09^i}QYcmw*@QZMS-6  P]6u~v~2Va.(4=]oD\k"H[{ xv-Uf Qa _qtbrXkz3N^|6atRfBUj{MQVh_ix +! + "! + $CP8#',& !0!3 +.D2J!< ,+H+E@Z*Of9(>(:.DKb4awI:P"&: +&6 1?,5 %9F&%5&57H y_""*;#6+>*A lC9S)-*( " +'%".#0#! +& "#&  +  &!)  #% " %"-1>#1?"(6(6&32>#/ 6CJY/5E;M6H]o8Ma'+B +6KB[tA(;h{EoS#07F%'C[*(E!9# . %" *<',   " + #(-5%#)!#&! "' ! %#$+0)  %1:%5B(,'$" *& ,!.!,6@)199C >N&CT*?Q%&<>V&3G8JSe.M$>N 0B?P'/@"0@&:F1)6&>J;JWD&4!1t9f.%. JE.=?K!&2 /;WdC2?-jvtKY[|cun*7)0=+$/%.'1/8(0<,8D60>,,;%6F,7G,*7""/,-/@$-=%,:#'5.<%"1$2-;9I%3CDU+5E;K"8F!8D%0;>L'CT"FY"Zq=,@%5/>;K$9JBR&BQ$MZ/?J*=J24A.-:,1<1P\N*9(CU@9L+*@}Z;UNk&d8<]`-QsPq=\OmXv@a0O/L 8AZ Nh]qO\ M\!bkelDOA?CtmjRg_rL*8'kx$dKH#w$.2 CA=26?/NHKIR\WMIOHELC@GAJGD:9::6;E?D@812@KQ@:EDAFHNSOSadadZg[TMC@59OJT]RXLOPLJDGB=ALFB=DCGKTMGS7@EDIPRMUUSRThP\p=*4Zp +h"A1CXfV\BA?>@C>?A?EFLPCB&GICJE::<:&);687549659754-24<91*;4'%.! $"$*+)#*"'' #"*-)#"(-*4:@;?;4711.6617:1-1.1/2,+'!$%'''*"&*%*3+#!(.+2**%)/4'-#%6BWeF[Pjp>b},Snv>PjA[8U-M +6UQo9Pm56R3M,E3O#!3    +$ ]hFFS*;GGT%P],Ta.LZ+*<3E6J4E>Q+?)>3K+C 9Q2K>T5N.JFa.H ":1H-F/I?]:[Zy1Pp/>Xg%6s ]|)+x-Mdffgx&Ei/Q{2Jh VHio$s,Z~y;=_.P4V4W !@WuTqi)<[0NKh=W h:@Y(@)8O*DPl2N4N C_-F5I3(> +'/$   I\3( ')R:W?X$1-?" -E @] Kn>Kz#-9)(@7S)E *UlAFZ0!%= +B^=`&E6T Plu2f$w5?Z 'r7-F #7 w$  %6 +3I .HEd +_{";W^v%>`;[=Z=Ws q(bo#>#9!9-G>X-H +IfYq1 9!:5 :3Mj [vNg*?r&6j}!7I"Pi/Ey:)D Ol/*E]!BXLbAU?T'9!Rg)* ]n2;N /*/@(<)Me'@v;z=s2RiIe2,9]t"/0-'B!bk* npls `tBY Zqf{"byhyUbCK>JDVWjbm%Na?SK`r&SN^Wg=ODW +XkRbPb Se3l}"  " #..=!)9$ )CU*^qB(Ga,?\!fH > +5.K0' . 2AS2- (76C"& +*2  ) 7D 4C2B(9 .,!1 - +'->ATmE#< % +#'  +  +& + +  +"$"* #  + %-#&  +"$)318/:#/?M*-;*83A/?.>5G(: 9H5D#8D(P]:1A0@s`$; 6)<Qb>CU'Xl5Yl3Ma%sC[".G+A"9-D?W*)C: 0L2N,I;X&Gb4%?3 0#%# -.     " #(&   #&")/'&'+0"!*2='! $0% )#!"+#-*4/469!AI,/9-:-8"0* +.=0?[l=O^-9M9K:MH\!Ma%Xl1G] lFL^+GW+8F!6C"7B ]hG^gFHS0KV0AP$Qa5DU)CU(@Q)T'3I;S!5ND\%Nf3%8 /# " ".,1, 4TnE">%@":>Q+$$ 0-) (H_2"<8)A 2JTp5G`&AX0F.B25I-@Rj66LF\.^tE)ASl<3K)C&%:5GJ`,1J/MRp4:X 46R!Nh8VpA;R#1I+?DZ/!73-76 (C3M"6 5KC[(Lf.C^&2K/));"9$<4O3OEb%*A#7,?6 '=,FGb,He$Zx3b;Fc Uu/Kkb%8jOJ>6?>J=CCB92/,7A!> 9Q"Pg:( &Ec1:Y%Aa Ef'4SBa+/JFb17S#'C!:AY4nPYt6-G )A1K 8N(2I_?L]G !-"+6> GO,9@COO^(GY"Yn4Mc&8M1EEY7JYl48M5J+B5O +E^P5GNMf +FX@U +3I >V 3K=X9U3N3K{#ld\.fy )/B[E]k,Df Ko 7\Os;^Uwb$:Xyw:EeA4Q/N<^,R=b k-_`g Cfe+^|/+X'CxV.NBb Pp:XAX$22K1&Tj9+A  %6, +.%:!8(21R";6T7P 1-=8H) $ 1+<15O k)Tv ('7-= +Re+sN+F6*%0CY/0'= +3K'?(B#<QkVs" <\y&8d/h\t7 #&-$:j;,ENiIdPnPk%8RMlB^ *E.FhZx>:U +Le:S5L3J $; 30&5l7PY8R}<]tNfD0^1F[6R`{1AU =SB^Lb;Q DY L`'DU+! *6-=:K ,-'ev0\AjSn3c~-f}K`v-/7HUlT7H$9!4 \r%t4}&pyfn@J &7BMuAu e}3Le-_x d}=U1Gs)bzDdz#;Gd84k EyBD*ls#{ lt :JvQILXbpw)LY]emip_hI} Z(4(N\?>uep/6Aq{,z}V\:GBRg|+N'&3 2BN`,I]#&;qCMh$1'( AN4HS2Tc<%5dxW- +33.CH\+[o6]r6EYxURj7+BAX04J#2+D4N!$>*D:T'6Q$C_/3 /F#//"( % 08J0);%!#   "#$.1"#!"+3&"* ", 1>-(2#")2&  =@:14'6;-+3 %.4?&DN75=)"$&*&)-202 1324&-17(3<"3;!%1".7C GT)DQ%BO#Q^/Ud3K])J](L&1?6F5E6E3C4E:H ?M 5FL@9?27C;J9JWi6Yj;AR)+ 7B(AJ6JWKDRG/C/'9 ,:`2B +GL{ =:,2E)97D26A:1;03@,0>%'2(0*2-8"*6#:L&AS*;M"9I=L&BO,,69C%DQ*AS Pb36E/=6B3C:J;HJY&DT&@P):I$7I%UdK(/=*/:*+Q`:Md3Rl3Tr6FaH`Jc!_|-v6g)6S=Z@Z$=:4P"?Yw +Id_tz *FRg[KtMS4.=J30>LXV^N_bVTTD6>:5:*/8ECIISXDHLNPH=E:5:GHJFDEFK:HJGKNLMHPQLRSJRJ+AOIBAAEBLKLWMLSPMLNKJFD@CECHIGIGUNHB=3.>7>CGKN"@V#@\5T`@5TRn548 1)@&=BX.*?-'<(<0H$99 2M"";%<#; +:SpNNf24+&93 $; 6O,F \u:[u9C\$5M+C"9 "<@[$9VOl*&CC^"B]Zw,b~)n,q%e 8?!%=\K!'030%/1.2&=HFA:A>AFD>8@B48/-2-17,8822:<9;@/) &*&(#%-0=9///47 /)#-+!"%0533(/$$#1ABDGJC@E:=F?=:;/)5=;KE0-+165ENC12)$!%$)+&+,5..3%,(0,/&1*!%"! *-,# |v+Ke\u$.M/LE`az7Tr-7U5S8Z&+H6$<>T&EY,'@,J7Y"Ji&Cc#Fe*2O@[*=Y)2N :X)91 +%>8POj'Qk/AY&&?,BC[0:N*$ +&,49="AE#;C1@ .@6M Vl*J_AWI`7K -B6G ;MOd Rj Ld^w(7QnP[Wm SgZPh2H2K+F/Ku?\g }'`}6P>_ f]}MZtGcc$!C(K_!L0P>[ 6X1SBc %AVtPoPm Pp?btBMu<` Cgx,BPs=^4S ,G.I2Cb/M;].Rn=9S#2!71/G9P1 ++" " +AW. &79 "=5V ?Yv2Nh&  & &+# 0&?OkcL\1'+,%8$5i~EBY-B$#Of4+1?R,A736O Db.KOk4H1O$= - + H\2E^"*CDY/F8KI^*DY"Vo5pINi h:-$'69'6:&*.2:$$,4=%FN2;A :C!8C/=;KAU&5L8Q0E4F);*</B(94D3E2G7K 5H7H/A(: ->1@8FH-'2%0&4.= 0"2-"/'4(5 ,7C&9E%-80<2>>N&8J>OCU!BT$*.;4='BL?yZl[,@ *9 +44, %SUh "3@#6B+:G0=I1)95@(,43<&%2'4":E6DN@;E;%2(FSH - %1 .9%(4+7"*6&2!-".'3)41;?N)DU10B;K#?N&HV/EP,.95B?L"HW%JX*%4(5/<(7 +-<ERHVHV$@N&FR.IU2?J.+6#ISCHQC!,4C_vFMf0@YD^Ic ;SF^SnC;X7R@\[>Z2L)C &;@R8I?M\F2!KIT}W>d6x"RgSdv#;[cr +[j)v  C+%C#PF1+78>=2:EI?B5DA=A@CCDCHHF9>85-94?L998BETLQWEBI;S[ONF9FJLPHIOVKG?BCJFCCE?G@<@OECNLF=>:9=8CMFPC1X&x~"\ng|*Qgq5r9^q*bz;n{rfz2=QK_!-C .E,CCY&8L1HNh.@Z#$;6M4ND_*Wv;2M>Y$8eNBc,?_(1N&A*AMc4;R!:Ih-Gh,Hj0Gh/9["&F-KDa.!9/ / +-D"9*!5 +(</ *4F2E4I/AHW4)!) + % -- #:$<!6-& +* ) * -. #:9 Nl7Ig0A^(Ge.:X!-J4K>T$2DZ+I`1;Q#4I ':':32H-D3H=R(AV,;L%N_5Lb.5Q_~:Oo,Oq1Db)3>X++A"73 3I#/ "/, -B2L%'B(A+%<+C57P:S".E./ #;AW,F[.0F+B Rj0b{B8S=V5&@C]+6QQn2#>8P:S@Xg-x;j%3r#*+@0$'#&.10.4C@@FBDAFPG<>:2-61 +7-*B@F3>;0$../ #&15''241,7804665?0.3*"#()*-38A=.+8016&$.,7B:>><836A:<;E76=8D685-175;057;1$)&%"! !'142,)(&.0*98-3'"!++0 $(AI(tE^8T0L0N B_Kh&Fb 4Q3Q)G(H8+FC]-+DB]!hI:X5V3Q>^! 3"" * +- (#="?">3Q1L.JKe/X!0%2x7m8wC,GwDh5Sm$*D>X2Q>[6R2MQk;3 +Qj44O 9Q-Yn.*@%:(@'@*C2N2P(@!:7Q)GPnq330J +E6L(> .%)F_)@[b}1A^ Wt# 9E\7Qa} W0D )@[Ge z>Pk#;+C f69NAYp:/D^qu,bv&-9:? DV)%! ;LUEai +gwY>Ut8QkOg Ax0Kexg;Rq"+=N7MKa0E@UzP+AAV ~0:SOcahs"an!OcVo WpOf:qw]wIdDn Nk&@0n&8Y=sL_ 2D G~fk HMY`BK`g )nv?gs*XaMRy"$sbmQV  FGJQGRJkwvZgttXfO]aqO^kx$`oX`xflcl^f`l"X+;$75Gcw+v8cw';P&:=P8N 4I=ON_Wg#;L T2G;O +?SSkXk 3-!#*&%/*8, 1 / +FX+/D0HGb(6N)A 1LGd(Hd&7S^z:6P,D :QSf5}c4E$'% (/7!HW3P`74F9J)9+ .4E/C;P:Q9R?Z(EwG^}-5R  &!$ #$! !#(" $%) " ,*66D 0?#0-8)/?I'/ /(<Wm=kE[)8P*Gb\w> + '2ER4AQ* $'?$* 0K%AZ.BZ&e}CVm6Pf6/DQhH[rU6L3.D'#;&@72L 9-";,F 4N'1K#*D6 "<-G;U)!:1 +&, '!#! !# #-+:""/ #0"/**$.'") %%* '+##% " "!#" ('&''%! ,+)('",,(%'02%35(%(>C1;@.8=*EG959(13!79*HK;14$ #)+'(03"#&#)$)")&-%-" 4:!093;3>1?2A+=0)2%6%5*/(;1D7J5I)DX.YnFi|TuZtXJ\/`oDYj=zZexEonXBU+ZkA{ikj~OrZġp3?'G<7@8>E>?GXhgJ_G,@6KO '>):EUFU=K&5B%?M32?!S[A5=%?J55@0'3&+7-:D=6A<4?U3N3NTo%,F?X$:%n5j^5YClpY7q_qm|t ;FeoMHy&Z6LiG?XZf`VaScYQRUM@A@+').34A58?GE@<=IDEHBI;OC@@MIO\@LFBAL<;;?<5E?KUV=^eQNcL`I^TMaK]5D0Ay>q/~7p5Pe!]t:0H,D?\(fLMl1Vu=Nj6+IIi4Eg0Ij24R0I0F/A)>Hb0Jh.Tt9Ii.Qs9>^)9W$5Q!3N"(@;U*8P$8N$ 4 +/#40Xk=Tj9,!'(' *) #2':/ &>'=-,).:5C#%3,=0HF^0XuC6U > Hf1Hf30O/+D:Q$$9C[-Qh;2I:N%- 0 / 4'=&</)>*@1B$5#8 +\x>^~;3TTu7,.Jo^1 73 7N)4K(' !)- 9*F)B"8"64K#; +'>#;#9$:5(=*<,<*<.A Nf, +#< +)A ;Q:RNhd)d$g{(%" +1-,)'"+031*#)8CX=<9?FTSSZEIN?1ELQ>AEF9AC2;06041580)(@3+2(.*(,+2''48==;DF3.*'"(-6:23755<77*.,)"!("03.%&),*'-+?0.'()0-%1")083q iLe.JDb^|:Nk+Fc"B^3Q">6U!1 )E71L/I;Y/M<[*L63Q9U!'B"%B2NGa4@[/.IE_/8O$: 5 $<3BV'1CU---&4%1 R^1(LY!du3;P\r&Vk DX Th!K`Oc Zm*Sg$J]G\4H2G"7AX?Y3P">0J PiWm(jj,!6Sj/7O#=5D[YuEc2Q]}H9UVoNlEeJj Jj ^"Scl6n4LiMj@`i*a Nn PnFdSvLqMsZ} Qr<_ FgAa q:Nj >Z-L .L<] l;8X;/H9P/FX26J!Eb0<4R0N7!3D!+ '?Oh1>\ B_%$= ";/(9R6N +! =S )A (? +-Dn#A_|(*F# " *6 \h>4C01C\*:R"7!8]yLh|=@\ 67$>?Z +-H/NwM!>0N 9B_*A[&58Q%9 +$ +&!Hb"<65PSm'Yq/Id"#=;Y B_ \>Z7JfPl#3J%:$;=U&@ $Gb+0N 7R &Cc}0YwVqAXWp&1M>Y4PSm]w5.;R/ +#DYMe=RH^ G^fxo&CR#+,1 +EUbs5 2`v5x?0I;XeQo<|ATH`@W 73K;TYtc`g~%Nead},!85J La53Jw8Icn%hfx UijEt'[vTip>v Xs^z $?8T?]Ll +s}pGa}#ZB'3*2`j[bV^lt?R+| yN!akIPIOGOMWEPevr~5.J:)QNOTNblVbrgs {0KZgw^m1?`r!M]T`cmgr"AMGR'7Vd(Q`#4H RE[?S:N CWPio+Qe at  "!(&0'5*;(;-?Ug99M9U3N3J 8 1ID]-KZ5=K$Zk@;L 0A!3 +#69M,C +C(A&@Mh'3R +Lle>hK" ""!#$%!!% #$!$%   $-#0"AP+$1.628BM&?P"/D6 1+@)(>a{@Wu2Zu?"1 *#1WeHFW2)?+7, 6*D:SLd1,"7 :O);P6.C59M>4F4-@()A !;%?&?#<0I"6O)1K#9U,B]1/K6P!:U#+E0I"9 +>$39F08A4!' #%AF6!'1;'-<$&-:()'$0+!,#*#*!'$' "'*# #+.&')"01,**%&%!53/0/-*('"!,+)*)'10/--(01+@A;<>379+57*24&57)25&+."#"$#) $+&-)/(0,4'2+7*3(2&/4>4@7B-:R`5O\1HX+FU+k{TEW/*9\mEGW,]m={[:LDU#dsFuv^u^}j}ayjbg^wX|btclmzDT7RaHcqXϺ̺\k\rs +  "#!)%. $+#"-#/+8#3?+3?,6C+4C*3A$2@!AP,.<.<CP0>K0-9!$1-=0A"4D&3C#.$18">G6=I=0=64>;&/14EE(()!+8)!/'5+;,9 %2-;8E)/;:C)CQ3'82A ?K7D?MLW31>".<#*7.>2ELd.Ph1Of*^x72M Of \pAQ`BZ@[a~(`{)n=%?4I 8L K]!G1hy,/#2LO_t@!-1v-D:YB3Zt__n^gP\SZa_TNQVH96<12/.:F>FIG7=OTOCJKF?C8@44910>B::4=6?21::KJ9=<9=;MNT?5:57GNDB:LGJMOGDQB:IGC=MD?>LTPWGEGB?:0:225A?CE=[2J=#@Zm>R7NJ_$uKdv97I*::J8L5J 5LOfZs/Lg)Kd->U#0F6LAR%/F>Y(7 +*C0 ++@%;"8%;/  014,G'>3 / +:P)Pf:-B#932 4. +!0-=7E*< +B6Q1N1I)A+C : *E)BBZ%/H.G=X=X ]z$Gp'V514?1,*-$!58;45/LIA:7FIPNSWR_VVPHNRILDHA<>352863-.716::.+(' /(08503-96DQ/50*($ &)1468865=:G=.+,9:6?:>856,.)*49-*.,82-2,+*/,,5=;8881*.,-)+;5/9.-+6,$.//50+0;=7670$(0)+/:5:S]z^z.Wt49Gd%7T6T>\![yD-I1L&)C3M'5 ,,IDa-A`*Gg2@],4S": +Gc04P; (2O,G-F )>0D!0+5Mau=)+'7 0Wf7HY1C\o,BV g{0UiFYWm%1E2I:K 3E );5J 7KBU5H7H #Ic"*J.I6P AY=UIc):R .F3N Sm-*@F`$L3Q7Yk!HiPm +t5j%{2Xv;_EgJiUss0Pm6U`~&@St;[8Y\{ UuDg Cf 8ZDe#@4UWw'-J.M*HWv&:&CCb&E<#=(A *A +/ "';/MFe;[)I(B)& +9M,'?2J`10F*:/;R{]6$7 6HNf-#< *mJp@]wM5R6M+B. +6H + 2G2I-$=,\w+Mj +*,IbC^Kg ~7Eb Pi$E`Lh&/&C90M1M**A -%3\_{'&@/Oh!1K1K0KUq)Xr7*-FWs'Kg.8N6N&>;T?W$B +OmVs>[ 9U4O>W Pj +N3S +3R %@7,3E\+*;,@ )+@McJ;EY ds6#,T\&(3CFW AS +H]MdC_l,GjehUo 9PF\$;Md3i7e}61?P!/E[ +E`ayEcXTo&7>3\]zmv|#.rJUP]+n}S`TbQ_:H:G 4B4B-<4D Ra***:Ka ,B,A:P3+B 5)A+C )? +#7)= 0D2F7L6K-C 6KG]&$=>Q"n( &(," ?G0:E'0>6C!GV.'6 '8 1}W:R'? xU2%?Kh :[ [z.2'# ## 01/  $"#,,'%& # $! -;$";H$AM+;C"8@GO(8E';$=:S!723I"Ng7tU9Q"3!+ +*:lW'= ". 27/J:U!+F&@ =V(Jb8LbG7K;Y&7RE]*7O 6J#2C#-:!2>'2=+2=(0<))3")1 !!)") &!&"$,/ "*3 $0 /<*#$"*#-"+%,!$)7;2#&"#)+%#'$' %'",.(34/'(#))$ !%&!#"!&(%' $&),*/%,"(" %& (*4(#-", $2*9% +&-:0<Ta75E3CWg7:JN[-{[yYjLZ3Zf?֬[k|Eʓp~SAN(pqj|RlPnΟjncpDYc;Ȧ}j}SaI[jQ¡( hw^ZeTIRA19+~tPYG!'!!#"!$ ""%!#!-%2!%0&(#.&2#0)*6!)50?#=L,>M)4C '50=2?#)6,;0@!(;+<0?,:$2-:0>1?5B5B6D/?2B3C4D6FM^,Vg3ET%grK{Ha!j_v +%#92S"08D!2<5@$9C&7>'.9*EPK5BCEPP #>MOR`^%$2"#1&5(7'7AR,1A6F#0<3=8BAK&:B38JN"VY3Yb''Ohkv~+Aox%cn\mY@M +8C -(6JW-;G):H,$/bpErMJ^#Od&G[Vj"/HM` dubtB~h8Lf _xC^MA&i~-!9B(y +"VeqJ,C87@=\^xifhkedWV^FNHKGMUPHB@=99454A@5MG?AHLVYQ@EB@3'+852/39;?<=476#+-07"(0:.#*25#7>>3DCHEOIQKPGLJF=?HHFMDIIYPFHD=LH>GA>>8>G<77%9,m~evEWWl05L1G*< +N^+@R9I2B5F;OG[#@YT'.D+?/A(?Eb2>[*'C'DIf0Mj0Ll/$B .I=\*Ef4<[,)E(A/I(<0 +*5M#?Y.#<&@ ;076113G&+@0,F< (E99T'?X-%>#9=T(Of:.F%;42((8-)?+D5N%$>3/C$!3-,,;$3 DT*CT)K`46M ";3M!-F 8,E1J!$?)B5K"/E)(A!71 ,'8->@R)7M%< +F^-&=. "3(:"77 7#A /Lt]8 - +*>.*.. #& +(; 51H!,@#4 1/ +/B+1 +"8+C- #4(8U&!9 )?'=*A#; 6PG^+9QE6'(*.6'1+*0/0-1FF;-/78>83+)<2.35)5;>=A10980-4C:5)+)'')*$.*,45,3,80.(-780NB`#>\;Y[v@8S#6'@5P'5N+2J*+ - -H(D/+8U$8 !Ig.Ro7.K75 +#35I&*: )!awGWn%l:cwKXk/_t4g{8n>Nc!sBMcK`K`EY@TM`!DY>R8L-AEZ7K 8K/"0D] Sn"_|'EcGaEUqVsk[PjD\nHqAm;.J +'Be23S[} e0 =Yv)Yy'!@Bd~9;Y B` +[wB^Mi4Q-KLr(HgPo\|Xy.RGi9>]0O<14L8QMd-(< "1. $4_,Um:1 , \yB]Sm)C[A` r-v-To!'&?,": 9 64M#: +. *?yFs:He :XN=W!9.H5N I^911G+!9/J7S $;.*> +B\3K7PEd;\ Wx#*F=X -J%>BZJf c21Qu6Ys0'0GqS);#L`*P-BNbJ`[m&;I% i?KP!4.$=,HPot"BcUm!:QPi+D/I 4# 4QzG% +*<%8 7&:&<9V?]Gc#>5K\tKc2􍎥Ow@*8Nb>;Vm%PjEyq|/x5MmNjMmiYt5}/XtB~7Y^ /imgqT_CLAMQ_5%S^ +1@5@ +>G=KIYK] >O +0D6Io|%lz}'\h qz  !!/'5CP1* +GP-3@ASX'A +?W(BW1%7/ +0H>R#(98G$ +$ ycRg>2 *B2I2L.J3N(D 5Q5Q@Z1cx[i|aSgL)?&(2H%'?9R(/I)B-E5M ,C/I0J>V(Hb-9P0FDV/1?0=.; .:"2>%(4.;",7!.:%))5 .8%!%12.,-%**02#-/!',)4!!**2#$)%,")05(7>0$&+"')#33../)*+% ""#! "'*!" #! & )2=G,;C$bmJZg>rVqRsgwDbp?grCZi5o~Ik{E[k6z{}^p|rVb8dn`m9h^o@|`|eFU0l|WvȝcuNqXsVfIS26?#aoVJXAŵ¨MZE!1',$&-"(2"$,!#&(/ $+#*""$#!!!&**0#"1=,.;).:)#-*#.)&$0/9)4>(4B(8G,EU58G'3B"!0?!+:)9)81>0;0= AM-.94?AM*1>5B;H:G=KFU$N]#HZEVk}@fvJ4F2]r[o}HD4ln{u,I>);D)4;$.:8B$7?&LXJ/:;[fg2<:+42O[ZYgcGUL,(6"&4*9'52B3D9H 9E:C=FS\HNYYB?@U[~%U:TXHQ;C*EV#'2t̋HS>H"@I)=Eq|Egv1IZ\m x1*:31z =9::835CHLNSTHLSLG308,#%#8HOTJGNFG=DEEFJJKGF>G9BFA9H@LMD?>G>DDFBDGEFK]IKXM2C3;P0x,VitIDZ#,-=Q); 9K ->4E4G>U#Kc+Ga(*E 9S#,D"& +,-06,E7 7 8 :U-!9. 0 "7:Q',C2$ 2-'8J^3#>Q*2G;S%Ni<8O( 6* +%: 65/ / *=&8+"%8$: Xp>8N!0 +/)*< 6I+>0F1 +"8 +-EMf2AY&3K4HAXoFXu,EaIei7b}.Qi_t?A<<;CJMHQHEHMS@=7>?AB=??>770,9:=,<98>8;3.31>:;476578=B<<9>DT$(0-34JBfyr)_y.BZ(> -IUq60O6%@&?2*!(Rl@8 ,.M0O/L]yAHe+.K^zF8U"0I#- -A )  $ + .Ee~.AY I`e}2F\cz1]85%@8+K t@c-[|+G)EXu#Jd-E[u9-_{(>^7Q^{!;X?a7XU>[Gb)E-K -K">nEVt(6O 5P )18 0G4 "(EV$Zx";Jf1Me6'A$=2I 6K +  "31)Dl>+D (#68M,%@Rn%Fd.LDaHc0N5PKd Xu ,^z&`#9T 1#4>N()( 4 f{08N"5;P !68Ix!:BOꁮY^So "sDa)@Vl#k&)DHd}4r=Znh}cvrHFVfG 60F7TNg=U,C4J=S ?W>XTs0+-?@XYQo[&=Jf `J`oPjFbwg|[u\TnHdv Xd`i AGCG9A,32;#5/B (=L_--,@-@'0FGZ!lA6G,EA])"9 *%6HX9+ #( !%%/7' +"%9?(% +!, +6-82?"24Gb{M/. );->'7(7$.& ;E#iyG:P~Q6R6.B%53E%:2H)?ES'BQ)+;1#8/D0G)@'?$< .FayF[u=2N=Z7T6UD^(Og5Ne8BX16L&4J#5#9(@?Z+7P +B9Q 4L7O 0H:Q':P$?V%Zq=;N5E 9I$*:2A!8F*2A%+:8F+9G.2@(4C,CQ;3@*,5&@@;892()qxRal}JVj1G\GZ!duJW18DMY,{dcpzt`lm̧`kH?N-6E' .$&)!   "$ +"&&,,1$!05)16('1!+%+ '1%$%$%#!))$1 BP<-:''4 +&1 #."-+!,09)7A+5C)..;0;:H9E?K HU"BQ`r&I]΀`t6?U-ʥ~[N,h*6~uh M#7+;O[?8@*.9;D#?H+P[K\idlxzLXX.61;@:@JB;G>,9*"-)21=$)5+66D6FFS*7B-8pz-~syDz)FfhGS3"O WWc0zJo~CR(58B CMx9Dkz*b\0)/1:<1\n=v^o d}aSPcqQVdeZc^ecPSsnXX[[WPNVIC@6-:'*677>BFFDD80CFKGSDD>BDDD?8=BLOXIQE:J=3:A?:=94D<>B;9?F8?BGJMLYLZSX:S|R]*d߆Tj9QBY$e|F3I$8 &9 +$4'69KUl46P?[!1L-G)B.E3 BZ0,KNl5Eb*3MFa(He)=\Pk4,G8 0 9+68$8)-!50 +(?50 #<3, ( +,!3 /* #6MH`1Ld1,B,I-("0B4K&? +F$@9.E#"7. / *72?%7DT,7H'8 $7 1GId*AX)::K$8 +:PCZ&.C*<'95I5KAW$3J'>.D5H=Q/G %=Id Pj'Sq-A\9R_w0EkZe((-U`+E>/?9:4>B58=B>?:644@IB4-'+347?>7-+-231;><;<@7<47<@@9/6=J865>FKJ9HWNIB?2*;+*13>3674298:=40B8:D>85#0+*+/&2,"0.('&'&+7:;4>:939C7-044(8?95AACXIGJ=@=?:LKEHCDK=77@4`mEV0HAY $. 2OSo9$A6Q6O#$: !"7)A&>Nh<2L3L.&>@^!Ll,Op/=]Mj0:W 8V%8V&-F!5&8+ + >W +RjF_4L@W G^b{84M 9Q:PCYEW *= $81H:P*=4G"5.E 7OF]#6I!3 +4 -@+<#5kSQk\{[_u/ AZ +Mi 9S]w fAaQp Kj4R<:45;:^ SwCfu<]}%1K %A?X%< +,_y6^w*8P)>;Y8WBb(F >ZAZG`$:V8Y!AJk"+G + <384RC`6O&; !2#4.>% +&5%$?Lk)<8V.L.%) +$,(&=#5 14(D C`3L4+"5^tH  + +<4D")= !5, 4Mq50M-F +' %%A[v"Njh.O{-/F!%+3PEc Om2O+(?.J$94F+( J\/);2I;R'B +4L*#4+[q/I`#62M 1;V +Ig7T7S_{*XpX3OUqFe `zTm!BW (#2 '9Ne+.DXp2HQf).D ++=Hi*n %6@-;e{z?*:AX !:_v!?^Xw5ToUk`s_on|hx !@s85K@WOew@K`Zt";=S g}.+):(4<ey*$;X 4a| ,hVm)l/D46{I]j{XeOZ@IM\Q\ _h42\cFQGV HXEV 8CHTn~6dx-(9+7YdkyU_XbFPa4L[r4JVh]mgu>MY:FWeUM!*(4/>!##, +:.)!2?Q-@$7 *=(: #86J/D +9J:L2B +>S0A +4E ;N -CAW@U;N  !%!!"$(!  2@ ,<>Q&Yl?(+<,: +) $% '-9$%)Z ?^=^c@3 '*! #'  -+)$*0(#.7+ 156A >J&6F"DT/ 0 $4,9&0 %%(@G'%62G 8>V7M=R"+)<9P!=S&1G6KRe4Na2J`1+B+D+D7 6N!3M4M(?$> (C Li.);9H!;N ?S#=O".A7I#7G!3D;O,AR/HY7-< %46C.7D..9&'!( "!#  + + &!% '$&#& 02,791-1".1%7:,!&)1"`jF,6LT/LY.ctGM^+yTrIfˆ\i4MZ%jv?~S[tz޲wVjuNxubxj׫aoKNZ3hsL,8$ '1 '2!/ -*5*6(6$2**"-, /#1&4!-9$/;(".'1 +-:'+9'!-$1$&2%#/!'%,$(#2<**,9(?M;,9%)8#",)4 *6!5@,#,",.8$YdL4@%6B*KZB?M64C')7)8.; 3@#2?!#05B'2?#'3'16@$)2.71:.70:/;6D.<>K >J BNT`wD=~JPh.nQh?Wvx/7)p_x !/A.=GT36B";% >D&1;7BAL"BN @HIM;?@kZ\5.E[4D9")=:$786s,uv}.~/=Y{;/P+)''4))IWG_[QMCNIBZRLMC_QCIL?JLUXM70)CLMBMKDG582Z#a}H)A Ka-7J%A4 - '<1+A"4&5-,)>'@#;(42J,/."")' @T8! /+* + 20!5.4F,. % $ !31!4(;-$42 +3M$!;*E: +;V%A^+1N$@*E- !4.),@$9%; %Ga2E`&5QhF;P! *.C $;(C6 8N%OiAOc>7J%7G!;J%'<3I-F)D6$;!9-4BT-9G%#57I8J&9 +*> _wW!C[";U-0=R'exK9QLd)Ne)6+7@=B9:;LJ?6DE@AF@@:;6>A@C<$2;/79Lft*,>$;"9 AW*/J6Q?Z'8S!.FNe=>S.*?0HLe<@Y.Ha25Q;XB`)Ki1Su3=_ Ut9#A '2#>(D 6$7_rQRc:Ma6,;Qg*E]h5Sj JbKaYn,@UOc"AW6I>OAV$+@.E0H&@(=#9'? !93 95/F$:(+ +#5I>W2Tp2N)BMe p~"GeiMis4]{$>3!=Yx3'E9\8W;< @)E=\F`2*2$7,%:/Dn:o9Sp/H5%A+I'G 8YZy;yPW?Z:UTm)9N }XkC"=c;5"0 /## 9J#!1 KZ< +/(: "48K& +>X1M %/I 5P HB_ l.n3d5Edb*|S$$; 0*$)0ay-Zt"Yr),FE`,H]x&wN$02lL(?X /"; 4---@1ERk&0J&1-;5F7AZe@Jlv"JQ'/1?+::J&=I 0<GR 4@Pett/_n[fjw06GL_ m0ZpFY :N/@8D(3-7t"<8hpCI@G *   +"1*(:0-+#60;N*#5AR"5DUKa &97Hn>'<CX8O8L +"$ !" .,( $ ) &5FX16F"0 +1?#1'4:G+0>## *$2*8.&#/$'$.:D*.86>.8bpG@Te=Tq)Ts*Xs*Sm(-FQj)-E5N +`}0=\Hfb?.H&($$#*!!# 22.+--$)'  *0%/5+  %(;C+,47E7G!DS.0? "07B)&. *,66%OW41@CX 8#:"9&<auKL]Q*L]9ET3BQ1?O/>Q//@:J,.<"`kY$-#! &$ jq_&"%!'"$! !#()$;=6+.%/6$/8"%-#  6BKY(Q_.lyEmzEq~L~ZkyH`k?KhvD~YppgayLbiubesM{Zx^Ú`nDpΫྪtD\9,?4ETa4clxPgrJkO]92>hrQXdA"- , +62=1=3@#%2(5-:#/5?%J'6D.>,;AL 6B;H \j7`u p&t8_xPa|2NkRpKg*7 +=pSl; +"7J5D6D2>MZ3=J$CP0dq\/;-3?64=5#*"1:,em`5>.,(77D(9D$9A6=Y2:!({/0<38.36%6$(-%(3*<+),,;QAGPC{\:J?98:5=52+`ThN[_`OK?A7??QUC,EJ;@J=I16JHFHMYFH77B<6?E8:,%-7$$'272+21)9?HHLBFIA@?F:<;/(,21+1(+1,8.5* 15' !,:&$!316:82)*40&!%+3)04-.1>47BDMO[HKQJMEDEIPLJW9KPa`PdD\lFW>V]s]xMMg-B^)/L9U":U!R+!0* "78A\02N/LFa,Ge-%C ;,G8Q$,$ .+;{^5L9P!5Q9Fb'Tq6Ga(3H-=*:*=:S+9S'/I + )#  . +.;'86K%(> +( "(>22 +4I1B/B%8 !4+> 2Jf~>h<9TOf)E\ *B /G5K"8 ++=S&7P_x?Ic%Pi-Kb. 60D@T''? ,D H[!6LQj*[x.m1;A}'6eG]*@Ri L@ZY7'' ,)93-+2A:A5-*5>2;=40+)7F270.38/9:3M@@CA::;@F7=AA<;79;56;=C>8216BD?33>78:766E:3@:34565552+!6+03,5424512*4-9/;7-;;E?95?<99<@35=G<;<>?BIB;BADDDDM@.*,.*;F4"6+4I)< Gb1-G?Y)&=. #8:P%=X).G0K'AiOWu9Ol05T#G )L'C;#=?X4/H#,5L'7I,#32DCY[t,Xq#D\I`,C8PBWCW3D +-@ /D/A$5/#9 %;+C+E ?V$; ,B1!7 +,C3 6 1H"1 (7I:P%= 9Ng~-QtDw4%2Uo74OSq5P Lf)8Lh"(D@^ >[+GEbWs+BaPpJd#2C[!Sh;!%02G_$6L/H(@9T@^,JCZ6S0N qB;Y)6 6P*HUr1#=%=,D )#@ If57R Om1_;BdIl#0R ;B`*GyXKr4Dg Qu_~-*HO=V-B-I(IKjKPo( "l(@ +hI#8  "7%: )  4(< 2*?P*,+E2*B^\w'%AC^&@)5$BCaA_32!9 1I>N!): ,%5%%8=V_x1T!2I+.7O0J +Vp)(='A !2MKgI_u4,)DLg$/K6SUHdE]& 0 &(H].D +H^%>(B :4N 3NZs.g}&Wo=j+|*JaG_ Fa2KB]*CYwyPmf{K]KZCVtS_s~!(-= 58Q5J$2h}12I5Q;W ay,GY,;"/)5 +=K / / +#7Ib]x06.7FZ\sTm&BZ4Ea_oLY6@:C :D ,6/:OWir((32;[e++7DS7G?M+7*6^kGUfN_L^-=FU"5B _i)38FL7CAOQ^Zi8HDT;HCQ =K Pb5E2ACQ0;OXS*Qaoy.%?A9@)%/%;H5Bo}H&8%)+&8 %9 +0%91"!,22<8D!-<$5(8% %0!2:*(,<@,3>CT'E\$?W4L@XZ@[Rm*Mi"9R3G5N=VE^!?V'>9MEYIcPj(To,;UAX$lRzb4DBP&CO(5A=I&]jD7C1>,:5E#AP/.=)*8 )5+7!.7(NUIdi`?B9 &"74/.-'-.#57'  $$$ !  &*  :@,^gL)45@h^Y[j1wQoEd\IW\i1}QZhvF_~SmrkidÊvj^hšͨNZ?(3ѫzBS%.=+7 5C@M$.=):2@%3 :E /;-8:C'4>!5B%%3'5.<-;0=#0(6(32;1:5A 3?:F":F4BBN"AMI 6B:F=G'coV1<,=H<3<1,6(;E6W^TBJ99F#,:1<:5)/';03;<,2,,1/724,0)$,.+3=B@B>BBGCBCBJ<963/+*)6;71"##/,"&$9:;5,(% (#'$"")*('-)18:59KBIED@IHQNAHGFAGJg*y +-7V=dt=m- 5 g}R-*), ++)&<*@"7* "&  +$0+ +H\5K^7+*<+!;8T%A^,'AId.Hf-gMPn=Mg8+E6L&.B '6'-@.E6NEa+pSMh.,GC^) 7 "1.C$%=90G#6 )7%2( %&1& #6 4!63.)@-A5G&9 0C)? 0H +Sm)nb~2a{3A\Xo32G--DRh8(?*C ;UUo3,D +8M?T!?Q"8L2I?S8L3H]u:Kf C_d~#k$x0azWp MeRIu-Jd*(I. +!7A*1-5==:A979;>D870,3-7*.82.2&48&/<;?:2-+987:F=B8=>57CDDE>7<7/9970/?=@@517;CB@<98;>@23>;69CIIE:=7B:-.3,5*,/5874::,@>EKK>71<;>???H:KB>AD?9?AAF>DUNK@#44;>>ms cDYJZ"@[,.(%;, +)=+?0H2I!6%4JJd03M+G=^'9X':V,,E ,B"% '8&9# $ +9LShF\Qh$AW=R?W=T-A +3E$$!2 (9,&(#; +,EI_+AW%)@)*=0B7K'!1#7)%0G)E-F?T'ARU>[.IHd(7e`z#Tp+Oh)KexL7Sq6a}+9?[4Q">?[.G &@5Mb7*-B #,2M7P=U$=32Q ,M&C+ !Rj6%?>V"1%-F'C 9 +(:ZMm 6V Tv)6V7U>@b1Ph<|HJnh&Lqg09=`JjRo*7RHg(.O7V Id3gLc0;U h?Y0'3F#$: J_-#: +'>4 "K\9 ;+H2N)C5Mh,'A $=;C_!-#;27F]'-' $  2:O!-gE3* !@T'3J%=-H 4N%=2K>U6/0Ha{5Um)1*6Q,D5Of>5I4H;TYu76R&@u@3P ) '1%3 $8BVH_\u0!9Fc!7Q 2I'?a "*9QgQi_v(H^!D?X5P,F&@Zy:q,=U;RIa4Jl37/~ WeAR6M&?!6,=Nj@)@#;j'Ra +At3gr-#&4/@5M +,+BYqJcLE`KeQk h}\jer?KO)*> BQ'6#23Bar#GY5Gu=+4OZ"3>YblWa S\",4@+9z3ft"=JQ]P\?Lnx\f5{K6*?RED 13 '.&+54C,@ 4+@*;02 6.*@CW(==Tgz>f|@&;!24/C+@AT!!!""# #)&,81>!,!/'4)6-:) .' -!(7*( )7 +(8-A7 333G_ G`8QSl)@ZDaMi`~0B_Kd64)/'3(#$)/+0 "%35,   +%2>+ " ! +6>$-:!/ * +(! &(.%/)5/C.D>VIa"Mg$?Z5@3>;G#/;&0 $1&71A->(7!/( '$!' + +      !"') #!#!&!"%   @D.rgKT+juLVb3T`/_9GXg+YS`%er8T`&bl1!-vHzQ_$xJgu:Sa+Mibxju֡mŒ~|ls{vsaܺmwW}ֹw##31A;H6C:K6G4E->ET'$3 +&2 +4?7@"@H0OWBKXB;H39F/=K1=K-1@ 9G(>M-KZ98H*/>%RbM)$0 #/$/ &2.:(-9&/;&$0*6"*7"(4*56>)35%,."%3<#;H42?-6B-4A)(1+0 +23N-r~,= %4.<$9E+0:"/80:8E!-;.<5C(8"1 1?+97A 8C"6A4@?K%=I!3@>L>K5B n|,[?IXtUtWyQoo+dj$g& +# @~:2Xo+<-ZeYEPA*3#FOAbjVLT0vMIR"Y`5CKto.f=40 '+'6# *(!!&#='*V::YD6RGFFZ&PI92.141%?;;6;45=,-.+'"%9'!" $-,&)0.&('/33257??B=?DRYDJPK:>?E4<1GJ!~o6@:[!_|B7TFc-:T"3N%=3J6K%72 ;";2H!1.#!1 /1 53 1 -/F*0I)9R-.H"8 7 $ # +"3% * 0C$*?2N#.J0H&B -H4O.Jq'= + 34J++ 3 *B#) 5E)/<"'% !# .+/ /- 0 8)@7N?S !57M@W"tOOj%So$o@o@Gc]v5Ri/$9@W"[sC8,085CHD=9848AB=@AEEFBJHBIDB@>:=A?:B?KOJ=DR*%52:MKFls)cr,BW +Yl/8 0   .*<1+@%;+ ,@ @Q0.@"6 Qg;BY.(D" #  .P^9Ym0Vk&:M 3"6- *@>WAW4KF[/0G8J+=/C*AOi/=VCZ$5L*?+':/ " 'a|"j"AZ $9Zm*@9Q%=,IPpYSOeE`}0Yv&C9SNd(6I.0>#2I +_x"1t4mYvE_08R,E+A /E#AW.% 2M&1N)FG`* 6-*H =BaB_ 7+)(1 =S"*F <\.MEkKl 8WYr0!,Cd f:2Y +6\8^ +OFha(2P!= +?^~3;&A2O7V\|*)C ?4;>Z5T:Y ^}69S @U)>T"%+<2E $2Vh1*:(:)2);X")G ;/L#@<7T/80,02J4N(A?Z'@ +%'$Qd0-B 8M'9 + # #2!=*F /4+B $<4O ;V!8,00,D ?Y5,D @\7MQkA^b~-2fs XP-B1#5 (9"5=S8P5*1rWdWc HS .9(9ߏ-C4Oit)0>Y9Q{BVbNZ 6@.7+2]f$CNKU,5KT 5=6B 6C/;@N'7&6/=@R.A^s,.?! ": (@ $/9n{WhuV>M-{hpz5G*)$. +5?) $2@O(:I:IJ[&AQ@QEU$RaNVdS%3!)5$%/&0*3#0:*4>,,7$%1)5"-8'6C//;&-3!23$76"AE%;D4@"FR>!/BN19B*/8.7;F%$2*8+:(6(6%3,93>4?7CF6>9C"=H./-*02:7>=-:40,6826690>CDDGIBEDE]KB4,#751A=3(&87/2' $+??E>'73:.9,2%.19E<9=16?6<Y "' * 1F +"4#6$ ,-C#&=0) #  - 3 "4K$9U(3Q #C$A#=(@+A-F.H ; (B*C(=$$&" (%4/?*4G4E4K!-E2 7 +).F/J0"?: +5 9 1 +*: !&62C '=,D0LE`->X'?V*,A (>%>5O,'@ 3"3 &% +,-* ++ "3"9+A(>0E&;4JNg*`x<=W>Y=ZXv/Ik.N;UJb(;QLc-F`(:S1G,C5JK^&;N,,=+=0(< 6GPe.5N1Lg.};v6Kc7M:QMk%2JD[Je"Mg}-Nb5P?54B4.(>B@A,38<60931+,*3(5)2D%54'%$/%.*.7./12%#)'!!)6B?23-6?<>6789CC==0@IBFA>NB>DN?QQAA4006>5@A7-/(8>=497=@G:@EFGAJ4!>D=GXKR#PVkTj.:1" $&14H"$9)* /9G-!'>oDVn&$4!0 +5E9I=O!h~+85NUZ,>jr)Ru0A-J0E"9F^RlB`@Vm% .&9YPhi$D_(C,E,E1P ?X:O    &*ELg5?^-J +Hg'Rq)4S1 #.C% ):=N. +Vm\^w@ 3GQh Yq&[s/-A#6 /3I^u1(B&Ic!&@C` 9UB^ 9N +?V6M>Xd>A]'9 'Ga*"> Ki&Xq61B!&.3+H Us9)H 2O +;Ub2,G 7T C];Hd -E)A,tAD_ BZ 5g~(6L-Aa}7Q&D)@^u1Fg~ +Vou+> 1#;JAV Tml"-1*4?/fCNM\VgE[ .LfKfZrVon1c9BCOFR]fQZQ[DN6A\g'9B>G5=3> ) )AKH'67J;M Qb!#O+0F 70H80Tk2Ib$1LHfEb=\Tq"No&F6742, /?'($&$.5%$+%, +&4IY=6G)8H+)<'80 .+!-"/ ))008".6"-6"+#.% 2;$7=&EG2+21=.<3D>O":N7J/A=PGYSEY I_"F]!Og)=S1H Mc$Nb"Vh)\o3IZ#CTIZ&7G7E>K9C7E.@;KFW)1B2A4B FT1.;%30A(:(:,<+9.<"0-=-=$3&4#.#* "    +  + # ?K)]iGS^8P[;BL+DN(gqMYaCP[7Wa@s|Z}egkwG}VVWisB;DqX`gP-8%/<'"0)8?N3 9J4BP93=5?=G#?I#CN)=I#BO&8H>O!HX&K](CTCT AS#6JCU%6H7F)82=4=FM28;,ck`wz5@4ScN:J/8H,@Q7TbLEQASaP_m_ZhY/=,!-(1(1*0#.5',4#29(4<*&/2=-GRB2=++1 68":9Ȉ?M 1?!*6+3$, IQU'>DU?S%9D#4=#09)20?+ 1?)7+9*7.:2>3=6@3=7B6A7B:EG8DS`~)0'oVsdr 'fdb})+Yp", Vyp3),<ER]i,6A9B&/\/8 2;#+8@{;Pi^]AKH4=/0 G- @<1$".(/8H/.-8/AMHDZYW[WUK_ZX`\RV_n]G=@Q>8=@ECCHKKCRPFL@DOQLFHI>'3.-141:??9DI?ZVu-; (C)B1H:S$0K-G9T'":-A"4+':4J?T,(="6.7 +8U$*G2MHc-A_(Ol5,H/I,FB[-@U'&=#85A]46P'!:"62E(+" 0/3.C- &)% *' $6): $  !2$74-,F$A&D,HF`51 +&<$<-H.%@4P&0E!,  )2@!):$6(:"7/$(%9,@8Q%";8(D8T!C[-)@CV.)8 !/ /0 3 9 +3Nf}O'3-@ '$90J')A',) + +!&$ +!- + %1 1?GZ.DY(+= ,A TJ_Yp2E^"@[7PEb"Vt1Vw0Yw4Zw7nNAY!E\%7O.E&9 +7Lcw;bt7cw93F +>O 4F8M>O#?Q"Ri2Jc#yGx9R Sm!Qj/I}@Rg!&8#8DZ^,9%BOj)C[ &2,$>5Ok81M4R(F"?Jh&5(G 2Q      2 ,C 6P1K 0/G + 6 ]y1@]Fd'C +-7N2K,E 6RC_b14#0aw:Ja G^TlD_>W$;@^.K B]3N2Hg{H>S(< 286*?k0[vOIbTJ8KQr>ez.ZsTkexRf`t Zl}>- /k})BXBXx77[q}*l[s s6GQ@K:FNZCO KVJU4>,'5APXh .4xLX[gANHTam;FHT_n?Qq&)&xB.47sFizy-Y\('%6!45.F0H)A$< +6*@ Sf'2/=O)AW2IDXhz%kz& !$#" +%&"&).:!*)#-!7@('0# *'+!/3A$(5%3#3.>!2 "2(7"0".*) 7G"=T$F]+3M8/F-G 9UyQ4R Zx.*G`{3Hf+064. -0&( ,6&"+%*4"3=(.=#%9#5*9 /(8>O)CV*0G0H3 /,$-.>:I4D6E;JRb0Pa-8K?S:M\l<8J=PBVM`(BU@SNa)@Q?PFW +< 2D7H4E0@5C1=9F6F8H9H"0>(5#1*7 /0C'9(;->+=#3-BT"2$3!/# !&  "       -/37"S^BYbET^;5=>E'RW9T[6zYvKd{SUe-tMn}e~TsIjuDgv_NY>=K+MW5BM)DO,WfCIX0AQ)crJM^/M_,FW'9H8H5E7F):6E-39#!% dh7kt_n.-:7B',6"8)-9y(8GQ-196>!#.*9&41?,9,83?4>4>7?7A7?6?7C8B;G9E@JRn*D11;L򁦺Y>5A?H!'v-j.az&Xq a{'o/LhQma<'D.)D5N Je6;V''C&A1K"70A*;%;Um>2H/E0 6 2L8U$8V&8R!Fa/3 2 +:5 72G$,!00#606'*!$   ! 9L-2:M.:O+&?%?(@2L7 ";!6*3%<"<,E#>3 3,?#( - / #/=L*/ #5"3&1C#4'5!2 ;Q*8=X+D^/Ic23M%<AU.KZ5&$ .,>1 ,D?X-,&2%8;N0#6/%=4K-+ +  !! #%0(4 |L1=!9I+= 0"4zN~T0F +Hb(Jc)Qk0D`%dDFd#Ca"Fc&4RF`&Nf-AW -H)-C+! 0,#:.E9K(4F&1=%& "0',% +0>$'6;K02D)%9+ +@S+Kf%,G8R@X/F &?..-@1E5H?R');&7/ +"Q&9 +4)v:Hj+| /)^}!%F<]=]4S*G8R !:59P +az"Eh~19Mcw:AW0G(A>XHe A`LlDc%B +!4 +0 %)=%=Lh)-L7B\;T* +&1K g::6 + % ,&/D +.G[x96Dd,+"$4Tp1Sv:[ 6T ]x2&@(3`x>;P'CY(,E 3M[v)82M'E8!= ; +-HKd2   $Qd14K +D[#';;Q&) \u27R8;)D#:'@ -D Kg#Wt'k;#-C7K 7K ,4F3K0+E <[Rq)*B.D%6)+(':*<9N+? 20)00F Qi"Jga{.,F0J 6R1Nw@Jkb &*BVo-*+@I\#2"%0 /;'9 6KBV!1&?*23,F 6LJ`aG_.J*:*.F^5K1Ddy'NaPg|*Zk YmN45aw Xl.HNg/G_%@t3Aam 7FCO:EKWBQ4A1@'68F 8G 2@gw*u;H=FYcts{{1`k'BLXe/0<  Wi/;O bu)`O^ 9GIVO_,<er&YfZjXhZf KXIW&44AO\7DSSh#k6"5 *;8I so1#$4  !#$% $% &1#/%.;E,$,(0 #&)!& *$3$3+:,%3!.!/ .% =I/**;Kc1'@%@,E-H.L iBNl(Vs.#@+EMf,/12&* ,%  ##$(! /=#2B#&;%8#/&+96E 0C>V#5P9R#G\/1@)6*5-4'/28!'9?(69(=?2MLD{uqnnbte".6B4B@N"6C3@;H;IAR"9K@R":L@Q"+; *; +HQ3YcBGQ,tRyHW+Yi?M]4)"TcIM\DBQ:8F-=L2vlETtbXiEUfA]nI[nDOa3@S&5D;J$=K%@M&0>6B;G:E?GEK/AD0SWKUbUCP?VdRTdKM;4A02<*-6(.-1/3+0+/:@*5=&+5!2=+17(59!lt6YwZl%P[<-5QB G3*;:F:E#?H)*4.</=+;*88C*5 5>3<8@8?7?8@6?;F>IAM;G7CL[惆q*rWt'p$Yx`|D]Vm/gb|z|Yt -_ux $L[>H >I ?E v|#vr/@UUL!-30-49?'=#) IK?V;NVTEVZab[mXX[WZ_JBDIIJ@EN>IDMKC>7;C@H=FICD=GFD@ADHIJGIFJ>@FJ=:@9EDB>@78+55.4&"++$#&-4;*.-#%3@?A8'>,0/B"0 7M%5 8 ;4+# +;Q3%6 "-$1( #3}h0@., &6. !3I"=W*>X'5#=. +%*77B !- "2!51 +?V2- +? / !Z4:XVv6aAHe%:TL_(O`*H\i6cy"p%Aq3!7-D,EDZ*2H8QFb#Ss)DbSq]x%Ul`wn VlT0F6J -BAW@X;TmBYS;;3:7/5A14"*5=;0)63.4,$610/$#%"*#('%$!+,'&"+*20,6"$#&9.-#,+&)-'+26?>>7?=FJ??KHCNF99A=<;@87E@84B956-5%%10-8("%>8A8.8:3634,4@51?1'&94.7:k%Ea 'F 6 ;/K+'@#.+ANe8Mf;+CBS2'6*7"2>)9&8-C-( +.@#@F3OQmNj ;[e"7W1Jc.Yr&>X &?#=CZq6%=.BAV$:6M sJ6P0N2R=_9[3R80%&(; 2H%?#8S!.&"%=*G;[<*%=, ++-E- +'.'\v::Q8J BS/2.F nCkC"=6/H`+1H$#"!93Ea Lj$*1)G9V,F2 5!8 "6-C5J-=:I.*5  L_5:P +%,?U/2K 9S (-2 &0I&@6P +F 3J0B %8"2 5N;V*C@aQn)#9&8 +3@$2#- . 0"3 2*>2E+': */I` Qi_y3e~:Hc$BCb3R 3M +!;Obx<)1B 5B(4 )%'2ET* I]7M 7%A;T +s'ACT"$2CTOe%0F5H 3HKc8ROH[L\ l}(ILc7I$>u?{EKe2M5N;R6"; +2L$B +9*C +(@ 9P1J)A $: ,. 03!40C$1/*,($ +)$3&8'6*4" +)3,:3C7I4J4L,FH\-CS*6?agE4<'0&.-2;>/43)NKBNIDDB8 @E**63?+7(4%/ ,7+;2C0A/@3C7G5D5D5E3C.=)8*9('8 +=Q RMyA^q.dy*D9f}Xm)*<=S]|Aszqr^n|avWoquvWuX_lm}EÊ}Wvis>R[$PZ$~UўtSsz~^l]|YxTm{J4B9GyVv{Pb`[ăxq|?}aswEjq=ho=GN!   !,( $ ) $ *(14M#LX1CP*9G%@O*4E:G:C?G&3>$/5/7/7!0<4@0;5?;E /:5?6@8@8C:E;H4@H=HAKs.6n'wl't*=p\ru=SNg Fa{!->Q!6HT]26FlDS;:N@<./C0D@E4%hz)Oe^CYl[nGMP[]EJOQLM78369IE=>6GB4B;=:>>DA=5A=4+:CLDDGTH=EACF>-64/-)>)2/1",-7?:2876*)0,(/56540/+.'$04;;@D78HGNNLCNRJLEL_\B=DJFF?FDFA?MOMOKMHPYXNDCPQLLHPJCGFD@@EGQKHFIIKLUWB?:>VaCo!QhH`"BX$Qn4Us6UpAD_04O!4 8U*TnA%?+D3L"/G!7 6$<)D+D'A3M=Y):U%;X)=X.&= 6"=C_.0M7S#=W)*DJe;,F+C%<;Q)3J#"#"6"4);$# ,/!8%?3 1 !8, 6I&P@:/10.;-0.(/:96 #*'7?+%%%+'$/2-#!$! *)+-*(/7;')0&--.#*%'%/#!/+ $"-,)+"4M92::798C>;&4,404.5)+'4243$QHc !-JGc:5/"<G^5C\,.J(?/?!"1+) +8K Lc4$=+!6  # 3=# *? Ib&H_#6K5I#7 6 %=%(>NBTM`&>P7J;Lj{BEV$5A/@O). , %>,,CN(:RMhZwHh[{coYw5 :V#3POk:S2I.I,I)CJb!C]/K@\:U0I 8X 1S@b8X0I1)&4K60+F%C ;U&. + ! !1NFl$;Z*C3 1 =(E' (%Lb6BU4BV-:N'% +*1C('3 $;#>4: +!; 2*)9&7`z9@^. ;3R4R 8+B#>(C ,F+F7O#  EQ6%5  0A7H# & . +/;WIh2M G^ 1-D5#: 23,H +D]'@.C2E-&:'>*C +/L +Rp-*I&=(( ", . #*$5 ">R &,> / 1Me5EZ& +,7QrN % 5$$%';Pb(#6BN/ "+)3Re5)=7L(?1/$8(>6M F[ ,9  . Xk86G &"'$- *;*=1D?S4J /H'@5Si.ޒ0?.9 .*;%<2J +"<Vn*;U ^v,CY_r&1C4FJ_RVi)";*1Ng(1 +7L6LMUb7D$15D6H +=:M-</Yi@j{J3Cz)-5071=)9?S&2H(A,?1B=E":@?H%OY7HN303##+)42(/-"-*01#(%- !,1>)4(12=2@.?5F%9 3E-=4A:G 7F/='6. "36G"0B.@(9/>,;6E1A8G2B2A*9(8*;*;->#DXm,D~+Rv98OIar}#3Wp[u;m#wş{yȕ`p}FyLWzfNY)\d7}_WkvxXŦߴR_B ! +:8G'/:/8098=$8?#=F'9G(4B"-=0>8D& 092;4>EQ54@("-ZgQ[gMfij}4+:6DK;E!8?7C4?0;8D;E8B8C;D :D:E:G:G:E7B>F>E9BGO#ISJX~,qc~ +hk\w nUpu /%[J,= +:%#UJbwHS;TB,@Gi^T^_N\?MJI:=;;#3< CTL=NTQVTGNOWAHBG**368D-1'*4A=796=JB=57:B>9AOOPD842=@>7",&01?>7/AA'469<677=5C38>:'1?A@D=6,5244=D>CJ5B7:9>EOEOAIfPUXWDGLBUN]JDLIQPTNRGJAAFSMPHDF@PPG?BECC;GD@?76;646?E>;;@FHMS?/22ES;_rE] (>$=C`$@]!.H,D0C$1 -F&B,HA]/ 9!:"=2. - ) %8"1% + 2'#" +" +2^wQ$A9"<4+ 1D!+E(A,6J*)>""5$5  (:5E(( .+- %.D!'=,FXA  1,:!% '-:3A0=AR4*=2 ?U-0F 3 * ',* +.>6FK]/=O"#8 Pf;'=0 + 205N*>S0 + !"  #(:J!uW)9 ER4C YXc2,>7J3=V'>W%(? I^AV`K3MTl3%</G(@*A:T6S3O4Q6S0I]t*aw$AS>P6B9A<3-+'%(/,(.)2+/%.'&5/-7&7=85,1.'&8)#-30:.,0FGa7O.H.I8U#;W*>X,)D9T+@\/!; +46L)"4.J^9(<"7[v:>Z"%:!   (50HUp6C[8L5G(<+=/E1H %=0 !6%'8?R$Nb,G\#;P4GFX:L6H0A +%89K&6  1*,?la{:*B +2K ":, +#c?/W9x*^z=Y6Xv#4S)D8Q2M7T :Z2Tq#=Z3N1K :SnMQn1/+)D3..$ 6 =Mj&Vs6Ml+Jh!4M=T ;VFc.1O#'9"7!6/E G\+C1DWi4'*(Qe7 3 +( ' +&$%$&"5.44BV 94MMe'=U(!2B%2$';#8)A4O 8Mg'F\3HUlF]0/A9Jn5Td')@ ?W 112]s7-?!4.M[ T6I /&6 .CQ5ADP =FAM^j2Ze5&0*'3+: +!/ +)>L5D*(6,:/Ao~/T@p'JW`qVd.0>+8&56FCTg}(3,ENe ++B\tQTf%+'*0+$5 ("6-+=!9.*<:G*=?S9M4I3I-? 0.<CE8'+    (%#%  !%"*% )*5(6 0'9$4"0& ,7%% #0"2'8"5%: 5*>$<#>.D!1 *8.>.=0@>J(( (0-<5AEO-:B 9@FN-=C&;>(<<,><,B@1ZXK[YH89#+1-5(4 2=.90;*5 +:.?4G$8 '80?5C3A $3( * $)9%6 0 (65D2>&3 +!/)-5D/@9I)%: Rc*bq7|5bo<%, 3:TZc*KIC}`hyBrS~ay^T^Aunw[NV:\aG#  ",CN3oW]HQ`FQ`?^kJ?I+)-@C-'8|huZcjKzxZxXb<|NV4t(- ioL^d@"& @D4;>*')2=cqIHV1GV.Wc>boG:F 09/6;B!;D!J'3B+97E"@L*:E":D!LS/JR.DP*Q`9YiCdoOLV4dpPvnylusdq]]jS`lWlzeduZIX:IV4BL,_jK3> :G)CQ.L\29E>GAJHS'DM+EJ2X^Mt~mlx_GT8;H)DP5_lSZfMXfK=M,TbHHWCTbQZgZfrg`nZ,:-:6@#0;+6$17A(JT?:C+eWs|{eF },?x#!!!H6GX 7E 4?6<6>2@2?4AH ;D;G9D?9382)87-3/=D;CD?HEOIWSHH`]TFF?;=KSEBK-788EC6858<364,9<@DCHLHN@^K=>,(+/3>;<>A=>A@76>51:80/./.(*3@4;453@9@HA5.*AJbe_#;c}5hB9W1L:U%8R',F/I1J"2K##=!;%>'B7O(+D3L%+@'=$:!9*AF\5.F)BZ4$;' +$)="=?Z,#>#;%:2 1+ * $4 1,+*< + &) !11";9&C-G 0/2$7!4, %# +(' + .!/.5D$);#6#6'/.%   6>%#. 6A?N&;JQa;,2C!1B%97J#1E"7 ++ 1* ".-B(@ 0H!; +(B6 4 3 511F& 5   &  + +$ . %61@9I>M 8G:H$/ +3AXu22Mh"2DJ\Mc!Rj$Yt'WoC^p$)| #9?10:;*.3211*564#%".&%+19#5+'%#+73$/(+-5/33;E:31//(",)$'#&%$"<*,+)54((-/21&07?D>725724?7?CD6;73:8H:DC@A=//4)07('*236).:&).*FF98@0FE)-$/.%%3.+&-78Ai&/H2.K5R";W%6R!=W*-H'B $9 ++C!. + +yaXs7h?^{2tIQq'fjH* +DV)Sn/Jb&4J.B #6:M%DV03I!%;5/ 0 :J&FX02 +/3HAWE[[p,*>-@AS;M(< ()#(9!7 Hc&<\m>f6Mi ^x0Xq03LKho)g(F;Z7UJg +d,Tq%Xt&Mp=Z )Cq@f8.M97S Kh&B";1 zV@] 43$+8I%2H3K(@- 7 5+I:6,E6$@1&$"7,GUm4- &8R6O# /+  '8 4/[oB"6 *9S,02-D3$:-@  !+C1OJg%)I$E$B>X3J2#9 &6Mg79O 5 $# 2 &7'8.& %&4%0 +1.G^%Ke&(3.('&< <;,IEc2N +?W?X,EOf"0H8R Rk(2K ,I 6OA\7O /''/!6- (9"-4I?W+C +.E 0)= (>BZTl]s'>R+#$67I,>+@?RRc)9*:$1cs*[gN=J;I 2F +$86F>P$#1&'$3>*6 .8+#/##&27C)9)Wf*AS5F 9F-:)6:F[m]pSeWk7KDW&7(7.;?KVc->K "4(;3F/#5$2(  !"@B3%$1"  + -%( ($& "/%&-:",.&6$  "(!/"1&4*;%6#5. ,0,?+<%3,93=5@BJ*18,0%("% ,2#6<,AH8BI9,4) ' 0*6pw^"&("%,."$#,&3.;".>"2@#.65>!CM-7>178>#IL646%9;+<=-JJ:68%%)(/,7-9.</=0>1?+;(8*:"2 ):%4 - $ ( (#3 )7O[7NZ2[g=EQ&lxJo{N|ctTh4nƐɈr]D>,'9xJdgP&.949{5=9Zm@FV1*: + + +  '+"$!$U[E@H26C,!=E.9C(.=Xf@:C'AC,QPB + #Z_L}pNS@^gNW^FX]F!)6<%,/ $ " $ $+!+ & -*7'4 +*6 3=4<7>AH>G=H!7DHLV)FR'?M'L\1HS*PY1^dWfAMW1]f;NW+MV,R[7EJ/FK9vop|d\iMdsUGU:R_Ese[iLS`Fvok{igvjWfZ^l`GUGbr\1@$5B"8C!6A#/;*5-77?)'2^j7gw*Sk +c~wHAKI=G@G?GCM>J>HITPbq qTpl#Vr(BcUF <0A/dr<1NKQZQBNDGPD39;H7'285BE>=OCH;3EA,6'7DIJ>>:5144*-66:CGB=FAWJLN@HMNUCAD?SLTYW]WH>D?E1?9>>=78A8CEA@FKMSZ:" *-2&79IE:BDBAJF=;FD5KJB@BLXRX[ZUWOAGLLC>EJONRQRMKRF@KOLI*2.%'$:3,(8,948=52157M$.H8 #;9M%1A%4!.(7*;&5.*& -;&5D,% +,&81)B0H#";- ) (& +&3+ .5C)"2!0$3-"1#30C* *(- 0?8H$)91B -='+ "2- 8J'):* +"7&<AZ*C]'6Q,F=Y%/I'>f~V.E,- 2F).A& + +   .+ *#$, +- + . 4C1C%5 +1>1.D0G"B\13K!0E31G1G%< %;5MRm4>Y9SyKsBg~/e|0H]3H =O6HRe.%9,(:1D8=92136/6><8.9B;<8/75B4.,- %!(.32131.36;S)Lh0:Y3Q;UMg&4K 0 )A$A +,E+3J#@\%:X&B +'<   * +' #8 +A*B)>*)$;84'\u.9S* 3 +AX'Rl.!:C`!Qp Jk 6f|C#5 ) +%./*D ]xC%= +!6I1 ",.=R**! / 75 0;OEY$DY&&< +:O()A/"@Ih#C*IFb?Y9S[r)AXF]9P-Fb' :g<>X)<&6*4I.-/.-+<#5G_#3M 1K6N.E-A  ), &-?1D, #?S!5H2B /7(4<0  ->9K)?[q!br-" + 6G6G.B\t7wEKc)!!!& (>5 *B"=Gb(8.F%<-;QZn=)@/E( 0(3GBZ$ "3JDY$0/GTj-+DCZQh(0 +4C  +~M}NY +gs)=L 8JN]9F-::H DT:M1'$*5( *% 0! $,7 )4-FPYe1/>3G-A9H3? 8B-8\l%AU ;P @W5I`t2':,;&37E +/</;$3BRL^CY2DF[XpxU-<A><}*U5u-AKqz8GCJ6@*;)$>..2-@0#"58J;K$2   !#$#$+-# --0,+# # $!&$ !("&$*# "0*,:" +%%&*6$1)7'62B"=L*-=!# 3A'%55C&-8)1'-5:",.12$+* $$;:5CB=)(!(&$tsl9:/W\KeoZ $--3! #"$' '", -+!* "($,%,  ! #&/)4.=&7%4'7+#4%4( .!!-&5#/ P]9!- Wd=n}P`nD[h>q}NuOkuCi鷟yv~֖ykZ|5~&8;,F74A68?z8-s Qgq4V>L7O!6=N$7C&+7OYCT^G28 &,+0)-15%+-!!#,.&)sxj[jR6A*QV<׮wuw`zwnȫ\iadlcmreGH@ce`nun^f]ci^`d[CJ@mtk"-5%+.*-+/%+)/&-"+%-'0*7-:3?8C9CK!;F>KGU"JT,JW-FT%OZ(@JZ_xuG$?G!DL"JS)PZ6QU:EK;zzmygUbKK[A]kQ]kO`mSVbQ}bthYiXET@HW?7G +^q es !2'NcA ,#KYl0; :B>D8B;D=FBK AK>G5=;C4==F CMQ\>G AICKAJGSJU!IWThLdRm?Y2vJ=3;?@5)4-)%(1'-941:FO@SVWJVo\PK:@MTVSCGF;BB;HTR45DIK:;552*,%67BCE=:FDMQXCCOJMLK@:=PRNAC:>BB-FFJILOEKBKTUIHLLVZ[[YYYTQKFI9=?L?HPFDMJOHA3)41,*07*&30;+375.+5323-02639@1C08I(49PgQkXw37T,C%;2G!=T..H +D857R)7"="=";)B0G""7,# .3B+& 1.C$4*D!-D"'?'@$?5O$/H%;/ +-< /- .A,?, ( +/) *$ +! !- 0 0A#4 1 )(*9BQ3)+@R+/ +2 /J,HEc,$?7Ld0 %- +-A ,/%9   +  !   0>$' $$% +&60+5H%3I$6M$"9 :"<#;,3/E1G+2IOg17PKd*BZ19:;;.487.'",.D.9;3>E6=0w!q#l#d#s2e(Tt_%[%Z|&\|'`z(Zu#]y#\u\re~#o3\w'Qm!LgWq2BZ#4L4": +D'87)G 0*!? +Us1@^!4-HFd(9X[z3Ll 8XNo%Nl"Db8R 2IG_*) +!)%6-'6+;%< *! !4Q8:V" #Qh1Oe7 $.9S1P0 &0E4 4N#'D9Y+MIe1( -!9)C!:(<3 .C3 ' $#< .L3Q*H 7*iŁ)= #2L )D&3]}.Gd,B)'$;'7S &B8 +&5#=#< 1 +!;:6"9 -B.N";/ +- +-1 +" +5J%%<8$BBbDeMk#Pl8T +E_DZ&:&6 ( .H , :G](=%0D#; 3K6,C !7*4'&4#<89OAU !+ ! :LfxB 2/E?V*=  &4+<CU8LzHbŒn|H7J I^ /EB]D^:S##1 !).F1:V!/B[ AU$ 3CS* +3 .?(=!"4=L# 3 +:N!%!4('8Vq,Lf++5[s(yIH] 1rL|MWV^NY @KUe[j,8F@J +.8>N 1A"5AP%. +"(!+>O H[ 0A$2#/ $)?LQ_'09!/? 0E=R-? +$.7?$2AOCW@U 79O8O +/D^p0IY-:DR?L9H@S4H8O 6LH^ >Vf~w;esXhr-z^Hq}&:Nck7E DW1G#9*9+)<,2C2C=O 8G!2()$$$   +%$3) "0"0(5$ +&' %  &(5 &3*7  4>/",$-!*3>&(50?"0>6D$6D&3@'&4.:(6AO2-8 ( ),8<&25!.0!()&'>>6{yve`_f`_RNL|uur8?.$*5 3;*!%!&#)(1"*2*2!*   +1$.$0& ($! &V[@]bKt{g)-48%<@*NW;GQ8BL3hmUqDR4o>F*35#htl$!X[S&)#JNL}4=7TZT=C"49@I#DN(LU3bgK:?/WfT]lVds\N\DmzdĻ`tiQdXO`KBQ6IX9=J6E a+93@0;BK*4>?JAH8D 5K`}u>I43$U@Q-8>F;B,18?7?@I0: )T &F^N2js$2;.4AJGQS](MYVd5:ur[K9 +;,>QJhXENNPM8?/3-(3'+7*.0@G58G;FGLG4238?=EH?KF@PC=BHFC@LFJA27;I6GC;EHK@MNFMHAJ4IM;EMCLCKMEMWZGGF?@?:?GCJOAHAKD>3++B:6.6023>884;6A81=88@8&@3L%&?5#2-$#,  3.D$#<*7N,;S/5M)#="= 9#9 6 3/ '-B 5*&90) $3'   + ,E"/G$'?  # ) +$ +#8%=,F"; !: ":3 !'%:(2' $) 4*A!6) +!/$-$,="5 "3#4%7"# !/(6-=GV0.3G+B&? = -I)C'C;R#2 + +!0' + +   !  ++!#),3C"/.5 0H"-G &@$>#>.(A.E$: +F^.Og7Jb2-E-FF]&Jb+Nf*5N A\>X@ZRj&/GG^\t;6I"4 ,>3E':*>K`+G_%Wp59P(@Og/Ng)]w.Zu&HcYq$H_Pg#>Yz@b}"h"^zjdz6+<=9)/71/"8@@@3;7+4-#" 4(3752(&&5(%"'.*+.7)24/258%.!.*)1-822>(11*0?=/,7,10,;4.22AJI:>CQ=E5>3$5,;7B9'17/2'&$)13(4936=|1z0y.7y2s/l,o-c~%`}$a)c1f7^|1Vu+Qr'Tw.Ko%Hm%Jj%Qn,Fd!Fc!KeVm%Rj e~6Sp*B]?ZE_%AY*=S(@X&/G%< ": Kg%kB[i?_~8]}1iP#6G>P&GY11A, ##2, +;+<"+(?BZ,E"9"5 '  "(!3 %Tk3Yw0Zx,Om#8V.M=]Xy!;^A\ h&] '!:-H +#@[z<%E%=5.H-K+K9Y ; !C[,6"&B":()& #: ; +,J!B*I +c}9Jb $: + +=U632Ql#<[\uA/G0@  "=Z)2Ml0)G&C8*HGe0'E7S$+!*(#A.' .AW-4 2H '%@ -'EUt/!>:6U*Ec{> *9 +((9#:Kf h2Ka, 0G*A +1.H *C;TE_"2B[Ys.Pj(Kg;V 8UOk"5 &4'5&") 0cuG&2Jb*7Q-Tk mw|=(.4B+AQf%2B #)&6 0-)9(2A + , .!/) %!- " -0&+0!07' & +/6&(0$-#)3" /9 !+&.2>$4@'"*/7( -; +6$ # '+%)$(#)- !  + '$@A248$ +'&'"*! (&0&/"-'%"%*/(0#- -8$/ ( % PU9?D$QZ4P]0>MWg1L\2=O"*:,;7E Wt}HXc,xKpsHsJERvMbl: Yf4_pL[`f^nyF\k.xDUK`ge_Yw'7:0+y x@2&%^Ze28:;7UyaxGT2+vZ`@=F,+5?I1_eNxջqzb9>)+3#JTC,8)O[F  !wzx5=5).(cf[,1*RUWgst,70gpg9A37?3!'* ##&'*',,1+0,3-4-44<4>2=7C=IFRDPGS?JBN-: anCN[/AN"4C@P O`*R^%;BE4EN-TJabz]cm_yWe[UdS]kV[gOPYAW`H=E1NWAgq[HQ<3;$AH,GQ/DN+enNMT;>C2PVKgscso}|˼^jZ{|\og]shOcTIZBPbB^hD0:IfIW8E:H0>5A`w &%r@=MA*.3yGo 8H?J9?rw6ae$'+v}9#|rx2vEr]tnKWDODP@K ht)>ap l}$ofR>aAM8JUUE_@J=<@=F@78CO@.11J3(,0.3=3>:?69+67?JJD?I;>OKF[ZDJMMGDCALLBVOMH:@FEKMMJRFLQHGJUKPP>IQMTIFNCRN@>GSAIB?67EBCJ?EGEON]TiNMMLWUJTR3=.3<47433%/)36;>7:@M3~EbE,Bj!Hi+s?DbA_"3K)A'A84M%*B9T+C\5)B";#;6,B!7( + '! &  0#<9'?%: "#8!6*A';$91'+ 4#:. +':0 21+#   + + + 543 /H#( " !$ # & +-D#:"= 1M)D%@*:Q.2I%. *7. '=-%* /% +#*&) 7H'&7 (:):$42@"# + +  O^34E2F 556 6R":U$0H#9 L`;!.S_C,;.@ +$*" $+ ! " +*)%-.B5K$4M$/I"$>4 8 ; (BB^,+A8P@X#4NLg17;=93F619=;.;-4=?@;.42:2+.))##5;7@.3,291;:.,"//+765/85<:7+'*9;/-/61;:/8=7;@;GDE9?>00-2999DBH=?43885058)%01..$1By/d%m0]w"k2e+bz([v#]w(]v1Ys/Xu/Zy1Wv1On*Ki&So.Po0Jj)Mo.Rs2Kl.Ec&B_$Db$Ih*Ic'Rh+I^Oh'Ia$1H4K5N5M:Q",B &To8YMkHg.N:X Xx1Jj\}-Wx+B`0LQ!2F,>&8 1' +( .= !+;.- Sl6Sj2.E/E4#4!1- 7G+->'9/ $9.F &DB_,J3P)G\Fd@^ 1MRnPi)D$<3i1O@_">4N4K2-Le26Q Vo1.J0M+J#B4Q)D (4O/("7 +": *-6#= Jf,1N4*@$ 8$? +9.IKg- >(F &Ib>+D#%?Ki)#A$96P(G*K7%? 2#8"83 'B_&.N)F >V#n.)!;8S ,C&= ,1'CхJb- +$ $ /K92Q$D (D;$E.1'?,!7/G0 &564 %5 $: +$ +#0L4R"A:Y:W1O 'E7Q ^r; +%'KaOKb(!);7O(? +2M 5R9"?8T;4R?] v0u+<>\Q+'6G' $( +#)3"9 -H8Wq3J`/ + 7F!3C""2 "1 '= #;+A +D :Oc+   )3,A1 #1 $ # !/##9$%6D 1A&&2!:***.,=%6GQP[JUAJ ?I9A3= +4'46D =K LZ6HFT=E ?A& +&Q[/6D]k;AQIZ#3D BVEY)$)'2? 0j{.*+7ADW./;++>M".>-<&*1A#* !  +,5"- ,*9viy:)'&"!!" "04%29''' +/ #)+3"&.$ %! ", '. --+!)"'&.$,% #*!$ !%54& ! +" *&& "(! :=&T\69ECO#!+'#,5;s]b;w17[iVim}@|TrxW|Vu}NqxEvkZt^k×ca|l|L\rÎ~Ï⡘_dM=1;.9@0"!&%'%&')/4$ -3/5-33:1:8A:F:FGU"P^)_m9@L:FFR&Ub6IW+DT'GW'EV&DR/6kq!oEY_$2#Sub}dq]my``kPPY=FM3AF3QXJS\Ms~mKS@S[DP[=NZ9lOV>EH5QTB{oluewwvwo|mctis|^scZlTYhHhrI{4vbGY:I +APIY9G2A]j$|/qhxsRgn}>:'1% 8Ym15cgVq󢆊73[ށbkC[nN^9KFT BPLX DPuqy4`iqcNPR#;>AFIKTECTUJCIJBB@O8#1IE!(;1104/0E9558/8:86@LF5.>=OOKMHDJ?DJJ?BLZUVLOIKRSQU\YNE>4AD@>=7>5=?@9I<3B99?69:>93;6>7-4+206088=EBBIG?.=G>@v02ITj$qDOk+4Q55M$.G 6O&Ic9+G>Y/'B0- ":0%/  $ $4!32H&'@'> + 2/-#4&6!6!1,9 12)$1C%!70, /#0%5  #!/'; 8?X,(C&#"+(*, 62 0d{pd{ "-:4*[F]#H\"Og-=T6L6I 2K;T ?W"#=6PRn31S_19X Ec8V'D)I/N<!= #=6S,I?[%4. 4CZA]D_QnDd=\C`He!B^7K +K4,=N)5J'?+B 7%;+0 &6)%6- +#"9 1J\u5Lhb3]}/;Z5Q9SHd Wq'1K9N0&<`w1Mf!;V /K.0M Vr%G^4(BEd-H4P 7 (7#CAd"-N)  + !7)3*G1N'@& #9 %= .2NB_#%>%<:J 53N Jg'& />"/E ;7U"A7V5 < : #B 9V3 + $"4',*@."<6#= 4O(,B6!< Dc#;X*J*G?\8R]|56Q/"   -.B 1 '-%+A5)G >Q5I r/@ &8 ;K!%6 dyB)A13L)>-EpJ  #&+!! ! !(7 $ , #0" $!. + . +(4HYs1}LG\&7J`p.JY)8 :A-4/:$1-:,8-6*25C/=8E 5CWf er*AI{EOV$QY%GU3C-?"4.> 1"40C?N 6E$'[j:-".!) +'0= +5F(; #6nChCW6F 6F5G -B;R :V+A*>9I/> %2,BVok7cz+8Pg"+cw8e}5;.ZT9J6GUn Yq #:F-$,->&9*7/A + +(7(!1-504  ( !)1 +Uf"e|"}C):' /5%"     !#"  +"&"% $  ("($/",(% " +#/(' +!- $ *3"+ ", & % & ",".3knWaeHY`?ciGCI%MT2!7B (4+69E+6( ++5( IP/q}{Rep7nmp}KepDryMBH>AY[/ip7cLjv+AS*@2B 2= 8C#+X_9V]5V_3OY-GP"15gpJAK%&1irLLU*n{MzTehvEZm/^:r&>SA;y&}(l:,+nd y6'+z#v qn(/Y%=TdPG`q73#' +  +! #0$1!.&,58)12%35(?C8ywP[R?K<$,%/)27>5?7B7D3@9G=JES#CQ ^j:P[,5B_m:JY'IZ-K\,Pa-Rb1kxBOt{#aApy +u +aesƳtz]h3]U@2YAJGEDMPD8A=<==NL;/+<0-(8;6HN6C;8BB;9@BF>BGGNE?@G6>2;==9AKNSQEPIQTKOS[WPK>:OMI@PKI5%)HGC29::=67;996+<<=MIGAAOSLLZNNYIHKMCHMJKHGFGFPEIKJDCFBF?CCILG=0>/<79<;BE;B>6540267:<9..06=2=CAJ8845?A;A;7@:ABNC@<95:972"6, (/&, % +#5 $5"91+@ ?Q7. .;'# ! + -0' #5) 2&+ / 5$(<0. -  $ +(+ , $4 .(,& &) +48Q)Id8#='*6O'(16 >V0#</ ) & ,+''  *#,&%0)&6!4 ."2#1-7F"&6 /nCWi3#7 (<6 (D%>>T0)9% /: %  + $# 2*> ,++'*,:!1$ -A#/F1F!-B$:1#:'@5 Ph73L7N)C +4:T-E -E +EOe40F/E6@ZLf&Vn.Zv0Zv0Pl)]x4D]-C %: ':.0 -AavI8M79815:<65824=DA585-334=;737/610/+,680<50*$)-,$! 0'%'%/3932@29/.,1<28=828=-,%!(x"7{(o#gq-k)j/Vv!MnPp`}(g+n-a%X}PwUxEiUw%^|,[z.`3Zw-Wt+Zw-^z0a|5KeQo)f=Kj&Vx4Ss3Hg)C_&Ec)Ur8Ie-@Z$/L>[#Db&Lj+Qp/Db%AY =SDX#Pe/BU"FV*7H';I_)7Q(B7/N Rr,Df A_5Q3O#>6S>[5R/K0M9UF^* 7 #7 +4AXUs(;ZSm&Sp# =Mg(Uo:*B- &Qk:7+B'9(EU,-.6 +$<)G $ETu46 2 60L6N$&(;Q''> #?"= +53$>)B 8!8'@-I*I +$E"A.. 7";6M$7.. *EZ%6J'<5I@R"" 2A+.:Q!)B +(E;Z0P+MMmWw,0K8V?[)E_y$a~,4Rm9d|H +ab|84M*B3H +E_Kh(KJj;[ ;X_vB  "07E6H3Fe|.BX 2HZq,'4 CO&Xd<-:HU-tYXdaKH֙uɑ`k; %,#+! EP8FFP-/8 &HO'_iC>KO\3fvNes@DBt0v-u*q$RjpPD07@1AN;,9&%)4!AI;36(-.!IJ<-1%ah`PXNK5ZeP&/.7!,7!:E3p~o=L:0;&&-.1?C0X]JY_H,6'1BM+4?4@/:0:6A6BBP!4B;ICOAMBNQ]-M[*DQP^%5C GW&Tf3Vf2Xf,ny6K[ADG6@SbھǡǀTyDJT6goZ|z[kY5C%dgMaj:iuN`lLagL:;Ǔޝgk@¯ƭ٭pLpm}@T5K@`|7'8FWg}lPiGcJ]_g +s{{85^fV[tZjkgZvsizfy`XS_Z^ezA\F5%?Dnd?V\6h8(A1:P5K?S?D?CUJRMB8DH;4:D7+2105351&<3.@8HBSWQAB0/9=@ABFC=7<5/88AGU=ILNNLHI:;94>7>:42#%%-509202586?AE=B@2='<DZ;%:"7)2'=.4J,(B!#7# *)?!!7 2+9$   +  +$ +6 ",*% $5=M//?$22+?",& %& ! (" ' $ !?M%qLDREW-? 25 (D,;Q. ' )6 *6 (&#5)< ) %! " ,9(5+ "-0F%:$9./4(@/E@U)3)A +6M1H7N*@ %=*C *?9J/E!9=W 8H%9 4J;RFa!b~7Jc *@);:PQf,Zr+RiVl3 5%/:/(+579><<@7>B?>7:76=:83?@<4B?703+72(A;03255598>98*(((.,.7161@$- /'9-7&0&&3B/1-4=2,#629:($&3.02rr#w.m(l*i(YzUwWy"`1Xw.Vv+f:a2^-l7Y{&U{$W}'[.Sy(\1Zz/_}4f;Yx.b7d7^{1_|4Vt,Us,Mm(Qs,Vw2Ss0Ij*Rp6b}DFa'Je.?Z$7U7TUs8Nl/Mk*C_"D\$@V"DW%;M?Q,>AR$Nb-7KG` Zu1a}:`}Z 5P9>[&>\"?Z#%= +&9 +.$91IJd%6U7S9U>Z,H/K >W=VKc%-AW&-F8XB[)=1=\$=40Q.K0) / ,Mk&66 '  +D0J & #EZ+%=49 +!> #@$2*(,;%1/=7F%ZjG +[s<6/L6Y,K 7 + <+CMc3 !'CX. $#>Y0KB`@\.Ie2MF` 80J 5P (EvGC`1G +!9Sv_'B>XGa8UXv'Lh6S;W;S% mY * )Sd2bu0,A5K =SCX9NH^>TBV.A 2=RvP/ "*'9)< +5 1 7 +C- #) (8)!),4H-C Qg+oCkBpR )+("9!5-DIZv?JT%2;F0< +)42=N['$-*6&3"0 .0;7A2:*9,8) /4F(8 "2 ,9!2 #,  . *8+;0@$0<!2.A 8O /D}SN]"092?AQ>P,B3I '<)<4F"4 "4/@ w&v&Qff~Sgew0?#5G9O{)/%)t~ 4+BGS&1:?19?I&0 ")M\o+v47HuAE^jK !"     !"'0(1$  %%*  )!**6CP*P]5>K!7EZ_qISa+zRgw@3B >NFT 8F8GGSFNQX.OT7pyJ_i#7=2BKDLZK#0##-X_R9?1#&LN@8:-ZaVS[O2:)# 8A(XeO+8"(50=%P]D5@%&00: IS;J8C>GS]*GU K\&\k0JYJVQ^)vO~Ugq8G9gt '?$?IA6Ѥnd^Zv^`hVacVem^NXFJS0^JjMdn.nwT\`LTT/Q鎇Fv~\ºŷݺ֮޾ݼ۽НfLJGZmFfUYjz!O`lSfj|ar \ilr=AYqoMUha\j]OatU]ZTG_SQP[mXTYbcUhINZkn\DVTRW7<=JEGB7I@>EMB?H@CB1-35KAFC-0191614<4CC'?:9A):5?/%?@6?30!.5:/(.6/57FE0@6B<=4MDCJA84GBBB485@BLHMIC>FCEKNINKI>>H=EK=ACIF<@@ECCEKEPQHFK<1<;G1282100/388>;8??9B67D5C7;D>0(0:=8C@CA:68H;IG466;K421/38@?NTAUm Rl7P 6MJc'Ph/=V9TJf,F`+8T")D9T(7 8573,0D%2G&(>70 +!<!=- 3% #3" +     # +<$& ((! * / % $ +('+ -&"*# +)!& *lHX3% / / 1 + :.+#0- 9L,&9$9'>*D #!92/.# +  &%1 )  NX1(7*!/ +5F(+ +* $0 +(6&7?Q 3)>"9)@Ph2Qi5D[#)Af~De); +#4&: 3E.D3K:R[r6=O0B2;PRg*\s.E[ Ti @HP..&//5;5>:AD57=:=54717782876::?7B9;8+()./;8:0*/1'&,,2;7;>86&*=43335?:9=?337.--21+$6&+*.16&,ypx+m)m,f(g-j1n7Wy$a3_4]~5Uw/Rs(e] Id(Pl.`w>^w@Ri6M`-FZ%[n:FY'=P ;QAW[r2Sm+E_0J 3M'A-G (B-G :S7S!<9&A5&> % +!dxAC["B[ 8P7R.J/J6S+C #: !,EX50 ).B*<0$5- + +(6) +"$< ?Q#);+< 0ADV'-C"9 +/;Q*Ng6nMGa!)D)A:Q+B14*C by@-F $<3&>,'D8g0.H 7 2G.E&7P^{@: +&B]&2O'D=uU5W=1P.K.H4)(D 8 + Ne; F^,2MB^#2J9Ut]"9 +  ,-FzW@Y(,  0"2 2I#0/ 7$=((/8Mj40M,F:L*fxT  +!-) 6D& 1E2J3Q2P^z=\v7=P&$3,M\5Л ("%'D@],43 1+$;%$; Ic& ;=Y9UE_',(C /F/  ,D4L$=4G"6#!@O'[n:0E +%6"4 +',0)EXr%I'>0K.G6Q 9X2O >X+CGd#BeLoDdQI]"DZ3,D 5L#::O:OI]'<0G Nf%qH6N*$90 .2F '>:P * %4* +5I$Wl@Mb2BW"&<;QCX';Sl-)Cd9Um7$2# , 1-7IMb'K^ Qa*(7+7!1)(8,7J1$3 #3 ##)%CT*>O(#+*#. +"*9EU#(%:1'7F1;+8AQ:L1G 1GBW(=(: +(: /"5):.D &?Nd.D +]r!p'MaEZQjOe>Ut+Vi]ms/f|h{|)U_#}R^ fs$2#Epw9F ]j4J]Vof~&:MXmazdZv"      ! +  $ )v~GGr:0D0C +ax6fv1wBWgmbfqΔhd6Rm5^|$k,e /.1wA1Ew|!<~ 4_{bzL."(.=q!I]s02>@K6^iR?G2OTMv~{ļANC6@5T[O,2$KN?5> =F,?G08>(@B/-/ CF88=+07!1;0;/8.7 AJFR IU!]p:OSRe\YQQWeQZX\bgTWa`WcRM[VNKM8:6>>?BDE:1.<*9187;4F*5>6D25>;76@D=5;96LENC>=G>@72707C6AIEFF>=<75<<5?+6,8B@JG>D/:893B=8ABC75;8:BFD;9B5G3=E;=76<)0*32*401 /XoF`7P*  21+B* 4":,@#) !/(7."0+8 LT0`o}R+9"-9%2)?M CT#FW&SF]KaSj} /!##,9&7J81?4=7482A:4>9:74/7:979.+6;9<5$#0+)00(02,*16243;.?@5ABF9<3')85D36@=-'74/6.61#&'22,t"t%k&o/d)l4d/a.Vx#g6Y{*Yy-Mm$Rs.Qr,Nq'a8Tu)Su*Uw+Kp$Ls&Qx+Z2X~1Su,Pu(Mq$HlFkNr&Or(Os)Mq&Jj#Bcb;Km%<[Qo-A]If(Mj)Ok,Li*Qn.Ca!Ge&8UTp3[s:^w>Qh3Wl6Pf/J^&DX%8M2H Oe&DZ@V=T3J /E 1H9S:R'@3KKd06&A0#:iN[r6@W;TE_ 6(E2K%> "='B 7N!%   "2BT..2 -B+ +.'  +! +&65H#0D;Q&0 1C0B*=AU+'= 7 &> Gb Fb?ZUpB]>X0G%@&Ai@0M d;?\*DIb-/F.@ +n=`a%4T +,-D $8";2N$B3-.JGa&&?@[ )@]&(D38WQl4D_'Kh,Gf(?]!:W$40H= 05K1K:Eb-9Eb,5*H <[1 3 2D=M,1@$ /GKc6"$*9 !2  +Rn9&A$&@(>* +%$A 7!9 + 1 + !  !#.+F52 &= xW% + +#=E1, %'/ +'22 *6 + 6 &A4M1+&;Ne8/0L3J("$f|HlD0  #3Gez81JZq; 6[q<#80( h{>Ws%D^?Y5O3L)E/G+I +Ph-F_Om&Ab5Y9Y Pl!Vq$&A#<1bx=1LNe.I`1Pk.5L "6H&7+?8M'<4H) 7 ayB&? 9O7O@T3&? F`!*D"8 +&*(/E!1 - %5 -3)B +$= Gb"/G Mc5MGa5P -IiSXi1%1); . %6*#4 1 +/0A+&# 3,>(!(3%5 + / + /?Wk,,A (<JWGQ@P J\2D2G Ng&/D.D(:#44E/1C+C,D1/ 4ILf +\sLbGa.HGaaxQe :46gy {3 |!=kz gx @&,l{: ;I&Rb6*@(@\t$2H6K +CWH` =YX,@!    -6.8XdDLV?S]ACO4>K-#1 TbAT^<1:6@LX,`mIw}h2H 9&^kTJc%>O,O[HOYFJR>BF>w~~w| 3=2IQG?F;>C7@F6:A,.7#+5/9.7?H+HQ8L[>3C%,:IV:>K,.8?I&:C!?G'8=%),BE4&(cjZ&05?2;T\44=GQ ALN]&AT3C1=9F4@DO#AJ?G@GPZ#JWOZZeUd]m)O_ \i.Yw+z49DE6BUSSvӣ~BG]fucbo[mtezt9K:=Pdl^nuMY8;񞟦t\xڛfՕڗaRTR:2WR[iyy$MJkBJlSB=ZADJ@?OCAED7C*7EXLLVTMPWQVSPQMZ\LGTLGKADCA35855+?GAELKO>:5=B79/2:=.;(A9?;:E92//E*:5:B:./28G:=CJTRXNILHQRI<<7/5121-6;F:4M),/;7657;=6;;GD?GAN?G>8?6;<>LHCAC>@BFJMCA>9FB=AEA6-M<95+4<:>EKNH?@>=7?H=+*$70+4*$ %o\r!E^!G`(=YFc'Zx;EU14H',C%?1J#31*>WmI& #"71 #;!9 $3!4BQ:%yf$ ' 3?* + $.O\A!.'_lEZi;8H+>#6 &9 $54 ":+E"20A(& #2%5.D+D,2,F"&@ 3 & + "05B'!.'$7&=+A1D#+ $2 2 7K?SI`KbF\)?-D&: +>OK^&?ULe#c|:Zo3@R$6H[*?R2i}G;O+?*=/F 1?W;T"BS(,>#5 +N_4(:/3D+;4F-A3GNc)F]F] Sk;56/AA9P71-.!+A24(004/-/2618>89/5,>45+,%&'#0-)25543<9879:E:88;B4881A48324,$0*r z*x%.130}"(>{"15|.`Xwi&k*m0]$j5h4Wy#h6e6[|-`2`3Vu+a9Rt)Np$Tv)Y{/Tv*Pr$FlIn Nt'In!Lo#Uw*Or%Nr%Qx*Ms&Ls%Os*Sw-Fk Hi"Gh"Yz5Op+Po+Mk(Qm,Li&Ro+Lj&Pm(Ol)Qn+D_Oj)Kc%Mh,Jc*Nd.Rj0Kb)Oe,?U0D:OLa&K`#Nc!3J;P5J EY 6B[('>!8 6 +$=%='*AE^JfEaOm&:W9S5P0L+GUm>8I)$!,=& 5K*B3L2N=X85O,I]x#k1e%f's:JiKk*I3Q0I +B +? +uGvB1Nm4*I)J.K +;T0HJdg<7:YYs='/+C3/$@:A^"7U*F ?Z"8A_ Nm0*D(> =Y1I   +  ";7X(; :*#9)?,@Sg8I[/ %1?& Da/Gd0%%7#!9 3F8K|]D\1K*B-*B0I4M " +$,A>V ;%?a}69$=(D ]z>"; + 4,IEe =Eb#Hb\{3Un.'? +4/I +402.!8 +(?"&.?,@ )@ $5 .(?7$< ')A]u81IJc'[u3&* 5 @R2*B1 "7 +BW0$"(>2 +%4)A 5NjG3": F[J`"2/0K 2ar.]i*Ze)L.B6K &75F+0;$%5, ,:+ 0 -:Ud.qDZl,EU0>$216F 0GX=R6MF\dx1Qb3AGT^m,4D3E +)BboBjwL>K!)1!O\1Xf;Yi=Ud:>K%hrK>I!HS+kxL4A(5 +S^9?K#PmoI/~x~$hbC3X]VJULXcW&."4G#9BIP.CJ+38FI5!X_N8B*7@!L'.DLIPYQaWZXPOPECF7=9=3?<7=92571<+LB?)-+4+3.J??@E@48+ML-=@>H@69/33'+/'588;C;:4;:CE?B>;FC699EC?FEAKGF=A;GCD>17766BBCBAD??DEBJ?H?GF=@N:DH7Q/1/=(;DFD:@;C;FJFEFBBEN>:6>:5.4;;%-,&%+23QK[oLaG_&C^&5Q>\%5SKj5Ec2-J+F/I$=8(B+ +( +22,A!Ia?(A1 /K&(D</N(Da:=Y0-BY4*"1*-(  '$2  $$0! +"1* &7"1 /!%"-/%) "+ +)$3 /+ (78H(145%, $(3>T.!8+B31J*#9' %-  DO7+!.4D%+8/:" &02= + + ,% (5+& * '5%4 '8 3D/A,>0? 0 %8&;2E'#/)=!7*C!:(";AZ3;P3" %" .*%4 )+ &75.&, .?3G9O&9 4G5L D[ +?]wz.m%5-.#(r ." !&*2/,+(/,297434361-)8;<09.-..55@8/76152(;><3<99971/;BB=:7<0*-w&~2jg w3{7{5s+o%m"z,u't*o%p*j'SuUva$h,c(^'Z{&c/](g4`0g:f7k:`0c4\,Z}*_.Z~*Ps!Tx$Os X|)Qt$Ns"qE^2IiSs'Qt'W{.FiLo#EiFk Tt.AbIj$Ut/Po)Ih$Lj&Xx1Pn%Qo(Fe;YHcPi$Vo.Zq0Rk*Sk.Jd(Sm/Wr5Yq5Sk2E[&Nd.3I '?!8 /,B#9./>WNjNl;Y]3O3O5QUn=+  -B&< /$9 .C':4. ,BTk?;T"3L5 #-A 4)BS)/A'1 $: (? E]%Z g8@+J1J 4M";.Ri$JbHa )C'ACck9Dd(B.I2N@`#C@a3-E /G%'& +"Ha/iP/ > >\& /D#^wE8W4T&D>YLd,9P 5 +(;/+BHa&AN6+, +)@#A"=*D#6  : ! +'>-Sj46N / , TcF +   + + '2+,@'4'?4!3 ",[p:CY%Ph!54N'@8.H 1G#8 0DUj.=T%<?YtM.)C +Pk0If+%;'"=Uq4$A 8>\2I728P#=&? : 5 %DZ- .(9-D. ,&=!; 7P)E0H/:R2 Oh#..9P.3J7O)@ )<#73F$/ +,+#:1H4L47'#< qF1E.B*@Ng+)>pB;LNb(GZ &G[ +%6'5)!(!6A"$  *;`n1bq3CT;J +9I-<IXZi$AT +>Sf NaCXUmNeG^Nd]q#Qa?M +,::J#7&:3L,D +6J1 ++!# + &%<4E" 1J`5K9M 7OWn'#;CX5K;QJ_ I[L]QadttG:@OWe+?,>&8 57O +Tm@Yv 2-? ?V:Q C^ +AVo72K#9[p%Ia/D +:OHW"BP=KP\+~co@uQ^k8rNhxDhvBgtCLZ*}Q_-(7L[+LX*0<$ #/ -:+6 N]6Vg>^l?[i3rayԕŅd|EtfsrWsLVpYdyFdoq{LyKҞptvQNW.o|Tmbҫǿ}nalThr\XCK}/Dj]l46E;B*=E$IM)GP'NX+NY)MZ'HT!FRFN@G:D=F;DKS'HRGRP]erC]9LZLh|(^3<\82;?@Qzq`5FY}|bwlzg5-#LCbm/Uy27N?䇼mL[hILS=EZPRNTM\C7E6>CMSFD@I;9*9<9D4SM?A?ADFFIPVVRKTLJ>@=./*)/427./KD67:4C747:=22IL==>3HF=C=F9BJ?;DD8182E55G8@7B30342;G7=:;6:;;5JMCEES><>EHODC<=B>?EE@@9=?8?@@GPDBA@D;E8@BGH=AI@@@@EIEFFI:98BFCKUPI@=1I87=EJ?DE?=B:189B:??>AD?:OF@A@?>EPPKH?:LIH@81;3)G8*LJUiH^3KC[(1K,G*F*G+G.I".G$ :'A&>0H&'1D())< 8)A*B .780!;*E:W-8P*, *+ :$> 7M2' '$ +!-&$ /- 0 "6/B #5 & 0( ) 0( #50& ' # # ' +#)<;P/'<!1C%+=$0E!QfB#72 /& + *"!  "2/> ,$0+$3 .-,;&   +! +)"!1#40A0A )7"1-- SfG +(- 3-Og=C]-"< :'@)@-# # *$ +( +"% +) !5#8&<*?$<'@ Kb/D[j;ug}6 6/C2H8M1F Ja%>X1I4G/?4C. +("3!3 0"3"5)>4KRk/az:hCW")$6 Ti5BU!+,<1B"0 ,=(:*>0C6Mc|!Oy1Tn +Uqux"/&9J2(-00#(,#!,),*-878,-07(&(+*+:6/35=757B=<1;932)+*7738;)'154-{*8:7~/{-9w/x3s0a"c&n6d.n6j1f+j-e&e&d%g)f+r5d(k/Eo2`&OpUv]~'\~$Tv Vv#KiIfQre/Y}#a+X|"QuXz#a,X{#Nqb/])\}*Xz*Y{+]|1Vu)Ml Nn"Lm!6W Jm"Mp'St+@_Cb@_St*Kj!e;Yy-HfLk?]EdNi#\t1[r-Yo)g;Tm)]x5Vp-Rl+F`#Og-7M3K)@(@ +8PNi$E_AZ-G #:6/G$>#<(?"6*8 +fCVs,Pn![z->]?] 4Q8T8R(  " ;R$40(:0 ,A0 +0F]-(-D0F+>' # +)6%4!4Of5Si:7L:R]t9:T6O'>)B]Vse8UVw Zy&D0Q'H9U@Z3JPi%cz:,Fb'NlXr(^y.r;]{$Yz%Us!@a;[ 0Q=]Dd6S5":)@"7 +0#9% " + Md7oW'B$@>ZNi39'A#?Hg%2Q #@8UGd'2J @]*E_, +!!#6 !Jc2A[ A\+G 57 +Mh2)G +'(7Qd}C-D  & /[k+Wi+>Q+<Wj':IK\\n!NeUjf|-_w&Ld:PKb6L8M2HO^2A /);6I1D=V0+,##HW67K+@4J 0D'<Uk(Mc 4I*8I F[gy%I[J\ J_q@3'=)pFSm$Qk@_br8\r'&>+B|~.`dk&dhZk3k|J+;crM3B=)(,6t%z/656u)9`3m"G}Db*Uv'Ut+n={BHr3I1QEa)B7STq):Y Qn\w1p;'e qy#/,'+)<$!S>V*<BQ*=L(BL+'2"+' +,4.43:!7@%/8/7BL/1<6D;GGR+BL'8C8C5@3<0<6A4?9BNV-:?=B@*DF2GJ4=@$IK+X\9NT,KV)R`-DRG>GKX!BO^z@Cv'Fu%y+JB:3+>*=(N0Ew4#,TjkzH}hpmqdzQ`qo| +,%5?PcCM*!%v}!X0g`YbPdZHaPOMYU[OQQIQU:KRDS79EJ16-LVEM7GA3CDFIOIBD>6G?94.2:1?.J@>=9?D:A<=(83BJDED;;?D9J>IG6D7>A=ABAEGHIA?KFEBHKJBJ@CBQPQLHOPJICAEEI?9>?GC?9B>>HECME>>89H<=>EAAKICK@MJDJ>EEA>MN>3:ICHG@?AH;@>B:BKGCNA0@@E:4<9,30Y*,H1N5P"9'?5+B1I',E$#91# +/+E!)C ":51'1/C"$?$@.F !5114"@!+  .&  + +  +"! -53%=*@- +%2& ' 2/) $92/) +*& ++%+ 1E$.A")#2(7%- (+ );.C%' " )&  + " "31D&"5$5, 1 1-$ #   +$*+#0 .!2' +& +" 0 0) 0;P,2 /-,1 0JEb11K1L:Q$4"@K/*88G%"+/ -C+B;X&6R!+E*B55J 4H@U4H[*>T 5@Ve~CMg-:S';! ( +*Kd"{K6O&%8$9.A 4IXh3$3.;!0 ->-3F=QPeFC\vr(e{%FO43G>25698625879841*+,)*&//7*+*0)$>;68%0.30@/447000+/422: ()~+|0|/s)dr.m)x6x6l)y7g'q/m.k0t9_'`/PrTt"[z([})\'m8l5c)d,d+m3d)f)w9m.}D_~%`*n9v?TuQqPoLhKka*b'i.]!h0Ux]%RvOqIjYz%\|*c2^}0`{1Uq']z/Tr&Qq%HhPq&Z{1CdZz/Jj Wv+`3Tt&Oq Pr Tt#Ss&HfD_Kf$[t0Uk#`w.Ql"Xr(Xt-Ql'Uo+Tn.Jc)=S4N/I4B^"Gd 7U9P":9S#"=8,'+#> <4R ?`0O"@&C +..M*DH_4* +C\"/FLc0J^11&!7AW,+>4I!Zo>=Q)1(   +BT$/G4L,B#: 25 7 !#cyA])jMe +J +0.Ge+ "%&Ia-1J: +!; 5QJf0:V#38*@&5  +&5+9brQ6D*   "   +  byMCW0 +   EY5 ".-K< 1+-+*7      %;:U)J #B-L]4 %80D-!2H ,BK^ 6O+D;R 42#6FZ, !  +!1 5F&71B 5L 5N >XF^ &yY   +HW3&$:+F,@BBR2B7H):34$?/G`)H],"93K'>D[KDX3CW4E 6HM^=M4D?Q!&= 3L.,A !'+B,D $*A 2.(BR!&8+?@T$3K^-*= +$:AV' 1 &#)&:E[Ib Rjbx4[r\p[Wd-_dqV{KZm~tףtrdxFS`&%"  @J$0=8F}QGVak)Ytz:LQ(OX py;wpxzHl]ެդpQT7suTvw]}plb +&&)­ vf~blx*1.|WB>Vpx=n|6i{3ؐ@TqJ5I Md )Xrlm#7x2n)_zDz4M3fYyNrJle5Ut,Pn(Zz-Zz$d,Ug5Om"Xt2Kd)kITp-ZUsB]g"\v CLlD`,%e'!. %*<D7Jb:KDQ()5-77C"4>5??H&(1%,2<7@!3?@H%KR/7@8@;C#4>2<3;:B:CGP%EO#=!:: SV;TW=<@ IR.Xc:r}RP^.=GR.93;<63:=;23<<4=4=6=B98.-1#$8.#*2<'/3;<97-43949A@C>AACEKK?GD>GTGAKHRINHIIK@IHGH;FE6NEEC>:CD;9:DMUEMFBBJPBBDHIOD6:7<:C=C>7@+1:@F:JBH@A<=DGME:?;6BC;C;9IPL>G@(Zq8]v33L0H.G'A-H8T$n/a#o4Z}r7l0U|c(s7i/Y{$[{%Qps>q``5ImQs!LoMn e7Nfn>I_k8wBYq$Vo&Wq)Zv,d;Qm*B]Jd%9V,G;WKd(5P>Z >; &@ +E)@%=. 3 ,/!??]=\Vs43O0K*E9 4: 8Q8O:Q!/!9 To<8Q1G)> !3 -(%-?&:,=M&$4( + #4(=5%; "8 5"3+' % + "1JVs!PmFc$A6S3P4R*H)G>^BdX>`;]0PGb:WG`7U +9X DgSx-Bb/M+HVq(A`!=!; 9 $$&\w6/M;X.K +(A &@ &E %BIi-4P3/=0= + + +    N`54L(E5V+C&3L5J 4K*@/E6L +%< 3C0]e*.@*A(A +@=N$'30 3 &3+>9L2H >R/A 0(6)@ 5K/FZ#?Qp:;J 5?_CN.6 EO>O 5H 1I@X#;+> ?R DX$@P'-<3K 29H$, , /(;30E 3F4I +>T +4D+< +000F&9+ $$!--E +;NCW>Q6KBW=Q +7I@T/D*>*A&<4Ld J_ *?.&: #:Me2*B)3 '<0*?.DC]PkXXpJbFYGU jhu?4J 1GVnv8Ga"+MV"`h2Pnƕ򼲿nŏ\Z_nؘؕnYjg|`fo>`i9rzM8E%.5B#- 9@X^?pnavrm۾@WH +͠ ^HjޓvwTZA}_fX=MR]GWg DKiI8PNhA|kj"^wbzA[sXpTmZt^xb}Ro +LkSb-Nm 9WMk'?\8BG#@G CL!EN"JR&CK!>B:=IM+BE$BE(VZ>UBLJJJH8<81A=?G-8?7/'DE;<2/+.037;-+4(-:299E6??F@C?;I8=7:4.=EE-406::7CB:9>3926788C-7=:IKIGAIDK@>=?DAK@PMJELFA?CRK=;9@CEEG>01::?=69:?BL@FBH33;<<>AA7BCF93D97<@CCER?F<9@9:B=C<079997><8KC?.2%:@wO2-' $ $6.!3->$5$5 /  +  +"#   (- /!11-@$ & (> + . 6I&3 4 :Q+0 +[pI=T-2'%4,% '.<!/ . 4/ !8,D7Q!@X(9Q 0F4G'9DS%3Dbr3nBPd&CV!'fyP(9->"0%5/?+;,</ 7J4FBV$%9 +9N0F .I=XPj:U j65JXl*Ug+ew=&6); K]*CZ!Nf&RlT15IJ^*4{3Siy7 DL6>:975538=*21*(%&&,1289J=5/4:?I=;>:-51.,,1%'321+/'<-u(m$h!e$k+h)d~&c}(g+n5r8i/r7o5r=TvSw#=\[z,j=b5Zy*]~)a,i1f*b#l+x6~9n+o+k)i$p1l0Fw:j-w8u7v3&B>V*B.KHc1+F3 8!89J&!1G+@ +Yj>'kt` 8G1,<! +@P.$*9* +) )2'/L'@ )<&       2?',-:P!,E 'C3P:W8Nb/C$91H"<21&?+B";1C + )% ! &9) 0$8 #=+G2K-  # - +*:!0$9 +5K/D\k[j,  '6(9"82&? &@ G^)+C !;jB2I +(=RG]!9PJP-5Rls#MX;I,;>R#6,=  +)Zl)Zo Sg&6J6J 4J ++<,$60Dav4NdCY +J[ Q^=H BOdPP0T]m)-$:1%; $9 +11CW ;O7,>($ * +?T"7)< ,2ECV*<0KX5C"//;4?$5,&.C- $3 ) +/>)*/G,C0C )=+>.BAT8O 2H ++?2F +@+@AV.EPg(vISf&$%4G1F 3!8!:)A _s;0C-C7L ##< OeF\O`0)3A+>9M$:":CY-A +    ;D`iE!_]Ub*Z[R_&^l1\k;q|rz]~մΝ,'_|E)7cxti (}0@`z)<DQ3A2> *5&2>K05,2G#kvTȠ^q3}>{'* ':}gq<|z>@=#,z ID,'J%I}Ď迼šݲ͠čӌjYOWYeFbUXROSFR=39:DGQCE>JP@@=67,6:,'5*/(-!%//+)-6(&,/./13,/9<24/>.93:3;833635;92E=:6BBA;D94:43.2;-:<;C?5(; 7*,25,"')679.&3;59;A=I7GHDKLILOFLDKO@SCIJC<>F>K98GF?@>A8:<9?A>-=AEB@C;FC38C:5)*/(*;6=9?4E>4;:93>F?<7<3;,;66>B=B5:3+==H$!np%b{#t5)1*> .( 2.&: +% .'>2J() , 3#4(<0"8.  +     !!  %45I' 3'-+ %+ ?M, 2 #!1 (8"5,# #)&+ %&%/ + ) ( !4/D!5+ #2 +    )7E3!-( +! $2* ) !2)7%3!/ + &%'  ! +$2*+&  .?(+;"/."#;/G!!7Ka;O`A + $&-;AO+,'6+ +/ *>"90HDY.Sg9 M^2/A#2/;Ra/CT% %6  '*-. ->'8 '6 $4 9HFU);P1G/F&>Jand{36Nh5.EAV Yn'#6I]-@9M7QC\Ys(JbYsKai ~.I=h k|7}~:H35;/-8B1 "%$&5*5;5.8147?L?G;9&+22+/$$703~,|,D0w*q'u,n%j$a|h'}q+s0p0i.Fj a#g+_$d)c(o4Yy [y#Tro,u4=v5j,f,t;h1[{(QsX{$NqMqRs"No_~'^|(GcTnd{)l0o.\sg)k,\w h,j0Yt"[x)Tt&`2Iji:HgQp Uo$6U,I .L4O(C,5$= +!3 + +&  9N$6K0,A.C, #DP$dt0.@n};6I &=0I;Y.L `3b)Im\|,Gf0L;X =WC\$=.F 8R#B2T Uw*3U $BFb%!< 28W'F/O*H A`&1M'A :W@^Ts,gE7R +9P!Jc/=T%0*A 0*0K.KHg(@a.P +2'G$CPn3Pl0) 4 +'E ++J2 +!> A!@ 3'C)@"  +  +$ +  -'8!6- Oc>!3%4-> "( )?0*E&< +&3    +  '8 3 )3G6O7O.%< +02J.G 74N)BUn+1H2I/0"<1J Wk1  !%3:J* - >R -E,! +$2C! 0 +)/A^j=$  +/ +*??U%.F-!:tS=WqGmE!&8Kdv/ds^kZ:COEJcv%59I5I CU!CM(Rf,Y\q*>N`#M` @U0Acq/M]!!0@OHZJ_5KTi OcLZCL$ao-_k+LHfvUe(=)<#83,@ 4-A3H0)8 (2 DO&$% +(F\6L $:G\-@$7I]#BV,; 7H,=+< 1@ 0 $4 =M.? '6 +4@(7 . * (6$!1 0 3 +#< %; 5&)=18N>T#;9N4I,A /3F3G *?"1Qe _r-*< , +4G!2K5M(&:1E5HL]%) 4I1B (!) 3; 2CBRk|;9Nv?^wKWsɑ͕stڣll@F =Cchr;)2PT 23be-]ir;_k9GS"@I/8U_*wO_sut~B\FS@M0=&1 '1>F2#- KX;O[@4A&FR79B,iq`ȸ&.,xwýܲѝNH[CPUgyk dhMD@9򇙜\mj_i^W]All9no1{9Uv-0C2$Xv{!&t=Ym$c}!F]Oe\q)Zr)Ga{C|99JCMnAbMm>] 9V0M9Fb#?];Y"?6R.EYnBCX=Sv889w:($Qq6Zt eWlh $?q&0}NFW!2Ue!r7v=$33?6>*2/61806:@#;A"16-2KN5UX?.6EN.>/1)1/%,3>*',* !'2#('0"97-.*+604*%*+/778+=4<<<:I??;7?;8AKGFAA-222+..+5>ID>3>EAPGGFAFRQBECIN?EC^S90<5<:09@S66F<77C214);IEFT,XpMdqAh~<3J7P3K.E2H 6/-"7(=3F&1(:7M.&:10)8..*@4 :$:-A% #5(; " +# +# +     +  %   (->* ) +&#%2 .-;)-9=I1&3"%4"1-)'  " + *3!2;' +-;!* EW5,B.-F( 3  +    +&3!) $,8F-'6+;:I), $% ! "#!  )! ! ++,! -;%$# -A!65 9 5O&*?$/?*9"0(+!2 -=.!,+* &#58I#7F >M'#2 ,(*6((70>)71@(8(%4 *; ,>%6%7HX,=N!%; Vn:Tk54KF[.D3J:P d}>2J 5JiPCWEX Ti18Q9SGb_z)f%=R?SD3QOl+:l\sEB3)'67!3'(/1)4$0/:,233.1(6(0143157%+<;|-*(@y'5w,x,>o%x/i#l)@>o.g&q/v2r-n,k+p2u7Yw^{ q5d+k0Dw8r1n-v2j&o(_~p$p#hp!p&im$p&u.h!E|3fy,ls'q&q"b~l }7aPu \g%c!c"Os~?Po`~$l0p(p+C>o*j&KYu]~#a([}%BdKki5{B[vq9|>|@fz$o*G\s]xm/`z!f%ZtRiQpOpWy$LoKnVvc.A_<[3P">;'C]x=9P+D =X!&B =Y">4@V)+? ( +'7E^-/H103 /7 &8 .H3(>Md9,B 8#<,@+ $ $0#   7 8M!+* ( 0F3D!,<+44<sMYco.n{>#6.H1 'Ol+@[rA8>W!Lg&'?C[%0 +"87'A:Y4U3V$E.M ;)E6T @)N 9\3T#B 1Sq/`92:X9 + .>/13&BRn;9T3Q"BBaEf!Ww6+H8V'0J % &+L<[!Ql:-B!XcK *#/E)B 999!=C\( /    %  +  +' (,(*)+-D*  (A9ax>]sC     +- .B/0*> Ri0$<./CZ$?SJ`D]S Ma%;O1%>N-)5NW3#-!- %6@W2HWm,Tk(9NEV-? +.@ &1? .-; . (  %4 DT5E$3)#% *7. +&%4'.,&?(.*!4 &9 ': &9 ,&;:R;MX0uk '03e~@Pc`un$|2PDo%O/69?7> 6<DI*5:37:<"RV9(0IQ/;B BM$IT*CP Ua/Q]+ALCMFO@ILT'JS)>G;LV,4>AJ NW,>G?GBJ#kwbP[:mJZ3K^*^w+r*Cb gv +pi[xi +)j$AJ?R#M@iSaq㟾đڨzjz؁[JIFJB-"782=42A'zC789207=A9GOFNPBF:OPIFE83EEODA,($#3+#$#2&('$#),&.*A:80.0#!'%17622'21.77,/26/3+76:302,74>;BA@87868F;:I8C60GBFGIHDIL=5D?GE9BI,QHFHMCFB>DA=>A=<8>11-:A=I8FCHJAECGHLEHGKB@:EBPIS=9MAE>7=A8:545=0:>?CL=DE>4E><75;7=7>?EB<<6A=AC3AY-1F)!2 %4 4 .B$70 "5*% .3A)'!#-#/'!-!%3;I(( $25B +:0E"6@T\q4i~A0D.A'; ,+B7M/,@ +)>&</E3,D5J +Qg#,C7L 7KD\$Yu6Hb"Zv,q4c}p)4yH;.Mp!IfTq y(D@,p<*4/4/ '%)%1(,*/21/"((+)3($+&)s'0(+%$||"G;9{,5>z0x+r*t,u+k&q-q/u/}4~6;l#t.r/@@>{7o(B1:u'z+~+2r4{*8y*y+g|0w,}/~.q cjq)l;,ux"tw$d_ |,r!fjTt +Nl \wt2{1r'1y)~-y)9r#Z|Xx`"FHdq5Sj*F=O{2w-Ef~u/_{ b{en"j Sp WzW}Z}t6Hj q7Mo`0Ca;4QHe%D`$:%@ +9T $? 3/M6S 3 +)= $ 1)+0G1 !/ +/ CZ1RlB/+4 *.F+C(?$9 +  + >U*6 *+=+!2$5#5*AOe34G7FxM$)mv1$-KRlx8.8 D`,a$A/H #@ "<*+5M8L*C6QJi=^Tu&4TA_3R0I?X"&B +(G -M=] ; +30MLi->\. =8 +/.#  .G@\(2O2+7Q6P1O^|;Fc*0+. *C1/KYwG"9T%_wE^uHAR0& / + +.EC[#8.K76@Y$,@&# $ &%2 +      +- .D13M +!) + ,E.G-9N AZ#.H*H/     0)@ 'B#5*3+-AX6N4JLa6P +Sl(,F;VIa(# +=S(dB\ 4% 7G!&)8 0 0?   + , !61I+CDa 5R$: )@&=!7 +3I0!8 +56 Qi5"52D O`$+< 8F;I" !!3Wk7\s;a<*B 4J.%< !4%69F /, 8KVg'8H +#1^j$K[:J/A !3#!  +7D-@ 1B#4'8 ''! %4+<>N&7I6J !01 -*#5(9"- / (* 8&=0.0D/A2D3G +3D[")@$:Qg2-A039O8N + #[n6L].Nf$Ue s~=Yu5bu"h~+DWlx2[k$:J p7PFAX͋mՙs~Q{XGW"mwC]X_5@H5BOY(&PV*NV.%1!. .:+5)3 1:7?T]8`kDvZuY\h@YeD#28BH'EF)<@ -46=:BCL#HR%EQ!O[(Vb0@LO[)Xc1HP R[,T]0FScH`|׍Á}rgiiLKHG92>B?6C4989@I@836-EEMJ040211!,,. #!..(:8E+#+)*.-3% *%2.-**/73,.96456<>>+;18/.)&0;A;68=:I7=EB9L@77?AE=>@JC;IBH?9B>@<<=;@@@=E@@<=;78LH>EC<>2=;46258BKBCMTKGHIOMK?*ABPEMNC=EI=7L?8E724FE<6>AB=7Q=KFG1DB/568>514G:B=:;26I=?8>;5F:2036F<@34@>2-B6-q 4J!7Ma")CIb*>W+DF`.2J5M#@Z2-F$8&-1'.!64!8) 3!:!=/ !% ! ',  " %  +  ",2>'" # ! % ! +! +$ %*,*# )'  +(-< )< !3J%&>"$   + +&    ',*)"0 / %1*5& +"%4$3*! $ *% +$ *"24' )-A%.B$'=4M%!;Pg?+!7;R'"7 +7L*;);1@W-(=)>';-?""2 2;&!* # ! + !-%#2&4)91F*BjDoFZs7+D4J&=D[".E&< &< ,>BV"1E5J=U+C 8@W`w1Yq-C[9PEa!Lf%Pk!Xt|@^{!eZySs Yw]zLio1h'l%\x4JA3F'<8<6?48):/&.$:/6<25 &+$3%>32:/&t$"'u,6z)0/0|+y(r r!x,r(o&@?FQA6=7<~1L.1/4,|'}%.2+juu 4r"~0t%42*|uyxl ~&!)+($~(tkMiUol|){#p$#6rsA;r.~>axFg|5y.lmz(8Yq +a~j!iq7Ga bt$z-{1YAdy7SvFg-NHe,I)E 3M7 +E/;U&5R&A+0 )&.)9DV6+ +>0 /- +A( #+ +!5, &:+A 6 '>': 9I'1F-G1H9N0"3 0 *& 2 7L,!2@N WaDQ;F dq"IY(-'@6>_C_ ( 8.Hd+d !6N,I(G8V;[Pm))G 63I"6$2-yc(D5Q%BY$,C 7:C\%/,D+) %4$A4- #>S)!#*;"=T$ :/!< %A6"8 %%3 !     ! . !/8P&1E$*;gzZ1 &Lk(Fg:X*F.M:"> !7 + ' ( DX:   M^;7N'B -?-A1E&;9S/I.H 4M9Q#=Pi%BZ#.FW Of:Q +' xO 1 (8Re$9O +.?N^9F;K -=@UBEW 2D;G>J m7FV +%2QpgyasSeCP>M N^$3@"2 ."  YeL#,,@S*+@ +&:4)A+D+A7M,? @S,= Vf5+5# '3 ;J#!2(:2D$ 1 ! &(8?R$261G1 -),< )2&1!7 9O".'&8 =L 57M9PAZ!*D .H f>Ng'.C!63H[r/bv2%':Vt0BGPg#^w?䜋P^r9FW~2Cu;#[g6s~N*?M6H'6 *5&- +388ABN6S[MTXSfgg438U^a`nnmz|wn}}.%<̬ͮɤ^uo",+媵hn3w0S_Ŏ|fzu>K8AQ,?Lx[b(_e,~Lx:iqS"n,)/b~b{\zA\Fa0J2KpA=Y Yw$c1j;Aa)D#;)?2L"=#;(B'CE`>XRj-AW*i}RBZ+c|Jb|DKe)9SxID\:Q;R\uEs o-w2Po\w5u +?$)PB&=Pt"?3g>uWj%5C=J KS,8@;E#BK(DM+ajG6@EJ$JL*MR16=FK)6>AI!GP&CN P]+Vb2@IOY)aj9S],AJJS$JQ&;B@H>EBKAI@IHQ IR"FN8COZ.=HANu8u+kktfTn}v&($:;HTXBF>8IXUV_ecZk[`^F;@THD;96CDA@5@DFUDL68=@B?MDO<;C58B;C841FG?67957;239,-.0:-6<;:8775913*:8<=JCHC7H?<<:GE?D>C@<>@EECBGA>C:9@>:9ID:A345:<726:%3=A3=764?D?@;;=8>C?HBFEI>J?E;GEA6>I;OKD?GG7<237%09;C@J89A@G=7G:;@A@3?3$44/D<:A43;5>A?8>EL7-H*2<;4C>@FHFHB49?3#-%;3K$3H" 4. %+E[,@V''< 5XnA@W."9$;&&7$   $ +#!. ,/<'5ET3%( +- +%4 /&=+B +Dg?}U#</G )B+B&<)'CW#9ME^!Ic!Hb5L_y/D_Yt-Tn+A]01LRmUrw?DdM[{f%Ki0Mk)Gby+v#CB' #+?*5,78?@;7+3+3:.800+00*,'= +6 *?-#."2*!05 5&=2F0D $9_y69S <;NiYr&Pg#+"#+B: /K:R$.. $71%;6L$ *9   # +(?/I0H%:/F#9$%<'=+A(= 7 &/ '?6Th)Xm%CY/qN1BIZ./= +-(+=0H :R3H ?S2FQ`ep%FUATF_QEZ Qd`p(m~52A*'"2 #&5 &1  "% /@18M8M-F +-G0H +Ma)3GPa-;L)9 &4 &- ,9K&3E(9J , + ( -':2E$7 $7 ( $"6 5 ') +&8!).1 :$>.HVm%|9F[ Sgt'\l  /q hyM'7DWHEF=PCD?LW>F8FN?BCGBF=B60A497C4<>@@2@A%38-' &,/;6)'9<@FC@I>512:)0/$$')*.)-E726A@<:3956@J;48:D=E3/38@31B-510=9@:;=6EF>@?/6IF3LBD>AGQ=>B@7F996J;FEGA/8A,6>G1<K?7>437?90D3B2/87'3==71DC@43-$$.$<9=HB55G!1E$9 3#7(;&8, * 034-$ +%4- ( '     +   ) !0      +!  ,-&  +' ,)* '!. /$4(+ ' + +" +.&7( */ - !3!1! !00 +   # -# -% "  '3!)(5&' 0%)"*% & ( + - &;)<* 1(;1CN(/ '' ".*;.? +Rg)*A\v4Ng#h8+(&6$@9/0=D7C;/$") %0)&60'"$().1#v!{+0&#/"> 2+*ER*'"h ukrt x(kk'j-\%8W 4S*G34 ++F1M@['8N""4 UdE-; (+ 0 1 0E$'; 4   &5%4' +" +!,! * +9P$4P+E$<?T'- $  -,%6 +Ph!lWl ?VSkn#^za{4SMl0M;\\})^y.p8Vrg(RnPmi6QrBVx.Gd) %$;1N>Z&D /9W?^ 1Q:Z;X' ! 7 B\"nN7R*#=-+/H_(3NFa%9T4L8O#' !0K6U *:& + &frH,2"" ?W&I`4!<Gb/)?#0!0+>#' #70 +) %/rQ6F\+5 +2  ]x=Tt'=|>l|<:\l)IV#KZCTAQam'x:NYO]EPQ^Ym_sEYBY(2p:fq)5C2FRg3H6E>U +:L Th2#6 +++ $ %) +0;/:%3$ "7CW%?B]Jd&2G .@ *9M)84D*9 -? +0A- (: /%=+%3,;&0%4"2 *, , % $3")  ! 6.#7 "*2 +@& - +-B#= +6N8O=S9Ph(=x'DSU[e+hy/Td*:$6@VJd Mio">Y(Ca~h"Lg Oh TrGa$7/=4?6A8D7C5A4A1>8D6A5A2>4<3<*3 *22;/7/86=.2 4<;E;A!IQ6kvZScBP`9Rd8O\3alKR[EGKG, 7s{dou9HG¼̾ëkaJV= ?B7uag`EHs,ڎjkzMXi'Su*m{#t.O] DA|5hZxi!A(p-[1.I 4M Hb@^9Y3S 4R @];UC[8O؀?Ye,6O>V +9SD^qHD["+C)A 5LAZ"8RpIoD:Y%C8QB]by#`zv.o$\t n_}j %+k&&~d%Qt):%&k{8Rg*BO@HCH!LQ->E":B@G#AK'W_:Zb=KS+NV-R[3LT,?Hal>GS%KU'FO!LV&8= ;@ ^d0go5ENNWR\CJ@H]d#z=_eQP-~.-/x:*(,2OBFFVNTP^YOhVN^]:7&8IC@=DQA)T.@@LW_?KL]K@BG?N?9FD8@1,#+)378=:1524?<::5BL=6<LID44:68A/1((&/)#04@/5=:7<779B;MAH:>EC01,7=C:?C>8H?DCA>98;B>=6;<*@,28,524<7&1.8A;@-+"I5'.-;?C9;768=>A6DC;DJ.*>>5-?<1FHE.3@%1I8E;9QFDGK3:@B8<33=9018>2CCA91//7?*.<)7AA89866:6;<4E;6]xSSf(-@6J&9/B&<(:O* 6%9*>(=5%:/'  + +,>#2&    " /1 +  +  +   "!/"#50#5&  + "%(-<"+."/*%5& ,( $ *. %9 $.2/1D&1#5#5' ( % 4%2-2>6("'(0'>+627/0)%%#*)),*)%&"&*u'1.VNl He +sBRr%@bA`k3`|*h0+J;[EfEd_~,-L*G %6-G%A5S'E3RKi$4UDe#>*J!? 2!;,H1 3)H 82Q"3TcF^Rq41/LGe+6SCb".J$< ,, ,#>@`1P-H .#1 '/?an@]pH.0 !6 $ +!+ '',+ 2!3 10I )Ws?-#@ ">%:  (3#4 &Vr3+K3V > 3F# -A! # +  %(:( # &5ILe&/I1K Rk-;P%x9PXi!FTM^CZ2I6/E !5 7(@ .0H Vo6+26NOdTbnx26H 9I:L@Q9J +;M AS bu(Q_8G;L Oe$<q4]o CPO]DTOd8M'>:Q :Q 1E`u%Zo+1D +"4 *,# , ^sB>SoI ,>CX^t6,C5L *@ ':*< *9 *<>Q;NI\$@T>T:Q"7 2#9 +/E.C5K3H)98E#! )2% + +# %    "  *#4"3/ #0 0E%7 .1!8(0D AWNd7I:LFVj~ 8dLdBV +ET0@g}0`x"E`XsXrYtSTr7S^{#KgKd%.A(5.9;F:G7D6C9G:G=I8D+5 7@7?5=2:-317178>;ADIBIQY/@H'_iMdqQgwON^3@Q#M\1[dAY^G{p%)$%/ɼहQf^ry:DC9DH_glP]^ZZb0+4^ZY|^4AtPaDJ9AXk{tOcPd u'_q|7u5BMmx!o|+5IflZtZv} Te}1Lo ?)H +:R;S 3S .L5R2O:U5NG`:Pv8g}$d~(n2XrQk6O H`!@Y0K$?3N7nMh$Fa6T 7V 2LSMkKhC`&?;5Wsd~ u :K9Rmk(gCvKdw1Cs#g}'bs4/; 28;=gj537FF>HQ[C{ov(FC=LID'J!I,W(&C0)/.GDNKU+?-4>79FHL>DBA7C5B;LYHFJMAIJGCIFLCHG=YFED;17((#/,086-3)-+9>1/6:2-75959=27;9;=>60/CCD@=A=F<57E10-.6&-/.14*8/:A53;BLHODD=;?0::=9;H62<9086;64+<=DJB:5:?=68@D;8=@:@%/'2>X?==+>019C<:@=F;2=B>HKC0@6%F:2=<<7F8-7>FAC?@5DAHFAKEED@A?32Q+BL2/?::755=2964:BB7A?49CB7=;8?852=)ni}h>T +3-A-B#,) &:$8% '% (%1*#  21,A 3&' 0) +%:";7(:% +( 0A#+#4'8&7 0!19J)/B!. %;(>/-9J"4E[(7N)#71C!!2#&5.>L\0GX,+ + 0$! ' + +& +!05D%Pa@+>!9 !7_f}:+ADY`t;,? +)/IY-$5#55G6H$6=SH^%9P?V7PD]2L Kf$B\#>4OXs,4PIfd~0Qlp7UpMig/b~(]z"{;}@Id5K%<azFz#044E3B7:@?B9<:0B3B8EII893+%-20-"/ #/r+**&.224*G*50A72" -9.6319799,;E112-2:957A;4-0))33.& ",'%&*7%" " 4|}E5(#%#+!u~ oimD+{!z"{&w$z(w(v)s(t.JHk.O8W;XDa(Ec, = 6'+* / " ) %#/DS72 0 < +7+D)?&<. +!%7,"5/B!$.$  & +3J#+F!> 5,C$ $ Ym*w)Wjaq dxRfh|l(tq0;[@cXy%MnOo#8T 4Kee2<]"A.#=1M . =8U.K(FHg&>\1M-LFe#&E +%D-Gd,Db'1O.+eL]{6Jk#Y|7>c$V{8Ei$CaB`;ZWv5*I +3S2K!9)- ( ;U,Sp>6U`{BH^+CV0#*,= cqP!(JS?% .D#6#3 '*B$>5 /  +<>R+ !)2P)F +'D 2,.I$ "3E&,2B N`:5P5Q.O0S +>\.$4    4%8 0:N"&>-F6O Ma+"\u7#;KfYy2&@ $(5P'E,L +{MNl-8R -DUk9)< '(';! " +# +!    4F,= 7%<0A/ +! * )0F 5 "; &<,uH5K 5~g +89H# 1 28)< +@R\m."-3F%;,?3E-'1I'?5":,D&B !9$>31(8#!4T 5E'6(:#:.Gbz!z+{&DP8G@T*BCYAW5K3Lbx+8LCSP`*O\:%%.9T]x3Wm4)=Q`=*' 5/F.D+= ,)7/!4*<.B 3G/E $;7Q,E"< +'< /F)?%=2$3+6( " +    +# +&;.$3F%10ICY1&8 !#5 1B4G +Xm&k4:POa}/a'z2 0A=LK[GZ \rNi SpXr7Q@Y[w"B_Kh &%8*:+62>IAL!,4 +9@8@5;.6398?-2=B>D@DEI"KQ.w~_r_WeCN]5L[/GS%EP!GR#HR*OX:rvnMISN=Z˩-A3IUJ8>8=EF.47W_^o{v464./)y|kĠ{mxGǎdt:WJp+J]46Fk{8l}=gz6{>v/^@SAn|2DPgr)#L^wGbRme +]xk`ur5U/9X7T+D,G 'B *F%A3.F DXH["3cx*Ib :T9S_7Q "<+EPj7Tp62Mg9@Z:SL@LIV'KW&ALFP!DKAIIO)S\6KR1IO0OW:FL2NQ7t{]AH#")|ek9#$LJ_aWGMt*LfDC@\g[qVIONPGLYXH6"<75EE>BB!<;@D:?IFA]MRMINGFVEG;LE>CGFROE@G?7:TLJ/*04**$8-44%105.7-KGL78::GB;GHBADDA<:<0:2A:69BA00:=AEF89:AP6I8=BIPLJ?G1<>=;6.'2888>7<856898@9=>25=5;6,6,;;8;B24A6-/03* 3+&-671.,72/9>=??9*=;P<>B2E.-<<@=@>+<28BD8?M73C>A39M@@>,::<6062>?BA6A666:9;=87;:4?315122/-CD)vBp1bx*R7O Sl.Zr<+2 +.9N&*>+A&;'; 74/ ,-/$"% )0! (   +)).0    +  +  21&;&<"7&#  1  +  2-@$44E!/@ . +. 50 )?"8- 1%;-F,E)B* 0#.@#.A$$ -,- 3/   + ".+9& ++' +()!(; $;. 6#7#7#505%</( (#  $"2% +#3(; 3)<YWo,E^7Q =X +Mh>ZC_ QmVsNhUoA_ f(h&6Dt:B?F:@GBABEAH@=P?67? +01I)?$3&8crO!  .'0K[tY">7 %'? %>Qk*7U Ba@_ <)G,H$> +)E 7TeF"= '7 &$)0H/ 1IO_;!#-%:,  + $4#!  & 3%># +) $3 5 +) +%7S32R -Sq8lV!   + ->P'Yj?  +YlB2N\{3Df6ZFfLd0BS;0@'%  . +0 * !3/G,E +-C +4Gg|G1J #=+CZ2Tk:Ed=[)F)C CY( !0FSh0"#: 0 ,)  +# +#/@ 1 .D$6 8K#O^:   "7M%>W)$> /G.E+?1B=Q EVJ`(FV2 $4,/?%5 ?P!/ 1D#7,+> O`1@T"/G 8'? 2$= 5:U!.G*A 07HHZ)$5*<3E-> -? /B J^"fz4Pb%/dx9Ndbz%CXk +DI\Yl*\p1Oc+>:O=W Mc?SCR Wg!q+7  /?`w9by7:QE]7L:PfI`kE(= .FG[#5*=N@P0B&6-> +';BX!/F;T52H 6 5 #8'8    +$,  + +  &   !4'!#3%0D2H 5K/1C(85I.B/B!1.u6d p0/F%7KXRaScL_ [sXsf".G,DTpw3XsK`'6,7/:7@8B:EBM!@K /5:?6:6;-2-//359=ACG#QS2JN0oZhGN]:?P%CO#BJOW'FQ!IU*ZfA]fN41;C7S۰);G707-}~cgf7<8T]SUYN47$msV|W^m,x?THI^e|/Il(s,|27x4o5VYm by#u.JTj7O Re]m!]k}'n7NoUns.\umtQk_ i'9,J8&?3)43>W!4I+? +%8/n}:m0{7Q9T.H4N#>9C`GbtKb!/Zo:.F'D,I t5;fEPUSREREbZEOM:CHBE=?:E>?KB=6@SGE5/ECC=WWSN@?7C=?DD>P>8R=AJIbHH>>@9>;EAB8J?=FBBB?I3=1:4/)%5/8374>:=>=26654:-*+&!-/22/)()2:1OuRUo8V>Z'B +D#:+B'<)?'=*A/ 1 . -+%20 0.6H'=Q.8 9&@"*?& + +##0'.$6'9 $ &    S^?\m7>M0 -A31' # ! +#  !26J)2 $8 2 +?Q)_pO&8/'!85/D!+87S*">$?86$9+ ) )!$ * &   $ + + %#7(=!5#&'# 2. 5$<(>, +2(?":7#*$  % " #+$9&:4I!4G!##';-DOg-BY_v/:T.0. / "5/C';1F?U'Pe;-40>KCFG/<=B@AD<>2GIE>5--)32+003'=9~%0A<-(/3D+"8+9424/ #196.2+6,(')56*-.,)+)6-&7+-B/&4!%% *(%$ '-#&"!0, ++{5|$2| '+|$>:|#1,+6z$6.6r$o!~-y(1u'|6=[Gd +H7 : +)E(@":2J(>&) >O+DT3@P-$  %+ 8 7Ic3"(-HHb10 #- ' ((.:&%% % 6 9 )  @Q]t)=.ssQ* *#0?  &4"#' +"-?'8,=2? +!5@W.$?7Vo.u^o!cq!&5`n$$5*6  ! '7,7J:O';&: %3 %8 (; +1H,B,D-F48(A%? 5*@/DW %8"5(:+1C"2*=Mdbv6O_)-)=*?4H6KI_Nch{> gx&Ocf{+-C=T `w([jv) +0: + +IX#K`-@,@ 6I?T@V4JRl&EZQg&6MEZ2g]l0bz"_u$BT=LJY?NQbKcXtz92JI`:SE_Wo^q-&2.; 5>6=7?7A4@8D:E7B>G:A;@*. +:>3689,.14,/:>158<TT7=>)]lQN^=AQ*@L"?ELS&OZ+T`5IX5[jS99Cc]wၒ RZKLUH:C;RVO)+$,2&kq`=E+V_:isFco4`n(S{?f}+cz+\t#k.|<|:fv!Zju8|Ax:[r/{7e}'D\'>U$3MAZ"+B +B 9M(98H/AJ]6LH` '>Wo2Ib);9^z3vO#6 &8 >P *$;Ka,27R e,G`9O 5K[q_yf{q lpff=a~psau*<.#px/RNX+}km\#NW"MV"AKDMT\*CLGODM=HHR MV(=F?F!iimPJN+QW+nuB>I qrpjfW[UPPDMKE8;@422DP;95-=58<%047-:$2-06;1?@154D:<@ABFOMMJ8CF;593@;3/58JLKEQA7635:..,70,46;0<8)C4GA/;96=#%A->224:FE/CEC,33+"#&#+849>76I<3=@5:6?A>6??=C2=&4J<;-=21586NKCEGGMGEQNAFFCLDHPKT=EM@8FG??844G:;;/<';$/"+3-2*11+.(# ')72\9126.@.+)4E)#./"-?95.=/8CNd*,0DRB060:4;3-2F/7,3,.=0+H++=;.C14<@B>)>66A;G79=;HF9EC847@=9/8#,;08B)7:B<25:152?/"/,4.(*' '8G:1:OgC_:Y;X0L*C5M!Kb9-B9N#:R'$<0E%>33 72 3"/*; + &= ;5% .&) +&3   wcu:4F $4#8,@!4.*> $ " "$%'&!2"6!6 /2+?FY6/ +?+*@'>))4 +Ol>: 8(C77"64H)/)& /#8* ! "   .C&<22 ( /" 2-/ '<1$- "62I%-"% .  ,,- / -C4H &7#0)<8Jh~9S@Y20F50F!4$84I ,*A/D'<$EX8-@%/$ &6"+!-.$5%%8 +.E7M@X%4L)A0G1H0F#9'> Uj<*7HGX-3HEZ'Kb,Yp7;V*D +G_&>X3=XKdLfIdB\Um"Mp1YtQmNhSC`d%Lju+?1D5-EBDQV5'+t#|*;M@9@4)453$9A9;,J/4>1'}"'42=70241+-'3##/-%70,2$,1*(,(0/$&}~%)%%)5(!'$2*0}#u!sfnz w"ly'{'{#v 544~0w+{/`Y}r"~+86w6B_9R,E": %= &? )@0H\rH.' "2( .&  ,5$$0? Mj48U'D /Pb;H]2% +5  .(;      + +   + !. XrH$84;!> +(E,6U![)/'   ( 3G4D  /IZ0.>+1AZc6Pq"9X'< +' )= +12"8#:!61%< &= 7N5M)A03%98M*?:M=O;M0C>NAR=R?RRc0'71B*5F%6-?2H +cv4I[ 7bvCW *?5M)>I\_kPZ^k/7I,</@)8*)7L!5-F 9P&:CY6I)88H;J1@,>0A ?PWi9#8-/+?3J&/  )    #   + ') %*)$&%) $   +0F!6 Ri3tL]s+=RMa),vDCZxE)?k{< )AC[y?J[3Dbs-UfIa [t^t(3HBVShRi=PHV#.+6-5393;3;6@3<8A7@5<7=6:,08<?C#/1$& +36+.9>7<;?DD+NOL=d}vLf~Kc C]NgGb_x +jf9^TwjLj/I6M5K+C R#$8 0 64L9O1HTjPgcz[s08+s!LgNil.&1't./?=_miwet*,NThp68@CLBIGO:@HQT`!NYT_'^i4jqCgyX]w}BZ`w7q+?hFFD=99H?KGBN6GDFI?,9956>E$;326-6)1+3./:-;4=D@=2B?:AD@;=DC680@/1;:36:A9@=62:20498&%1%+%-20E=F:I;9@FDB/6:-.964:80:?B26%,1 &C6"7*A-)).@$#263?9@E3F?7><5<8:7:<;88ECC=;54C?>DA@BKDACBCICD>AC4:@?CBGGEJAC;:>7=:36 71 ,*/**:7)53%.-)"%#&2-<(V+'=;)5-&564!9%@<344=:9?SEBA;+A333461),499+>04:1@-/;+2+5/5<822,3/!D8=;4<403*8:658-4=C9165!.841J3>C<4//-0:.:.-*=23,3*.5-:":dy^wNg?\Pp0Ba&;W")E";6 !7 4I0G*B/F2 9 %&3%8"42D'- !4 7 +"  +   #5-+ !  +    # ?ROc%Pe25-" "3)#   #-'%(=EY.1F%:2 #7$7'. 5 0F1J!+C-G3!@-N#?&B71 0 ,A0, .0,A!'<  (: 9Q$0I. !% +,-1$4!0( ++!1 4,% #2#3!40 +1-E0D.A1&61C(9 Ma)l?I`!:CZ(7#. 5!75K#DA<<2D~0k6<7-+4(:02>;;67;0%,:15+2$05!-7':7:,+45=,,6%,393,1:5231++3)58/+612@9-7#'&(%)<(*$$"}y* *0& &3!#~'+'pqrns |%iq /gpx$m] LqZfcw2D_ku$j>Mj8/H.E1I1I&= /G+B?R+#(.$"'',5H6O1L*E/J&?&=8 1 +7$9.*;,!     +) +b|?j:(>eyAm4A o~JwM:SIhFc ]{v1Zu[uIdLf$!:'@"<*DC^343I0G/F9Pn//M >Y24Q2M =96Q,H0N@@a%Nm1;Y&B "= : +%iD'H>aKl%7XUz6jJ93 ) 33I7L!(  &4P#Ae.?"@ +4O>UC[% "^tHBW5(;  )43;+     ".(@+E)&? *F;V#1+),"4%  +  2Li Zz2>+mY +    ( !2#.4C) BV$^q8-,  !  206P>[0&@ ,> BW#qD0# "'' ((7*  %/<+# #  +.!0 + .D)$82 '?7MAetM8D_l Zd~'9 .?!3B+;!3->$4/- 3 1".,?Thk|)WjJNd4KNcEU +v6z<~CP_uGW9H*:(,2E3F'3AV#%6 !2+;*"+= +3D0D!2!3 +$"1 #%", !   !& ) ($ -  /"2+%#!0  '?T*&:$:/D 03%( +),69/268>@ CE%??&?B.58)ՉLU?pz_akM3=?K%frLP\@[cTDHNvP^X_pa9E2:A/=G6%106!02"%AF.*2-3*?5J,A !8H^,F['&9/Wl2&>*@ #9 #0Jt?N};NtA. -'=Q'?R#6 +(>@YQiIa!9D{-76(c +-Hnt1)2@C49@703#+..,@//32>/9A<2%.9#+)1:)8245--7?+<<9G;9ECO<839B@=/FC;A7CD2G;GAGG?GVPACG>;>=9F548;3CA9A451+;6J@+>630&-)9/&!* 3014-480'13/$*-6#3$ $2.86/;>@-+35+'*(#&%5-1/:2%$-,9!#1'4#**-.23)%B)%-)(;)%6;4-83(54-;-::764=6:1)22714=1;<235?61H:.5<>;<8D+*;))(--+*6,-/7.1/Sm IcXs"Kd"Lh.5T/M/K+F":@W-8O""8 ,C$:$;+F$A"=* $$"!& +>Q62#  +  ) 0F)1$%  *!')& %*^s=G]+-+"##3## "  * ,?Wn<:P-D-+?.B 41 @U/=S) 3 ,G$B@; +F#=%A7 8. ''0 +53K#2 1& o`:R+(?1J@[*,J=Z$Pm9?Y- 8';0. 4#5& +( &3,& % ' # !,!31$9' 68Q%&?$>#<:O)4*0 . 08LRh-Rg.Pg5,2 +0 .+A'>  +1B$2B$"$ #  /$   %&4+ - ,?:Q"*A';3E.A/C'=(> 8$: )$0A8J%7"3,= +/F9Q/H C[E_{Ho:NJd@W A[Qlp+Pk=WLj RoMjk'\xB>MC>B270)3>>x):80|)/@BC/@42,36943,%,)*&1',&1,,&+(K'7++(*(#&:>8./%(+)3/B-1@<-6#*7#')+/?05'$,-!y$'${"q!#4."xu&?Dx/~'3$|&k/r})0|*|+|+q#w'q 3~.X~ +bn'W|`p%dx-Dw(D#=;V 2L3N2J0:R!+C"8 /&) ++) '4  -**;(>8P'C: +* -H#=61 + +( ! %1) + +*"=RIi3Le>wC:Ytd~&Yq'JbSpA_GeEd&B6Vr2Ie%4Q Xs-Gb#&Pl/,E#<<[%D(G$@!-H3P"<458.)M7[Zy;Ea+5.F0K;V%Xt>bA+J 1Q Rr,)H +/P+H2M/F,$9?T(*  +%$(+H)>[&2N9T?[ (B -*5A! +  !#     &  +,A$<+&2J(? +'E\,(!  +  +&*{S%C ++E$        ):*<.1?* Vi<+ +-7    -@G\!2G Yq2-Yr5(: !+ +%[o:%( + $ , $/A* 0!-<%  $  ,# $&2G!$=*?%+B9PDW MWbl`m":DUaGU_kO]Ve(&6et9\i;/>9M0E ,@ +-C8N)?-D&>-D%>.F1F !"44E!6Ul":cw**<'8 +6F!"1#! (#3!4.APa8ZL[U7H5I0@4G.BJ`2G +$7.?h{;bq-.?:K{5^s';M%5+CU*+-@9I!!9:Q"*&+'6 +%5 -<9K"5'; 0+); 0  -"3.C"6 # $ * $% + '   - (7"& "-3%;Me-F\:L,:);"4 4J c|2xi|LZn:DZr@.DH_Pg7NXnNd"'1A ,@O7E '308 09(0 -2,2>C#9?7<7<05(,#+.(+47@B"@A!44MN6kmX9=/}vQYMW`Nir\09Wa;]iGDL4RSJPPOkklKXK0>)BR76>$+-5<'9D-'-0/+.HM15<%( 24gnB_m0dx0TiYn!o8s>]o)br/QaHXYx6q,{9fz%D[k-l1VnMe8NXor$}'lax xUn?Y2Mr.8P3J| ?[h ,Lm=X %<$9 .);3;P!5J,C,6K523 8Xp9E]7Q`{)k58S`z#A[e}4_t6 +(01Mc-)A^y(Zy ](DYsRi *Zw ".N66HR*R`|0P?-P~7@22ƝH>JfkX^XX;^JeIW7]g0C210(&9>94:8@B5=3,7.=A6CC8<><402(,0/63)&&%($1/,/3(+")8)8.%(,3/)<=1BC?1:16A1-)'5+<13&*69)E86A1/8-)-2>H?7=<872*:;;?E@DBC=;6AH0-91D7M7>I94=8;5=>5:8<>DMI;=E=.34 *1?:1/,%2.**:455%23"+-"%.($'83=($'6>A?59<(3?6/20, .4/1-4.//',0)0*,$/*"%&3'>=1&(>)L6/GA;61E.)?+/.'!*)1<3+:-,'-4/99-98@31<714<<78:5A<>3..%.)&'5+2-)#( &/"~'8{8Zw$=Y_z?Pm9.J-I@[/$</H>T'*@)@%;!:9T+/L#9U.&=, ('"4*:!/-& - -(@ -H$&! +%  +$%)13E(,!%9,$<#810B$5C(% +'7/>'&)&mOIa(D[$&=%:+'%9*=./.H(D7 %A@^-;\),K%>#;86%?/  *C!;48 +6 1G .6M()C-4Fb,Qp8Ab'3T)I ;./ - - 2)?. * +" '("/0+@6N$ :#; B\,8R -I%C&B"<3 +#907I3F$9"62I6L36 2J .D6% +"4 18J'*  ,<#(6  +  +# # + /'7!.#7!3*5E2F(>%> ,.K4/Gk~,7./*9-=/<:+#05*'),)*-,%4!%&-+')$:52%-$'(*5&:2:84%()/;3)/y".:/|")%+7!&*,"3(,;)24~q#),*~#%%#!7~&>|*4*:}$~#}!/}!x"z(t#s 2q.ooq!{*x)=v&|+r$s)Z~=^Oo^~a_z+eEap7qD0J +7U2J": +"9 &<'=):3EK\4Pe3J\/&8#2-% * (=#=59 +68P37O2$A:- % 0<(,:' !+(%/<&"+9=J#4w9\r`yUm}2k_y\u[qFXOg?Z Vu&CcIl7W9VYx4AaRp'Lj!/K (G %C)D0'E\}6Zz25T9V,G - 6%+B'@;ZirI1! + +n@Z-1 Kg3`|A3O,(*3 3 / &' ,GMi3(C"= 6RpU53OUqBL`A  + +   ' + GR;! +  +  H\2BX'!8 +Qd8)<AU'35MCQ0 + #/'9*/$    ! % + %7. 0<%!  +#+ Je1I`!:M/g(; %"(. & %,  + +* $6L "=!;#9   +#-& #2 *)'#=(@ +7N YhS]rB.9r~9Vc4z2/B.A .>/= $ ,#GX#?M \d&3>,<1D*;(9`q7-@ J^!!2( 2,9KHVJ^:K EV*9J!/?-6J#'=, &61?),$58M"!5 5 %;'8%6.   * 2C-B0% .0) , &   !  + +  +0$*#& " 2.#; .Qgo@yR##D\=Uke{+*?@V +RhG^ AZC[ VnJ;P!0IWT`M]l}<4D GYt;AWZo)Or*ft-#.56=9?;A28:AEO,7>17$* ,0(+5869;>1266IM4259?+*-OXFs}gHU95?OT.=F#BI,KO>9;4kqhlsiT_M.; 8@"16!$ 6=$;D)#' /235?D$5;!&W^9vSf$M\r"_t)l<{Kh{7~M5In5q/~9t+:s2PhI]u%Tlk3F^Pe\p@n"Qgiw#FbDbTs +b}&1Fli ~!5Ay6%,-)85D."(;&9 &:7>W6O2xNB_@[Hb%F`_x/R]t-67Q C\E^Ic &,E Kg o;c-A\64<1>+=$!"668';>573A06+;?;@;3F8EN;2CB95R=BHLH6.;CDLFCBEDKKIMFA78A85MEEDH;AR:ALKPQLGAD=6=5=>CA9H@94G<>=A:.5<5>92(*7<>8!HD?*!(},//*%&" /342;5-/&0.>$=$9P,63=3,%4-1()190$#0#*.+B2? +">%&#!)/(A( 4(04:;742,5(.+$%%04*0173/&3//852(IB1@32F?4$.=D?350+0$&,'&-(1/.$$*=<*CAx\%D B_/:U(0K7R%-H$?-EId2!&? " 4)"   % '(9#' % 4 ;N0& VlB'=9R$0J 92J#-C&8$5+$ ,0B:MJ^)!6pSI\2#82).A,A5!;$=+C05#?+I0M*F*+0 . +._zH: /7C\-2I5$: +! 585S@]':W#@ 59%?*C1G(4,#' &%" !#(& "2!; &@":'A"< %@Gb.)E/"=,HIe;:T(+B*;).CPf6&nW,C*A"9&,  7C0) .!1A& 0?$ % !   +  #-%& +?P. 2 / -?I[22D1C:L 1D*> >,8?B?>/+}&'-+15*-90}!'*2()*'-+. ,1+;&+(-,$"+!('4<585+k./33*/*.-+/D0{%30w .(9212#$}$+3.7"1(~/,,6:<5/9.9:/Bk04/-|#-|'1y)y(y)x$g{%qpfo!`gW X} fi{098m^f5t&2do&[Mh*C7N8N07'@ 3I6I4J3I=WcyI2C* 7F)0?0C9O$.F6 4 +7 *D-F0 +6&;" +" % (&2#  EQgt(\n]mO`,:OZq o!ax .Xr65S'6(#   +> !* %1-0G6H&!'<2 -:R!5J."!5I%8P& +"'A"< +">/&B "? &B1IPc? +%1!07+    +& % +Qb9 "2:O->    +%  3 5 $5J%/   %4' +# +  #1! %4-9 + ! AW%7@O&(4 "DU6 3 ""/) '96M" 1 :K(#6'?+"'&   (-  +5H &;.AX'/B +oCJUCOHW%M\!br-0BH[@R!/)>8I u?kv0!/;I Xi6+&%7 2H'.": +, 941J-C%BZ,!< +w2`| 7E'*:&7 *< 0$#-=#0 +$5'9 /;J'7 +))9=N#->%8 3H4G/'9;L!/@IY& 6H &:)=L^*); + +)87E!#1 0, &5. . -<;G**  &   !* ) #"2 3-   (7( !%3#'6' (  (1 qMDWTd5ES**:&9Lc%;/LgXkk1MdE\ +;T`x$%@D^q5G[P`HX\oBY-D^x"p$Yt@YA[{(cv#-4?7A187=395?BK('/ 06(- 8=)-36*-BE'PS56:.2FK.02/38='3<18BGBI":A?B-*1 ?D6SWH~r/>LkrIidNƔPG6:5>8"7t E;'<733A99P/:9237:02H;AE@HBAI:HXTIYJNORNLGCD@HJD:A@B9L1AD(;67@139E9,"2<0:E04/3D2B=<4=:2?9@57>7008:98E15@;A7A<2+-7(8./%>:;4,&>B]w@^}'Bb4Q .L+H*EA\/;V+/J[(-F,C(B%A615,C!9&<* ' ,!   +#9(?5+ ' 0@  +,0F+$ & + 556L+35,D,D:T'%>'?%>2N#.I .H!*?2/%"*;NBV'1D*- +.-!/ 0E?W,>V++C: 3MMj84O*F8V#:V$C_- : / 06 ; D`*?['4-H]xI0H0 ! @^-Rm;:U! :1M 'B2- 2.-& $   +  + $ 5; 5P$'@"<-G2NB]*>X(*D ;.K"(D0KQ6IEY"H['L`-/BBS=N0B+?D[%E\'3I7P6MHbSl&|Kk7:RRhT^ze:h.2,7=@:4:-456w"5Bz"3w74&;.8B{!p%%4)10*#&',0'' }!&$,- 4;3/>)tso}&'5)2'{#/4-2)1D-y$z%}'2*4E06}!==!0N1D))?0*.8/>0{$,255A1+93v.0jdSp _|=dHo?},jx/n%t)6ddSyZ~T{ +dq%~08:75{-gm!n&b"2OBW4G%; 3&?Rj/BX-@(>rVLf*.0 4D *::L%EZ01I.D(=!6!955 :0! + '4#! dp=|Kb&2$  O_ GqPbL_&N^0KaLgTkez,`u-Sh"D[Jb?W*E 4A^")F Li%:X2K $=2P4P,H,E )C ;T4NGc!>cEgUs-3M8OWn26 $/M-F3H#!0% ! !1,,0 +Zv6#?' $9/->T)6K#)96O&23N6Q(C+F,E63N%A"; +      #       .="1=$    +%  " +& ++ #<%:/$0 +&."1  +  (   ! '"!0&   3;%4FEV( 0TdE #5 N_8 / +1"5 % +0 +WpC'!   ++:/?#5$50*:* 0 ;L" Xh6BQS@P&""(1@*yI$4-?7D*8U`(JV-9,!CT(2DBY+2I/F!=$> !>3H!+$8'C5Hg`z[5K "1 +14GN_8 2! +-@.&9,/A$   +$4% +(?);DX(0C1 "2 (: -.(= (=6J,?AP) *7+;#!* +#     +/ 7F&/ /<=K3 '    ) /% +/- ,# +   $(#%$4! *0 $]n=(=O 2E (.I*DOlA\ AX G^ I^b{$k+Hb |A?T^qJQ(@G 09+4 7=.3.2/4.2'+&* rM#/F?V0-5J@=?@B)496@C6C?=KB;9)/?>16613>5.142/6(0.=+%:0/;;!$"(- /)7,8'01)3,3&"%5%<'14%;:GFBGNH23J=:=<622#1)A86:8B@??9;5F@NEAYOQYK\Y[GVIBOBMCAPI486089/78:6>G(785=;&G%*7(6'0:4,-}15A44)2",)24,5-7;0/@)98/$-)-+)+.2.( (!4'#(5--)/841799!-.5)1" +-3,*"4%,+8'886>3//2.4/02;3B968%8=520*B4?0.''='.&3))--,+A7,38307?=@=02;5@99B858=10A39.03;07B86:29@`xd{$JePl'Jg'.J:V!Hd18R',G.I.I7R&;V,4O!.IJe70%:+ +)C$5- 40 &>"9" +" * ! -2"5,% +% 3C)-A!$90 60J* #'>5N,0 +2L'-F!:6 )D(D+F(@8&@;S/*@%-' ") 2F!CT++)8%6 0"*"6":+C +"-B-D.HHb22N"> 8T!Ig23O0M9T' 8- 2K 6R'F&C4Q$"?%A+!6(!?P*Pk;C\-'B,H-H$=* )# $%   #+&8(9, "8CX22I);* +      % +     +  &'# +% !0#4/D(#8$9(:1D 4)= 7K/C31D2B'7+=.B!81G3K-F5M9Q*C9QG]:R 1FNgt16Drlo98A464$7++,,3L;F;>M*5&1(A$<"9'@3L&6/ 0, +!-.<ds/`uj eixK(+<dx8n3^slq/9I(Um {0Zps6H\Zm&H^OePj(+F'B =X!6)? .E*A 2KIl"mBB`Mg+=S*@"7 +07P%7M)/#  +#&3 ;T%kPx) "'#9 +->;M%4E#~YiFu5%P Yh*?RDZ XpRiG\ Uj9NMf`{?]DdkVxn%x+8Cq6.=AM>FFL$18.5-3/4/69@!:@" " +wy_67 4676;:106699=>>?58FJ=C6<13?C%@E,DG/8?%5?"275;:>#7;#9=&RS=EI+agDOT.jqGEM#&0rI]p,[k$al'JWlAh}=wJ[n('6Ik(q.j{'Xln2p7Tm dz/I_v;e|#auU?TdwPg#Rn cWr@Y?YAIfZxc.%= &"2%3"3 +>P_v/i1Hb)DVpFUr1K":+'+-?Vo-[nJ]@VUq +v *(if`uUahAH1eC4F6*$2,00+?@:6D9_ct2@B㷋C}",,030,;&834?>H/,3@+9;@A;GL?CRCA<7:3.)*+*;@1/59E3BE?9<:9<@5@20;,43'980*493&6+<+3;(7-.7,)<321/6,#7:$*-)&%&'+1# -$<<,'C@?79MB@KH20AD(GRORWFTVMWZaZPOJC@CGJCFC<;5(,,,7?A;B>,@<9162(,>1z:5'-G 98)'97119/%,(6%763<<+=%&1-,2))&+!)%//.%3)(/"*(41(>5=3!0.?--9&/$*%%&#(*0**5*+2;.,/,,737826*570.9,,16&'/2&(:*&$01,4220.313528-!.709?903;C876640025:>,78?<876'A=V0":*0* , +49N(9N', -= 0 $2 ' Mc>2M"*E1-*>65O"> 2N.K=[(-I:,H-  !:2OCb+)G)H#@(E = 3K8N /DDW-:S&6&B%A7T%2K$0* ! * "  % 31M!,K!? < 0K(B.H+B*B6 6 .G3N "?/K(C3J$&: #3,> 1,=-A3 -B/)>) +        & !+," "0, "(<5J&5 )<EZ+'= +CX$BV"1E(< CV%CS"?P"&8 0C2H5'?Og2Sl3Sm-rIC\C\?Way+PhD]CAB6P@2846G7<6;5/:96?40=67'&7'''5%1*'>00.&>3)5,0,=7.##&29?*/>101-!15.+1#y.$092.0-~*8v%x$5?75601*200-02+668>E0<77,413503=79>a{|:r1t2d}"i'l*ay!`"u4k'ct,p(q*&A#<)+/ <6 '131& AN!Sb!CWTio$Ss6Phj7Yo5bz#q"a{5I`3JWq$Mg3H.CYn'G]NeLfQl)Wq7+G87:W1M`z:vQXr6Si4@X'7O7NIa))@"9-H.Q #B#?2F#,A+$=4 % !-- 6J#* $ 8  AZ0ZrL $/0B* +& "5 +" : /*E8!:B[ D]!/#5 (   #2% & *#2 &   )5! + BN<   /:' !(2 ) ( 0 0 1 +(?- + /o_". @M2iy_  +!  -<  + +   $)   "  +(8Nd5!; /7M ' ,<#5 +     +9G(* %010* !*97FFZBVQap2Ue AOBO +GS P_GW9J?Q*!0 /?0? ( 0A:P-E .E 1.E!8 +61H! .:.J5{Q8F*="3,AJa/ 7(<C[[az-eu.{u#;l$l&;'<#8 5 +!ar5,Bt#w5-,=*; / !   + " ( +# $% &,0.*)+, !-,-" )  +)f|>DYWh'MY$-@hZyz@0,6&, +(1.<AV?R9K5F!3#7AYE` +p3>7Wm)u.Wx +n"Pk@R:H=E>H8C7?5</4,3:A#KP4 $ # EH1UW@>@)3524)-3568:=55;?AGGM\G^C8T #@Cd +/N!9, '#2 3EDZF^ P%4+.2+00*B?.78,99=.89,7/);?81,E+!2(9*5#-01.:/1&:(+5'-,6K;@.7.0,))%0:3*+*="2++*++):0'&)%/$$;# 2$,/42$1;.(:133)00&*0'(&-0,180&(16*(0/*1:9$456,>=;71$)++#.*>4%71:6+(>1)16:+'73%)8-(4805=4+/9542?2);804..80/5:;==91,637f 4O9TGd"Gb&?V*#: /I4L#%?6Q'=W0#?&A9446,?$*<"' ,11J)9%?* ++1+ +" !( 3/+# /. G`9=W/7 0K#8T-1J$79T1(B3 -F0J"6O':T+4 4 %A/ >Y/4 #=)C7)D9S*9Q)1 ( &7#2]kJ +#6 A^,1K#95L#5O +E1MEa24Q!:X'9U&,H"@%=5Kh7ZwB6/+F2M9V!Pl6:U /K/H&@1)E&D$B=V.+>  + +# + +'*+,D!"=7V#)FUsD5P#2'A%: , 51F#: 3NlT3O-?W0SjB "**%8(:12": +5QiPFV1%      #" " '( !2B(0@%*9M)=R+"6 &=%= E](BY"2FEX$/G+> 2B;JL^/0DDZ.[rC)A1JWq7f?XHbVq'vhf,Up?OP~.7~*5D:=KDB=A@F56602-99(<*>5+81,57=02-;>4?6.-+z$041}'6518;4z)y*q'~7]xm)x5o+x3q.k,r3z5\w]xr,f"t,w+ikw+^^j"]~h$k'm,^{Yv_|#F^l;7M%62Q+?/E1I-EE^/3"834 8 *D:O/  1 +'"+ kwJ8IK^ BXMgiv.r61K>[j!U?[Kf)B\So*\4St133 +0 6M-C.Fv~,S>wC@M" /=*) + $%:1 *"2Uf)?T JY DV AkYZWi4 +1)0*$3#/,= 26H*; .?!4 %4D%1A$  + # % +-$ + +$ +  !3 ,, 9$ !.! /0   +  !3F]+C#6>N@L*:Ro ToLa.9',**  S\#bq j{&HY -=DSASJ_Pg6S4SDc Om"B5Vq&G.@7H 3? %1/=(@)<%3 +exAtC.Fy2_z6Qhc~^v7>`s6Tz+?I;5OF@3[7?JI2,>:-4'/$}} tr|(}+hm'j+p5XsQpSw;EIiTm"NE06<+p }qu q ,&9,8-0,0)'/736FBJ9>/(?:9/;-3377+171/;;311.6*B11(-*2.4(+036,:;+2573/-."1,*.::+(:(379..8*&$"4'*'2;66PMG@QEGM0EEQNFD874@I?+1!!-)2-562Q-8KGH\VTO]UPRHJ6KD=24022'/1@5;-6,.'3D4.(.")'<&132.-1.$3,2&+$&+u<,8-B)4s(-;/%++1**0'0152(%1/*4.*%$-3-17,!6+#*...&3..01.58/16.,%%gWqD_HeQk),C*A:T%/F+E8T,7Q+"=$>'@ #8-D$!6$80& 4)B!6S.. 2$6-  &:Q56L/+"9,"8&-4,E.J&B+G+E5#= 9,D 2G$%<)@/ +9R,C_1/J*EVqC]xK,G8U'1B) .A *@2M >Z()B'=*A9-G"=!<6R&?\.#?0N3S"=X.(?+$>'A>Z*6"6+B5PXv;`D#C#@ Hc2)C2K!0K !>2N!B]3      &4 !. ) ,- 5,G:U'4B]2U)G]2+1 5I6N)E 0L^{EOk8?Z*`zK";00 + 55 6 /2J6P21C$  ( ")$  +%%+ -%6- %,"7 "8 '? +0!91E '> *@/A):J[)=T#+DA[Vp/E_rC^y+E_ +Dq0c"i$v+811=}*1>5>D?GF:>|*|+y&/s'(;5G57:937;26/9"&,332.+'r ' .;+$)#1,)7I7,5'&.%102/1-"),F33924~/u'5B;75>:..*45,9=343/}*1//,432/7.|*v#3.{-z.{2w/w.t/u/}6u.m'p/n-q/|3>y*_zn$v+lkkq'q)e"l)a~e&f)\x!g-m5_y'?V 3IqK2D7LMe,;SOe$G\ CZ:T?RFZ"I]%Ob.Re:0D0G'>;S)?3*9-0 *"2B*'$/8KZ5GGaB^SqMj`|%;U]xk(XvQquKId&Kd(1H.F ~NMg1L"=+G,JQn47P#<9R1L)D +1K/K84R=[@_7QE]#pMoNRq2jQ'> 72#<5+C*>. )$. (+( &92G!!5& ) /(   ! * +3*G%C !; ;W!!8 +')$7CV2 #%3! # +) 3E(/ :Q*%9" +  .='7)  2F$1G )4N( 6  -:!6E%&4MUD.=AUSl0":B_,. +  FV2AN-2E%2 6*  '$ +    ++ 02.! & Rh3AZ% )5!! +    /3H!'/(>2H1I#7#(#3*;(!7!6&#1-!*82A+:)&76H$-=- 6C%DU*-Kb&[t:#?G`"H[&?Q(3F"2 !,K UG^#/@1*A/-@>Q +~BG[BX +/@"u Gf{!et:'6-/#6'97J$=N!FX#

    R"-(. +*''v~Civ(Dx6!1vD`u)DX Ia :Wa}(Yu4Pc"[zPn =U /E(9)8QbzAn?Q^-.6JP5[bFBH.+0'*13BB*2448!jqS>D#=B#.2566589@EDJ LR'@FBHPU3NQ9jl\xuMWF 05#Z]G26)-49BH$EK(zZ;Fiw@Rc'>PJ\Qc&Tf+^r5Xm*OeJbgy,JY?IEP TaFUJXHWOc@U +I^G]?Uaw&H^Sk\w Fb/r&C22M 7S !APa/1'? #5#0?;MSg%G] p,i"Xn +x+x*|.D94DA3LBD702<2987<21.,,+/+/#|$nq$q$w1Rod'p2^|%f-Yzu8k+z5^"Xz"Jv'ls'*xy$:D:A2;(%+<EPJMTLPQNFIMK328'-3;--7+4)92,7(/9.;//")++/"/.33%*+&-<.4,&>5,*$3,>"*=(.)2@500,/A:6*#.#&+'$D/*.0!,6A2&"5)+?8!- 0"%3-8/&9=.2.:00515/)31,&53,-4/07?31;08./1:355,#+7/4$7*,/((#,"+(+/%-0('(/51*G1!*-'353/29950245'%/""$0'Dn'k2m=_y7@W -E4M"2G(A1K!%? 9#;3,+ 2/ 6H,'@)D!0K)' + + % %  +2"93$<, 76$;! ""0 ,FQm@?[.,G7S(Ni?4O&4L' 7(!5%!5*B8U'/K(CB\02O#7T'B`2;Z-"?1K!5O&$<&"#6!4( +&;2 %@9 '@*B&?-E3N%5'  + +   +&  *,A#:!9/ / !<4 .%>6M#?U)I`4SjA2 +6 +6N6Q7T)G9V!>[%61733,!,94/'3?H,3<<34?25;y05J* #9   # +0=) /?!(  , 3L% "&     "&m{_BR-@S *?Kd&,51)(5G  0C$/ CX++?!)8',7I%(   +' +"1A )$#  +4G#6  +  6C/&&  "3- 2E$0 +.E3;R(*B$;5K'2) 4A :I!/E'$#2*'%(!",4A&$ ) '71&> _w/cH_Wl-?wE+ +%&a4EgPpHa+)0E!4 0CL`'DW3G,Bfj =Qo,5z azYm'.@ AP"CR*;1 0 '0-4HV8RUr:W+G_{#dHb!9%;Ui)K^k(s-[q>S*SX@Y_E]bI<@&OQ:&(DC*478=?57EGLN'PQ3WVC{џ~!,!%lo[knY.2-1CH&EJ&BF"LQ+)3Ta*IY!M_'BPKW JW L[#DW=SZp%Zq$"2DPNX;H 7F@NGVXn%fz3FZH[Vm!Ri=Ue}"|)^x/'^~b +;Wc[ -Un592C",#20Ag{9Pf_vo%n!7v%I98E@9B9047+)%.#C-561%'#2+%9"&w$w(Eap-c i&Ao+s2b(p5n/j&~BLns.y*:z*v1) ({1 (}"/+4,-'1243@44@BBC85EA?*37-15=2&)6/:;;BNN]ZNKKIOS@WX;AGAH3<,'1-).0*5437A5<@$ -:5$2$-5'&0(0$#';34488;)=(933$73-7.;,3).-,.92>/;38:513,3;;:2(&5)%&3,+-$!=%;6,/&+1+05<1)2102/D$*(*$+-,+(01.4)-1,*30-4,+72,12910560))*'***4'.:490k.Wm(Vn/5KNg69T'/E(>,F*F2L( 7.$4"41) +!-@$+D!8,+)&+ 417!9$<!9"95/ 4J%Kd9?[*7T"?Z*6R"0L'A8"< !/ 8!8 :"@+G <$@5Q('G'G(G6Q(,E7Q)0 2G!8L(. (;' ( !Pg?*B03 !9+E=X-=X,3N 6U"6R@\(#B< +H#@#?0 $6(7 9)C-JLj2Dc,,H4)>)= ' !21E0D"$  + +%*  +##<!;9&=117&<"84 +2J.E,C#<9O"Rk83O5Q=Z$Gd0A^'3P1OFb13I:K']pI%9 %9 !4+<&4 0>#"1 " # (!/)  )%5& #"5,?!!0*15K#0F.F3L(@ "8?U(9R# 5 0C/A-BSi:9K'<AW&5M=TD\i>F`5Ps5p(p%dl"{,4m|*o~+4*C(,2,*56):4)5*15-=&:;0~+?30",F{5.|71,(5#$/;,/6.32/<.1=7/=34-8./;&%%|",~'0;312?|-61|+~,.33~2{/z1}-0}124s%-}*~/4t&}/y+{0t*r(m&q,m%t-z0o&^xb~!kkj\yo'\yw,4oei!q0f(b'\z#f,^}(_{(Xu#VsMiZu%j:@ZUn(Oi)\w63Kh}>GZSh,Ne#bqc}5:R +G %B@^,6 2F5F/B-):Oe)*= 0/@?P/r\CU2& +& "/)&   ".%=2 (   "  .5 )3)A %~+b).447++`h6cn0IUHWBPATMc]v#D^ ?YE]7O7OZrUqh"Pc)j3=WRkG`c}J 1bgNW\BCG/23TU<()649<"IM2,1NT38746:=?C;A69anX'2%+T GNek heB@`(Ps_P.$+!1&6?L q.l&g|20>5/+:*8.-63&+("|}A!(*&$+&6")&vdk|2v+p&k&B~7q*h 4_~`u0Cbf%Tr}-{(iu!x{4?"&06$#&/,0,!?<;3AAF*B.6O47B2C7&(/%9G8&)(./(0%<6;+'F&"%)/ !&,*+7692C9JG?G>FOFIBX@D4?LOMEREADEK>F<<1/1:+$-+"/+1*5/*05-6&/)48,5!0((,0-);5=NA8?%G497-"'9!$.&(607.4''294*253::BDD:;:B;/<:+"!"*1.11A6<2#1:1-=@5885:03716,<)2/:( +/$7.,6/ ! ! ! 025+ "4 6*2 +,+#2I!Lg=8R',E/H$=)E(D!92/I' :.G#(A-K/JFa9/J#,F(E4T(*I4 $@9S.7(;9L.8M+'0+ 0 /2 (A(B$=)D-H,I(D/J;W&'D!> #A-#<%>'?*?$9!;9 8T%/K&A!<!961'9)# 6D)&4 + $ ' #& ( +#3.H+E3 /"44%=+>)2H !783 +;S&5M>V#BZ(1L2L0K3OWr<-B%90@ # %3DU/FV2 / .>!BV2.C!$    # )) /1!/!3#6(<26ND\*3K$<3$8 &8 (9 *=4H@W&-B1F'= 3JXo:9RLe B]Zvb4n|0kpz&2hqt -z"pvv+8)).{ -6z!-%&(0=,v"5:3830.:0+D&#/6%+"5"$)$)0/}!78037B0/:043-3z ><1.,11,)+<0z(:;w-4997|*97C70;{,22~+|,{-|.~0w(7{/u*u(}./u#w&|-/x+x*x*t)p&n%k#d~p/p/eb`GuPVqjJd +^wm$dc}l"]xl,e,^{%_y$_z$j6d/_z)i2i3Zu'Kf:V Pk"Sn*\u3Mf%]v2K_Xp1G^!Xk2Rc0:K8K(9 0 /BF_}F7o2^h|Zm~fyl +K^Wf!hw2ds-ds+;K:N>Q 7L/B6I'?*C_9VUt+,H*FWs"Nkg1Z|$a,Cb7UGd"2OXs37T;W0L+D*A5 !7 2L(D5R4(E 5,I*DKb0.-4Me)G_&?T$&(#2'5 "2(9 ?P. 1*7"1"2(7  + # 3 +0%C 'G :XB_Nk&8h6Wo$q<:S D^ ?[#"9 Ja2D[-2I,C#7") $-*67D+ 1 "(>-B'7*  +  . $< c)4NAX+Pi&a0MwJKiCa5e +"4$23tEH'?&?*4Q8U Fb [y"Xy\}5}1KFX!5:KFV07GBTJ\!:N3F /1CEZ7M EX+< +@T0Eey9:O +>TQjnBR"  +#5*+A. +,@9L%BU(.-A!0 CV23. & 3+*2 );=P+$6 !5 ":841;O7L 67N"":#:-@  ' +!#&  + **86>#9>'`}H>H)- ;= @B!58=D5@FQQ\*6GV0E@XId f*KaAVG]KcE^D_VvJo *J#@f*Jc t3J&8 yex +&&hhP11(()+qu\djLEJ)?F AG 6<27594715183;3:>DMP-\`BHP4JS6hqS0:2;8?8=9=EK*387;7:Za<;C9F;GCPIW `n9Sb07Eet9bs.`u&]t!>TL`BUKY?K8@ *:Yn/HZ:LPa[m'~I22t*y1q${*6z(l{-Ur gjo${!*{9.%1'%0!;4%6/*()+*.DA=CI49D7:.!E.83>$5&/((=*$(-):$95&5(/00.#$#5+:618<.<.$$"*?-4716?=7AC?G8D=:7<91&2195'77-;76DBNJHPE?>BI9GGJ@Q.K-8DMAAKRRIE8XMGGJ=:HB6*6=2-*&.'&)+* %*3(#**/,<9?>4/6-*2..*4-4?;$='09,*),!*775<:9/7;5):;C;24CA13*++(8:>?CFA<4A92*%%22<6/@/7G?84960%5.78.17A<=$1<28Α.1.:2.2<>55014+(0+..14.85K;?%2:%0108)/9(0C<3B?6/.!+709F>B7864=<3=:4@8D9<8E@Ne9T 4N9RNf7+B$;`w@nP0F,?1 /-%:-C#$7/B");& -*='0) + +(;"/D($<8./!51.& #7 + - #; :6%?,F!;6Q$&?5,E$0 54M*3K&0M!'A5)BQmF"=?\3$B#@/I#0K'/D% 10/3M)4L-#%<1 . 6P+4P#5P!+F7 )E3Q#*C*":Mf9Jf9 $AZ12H"34J%,F!!;/ ":'?1 :.H!6P'%?/ 1F(% +"  +7E-( !!5'<':' ' '$<63 +1 ,D5%' UkU (AUp(e-w58v&qt#y'/)u!w!x sv!s ,vx08)%%{!q{$2|&p| {*{%t#-7640,(|"*4:60,03~"%q* *./p}#(/D7@668-.@3??:%.=;675.4/;Ay)0}/}0w)6:7-:-6w&.0|+y$~*t#3t&w)|-l}/t)o&v)1s!r v&l"w,y0l q&j}2r(l$i&e(^{!k*^}|0`$or4Dh#m(i";N_yh-e0c.c{(Sm]z)Xs$c}.Yt%Rn ^x/Zw,_{1Vr,Sl*@Yj@Og&?USi/I^*O#AR#-=Vk0Lfu0h=8a{nd{{&JyR5z4"4&!4-]OoWto5Tp?` BeCdmAUr.3Uq5=Y90K&A : 6 ,%<+F$@(F'D Qn8/-3M)C*E5&> +1J Ib(h8O!*'2?"2?&0=#.="#5/ "68N&,A!6,! - 7F))&Ka7#:%AGe,Xw<5S5SVs1&?7N0IJai6)@5NE^''?11;R#Vl7'?&::ODY=PBT%0&:# +*JT?#   '-T^G&, +'<11 !4!  +%6 42N"= !&b|;_y: :;S!0J1(?"7  6 Jb+";G^-&=9N!4  $0  (  !.-  &   " " +# "&.$/& +! +9J!K[5( %5*&; +La2&9>V).E1$9 2G &7)8 & %9 %0 - !/(+ +- +* 1D' + +"  5OA_p.3dZ735t2pC_\w"0J0ILfe2#8=j&e}(uG!9/JUr*8(B.J &IPsw7Wj!G\ ?T"6%68I?Q8K3F&;/?'>P7K5HCV:N8L$ 07J AVI^dz#{;K+F |R)  />6L$#: ,F)>2 +(9/'8+0G ,F(!< $A: #:5 # 3 6%=(*-?2J"+ $1*BLcLc*< 3#:$*8' &2 $^mIjw[ +   Yp&"/07-4 08 %.BKHSAL1<!-cs*dv%^vJb 1KVmCY:O 1Gg-TlFb Rrh'o,Fe>XPhwD,;OV9MR9BE.'(@?)87!vt_>@)QV=IN/bhEBG!CK":@7<582707 6<7>6?FMOU(W_.FO /:Wb;1<09>D 7:06AK9?GL,26,2.5.7 >J>LESJV$DP@MIWap,Thc|,Yo"?T K`=O&6*;BP2A *91AATCVxDE[Laj,k$}%XqUpu~| ds4RjOEW(:dt.NC:}9u-;28=+w!3<,>/$uw-0$""~)06,@2(:5:v!z*b=u!y&}-<~)96.7~%q310,.2|1& .$)0)/7 A;$@<9?76;26:+362@,&29949)//6.,-?;-#+:,58-)$''/$);+&6/=+5M19//$52F8()-/&>EC=22,+3,.)1+,./87*321761DA8=18662*H<@;F:82O?C)1417DE2787@<>9<7=6:C32D@9OHDGP=;AP+651890C/7=8.+1&".&4&':-:<1"624/D4()0+4';12C;96/.72217;<<>39@691821(5:248?2:>87@6385B=614E<8DF5;?839;718FD=7;=B8TTrA3M -F)@ .G]v?l=T$; *>-( * , ;P11"6-  " %  +"(?#'> #7#7)="3% !!5'<1G 2K#'A9T',G-H'B1L#&>)? '> $>"+F"(@&>0 7294N.-E&#<77.B$,>",(!2$; 71F#*@/H# ;5>W14 -H!"=8S**E. +3 #:'A3&<'/ *?2I$'#/1'5$;,C#:4.B$,,-# &6,1!8/E$#: )*( "%+ #72 ,G+  + -$9' +.B#2/.67M"(@E]*6Q3Tk@ 1? ,6$"+,87E!%7(>)-'=#92G#(<&5,"('#4.D%*! " ' + #6/E6M4M(?6 #: +"9Sf64F&9 "9 +-C$?>[Gd$ <96A}-41},0|$z%rw$w%w%w%1v!|)nq%{0}0v,k"u*l$o)o$~.t&mhk%b}^|o#hn!p%t,f%g)]{ c#cSu fG8ic};m)r.w-3Ztbz#`y'h0[u&h0i4a}0`z/a|.`x,Um#Vq(Nj![u._y3Uo+tH^u3Wl+H]':Qf0CX ': 7J)#/ eTk 8S 8S /M Zw0QrEPOm4PQq!k>9Z:W0L&B -I69: +C5N%3 +,+A$?54P/M/'B&A"< = +3'=H^%CZ#*CH_^x9:SWo:5LAU6& !  ( )!#    $  0 #6,E8 0.& ET2`u<[q:?[&6PIa+Yt6iB[~2Ro, 3>Y 0*@!7'./E*>-"5 /E 4I0 6Th7/ + ' +!&5$/?$  !2 23 6 4 +.0 &&!1 +pV&1I'= Kd3  6 .E31 ++?''2A&/& +,=!# +  %5$  "FT1* 5J*(=%9,:# + ) 2 &0*t4JTN]Vb{/"<`}&p+Bz2t,G7d~*4C]0J /K Cc$D#A&CEfLk/J8T +m2)B+A*< +)Se'8K 0B 1*=-B2F.B(9 +< 2E 6IAS5H +>R2C*3D YoS<]Wk2 )4%<"7-0A8J,$51/5Ge53O&.5I' $ (<+"4 $3(Vo=R3C|π"#41E"'@ "%, +$; "- &3  cx+0A,6D BPWf(zM#3FRDP*7|Cu3Od n)Jbbz%DZ:N );0CAU0@1C/. !+((1,)# '*.>(2+:521+C38N*11/2)411/5133)+,+93"6)(!$*.0.?757F. 15-*$),&3/6/(.+.-F.':.+-.&45++.17<1HEHI=:9<2=B8RC>>DG;0;70B67I/+.:#2#53(.&~#'""&9;2/'0,74411/29'6<.8'1))324:2()15@90978;@,745F@H>??555G;DB5@38;A$469797646H=N@F@?9:9?<1897I6C*G8/17,1(.67209/4261&)738>;3/3@D4?+>8E;?:?9797=39>><;<=5A7LK9F==2<=\v8[ax;%9'9+< 1- 11 +& /?% ++       2,12.) +&) 4J,*B!5?W19R)0K8S&$,:)+ #$2-:+;6K:R(@.D.C'>": #*( * %81G& @S &: 1*?Jb,mF`Qo)#@D`b0j0a%Vze0muts#w#+|!(z#y"o)t+x'w"|$-|"fm}"}#/~":,.}-|,5.}'*{!t<>873>,.1~%,0~'w/4$>@91t |)<983=521')~$12A0.~+Yv ~(~)304352z&03-:y#(t o{({)r#w%u#r v)Zzm%u.m'g"j$fp+p'n#k _w-c@[xe"i n!n#t(a_}Rqa~&g*Zz_XyiLkWr v+Hbk'y/LfGOXqb|&f+g+Up c}0Yr+Tm%`{0Xo$Tl"Rj"Tk([q0Wo&Tl!Xn"Xp!Yo"vC.,A,B Z(>Zn3򣓭IG,xz+}-hD^g"C8L9N5K/G +=R$::L@DY 9P:R,FZt);V Sr'Lk 6T ;"? ,Ix^}BLi+Us/eBGa$Lf-5I  ($*9"4) 5* + +,=%:)?2I*&@/JZx4eBJh$`~9Zw8(D)E >%>/F*= )?/D0 (?9J!0 (:$4)&6"5 )<Um17B]'D5ODb)/E#**1 +%)!4!6#5"$ " +(  7E.,;J%0 1E`46 + * '?G`&f:S '@Fe"HfMj=]1R2V Jl zWHd#$> +Lb1 . -=0Ma9' * %0A! .@3F!%6    -<%5 1%%  '0&<"8 83)*?2F#%+( 6 q7ZMZs-n &>7PYtfVv>]=X'g-XdDa1K/I /R]&2Q9YIj!A][u&UpHhWu- %/F Pd,;P-A%9 4/D(> 70''%4E "4)?0D 0D fzL*>WO(1G&@+ZxESo)Pg'xO# + * , )!n>p+Pg&s.!./A" 2*@0B!0B#'),. +*]mD-5 #* +Wle{/r6WkQe,?Zr'K_L_ -=,=N`AX{1fg!WpC[+A05I 3G4G9M:Q.JNnv4o)RiN[#-4 +RW76<AE,13.057027:"DI+BF'CH"?GEL!;C,3 187?2;9B:D=GALFOHRKVEOCN4@F?FHT$S_0HQ&19169C 5?U_13@ @Q^n7br;HV&Q^/FRR^(Tb#[i(ATr=RdVj'DWH\#=OTe3Nc+jHXp2?X=Uc}=f~8x=w4)8&:5-5$!9 $#$&2:3((()52(+#00<1.-2)1#166(:2;.'/)(65)/# (v/*/2o@1-.!,02-*)+-1-0*4* %63 +%4-'2F4/G?9;@1>:99:338<2/-7G/,8:.31+4.64=A+9=CF>;C9603I><=;-)7(@#7*309:-C2/-*-0$/1:-*556A;F;=7PWLA>:;E672)7+-1665-3.52498E?8EAA7?;cU?D;8EF=>DCI?2U>>@D;9;4C@KA8=>=I?BBCILQy4n8rE[u3Sn,`z:(=*> (: );$6!2 #8&;:P,&;( !/(&% +$  " ,1#6( /:R1%>1334L'C\53M%/J -H)C2L&6) )) 2$;1/* 07(A *D"":*" + /3(@2.0, 3*=!122- +*D6"9&>6 !<*D4"=+ 4)< #6& $(9':2 *%=/ ++?U25&;31*@!.H'0' %91'" +*'< !:/0'?!;N2*- 3$3# " ,:&"63)$4 ,;)")$( 03$6$6*.B-]q;+A/-jQ2"3\n@,!5 &(&2%4.@=S%$6 !7#; &BPk>b{NG]3"73F#! 4DX48O+- "'0 +& "(?&< 66%= '4Uk8 "&< +.G-Hb}51L@3IEk1=y(9?kB0::435.2,971~'('p&223@lD/.43w&{-q#1t#w"+~(rw"s!k.{)ndn u)mhx,dek'f%i'b~p-v2f r+h m"m"x4g&^|g%h!el#k"WuWwf)c*] ^\{^~h&be_y[tn'{/K"fe~Zt_y"\x!Tp Ys(Vp(\v-[t+]w+QiKaRi&`u4RjPg[ru6x8@Gh5wI3G U{/OkdSZuTo$;AW KeRoA^@_6U ;;Y?[:X EbFcGe!=Y-K ,H 5Q0K6P"/I6'<"7,01 "90K&B4,$C 0L(G(G2O=Z52JUq0Fa$(D.F =T8I" !() %6/+ (1 ' + )5 +%= #; +4Jf2Tp26S3(D +)E*D 9 +:>Z &A!8 +-/D,->!# ) + (^nS /E-E  @U-5P 0N %E9Y .J\x,_8?^Dc9Z HjDi'K/M~2 $ '3 $- ' "61&:  !8Mz[0Pc4/GOg24     2 +)=/D#$!!2 :P$1(<Vq;/&=&9'%<&>(;/C0% 33H .B!3 ,,( "0!$6- % %3)6 +  + +    & Zo<ts az t09U^x-KhRo Jd.-DpBWl+?L^q7<0NFbYs"Ww<]i/b})5I`$*A,B "6 +#83 #3L PxMVo ]t),%76J+A Nf*^x7*e{=Tu9>RPc/&-@3uPmU6Rpia& )%52#$ +%6* #& # "*? +Rc"\,9&2Hd{!ZsTjCYSh#,C6KR ;O;QD]Xp @[C\AU co6%-7=NU3.2AE'*/=@ .316@F%>E!@G18AIGO%2929:B5>9C:F6A=JEODMMVJS @IBK2<=FDL"EN bl<5?.8doG+: br?@QN`)H\!Pb*JX$DP JW'MY$T`&ERAOFV@Qbu5@SDX?S(>Me+pR_|AmOXs9fHl~LoUHi.as$EPTx4TN?O?9?B943D7*1126%9760=%2M)'#(- '%# *. 23,?>I341;<'?(*=@J;D589607*/$}x41&37>:/=$0*)""#2+-//++-!& #))3"81*.'$1%((#,$%2)'%&+9"K()71%)4(4>#L;+'1& $#0'//'+<%''5%/;& $<-++&2@/)><3?9AHG:2308203,-+#)- 4-3/$8#/-81///7)/./:46E9.2/&"%4'+603>7,)5/&52$"515+1:53<3<,2*32%.'./8/=5/&)4).2*5%326129/030$B,'/57769A;C7=772)FA852@>7-3+>3."B6$3<9+:+15=7<28:1?=FD5:2>@D87.1)+ +/&'%0419(08;6+95/;@C9B8CEH>J@B=JD@DD@A?>;5F=9962C:>B=s)]q!Qg"=SVo/Sn-3P9P/C0 . &85J!*?4 %=5."&  )   1EZ=0 +#8O-%>8R.#<1 !: +" :&@0K"$$)1'; +H]&DZ()=,(>(? 5Uk;^wAOg5>T-"$ ZiI@R+0/A.F0K'D'@+AE]53F!+ $6-B"5h}U1A!'"7L1H#: /*>0D2$< +H`(!82I9P+D +4Jcq?KdJBM7n!>?/267&-;9?4+Aw&1~#v.,*42,2..554}25q&|1I3x(<2y-9@6}296E799p%}3C88v*3*27,044F}*.8o!;6v':x$y%u!06p$dv&x*mdo%u,t$5m"|3co+z7g%h%q.t.u1t0k'p&w/Ut \|l'_~g!eem%p+j*c'[~!j-c$g$f%c'n0Vsc|s/e1MhVr([t*Ql!Wr$f3Tm#Uk#Wl+g{:i8Ul!^s4S\rj 7e~Of0,DZCli`z`z\x7.R]w'UC_<<\ \z2,J;7U m>>]%D>\&Tl=$> < $@>Y*8X =^(C_*cI-1"9)@*C +'@23JEY($3    +N^C- 0* "-0 +0 3&<(?(?Kd4D]*4M0I4/F.E-#Vr>3NUn30 # wT,D=S&&:(= +Te2L\2  4A#3G+85M(@'D?[!?\!Un8-G+A+. 1AXhC     + '2F$'= Md>Ie.,L3TA^Om!6T "@8T5UOq&e1Bb'C   " (B]*) 7 4 $< C[6I Th1:S'* %9-=T&2H"#4"2 +8+89J$&9 &58P 1,B. ")= !*;'8 1- &34D#&911 #3   3( ,;%3)(  '.(8 . ,9 +     hxQڌr;Ezf}*./vNiE`E~"MKL/HD[I`QkF_HdUq?i<\]S?a 4S /IC\Dd"Uva~Q9+"8'=+0-OiEZ'@{.kJ`"3#9!:Ws)}KMh(Xr.n@!4*&: "7D[J]&.0+>Qf3Ht/1,0F}.9f.Ek]# .0DTNg ~/*E]w >7By( &- * &<"0@!& +& ' 13 %.IzXXn.AUAP!>L! 0 -AX +p4Kd2JNdXm*(<*=(:=QDYaw/RhOgSkh(g*u4D}@E#@F"JP.4:3:1<7@D>DLT#[e8PY*7A4@O[3_pD%:K^+^t;Oc)1F +[p2O`)Ra.=I?LQ_#KZ@O8G3F1EWk*_u2Zp-4eBlKbEqToRoQvUjBxFKJz?J;RJC6E;3><576+JJ5;A2?56JC:6;6{ -}2-#)$(6:4%,!=K6( ,/9582'*,2;0B2GC?)0,@5',uv}Uu.'-.'} )(#70 #+)" '%"#"&-$$(0&!5 %3%'8"/'$1"%% "&,,!"'.3 $($41!?9/76EA5= >!*$,/"',<,0(%%59(88-3C046.3+1/+=7:7<;9625'*.)#%v#'A*5",B/07-%8!' + #,x%$5>'#$y  +##.81.'322+(1;2",1)@<>@6=HA=E@HPB4PBE;?MAH?2615z7Ld:P 6MNe.=TG`(2L%=%;(?=P((=2G!753 5%!2$     $ +,>U8!:*)B9S-)C7R*?Y16O((?@Z57&A8Ng@/I#8/)#2,& & ( )( 0)(;!' !21( ,!64.%+ &:3I"2L#,E-2 /->"#4!#3  0)" +!0##  '.%2$ +$ 0!3 %(#3 + ()*9I,* - 0)8#&6. +!3 >P!(< %74G3#9 %;54K@X",D$= /( $5+,>.A%; .H*D*D'=A[07M%.?)<*A&;00A0 \rB#; 8*B4J*=-A": +%= Rk1Zs6-G +Nh+/Je@F_g8Vq"LgNid|$~7?|,/^{ ^{ ri{ %*~"&+,-*e~",08|!0?63DL&&:4rn t)l%u.>s*v*IAt*~5X;}5~2}0:|.}1?v,6~2q$r&A7G/:}.=?~0}-/0.u!z&0/|&90/7+7y%z&u!l4v)v+x*~0jt"qz){+{,gz-ly+q$`{k"iu)y/y-r)q*l&i%h'i(t0p*r-j)n)e!l#s)]e\~aafm$c_~d%]!Z|`!h'l,[|d%Tr}:}9Kf Lf s'o!Tq /KPl UqWr#a~/Rq$Yu$Yv%Wt i5i4Tj"Ui'j>]r+`v-G] .CLb B[n#gn<[o#3E[|8Qlk)[Wn@ZvSpuh&'"1 Rf)?V=U*B)@;%DOnNp?`Ef>\Qp'.NOp:Y d:%C<"?<&C +[wA%@ 2L;Q+0 +#77L#&>(@&:0G.I*G5R$? *J.L&D,KSp9Pj2D\$+C 0K0I-D,B);  + " + + 2$?3K!>U*AW-1 .BatO +? 4>Q >Q-!22F8 +AZ!EZ&& +4J5N7LDV!#& + !9 !: +6&@ AZ %B*G 3O2N$; #: 0$8(9(+   + + ' Ma5%`{B^z:5+6O2J!87PD\*>3F#!73J'@. 2,$! $ +,:#%+;/@ +!0-E+A$ + /@*2F- $29J &8+'!20 )&;7M( '  " 3B$/>)*9*:)   +  ES,@XZ_suqhi<#Qm-GTp~[v Ll@]\X)A "C\C] cL0F5ry- *C 1Tq8Gb')Qj9O!1 +#/oRj|@n>K_&"$:m.RVq]rPci5JYq!F}9Zl3M`+1HVl"Ui]rfcRZm0 %* '-&    $(Yq;Gd2M/F +BS 0 /?=S Lc7OAZ Wn!F[N`FT6D9L3F=Q4JJ_]s$Tp9V!>9WKj +Ea >WUk(8I 7G 7H =K9J.:?J3= 9A8=7<@F!CI$AHMS)?E8C.;3<4;=B@E=CMS%NU$LV!LWCP4B=I8C +>HFNFN=F BLPZ$HSKV(IU(OY-V^-8B'38HpU3JOf3G^*CY#Pe+Ti-[m49I>MWd)ft5gz6EWh|9Vj'Wm'^u-n=l=^w5``~>_}@Wv8E_%J_'^v:oGRo `~(h/r8p1s1t)j5}*0@:9406;A.760;33*17790<>1?3&/,.14807/9J1~|zz7#;:?C4916+A-/79:@=:.+=049FAKIG?CIG?DA?C@&<' 32/*+#:1E( 40!42.5 3 1++-8  !%9.%4F+"       + $ +' %!#87L)4#3#  +  $ ++0#&"1, $3.<,;2AJ[(.>%7+>5J!:Q$&>.'; 0G8OCY(^q?@S$%>0. '5H[r!]u!>VSki(&>Vqq-3d}"/ .ao>?MAV)@0LD_)GOk$^~,|GPvHkZ{*Yy-Ki 2SIh /P 7#BA^*9V"%B 7S5/"=&<4?T*6KB\'1G%9-$A $C 5N)D$> /L=[$Da,,5$? 9V%? +/Of93D&)  DZ3@X)Cb,/L/F -0 ,  kRe6BZ'4H 1 '+:-BW))* $5 3HQd+GW$1<! &   AW3)E"@ +0/F0J.G /M Nh-,E3 3-$)=/ #2'6euV. &/  - ,DMg Db*I +!?/N ;"A(:_!)P7Y_}D=U$&: ")#; B\$2.Ol*Nn Hh Ec(He(-F F`%;V?X .@ ,"8 7 + '=N'@Q' &  &;I(  +*$/H4%;*)8\qD-D X2CmSpQU,D6NKc+B6>087>=CBGMS&IO CKPX#\b,Ze,ANKX+(/+/05<34=49<2/,7B89=5}-(+B'%3-=J:7=:@'AD=::=>"!42(,%3#B250*2*-"-;&5;$/9)%?!"$ -4%#''"$4%%/(*%% 9"*' '*,!4-.2,1+/0(":"~9+)/,!/+**,1$4("(B2%6,*"4.8*)4.1'%42.E*/<:-#"23#)/,+%"$'&% #3,01---2'# |9}-96A~2 #-*%~ %%,##.-#')&!$ 'p , " | {!(%1''*>!x,%~##| 7-%3%***(1+ 3))#/.4-3u6..32%+26)9+/,$4'9<0+45;81@<;38E=9;96AFD9<6<52\.7"0(,$'3/659841:H9;HKBK>DA6:B6::548A82;F=<;?AD<=M?DAFKBG697DL{)XlVlp@FcNi+=U .@(:*?-E3M"#<0G5LJ`1(=#8#9'</I!+B!% +    2<1%1" -*:$"6,B"!9"<.F$*A$=&A0L!+F!<(B/I",G2L 6Q#8 5, +!5!1)8!%4% $ & 1!31# ) 6!;'B/ ++?#" !  !3:X,-M/O"2 +3*"1/ (. , "//B)1&&60,?#4.#4*=%  % ,7)<7L)#;4 +  '! ! /, +) +, %.' -#3 ( $ ( +4B(-( -) , %50BJ[0 3 (8%0;Q)6M#("5L`'E[Vn1Rh.G_$Me)Ng,*>)>CW)E\,9P"2G-B:N(/H9P(7K#4JI_/BX*?V&Zq=9VC`)y_@[%Og5,'(5,DB\$0Nh*.I 0I 6OQk%Sn#Mf2O>[j.n-By,q'{3q*k$Q4~)'9~$6}&,,-"+!1&7B1.,/?.u%z/v0z3q)9@75=Aw,v*~4z0w+|-v'u(u%9Mb}=z/y,u(q%1213y&{)o#jp&z-y+~/ny*|1z/s)w*u$})y%/w$w$}+v$jv(y-ly*2s$u(q$o"m!o$q$r"o 5q!s$q$z-6y+l }1q'n&u.t-w1o(k$r,ec_\m&a`k#galkj#h"e$Xzi*^n(~7[zVtk(e f%GaIe z32^{%{If;Xr0PZyic#c)NjHcUG^=V @ZE[4L3JFa dX-7VSgjTg$/Bf|2s&< Td@'1    + +-D4 *6 5!: 8O%1 &5 $ # >N"$2 1@!@T0(0B+DT+/A/%56H J].:M(# " *%+.%>Id/B],%24L*E)A^y?To62$= $!) +2##.)<Od:1 ((<#:i@:X>]9C_'9Y;[ (D-B^-5 #>Vp=E],7K)/>&";R"+G If+!>+#? 9*G?X$3&Nf.0@W'$ 9N(-D-F 5'-AIW9   + +NXB +3G!&<-B,.+ )2 + !&:3G! %"24C!$, $7*'+ "56G+"3), ' 01>N.!) -"$AQ Zj"-7&3=Sj&VpD`p#k CX>SF\5L)@/EUl(gQ#>AZa{mlO.P +>c-RdH@n!/+Gm,b9|o t&Oe-Rlm0]zTr^Tm&9P:V<[Sc|:-$?A[ K\(5:v9HlsOwS9Ysw(4AQe@Wno na{=WH_6NZp$-; +4LTll%Rh3:Q7J9ME\LaVmbx*?S@SBR_o.5D 7B.6 3::?57?B7:DK"AHCJ!2;199@2:6>6>?EDJ#DK!DI EKOU#JOV\)U^&Sa(Uc)GU;B GPit6IS@KCOES^k9Xe4Q^0Yf8nMmL]y?dG^zA^{=iGfDb}=>FHC@B?90F0429&'+06?54FG?DEI9@;KB>>5;>?=@8<<98E:G?EC?F=58>B2FN:cy'=U Mi$B_ $< 41A%6(>$=$@6%=1H$;*?220D /F!6J,%  +  +      (' 6P.6R-4L(- . 6O(:V,@\3&A'B-D4H#8Q'0L,G#8$5#4-!    +$'3A,)$ ++'!3*@"(=1 +$"6);"&.$9 &*G)J&B4& #(6O(>X1#=5 -" +)7+)  #3:O,4/C+<% "%-G!Pg#;UMf$F_8Q?Z$F^)3J1I-E(<"76K%%;1 !4 0DexM6H!)>)@-IGd0-IHa.+@"3-(,?/C)?*C ?WBX!7LD[#>TGBa!}9u+m x(t{&3mqk,| } ,%(-30-*2135w*s-s+h!94=x)}/}0w,v+o%v+4/v*t%5y&98r'|2n#o%o&u*=1y%0,o!q&w-l"}1Ap76q%9u*3s o}(x$t y)v$p!~.n!e{+t%w(1z+p"o!p#s"o.=5v'y-q$J012v)y/o'u.y.r*=l"p&k!n)e&o'r%q&ghibcc g%i*\~^o!u#6RpYvSm`|j,䗱uZyi!y,^5T\z:p9i"~7p)y:jQI_up0TjTi `wz*w*Yo>OMcPe8N +BV3/G@[KeLf4MXr Pi y,Mi8Mge|XoZp& 6_w%>U90J*G0:8:4Q@[+D 3O,J 1O8V5P!%@37S!Jd6&?.(>2 ,A/B,*1AZ($A +Zu>: 9 +*/H8:.(I?],4-J+G ,I)F 4&> . -9    +KbA+ -&:/%=- (9Ml0[wC)B &Tq9qNFe &D&D2*F<(E&@. +# +>R3( +Kb2"> (6 ?[9R$';S!,>!&=>T%#IY50 #2K4L&=*    +  +    (&2A+?3 2 5 "9*,(Ri>"2./&$'&(( *9 !+&6( & 1 (  -! N_!YetJ"*,9 >n}:?MTd Ka&PhQpUmUn J$Skax0E 7O4O>W9P|E_ORpVvGg JkUrFbSn&~"c[{k(X@U?1EFZ Un D^Fbj*2I`Mek6 +XobwI_=S!84KE[!0YzDw !'" * +(!8 _v<2J9P#6" + 2- 1 o3Xpi2>VJcRh C[&94B@M7@>LKX9I JX5E +/C=PSg"CWTiau/2B@QFY;H7B5<6<499?499>CJ"5=;C6>5=8@9A3;4<;CCI!;BHO#TZ-GNRY%QZ$P]&Ud+Yk/Vg,AMCNO]#LZ?O\j1GV\l:qRPa2\o=`uBkKiJfHZv:Vs2kGmDi;j39B00.*#&.'#'!(' &(& & 4$*8%5/3$51-,1039(3-/+1557>623-/.#=/*(3-1/!**3*0.D2*3/2&(820"1-=5(A(0&8)9(#)!##4&  | !m +x!9-+*pt&, $7*E(&,6!,,,2*+,$$#+-}p{"#fkt w w  r +#'''2*2 2-4;91)4.(9/}+"-'62/!n yuo`k8%,*.3"/%"39&+$0",1.(03.+?>6=5?=1A7;2=EC??E>?EIC88@6B987;556A8=EB@@DE9<;AB;>@IG<:CA8:NB>FKNr(e+Kf0MMj+Le-3I 3 1.B5O'#='@#<0I)?"6!4%8(;2.-'  +   + "6/:W/.G##7$:6*E'A'A0H$!:(A$>$?%= 1& #    $ +# $ +#6  .6L('>!9/$5  3!5 :#@:#>&?)A"*!DZ6Le=., #)  '# #+")=$:)>*># +*> 15% #&:2-?$+=!  0( ,. (= 6303#/!3dwP- $) ",  +( *! ,$3.0 3!5. !4)0 #6>Q#Xn*_u1Qh"/GjAId#YOj.C`!Fa%^y<0J": +(>4H#%9-)EW2#4 !1 &%5(:">1L6 +"$*< 0 - 1 *;9J2E3DNa/DV#0@/@ H["DY@ZLhZ{$Hd"v3Zw!QnPma~&Qoq0d!i }24z+t#x'q,&zy~u.{'}''(.{'06@z/ez25;94n2p%7}4o&r(16r%ggq!mw+s&q&u*w0j$dt(},.u"s!k eSs ciy+5}0:={0;},4~-|)})qt!z)}-/4u&y'w'v&6:{)|+r"-|*~,z(6~0y-y+1-.1z)x+w+x.x+n%{3y3v.u/u2m,m$iw-g gf\|f#m*^zaeh(v$yn!g%TKe2B[l+UxabJFzTq`|k x,=9f*i/h/Lcu8e|"Sm `l#KePh?P8I AUhz:3I)>Ka!IaNhFd5Q t8>ZVp1IPj +8Kj nTn:V*CLgPj$7%?)D-J?\4Q>[Mh,;W/LA^#Gb/ '3 08 8,!;4J$2 &;2 +/ 275N*D*F?X)#= E^/6+G.L BJl22Q3<5S3Q,I2L>U(&   %"%1 ) 9R/ 9*$=3 6 2&=0 /!- +2>& + +L_6 @U#gzF7L+$5 !7 +-" +$5%%-@8F$ /( *>!9 '? +1N.&>,E_yC0K'B:U2I! =%@Md8.I)D-F:U5Q;,2/J5R10*?[* *&C!=63+$? 8"6 0* ) 1I!/9N#2J& + 5F!)5G_-+B1 5 . 3 &EU8"   + + +    + ,<5F$!,,D!8+C-D00H/2D- (;*84F"#8*/ +4"6"  !3/ 0 + -$4% (%"}+Yi"! +Pd&f=BFOD[i.0EH] I7^yUnl.6QXu'Kg2I1-GJh.{&NTq=`%9X !-y3OpVytVo"Qr JgUoMfimXxHh +EgCe:Y#A.OLg [w g.Up1O3Qi4(2">[x$HdA~?pPxNG\?Wy7`w0:K7Hz/5VKc8O =Q5E7C094=:B:B:B?FJP%IO%HP"R\.Wb2T`-_qN7GN`,L\'qKizDpLuWduGnSnOJ`(fCRm/Pj+`|9Yr/^x2xJPhYt"p8d~.Wp!Mco4o/y4E69==:6C4;=O;?7:NHE<;9D,/<<,|2+B.-*I0MF40235=;9>7H:)28-11604541/1,03-6;2610122#.1/2426+E8L43."+2".(.:<94:01/2"2,9+57A@,JB804-,+$#-'#)##()')!)"%4)-5'6-4%/,.(&- 4()4+192.=@(1A4/5-836-,0*474845458541#7)4,&+1-/,#{$"$,$',0)0("v ''$")'2&1)$1(n o 0100  ='))$+$9-,'*10 '+),'6.|t (u!y$!94&"1$/!*71+K! &/,18!(+(()-/+#7*z#3<1$31(H3/7-')(#(4"#%+/('8764365,6357?HD77BA<8J=>:G>4=2::B;=?>FAFA?7E89?C;><::?;52584185:=>K7s+x=j>@^?[!:1( 49M* 7)A,E=X+)C&>1 ) +0 2( " +'"1# '  $-$(& ( +&B+E1 ";4 + <8*B .D!3 $@$@&B4'  + +    #  )(=4+(#9"82*  +%?(BB_7+G :+@#6H-" +3F&,E 14$ #    -'5% ! "68N&8O%7K&, )$)0I'+@%;DZ4#<5!6 3 $3.= - +/ +-%>9N*. + #4'$8&8"1) "2    ."0$3-"26J+(>;T-. )!1 'wP~S2HRj&Jc$Ga!Jh']|8On)On-20L+5(<'<6J (<?O,=P)+:.<,=N*1I20Ja2Pd7 0/'4&4+9'6-=:KN_2Sd76F3C8JYn/Qk'?\Omc([{h.6R :U;W Rp_~"Uv^!Z{bh"n#q%q"u#t",Ehv -/u"/%$-|)}+<:Iej kz,t&Cy(|,z,p#{1;r(>w*lgq"y*fo!j~0q)u-{3g3i|+r!t&kl%`f![q$2r!1z-|0_}p!~/p!s#y%-r 3iu$t#u%57042y&}+-~&-,t o5q!~/30+~*.}-{.LdB[CH:U Oqn$Z{^|>9xC-9EF|>n4p9}DuY$%C07(B2  + #36 925K      YjD0E %nH.C&$7&;(>3 # "1*;(  &$3 +&6 0 -E^)Le01F/=T*&!7 Md8"<Ii){a.h:B`B`Tv(3S @(I<\7U/M*E0M< 6[y:1O4O2K!: +:P";,F #/B'  &5(9  +%"2&5! N_7' '>S+%.'< 2 / +   +  +  ?Q**;/ ,@/ +$:-E*C*/.1#: ) 2@+3A#+ +?!5 4 '9) =N8!3$"2*8& %/! +,#4 NGoN>N7G?P 6HGWCS=PDTFR>H8>4:277=:?4<>F!4==E/87?GP%;D;C=.F5FMDC>2.B.7E,0'064J9/792=0?4@>79=76A51--:()$76/&8+)0.+53/3*&'"*'#1/5A-0G{4/759,x2-%!%103"wtv$~!~ ~$t"(t%4@(|6)v!p_ x'z*w#/.""& &-*,-'/2-9282):657/02-/-'. :)-! #'6N(50&10,*.2)01*31@2:/60E<.2*%+97+!*"'% *!2#))-y%~x}"/'#,$$-46*00+"2#)w%0 0' !*(~$"$(6D&-311% %.z{)#+&)w".z-5+2 ((#*<+,40(85,!~.|0#0/"+#7((4@64,089+,))4" '%9,*74';<3%32560/F:6D?9@4B478<=/7C;9BB>@BDDD;==;8;=6544>?@;?CB3,10.1387,557;<>7?<25@:345*238<7m-LmFePj#jB+C )>+?:P)4 ('@0K,G":4%:"4& +! # $3(7 # -'2$(4!0D%.E"/ +"<2N$ 9+/I 8T+.H 5O+%, 3 #>9U)/G $", %! "   &  !%(3G)&<*?+@/0 %9$9 ( )> $=82 & !$6'( 3  %$ +.+:(  '"0& +:N+ 5 '=/D#&:2D$4I&$- +!4(=&=2 /.% , &4 $)-F(@0 !8':&75 +'<- - (8)9& )% ')$ ,!) 21+0F`7}h#1.#6/C;RI_$Ha&?X5RCb]:=^Ge.Rn6[s>7O7M ;Q#7N )@!78K$( + !1=M.`uP$914 GX-"4 -#0 5E 9I#9I#6F)::L 9K6I9O4KHa ?Xe6]x-PlSnFcSn'Mh%A]Sqr/^>`Dfc]}@x/x-z/w)5b|r 24Bv#u")-0+183;l!|/43hz)0b{2w(o#s'in @7t'|-7;0{-43o 5~/y*k`n!46x0bNpaat"r!|-p#p$ar"//w'z&so.@2-|)2*{'9913~)~#y+u|%0s!x$,;-=//~1s(76i r+n*x5x5x6w7n+o)m#h+6D0w*x-n#p&a}v)``{t!\y4MRkc|',D0Gr6cLmlk :.Xt|"gnSd'i2Vr!o:pNZs!-IFcGcPj_vFX ;RVlG_AX6N 7L ?S9TG_8S CaOp>Wv%2PJk<_ Ro"7UJe=WDae(+EIcB\:T?[;Y"@;-G0K 954*3 63M#$:$=0"9 BY+D\,42$?+F!< $>1L )02 ++)C\.5M& 2TmD+G323 +6';) !$ %*D-(@4 + *9 + *8G[.5,Ma(Tj--"4 +);&;0 %:  !2'8$5+:!0!0?M3$4' "5)3K.F%> +#5H#/3:\ Cf Be PqPWz#]+`1Os Nq!Eg8!=Kg6Db%BaHf$Ec#4S*G '? 43(B 0J'.='    ,+ 1 # ($/F-A       +  & 0B2G)=#>Q+#,A!6 6)@*?Ri90 0 Xo<6M&& J^?.B 4%6) !1   "!4"22Eau >s7G\5MBE,B-AXmnrCE_,E5O3K Xq'3j-OiE[ 7N6P"A*J!=.K *Fj4d%e#Gef EXJn]~ (?\kOpl)-K,KIi-N=X P\s&rBQ2 +C[#_z);d++D 1H*eyP # 1+')#.+3I6N F_C\:U9Q +Ne"jD)71> +6?=G5B:G;G DQC%IQ-JQ-K!FP)_mCP^9erP\jFasIQf9s\F\0_pHYkBh{OcuDj{Dcs;pDbs9k~Edu8|HIy836931:$+9%+3!%.#+%*1*,0'., ' *',+'35%3*7.<2:,3.71 F!yz!ouqz%tw'^u){$p{'40)tk|+h`[m#g{"&%3# (2)+$,0-;6+,':,58920)+&,.+(70$:52=+66722+/+(4:3771263;=2(+I0.8.3(.0.6"$*"0$}z~|!3$"!r +!$(,"'")')*}3(#03}}!| !'29<33-,052+ "" ++;$64!$'!)-%&%'$)3!)p 72!3#)*'12&.'!;500<63?,(*(0+, *4%F6:4.5@5%*7$%'-.!($5*41505)590)>A<2-=:5A+92+3-+.8*;2A;H6GB7?7F>>:57:6=787-)-+550=*6@>08@?=M0D24B3&77(,;/~,IA(EWpa|1;S":' 4F1GVm;'?%=+C%;%</+"0( + #   & "5(?#>!=$?3M%9Q+3 !;/J9CZ74H(*+F9&?1# $ +$  +(! ,( '  #! 1C')A5&>&>6Ka<2$7$6&71. 700"4! +! +)%3#8.  "$/=$& ++@"3#6( 3/C* D[5&>)/ 0%7+%;1' '"  $8+A&@4L%' &*F[5*=++<#+.& &*& % +# &5O),F9N(5J#&));#5 AR'2D0F3L;W4R@dKn-Rq4Zu>BZ%\qA@V%)?&@ /)A*)> " ' &$81F%7 .- +9$4 "3 0 **;"50C.F/HF`#1L 6P 2JE__t,i5C^Fa)D>Z Hn&u-H9v*y,y+{0~5A5s'r#3z-C70s*/88.552x)98??z)x&{)4z,x,m"|/x(g1w&t$q#n1u%y+u%{$v!*)u"{+5v+}0:Ey.?2|)})m{-v)r$-u!y&500v#i{(322|'-.6**)x }"| ;pz y#,3,:531v(>An$r'w1x3m-u3o,u2<|2s)l 2G\yqgk"~:f$C_9U]u!`V2hm Ed~&Md0ESow*Lj^{So1/Wuqx!fPlo6n=PkYr!\x"ZLhUp"F`8?ZB];V,G 2J B[";1IE\."8 $;:Q($>"=+E#; 7Md5"9 +'>'(A6R>Y$5P ,D9P"#7  &5"0@", +. :83 1*>0A -1H#@Z1)$7&7J+- , +)   (0   ?P++#: +H^/D1%5(85I#(= +%  1,# +""1!('$<%= +1J1?!-:& 5YPrOq>` Xz#Ad *LCd7X c2FjAc!B3 +nODb$:<.L*H,G$< ';11KRl?0   +&   "*  8 +(>6N%1J#;*Og:%;+ +*8 0    +,<-A+C*"8&:-D&8 +1B%%"40E    $ +.> )    GO9&3"*&0,AQOct(9R8$Eb7ov2RiUk `ySp*F<FcRn,6-E>V.=Si@ ?+E $>*ICc<\ 5S"C@bk()LZ} +o=Gb}5-E2K[t:5..G2Ld4f0l6=V 0E^r4J^"'7>JAN.<IYDRiz2]q&AW=P4D 9I[j(BMMX Sb,O]#_n3{P=GJQ-X[D@E,?F%DJ+BG'CH&=BEJ#KP*JQ*?FctJWh;Re1^o5eu5`p*Op5i~1eu'Bx/1q$j~5y*/v"~%6%,A#B 4-5#69!%0 s'522~+}.},|"=430CM8:<-)*/099!:067=4=5.1.960+*&##)2,$+-(/%":%$-,#%?!-'10B/6H+;@G5>02-"{*yy{2Xe/rt#mr92Z b^C.k_UzjW~ ae`p+,&%"'-($6, (9512-@O8;;,037$6&1/*):03-61,9;+14615;+1;A3//566>34/94>9=.>50*B4z',% #~|~$t!{zu *'"& ~,/$ "*2&-.|2((st }(%5",*4&!",')*/!0/!#(%$ 1* 4~-&*"$ )#+&&+(%7/(@*B0-'%#!1%9&4<4.546*8|!.."v"&.(13533/72<++*)71"56&.(-4D8=.C6A>?).(-/1=:38($%8770)e3vL,EC=86:;C<;99,0@09-52?)5.,.2/.&#%4255+:;3::@96Q6H-02:0=01,51-9o"@Zw5[Tp\w-7QWk1N_,M_.Nc21G,A/EY0"8,&3% !27I..0,%9!6   +-C9S*"=!<+6#,$49K/' 5J-+@$40-! +  &#% ,B) +  -%8 ((/*A*( 1* 1CV5-)D'A8Q'%;, +* +$7#9-  +'"   4.E1J!+10 -4 5#41 +)4H%#6'9+& (+# +# 079Q0*A*7K(;O,+ !2!12C,=2C9J/E.HFd'Fi%Aa"1N4*@x_*@Ne3Vp>8 +!:'A$:" # ,) +0"53D'9(*():6F&7 0 +3DI\/Qe7H_/8SGa&D^Mg&4M E^Rj'Kb?[JfMhWui#i o't+r#ngbn>8t*u*t&k{,31v )C*-/z#/{%3|*|).t"u"q s!s"3kt)x+z,v%r0y's!x(2}+w&v%~,x ,1tz"z%3l w)n$gy)o~)3{&ou#inr34y&0...{&1()+.2*40&/(~%''.w"--u#},12:}5{7~;Gy5};~=w;Cw8v5w-9|/|2Fv!By+Ji /LLj[y!c)g0~K;T}9Gv#7t/MG^]r)w5It"b9VgOUfQl_LGeOkB[ Zrx 8$: 4 '8 +$@+EN/.?-> +"  +( -(&*B`xF$< 4-&7+,:$ 5L a|5~E=`Ih;ZSt!Ce_-Kk Kj"JlCd2TJi#56T3'D ,H<9%< )? 8E[6# +  +      4E,  *1G!2 0G *A1 +/9 ++F+@/#431 3,9# ' +DS5$1" &69K%0.)A/E( !1D   + 2O`9  +   +& .)8  + + dtN[j98E$7-E\1u*O)?Mg^;~xpc{ex av G`i0Kk*1P/L$Al#;e}&*@+EYr:,FVr).KCcIf)GvL9X Vxj.NqQty(n!";#9-J<[.6W B`/,D\3IDRk'Aa~/@_UrGa9OkWr>5Pm+w;?[Ss1Q\s8*,<6Dl<&?Eb k8b~$p'^xBZ Vp H]Mb etR,> +dz#;Sh,;Q1D -FEa>\?Z"4d}"Q)5 doKPa5*;,@&<`v.O^""2 ?R B[$Oa@|JYxaK`!evN09* 1/ ,*2M :Vg9i>:U LiEda%v7Ic!5BW9P):8G9I .AXk(I^BW Sk|B]r$j}0IZMXAJ9B9Eiv>Xj)wJ4D '.GI3Y\GDJ/VZBCG+FG);<LO/AE!\_9?;u-q/rs?^s1^q71A :J.=2E-C3 / 2) ! #, & #3. &( +.5J(9"7DZ479 *D6. ..H%>-)*9#40>#- !+ !3,%  +  + + +A2 +(D/K"+D+ % %$ ( !$8. +   )$9+># 9P23) +#+('$@*B%# ( )!2!6=Y1?Z1/J.C. +) ;L+1D$+  &1A(!1*  /C"3 ++F(&;&:)9(< &: 0C%6 6H(>&9*("4) .=#%<1(< "5' + 3( + 5&8 1 $4 YPs- @ /*&;,A4I3K)A+CVmF*A* +!-,,%"3!4 .'6, .%5 ++*8)$4 7J@S%-C4Jby0kPo `}!OA^4N8R1L<]_+Gd7R9R 1K 6P3M)A1I +:S:N)>&,B,B 7 "8.(D < 5 '1 %:'/ $6!5 + ;0 1)   + +   + ) +>BY$+CM`$9!% %2 +"*;%! + ./<$%7*5 5 F\/8P#-E61G#7  2KOo7Y%C1$A)GMj$FfTs1.2-JDdQo'{V7V$'C 4Q!>'E +!<7Q1   +    ,*@1 2 %>/*Og4%=)B ?(C/F "' 5 (1 ?S3  4D$1E)?5F 7G%,2 15L 8P6 / % @Q7# '9I(3D,#2  + )( +% $ &FU4gxA;Jd)9 >O 9=$({LeG.D^[sfz+6n Vl!8d5.3pF[O:j/DJa Pk>YuBRr#A^,K ++I?_>`EgRvNr +/VRk +9PEe0P rI2UWy d0m6^w BVp;T76Q +1M.K2No$@p)Fx,B[ 1F*D`{+:V,; ev61G%=-L% "#Kd1>4`v7K9f0/Ma Wh#]U6L,C>Lk/N7Q,p&?j09 #Lg$8M-8I)=?V.B 4J,G44L^r6S_B  D\g$Ga'Ke DbFd=JfPHY.?=R +K^1EGVJY3A9G +=I6A +(AN\i51;RY:AE1LP=Z^IkoUNR7RT5Y[;HJ*59HN(CM&:E?N'UgARgAWkEViBcvL_sCVj8uR_t:hCmCi}B:L WVi=}..1z$,(%&%(*,"z#,#-.$#!"*(4$*?|-b9{3|-.w<11.*,11.&-#{#%$#)&+5#($*%#,)(%,* &##).". 5#',):-4&x="&3*s'}$g{(i,7}"*7%>9sox$++/%,),w!d`r(>w".,*x?j'$31=rw|}05*>*#%$fo)()F:85-90,1+718<@35@<253326,;I7?=6.363<8'0(55)<084474-,'!)|n/#zp'%%o"u")|"2++(5'%$!.(.106870&45(,{(-\wgp"(7@*k'&8z2$(1'#015z?5'%t)";',1&* )%%&.<-:!'$!*2}A:E<2>12!./'ig0tp,tl10.&/;=B:%/3:*47+8=?5=79H/.977'49B;51(41)/,4177/5!-:+F9#uv;~IB>ED@>(9(/1)/-2-"&&,+"(.!,"%{){!0+804*,6?0461+1+-34,20$*%@2H6w-|9p3F]Qf'+=6F:I!+ 11;L*"2 1/$5(   # # /B$:L0' ++"  + 6)@/ 5 (A8,7A\1A\282 (-," + -!4 9I1!  + + !'@7T)32L%(B +  +   #-()+A 1D$$6 # +(+$5( +F!60J#7L*&$<%:%  ;L0%8* 54O'Jd:H^8%!2' (    5C),;%33C',@ ( " $%8"46IMd,2IE[+=R$$6 )8# +"" +!# +!/$ +'+%  3&8&81(97G#%6"2$8 H`-2LcCSp-jH;Y5SWq?$:1(>7O#'> + 1 +2 #1* +.?(74D+;+;5E#-=.>1I,C@X!4K/E:S3NGd#Id$1N`!Ot-AeSwQw w#qm|!z y#pnlom(v%w{!{!otvr0Fw!u#z%{'8{(s 4mi|+t#fjw'n/sr)z&z$-:fv!npv|$z%{)~.l p#u'~-s!v!r(/w w"nw!u!v%3z')'w v"z%x#w s{$z$)|$,z%v"g:w&}-u#x(z-t(z.n!|1k#r/Ck0w`w!bz#z5n'`{fTZ:Xn!z/\wD`TQR[sw64LF_ic^~aKc CW/DNf `}0EaMf?T[@\ +/,G4N Yv+a~1a}31J5M 5M:R4MLe'7O8MH^ Wm-1E /+A0H&>(.:X#-H7Q#(C6&<&; ""Z#B/M;,H$AZuCR&4/ ) +-)?)A +- #*>>R-$ +':0B,B -0@&  +I^ ;L"  +, " %qB>%6^FO_' +.Jr[q>V|,O2I-F"MaW6O 8R>YTp!HfRn1o+}6`{$4#8$Sr+tC4LTi5 23G4H-k}r6f +=^yZsH_eb}x,x:(?%bPjfn+{7P4Kd| Up!t@A\+&9 +=RMdSh*!7&9 8I1AZi4{@D_11I Je6R 0H7;WOlc$]zDa^|}@p6H_AY +L`x$#9IY2A5EBSZk '5ER(=M"Pa6`tIN^4Ym7.289P,PA6@9A9')<$9./+*&(2/.:17?>:<5K;B;?B?>BC?9=<+7$7)5+}|)%5|;"%x( --&|$C<--/*:(-$0y05$-2)&,.0(7,/-u u)EUtg$UsYt$8P,? 0@&7#'8. !1!., 01/+  (AM4,-& )6 " ).A$!61    +7O+3P$$5 4/   + #&(0";?W.@U0) "0& " (& #62;W/.H8+ +-!/@! 5,,$ +; .(%&=;O&2#:-GAY'*B'= +*A +B 0H >YAaNoYy~/t#Sy ]q(y'pr{!y!ut }*tw$hum%=}#qj}(q/<*-6t"{'~)}(ot!oy%ky%lgo,''~$**2sz"|%}'~)x".2}*njq"o 4y)u$t -*x{%so~)nk2-sy!z%lpz&y"r})1+v%2z,p#o&z2Zuk!t(l!q&p&i em)j(r1i-bz)g.b{)_u)Pm Mi HdPl"Uq(Rl![w#Xu#TpYv#Po?J?i&KoDd<^BeEgYz1A^-Fa}1k6<[Ha]t"i)b{`{l'@Wo9RUh"D_5Ns1_v%[t(Um#0F6LXn#j7Pf]u"p,Yse~"]s)CVpE5Md!Me 6K ++< "5Ti)I_LgLiBa z5HggNiD_RlnBDa0O5O +6P5N 6N?VE^ -G 0H 6N8NAUCX1F 65$< : ++F: ">'A%?1&>$:10&Md$2J Sn'8R9PKc?X CV%5 ,:#2- ++ %, 2O%5 ,a{RAZ5 VnD(</ 1,C#:!!  ! !    TE\4$+   + +% +?- 4" 1Mf*B\{Oj +,E `x&j3*@1C %4C#.+ }FFb d!m9 :&?6K:N#"58SUoUq c&d#b!\z i6Ng;SPgbx.Ti$/>6E N^"^n/bq,r=APly2Ve_l&Q_M\Sb#5B 5@DO1nxbO[GER9=G'VaAirRmwWQZ:FO.^hCjtLU`3itFWd2Xf3Wg4Tc+_p3Vg+i{<]p-n;Ths6s5r2ar5))0.1(./!%%.y'%qyf c~%"/ |}y8}$u!r!s$lm"d!@`Xw|8Z|fA|-sv!&+w%tfuo&cpt*|~%z)!&'~$#21(5+"7')-'+"r { vmd 0x# ||{Ey%|.B;6s$ehgx,{-{*q!x%pb1w$/}&>s1A7,424,2-+2575@<0;?42A9=6;=<@@:0/C'/887%78~!+2" #%{&~"$*D'78,4,*6-*|!0*+-,0{ *6-29.((*10y*SsHgXya&Ff,F+B,5'= 40  &!4( ( % !    ,+>.E #=&=2, +-% + ,'4N$5O"'? 3!3 +/% 3&=897 2F%*04G", &(+:# )3.( 2"4#5& )!(! " & .%6&91 7M(-C*>54L9Q'?'."3  ! # ,' *$ )%26J)-D)?+"6->0B6L@X&+E /IIb(B^$B^%6S2NNe59O 7J1G9K$6H$0"#/*94D"!1 (8"2 #3. !1+:"2:L',A#89P$2H+D'A1I;S)A-D0GC[7Q +XyGu#mz%l^|-ncg{&{'jmnporsp{0z"s".{(v$|(-}&|&7z$/5|&y'|)o0w#y%dq#w%t4ptw!~$*,&-y%x$}',q.z*y*x*x*{+r!x&x#~&w'omkhU{{/6z-ep q u'p$z,/1r$p"r'4n$m$r'acg n's+a{h"g$d$k+n/n2]y#`{)Xp!f}0c{,NiPj!Ni!D`LhTp%ay1Xt*]x-Ws)Vr$UsQqVvOpGgJj9[cb,QsSp&7R @Za|/b+NhB[ McVoJd +Qn HcXr\t$;Ofk~;8PF] +g~/G^?UAV7MKaOe EZZo&cy(Pf?Vd{5+CP%6*"73G-?,>3G B[Mgn>Ea4Q l2b|%Nil4r@4Q Hf"C`+F9S4L =T4N.H "6G]'-,A 8N AU.>- +  + + ++#8+E*D:R()B'A7P!'<"9 !8)CG`. : %=(A"= $@ *4M0F$  #7&=)A+F5.J&C /J&@%>Ne)"@ 8.6 +8 '$) &7(      ( +  + H\97N!)'?   + + +$ +&, +2Ti=!:.G'?2$8'+<  +6B/' !-4B/" + @R*dFY&/ + +#4&!4*, +( / (*O_:)!4r.ELbq>&^p;H5 {Yr|44Mq!Ie]xJ_&=O9Q-I +Pn$Cc1N g {STx#5X@[Vq([v/0LLgRj"&?*C7-,2G1E 7$9 5/G ,E1Lr68T4Of-Ici7jv7g%g:Z#?Sn#Sm"'+=Tk|+7O#9$7}>F_x3B\]v}s=},JTo1Me +\u E_F_ ?^vax'6&5=NTXv"EdKl@a~&o<_u,/F3K ); +$2?"# +\q2BY `{Rmg3%: +Kb,- %)Un4M2K 6QDa +Sqb-zN`{=~^Wm3tJ~UO_)KY(HW"Rb%\k(DS^l+Xbo%v;cq1es8Uc+BNT_5Xd>anIZh@lzOM\+uRO\.eqET_4`m=Uc1hwBZh1WTd(jz:ft3_n)OBiz.^p Du./,%0.)"(vw%1eb mrB~&y!pr{%0Trv't%.fnk}0o#o"g2y(v%rv v` 5jjuf$twr s{}2to ,"-#& $##'%.$;*4>1% +0z9j{(|kw"w7l"$+|-:B|59x,Lqbu,g#^Uu~.u$bHl+x x -&%v1mN,O=>8'%|{$(/t p{,290./:>7<:>*6'3684-8>7/2:045.;A460'53.!/4981:-'I$2(8+/.+%17;76.(|52'~(o-{'.1g +| 0,=>.'"m { (!q'|(?"&(&52(%3/$2?676#*--)t'F%}"*3 %*05,905C-4.7925'B'!((,8.50.$&&?)voF?:842/B}(Dz%p'v$"3152"(678} '8-,;:5.6.609E573C413D2279O8N:A:2FA82R;=9;;(&)%)<03=E=7@=U=9>@>:E5D2=66@;3>1)77-*4y&}>1(28,'5&/'{"'"##&)'$%!&')}"'#-*$;().&33},Mo \}i&f&JGc,E!60&7&=(?'=0!20>$$4$ +,)=3F+',&5:J/.#"& 6D," $53G#$9E[3S1#6(;1 2#   # --*% +(<+A 0E$!4+ , /(' ! +" "/').& 4(?$:"8 8O"6M0J9R!%>9Q"!6 ++4D%*:#& $ +* "/&4 &,,'%<!9 9P#9N%$/ +,BZrE?V'$: &> E],7R*G;SH_(7KH['Si/,@"5 +0?9H("3+ 1B"#1#&64D&9$5+;/ !( +, +2)2I2L,G*B+B6N4L-F;RLf#Rl!s5]2p}(}*cs!e] g\ijdant"qbpw&1}08mfv"}'+0sw +onr!z'~,sy"/u#8v%u '{$|%x!{"r5(ru!nmjmoq!}.z/v,iL~.v#y"u$z#u$ilfv({-\_al!{1u,z1s*|2z2q)u.v2r/co(r-i(j*|8r-t2o.n0f)c)i/\z$]z&SnSl NhUo$MhVp%[u-NhTo&Up'c}5\y4Jf#Vs/Nj!IeKhLx1r43PKmXya'_}'C_Oh Yn%Kej7Ql 7Mp=XoB[Vu>e"XqAV}Mu:v3\?XD\ cy/AWKag}4;Q 5LUi Uj=R9O4K6Nbz7Vi&$70E $9La "5;O>T8S +%B/K=[(C'A2KF_ej9EeuQ.K T5J Nc[p&Rk"E\4 #$9(=!7.(/:X.)D! )-<"$+ ,, - !% (      0E#'@.I(?+?-@sR?P%"1 '6(  $4:Q),B3 :";,9 #C '&@kX !?X324N$<%? $*D'C$A .L)F2P"8- - #"((=9N+C4O.-I3/G3!8 7 1+.'A$; ' +7O&0 + *! +  + +'! !- 3D2Jd16L# + + %3)>T,,/)&Tl65 7 +8N#   0B!'  (8( '/{@NX/>K9 + "!5 &-"2 $"1 / + }ILue}.p8|vyEXBX:w 1E\@)FE\l*+B(@-F-G + 95N `{MGcf} +Uk6@V ,D # +'7T:Y5T'C; @Wy"9S Jf!Ni$0KqDYt&<3,Mg+.B'+"7 >U*#; 89QZ)BR"8O`3# AO&pGE[AZPg-FYo0/&2 )@X3J.DE]Pm Uq$FbnNPj8RkBauK[n'2+<,0+9r-3j#w("%Xyb)6+-+::/%"",!'3/-3".-1!(),&):6"0+17*3%*7500"(7B3;*22$313*304@56=/244+59<46,067.:+15A19|"n6K}.27G85}-0q3x!vm} {0,'<*:?6950606F=8,677/:>AC=76=+927;38>?8=?:97>0/=@L8-8!.2/7.26D>?DB5F:4678D>986'<7088=375+4-:/),*{$+11*'0&){"~$~|)~#'#"-%'&{"-#)2& &'~-#'{)r"2kUxq67R %<-G[!Nj!$? 9 %8)   +)=P2

    "';* ' ,()9# + (!"21)&<6K)*0/% .)#' )(,/B&$) .&* ./+ 4G(0W+H /L:V;W0N&@ "; 5+!= +"?2O;,4!8 2Vl/i7EZ:P)A0G ++)(>>S@VCY2N +G`^w;+!72++/)UoG& $-;#  /"  "  +  + ))>(E9V),"&.'JZ;'7&=E[0- 60 B]89012 +!:$<>U&'1G)B&)/&CW&&@240I .GWs6uH*(@2K+G)CXrk10A '9 I^1H.(<%:/G%= +D4L3HXk2}N2":Ke%C8S 4P R"Mi^}GhEf8U )@4Ip,[| +Stz#DY*'9R{Iq2&>Yp4f H^Ld C[`z)C"<B]E6QB]>m#:O *8?X&F&B7 +Ui&4L%<&< 6#6 aw((<+> fzCE_"Vm5iNNg7Jd9pccvQgxN^oCizQQa;^o=qB{FQt>l|2p6jy4n{;|FJv>q};n{7ECo{-w7r~/OG|>EH]nfy)u0:>}1520C;.6)816B396=03$$($!)&6'8!"4vz!uz!('|$|'v"k,x!'|}%, 1/}#-6c +r~&sho8{"c~ 7g szpo +~#&~-!z#- ,& /," 3+.*1-&!2!30 $9%$)1$!.{0#&~q^{ aLjLht)p!z)n~1\xRmw,41}(+z&ny&Ei39o2+}62)1.#s$%" !(14z.n%i"u0n*s/b}"e!21<23)(*(=;33A2802551123=-9/3%(0*'B?+.+~!:yz0 #%!./"7'.3,8! "7y-|*qFtp1*-1-y2{v&!#610'9-/7(p '#)&*'3-7=5.#-0#/;%(6,./%5611?2=2,7@6+75)E425/:2@+8<:/.,2647662-A:<9B>E~177<;=@4u$?u '-}"'*-22+0A0:7087?D39528D>56/138.88389;)z%26+0/|!+v|".|"w"$# #($}"23}%#|$+)9+2.turAAk7/H"85NQFf!90G! 2" )=M`;0 +06Q'$?";/D"& .33%9/*(  +  % !5 53 4+ ' 2C)*$ +)=#&#5  +$3) ' "9' +3*C%12(*'   +)7! + +)@ ; 6R&B(C%A7.D,?#) + :P-BZ30/+.@' +)$5#    + + +!# ! +; $3 2E&3G!-D.E*?!6"7,C"9 ";.FA[05 +.-$6%  +  +) /)7)$0' ) 5J$Od>4- -=)+!5&;3 -1$;+C/HSi6nK.B AW(;0=M!CR*);K$0 1A)*0 (!1(9. %5 +#1%9O#": +0H/E=T!3L@YIf"Tq)3Oi7JgIfAaWzs/_fq!h_Mur(Pv W~h\db`lhgx'3v'im^~,/s~#&qm5{)x&03u!x#|*w&t&z-jt#~.r w"r{ /umence_y+w&8jbj hho!kt(q&ei$o+s-t,v+x.j$n(o,v1p-m)

    @[LeGb$>.C +?RCT+@BX@YNgZ(,-B2C, & (:" ! WqK: 02 !88P%'="7.,F) &Lh4=Z#4R"23 +. /BBW0,/+2 ,B^+!cE7Q%&6% 2I#"8*-?  -8&    .>(-$ +(* ++A 7   /QfG   . ,1C)?6K>O':R!3- &6K#4 )<#BY2+?D])+@ &8 !Qg6(@ +A& + cSRsazt7,:+9~f |A=dyr wj|@Q*@R2XM#)+WjVvanr F~'e};K.?;H]QdBX} ?*3Ka' 50 -F!7 !) %'B +!!;+GZD\$*'>:Q)&'"77N#%= 6M$8N1,=Xg/+C -X<\Kkj,SpJdu(UoHe135Wr~4'?^s2;PsO'@$B2(;T#3M 6($:0,4/D3J3I GY%2FPd2kL! "1 AUEW 0BZm@Pc4GZ-awMRg>Sh?[oKL^6asFl~Ri|P[r@`v9k6z/z-w'?]x @7J+L317*134C37;7A@?<;@H;C=>@FGB@AJ6=<>=@3J8,6E)/7,0z%3<@9664;A67?0:0+9@25C>58:6.;155.7:-1137/*34}'(3<4,0/24+24/416(/}'x &#xvu#y ~6$0*''y!($'%"4)41~)t&Ml +a}$e~+:SMo: ?<1,  ) )=(=*@$<*E$>)B0) ) 2F*,.)# +$ !& 40F'424!5/$ "1LaE 6#   *0/#8( 5#8! " $ % +"   +-<#$8 "2L/K$?6 +D64 # /."F\98K./' +%    + +  3E)'6' *'  !22I!;S'84M%8Q)8P)*@CY3'='=3-D)?)>5L$3J 5 +%;/ -  - +=CU(,* $1$ - + (7*=-A!  0#1/) 2!5F[5&=/ +)@-Ia3'< 5IYm7Ph-9L6H,=-<FW/GW/K^51FZnD. (#5 "1 *+$2 '4)7+-=*=(< 0CBW!;SD^Sr$A`GdIgQlt>^$Vx7[ct(w'p] ] f^\y-i`X} ]kejls#t#y'w'h|(rqy|#w pp u'r"h7+w"-x*x,v,m#e>f~)}&ny!pitocV{ +jt(is"s ekq&r%hjfl$p*m*s0q,m'k%q*o,b~ [vl,t2Aj-r7e,l1x:t4w7h(h+q4e,g+i.h-j,c&]{#a*^|$Zx b'RpQmSq!So"Oj ]w.`z0PkWn"Xp%d}1Sn$Oi NjNn&Nk$DdAa@`MjRnb~(LiMlPoBb ?a +Rq4QC_]Ga&9O*?J]K[?SG^Pg8PMdy4:P at/w>ez+aw);M1D`q.k<=S'=Ym*Ph[u i,OkD` v?Jb3K8S 2O?[ ,C 50(F&EiD2Q A_7UIg 2P@]Cd:[@]4R.$=7PB\7THf&83MD\"3M,&A;Y2M$A 8 08 +/,I(E2N 9Uj*F[EY3G )(?;T$=%@N/ +) +TdF$  " -! .)2    ! +!3@#*!5 ) %,%??Y-*     + +1D$"=T.BZ/"CV5 +0,#9 +*v`e~S:'D &!9 2I"'# 0 + #)1F(1 &&A .IKd1 !. ):( 5*0C!6H.    (*7$   3H0"1   + )$2E!5E 0B(7&3+ 6L+& 7 .+)7L)$9Q8Kk8A 3A +DU)\6G Ak ,Yo~ KAv\n=P|0}PKdi7Wsl 4zd}>0!.0?k~$I\Od=RNg l@u|$M 23F/B $6%~UlD\|>?x!gg{P%=/B'!2 ,+?W'_sF]sCeB\t2m<~Ej-o2Ak r+{79?5&:7<9&5/>=36280..=8%;I-=5:&&# %*3!'*$$'%(0+ 6',,-u0-7%.$0*+?)*,31',39+~"*v:{0(?*{86$&&&,& %$( 4#({q+1x03z1(#$+*'+$')&@*1+'<}2.lr$oMlw"q`zDLj~/0+'+t31>;%*+2/)6;;5.*~2 *}&)}!y#4gj#Tvi#v,7j35|+~,w{$-{0t*$<5/*~"4"}*~:z-$2+0z(6v!0*~'CA(@0-1'(x#yz*!.8!(' 1$(%%&$%,4' ~1"&4&#+9B/9-'1'*1."...6$.#&+/!145*3H8?54/82!43+++%421&>10294*52B6>5>9=78;E.009.<228*-H8?<;?-B18-r0,4@@DPCA?@1//K:-A>(B88<9<9BA8.4?:8:?D24363?69~+97400?9%(6!0,-A$#9#<4* /D(';  + ) $! +()(  &8  +! % # + +    )-A -D/I06 1L;U*1 /F$$% % $ 3  !   +# + )8(7)+$  +-',0 /JOh>D\3$1 1(=,@.+A*8N'?U-"7%$%5!0( >R:ONb"6PPp\"l4Ig-CMcm45MSn#QjD\B[=W!:.I$'A +182Vu1uD=_?\Jh 6S&> -E9T2M/K;Nh%2E#4 2?W-'@3L1J@W 1H0- `t?BW "   /+'7* " 1&/(# +! SbC*)6I 3&?@]),G +  %5CW2&"   +$2d|L^xF+>U.%30F %,5Q21  ((;)$5';2Ga35 Qk9G_3pZ! '5#:-C#8    %: 6  (2 '8 +8K%,  (*>E[0 !3 2( 'AC]+60iF1H$8 , &'bk<|[cahTZ.UqxGzRcM'v3B +*6w,.I%n +yh48rbx~"'A*tk|p87Kg d{q +g^v]qk{o![GZR5J(>XpDe*?p""6->N2)*?YvWx +Yvo3Vv%D``v:?SsHEo,K\(,>1>V*BCXEYdz>3M%> %={QOj)]v!c` +s9%=YqXpq8VzkJd+B-D:L%1&>2H Ui0(?m>G^5DZ +Od'>H`Yx=o~U:SVo/D8N4J :P';4=W A\ؓ>X!*D)A 2H`&9V0N%@0MIb%=X/,?  1!:6OB^ m@R_'[h5^m?rW_pEM`5^sJVkYo3Zp2g<]w.^w-k3v7o+7@;w-35x)1(/'&*/00/,3$55$8;@8297.-$&*+$1:5+<8*C)'/ #("2($-%" 1'(-.+$$2/@.<*)*-3('!%#7).(4+-}&*:0*6+&!)7''24$u/($)&&+&/4*!%(42"1*-1}-))-8jg`y +Jr8[u{)|+O0/BC/}".<-$7)0*(4#$%#.(-$C'1-'/'+/|+b3~.pw&-0s,N5s(~ ~ p*\m~&-021(~#'*/('t/v0z(0y)|(,2?094,7y :+w/*+%(8}/6$7''!-$+NLM-/"08<7#:4&87*#**&*323(/4**+/-!2J40<2&*+24612;)7.,75$$'%+$3?.>+(66.8/:6?=,:<824;A/3F5;/-<<8/89:-**2}"06<<8CDM@>2/29;3226'1?S/5";2M%2 !8#9+ ' +%7$ % +)     +  "6( ' '$"$3 -,&6)6" 0.B3 676 72I%*=((<&;=R,0 +/A0 '6K%%:0D+;#6"!"56G8I .>, /<"*8*;( +2,?*<' *& +$3*:)<#8-&+@+E/H0I%='<6KLa/\pk}DG\2NIhb/Ux!Iia Ml>^ +9U8!9La=P Ue&FVbq1AU T9R2Tl!3E GXgz;Zj,dq:BP1+0A+; ,?-EwD]})LmHf9R=Ti36N\t%b|&<;WQm!?Y1~T54>V"C]Rn%Ji.,K5TUr1Id*4-FA[".H 8Q;S)@Wj6+A %< 7Ie+1L6T7609 : 3:Og/fzE'%5 &=Z2NHc-&? +2Mc2"6 .2F .E(A ! + ;H/$ '&  !/* '#),#( $$ # " )ewJM^1qa{i"  9F(%,?  MVDWfCTeC pWq.=Z_}|+c )\c67U)AZt03/@ 36K1K +C +$<4,/'&(? 0G6#@ :2M'B.H4N 3I>S$  7I 4I Ia9P1J?U ^i*bp8fuAgyIizI`tCf}LTi4Si-aw9Zq/j@Zq.f}5j6^x*b,f-Zwu-t,p$t(w*v*~.1,%3"9'/+0)(A/00,00615%,%'4*)(11#2',1~(- +$!)1$"$+203/+($B'>6//?5//.38;+,7!%396;|'$(2+")+.&5&-2{ **0:(3s8#$ (*,)(#$&xtt' ,9+~#*'65Av2SQZ'#*2$ ()+%8-!B$)--.-(2:-#60"!64,5*.'(/"%>)4649.(.1&,2(.,9+.,#&'58=/><24,C<55:>.0K2<+6:6>$.596)4-6117G<<9;=.D:09A057:*4-?BK;JEA??H>C3>57F46;2.<4=:7==4:@B@,-8;:z*~.z':{*6F83/1,=2-96(,9@2(,*&-)*7)z0<),6&$$w){%/3-+$-(D/+/6+5"5'(0>1<3~+z';303%}"$u$}{ m&%#!38/6w75282=EAo2Zz'*G +2 : (A#:0H"!7.C!241G>U)7Q&&?$;+ 3%  ) $4+>')( #9 92/-& !5 ''0B(+@$+# , $ 7(%>#2" -(    DT9$7+ +7/H# 60+   2) +&+,$3% + %3( +)#8-A$#:2D(#   "&2@* ) *4C)"0 &2.=(1G)@PhA0J") 3&"6%*';+?/ %+"82 %%% + ,#5%4%- $( % !!4 58+, "& +.$!./C 0F 2 ( &0IFb1B^/i!7 '>*?=S!24G8I', (:EY)*- / %3 "46I-=#2 )$!. . "4 8J?S;U a(Rt["KlMkJh]|{3Pu +^[t&i3e mntuikmmt unmr-v#lw *|$6rnp/y"v"y&p}+hw&u}*8y(v%{.ir({.lmphffp%n l!u,hfkw#fu!pjz*3z/q(|6v4q,n)u-9m)o.p5r7j/o4i2s:a})d~.`{/Xo&h6Lg]y,Tp#^z-Vr(\x.]~1\|.Yv(e2m9Vq!Sna~,i0Zx#RsQqJjDcOj JeRr'Xs)OhXs$QlZx$SlTm!NjGcZy.Qq$KlNpd,KpWxBe PtDd(u=\})5U`|*IdWp&d|1Xo'G[6G dq6BS;N +Wn!]w'Pdz0Zl(.?\l+>P HU*;): FY.(9(9 JZ*6I Uk&d3c(k1=WD[F\E\6N\u s4g3]|#Ef6PRn$`z4'A@Z@\*D7Ml,<5U#@61MHa'(A&>%A;8O'9$82AY&B]&>Y$2N%B4)C'B8Q&C 1L#; +))!23H6P*C/+@-E37I*=3J[tB"; /?Z!Wo=*;'42?! & $ *" +$2" + !"!6()  ! +% % =K.    +   "!0 "3     - 5N($ I_8$=#Rl:6/  ( n}T !7)D #+BcwX " " !,CB\2+@0""*6   +$5$9'1A". *  ,'.<[,3 +6 $'B$?+ )A#'7L0C ("6#bd^k@2BgvU\-?FHBN+9SaYh J8IRpz~$4 Ng2f +2fx FVWd.Z*;s w0A@P}j~7(.F2Ig}p~0r7A`wAUK`Od(5N :VOqv)kHg9YtBZZs!2a}(UOlhr-j2y*-Tr@KfP) 8rHh{>\!%BCcTs\xMj1J;U Zt;Y8U,I?U RkDa-J}*5w.MghCR+=-/E;R '<%=(@$;7sU,e,< ,A+F3L+D +'A (B +HAW/;P(9M.D!7 6 %A6%=*DY 6M@UH]$g{G>Q ?S!2H ,ANb6L +5Kbq.hv8gw?as:uOlFcw?\q4dz8n?[r*g5c{/l5_w%XsMkMie!k%m"t(r$x+5/-*%3 0!487/80-6."%-A.+/'+"-+/%($ $%. () )##!'4)0** )'1,:$#0//7%>13.+*)4##.,%/*,=:2+B&7:.$030#/1&"( -'!'%$ "'/~$' ',$:&'| x}#s+y~(@x2(),@>4-3++.7/4E33-0q%6*! $#&,*6~/%+1,%04(~.-)$(<056v+~(3(iy!,z!#}!&:3+'+&%y"5 z"7&'t/z"|$e21603$/0x&.u 9z"w 7k~'vy*}}-"y7+2")(,2/:))#"&/')";601%0*,"0+25:~7%**1;&"('t"yv 0)0/%3&,.03(10+80'/<03535=+7?9@87B99766-A+::376C(3/45+/C3z 1%G<;A5F3:5-9=D63A245C7A1/B.I7>;*.37-05K<7.8&0>850,95@-50y#?@q 15E7)0:.'0|%3y$sx!x!%}!6&$=, $( ,(9&  (( * %<%22 ($6,$ &!.BO68E/"  (! # &9/%)>#"% $3.#) +* +"-2# +#1 2#5%9*+0F*,@$1'   +$  $2" %  %6;K'):*+"6 * +$=[Vp/8.I p?:Gi IiBa.KHe'4Q+2L5P$@(D )B !81D&< 4L)E/0&C>Y)8 %=:Q#= +B!8 '&;, 3 3H0F(=3H3 +($ 3%8 2-EA]'+H +4R9R]u:2(. . /)9 +" + )(>#<0H!#% ,+)   #00>* &*2$ +  +DS6%    + + +% -,@^s u"G$av Zq$&=rZn!d}cz~$v!Y">RSg)$*(Cw*'4HSl7L)1Ilu_z y)=w*5?Y,HMhu-75Ji95k`FnYp;,N^x7SkAW]r00If3tKCYWtw=X'D1M-HAZ &BUqB_Gf @u!qSnB]tcN)/-D>U $E\3ITj$k!^yCSh})Uk$!9&?@[;U=U=Z% 7 220!(?)@$?30K"=)C1Si$;P,@($6 +4#70GRjjgx.ev2hx8wHp?fy6dw4k;e{0bx,i.z?g+d}(s1h(|:j&>w-3q#>s$2~,0-%,() !'-1%$6*|#-&5{*#-} &/$%%) +|,7/(**,!#&$#!'#/+-&(+))4,-020/-+E8304,%1 "%$$0&7)-}91,10.()1*",! &+!$ ."$$$?&$ $ (+!t4}u11">~%,$.~(w(4{'4,66&,$IO#7)2x~!"z9",&#"0#%4'-1#0'$&36$ux7/&+(tvqB6z!1-{"'1*-F$#z$.$z%2*&-;0%1*0~,eb~?0vwe *f+x&!,")q *x%) 3#*37%#**&$%()$#*$!0.-. %115-/.<'+,vs"#vlu)| 4,&,2%1*.-0(,/K;4C:#912343A:3DC64561A.;<:86;?7283('182+DA>~!F@5E51:84A5.<4:488.3+B*:.3/.-9>C2%!&-9-3JA:54I4352#8=D6<*|$F7,9B<>1K?.107-04'23395!/2/3613}(46-A0)=51?.7z")+06%0*)'{ /%2)-5H1/>9>>8M/>w"|'6}),831},},2t"k,},#32$1A5r$7H` 6PHEbNl\{)gSq0(E6 0 5*3J"0J"<)C3K$6:5,  %3!   ,)9 0(;+ 2";' $   +$! $-C+" 3F`75#>+D6L,. ' ,  +$  +$ *,2% ++ +& !1"!9!70 68L0.$8$ +" ""&70?&   + #  +Nc66K *"3 #5 +20DBU+&;.H=V-2J!)= +!+>#7 +!4!45L1H0& *## +, #4Td@,$8- #8J-':2$>+C3 )=/ !0&5 .0 &<)?(%8.C))=EX3*<)<Tk:,DOg19Q"!5 +!.Oe'5H%% P`<+)0+=Ud9&#0 BP+(:.@BYzEl+`Mo +Hh +=W]vn}.o hgd xu{#{#&q'qjf` kiu!sv!z%x!v#v"x#})/s~)r(rtlx#olu qy%y$u!rbnx*fn!s#r"s#lr&m!io#{2s%o!ns!ijv)x,`gp%s+s)r-r.^h/h->V 7*B +4NvEIiAb=[=WGa8T :Sb9zG5XEe=Z!<*D1K +.H +!=+H "A3Qh43F'=7N .I(B&@)C %= ";>X,G-,BZ)5E $* /%) 4+) & &, CX0,0!<    ! ,#  +  '# +( + +  +;F+ . +  1=*vh&0# 1+#>+?0 $/B! - +(,D# ) + :J0   /B3Kl9; +(E5Q=T((=7H! ' +&8 %3A%0A % +   *)=S-!602F&+& +"!>4 443 #/ 1J&- - "Ea.2H@M :Ix)>HSV_ v(j{JY$_uP(R^ PbP_,u Tlo#A8v$H_WjWiYmt%|!EWrjezt*F=Q^u n3<&;-Hb'Gda{ +mq!C]/H`,l(;:5Yt y#\cy"9P Jco*]vp.Us$GF[ja Y_{Rl-Q-CP.^|0[x l.Lr; 3N k,g!Hd9T~;eqxm Snm1,5]t!/Me{9MKX|B7Wq/E6 #!=*C6P5 "** +&$: 09%@ Pwn1;S$:1J +22 +;N5Ke;;H^v7gy.r:yAvy6h~#l)Uo+o)p&dt)s(o!y)31{)|*v"{$y"%, /#{8z})} | |%zqx$%) -%(+!+2+z6-(4)(,,%:&%(6{8"0('E+092,/=>,=A6?7/,2%*#7(%*$()'44.*%-|,7% "%#""!"!&("*+$"6($(|$2otun 9+tsx${ u{!'0'*r9r my$Ht37%)~ *#{)0'$3,;4--*{ (+2(k>&!1@!#~(&~!{*-z&$wx!7-0o&~."8*"##'7x!~%*.2/!5n4z,,vs8Xuh}! "$z,r..~!xy ?$$*~$y28'+1z/$$*(%!3)+('#%*.14')~"x&*m0{.}#88*;*1-(.)A52*80:820562<4E+B:541248!78968=13-?3+%6>;3;AH*H<39,&;;9GC5;8584+67-24=6:4775E-7} <,>?.25+,,675=A'57@=%8**:1/:7@??:C9*C30<=r/:'*25():;5690y%3=9=9;13-+0$0((%*+u"xw607).1/=1S9B{,73427*{).z(5B@9x)2|*r rv,%(#(+,.{.Me A[LgvCe,Us8[Gi7X4R&B#<7#==X'=Y(,G- 935O'*D0* #/#  " +$4"1!*A#.( *>"$    !4!0.G "<*<!4&.<?N++<0@,C +'AWwn(k&E` E]az/3cjpghjv*|"xv|!jklo0kt!px&po 0s"~,|*y%o|'}(t!~*r"w%qZiggy$v!s|'rgq"hdkfkp"jmgec`fq#k_\kk"s*ir'}4x6_~]yy8j)s3d)c+d},`{+[v&l6k5Zy&Rr!Vu'[{+MnOl!Wu)Vq'\w/]w2Zr0Oj%Rm'a|:Pk(Xr0Wr.Ni$Wr,So$KgTq)Om'Fe!@^Cb!Lj(If Pk'Rl%EdDee-UOmXu"Xu#g5uDhKg;U LhMmQs@c 1SHh @SKb =WD^_|-"@8Q cz7.B)=:K zFPfOfCY:N:K!Yi3Uf1VGb5Ywe'[z!HgYz!Lh\x'+F:TKe#$ABc?_?\B\D]",F $>Mh(/R :Ec3N ;U2N h:@[1O +J8Y8T0H*@ 1-6.I: 36 "<0-0.Pg;)0=T*) + !++2 0 +(>7OAZ"Pg-9O?S",K`.G\1&4F5H "3 41 &<3 (2$8"&8H& 3 *6 9T*0(6!%0 $*%/  % (,  + +  +   "-4Q7T ,-3 ) % +) 6 -=$    + +!  +!"<.@8V% & ". '7&7$5# QaPbyZ 4L;S!6L   (3 ,& +  @S%E[*?V)"8 WsJ1 5 + 8+;)D-G ")ZrROj<#< 1GP  +  {HAU.tKX Op.w1%4+9Ud,<%2Wk.CauUj +7r d|D] g@WZncwOdj},Qmh qB[ ]u!,uy/Of]wm)=X:W`~srt#b|OkqM"I735uEZIaeaa4I!8,C6Mn.UpLi).e z$<-#^t8Wp uZu]Ul)>OH_5P7P?W!=~6FaqKfMh|Dz2t(CYtOr^;W7c X{(Gca]{;D@!:@V=?_zs *k'k \t}5>U /C m]!7Jb&"('95-L5T.KA[{rbay-"<4N.kTkJ_AT6Kr+x3hw#>Nhtq(}2372;8?I5~%)%*(),5&w.}y"%'&{v%>{)8,*}2&y3|%~$<&@2-(!9!qi 56}")2o-('>s9rv"h(3!p 7&$!$)%&'|{! 3$/.*-"$*#+&&-'2#1.6.(,4 "10/'&@-:5'732.E6095,2.&-7*D0:42'43)F1B30?65<:/9>43.488I2364:8-+$7E0/7-<98518L"z+7'gx 4=86:199>4#72=A*2,5171?:9;;77=14621~':-,7,(4>39C96>z%~./>5=H65B/()&$! t!%2)/@/77233:?~14C06>7B3B*061~,.|(x&{)0rz %,$!&*z(z,v.DE`4PQnJo.X} ]}1(F +,G"<3+GPm73Q4O ;.F!:+/ '<)8$2( " (6 %-+-% +(=!52&8" + +#, * K_A"88&=-A".+0(A (@#0  $ 2.C$ +  # +*#5. ' )7  +,B$(>$> "6/F#" ' !4N&4M%!3(! ')#2,+<##  + !07(@!6.2+@E[&-D$: #; )C: 2M#!5*7;M#CU).;O$; +## ' ) ., / - 3 %9 1 3(>/ / >W*5P!*'= "6'&$873 7N&4H"- '7)9-(;6KAY%F`)2M(B1H0D'9/ +!- +- BP/BS.!1 (8!2&DU+2C5C(#4,<7K1JRl#Wv9~3}1p%z-5s#ipw%Znmx#fnfjtpp1mw)ow&m.nhn!lm|+{(})sz&r})5k_\fpnfhhjekknjllq kjm~1o#igdhfjg\de t,am&b"h)b$c&c%j/d-f2Ut"Xt%Wv(]|-OlSr%KkNm [|/DdHhKiKi!Wq+]x5Un,Mh$Zu2Pm)Qm,Qm*Kh%Ke Ni%Pn'Fd5S Nk(Gd!@`Ih%Ki*Mh&Ni&To*Gf`0_,n8C_ Pn#On,K#A=X*CXc|(]{o0h,]{XvWz Bb :ZYu"d0Ni pI47yU2N /H,D +*+H ?Da :U1K=V3L HdKf&C(FBc"Pn23N-E .FJb+"? 9'D#<,.&0 8O+ #0@U,D[2$!897,.<%@ Kh)6R4$8, 0,=4F)&:,/&<1 +%$: +& 1 + %3&),2 &  !      +#0          +D]2@\+)AV2#)9K-#2DO>  +  +(7"  ' &7U)0R#@- #(9&< 5S@]%, # 5( 6E(*     >N,"4 +f~J:P+ +#-K`@XuG(C7S'#A7 ";6#8*!* |BN@am-=Gq6!.CRReu$4DrwA"8DVq]rd{ ZpF]j!=Ul+I] Tj\so-:S@Zw(e7BJ`tYn-?FY +BY%=C\EPw(-` [{Dd?\ju2D/EOcDY9Q.F6O*CtAVm#j8_u*Ic /KhfSs#Po?RSMd]xdx!eMYrm0\w&9U +#=45L^x`n)9^zF`[r *1w!HgZu+a`}BSlKgOlKf 1JZsKa%94JPiq#>X*b}pMh'?h~(/by*W38O qCRf7:L$ ,( -UWys!kjmSnj5:/wBv[p(Yj%%9Tg x&p v'y*,-'14,,$+-x"z&n{"*5&|2C##x)-}|"{}~w|&,q}#~$&$(#*#&&-6*'9)(*-3742.+,3),&+-4-:$?:;=A(32*1;&010/)("/$('% "$*+''#*x+})>+$'"$"!*#}"ngi{$te43!7fww$u^ro3t a +'ix+$%7z"b s1ol0oqtw'>%q=$+1{(%2672160252u(?&"/wt;}*825"$0*?-D."*2<})!)&(./,-49q"h ~ *I6/##:r ,}#p"1%1#+~y),!y{~%{~((:+)$,1#%2&|'(+'{5&+,10/5C-1+)~.s#8*'|9#$,;1+1$ "*3131-.@*(0',)>8$D39083:45H=@>64477833-0:<3*'6'413-0>?6,3,)5&2/7;:9<2-2&7904=:F96D:&A556/001.C:&018873,(8>2(>((-7*(242:85-14>>68=1.,/-69?64,26pz(G~3?6}3j~$;v-q#,.-&{#!%$~+{#+00<}&~(912<;2245-30{%/22|*x%.(y!jqty$}(g|%q}#x".tud8\zDa >\$CVxu0\!1REb'*C4M1L(F%D +-L(D%?%>5/G$"8%* !% 0>&5F,' (9"" ++="03-A&' & + 0 5*A4M&7";4P+"<)C"/' &557K2- 0) .!3* % # # '# +!-( " % *5J,*=1$ +,5F*6I)#"9"99U,*C4$(.<$/>#1%7   5F) :2 #;1*+B'>)? $< $> 0J/J;R*)>#!1 8I!,J 'B15K .GD_&=Z"Ge,Jg,1N#; 3+C1JUn091N'F +420I2J1MB_(.J$B9 &7$G^4C^) #/ '8 *19 > ; <0K3PUs03M Rk1/D.(; 6G*g~@e2&,)?#80  #42@!  )@2I!(,<  "  "   + IW? +'   + .1- +  )1"YnH@S5wo,9'"/$ ,+,  (' 0%@/0CR`@ >T)$= " ]j*$6!6D,$,!  &7 4D  DW3&?N;/;2.H%<2 ) dnK&6?N% >Yii{Vh5GFYRin2B[ZvNh1G/WnVmd|(g|*_wH] 5L2I5M]|-ZxF_Kb +FZ 5I*=!4f|.F2J5Q4Q`}$\}i'Qt1Ro,Tq 73-V>5IIi39X|B6U*F?[ :^z yeSo @h9y"\r@o%-]}u[u(@Ws~=i,/2JLdf~/6M,E:ROiE^D^a~\?[;Gb~.Mi9ނj+NgMi 9O %(?`K]Lx Sob|VpYw"cy>52*,)/8/9;6D3:9B56/>9346625.56%69,- +4,*)3,*1.0/356'|9)83?&$00@;1!>.E9B7?AHG,47)=*31=x(v%;3.}#'.-.0~ ,3B/,.%&+(+3-2/$(-10+;@/8>23%+//1D4*.|'419z+z0v/|4t.o+E~;l&34'$ (%!kz$x&{)-}(x"34z$4=D.7./+}&1|'{)~.v(q3,*xz!w#qs{#jketpw+xz$.8^}OoYx"z4z3s.\!,O.L40 ,HB_'8X+H/LC\5,+D#9, ->#3& ' '#!9?U:"  # "41(0$7,"6. .I-H(E!>1M%'F'E!2% *,.$  1.. ) -@$8J0    (%3*$  +    % + .1B$:P,0 / +2 "?1L!1F + +,"/' + .C&?P7  + +1"; 9C].9R!W$C^(]Dc!Ol)A^KdIdA`Ca:WDaTq/Sp.Vs1Gd"H`$Ul.=X>^Xz,Gi0N ,I +6TYw/Nk!]|0_z0Oi -D6M]w+EaYt,A_Vu(Sr'7V Sq(7O ?XE^:SPh&Un,tKfzC 1I]$dAA`7V>YBY#1$>&<7LYp6F`]w-Rg%EVkE_;X.OPs5U :Y2UGgCa<[Vr//M +3PZx>4!:Xu8Jd%Sk3J`,Ib,&D'E +%E .M>W'< ,&'-87S!>]'5.J4N3M$?0L*H./M (C0J12H1d|N.  %/$=*F ">.6S6S4N(B*C*F Mg-:P ,?1oBc6N (''+ +1 . + (8)"# !'?SnC )    /$ 3;,#  + ;@0arMDV*EW+3D      +P^G6I(       "$( $ + &AS6Rs;&@ - /C 91Vo./MӃ2        +  0=(  2I%2 )F/J- ,@!':   + *5"  +r{hJQA]Rq >:FAJm]o#a{d~Yt Ld +Gb o"0d|r".Esq]} +ffe!57O d&;W3H "8KbQiA] 5K_x$8O;P8PSo +EWw t"8+.@X5Lb|)3n>Ia&! $AV.Ss);W Li 6B]wF`?[Bb}SnSkmg/+A7,+,0)+.0.*%12/';"&!/2"3)#,'+'$20#~/.y&-#6*/#-,;4.235<&2)9(/'!A)%/6+0-)-+-,8-,)H)@:6q r"-z&~(+'.z -+*()w!! "#yu$z~'r0yvuplx-Ci_7m1u#Vo|)}%4{x#)(#pvg g-{&"-~v-.&n_ q!din!Ssx'q})g|$z&v&m|+v!,+tWsi %$+&qu i$*#%zi :,8(8,E9>/}#r0v."*z i6]2)1&2.&->2,+-"+,7&} (>71-){8&$"#'&"|yt{%(+",u +'"xvl *#})' !)~*%&&*6$%')!v10;:"6u*-0#" v "~$!""$%<):('9,.0&)"-()>68.45:E59G921;-550>4+.#,';&-60:+,&)2!,3#+(0;,-,')0/+,-,.~!276E735479?9<3>:?677.5.B5w+6x%=4}&--'%.*)%'0('+#~!C+*,.$-),}6<-2;,8E4$)0*?}#67A6/6/y(w(4:}1l!s+v2{8n&5J0"(*.&~ .binx)52.01y#-2/|(5u"u#w#u +{&|*|+sy%,~"}!}")z#x"sz!lhou }#nz $|,y!eX{n+e=y9?c ;4R/H!<:X5R2P1L%>1 4O(-F"$<' ' "/+& # +=&&:  + -(+(  00 @Z.5Q#+EG`7&A99 ' +"  +!00A)/!0&  .@$AS8#, -/"/ $2  )     +1=*) *"5"3-'":(C!;0 11*+* +'< + #1%9#> :: -HQn7)E 6Q3N: +"=#@%@9 ,D&')9&(*-@$8 +) 1 * #5& %):*; 2 "3 I\-.B+B8R-H@]?Y@YPk,^x?%=3 2*< #74!8%<0E"3D" &7Pb;(=1JIb+D^%/H]A_Rs.Sr0CaLj(Yv3@]Sq,DbDcDcBaCbGg @`FfCbIj DbVt1=\DaJg$C`Hf#9RNi&A^^~2CfBaCaFb#IhPn$JiMl2";;S:P Wr$JeLk"@c>_Ac Lj6O AXIb!E_nIXr4F_"4JCY*< +,@ CZ$>U+CbyB0.!6 +!5 +#'%:5t>B[ 6RNl?]LlYx*/M*ILhMi4O#@B_'E )F +He+dDSk:.FI_)':&<+B 5N8TFa+!;&?!7!6 +$90 %: 1"@ 7Eb)2P?]!"=04-6T%@\, ; $@ Xs@$>'*9%1* 4 #0#$AWu72N 8TKh%(E)F 2P#<,2F.C.I7W7Q "$ + ( &%  /OhA,H7 +$;".$(7( #$/19)  + +%XfB-#:=TPc0AU1  +  +       *      -)-C+@- ' + (  +  #-8(* +'9 &:*C &9o{#72( *  1  '+5     +  '<25L$ ( +!.7O0)@.E#0E)  BH0ePc_AU3J/F +%<6*@6Wq$;YYvh!Us#YZD>Xju"dSlZrUp=Xv6VuC^2PFg w9[Ux=p/\}i q#p)Yv,HA^HfWqXql#Vqt%4s\y\w +aD^>V8A[ KfJbKf`{/D^D_>U Id6N8OKcSn 3Q@_ +Ll $"7NmDWr#+d'c%c~00H^&=U&>8#??Dbt'MKnoBYr>Rffw9':*>39"!*0,891*-,(.2**+.&(!)5}//|. % '' #'xC--~,:5: 64&604%5#("(($3,",0-))C(2})hn|*t$^|q+o)$%150*:))(82."." !-"&w){ $ ) }&~{)"| ~ {!{(wwubAlin px8!+"),w "&.u.2!!&&~0~&6#/*+3)}p &%}s!(t*'/x2306/*5(0%,)0;%-3++33:,3./=+4@70,36@;2,;('0/ 6)N-)12:(3204%87$($3C2/3.+;&w9-4;/~..7G30%1G<*)@0y&-5K2~0{'?6z#~$$*$ +#}!?,!}"'u*&9/'/--7}!/B8;")  +'8"553 :"=4P7T/JOh48 !<9"=>Z/$>0F1G"1 !) - 0"70#5 4) +(102 +1E*,@0D,AE^%4SA_Oi0/+@ '= .E #3-0$ +!4/C)- ):K$2H^vD%< +;TnJ[s9/G+C'= #/+?.0 "4 01*<(7I:K$5%6 EY)dC*I}Dm)Wx[yQoQor"mq#s$bdbm!p$m"kho!mx&oqu$p fng_ .pqp+xpyupwrhpol6nc\hbf\~p%f~o#i!q)f [i"^]ch&NuNuNsUxSwQvc#e'PsTzY In@c DeNp?^ DcLm!Oq$Nr">eHjFh<_AbJjFc;Y=WFc Lk'So.Sp-Kh%Mk%Ki"Mk#Aa@_Fe">]Kl#JiBaFe Vv.BaB`@]Fc"@]C_Kg#AZC^Nn$_67XMj&Fd!+H?\On#Ji=\ ;V y^NiEdGgFhDjCfEd @Z?WAZ=WNh-D^%;R:RDY!*;3E&: Ma- ! "-), 1 +#*)>PgRp4R]}$>\Fc 7W*I2*EYt)'B6+I 7U+I0M4PEb!%9 %; 8L.@!(? (A +/G8T $;4N"6 ++#94 +3K:-I/LLj1&D +>\!*F #> #@459V(**F$@ 'E 9 + 5) #0+;%;#>5 2&B @_!Pl)5R.K :'B 5Q;X 7 !0G|QRt :)> +- !' *$- % 8- 4 1(  )4A(')  +P^AaoE^kB% #3!4 0AV$ "!6  + *.* $     +    /*/I)(+ + 1=-   +EW7VUk Pf )<y5The{;Idn^||'`u5JRe>W(;~!M^M[Wg%<q%*A5MYu(Jh_{.E6Uhx(&6L2F.B+F߈BE_O9X+F5Od~-F`?Z@\?\]yv(k{'Rm;Tj(UoTo*4 +2Vo+3N 2Q2OB\NiAY8Q6L.HfS3QCc`x {1|,KE`SpWuVq+F:TQlJdNoVt6T0MJf Xsr#_| +~k+.>*#'+,7)*<1$20/+0+7(0&*%$+ - $1/t#'.),&.781%D1>%*'*.*$3 42'( -+(%*";l[z`{^~o$fn{-q%p'j'x7p-YsYur"}'v.&,+$ u! }'z $" (#('wu~(v*'0nq m-lrppr8w'zum'+4 wn0x}z4xthqry(z#ot%x)0|(x%w!6}$-l0qt!x#p+}"}"}#4%*-~ 7~%q,+,.~&+G>1)+(1;6F%//-+*y +*-/3/ /8-*;/'=?!#$4.!.$(!:&$#*"".&"!&1"|!-+)#'{ph +wz!#&x%%n2)_*(l; ",!-6(!)4$~$)|%%$$1/52370y%0~ z2)'&"#|5;01,/*-(/5$,1.|"5,1((;@7848-0/,88;C/41! 1(#,,4~{#7***30<%|"&!$0u*+-+&-.,316"/J{140=2%'(+0-#4@8..~%~)3|)/y&2{"~"#*/%#+&%('~%}%,1/*-0}):,17A90D:031{",}$?.A;8~)x#60|'.25705K=DAz'.6''&0.w-q njq%u'y)y&n|(/~,r!y'|*w%4y)0~-u%v&v#z&|'|#|%(,{"uw"y!y#y$v!bgaheuv rqxwsq$h~5aJrVz.P2R 5T@` <[".K0J72+ 8.E(  ".$  #2"6( )!20 .C"2 /(!2+@8Q(*C4 +1G 8Q+&@7#   $5+("   +   .* )6J.,?((:$*  #!  +#2 # $ " +. !  AQ8"3&( + *(4#   +#2+( %)&<,E,D4!7 !7-*@-B4Hc$7TG`$0.*)Lb=!7+ "0$9+?&:1 .  . BW,Kd28PG_+Sl27P@Y 34 )-DQf8 + /.A+@0CZ)2F1D3D6G&6 *0EB^ Gdc0Xvj,WtRow3cklfia^`egdhm"bhnocr"s#mmgjkt"pqqtmvqrkhsghpl] f3_Y} n#r+r(r(5n%e[f"ZTyi(b"VzPt[ _#SxZ}h(^~^"VxDh X{ QrWz$JlWz$De?bVw%Y})l<@c Ux$Ko]~-Vw&Sq$Rn%Up+_{8Kg$Mj'Ig"Tr,Qo'Qp&Ll#Ml&Ed!?^Gf#?^A_Dc@_3RNk(Eb B_D_Hd!`z5Uo,=UA\EbPn'Wu2gDDaOm)@]Qo"Rso9Zo7GcTrRvV|#U{$TwSy`$Bd GbQj)@X0K)B *@ @X qP]p;&8AU"%9 G[*jM-C/F2 +I`4ewH$0CSi%Ur{@{De0Yu#Nk`2Vq)Nk$Jd"A\Yv,b,F8:"? +Eb-#A2M,H-F'B*F <"9T&; 2N.HcMJb8   /+%?.JX ";9V0K 6 %0I`}08Z +, )XmL "/$% +% ( '<1!;5K+:Q/'$(<%92E %4    "+"5Qd1.@;M6H(93D+B 5 *7 !7      AP;   +   2+ (.$   + + N_;AX/)k$!9a%Up'#4H_'Oi+6 8"9 +":/H6,E+B + + +AT-*,&HY1 +$1͔>J! "/ " , .;+  +7O( #-# iuKKT&!*s1h,@AVvIvHVn$r6RhCVTlz+\rC]k[v +`{ 4Zr h,,sQDPM\q:V:N0HPjUn_yG_BSk:[%!(-@0BUBW:P +PmPgBY vev"39O 3'?-'> .)<6\n>.8R=x!)V 2 $$#!+|')#woj MoXxfbn 2<`|Vuc&f&a~o'q"y"')"||'0|)-%y}%%#1"*x mx oo{ wy!ngsq+s($$*%,&z#~u)8%rk-uy(&xy!&u/46.d{1t!0,(2(2~92w%}$/#a{-#.-65/(1343|%,80+x%/&-170<9.&:-7''+x*&.'z,+$&!)'}#/ :'(-"$ 0#/"!}#y!*y-y| .##&}x))mzs -}84w -|(6)y;|t*#"%%)$($*,y*!.#42y#pps#-x", %|"s~%,/,!*&w0(5$-+#} o,(2z&&'"2'-z1$8-71&0.,7%7>1/)'$*'/1+28'''3B9+,B7'!%(()$~/}?16(.,>235.0y#).15s570 %8-%&xi}&jt 3y%{#/"'$'sz}%~!-.}'v ?9/~'A;7|-58?6z"953T~(5w(8-4(C+20}%/y#3-0..,,72++*~'/})*Dz(mw&v)B29q!}*42y*0z*19oz*y+|.u&w)idk(/|%})32~/mstkgcp#n$d_ennmrkz'mhj$s0[d)Bh?bSw.=\2P2N#=8%= & /. 4%# ## +  +" * 1& +0"2!4'8-%3&3' "65M&/G'>-C9R*)B+?$%  +,-<#%5 !  +    ! % +1 "# '*)-& '   '"+@H:      %/   ..-%%9 75 64 (*.3 6%A8)B)B"< 3 +)<4 +0,0 !85(>$93I +B#:6 7&<4 58R2*>>P!$: @U( 6 6L%)"5( #5,E5M%6K#&9"/ $#5 ,C@W)0E7P(-#; /F. : 3M-."9 +0H,Md/,C '(9 * %7 +J`+5R9V\x-C_:V Urs3f"n#ffhe[e]^i!W| V|jdijid]\mhmmkpplpove oinhmnospghjbu*e`jegjjY}V{PvZ~Z|yXEbHd"xRMg$Ml#Qq$LnRsMpd+d+t7ZzOxFmY}&IlNp\#DdGg9Q:S)C[ Ri1-C 0CuN0C "61E$9 Pf7"6$9 5ILa3*+*> Up'8VC` +6(CKhsEIg=W6P]w4Kd#1O -L +Pn-Nn)>^=[2N%@E^(0H:Q(> '?&@)D5!=;7O.G6'@,F8;W2O0N$A 4:&>+A$>/I1M!,# ;WqD=W(,2&;, .) - 0 + :9 +4&B #?,D5'= 8a{C1!7#5OLl[~%_=Z"?U0- + %+$ +$ %8%B0,">)?4#9 ,B6Qn6%.FY26KjJ 60-@G\#@S#8L !#9 #: %$?Hd:%6 +   ( 1   AP4,?0)D)@#%,   +  + Ri'`k#)Cb `}#[fzH"6 $H_25 :-H%>"9 +(BB\$%A _{@kB73J01La)(;"5 + 1.Sfiam;! *9! )7q\Pb;7M"4 +~Sc=x4h~2J "%<!5)B!;CX2JP`ySm 3XsAZ[q^w +Wr/ISkOR7J%7J #:-B;TepOe9MTm=V\t'Mv73A R\u1Hd7Q ?XE`vKYr/+E0JNi#5Q8VA^in&h-HDY dz(by%:X +AZ.c|7He<] +KiYty@So.x>Vs (B_t*o:PNo}q h3,D/%!.32#-4(4--7-G.54684()'"85$0%/$+4+|#{$M7ms"9P:4.88%&&%) 0.,+&"y x4;A3&='!z|u/%{ ~#t&xl46A01<8}*5717,93+2~&='w:&z!*2|'}&.*y$2*.0z$z$*t'y }#}$*|$,6{).{)|+~.}/64?5q2x)52~-p/p o{.kbkX _X~ |':ea|eo'Y}helfz+^^r(_iffhirlx&jaZV}RxOx`)DjPv(Cg!8V 'C$@#<* & /"85M,#6$ (6! +$   '(#3# +"2)**$0) %:(?,C 93 )?';$6% #! ! "16G+' & '* % !4 !'& %#%4-((1D+(! ! + #%) +!  $,3&  +&."!- + ! .<#,!,#61F 3K!%>76Q'0J"&A;V,1M%!= =5,#> 7'>#8#83 0 3"2)1%6':.*@"73 = *A5/E9O3-@CT)"7#8(3D AS0, 6$>82G!*?=R,/ &7 VkC7K"8N"CZ+Of7&"90G%?-)E-J*F:R0M#@5R &E?[ +\y[{h gZ bbbW}`bgfX~ dm"ek]^[]im"jjijmihd hic nefc] +edocecgo iY{ eo p!fih!h#a"z>i,i+{>Lj`%`$`&]#d)q5l2VxXzl1i,RuQu_*d/])@b Xz#NpMoHjQs#Qr%Rq%KfOj%Le$d~8Pk&Hb1Us)Ml!Ol"JgDdLi'Jh&Lk'Hf To+Xs/Tp-Je#>Z=XPk'E_>XIdJdOi ^y/Xr,Vq+IcYt/Sn$LlMoHjVzJmq:XyZy+\}%JrOsUv IiPoDa:W 8U ; <F_G__t9*=.A +*;'<Ui43H9N!K^3Ug9+>&9 2+"76:\zYx#?_6U6S ?_6T 4N +3N.E 3KHf)>\/M -L 9/L =Y4MHe$#@F`#Sl,7PIc).L =]Mk+8WGb(7P-/J/K 2.55&@-F(B*D +# %/-2 1 3 ",C93%? '!; 4+A,!73'?&'D_GhY{%,L If+5Q54 +0&9!5%>, (;N/$5 2#N`>5Q!8S#5L3L;Q Kd*d|>qH>Y8TE^&cuDxX(8 -AoK 23IJ^2&I_-_uAPf/4>T1$,2 +   & + + .    +$5 &"9 1 - &  +   "! ,?cGx-8S;L!' +82N#< %!> 6S#BAc'D=Y(F +.(?U$;𒦸mKeygsr[6=%!1:L \i:%6JX6&3L^Y 9U pR@OC +#Ar0/Ko2Ux+oca~KeLfE`+Ha$v5Z?Vq_y _yD\VpJe?Z/E9J (0:BO2F7$,~(,(3x&+"/$5w$(2($"($ $,##!0&5-'"&1.$##*,&)$)' &}(s&uws%6~*ns"})hkw!-+qgn&fooZv*v !vv0w}}}wot8ux~#t!v~ #&x {!t{xjmfr{{vuy%y!tw0~".*2.~%u{#3F80/4)/(*3*0~#3%9,()"1&<5#&+$1*7100-27+260;88v+F9=6z(3181+.,+(7}"4r6(!*#1 :{%+- +!%%}$,0"!y#{4x#{%)zx'yz,*vww|!y{ -!t9(ur&| Ay05z"z$-q v$:7|'1y)$5,:)+~2z&%*!!*2''~$vnmkfaStdx-6v'`|z(.;1&1} !+'vv.04%&|,(z)}~|4{'%$%-|<(!6)1%6+*775.0~$1/-/(.47=2/)0%*'z&%&~#)z!p|%y#-Bs{'2z&x 'z!'}";(,}(}(7.|+~,s"~-x)p!q#ox&u$ms&|3x,n x(/egr$t(o%m"Yb[[{ }/St dMnZ{Y}g$f!q(h^o&^]Tzdl dhf[ ]c`OvRz^YNxV`,@f0T1O"? = 'B+D/1)=!$<!5.#       '  ! +*5"$7AU/K`9#:"<7Q*/C "3!2+*)! %4-$%#:/   + 3"8-#7%    , 10"&' +    #$ +  &#24>&! 0 ( '76 $*8,G%A6 #>0K7 *E: <; 'C0K9 + 6 3 +1 5. +0 "2+ ) %6(97J!)>+@,BJg3'? 3IAU(&=+,=);(@3 +'%61B") +, +&;4 Lb:1G 4 AV/[pG, h}V7N$CZ.),B&=+7 + : 'BA^$Hf).K%B +%@D`%8Tc>^v:&10%<*E<\ Rs$=\1P]|1YLmX{\[~\ke_[\U} `m"\_j[v)g W|Mq +ada_ckdkdjeegkncv'o!ehkf_ o!_chifz*p!mz)|.bi$_Vwa$_~ r6g+Uwa&i.Tzh-k0a'Y|!`'Xxi-c(UvPrOri3GiUw NqKnKlXy+Wu,Ut)B]]x4Xp.]w1So%KfMm"Nk!Mj Ni!JdMl&Uo,Pl*Ok'Yt.C]F`pGsHC]F`E`IcQk"Tm$Rl#Vq&Tn#Wo%ay0Pi[v,Tp!n;~Hq:t>\~&X{'&E hJkGmWz^~%Uq[va}%Pl4N c&r79THa9N-? !2/@ 5''7"6'>'BYs0_x*Ca4Q 8(DC^)/I6 '. &<#!>8/G)A, * + +)-$#9)=H\)&>=V:S.G Wq+Zn,,==OnDLc'a" 6O;T1+C .3$ +-A4    '        + 7O%+%8#   % 0-=& 3, 81Ie+C`".K )G#?Nj27?\'3 @cw L[%+g|+IT'/@'`q1pO^& +  +. * 9O*0L = +6 )A !`x~5cz"Zr'.E 4K+CK^ %;/5Kb~(cQoVy E_Azx'Qk UiYmer%SPe[pY(#:ay#]vh~\r -]u(Lg'hN^[2Lc| 9O8D^cy-%=%;Of:(A4`=3U d!Ilf,h8/3D^[sD['?f0_}"Gd CZ 8ZVyC  C3Q2%;&:;T6N(':&  )10J4N0J6M 2H FY DY!1F5N%= +9.HNk?[EbVr Qj >VXn!Vj!9R Hd!Nd=Y=Y\{1!;!;Gd +?-Ca4;W406>\ Opdw W{Y{q"nv&SpJe7R +/I9+H,B\ w/~4c|Rl:R6Qr}/i*Wi$$HV>LEW#yw(%~.!)~ !{x%|g%~$yx~$%+$'+#/+ )&4 -%%/'#''.&-!rxwc a +ww7wz"ik]t!+} !wVw{,mx!d |!*o("0"/x+*|z,{{ n(0~#5",!/s%'v&u'r)&g3myv}!)r&f mtt-/.63-*}#~"~!&5/+%&C6'5;B@()7,17.'()%"'4##%<=-05=&31/?2^yi%8z0}1;f49!'+r?*#22",+D3!../7$($/-.|!(.13;10?83DD;6:CD<+204)$2,@-@Lw!9D~55>~1;6x"~'1'/2-%/.*2.~#.)|.|!"(0')&&$'+|*)(/-/*1<27;283-=-}(s/-x)$'{j %*z&,2o}'v!s!.u#u#w'pd.{%(y >w#qr!z)~,})m1p}-0{+j2qmm~5|4z0n!u&3f`w-n%m#n&c_>Y{y0e!n(Vwa]aTxh!e`d^^KpTz`[a_\V{_a^be#c![b^&4ZB =-K3P<%?5"8"8(& , &      '# "    -B8M%782G"/ (8*:-) ." & !!)0'  + +$ & * &0' '&$   ## +  +  +     ' "1' "/- #6( ++1/'@!;9S*:T*,E4 35 30 84 / 6. ., &+ +, + "/! + "3!7 +-E 7 &< AU)1C.*1 4 5"7+; ' +12 (?5 +,A5K$$:.*AI`55L"*@2J%< #-":(C9 +B](Vq81P:<\"@5R-H<]Ea Ib%-B/8N5NVt(Y|#Vz#]*#E)MX{"f(Sw^l%Y]]]c]]W_bhccd[Kqw5aTv^g``hejojjdiegd`i co$gjd[ak!`k t'n ldk=h#o,b"^{^|"g/b(a(Z|k1[!Y{l2Z{"\~$W{Wxg)]~ d&[}"X{ [~$PrRu_+St#?`Vv+[y/If[v.Wq+`x6Zu,Ol!KiJhRp&Ok#Vo*D\Nk$Wp,Lf"Up)Sj"MeJcF^=Xg8B\Tn#Wq'Sn"OkPk Xt&QkZr!by,h6Rm ^{*k6Z|!W|!:\EgLl!b0HfPpY}!KnSuLmf%@">8Tf*]z%;V +C\6K@S##5 2C#8 +Wk9$9 ))*.=.uNPoaEgm9Zz+4T /M ,I +4K*?";!> 1M5S>\6R=Z,0ND_"2O/K +#7S*I "@>^-I:.J'B 5Q6/I $2&86MUn-c}2a|. : %7S$? '+GAY* !1 ,I2O$"=84 5 &8#<.3I" #44 !9.F:R1H:TJ^&7HYj42E +CV (< (;J^+C[ jG$; +6M+?&7E-&  + +4$ +    $     +    + !4&(> "$8%& +  % .:+5F,XiBjq1?0+.@ %8# ?I2 '?%=,I0K8;V.J+G,G9Blv-E$EOiu1; ->7In0P^&  1(  +#9Uq92P8)@"9)> Mc 0F +h~11Iy?eg~'(?@VvE@ZMk}23CcPpA`Vt `{Qi f~'TjE`x3;UplG3G 4Ne!Rk Skn[s4JWr@c]8XBbUuy=g22B\PiNgD\c,Dd9Sk1s(g^2:@Y1G$<4M +&@(<%.   >U#3MSo-\u/CYo(Scvlj(;$&y":}$0%~#"$}!{ptxy(0} !qw }y&#("&3"/(#6-! 6-!-#23,%*%-' ~"wqp\s v|$~#|+u6\ +r#1'$kav'Zy-nx")y'~s&| {#(#|yoz$}%1%2?1,}!z r7zwx%"%]zw"t&wrusx v{#vx-u.}!w3).0!+"(*4(,073.6(<130%*"+$$~$$*:+:-),=~'|&uv!|.c{`}g%w0;fYv e~8~-7/2+)4|;ws&p{/)*o9umz,gy |p n )20"*$!su}v#"[{{({wofqs}$|#.5&*}"z{ |&1=$)z~!&:%A+:+++~08v*x(42?}(}$'/+'*}'o;-3&}''{$w!q09/z#~',*))y$u!5;:q%y.y*=}+16,).'B?&8&,/1/0%8*9&vz!}$&&.-6))++,1',)%766A3738302&:,500267883894R4/<5.#{%t0,}'(9B,1@v/~3~3/?03&|,*|${%u(i`z2u=yz|zvp{ /))(*{&~%*|*,++-|$|&*|"++-*')'+*{&m-s&}{++t(.3+~%&gy"(})8/s"w(v%pv"s|&0w$lt#u&y)u$rnlliv${*x&-ny(}1}2y3i!v,fjchs+[gc\o,s/d"k(h"dn$n&`aZz``ax4Uy]bb`YZf ^`Zd#g&X[R{U| S{ PwEk+N +4R#B +1R/L5 +0 0 %")#4"/&#0    +  +"$'  +  *%83 +-F9S+#:*&56F).) # 0# !*+& " * 41 ! & #*! &$   +    +   +$1"4'8 /, ';+/H94O$"< 7 ',2  $/ 11/  &.' +) ) ' ' # +,,(#8>U&(B30K2- 0 2 "8 5#:#8 +-F#7/ ( )%9(=:/$"84J!+A'?+B6O$)>V"F]+1J ,6&@56(D ;Z4VEf"1O/L 4R/P 8X2M 6 DV'.)D^}+QxQwLqAe +Lpa(Jp][Rw Os Os Y~[Z^`^c]iffdm!`Wx_Uxeedbjq#Y| +qorXhg]V|Tyq-bYa\af!agfdt*f`]ci"cx4u4b%^|$Tu](r9:[Yxi,d'd([z"`*UvSva"p0b#\za'SwWy"a/d0LmPq"Ml!Ol$\{0Nj\v/Sn'Yu*\x.HgQo%Mk#Nh#\r1Nd"Xs+]w1Ne#Vn(ax.NeTj\q*Sl"}KQlZu%QmLiKhIhTp$TnXpbx(UlRmRp!QrRsn8Ce7<\^}/NoY{#o>Wz#JlGk`$k2JF,H +7/G5NI\%:O5H*> +2D 4'8 +):#2 $4]k<@T)AD`(E5e.Ru<^&KRr%-L4T?\bzB !*'7P?\/M :[@].L+I 6VEd#/L 2ODa@^Lj*1QFe(6!6 +/H&A9 -I6)C'Lf96O /IF_.:R# "(/G4"8$)$)&-$ +'6*=!503:U+1J"!,=>M-5C 2 3(? Qj'@ZlD+C(B a?\[t ]qVo7SMe Mf[r]wae 8p)E_:SBZu7+A29SNg +g(*E0I (Gj?Qp +D@_Vpy:|EGW1D +5=U'[o7qH";@]h/0Le*>Uby0BY?U3@S6J+Ha$99W/H +IWs p/Utb{h o(ZJd:VDb`m(G@`.1?ZA_:XLj#@_hD[/q>[u29LVn Sk 1IF^ 4J_ut*" 30:{(x$/z!'y iotxxk{#za~|x&pn(yr +,',}>1,%#"##% %.)'%('|)xzwo}"."& #'mdp('yz$,~&1r&|").-,)&#40%%-4t-(+:141.&y!,z#2} !+*%&(#+-z/|%"*,%z*&}$+|#.~!43!,-$0%$!)33''./z(07~$)+2,*}y(.*//2/,0%/}&)~(z&w(p&x/p)f|0x,ls&z/~.|'135,&1*1v)3.z$:*,+|!r|#$(!1y6-#zs)(2,w{|&gw*7/j}&-+0~%83&+#z 1{?#1#&&95H'80*?IE:}1t*v+4z(920;1**/1-$:$'.1,v#x&:3M%+77=-:x1/1y,39}*7=7-,1/4?AM1,4~#35(?)15A0((*',*&)$2B*0;w('0'/)$#($/3){--/0<"*?*11&>3C170/"(%~#z"z$.8~,/<1<=s*o#=m+(.*<,)70y$,w$qCu*~!u-~%~#~%z }!!$'y|'&'y**z#wt1}%x .#u&%'#""{~)r*{!x}& y}!vxy#~'~'|&utps!w%w(w'liomlv%w't&s#|,|-n}/v(p mlhhjt%t#fhq!t)de"`]^`f]dV{s-i%f"`e"i&f!i!bbl"dk%l(Z{d^^X|c f"X~`l#e_Io IoIqR|XS~_!^V}YWJq=c,O*L*K3R >Z,6";+B2-1  +&   +  + +"  ! &   & +2%<=S,5$6.>*:/C$"#4HX8&61A - &)  +   $*  +   ! % ./C(      + +       ! '- 2C 2C &/ 75 8 "=7+2"6, 2(/ 3 ! +  +# "  +  ( %94 B[,5P.I;S#'>%$+ 2(</F#:.E'=4+ '8I%(<'<.E!73 7 6O!-F7$:0H4N+E?[$Zw?39,K+K+H +)F0N Hh#Oo*Ed ;XOl.(HDb":V*,2MDaXz%OwT|!Kq8H 1]q9Oe$1Jb}2qDSr%c4^22V +5A\#c'= $0 !'fI'F-LA^C^.H=VQm*:T.K ;YJi#Tt0%B #> +'D)D$@.I'5 4 8":'@Z,8 8 + !+. +%6%(@&<27/G"'# )=!+ 1 4$9&0!1 +/C7F*8F.( '2D1A[*E&CFc&Lg2M 2O1M87+ !6 63 0I%1/8V"*/ * (>:R;Qj,":Q+@+ABY l+]uOiIg[w!Ih +Hd +UuAb @_ G^C[C[">[r)BhYtTp%Tn^vEIa4K8QQ1AXE[=Xe:u=9Yg+g,[va}']t;J F[*>&>,D 6 13-C +Wo(,DQk8SE^TqC_DbVy [y!-F*C H`s";as%3Qx$Xik6 +8T4O %Me4$: ": /*@W!/F3*>S.@ +,'&?FcNk4O4Q;X @Y>[&A0JBZ@V "F^&.J%B0NCbn;Kj Pl!<'>NdI_n<0:549=B@T0A:63-627-=14=)|$:0}!++92:=z /{%.|(z"zryv"$/'~#))+*,4#6&/16"'134),:+$r-}%1z%x'q"q$~4y1n%w.hg}Jm73)*9--30+/9~0H&/21{{!s{y|&*.sy"y-/2)r(x8+{$/|!'% ),%0x u %| uyqq{rqjv u vusu!o gj{.s$lp"x(hop":o#w+1u$2fbaiq%bfm!m lhu!ic\|t2n+cdS{[Uzl'[^a\c\h%c_j#ak"q*b`]bd`Zd d _f"[cJq;c0W+R>f +GqJuP{QyNtLqQyLu"9b3W-O.O+K%B)E94M& 5-'     +     "#&'   '5!# :J0)= + 2G"8M)* ( *;)=DW71B!$2C0!"    # !' ' ( +# +#  + +  +*= 2I,%   +   !!   *8-<!0**;(9 ! 6&@'@$@'C< '*%##7+=%;#7, ' ,     +  +(!91K <,F&<$8+/ 35 6%;": 7 4 - +2%76J!#: 71G!#=&?6P!,G#< /H/E0F4QHd.2P.K-L Zx8No-1Q=\;[<[;Z:Y0O +2N6S2R *74:V!Ro.Rq'Uv%LpVz$LrGmV}#NwOxMw\g'Y~Im _ W{Lo Ns Hr^T}]dZp%e\_n*m(_r+\^]}fml$`}q/^[ hi#ba"^bf"o&c_c!e#p+bb^T{w1g$f"Y~l,Z~l)d"b"_~!_$Pq`'m2]!b"_}!g'o/k+i*\zRth0a,^~*Kj^~.Po Ut$d3Xx$Rra,JkSs^|)PkXt#Qm!EaKgUp*JdHcVq(\x-HeTn!Wr"Le^w&RkSku=b{'^u%`x*Uq%LjGe\x(Xs!Wr$Yx'[v+MmVt&FhEfJlRsBd ?cIkJiEaD`b~2Sn!l;@\`|0Qm#Lf7R =W "5 7L#8 -C-!1 +(2dy9>T@YQVt) *!@>^>^9VNf/$ /5H*2Zw;*J1O =Z?Y?X5K $32L*H >]'F-K !&F9W%6 +$05 ,$;4)+- +( 6 +-+ +$+4 $:6 +C2J -* ( #4- -C.+*#>1H!&;  0 '#-@ &;"<d3h5j;_|7FaVp,+D/K"> 6%=+ -,=7J)1G! :(1J'G`6 %3 E]%6M1GZ EdWr@"7  $ ! +,:% $  ;I7 +"  +&-!  +   51+ +%'3#Wc3 oyjo=m+:N0up<(D >Upacl4F-/x~+g#La @$ ۓf 5qkz)Qfs9R_* Ik6BVQf8L!0(: !3]q8\%1E [k&=a$3 n9Yo2.C4K%\x.5_uLbI`Ld)EXtw:6J 6*B #;3N8P +=R/+>Ax}1Qf(0H )A "5/!. %% %3G =T =SC^Nk)C,HGh:].J46!; NBg>d@d Qq_{bXw".K2N2P-H F]."9 +0 + &Ja7 #'2I5K=S$  +Pg8A^]5O :U@[pA^r4mCSj/-D}v!n!gPnfk"q#,12--'('"nq'po` jo-vx|z#|.'$"|)')%z''$&)0|,z''+%{|#%x" &-'w wx!+2}"*,5*O-#;=*644>5Hy)<-3+-{ 3:++'~!!u'rl1%,t%)(|2-$+{&.&%{0/-(/'+*{((*#,u)//+(*)D,)*5/05,6450-G/=2-1)%,-;(&~3(/%#3]x}*).z$*060$&)yx'|"3(1-//*/)1-4*z&~.ol uzmob -[y}tj%"/"9.Xs+(/h}%2()018+.)).! 0../ '%,*)2,z!}(=v&h7r#nl7-!+9,/$%/+:'7|!?a =` =b8^-U3T(H A "A%A-H!-F.8- ( # & +  ! +% $ # $(2A+  + ! + +%Lb=&8.& .?!%9.@!% @Q,, & +#  ).,>!0/, /C#&  + ) %9%(!  $  +   " +   + .)7-=&* . - 71J#1 7$>+F,I =7P(=P, ) ,?2F%"( "" !$  " # 0 70 +%?4O!: +'>01"7 2 ,D/4 5 "+3 "63 5J'<15K#$>!:1K/I.H3L;T%8R!4O7S;Y!;X,JT[q07M2G 8O/F G[#FZ&*<31$: 4 "5Oc=\oB/F6N9U`}* +'<9X6Ki'(B -  )6<@`7X 1R Pl(7P/F$5#<(>v_+#*AC1F#fz$>MfF_ i2u=#<,A=R:QKc:UAZ ;WC_ Wr"5N-B`v(&9CV/D 5++1J +[w(WpNf/J ;Rq'Uo6p7p4?JgOg @Z6OJf-H:T 1K9S 9#84H 7G &AS!/C1IF_b}5NbJl^~EdVtFdOs_o!m|'svcuxta gqeXySt-mt(u&}t} '-,!-y).,#)&z+(("%.l m%}#zw z'{$z#$} z~($'&w uw%x~',%-)88=v!Iy4h*` l)m)c}6.)~8$$"%n.%vy)yq."&s 5,#.2'|!z#~"%8)2).-'0'!|%~"$*9|'2;/22'&*/*y)~'4275+z/)-06%1} #~%6~!2~#%+#2//7+/$0,)'.u/~#:,,(+1004,.&&~||!vr0%wx!nu&,{y#(,~-*x"s4(.}$1;/,/06*%//+'&1.#-6)'.&)4|nk,`| nu8.x +!9.2*++((,+1.2+)7,2,:7/771/@&39{%6/4@5.12;9>>G6?;<=561&-*+3}%.7.64.4(***43bEi>a=`EhFk:a Ks=c<_;]<]<`8])L#D -N'G? <8/ ,1 6"9"6"$6"3(7!1)*$7+) & $*+ !  (;M31F(":2'9) #3GX:3E#'8+;"0(62@"$ +" + $$' ,/( + . 4(:! 2 1JbE)  "/.    + +  +(   + (7( 2%95(A#<%>*--J;V*.F': & +#,&., +1 0 "0% &#9M*!+& +*0 8#?8 #< 3M6N?U##: 7N#%?#<80 +4K!8 -&<.C?V'2I$<9 9 "< 'A:S$&B:B_'9TA]%?\$8U9VNk09W>\"5T-L8YDe(;[Rs0'FCa#<\1O5S%B8V;Z5TBb=^Ik#@eHl#Dk?hEnItCoHr?fTy"Y}#Z}Y~c%b$R{S~_ TXMuPwX{Ux_#`#Tv[}f(m.\}Op _UtUvVxVzj,UwOqPtVxRvJl s-i#_b#Y|Rxm*t.Ru Z}g"e!Z{[~ W{a%a"\^~Stg+VxZ|ZzXxo1Uxh*y:o2`~%]$^~&Oqb~*He^{)UrGdSs"Tu$CfKkOpTwMpDhY{$IkXv'OjQm#JfEaPl!B^LiYv#LiXvSqYvWt]{#RnRn`})MfQlNkIhHiLjQnTrGfHgHiPq"Uv(Tu%JlQrB<[h1KfIbh@]v56O0J 1K7P Tm(Kf 5O Yp,7M 8N )>3H@U6L/B 6G.CEY**+@ + 33Q7V?^lHVt68 +Li1DY2 + /pW/22QOp *J:Y'B#;6 7 6"9'.6P'C *D9 12MWr>6"; -!< iX3N&F^1, *&#% +/?# &;&<.#>4I#-+5 !<-,"<5Q(:; "A6J&}[zG?U1I0H5N +9TNkPr8X +Je*@'9+6Jf*;X:TgQ.H# 2&6 $.C-7M "<Ro-'@ + 0 +  '7-(ewF

    1..611223,>;/3--3/,v23.:211"):,-042H503+#+u'~%Vs-.-/0w 3/'/)0*#'#%$%'(+35}#D0t*0|+u$.x*~/u'u'p<,)y :w (@++.9x#+/).5'"/%'t v#tmqy"y#mw |&spv1(!~$~!{z!niw"}%w rurx,tju\ePxs!x*jin u&r ppls!moldfghkgfu(ZZZ~t*l%g%RyPvbg fk _dn k^ s mY +6igp)i"a^Z^[_b aT}QxSyLq U|[~^ZUz\SwOuRzW}TzSxQvRvOsCf:^=a9].QIl>a>`8[ +>c Be:[:Z1S+L"F&I< #C ?:04, +$%?6"<"82!5#7( ' **)?".%>1* +#    "2$7.D%'<!3( #)9. !"00>"@L0"/  "   # +/& -' ' +& ."91 !  +  +      ''"  +-@)> !&<'A5 7-F*F 1K#&! +# ).+@,A&< iJ?VfFNh/)0G3H* &5#8 8R%7 : +$= (A,E2J6!8 7 "<";2 XnG4K 7 +0F'=Xo@1H0I*A3N : +E4L8S"0KA^'=Z!2OLj33P+H)F"?=Z!>[#;Y#(F,L4V<[Rq28Y3R3P?\"He*@^A`Bb8X6V7W<\Gl%Fj#7U vJc2Ge<[+HHbF`I`I`#*>?T9N:M0B*4F4D->- *;* &8/ '%BZ{)Vv'Hg' (/*F8R#Rq0)I!? 4PEd Cd<\Da*DaxC(Pf/M`3&4K8O*D ;T$< +!/I8Sn;+F2M& =<1RyR\c+8HaC\Le$ =Nj]}0(G2OA[+A +4$; ;R%:>U#D ]v@" !   1D EZ5dE2P !AR(;I" + ++J]5L_5/@! + 6H)\sO $8 6F2 (5"#*"$  $$ %4$         27"? != " +  +?E2$Yg1g%Vl638~ &e1Tl1+pj fVg.(cvhxJGQu*AQz1z%$FYr2Q,D?.m!%s"cBb^~10G\Ul{1VrVvC`'A6NB[f#:T5MH\AV1F+Cx97B9F5C Na+'15I1-B +6/Tn C_D^ ?U>V*6G&79D_,ILd`|#Hh>`Z{aToj'BYD[4I+*&$3 &6 4F%84K^w&7)*3/K2[rl,Xt/8W ZwHgu67 +#84L$5 $   + + +.?X!Vr5@]:X nD?]1&A6S1P-H/H.H '@Rj-G`!7K4HczB1J)A+B %@W%02I)A 72K3/J(?0D +%::Oh}9Vq- 9&C.I)Hb i9$;1KLe+D>W";B]Rm u:RkA[2F&;-0 74 +\m)n%u+On +\|o#j}.jli~'mwpw%kq i9.sw"swwuz"qf *,- z rry!ru,}txr{#td--y#,(w u~ y ri8ut'z*(#7)($()y!)*5~,,0,t'_[@c l9NrMqw>Zy|4Jw!|%{!$%0$+u&&~"+45#:"'/<319#.%/4z!<7B.5~',500~-11*5-$*+)+3*I6."$200.*11-0),3/3A)"2! 7qx|(Sq}&,u3/v/o"3'25#&1&(%,)6#0%~$.{$~&|&,+.+t"E/s}&(-}$v+|%-''y .}'~&+-#&|ov&ro|+o"jou!fkr)mtouuu/3'p{srv!*z%w${&y#~#qrqjbmfm!jk"halhpnaw$j}.ais'o"fkdp%s'g^v,r(h"U|X[l$jffn js$nlu(dc_a[_\Y^g&_a\SHr U}TyMrRy W|U{Ns \KsPwEnElOwPvLsFmLqEh Jn=a ;^GiGj=_ @c0T2U7[ +9Z:Y-N*L0Q&H'H$B0M$,H!3 1 !=#<"<:T-$="<72.&90C&$ +& ' */* )-$ !  + +0*="!3$5'7"2+=( ' )87D)(5!(3% %$ + +  #'6! " *0B'* '*4.' +%  + + +  + + +  !*0F! !-D,E1 +!8&;',3'-+-" %&8:N6L3H4L5O >X0I 0'?3IAU"6Kd,Kd/;Q#@X*$: +*B 74M6KD['1'?#< /E0*A#: 0I4M&> 7 /F/G.H-F2K,D : *F:A^&,I-J-I1M,I;W$0L'C@_,,I >Mm:0N!@=]!Ol1>Z 6S2O0N.O =^3S:[,M 3SGj&Qu0Ls*>eJq#Ny*GqHrTz)Rz'W~'T{ Qy]$Z"HrT~MvU~Qx`%QvCeNqFh [}TvUv^"d(a\Zt,_n'i$p*b[~]s3XzVyg'q.d!t4_"[bi!g!l'_Z{`$Z} @fV{i,\W{_X|`dg&w3b!Wy\~e*SvKn^$Z}!Tu\y Li^|&UrLlKkJlPqVxKnZ~$IlMqPrIhZy*GbOjKfKg?ZUr TrWuVu^|$JkNoRtVvCaRpZu#RnXt#KlIlVy#MnRqLlLmMoMnRu"MnJlVv OqDgMm8T*C5N 5N a|5i '< 3DXi9$6 (9&5:K''% " 3L;R"(@;/Id2)E1L (G$C7 > ,L;V%;V( 7(<g{T3I!F^8( .C" 4- !)@3Ea/*E1L 2"iIYz,%D-I>X 77P9Tg;@\,G1M/N:[Fb=V,D& 62C'7.K#9 / '!- AS;6F% + `=%B~[EW'"1   (" * #'   + (!! ' +!  +      ;P*>Y)0O)@U6'2%DJ6\eBXf;:I'֌.b~9Jd`zmg(pv#x hat`s+A0Cw"LeHVmr+'*;fw9.=PcC=n[sw-e}o 1=U-Ms+z&on`|2S#E4Vgc}!YoB?W:V%BMk-HNgn,-IJbNg7K 3&`x%r/0< M^,0G #8 5*= 1/`y4>ZMb{0":H_@S0 '/.E0E"68MRlVrB\ Nh +Ic 6M,Sk-u*3.2|'z"{ *o-c la7w(w$**lq)i+2UTu Qrs:i+j*r)9}*-,z z-$'wz45)3;%1(1+/%(&$$ +#7,%1)%$$#-/~q}"r4-<()v)-,|!x)wuy!*}#,77)+$),+$.)m 49pz'-%!$0$,}6%77-..&/# /*613(,.*+%%1;5/30*.,*48/64271560&x%p|+y${ .5~ *|#%#00.3'(!&'(*71, +.,y&X{Ur&%$01.<43*@*9'y(%0#{~ (+&+'!%-!$07%&&)#:y +.v$x"x-yswnw )w4*4n/}"4{!({ sv{%tlt$ip".nnqsppsond&xy/u't|"su#})w$trs|$woru#r u&u(dhj%o(T{hlx&ndbg[ ^eeRyT{ hh\do$m"X~ k!hMtk'l'l$k eo$jt*ci!fR| T{HnKv JsKuGo @kLxNzIw LyRO}R~En OxNtBhV}] \!Fn MvKtLuCmElJsEmIpBjDjOtIn>c:^BfCf:_4X +4Z ;`Ab9Z/N 2R+L)J,N.O#7+G %A964 7*E)C0 6$:#8/ ,0(9 &(" /#+$*     + " -/."%5(;,; -+9" -*&" + +-!3&8' + ! ,+;!-?' 3H),@%",,+"   +  +  + +%2*7!61 3 6*@4 0E %8#8'>"<4I&-> #3#6+>!#)+);?R$&8K5+ADZ=S=T3I*A 9O.C +#7'(4J@UUl+7M ?TAV":+^vBk0G#: .F3L$? + 9 +#=$: ,C0G8P,G4P0K2N'C < +H,G+G#?(D3O!:9 /: #@ +9W1N'B +8T1O*G 7W<\2Q!?,K<[ Be%=_Bf!Gm#Jp#>hFnCkGnKr!T{'Qx BiMt_)W~!]&X!^'KsY~[ Txd)^!_"l.[~WzTyd#Yh"v/x,bcz0m#k$|7d `Z|b"h+g%^!]#^"^}8`\_ f't7]"]"h-W{[~cC=u,bl&e"c ` Y|c#f([Qu]"X{b"i*Omd*SuRsRuOsVyPrDi=a \-NpEdSo UoYs"[t'Ws&Lgc0k2QpKhYv#Xy"Ij@a Fhq8Lj?Z\v%`Jj@c V{#Uw!RrOrRtRtNqNpQsEh Ps\ m2Ad +yE|MOi"To'Ur%,H"AQr#=]9W;W/I7P +&.F/F )< *<.@'9 7J5F2D.A* !( +  1E7Q1J)E0L/78S!1J"< %A,E > B_/If74Q!5_yG6N!'<,B&=3 0K"">C\54 +3+ . +%>$?'C&A4 (Jf6Zw<<4NSk24J0')4N3MmL6%A9WPi'sF\t59N1 *>)>$"%-  +"/'3 +&8"b}N+K9.F/"1 +   +  +  + +  '  +  +'!,         $ $&,E1LPi1K&?>Y_~#^{!l-l#Wu;UG].A #w136".0=,= 9Qbz:4J 3)&;,4i/8O :RVn+1HE[&3K4u)RM8f|_;VIc7N1D >Q1B6F *7F%"  ,= 4, "BT}B3I("9 5M1xQh%>Y]{a}(.HMgF_3OV*= 2 63 2 +.+D +4NMh'Fa 8RId Li!6Ke_'=T+E2L <"; +8N'> *>)=6 +2%> /+3Wr)g4/Hh}0)sVV~G^w[rUsw#3Nn}%~&4}'x"x%y#z"&tr22+u$5os q*y#9}$,|*q{"v"~!~xnx!x$` ~'} (wp&z+w%z|5&b t |%&*#'nu2}(r|(1..4|,o{!#xrjge|-z)ox 3*s%j'`~&yAy3, #5*3-08-163"/8811+&).1(/:3;z(:C3}%)~$5rkx&*0z}w-)~## '(,*%3E4&-(.{/3$!{.x#/lt} z'|"|$%z3,%+('),!"yz{ x}!|y)(%-|" #%&+~#|$+/s }%{ u{u,.>}'sw .z"+q%{0z!vpifm|*w(v%2ostsrgjjb +quyv~x!|{ .u#z&4ty!rw (nv!piejm"_bv,ajod rn^eZhYWZ\Z_c5dks(k]fp+]i!c_l%e\Hq@jIt +:d>f.UEm@e +3Y@f Dg?f Dj6]?l @l Bm Q}LvJuJrWT}IqGpLtBjItAk?j?fAiCkQy!Aj:9*E#=#>0K"#=- 75'<$7/#7     -&4*&"     +&"0! ( 0.$4"6)! ! '5 -" +'  3+=$% %6*8$)% %60$ +/( & .'      +    - &8,>0D!'*;3G'/F#4J$,B)- +#6':#7%<&=,C+ ,20 .>"&..+ P`6"5 3F/.B,"6&??U#6$$ #Si)I`7MRgPeJalFnM32J?W((@*C(E+F6"7!7 +A)B+B-C)> 2I : +7T#*F">.H-H$?(D,H1N7S#6 3 +1 8: *F3P/K-G2M)E/L.O 8X/O @?>_$;]!4X?bGk#6[Os'GmFk DiS{-Kr!Z,\+ElLsPwRyV~Z!Sy[ [W{TxQt`"a$SwSw[~Pt[_[~ft(Z~kj!bo*c~T+B 8'oKn>^{*&@EcA`5V13+*E,J.NDe7W3P )D3M :U9X=];W *A!3  %7!8(D -H.I2>Z&51";6 1( :!:#>13H2 0G!415  .7'.C'' =P., 0 6 &A- +0K#@4 ) =N&5O"-C +!5/+0.F+@&?R%<4M@\-H)     gW=_Nl%Ph+'9 9J#-8  +        +  +     + # M`C;S, *    ,>s7f{[f+@D-2!bo:L_\s z-Ojh ~ x]vLjC\p,f%Navy !cyh@\f!}%ft4;TiVoe|(Mcjkx-Sn 6Q{9m'Tl?Y3NCb6W Lh D`3Uq#Nj2LIc=Vr5u/bTp ]{b#QlTm>W'_}iH^j00;U4M >V9O %8_u/??Sg%;P4K%=9Q>W:Yuoz=cu +L_wH][ru9[k8*ivM#/ ' &3&,0"Nc#w)'>-&:.CUpNk4ST&3.'?464J26M9O*+=1KPl38P72Ug2+>/C2I7N: !+$8 /)!4 4$: If+/N3R*E$>7QDZr?Xp3Jb+%@s3q4z9{3~37v%s#s!+32" 8,'0/,62#"-'3$!'+/s14))+-!..$)#}#x ./`}32wx *w urv*{"~"w.y1&qA{#@p"z)},}(v xu%0CeH\|r%p 6q-8@0,4)+~'y yw5/$.w3z~#&"-?$&&(.(nt*2qs;'#,'6,(&s:$"-z'#*/85;/.-=3*,}z'%|#m1+$&*|%3,-6'-$)z|$z#1x#z$t4,)%/))+{&(-;.02>0-62$13-0()+)%2717081x#uz r/x"r/r}#%z!{!z{| &+3*5,'# !/u||v {{)5,T/|ptzszy!'%3~ $%)z}|w~$k"t|$$~(  o~t"o~yyp|%0yp|}vo)}$}#x#kqv!,ols} %1rly&krt +{%xg sttxkwhl-{$| )~} |"vx&z&rt!rmjppmhb_k\]`|,mw "f tq j\ dl![r*m%\]b]c`^Yd[o*Z\d!\b\PtKoEk ;b;eTyMp1SNoQs 3T-N3V.Q5X?bIq8a 5_OyKtJrHpBk +?j Lu=dEm.ZAl@j:bCmDmCkFmFkDi8^8]5Z6[2Y2Z 6_9_7\.Q+J +-N.L)G ,K/O@8 + ,4 ;">$A$?1M%-I#%=(<*>) +,#1' !  +2@*# #  +    %*+& 4D(*-$  " -%-)""  +*% )  + 0 )AW@*  .* ( +        + 0"3!3- %:&8/* /$7I("6+0$4). -(<($("5(;4DTe9.?5E)8G*8 #4-? 7J(8H#32$6BV#\p> 7HIZ)6J,@;Q^u$cx)Lb8O 7N,D +3L-E%= '? )A$< 6"=0!8 '=6 $: DZ*DZ*&=$=(B,F(E'C(C#=!:!8!9(A";5 -%A#?$?,H*G7R&A.I#@3R4T;[?`$+J3RAa';\=`Dh$Jo(Fj#Uy-Hk!Lp#Kn$FkIng5ElJqNvLqV{_%`%V{LqKoInv9c&WzSxPtKp[Z~\Y|W{ffaa`d b g&w7]c#f)TzZ#V!R~n3Z~b&Lpo4^#RzHp XUzW}[\}\zl%`j#j)ZxZ^RuY~UzV{RsMpQrTxSuc%Z|UwY{e*Uw]~%Op>]MjGcHcQl[s_w"_w&Um [u'Zs'PiG_Xr%RlCaVu"_|(Mk3D,>CU#Pc4_v=oFn?a37Ddf44Q Ur2`}A#A +1[z9aA.LA\"+H 0N1Q 2R *H2QCcHh5T+C' 0 E],0J?[#B`'+D&> 8 B\.#<&<1H0 *. 4(?(! %+F (;V':V$ZwA4Q?Y%B]* 9 %*?.E!+C2 9-Qo?dP1M2 " 2H!#9E\()@ (? 8O 1M`3& *BIe%<[Nj)]  +  + + (C >\:Q$ +     +   8J6    # + FT2v&/ -6'  +   "-<#$ !2E,  + $3G`)DE:GVWbCU|+ex Xlz(f !_|ro8Qd p3o x x &\wUn-~UgVbv%gy)|#2@V&>]zWpJiop%Vq ?YId @.F5P ?Y#=&B":6N 8P;R-HA\3O2VIk Tt +@\Pn>Zz2`;TK5q: +(7 /B=V;S7S *E;S PifD*> #7':-E36+D:VnNe{/DY>VJd9QRg!$1;! + &$2 +$5)$5iF.ER.% ey2l 2MSmE^3JNfNe>U .B5JxF~T 55Jaw:b{7$<+ Ka 5^u4\p3iH6Q-E3- 4($7 '* $5 #42%9 +1.D3L +1|UPj)3/F0E"82K0J854+,!<%:#  (!#9.F.@!0 (8CT*,Ui97P 6$A*G+J/K4$:0EBT>U?T&<0E [r,h)_uUjst{}s{t}$ux {!owr TyV} ^r#Z Z s h[ ofk_ lee'$n~$&/{xsros0'w3u~))~} * tzsh~#jirdxzlt{%2;Ao.y=u:n-=|05?2|(-3~.z(|,w$53y%v$u)z2Iv6Bo/7v)7w&37.3*"/'{!,5*+%12) %#, }~0.x/5'&{#5}$((|&y lf,_}8&j` +fg-{w-yz&z!.924),n.}.{+,v x&,)'$:~*},3y(1|).C.>3)+~'y 7~'o{!'-,5j )z'%&.,)0-.//&| |!'u,v+-z ||"%(21+z$=.059s#~)2-}'+#,}-.$-o}"u-,|#}%+*{1|-}()n8db~rq5)$w,}4.#{ @?*.4C=&-0*-3/);~1,,|+'|(|,~0v'9mf,d (~%frusrt nsy"!!(}$}%y#.)%}}sss*$(#xS&y/},p z('('&''.#z'~%-u%|}zz} $4{u"qm k vmez##o~qm !w~ ~&n|)0/f rtlw&*#yAz(*n2}%)y!x!vqvz"|%.:4#.+&#zsmm{(fgkega ` inmtt y&z's"p {+qqotmkc{-m!an(g am%daXXZe%[Y^ `!`$_#PxV}Z~SxMrEj-T@fdfAhCiAfBh:_7\6\3U1T*Q/W 3Y/V(K.P1O.M9W +:!" ++'8  2( +%9)"2$4#* +2C3C$5%5 FU#'8HZ!Xg,6E JW!1CDS8G&5/;"  1(4G*= +4G9LEVTe!Xj$T 7L +9O1I(@ +$;0F+C-,$: 8 9 6 5 /2,5KBY-/ 9!<8 !=1 +63 54"92 +#=4%C =8 +2#? %A < +#@ )F )I 6W1S$G $C (H4R6U0Q:]>b=`DdQr'BdKl!Su*DfiTn#$A+KHf3P.N;.F6Q<De!+M 4R;-I4[x9<[[{.Lk:Z=\Ddl>Ws4/E#*)6"ViEYp:8Q9V$B6S9S.G 9 5P 2L43 ,A/ ) $8"0##&9#?Eb1/K:V $A :X;W>Z#Lf5 (4 +.H6 ; $Ge3=Z)()$  !f{F6N,-' )C]"Mi&Ca 2Qg3+<#&2  + +  +   R5N 4N +5S0PFlSvPn Fd +&Bc|1TkD[Vl/guc/B':)>W:S8R/BU?VWk'8N =V-834-,#*#3":+ - .*:7J#':&7 +5(B +oI(@V*>8O+-D*B5M8&?Wr7Jc,3P`y3nCg/)$.$. "' ' $"A >[5THa-C.AJ[ 5I#57J/DC[ AY +l0$}*t$m1(y#w w!n]~h]c_u$gZna +ldc~*] u%e` w w"y|$y%}&mp&u{!~%z!v;%|$<} u(})y|xq}!-+y%titw"u&z1q-z~00t)u'f2:s')1/(w#D1:)F9K=}26q.(qx}!'xpr.{})~$@(!#,'-!'$Cv4m{"1)|"+#$}8-4&z%x%i20p ?n"lir lk{"w) '.%l5!!('{"1%w}*5$}#t3Tn5g&5~")13#,4(.5))-&$/')/x (.,30)+$){ |#z#}*.{,z,--1|%q|#y pfwg ~yp,uy)!*~*#$;'(-v*ytqz~}v*%# *!!%++$!&&x6$%y|.u~!s"ww} | #.wszn un x}u$".($t}}!vow!-+{|#{!#.*rr+tor{&.q}'vmx x"ps{%z"z"}%b woa [ +d]\ _f^fcd_ jplt!nopr!c^b^]`_]f!YZO XXP}S}[ZYUYZ [ Q{VUKtU}R{QzV{X~!Z%DlIq8c >g@h7^ =c,L9[(IGkJn6Z +-Q7\ ;a @gJsJsDmEnTzMsDiJkIiRu#HoAg=b<`3Y ?d?b:^7[0T0U1U/N.R5X0S,P 'I4R+I 2Q4T5R/L+I4R!*J5U(+J!-L#%C- + +0K)8T/8 8..! /0'++ *%4*# ( )6"" ! ++&%#2 1->$.0/( $ +$ />'#3!(  # &%$""''#6 '+,*&7 3#4! $   + +  + +(  +  + + ) ( $3&;=O+'"1#0-  (5&5+=M'6/? ;ILY!"#5A*=J7CGT*S[9zUTf2.> !21@ 7G-;DR4B>O/?0B )=.D#81+$; /"2 2 "8 8 7 6/4 ,,6 &-4 : )D 860 !:%=%>9 +!? 6+I= #A%@(E5Q0M"?-L&F/R%G #F +.O(H(HIh-4S&GGj&6ZDbTt+KlKl IjVw*NqOr^.W{&PsSw[!d)c)Uyb(UwXze'Xx[|^"e,]"e)e*i-`!\t1u1h%`Y}Y{`#ZzKk^~#Tvb(g.SzPvSxWzZzk0d,Qs_'h.[ Y~Rwm3Trd$_|a ]]~ZzRtWzg'RuXzHj +Uu[{Yx[{Zz^~h'YvPl[wQq]{$[z%>YMjc~,Ga Slm.]w^w#PhSk^v)MdLdTl [t*C]Ecd/?\PmZx#Vr \w$WsPnKlGgJlEgCcKmDfAeMoPrYzQs7Z;` `'5YSye/Vt'8U 6,J1ODb2L$;%> /E.G0" 5 ,((9/?'69K=S;W +Dca1[{--LVu/%@ 20L+I*I Ts/B`1O3P Fc Pm*=X1M Gc.J Hg+;Y,L1N>Y&7O%YlMoYHb-6Q>VG_!?Y"=8Rm2'B <,H (D 6341E':T2+ +( %/ (<!8 5 5Q-J%@1HOm5"@ /L0L&?*B)B),E75N&#='20 %!6"4$  '9!5+,; +5A+0B1 &/K*E[qH+@]oB'4  +) +9Zu;*D $72A'' +    ) ,&! '   ktHh +  "/ +sV;PTh _t\f}y;Xl48h~UlMa{Ws8:wFp$seOk(#Zpe}YrNevWJm}btfww'`u0]tFd)HCdEl Lqa'7S +)B*/PfC]VlB[ B]UotXk`~JcRg%AU>SD[-A + 14H I];Kr[Wd3an=L\4 #'-  a`t^HW(): "3+= (9 /A->;N%#3 1 ,/ !9+?+/ +2 +,1 34 / +4 8 12#:3 + :"<$? < %D.L)J/P!? )G0M*F .K">/O0P=a +L E@c'7Y(J6U8WOo1Dg&4T?^Oo&Wv*KlNn Xx*LnJkHkUx%[~+Uy"WzSvVxQsYyRrWws3^|UtKjZ{!Oq_&c(`$e'f(\|]^~ c$VvQmh-Qn_|$[v Sqr7MnPsWxY{ Zy[yl3Yv[|%TwEiJnRwVyg/Njb%e)_~!a%d'Xwd%b#MoVxVxQp_}"PoKl`!Qs[}d#Vt]zRoXwXtOlJf]v"Zu _yd"Yu^y Xsc~*f0Slq=Yo#RiD`Rn"Tt!b+b)Yw Vq#^{"VtGhIlHkHjIlDg +]#zBc-1SHkEe FjZ|!:^@f +a'Rs ?75R Cbc:8.F %> 4K8N2!7 3 4 +5I!,$4Qa>ixJ/Ib.2HPg2+F;X" +;:X 69V"3'D Ih%Fe#6O6O@\,E-G > 5R7T"<Zv@'>4L$!   '- +"7&>*E*D2M,G;W%6R*F$?"=02 +3 9%>.H1 %>2I"Mc<( 8*' "! $4/ ,; +  + + +3F%A^: 8Lc=6QOh)'    ++G,=V,.B +   +$    !( +# +    ~[uc#7=~FFO' +  + & !"  # *5l9| t !{z C{$~$t@YG[ =Pj#Jd/Gp4Ys Oi&l azf| f~m0Rd:HN_Th G]Pc +Rg ;OA;+5fzr`u +8Hf 0&=';/J +ZGc^z1>Y)C,FLbYp(]v&,B9Pj:E_W]`~NkRof|/,B !:L- +nB]MG[I^`s,k;\q*-nQI\'$& #9R y4Rn Kd,C 7Ha9S$>64-0&6 {bu +.'?Ng#:`w6"8Jc7R +#5 8gA9P #1 +Me2Ui0anyJ7C & +(6'BO1"0/ ,A,* :S?X-I(@5/J*E2L)H 3O 7Ld!b:G " 2.6 +5; ( +$'AwF)<>R&<%d w` q$szsz'/txligv i|pf ew!uo}&kX mw!(~vsstvqyieirc +a~p|%qmq|')i{$vm m +q}}s~ywpnn`d8{1m&k#q)72)x"0~%~$/(:/*H1CAv3t2;t#z#u{'Bl{*q}+n},+313$#}$|'}ys} |+*#~')43/%9ux&Dr3j6|,ne>djt-rlmfpgm)5_} r$w)/t$/n'&-t"{-v,n#}/At"v#1)/v&u'ilv&}('/+}#tz|!s'(*0r9q54&*!3}~%?x02"- |(,3#.|$.3/*~"} 1+0'+5-u#^x z,jDr -v%knp8s"E2p$v|{$#%|/$8(}"zy#5#"w0(&,(1+8,./.543u"o4x'4*w!~'5^IjPv'Ej:_-OCdIi"9[Ab5Z7Y4X?c%Bf!6[1T?d6X@` 2Q4#5 ,(# $ . +  + $  + +  +   +!*   "#"-$3+ +( &."8;@E'EJ.]c9TZ-ck,5*4 !'297B,&2 &)'2LS-IN,!'38DGT-"1*9 5F1B2A"5)* 0-)&(*,('/ -A)2 6 +&= 7 "9&=!98!<582Q/O:= (F2N.M3P0N3S6W3T0S>a%6Z-OFg,8V2Q;YJi*@^:VOn'MlPp!KlHiYz)[|,MoRu"MpGhVy[}!\}"]|"VsPmh)Xuc#b#i-a%e,c*b(Yvw:Zy`#Yx\yk/PmSpJfPiFm4?\Tqn6d*YxVu[x_}%]w$XtXu!>_ +\}(JkLnb-e-b'a{'a~'[{!i-Vx_]|QoQqY|Ut]z"m2Fd Qt^#\MpZ{Z{YwPmYw_{$^z&Xr_y"t5j(n,^~Zzd%^}a}#Xuc,_{*MgvC]x-JgTt RtEf UuZwXvVuYyMoW{[ ["WyOta$V{LqPuUwX{ WxIlXzW|#+PUu'(HJf.qC4Q %@Pm+3K 77L)$; .E;Q#30)"  -&6 +.C]Kk&Ge!%C +H,G $>/>]@`/L Ng#,F=Wa{940F$&2L9V4Q<%B $B =9SG`#-H Kf(1M&@Hd2; @ 6R ; +,4K!(3J!=S)6K0S)1 + #!9/F3 -.D1 3 8 9* +2 65K&-/+ !"%!0,"    c8nEL` +^tl!kne(tPh_w]uQkNgS4g|M`_lDTNcAWYr\qJ\AS>dw&FXOboy!OfHb_|D`B[@ZA[>YA^ +`%4O?[=W)FTsm7l5Tz!Tu7R ,00/Mc+8L .D%;&<4G+@ Pe'5M Qi$.L6S(@4Tn&?X) DZ"oE4O 2M . $2MEb9V@X&= /B"2 6G?Q12C`r0"3$z[c1FUp$:R ' ^r  Ke3*?[rB'? Rw6Ib+C$9H_$Rk!Zw%0L]v9#: +/AW0G DZ/1. + '/ + + %#0% -%/2 +1 "-D39*A0HqO)'&+E'D9R8M J9L( /+#:$@ &E_%Wo14AW Uf.(20Ka(.A +NF\,})uz'~x 5y{o{|ole a~}kdrz uw!fk-o-#y)gtmwz eojo${ { {#6t1:+vm}"pto#r{ vttq"ihy)ias"~.}-014-539A6;|$x!y-y |#6+*:g}.bj'~:r*u&/ly#oy'b~u&6em=z$y"+}(x {"| tus+} +('{}"u)~${!v).z%}'+.2|*s"r"t#1:x"5u))rlu#/v +|'-g6s$/*)/v3~$+t|zst*:w h~(|%soz&qv!('')+p )>!0!vz#{}.+33-)1> :%1(-3.(+u:y$-w ~'u!id~y$/..',-{".!$-*&&'1v8vf !&$+$* $+$&*)~$|)0r"9t)~/n!p#n ky)lq et)t,v,z,z,w*71*--~+u"2=09u&Hn~,8/{%x#,)7{{s}4{")(y'lt#fp5{$x!z&n}"*r$~ ~ y{"'xtk {{sn~oyd{whtr{zz!b q]icao!`u!i$n +wq ~ y'-m+"~z vj ted}zwtqh{q #udvrswropm{|j +g}y~x}%u+{&u!m ca]Ui$l&cj a^m(k(f$Wd$k&`]s.Q} P} \[RYM{ +Lz SN}QXYRM~W"S RP}Mza-P|Pz DqEr@l6`!I;eEo#Al=g7`2Z 2[1Z =cAhIn;a<_DgDgKmHlZ|,5Y ;^@dInFhSu%DkHnElMuBjEnHqAiFl8_Em9b .Y8b @h@h@d 8] Ad:_4[ 6^?d7ZZ!#?/N0L0M8TTm.?W2K ,E +89 ((F +\z61N6R"( +\oL+E2#? -G5Q$? +&#:/ %)  +!+ &  (;;R*  +   ^cAu[QIKt#    HV(cw!kǟ + S/c!Nh >WG_ -k]zX*FMiNlA] +UMjMo!B(J2UHe7N') -<0@uBIiIZ1bt0")u{*&z!!x+zy&xv#*(x *1)3z*}.x#z${~*#2vqx'l{&v!*&2#),{#,1>&~$ysuqr{%w"ru)}#~&20x'Pt&-9Bx#),*.3.7=55)|"/}%w (w*)t 4o!cx/m }*ny!ts3@14;e| 5*|#*/rtwxyo+{--/ y+7*{!72x +)/.y#w$u#ls!|(z%t*ur,sd=0(+u:v!.|'-*0xm)~',})|(48r!//1ojm k|0?a{-fv!$(t06'p~'0w fp~+y$,0}$w7~%z~"*vA+:'4z"w>,-)>%3,-.2v12/8,2C*qA/*B3)v z$*7y@6u|!$$!%*#"0~!7{l-,+$;1-8*)!*&v/4y)q&A`k _ee~2o}/o#k#s-`[z/|,A/~'(2{*1{+s${-=j<|.~.|+},w&{(6|&*w!~)y&-1,6y(e`bcdemku!v#vrx5|#!yvw~#{xsng +yntpve +pf +"ub mlgz!w"Gkmtfhaq"p u g "}{q|&m(ump}udisid)p#f +f {qnsk m vo|rqmi` iq{$xiqowrpsijjjg_e!j)XZ\[` V\Ry`%Kv^$Q{\YZU~YQzp4R}Gs Gu UJv Jze,[!W PQT#GwR"IvKvDpJwAk9c5_,W GA(N AD.V .U +4_1Z Gq%Fm >fJr!Lr?gFjKnQs!Qs!Np@d?cfS}%HtLwCkY)Pz DkBf]46^>d7\?d8X@a.N &G5X8\2U9]>bFj'5Y)J +1Q+J-M#$ +'#3$1+8( + +     "      !   + +     + (0$ $%QRA56"<=+\]JfhQqNS69@$-3&) +06.349@FOV+BI.5*/(0 +!*1<)5 +-9(3 ,6&2 +2B,9#0) *", + *!, +.8(+8- *#!. +/@+('8!8,. 53 ++@1 +*&%)!7,2 %91 -1 2 )2 !; ; 976U $C )H 'E +/M/M5Q4R3O6TC`%6V2Q4Q6S.LSq1If%9XMk#Li IhDcY{+Z{*NpWz!QsW|$EiVz#`(VwWtVra~&b~&_{#_}$u:e*j-m.g)c(p4m/h/\z%_|(Wt!e-b},To ]x+Xs$^y'a|'Vqj0[y"h/XvLiVv Kl[}'Vv Yx#KkTrEePoGfXy%b,SuZ}!X{Uyj0]"Y{QqNpXye-UtPnJhUpQqUtWyTvVxXzKjUsQoUsOkOhk*[vZw[|Vyg(e$Z~k,W|Wy!h4Ux"Z}(^+Wz"`%`"Pu]~[Z~a$U{Sx`"X~KsLqSwEo IpJqLoJoUyPrOqRs@b <^e0JjVv%1I2+DD] /F1J9O 8O 4."9!8?T.M^>$ +% +  -E8Ni%6Q7SA\5P35Q|\=^]|:2*H`$7O@X$@X(^xL!:P+Pk@;/ *>[9W3P <4O;T%7$5 !5'<5 ,)B4. 3 (@ +"(&7- 6 9 7. (&9&  +  +- 9H''7M^8=R')!: ]y;6S(F &D&C6U 2K+5 +1G)Ae~K>U*-* + - . -+E ( #2I +H`92H( +     +)".%%Yi1?F  + + $!,7$ % !0#")qscU=J DP0XhBQh)\w/`z$C\b{HaYsLj<;@^Oq +w)Xy 4Yt g i l2IYpCZPgnRevThWm _wWkSh NcG\l108M9O:Pas$^r%x3_l1A]p]l~7q&[vv/Rk +[tUm$*?>U,DLg`{(Wte+Fb,H4T5S 0N 4Qd1H /%   !,-%0%4 $42G Lc'-#40*; $'<2!8F^&)DMk/L3N 8PAZUn31ICZ*L^4:QBLf Of&&40=!# + *;Tj=G^-Ws2Ig#e=:YcID],) ) (6:N!4=R,C6S&>KcPdq3j|.$/CI\ 4Ni*j:KbKc-D.D09P4KMc/(@ >XTm-*@)="8D[^t%9P BX00?    "DT81)B+H5M&# #0/ /3G*  -,4 +4K,DBU(&: 7J>O,*8) + +* +1 )(*>+AQ,JZ3,=+?:N1G0D[q h{2:J+&hlpk{} )(%,##1)*|t|zxy 5v!y#w"nr"v%2y&|{.*vwrtpu*=.1+,.('zxqot"6y*t'x$}&x!|"{$1=}-<54ty"|'::1-z(099~(06+?urjw u qG4w)w*dpv|&n7w#k~.:,y 00*,6|).t-101}!-6'"u '&*q4|%-36x"}(6ot%30qnsjun|$styst)4,+3/8+>x+z%.w#-.w%w$~-}-|+t$z*:q!x#t%r#/e'<*v(x),}$/'/y".1{'60@6C}"**'#3~'3wt{"{y>'1)-}%488%*()&%8%%5{&=43<7w#2,:*:r./s4~/2#7)+9~!;&/)!#$'0+(~-#/1,+v"w'j m#j!do+e z2n$s(o!n"{2m&r-n({216z#|#v!u%v'8v't(@;83?17Fs#{+2z(qw&n"q%t)kw'w%x)AX~bdft*p fdv$d(wrw rkrmtz#qrss{} joqlu&,qdx%+w z%me.rt[~ppw!ti uyri +k tn3lxd +okx g] t%eElgg\}&pqj i tg +} mlg rw lhp#r"qlpz iw"1hgbhX\U [_ZT~XV~XSJuS|NwKsIr>gW#Fn@i ElMuFlHo:aNuTZ#c/O~VY#I}Hz?q>kDq?i=g8c7b:d(S8b1Y7`0Y*RBf%,N >Or.(L &L7^:b;cBjP{)FoGpGnT{$HmTz#]*JqElQw):_>_KkDaGfGjPu!AgIpU~#OwBjOuJp;a +Bk@m=hBn?hQxDp>e 8^ Ns%Qy*CiGj$:[<]Lm(4W6Y9\,N,N%G +0P5U0M'D %B 4S=Z$9W!6V1R6X5X:\<]"=6S2M1L&E< %B"@'B5 :-/ +42 5!=+ +") *- %% + ! +'94D)*$ ,A0yyg.- + +), .2"*/?E#/6&&$/+6 ".'0#. %1 )6 &4 +$2 ES*BQ%'=!8 'B0.H\0 $2 &" +' 5L!Kb;8*FC_-6N%0F8R$.JDa2% +iҔU'8H/0<I|.y~-Z1)Ľ +& '5!",stVcM Xi?0br6Yk'NcPgIeAc a`zVtUw cFb5PGPe`wH_J` BYey1O^>p#[m,C?GZt+VoWocIQf?TBU3G8LXcm{%5I.?J^ k'Nh WsZs0I2I:R.H3MXt$Xv"A\)E=Z\w+=[;X)C526NCU&(:J%*< 9KIX-):#3 (9(9;KH^( "600 )1I2 >S"/Fi3[s*G\ .&ACZB[#+? +5L Nt?k + /=$(;11+H +5$6V --+2?,}(y#x'z'8y"1-z$1.-),8:2~&*4(~%>w *3*(~ 1!(,B|'w&sz#z -3t0~$'yA(.sy&r!y'mgr!+m6+z(r o94_} z&~+n,)k&:{({5*)<*.+)$07q43),1/.r {)|+1p4=y'-s!(1+3{#y -|!#,}#*~&30)69)%7%:00uD/w%{*y'|&+~"~!8zyz'%%&!-$/+.9{(6s$5{*708*0)$'(!j &{-}9,&1)~#5*%#'% !0&(&*y-(#-+5t#t&m l!n#}47}0w*t(hi!r+9k%q&8x&/XsH04{0Q<9y2e Fm@fGkQu!Ov Gk;\ EeVx*@b;^@dIld1[$P|HtGuO}RKxDsX-Jw?keNt$Ls"Ov%8`Q{+0[EoFpQwMtRw BgMt"FmHnEkNpPs i:FfOrInJpJqIqGoBiAhGk?eJp9` 3\.W=eGoFp=d 3ZDjEk;a8^5X4W<`:_)M;^?`(J 1O.M4Q,I%B8T#5NGa1%@)D9V0N+I)I0O-L4R*H0N(F*H$C(D$?2 +8- - 7/ +9+3M):P/ 0+ $  (:!"1(7 *"/%2,5H,2/#4! +)+ *,;%!.)6"" # +  + +   +    +      $ +  +     + +   + + + ! *,!" + +FH5!" +/3*3*34:AF"4:+2.59A=F/86?+'"0 +,;(9'9 '8"1 . !, * "/ &2()3&2!.+ %4$3. +#3#3*;0 1H!855 7 1 +3'<!410+ -/ &8,%"3. **>,B58.I6T.J0M'A2M7RC^8RZ,3 )5P&4 +5 ' +-B # 51J"+1$?6R,)A!2I()  )kH_}.e82G $8qPNg-Vl6@S'+9 0E&!:,ILi)6T5U/O 4S,J5'A24K&*)!2'91) 1E:Q(. +2 )C-H+G6 +&@$>c{V !(YW%dbx3  +pR,ia=M?d[G&5#% #&%8A:|$g{J "  + ,;?RC^ f"_IhUzLpTrZwhfe~AXSk:Rr.YlP_HW=L/BH]r3p)McG\/D3Gv1F2E@RSeZkFRUd:P2G,CF]H_ PiTrS,A 2'B8V/L5RJfFaE`#=Ic'4)C#?'D!81 +(92C1D#4):'GY0jzN/A #/EV4,B/A@O0f/7. 2%(;J]lB); %9 /"60G 7M9N$8 (7  +#1N,H / =='C /"90 -;4E"+3JNhXts!>I_I_s*:T2H3JBVIc!Us)9Y 3Q4P!7.2$<+#9 **>21G\p3K`Wn 1G6L4"3 & +" +% )'#3' +  " % % +' +#"3 ! *9' " ': /% !& 0 23F&9mGM_2")* 2v u%}0k eq|,h}&z"u y lm{&p$,{ q7&1.47}*39-4%}#x),{w*|#30~.u"|&uwtpdppy&t(vmn(~*.78u(7z%7v~&+*20x'}.5{.}-/+|&/3|$})y'u#{*z%|&2-/v%4.@/z%553| &,){#<413x8#&,&(w"pn)} 9o+.(t~&{)6w%l}/t&h=/z'~*{%o|'-}'.{'8y%}++(+8~.#s~#0394*-#$'2-,z%6.6)5?/s!=={)q=,5n724/~&{",x{)'x"}'4,1{!+-/9*49)+00q q#x*v#~'t8qu!0!'&)y}8'3$'*=w){1A|/t)m!u#})+&~  %(%}+~(! $"u*-'-+~ /!,*A*~"++{$*0&#'*st w%1t%31p!nSip&o&q(w-2t$|*q,z6_}j%t,q'y-~4~/0})v#r#k!Ip%}1{,{+o{+v)k!e;l!w,T| +o$m#f"Iqah!y.5go}'jhn}+np2qfjWzlgps\iat!pp7my#*.*3~$xurx|!f +y|&x#o_ r ilved`o!v(Z`jby+chs&ojleaiknqrh_{.bbm!\gu+V} \N| P} Qz^SyRz^S~IvLxBksBW{&U|%:c +MsNs =^Yz._2/P]}0^|-,L ?_Tt*=`HjOqOteIp!Ox!HsEmHoCj8^ KqPw$d AiEmCkHoWz,^2@bHmhEmJr>eAfBhLq'Fk#/T :_?c=b.Q +:\Ki&0O?]!<4R;&B0L2K3LB[+,F-H(C = 1O+H;/MCa-= > !? /LIf526 %2,+1 +E6&''#<+ +   . *,:$ -)9")#! !  & +      + + + +  +  +  +        $, + +   +       + + #%  "  !&)*..5*1)2/:GR03>.7DI$06077>&- :C(2 ,38@AH"5>/8", +(1(#!- , (8"0 !, !-!,$#.'2#. (&1$/'4!, ". +%3'5- +2B&8,>"7 5310 +0 . , *! " +# +!1.A(1,11+A /F5"= 3O=Z9V6S1I 4KHa!A[b +JnEhKmInn<_}1A^5V ;ZGd He$Uo2%>-!9 '< 12-2 #  +IZ]Ki-9W(D ))(>44M)4jWz/-Q79ZjI?Dd$b<8W0M ! +#03G$+A$+E"8.-5O/M!8+G4O"/ &   /{QE_6W \|2I^0{\2O W6O 6J3 +/?"Pe;0Sk4=X=4T0P +Xz3/O 6U\y=7 . 4 "7 +7F/=P6 &1E$0( # +*8 +45 &(+ 9Q21B'#'-$!2&4 #1tCr/=  +6A[]e5&).=$ % +& Sf#6 ?Y1L2OZuXq"@VBYNgtBD\:M:P"949V-L.J2, $)AA[!/(%;85H +;P;PAV/B ++= '6  *   !0'5(9   +  +$2! !,* ' ' +$5!3 ":>T/.C )  ) %3 *6'' . +;KyW%6 . +ri;p-z6h!l%r)h3|*3u!fbg]|n!q$z(d z%nWy j|0y.6Pvo!u/d ?eTzLq g1er+v#},r#dgu#rr`7g,z#{#&s[v"y$+y$0q!{'x!*|%(vx)%h vszxw t}(y$x&wx~)}*y*w)~2q'8jSz s*dT}i"el%p(go$p%m"hek$fbfht+S| +dWMxc IyCqV^GlLuKuJuRIu:d +DmKrEmQw"8] W{+Mo Hh=\0OIiEf+L9GjDi:e'P]:Gk";`Pr%HlRv$AdBfUz+Ag7_Lv\,Go7^ W},9^ LqJne4JkTv^)Ac >` LoAh;a +\.a2EjDjJoFhKoCjEkPw$9b@i>gLtCk>cQv)Di;]Il#7ZJm#@c:]7Y7X/O Jg'7T8T7S=Y%'C*F4N9S!7R!(C:6R+G;#@"; ;W$Tq<99!> /L!=4 1 *88!=74 2 3 &( !! +) !#$ ".-<#$3.,#   +/9%8B-  +   +     +  +%-'0%-  +  +   +   +          '*"' )(2'3 '&"(#) + #'$. &/ !""#", $#"!, - ++#/#,* "+(2.9%0-8%0$*3+6'3*5)6". +(5)8+:"1 )- +. -'<2 (- ) $  '9:M %: #: (> +D0HI_&#;,E .J /L +Kj$Mh%7Q>U1IE\Tl$?YHbB]Lg`|4Mg]y3Ki"7WEfGg \pNB`:Y8. ' &A6 +1F/Ga}D4Qo/*F*J/Li7% 2E_/-%?: ;W%&@0+ +4K%:R* " "% 6 "La:! +   .Kc#E_-I!@1Q 6SMf*7 0* 8%;]r08PPi$Vp3Nf(*B_Om$1N )GDa&$C>["/38Q%;P)'3  '</,** 'C0%4"5 +@S")<?AoPcnH^t"H]h.Ob9<:6._SB(!娆Z +   +\Ɇ +25  +7@FS {5}g/az*@Osf$3U3VHjKnMoVuw3Da +VtOp <-9^z1IEX ?T 9P.-G$? 6-BQd!g}52F$8!40C4G/':g{;$:6.LQo%pAk?)3G '8 +$Jd)I&, ;I*9@"e 4'@T!5C!#.  + + ,Rk7119 +Q#0@$ *3% $2"!  +&- 2C&9&,{,y-v,n#q${*Wt\w deaz#k^}xow"x#` {'/)~#"*|~ y%%tk{%fjnv"/mt%kp#iew'p+~| ({!)*/$z"*,ty(s%ix+s%u#0+|'w".+lhgo&{/=:u'e4z(k}*,jggx%{(t$gJl jl!dk!j be`km6ox!v$!#-$xyy1z%'z&{"09ev&u&x'u".7B7z&'.)&y :w tllffz('3,'%3(1+($8;+y"-.~%,}&,,Y)g8S$V(Dqb7;eGn ?eGi 4V Ab]}2=^-N;]LpPv DjS{"CjBj%L9bbGiOo%?_@aBe^7-N:[Hg$Qo/;Y;T8RC^(2L$> +8S1L=Y%2N,IDa-3P5P&@#; :/L?[&)E!= +.J"> +E/)- 9'B4 +877' +*=!,#  +*>" 3!"2%5#3 " +* +     + +"+'   +     + +  +   + + + + =C6  +    +         "# +" !$%+ +)!,( &0!*%*%+,/$+$+ ')%.'0!* ) ( *2.7#.!+ * *!, +'#* #+ "+ 0719)2.9=H!&0 +,70:6B1=3>1=5@1<4='2 #. ))%6 /.!1 (9*!1 .,?J_(AY @Y@YId"Ie";T:U9VTr-?^?[FbXr)H^Oe\s&d|,PhTne}0Wq"a}-h4MjSr&Ll Su(Ji>\7ZWy'NqNr_$e,c,k8s?`+])b+TwKmPsi0c)Vub(p4RvV{QvMs]RuY|X{St[{ RuQu^)UxNrb*]"W~RyRySz[ Dk +QxT{Cg Ej TyT}Gn^#KnOrQvf*YY%Lt['[#Ty[~"b*j2SsSqRom5PlPnWsTpRpaj"Sw \TxMqh)X~b_Lq +Lr SxT{TzPyBkGrP{Kw\"c)g.W{e+h*_!^c!\^V|[RzQ|NzXR}Nw Lw +]!JrAd -RGkMZ~$Fj7W !=F`%8U'C6R.G 8Q?X =V/E#:'42 +A'& "/1K Vq-Zu2:SMh$Mh#-G!;=V?X1K8T2N).C$#; 7N >U'=T(17P;W1O 'A (C++;.NA_.M+C,C>R. "CY/1J5/kO3"A 1N If$Rp4 = 0 ;%A5P%@/J +)1 ' 3@U12 +/J=T,4/*    ++ &5Xr3=T (:SOo%Wx2,). CY0 $Ldi6So"5P6PYr/Mg JfYr0Ni+!= +G &&A$?* !+4K(/5J&40 %(7   Nj<4 Qo77W qY'6zj'3Ϩ=nz &Ohm ,v^tAWWf W]fN_[.-(CLSRZ xahY1s6> ЁY('g} 7[m"g!(3 / [Yz<\@_+JJk;]Jg.I2Q=[6U2EhtPnYu 7O9RYvU=[ IbOf%/F[q#UkJb,B/G&=1+> .C-AKc#Yr*Uu%A7?] + *$5 +!42J.E,C\ +%Ok2/J3 +4K#-?R"EW(0o:z"/% 1m%->R?RUi`3A)4_*4 ' .3SV\i?J 9 !;O>O;J"!*  + "0#4E`nH\k +&9 !6%;1"; 7PLe+?YAW,Ld(;T /H$;&COi&4J7P:Ol=j0`r>+%?3Rn.Wq*Ql(!8!9GZ&:N/B,?,&4 /-+ !%/ )A-(=- +( +#1)5"  +  .& "("5""   + *&(:&80AQa51h+3}+y(i8kn!6w'x/h$f(Z}t/z.p#z+jhp"i p%Opjp!nb}(*y#x!w z | /'(+)emik}(y!.~$w}!}"~"zyw{o$~'w!rv rioqhc]cQz _]":cPzKuFr5aLxJrLsPxU~Gq[!Q{=gb)XN|Qb(f)\Wd$O~ PN f%YMv \@hLvSHt=k@l >j +d0R{Ah+RHkOrFhb*Ce Y|"wj :f +*VMxec4e4Uy%Bh4[ +Ks#EmGkHmPu#9^Cj[~']*Os<^ Ot Ou![-AfAdJl[{/FksFIm9^=d;b4^DkBe?b8Z;\=^<]Zz/8X =]DeAa>]2R,K -K ;VC["9S6Q7S4O>Z"2NJf--J9V=Y 3O&A &A ,GJe2+H+I#? &B*E*F1K%/4M&%>,G5P%75N(5. +"* *-+ ! + 6K.,?$#3->!0 /# ( ,9'*  &$  + + +  + (    + %/#   +   +  +  + + +           "#+,! +&* % !( %-% #&)*/+0+0/5-8(2+5-7+6)4&//9/8)%/ -7,6%+3$+ +8>5;(/ '0 +8@5A8B5?;DEO"0:-8 +2<9B*4184< FLFM?IJT%#./; +7D0A1A*: 3B6G(< 7MJa%Kd h>Xt*Pn#Wu,Mh!Yt.Gea4Wu(d1Yv%k4`v$c{'ax#i,o2Df~,j2OjTpQlUs"`0Dg=` +Kmb1a/Z&W~e*UzZ|#_+Np^(UxMpm5]#Z}!a(Y|VyY|f+LqLqRwV}V|OsX}Hk ^%_)TwY|#Z~$e/LqLrLsZ"LtRzR{V~Qy4L9Q=V5PYt8 %0 -%: "9 *A;R%/E 5M7Tg??X'*,,H?[6T'E*G9&CZ.Hb/-G+,D2I!; 7">2N3FZ42 3%5#+  & jXq+i}?);+Oj)1L )#7/@ +Mc/C^Rm,GSo5O8PC\0J #=TBY$( &gGOj238< +;S$Y+D(DIe#&C9T>W 6PQpzC9Sqj z$Ww%1k#]zIg7V(A7N0F +Og_EhRv(Kn"3X FhIl]-W{)*OKqAg:\Qx$CiGoZ(d2Uz%FhEgGkDiHnEiHj1SJjKl?b7Y @e>b7\6]0U +9ZJl 4V +7W 6V Qp&7W 8X ;[DdHg =[*I1Q3R:W'AIc%Ca ?[C_#9T?[ eB2RFd&?]7S ( "* (5+9 +# $ )5  + +  !   +  &' *" !    !23-==6&& +$%!$&'(,/*,%'),#&).0449,2-3+2,2")8> ;A#)/ & !'%+)1'0'$0 + , &1", +!+ *5!+(25?094>9CAI!.5 9B:B?E8AH ^d+V_$CQCSSe)Xj0mEez=DZF^Ng$AZNlQnSq#pA`|2Zx*Rq"KjFeWt#Zw%Qml)]wYs^vXri-d+_z(Vss;Ws HkSx ^*[#c+W~&`.MuY!e(c(^$SvOri1]$Y| _&QtRuRu] \!ImLqNsNrHp XNuHmRwMpRuFhQsRsKnq;QvLqKtLtLtGoJq Mve*OxEo=S#8- '7+$&7+@ 8+,JdHd2M .G (@ 89Q%>$<69$?'@(8,/F#; *=U#;UC_!8UxT!: +8?XC^""? =Om,A`!7Q& 9Q.Yv>D_.K 0M%</G0G34Sp<)/:Q)&>,E'B76 7W#UrD;W. "-(A6S%'BVpB:Q(3+=#GX>  !%  +  +!21- 5Kg &. (95K~Q=\Jg_{-Ng3O ,";Sk+E\#& 8)(Kc/64P/"> +F1J"4(D.Kd2/J*B  * / +0< Շ^pz (}~x4I23L%, rL%X7DOfA[#36gz +k} +cw Dq3#7F^ h,o.o3;S.5)?%G[8S =W4Ns4?\sW+;A['CHdtM3L &;;O4K +7P 1J 7P.J"<=W .H 70I+D)E@\O!/J/H6 *)%*3 : ?2M#A 7Uh>wA憊^45L $;Wo{0L=QMc.5N_~/Db/OA_,IJDFTEQT%s{*ez<[z2AL,7`u2'=%@,+DT.% 8N e.3EP]*|.<*: %/ 3 - +7P5Q.K 9#<2/0 +'2'kJK^$*(6 )%  2. -# / !12!6 2C2 + 50 +0 4 )" + '3"0 #       ' !0"0 +  + +) "!:H-+ ()67D(!.%&.= . +  +&AU"Oj'o7{(y'~'+xvkw"m0svwuuvy$x!t*j~ 'vyvvtrmz$u!}'w"efgw#0-w v!{&k,+{&{)~}/.&(~~ z#mn!o'fr)7m$r&6[}bEQty:` f"q*p)k$j2u%ht#lmqunhkr"x(ln"9gfbXX~V{w/x.p }-qx$}$n$xz&u#xrummtx,;,tw!w#{%{"urx}qz 0xmHb{OhKow5u6&|.+{,))&.*(%(,+1|$|%,/*01?(+}!9'+)30<7 |2$#.!)}"&~#&1)!/+%*+}$w~$xw -4*~!~.5}$'w"{${#&y (Xyz*iBIet/?s(}1o s -wtwx|z("}|%zd!(v+{$h0t#ov"z$|%j)v5q1w!0rln|#5"k+(//*'~#}%|%'++#~#~"}$xw!oy$sg/s{':dl{"q,6+?0,,3394&.=*4w%;~)|(m-lq4x)q!0/x'.|(bz'2mB*svr%wy~!6snu&km%{ gcov!lsqmji{%kqghb Yonefec`f[]c^U|Z NtT|LtIsQ| >gIrBkGoDlg7T{#ClAi>f_*Py[$RQHwFu IxO}j([Z\\^UN p+d#{;W[n/Q{_T{n)z6h"cU}g"cl&S|]g!k$XM} +^__a"x`BaOn$Wv*Po#Gf6V +IiQq&:[4T a;Qo*Ll'Ws.Nl%Wt2Hd"?\ ">(E(E$@8 +H/L!#?+F(DAZ.#>2 !8)?#5.$%3."2" !.$   $% '  &  #0*( #* % )'  $ +!"$#'   ! $*0,0 %$*,2/3+0(.(.(/&-'1+5DO+5?+3%. 4=:C!*38@08?F)4;$, &/ DN'&3 -:*7 ,8-8)4 +*6 -75@4>5A.98B1::C9B2;>F7@8A;E>KO\(=G9DEN4<AJ;D@JBKGN\a+gl2~Hku6al*em-t>bn+m~:Yn)Oe!sDMdB[D]LfGbOlGbYu'a|3f7^{+Ts]~(KkZy(B^ Mj`|!Tpo/`|RoWtOlUr`})c+a,SsOuS| ["]%QvJoHmOuX}_$QtPsQtn3]#JmWzOrTwNqX{VzEh V|_&Kr>fQxg-PuRwWz!CeGiDfKmTxGkY| Qv["`%LuFn +a Mu]#NxV~Z"U|?f\!b(QwV|ZKqOr^$OqEe<\HhRr Om\{$YvVtYw a{%_}#h(c#a!c%^|q.`[~SuUya&UySxRwSy[U|S|YZWz?R}KtQzT{LrRyInSxQvDi Gl NtW}QwQzKt NvPy=eImLsMqZb'Ej Jp])`0@c,PBdpGGa@[F`=W0I!7"; 32 / %:(=,0, 0/?!+(,?W'Pi0mMSo(ZLk;W,FE]#$?#=5O2N?[(6 +.J0L(C1G,D*BYq=F]) 3%?/6 @X)!; *) +!7' ( +"  # +  +!.B-; 7 5Yn.=T,'AXu ]{g76KdNj#Kf%AYTl-?WSi,;R$8 +5K">Yy6rU2 '3N.I4;U'AY/'>0H")  "2?*% %.#   uXDW Gl}p~ |5&E=O 9J$D&CkwR]"(2:`t|!~!#y%y|g~ jep Tlhz &dz tTq)lc'Ae4P3K F]##7/00I7He pw EaMgTr?\ +C_Mi Vr,.F 85GC[+D,D +/JWr-To*Vq+4O 1(M[(?|>~E[-4 4FY)'89S%@ ,6-J9Uc{/.Bby)Tf3J)>.DRhBV|6Ma*&Lc7R3L*A8P-Gh{,j| (({p|IU1IVw9N`)+A8Hee}24  #mEVQiWp"?QMY3 4;&    $" . +Me,6T2N(B +!942. &%[s7C]5GDU!FN4+2# +  ,#  $[qG*D5OOi4-F(A 52I,E)$( "   1:P0$ +$$ #$#) @J4  " # !!2$3#  %  +,5%2=-   + + +  *:+~&v$=*@!~$z#|&s kblq+rw}#y!piqdq| uttqtw"e}''x#v#9m0#|#z#}')(} wy$}+!&|3}~{%jhjw*m"n&dg3t&z1r,b!m,a!v1s+n'n(z.{,.mx'x%qv#z%z%nonkc3^DdVv,Jk!Kk"FhAcIk?_IiLkIgXw*;\c5Nn!@_DdMm!Sr&Ut)Kh ^{2FdHg Uu-sH;YMl$7YQp+Qo*Jh#EcMm(9Z7XJk'Pp->^2R>]1P?] [y;@\-J1N$B'EQo== +$C'D.K(H*I+J>]+6R$%@94) *:E 2<*6+60;'2/9,8:E;I5C8C2=-8 +51; ;HKW&:FDL :C&.4<4> @IJU!IVP]'bk72= 8@ U^%dl4PXbk/GOOS_c+MRRXhq2bm,fm,t:\k$MaNf?WPk!A[OhOj![u+i6s;`{+c}6kdLpUzBg +=`IlQtRuWz$=_<[_Y%A +2&(-/F.D.D--D(4 $*  "& &A!  # $8?Y)%>5J -,2H?T;U1Gb[|!Ll!;0K /G=X]w7Le$+%BV!:~E:V)D?a=8U9S8O#?3R4T >&E,I 0I 4K":,C?V"<!:.2K": /2KZsA?-@i{9h{0=R5GYs˄Vl"Zv/O 9 (/L(;WCshyBb{M{/{,r#}(60pli0fngmg~-{,_|a{/w+fTx }1RwkdZ edYr{}ir{"}!sv{j 0|v%%olkca` +,m |x'(v$," +}#+)89~%v,|&*,(-'7'|#()}&v z#s0r~$%)$$#&#.3~#4!2%{'z# |}#(2,)(3u|y,2s okfo#$"%*z$y#|$1'nu"y.k!B:v4Yol%T1lx"6v#}xwzvz xuzxunvkrv |(r!q"u"u#z%7|$'ks"n|+2mqowmy1#(%+"$&}'"&,|'{w}~#wtv2(wknw!"##5&/x z#-*-&y1y1(&"~ 2|!')4*ru#)y"z$k1i5v, v}"x%%!xwy"qjtropwv ~*s as ` hmq!kZ jigaecjr&^y.fW| aV~ m%`e \k']d&X}u;b,KrIqLtCk=g ?jOy ClU}!X"[ Sxj,Nu;`S{_%S~P~o1L}O QXY[aq,XIy]XYQ|`m,`h$`^_`_a[dh fP| +P{ \c ^bh(ZUMxS~Gr Lup8NsQt\*c/Ag Ryi.c&PxFn PxKuCo ?jTQHrT|NtZ"["MxW O|Is_&\&Wx#Yy'Vv'd1Tv"m\[z0]}2@a>_EfGiEgHi =^Mo*;^8Y4V7VEd!5W/Q6V*H8TFd,(H$D +3S+L/Q8Z#7Y$8U&5 +$?9#>!8/2H'5H*':, 1,>"5"4& &:.- 1 ( +;M,. + +#7 "% +!/E"6N'%=-5 .H3 &/ * +0#6 04E0  -8%   $8G-)8RcD) +$4&7, /%3+ $3*9(8 , +AS*):/9M%;Q&+?0A+;-=.>*8 5C3A7F/=5B9H2?ER=L5EM\)ivBDPKX$=JCRBP8GO[)O\*>K?MRc*iy@R`'CP?LM["8E Ye+Q\!S^!do1`k-ep1vDT^!dm1hr4[b&QZlw4Wd"ao)RcTi!Xq(FbUs$Vs$[z)[z(e/p9]y&Xv"Zu#i5`|/^z*Vs m5Zy#l5Zu#If_}(m4[{#Dd ]"St] Wzl2RuMoKnKnChBfBg KmX{#CeIk^,Uv!VzRvAdb%QtVzFl JnKpPuQuZ~VyNrFj HpT~ZS|PvJqNsNrTxLpDhe-c'QtQtf'QvX~a SyUyNu Em 1 ++(9?VSn1Vp4MlYy$6S5.H+G +/4M) 4 + !Oi'ay:Ia32K.,8Nk5Ge.8 -,Ahy] +FV8 &=")7)>7MEU >G?'--%A!N| F[cnuw =gzqHX_qI\lq +wm ]tZpYoMel&Yt1MLh0imZ{Km~36U3MRoi%\x$Eq)Jh *GSp(D(85T 2P.K9T6-C 1 $; 2 066AZ%-axF'2&>Qg e{'j~^"0[,;\mYgB8M,`z鎑Ru0Iq'un~B&2[h*6Ny@]l,l)8R_jx @zu +!Rfgz +tZi2?* CSnf{#cs*Tj#*B2M02,B>P(   /:+  ' !834O9T0K6M'& H^*BV ;J(   +  " +2*"1 +{Xc9)%A 0O *F8S1M8VPj"`}/Sl"IbRf2,!0% 0D&. +/ *:4 6L'%-'3!   +   " +8$" G2 +     Xt_|l"p!y"z!wpp!oo{+2iw&m0y%w%giiq%it!.shwnw!:r}$p,{#z$x$u!t-r*z!+utr){ w5$}#y~!*ps{(4~/4u%fa~4{-q#=ei0/:06@}-=?<~(dkp"liz!tlqz&{(}+mfr$hdq Ovgh\ r_nsztLv^ nlst+^~mrz(ru`i]]m&|t{~'}"}|#*")#!1'/5)x1*|$|#o~#"#}z%z#-y#-y$m~%xz!{(2##(#%),B)+zz|!/&u |~(&'"&'~'s"dv'4d';%,0{$m.1} |"t~ $%"#)$!(.%z+y "z""#-3uu~%y"nge z}#&',~!6t-w.'%205&&"|%%~$((.1z)9*uws##q'&wy u(|yx+ox!dmg0{'t!ihdbair!r"hjSzgf\X V~ +[n#iW gds*u.bn)x4\h#U|i&o1j,[!T|Ov?f j4NwMvGqY#IsNvo1ZX~^g$\l)n,Ak[[`]]_P \X^M~;j[S dXd m+U|e![T~Q| aXgn&_R~ +\Mw +l)CnYU\Fo]MvIp\ OwZ]#Nr{Bn6InLt\ ^!ZR{GqS~Fs O|QRYDlq2]T{NuP}X VGtg/X{#`,Y{'JkMnCfa.@h],a1Bec2Yw)HhYz+]~.OrFiJnVz#CfAcQq"o@CcIjVv(KkEfQt&JkKmCdXz+Km V{)IpSz#NrX{&Pw$DkDmEl^.[)W})b4Oq KlSt&FgGgPo$Rq%Ss'Kk[2Qv)IlLm!<^Gi;]Mr+?b9\6[:_@c:_?e"'J 1N%! 4B'! 0>%!3.@"#73 &;1 5#;#8*< 1+ /@!3"5 .E(>2H7M)>-F(C: +.F,D7M"81DDW&9M;O)? 1E/A9J8J>Q;O?RBS7J;N?ROb(FYWl.Od#?VF]I]Pc!:L GXQd#Uc#^n+gv4[i%t=Zj$Ocgx5ky7^l)yBjw3hw1au-dz1\s(MgYu(e5[|)Z|&GfVv!`+[{$JhYv!]z%j3Wt!Sqv=b*Zti-Zv|Cv?b+Yx"HjQuPtQtWzVy@eOrLoVy&Lo>c KoPsKlBc Ss FgOqPqVyWzPsVzSxOuDi Gk UzV|`PuIp OwQy^&PzIr MuMtLsW|#MqMqNrX~"W{Ty^_ b%Hr BkMwQw V{PxOwMvKsT|RzUxRrMoMlOnIkXzd)NlPp@a3V6XMnNl`~$g']zl,Vuq1i+n/d%]y q5e*f*l0g)Y|`]Zk#_TxMqCm KvCk Is\"Y~QwZOv^ OwZ`&Ej Fl?b[~"QsHk OrX}[#Af 8\?c BfEh Wz`'GjNqKlVv+@_C_/K70J :T>Z^|9Fd%-3 +(6$1* ) +"Pe7Ph1Up2"A.L 4S3O/K5T=[cE:X/K.H{5T)I0)D-. 6.A!"6+A"9 #< /I0: 8#<Ed,.N=]06+0*I@\(C_.,3 +0E/%;Lc6, + +(02* !9 /(+/ !1 (%F :+F,@0;T!Tq2h'1 )(+7+ /@&% 'B7883J3F\")?Qnf$Ge2N+H=Y0J";18^w;5N5b{7JiMk8Y -Vn3Mb,3 +2G &BA])Og<  )=!f|[5?.   + + + 7A#G[ PK.8Y +28Vm |C]}j8;9>$#y~0 %mz ZlY#< ++E iB[*2E1D *)>&? 40H-B,0F %  cv#hwALaqbou.(1 J\{z.fzLdF]8Sx!x{#YjftpҊ|6l|{uJW +R9gz0P_;HOai|[oI^[q Pfh~;rRa1Í*4:B_n%0?rWhNՀf<_9M*?2 0C8G)  +  )=!25?Z 45!0!  >Q,H^-'.>!0 4B& + +   3@&AZ7Q )C9Q7S.G&?+E"?-Gb4O 72K*#1  6%8 +    +  +    Yu Vrdm",x#?r"s({/t(|/klr"s"0{(t"du$s$lry%?juo{&}*z&{'j}%'{#lx"ps(jmqqtx|#){j z&~ !)~ y}y)8{(.jx(~-r!ku%2hr"q"|,/v$y'n>t$4z&~)1z$ffenmz$Wzx#/:*y%hs"fdi-] o-aeggx p\U}{'qgbt%}!{/&l cti m+{z!vz}{w"{~$,/|"z"|%{"}&utx~${"qlnz"s })nj{$px(-,vz)-:&"5|!{$t !}{}&&&w+""# / 2%((y'w'3s$v%.*622z *),%~$*?r t!4|)36{0}/o 1,*t0}'(+4y#qwu(~%+{ {!zy!4t:z&6z$q|#&.rx$.:5|!&''}!z&'!}(z~!{"~#!(owwzx)1tmwx.e *uw#szw!%z2*w u+~${"x!yy-{$2|#.|&tw/42}'ry#u&t"} ~q)lz#uqo|(u!q+gfhhSy Sy T| MuIs[l"_mi|-\f_MwW|1in#U} +r(m$Zedg cee!t,i%t2_m/T\"]$R|^b"DoOyo/][Ku b`[W_U}d_^r)j$o'eu,]dm!]T +o(e]]^k(c!` k)U~n+e bVYUn%R} IuNw \Wg'n,VYZWU~b(c)V}^#l.i,Y}V|[c%W~Z\Ta#TVZZ]!Lta"b%UzX@h \%a)IvKs['h5Uw#_-Tx!Hm^)Y&W{"Rw Vy&Vx'Or$OqLoW{%Y'JoAgOuMsPtLnW{%OrEgWz$DhMt"]0^-^-Sx$LpPw!Z)R{LvRyV{"S{ U}$V&W%`-T~"OvFoT{&HoU{'KpIlJlLle8MpTz)W}.=cImRu#Sv'?cHm!Ei!?c9];_Uy0Io&Fj$Bd":Z.L+KJn./R4W)M W~8Lt-1X.Q'H:.L(F; 6S#; '@04N#9U('B-I/L;V%&A(C/I7+4M!/J.1MOl8.J/K $?#B,K'I0S8Z9Z!*F(B*B62 2 #<(24.6I&,< 0 4/*D5 Da1Ie3/I(C8 ,G(@ 82 +B;S'": !; 6Q -H8R0L/L?^'-K/K?[D_#XC[?TQg$BVD[@WF^Qh&?XKd!B^Ol#Po$Qp#\w+l;Qk!Wo%Yp'uBez2av.`t+]t&aw-QhyFbu.Yk$]n']p'RgJ`b|-PmPm>` ^+b/Bd FgNpa-\|&TsVwUui1Yud+c(c'd(_{#d(Wsa}(Kji0OqX{!PtW{!QvNpDf@aBfCgf9ImFjX|#@dGjFi9[DfHjWy#W{!BeUy\RwMs]"V|PvTyOvPvPyOwNyOyLvMtSzBj +W~"W{!RvJnLrWzLp W}QwGq OyCmIr T{ZWNvNvAi HnZ!f.OoZyQnJh Xx>`Fi VwQsMpWz!b+]}%d*x:e&n/Bb~$h,i.d(h.q8l5Xz!f-a&[]\f&a[g%\[ R{S~Mxa#Xa#PtIm ?cOtJoHjHlRuKnCeCg JmVyVyMsk4Y~ OtMrOtY|WzUx[~ g/Qt\})c2Ee4R Lg$;VOi'Lg'9ULf-&<$4  -% "1&Xp<7Q$B*H "@*I +[lK7U6R8T'?26cE +'$@ 0I%$# +"&!E_0/4 .kIXw6Ef%qO-9.)G8') -.?!2E#( +! !1,@0 +  7O(4 $*+ 1 . +#RmAVt<2 (9O$Oh9) &#90"%."6 +  4 9 3: 0!8G]-DD`UpYNg!00IZs4=U9V(D ,J-Jh28Wc"^|#zH3P "<( 1+<*@   -;%0;-  + +  + j/j/B^sm{"Ne)\sQeim}!},@! (%(hy [m/3\twJ}i|`u +.j!Vo6Qm(p%]xQm7U?`=];[IiJgOj!)B7O k8Lh;U(?1ID_B\$= 68>Z\%9("5,1(? ++=KDQGU O<9Tkh~3E>@[jEYaulOeUi Pfavu-"/yE6?R3{#Jd m04Eey_uNb 5BY*5.. '1>#2H!)" #1 +)ߝIc+)C )C +*C' , ?Q0>S)#9 '<0A+;=I'2?#(g}Y+vYIaf02L3 +:Q%3).I/!< C[$-1/J5L!)@F^0/E"+& +        + [ti'e&x3x-kq!v-m$~3v,;n l}0v&,y&y&rLt!ow#1x#x$v$i24on|&{(.x"0)s{!&*y~&z#,(x)%y"&p&"!}y)z%0px&q}+.nt"w&,4w"+61-/z%z&r 2lr,v!`t'elj}%{#f+$vqmp{(ln-sGmje_ dnyvsjcpnp` i1is~y|}r}x} s )~s |*#vz!z!&o |x"ty!xuwowf4u0y lddmkcaw&nstq{wu +*|5x, ~z(!$!+(x)~! #,1('|4$-|(q}*1-()/0yeu~ !~,q+)-/)*y%v#{,v'z(p{#*|#{)+'d {$($*,*$.$,"| y(<}!1?.(|"&~#x-}}!}&~'vxp1~ pxuknptwvq{ z!yw~#tytx)z%oAx"'p)} *}!/+0=y$r0y&2{%x!('u5z&gy#px$}.{)}'tmx!xl$t|{!w o|&u4t -;_ kpYmv'm Qw S{V~Qy +cs+V R~ej\ U +o#V g hj o$[j q'Zq,W`o)s,^^fc]f#c!^\`XZl$dg [j!Xn#dbew*et)r(m#t+ak"u+im e_jadedfad m,[Mw b!e$`e"_]XWi&m*a#Yj-\!_#c$[_`!j-f*b'`&f)TwSth)X}a#V|V}MxEqS^QQFtR~a$Emd&] Rwu>@hO{W"MzFr@h V}%V{%d3QxMv\&OuHlEiIlTy&MrQv['Sz HqNwHoNuh6IoV{!_*MrV|"\*R{#V~)T|#W&\+QwQ{#Y*Mx`,IvR|Mu[$[(]*JrLsOwS{Qy!\-GpOx!KsOv$Oq LnGjRy%Hp9b CjEkJnHkHp?dMr$Jo"<`7\Qv(=d:cCf1P >^?_Sv3Lp)3X:a=f9aAk['$B +&F 7[4X*KAb) @A/Q4[)P *Q 3,H < -L:Z Dd)?a$0S5V&G 0R3R!? &E/M>[+1N+I?^%Ec'9W;X:YHh*2WGf#Op*Yz2Hk"8Z;\3V:Z;[1O4PIf"i<<[C`Zw-Qo#Sq&Ro%A]Kh f2Rq OqMoYy#Uw!Vv d.[}$SvOr\~#k2a&b']{ n0UtSr\z s6^z$t:~CHfQrWy!X{#OsGkGkEf3TQt HjZ,DjLr['S{RxRvJmAc ;] @b Lmb*s6V{NqSya!Lr Ms +_ X~KsLtR{UOyOyHrMwOwLt>bNrHk@dc!RxYNuMw ]^ Kw Rz]XS{R{["?c7Y?`QrNlJh Sq]|@bHi Uz\Uxi-Fi0RsZzc#b$j-]z$c+k2c,f/o8`+d/Tw]%X~c&Lq XXT|T|\^U~_[_ Jq Ei@c?aDf QsKj [{"FhAc<^ +4X4WHle,]#SyW| GlZ"b)VzSvMp]!Z|Y{TwRva(Ln;Y?[3Q:W/#<<3Q8U2M&<(  #G]=Sk@;T-(%;&0">0-M<!;=X-0>['gM3-%8 +  +6C+?M2&75H$/* +!DV72E/)('7)"%98 < $ +(+ )<#' 3 .*!#0!#<1K8 ,4);R g,Kd Z%= 1H(> ++B *A +/J.JTt%YxXw!ou<3NHd?YQn*  +*$2  + +zR`ux%Iw2tx&p{&9O2Et/LLcav as +|^rdp +Zm`rcufxWg%xvS_Sa|tQa}*oP=TJa:R81JIdLgJgJlSt>_<57&@0G +2N +H2K9L,B'>)A$5Ld >V&;.&%9 49Q0I,I`};A[%<dNgy,7I/C m>n3/MaRgZnz0s#,o*Pe +zuMdF\275^nx 5}&=Lap y>nO_YpsOg-B2I'H\ +OcZoq%]wi el,Ds)dL\GKZ"Tc'm+|.F\Ld 6N1Vnfx($   $!;:V4 D\0fJ^}<. " $4P:W +b}.QA\%=>V +4M$2I#< .D' + 1+ ( 0DKd,;T&D`'-E%(Xo$I`( % $*2M *C28 .H3P2M!;0Lc}I BR8")        + + +  + +      + +m*@a(~?y1^{j q(9s)=nnq#u&~.5})/-<5;-'~)0q!x*r#z*{,7t&u&>06{%}$,|"%~"/~&0+,,~!} $.|!&&-~ (wn8}-/r.8sh~*s'(v-$%x~&4m.nou r~-/[|hs.'n**(+w\y&foy$+wZrequvpo-q!t!gw#k{#y|!j%w2|z,zxxvt($+|sf ny|(}|z~j _hluqw wlw +_ +elX~] t jV}Gpx+art|#u&|u/,E+y||r$(w$vw%"z.uwrv.8}4,,'+(%v423x!|$.* *~}8#(u+#$|.w}")z&u#w"nc~ .{ &w*y({#k2x~ | # |$+--,2(!~z| #t$~!zd|)w!roo%[|qffktiz#*d ! x&tqprq"('~&u!o1vx#%vz1}&6qz*w)p#v)~6o$u(hz-0llp$iV|:}(qw&.ps*d 1"({}zsv np1/v'6v"tidX^ |+Py \`_W +e_Jv\ djlfl!o#k$s)k jdkkbfb`k#n$n#lldfm#cg cew+hgu)hhim kkj|.lcs'q&cgal%p(w.dm"\b__bv0h!]au4]Uj,p1FpYZSBng%q-d$g)Y}]"ZSzZNwd'`"RzTx^%UzY}TxY|j,SxW}MqQwT}MvOzO~Jx +?m=jMvDo NxS|Fo Dl [&JvRJxEtCqEqd4Nuc1DlAmPxQwQuW|Ova.T}#JsMu_-Aj@j S| W$SzFm^+f3W}$p=V%T%Pz#^-Lwa1T%S%T%S X$GwNzKuIsFtP{KtOuRwKqGnAhCkIo>fSx%Vy'Ps!BfX)Cl9e @j=b +TxDkV|&AhGlAf>c;`Kr Gp9a.T6Z ?`1S Bg Ei"Jo%Hn!Kv#@m=gAk3ZEg"Df"Lp.Tx5Fk(8[?b'+NUy::_Dj&Pt0<`4R,K +Hf'4V%F ?`%2S.Q7\Mt,=c>d!>d#Bi)%K ++R 9b4]S}1Mw#4Y0R%E > ,K:9Z#$F +O/P0Q%C+H1K/H4QDb%1ROs/Eh$=bBg"a!)M 1O8X A_';Z"+J.M0N7U:X9X?^Mn);^GhMn!Vy* &/F!60gJQo%'F5T @],-I6M?X:T C_k  ) (fuU=I2 +      +~5@~9s&@[d~%D]Pi)|%~,9J.|&Uk +FX?Vl +i|g{IY}0Md7Mf~\s\r p>7]p|z8.tFQ[q#,.D,B 7P2@\?Z3NHg>^9Y Uu,5R.F 5,//G 5L.BE\Qg$G]+@,Mc5K 0"9LCZFa$3P3T #B(GC]F`g@\n)EY!j=k9&j#Xp@T`tcw^s9tOv&ezu!y&|n}z&7M\{,_n s&buN` +`xw&D\7N>U +Mb;QSi>WIb`{TpSnr!u%n$0kk)e|h|Rh SjWokw,/6M  /G*F-I92 J`7%BsR,NË0 fKIh#:VE`Oh07PZq8AX*(*N_5>W!C]-H7[t9|'40)AXnJczJ;T%C /$< !8 8Qh[%9&5 147"= 89< :-J * 8-&4,6%&1    +  +  " '       +   +  + k&v8_}$j,r.r)j"o'w+6}+3/35~*1{$,58z s0y/y&.5r$5{.l#k djw&4/(~'&*$p{!2$,&'{!s-|&z#,*z!*tt"x(|-~,s'*(-y$%"'3ov%|"mor,kgmipmz ({ot+'&mv nci6xm$q`?\rkifkgz(|'kk~'(*}"|v&{ t+$vtm vl 'ov~#}"6'o%5.x0*| {ib +na |%}'ptb t"cj`u$pKoinBhOubeeusr*x}$~"(/~!wysx~}t" l }"#*y.vto3u&|&%*%(2&v!3)|!#00&.%&n)+u2t(#=}$z!('yx#z#x~"0v'# 9'&%$+2"(;&&v"}tudnirnwl+qS{pr~'z#ka0txwrzr }vux psnosm |m}&t!3t$o im$p([[f!p(]fl%n$l%h$^ehjm~.0sr-m&#~!v{($|&ju"y%4/v&4y%qu honebfgpacenmkap&n$\t,m$X_bgdq&k#m%t+k#j it$mk o$bs)l#go%m#~3in!gn r#o#r%br&fl dq%e]dy4g#g"n'P}[Q +R Q VM{X[cU[Rl0c&P{Q|YLw f%:dU{W|Ya%m2Ek OwXW~DmS|NwT{W{p6Z"Uz\~!`#^#DiY~Ota#U}Fo NxLzP~Dq EoDm +NxVMxHuGsSO|SO~V!IwNzR} \+OyPz!BmKrFmVzW~ Ltb,SKsS} Y&m;OwNxS{ OwLsZ(Sy#U|"d0R{LwJuDkOwCmh:5` LwEpJsP|MyLtHpGqV%BiHlOsJmCi=cFlNs?cDiHiIkZ}(Y)LuDp6^S{!SzIqOvElGn6\Y-CjElT(3]-ULp<^ 7Y >cBf8^:b8cc-S Jp'Jo(Bh!?e0T4X3X>cEj"Pv*@d3T +f?Ef8Z3WCg%.S =dIq#Gq!O{,:e3\,V>f\9BlDmNx'Go=cDi =_8Y%H A,QKp,;`cSwd&]\~QsSxY|WvEe +g)Yve&ZxWxWz#Rw<` KoImJnh4Ry IpKrT{![(Uz Y"NuFnFj])Ef<]`4BcTv QtOpTu`%W|W~Hn _%PuKs=d EmPyMtMuNvHoIpGmLrBh@f +]SybOw S}VMwOyT~>g@hHn PtOrNqOqUyTxo-UwRs UxV{^Tyl+Tx`"^!Twf.d+a#_$^}#g/TuY{#_*\~'d)_&WzNpaTIr OLwPzh(Rzj&U|T{U|X|UyYzd-_'RrZ|[}[xUsa#WxKoZ"d)Syj'Z\ g.PuOr[~&Hi;]QsVwRqx:VvFf\Kn+L8U ";=W;S0J8/K3 '2 %7#6 )B\*/J/I .%=%>5))Hd8.L!?;+2L"> +*F06)@3 +$7. + "$ +*@%<2H$$8.D:S'4!9G]8M^F     +)  4 6I#L\3 -dkm~(|%{$~)m()}#(u-{#sw+i)h KsT~6\SzKqPx^+=c d.b.>d Pv!?fNtQv"Xz)IjFfKkJlQs^-JrDjQxMuFoc,V"Ip?eLrIo=gR~$f6Kro;Z~&[~)Qw$5[ 8`:aMx$KtBi?g>cKr"8_[3EkElEl+R8^>f?g@hGnFlLp^1Ad3X 1T %KBl2]:e CnIt!Jv#Gq!2\chB?e] 5TPq#CfQw&Lq2W4U<_Dh=c:[=_AcIj @cOr#GhWw(b2<];Z DbQo%Hf4SIlFjGnAg=dMsW{(Dj6[ FjOs!Nq_+IlXy#KnRsc,i4JlVwHj_)OuV{e+a bj&j#_Nqd)q7h2c~+f/SoPkl4l4Z{!]&_%k2Wxd+]z b'c'Pqd-De SuRva$]$Fm Dk +Jq_"Quc![~n0UyRuTwo0{;i(d$Y{Rs>` Gk=a +KnEiBiIpKrMtSz ChW{$@eKsHoW{!W|LqKmEeMl!5X +PqEfMnTvVy Ty a)a)^'EhtAEjAi_/bZ}$\&NpOpTrp5?\Rp]zSpf+[}%Bc5R 9)D )C +&B %B A_(&?*/E5 '4 E\1*FG`+6E]+1F1 +.o\cLSo95R5S '"< 3J"; +6M$(< +%8.+ +'; +Si"Yl$  #1 +WiD.[r:%=#5(>Lf,;23*A +A"7 D\)G_"4H %1    "&  + JV+\x[PB[ 6I?YD\ +&>Nev(AVofwcwew`oFXVkBY>SUiXl\sPgQi=TOfAV8qw!&n-DX;GX-Eh1RfF]D`Hd"9 $6!7+D .I/L5/1/%:+@4L+@ " "/H2K6P.ILij.Uo$&;6:T'>0E%9&#5JD$ +'6 ($=S$;%;5LK]J]9P[rz@Na'=OIdv u-,h~g|#n `qz *8#.+8csE[Un dz!=U4lGs(?s1Md 6P`x"@X6CZ_t$Ekh_r '5FV`GnGpKqMsJoInHn=d;c?f:a3[Vwg0?c X{$KrMtPw DjKm`1X~'Bi;a9^ KnHmMtVz#=_ X{#\~%Ika0Tu%DcEcQrW{#NtFnBk?e Io]'Lr5\EkMpQuPqSuXyTs`$d)OnXwd)|?f-S{ h0f*\Yj%f!d Qwi,]"Xz"Yx#_}(l3VqJb~*b*^#_%b(i-r5Y{b%_"Z} b'OtHpOv[PuR|LtQzKq ]_Wz`#X}Osk1Z|]|UtUx^!UxRuDh8[BeMqPxLrHoElQw Dgj8Gj;`QvMpV{RvEgKkRs';^Ln@` m6Ac KnInOrSv7\ IlOqEiCh;cBh<_ AeMtHoKsMv?i LsKoGkEk ]!Jn?aUwSvKqX}MrBg +QvHj?a EiZOu^VzSwZZ`S{f"f"Y~a#\!e+a)_#a(g,f,b'V~Jqe*h*j-Rw\!\"IqBmEr ItX[V[XRwW{Srj*b#s6QnIk Tu\~\z_~Qp k*RwT{SzU{SwUxSy["GjLoFhYza$OmNl]{!XtToIdIgMpQp-K:W9U,G 0M7U:6O:S" 8-D^)Ha12J51(@1C/!+'2P=\;#>-Mf5?X*6 +3:Fd',C/( 4#9*2 (- *! :O/1@+   )! + -(7+ )2H#3 v%M>G"QY7lj $/2@.7 $-# +#2. %#(+ '     -gxCZk [i$q" (4Jg|LI`(/KBaIhDb{F#9 (=5L6 0 R 5G )1C:FY.@]mdyH^ D^_yaz-,7J6H!6N[tHNb~KZh0FHX#3'8 Mc,QbC(7E[2EY5 &<4I10&=/G6R 'B #=.C[rGAX,$8!5 "*#&: 7P&Rn,D^Rj*(>V-E C[),B@WNb4I ++E $<8Q5 76R^}8g *. +1 3 .-,.M 8Q7N-*I)H'B4 5 !";"=8S K_!=K!  + +  -+=!(       ('* QaC &*kk^s#nFb4{(|*z$+~(rx"z%1no{%}%y"((%)wx:~*rt#t#dnx&u!'y":m,~ *|{*&zz(~$&),!|x'#$'}$!*${% *u0v9z}~!':{!|")luxyyz))1~ wy!ec *!k k zu+(v#nrwvw+(|!tr{$rv#u x"v!x!tr\ pdijs#f>z)F.ip|&y"x"Ix"v"iv"wns2klcq j[x r&6em~)4ft mspt~*w!{%&~%w$+jh/"pnomqn%n#&ul)jv!~)0.({!x!w x!&w+!"zx{{!mkp%~x}}xuxh }"a joqsus*{v$usuzx|tw%!!1%s~~ !(xp$"t(+vn{wv}qxxwq_nw$rn{#w |!# p p &q q{"~tpy#ulCw msv"[}s9z)a~`}2mbp#b|.r'^r)l'n)o*e _i fYo%hgc2w#erl`h hlmjy*~,km"m%v,kmldcljz,aR~U} TT~Y^!Is \Z[\l(i&XZWX^c!m'bWY[WPz Zf!\c%^%_'[#\ ]c#o,\]g"w2z2z3cf#p.o(j'j&d j&]u3b cU}e[Q{ \^w5f%e$g'Kv _"R{[h-o2b$`#^"c)Z!\%_$Qzp0\d&i)Ip U}Xs2Lqd,a*Fl[!Twh+Wzc'k0i.AcUyc#[NxLtEl +RxW}w>]#`&f+Gn Z Eo Va&c&Yf'g+b'O{R}vC_-V|"^,Rz ^-Z'S{"V&LvJta2Z-Lrk=NuRx!Rx!Sv!JnSwJn^(f._&Jri-{@PuW~!W%g4V|#Vx!\$[ W{VyRwW}k2Af +QyOxDkNtQx"?hU|%Gj\|,Ihr?Oqc1],[(Su \'ZSxZ QwVz yF`1_/X);d Do;dh1RwX{^'JqNvJsFog7Ho>dBhS|'g +Bh 9a gBgCfJjRt^"Ik^*\(;b +BgJnNqFj@hU|BgIm?dg4>f Jm[} ImY~Z~[}"d+OpNjFerAi4Z"W@g +W~$`/NuChvCOrY|#PsMpWxUy`!_~y8Usr4b$q3u8Ko[!n2a!Sum)n't/p+W}ZVxUxb+]}%a+r?m7]}%c)e)k.[}q4_!b!` NsW|X LuZ j,S{T|S{Rwc#e"e Z]]!PrOqY|l)Y|a$^W{\!MpJl2TSw!EmCkOv['ChImd0Wy'Ru!DgFjLoW{d/JlKlSw"l7]'RrLlDd_.FiUv"5W=_ NqKo=_ DhGj8\b Mrb/=b JpJmDfLnAc Gj +Ch KjHeLn@`MoLrPtFk ;_MrU|U{`!a!_]]Ou TyY~`t1Z}d%\!Z~]"_$UzV{W}RzVU~a#KqSxLqUyZ"Ck Gp It +YUJs ZZZd!h![~^f)UwY|c __r*]aV}Ov OyFlU{[Ms _ Y{c%Y{>`Ur@]d'Wrt9OlIfOkKmUs"Fc>Z0M.I 4R'C ++C-E#< 0%<2D);)))!4$& +*F0MTo74P1!Ha5 !$:.H5PGf&53#91?@u| F KF "' ( *8*0@^FR%"/:CN4@=L1!0/< O`6BO" 9: 3,   YgE"- +@$A #A 0O = 6 +%>32  &3   (%    +'3 SgH"A,H8Wp!z5Fj'I` Tk&8>QPcr2o,j}'J_AU"3FW_n*T`TeJ] +FZ6@YPl&Wr&i8zM"3-(/67"?">4Qr#n1e$b-)C)&(9#3 FO'M^k`q&Js1 +.:JK[.Bav13H +iLv LtOs"Bce1Hp`-d2l=PqPrb.p9o7i1Hlb*h-[ SyQuJm[~&^~*h3Rvg,c(V|m2]%\%Y&e2Ir^,U}#Hp4\Ot RQqo:[}(Wz%FjJnDgIk\ BfQsOqf2Y{%IjOq!Jq=e d1@iR{?g +W~OwEoMvR{ _,m:X(EmX~'Js@l=kP{!3[SxX~ X"Y%Y#Gu=j P{?j DmLu_&T_&UDs _#e(S>l +LwZ%l7i4[}'QsDi d-X~%PyQx N()/ ++-D!7 * #)B ,F !,* +#3 +7S#2 4 6(D:!?)E!6-A!$  ! +! ,/   + *7##+$ + + /0 /B5O/O M]at"[m:Q 2F"4@NM\ Wf%BU J]`r"Xll&Ti _sUjj~%Pf u.e#VpAW1B=O2+5IMc#BZN;lp/o7+?Y?X9S '1E,2ITi$CWWl*>R;Q CWG\DWez_oN_iz(PgHa +Zq[qx)izB;MFWVl_y @V4F@RF\ {=AXlxPF`m-]cq'`t&%;Of ,>(Qf31)<%@R6/D:O(9% +  5C-( ;6 9+D7R + #6[r5l:CY*(#3 /.CAU+^oGCT*.A13K"8$65HDW) )<&7 +( ++ $! +)@"5I*=Q/*=";O43G,/$+> '0(@ 5 *@"1# wv t#|*5*kl2x&|*u"u"5qr!t#nd|+` {(./7}*5v"u*l4~$2}#/)2&*&z7v%xpv|{||x#}!y{| "+,+#|#2w)~y&k0e v~ 1!z{-| b+3ot.r{}}}.g +m-k{(r}ww+upjx $y."%%~#ts2|&v#t {&w!y"|'v#^ s w#y"*rz%|({&/-0*{"'14",z!+*{!d rQuw!r[x +s!y+r%o!t&b|p7iw&mF6,/++&/}x~ qvwj oqsw!k.ot{ (~$,qw$w#>2.,/z rw5x)vk noquf|'w#})mp/}&tv%z"mvv*tsmw"jpt#Xz +jq{$ptn+w%0z=%"t"*6-%s|&}$'9t/$yy~yts~%zv$$c d\ +9}'fhx&/dkjpx$y&q%tkzkvy9vz%sm*z"x"56 |tu| *y"u z%u!lmW} +_o%dz.gm!n"^gr'edo gjbmccihhgn!ggdi(m.?igv*gZdk"h[e`f#d"]l.YYm*[T|h%Ya^`e$e$t3a!``b!W^h&h$Ls +^!u9l/Zo5r6j+TQ{e%UU{m'^~b]{c&d&d$f*h._&X{o5c,c+h-ZTy]n2f+j-g(f%b$Zd)]#f+Cj JoIqKsV[]Lx S~XNwf*X}g%n-UyZW]$JpTyQwIrY^aUzo/f&k*d#b!k+[T~Pu^#\~"k2Y|"]~%b)a'W{e,b)]#XXj)d#b$a$^(X!n:c0Em^&u=c+[#h1Fra0HrNuBjUxZ"DkNsQsVv&OlSt"wC_)^']~$_'UwUxKnc-QtZ~'Ru_*p:Rvc-FkKoPwe/OvRyl7IpQwAiRzPxFiHi_(`+QtTy`+Gj?c +UxIn Bf\"Z~"_)S{"Ot7\Ip4[X%Cl3\c0g3=eT{QyEnSx\&a-a/W~%R|>k Cpb/Dl S|Xk5UX"Jx@n FsUNyLvXv9[\Jx ]M~L}Gt@g Ovc2NpJlb+OtW} u?U{!FmCj?hFnRz"Dk5\Or[{&s9b&i)Ur 4J +?*P( 2? o&Sb /7j:@de2bd8(-6=-;&4  ^p5vJ-; (Ve4Xd2 ',="1 1536O?tUO(        K`7!<&?-9T$ +$ &0I.H8   +   ,&gu3Uhu/yGev%x7?T2I,BC[_z76 ?+HQo`";U2J~ROe`w%Rd;N 3G+<CUJ\$5*:/@.3F@SEXJ^>TXjFZ 1Eh|&r/AX[t^v=Sh|-H\ t:KaJ_KbPk>Z'BIc +Jd8P:U (A:RPj,(0 +!43$; Ga-KNm?[ +1M3O +">:U 9R D^:S)Pb*SrB`naak("+8OM\ +ER qL$3bt':MuK$, ,!*5K"G]1$AS%$6#3& 2C#  & +&8Gb.7PrATv;?V)>8M*@ =Q/*= 6Vp87NcJp[%[%\'>e IrX| Im]*MtIoAd ]([} TxQw_(V~Fl AgPuOv=di0FmQyU} LuHpQy@h QyJrAh ]*BjS|["d-SwOrFj^'Tw GnIqN{R!@k +T}S{Fq HrMwP|FsUVJwS^!Ud%`u2[P}PVEm +Bj NsHjCdXz$OQtMqKnY{"JpJoBi?gJpJn?cGhIgj1Qmo-y7Wsq3Qom4WvCb g+N#7H9L"7J4 + 4 ' 2' +Pg?Je7@[)lQOj3:S 3 +$2 +0(CRo77V*I.M!?Nk..K hI6 &SbE,# ' +* $ ) 1%910?$DQ: + + +#  @V%,E /@N[ej(L`rBVj#$'NKQFM +&4)): / :L{H'3$(DOLQWel$w~qs8&S  + 4 +4*1H9 #@ Hf(eK'B    & +  *4/4[h7ar$s,bxRi Nds7>UJb9WOn":[?^$B*F3P4Pf6e|-Vm#5I5H 1k{?]m$E}D@P9M(; H]#?U=Q LbAVK`BT +ew)HZ IZVm^v&`y&B[e}*g~%1I8O@X +E[5Q2Ne/^{++@()<7NT Xm HBWn!bp CS5D/>4CFY6IESQa G]Wo#Pie~*Uj,B2I-C1FAS(G[Oh;S **#! +=Q)(/0 +"51B# + : Sk=_uNàRg] 3C`[v#Jb \uQXq*S'=#&&9 1E02IMg(.3 +!1 (0A$6 )9   % +  + +    +:5F!$5+=V*?X'0*C *4G=RVc;.7    !    +  0' , 0 +#< +# 73/ 5%%*>#:'B26M# +'~'u'|230q!13Ds opcr"y,k kgr%~.t#fly+7}0x&8y$x%{$%/'#)}!"ys")trr/1u)u$s$wum + ( (~!{}#|)rtw'uz"%"zy0u~%mq+6!tz{'}1r)*&qj,h/z+5{!~%+.'v-! .&$|%k}(x'iu%jx(~.dhpki1'~!ty$pw&pz'-~%'~zy'0q$lm'%z!6,lp2p}&/+{(246<>9370,ux%}$wo|"1t!}-t#x$~'51u*lk2|$4-*|"",r#f +))z!*kkm ep(u-m$Vr z4x0r(mp"kr${,l[ ?6|(5~-})20z&13}'~'}%xo8#pjx{g{1$w32(( ,s} ##r&#||"%,s{!#%#uy*#,uu ik^{*ggds{'c '{#xj{%qux $)"!y"x$y~}}y"~{$zut"{(ps"u'iu)t)jefp&n%i!f\_fdk"i!g ]_o-h&WVb#c"Z\k1["["V\k+a[y5Yd _}9bd f!c d h&f"Y}Vxf m'r/w4]`O{ ]t3A]b Nw V|l.l+W}c"e'_"`!X}SxMtU~NxPzErKtPxOsp0f"g#`~Jk h)SxTyLqz?[}"Y|"Swk0`$_#Txe*u9k.__ h,b(f-[!OxJsv>WHq j)Vf'r4b&S{]$\!h)j)b!l-h,[WWY!]&Fs XQ~Xl'd _h%ad"NxPxc&l0[!h-\}"h/f-s;X{e,b)]#e,]a g(Qwe+j0SySyGpi5c,] y;f(Y [#\&KvS|![%]$b&c$e(Z~ c.pAm=d2Tp!Yv#e/TrSrYx!_'DgOrDhMqMpGlLpFnT{Lt>fj2Pw]%U}S{PsCg Y|#KrBk +^&Wzf)Y\!ZS{IsZXIrCkOvT}\$ItX HvS}Ht8a6];bQuMpSv \%Lm[$_&WySvGiImGoClIuKuT}LtNwKt:cDn UQzKu9dKrNxIq i*`^Oz Lt +Iu +Q|X~X~m;<_ =` Mog,Xz8VJgq;^~)Xz$Rs Adk9Oq$4VBayBk1Icf!q,e"[xp0v7b~#y<_"V}i(Nt VzY~Kq Io UyX{r+7Ye]x0d\YJr e*Ho(NGmNsIoMqSx `]]cg `cju!fccz*eg!WzY{i&h'i(Qu\``f'j-KmOpKlVw"VyGk UzX~[j W} \\bNw]Z8a[!KtOsAhd)_"NwX^[MsU{SwTxd$Uwi&h ggn#OvLq JpSwPwc-TyGkSwQtQtY{TvT{RtWwd'TyFo`#j1StQtV|PxKuPtb$\|^!RuWz ^%Z} _$c*[|$b*Z|!_%`&i-^}!]{!d&`}"k,~?QsKoGm k/KnZZNv\]j&[`[Yg%e*_&PsJlGk W|X}V{h#aW~V|]h#{7YPwV\x2^j"`Zm$k"i$[Yafd]OxYZ#Pw4\Ag_Mt ]\Vz g n$cQv V{NuNsT{na`adif\Z`Uz^s)Mt`QzTz Rs ^~2=T,B '; %: ,=&6)$ 2G +@)( %6% ?V/0$=+1 +).3 +Pl6Ki.'3S<\5S*I 9V; #:?Q:  ' +# # +#22iU6'/J!":2K"6 !  + ' +   0I>Z!'@L`X_]f4 $/(6cuiv|5K!/|(2 +:DÅ Viq@{t`U^KX4Ra%2Z  %' <6B>CD%1)@Ke,& VjE49<fJ`yO*8$ !  /$ * (  a.dU_yUn\t 5-H:5*I0J73P Sp#,F%;53H G\17KGY M^ Qb_s*EY)>(=;Rk=Wm#QgF[G[9I BR 1E9N[r,:Q +9Q3L5MSj9Q4MD^4NA]Jg#>ZjC-D -6O2L 7*@ !$6Uo22"A?ZAZSh*?U:N'sW5+F1JUm73D 2B%;H=M{,8j})PfjXk m|"AN%/8Cjz!4XBR 1DC[LeDWK`Th,@,>;NL^!&8%3 4Od!J^h|n~B_;H,<-?(;HY/=(<G\*B>T?Rcx!Qg9K ' %62h{=&,'87J 1/F4J+$&9$6)7M)C`+8X8U$> 6 +%; *hAUu't:BZBX `yWqLft,"62C!.>-'>0 F]-'CZ++@$"!2* evO * , -=UKd41   & $  +   .E(=2C VgA   "," "# +!    (  +%06,  "3/.+ 1H!0K8D^'O`7#~(x+o$q%6{,t$5x&})n1cv(m!k as(n"t%ks!x's&u(5jw%oy'y'u{!|!y}!!'x"ky | pr~+)ue #{{ zup{z"("#-x|"}~v)xs|#z!'qz|~+ys}"(0}$x0{v|/%|{~ }#| 1t1mm~'e *|+!&+%} ))-*{!)(v ts b:bjp"kn"{*jg/{ yks Xvv&|,u#t!,y!vq*{!|!t~#xwwu+|!;*2|(v!,00x +-C9/6+|'2#)2tvx w#ke2u%v"x%,{$u(t6&{!~%z!s*-}(w}$-z if`kdar)r)f t-s-o0l*^|3dd|2j"r)gs)r&8w):={'y$x!|%&su$wsj vpr'tx*8~%*/)z7}l,}"*23$n v%zw%)3sy~%s'} x#ol-u 8ojf vrt"'{t*z!z"z{u+"|y(#&)u|x~y!{npurllp!p$p&l!n$~6n$m'm'z2m&e ep)]ga\h#k)X]s3g'Hud-RzLrY~[[Y~VHqIs`"`g&m-[v5y7[Ym(|7i$_ah#8{5Ts o+Ux~9`UVXe%e(_p/PxX}^\p.HlMq Qw[NpMpTxX JsLuEmY&AhHn Y{_i&Zyh$f#W}f([n0m0^$j.X}Tyc!Sw_!g)Z|l(e![Y_#Q{]"\"Ye$^g%c"r2i-t;Y t9l/d$b r1d%n1_ Yg'Y[!P~u;U_ch"`_g$aSzY`#l0W{e(Uwd)`%e*c(a'k1g-Z~[i*n0`%Tyu|Hi6k7Zw$\x%l5Qoa(h1UwFi<`Y~"HnU{QyLtPxMuHpb)Y Dl El JpJnX{"Ek QzT~/WZd#TzRyOx[Pyd%`!i)OxYXOyMxWMzQVAh['OqHg c+^}'^(Yx"Vsl0g,s;8Y?c +OuW"Bld`#k2Ae7Y?`TuStGeNi[tGq9e.Nn_*e3CdEeB_ d+Vrf#o)|5f`}=n,c#z9aUzQ{ T{Ot d `f"s/n(]z0q%gl fdOzaX[WLuOxOv dx,u)bw)n!ck"hd[ y$rdnecc^_[~Vyq/[q-s*Wvo)t1YyDfRsPra$Ptg#i \m![_^fSzZX[UV{PtV}U{^ Lu S{d!^_i$aX}[Z~`k e_icRxXU|JqOrLpImGjImQt`'e,_%HjXyRuTz^&Rv_#SvPuNs^%ZV{Y}Z{^ Uw[}!IjZz\{UtSs^&a&\}"Wua#UtKk c%Vum*l)XzPu]"c"c!` ^Vk*^g#W~TxV{f ZZ}RuTwc&=ao'X~in$f^\[X\i!hf]YNu`X} f^eSz n*}2c[`Ov Z4ZOt@g_ _Qz \YZ~`X~PvMsKqIpKrFn]jdkq!ddh cc^Rz +\\Ry Gq NvKn>\AX4GAS%,> 2 *()-B.*%:(:2E'$2$*A)5 1 +''B+ 7.N 7WPo+*H ,J2O!=-H,2 +. * + *8O&#=/H,8S!)7S"%@7 6 $BV:3F& 1'(# . (/E +{iN8U1P A[ew#ept} !>_DOp~I:emnyKjs@-4j8>#$;D6>~L :C}yI(z>_pQiHe{oEbk$)1v +#:D =CCGy$,(CH&2$5+#1 ,&6 & +- (#  $ &$"%  "- &>7RMgg2d|"h(Ur+:Q!.I)C0K'CT uVG^0F2G +Pfp4du!FYQdOe!K`%).,A$88K >Q9KRc'8J +:N;P.E3J +4NKd|B1GDZj46QKdIc##@ +$-AX"Xo6=U,D@Y)A BY# 6(A85R5Q#=9Q#" 0 !6*4%'"~mp+J_H\(>l&;h}Zot1HVMY!P_]p ASwL^:OCZ Fag}#WoVkVi(*0+;#7E (;7Kn0fy%GYm(j|11A .(8 1%6+:BX 1W1CGWbt%=Q +'7$6D J]Qj&EZyi6,= 5H'; &<?U(# %*@nW"B'E 3N#< +G ;Y3PHe6O7L1KfpcyBS".L\7. +?R*&2 /B"5,&6 (8 & +4H^Qz[P{Hvf/W"Z!Pul.b$Syp5Cl JsR|VR{KtZr0r0QwV{@i#[_l%i#8k$r,|7\g#X~Nu T~V|X|g)QvV|`W{Txa!X}^Sxa h)|@SuWzW{n9l:;b +^)cQt~HWw`"d*Gdc,a|&d(m1_|"_{#m0s7Hlp9Px}Cs8w;WxY{Y|Gk WzUyRvr:Kl6VJm=`Vy]c`EjIm >`SwLoTv!Ij{Bu4Xto.r4^wc)\y"j1_)Pp[y#Vud*i,j)o*w,w-:9w0g"k%f#o(__Z@n*h$Dg"\~cgNtMtb[ V +jz1g\`\Px l%fdu&y'kY o#l^nku"ndgfl"UzdTxd{6Vy\d{3m$Cn(g%h%e%Vya [^acn"a]aahXV WT{OuPtMrU|d'Kr X~f"f"aZ^eQv RxV|gkdt%PxPy c(WNuFkOtDj>d +BhQvZ#Ca$Dd"Gh!1P /N #?+G7S30J+1 (9)@( # " "!<: A^&9%A-J#A32 2E) 1)ZnM + &1H>T+:N(&'Yr4#@=#=i})?hxKM-_ll} L_k4wAs2et~,?B"'[`[Ob_j'!&Tbk}*H^ QWv 3kZi ]hg'-$-(2EP?V?.b7HNoz) , 0 8L$!6/ 6N( ( 4/$  +!/ '##8F#.uSʼnLc;p%E_^zc`~LPe!G]TjSe6J;Q AWCV(<2G.(:>O";#; -* $&> 6L)A ^u&Bk(M[o#/=%5?P2D$՚LaJa4PD` Nf3G /%.-B1E5L#$:. +$'7S%6T!(G78R'B^&-HWt/8TVs`xs/{.`|8[oVb/ "#1 + ( +.! ++9!0+B"7 +,C03E.   + "4>)   !. -=5J! #3 :J*!  HZ.':/2J 7 6 4 &%  0.     -=.   -A 0/,?!n*,y(~/0-|)3~(/t~*t$q$p"v)h{/o%s)s&9t#nw*w+n v'r#i|-u$j,(vz|mrsj~#rsMjl"\v gjtrv|!"&$~.u}}$s$#su's[}onj0-{"#~z*$*|"0o*$y!z)qxl|#~%l~&4{ a rt{$x#~(t'}|{t{! ~$x!x"~'=~'prOy(s"os$v%-y"qx%o*u8}(}'pfoiV~i` f|%jqhevr(~&y!,x(w%5y&-20qq{)z(l0v!+-~'u +y"*,-5s!t$9y(~*0-,.sv#p+l|$vv|4)}$ssm}+z,{0w-n&a}m+h&q/l(`_]}!b'XydeSp gh"iy0{1Qrcgp!Yy jhmslx$st.w&ff|0^x'gw"~(kf|$m63pr~%uv}$,wy"m *z!:46)5-'p%y(r~){&*x!l-wx+$(!: (~ ')|{q,}$y*tpysxj ?hks giy$y&~+mp%r*p)r.g"g$ab!]j+c"n/^e%Wp/^i'S^PzQ{e+ZZ_$VW T eHm r/s1Sx]a_y4h"ip(t+b}y4j$[h$y4\Ox Ns l.w9CgRxf#p0TxQvV|h%Qwl,w5e'k/h.\$TxPvMsWRzQvc$[~g$W|]ZNu W~Y~k(]Uz^^R{\Ryt0k'\ej"m)Y|Wz_a TxU|g*QwQvd#UxOsQxNu\` ]TzSyb!i'XKuNzV Do Q{R|V~PwEni)a i([d#p/j*]X~l+[]X}Zg)f-c)Qs[~ XzZ}X{Z}^$?c|F_)`~(\|&X{ TxY}UyW{|>[QuImPvVGn [!Rwr8p9Updz.f1k8b~/Zw%n8Qqo4a%Rtb(UwW{RwPuR{S}k2Sz[$QyLuPyY!X NsX|#Wy PyHr Q|Xw4bZFnItVw7PyInOxQyJq Sx|>Ho HpKrAPpp6E[xg(c(QpSpXsAm-d}%i)x9DSwt:d*X{\zp0v7k-WzZ}QuNpZ{ l2VrVs AbFge't0UxQsVyKl2S<] Mn=_ &FWso.e"Wqw5n0c$Zxh.w=n1_{u4o,k(bdu(p$s+u-i du/e|5a\Ot +i#m)]ZYwch#j#SyV| V}bie`r'r%fQy Yj!r&`g` bjq#chjqle\o%^_l%f!Z~dw2j$_d?^_ar-\ZXj&Yfw+\W~ `Ty Y n&]fcU{ b!Di RuLpLqOuHl Rv]Rs[Pt +`X~X}Mr [l"i] ePy`Is MxDn Ah +PyW~#JqX#Z"Qx[`"SvMpWzZPy=bi(^#Gq ZVy_%LnPtY~PuLo[|"o5k/Da XvGiDfFiBdQqj(Vv[{w4Mp +n(VyLrW~T}aW|W}^YQ|[Nx Ms ^f%cOul1c+f+Zgz(_aabU} X^_^]d]OvX \lcjek1`m ]^_T{U|bgX~ ^[^[ +gw!hgtrc +pb e`^ bc_V{`Y~X}Zk'YW|QuIo +Fl KqBf 8[ 6SAV'"3 +*<.F(< 2 +3D EZ0": 8,G#91C#=T&Ld1Lf+Ie(,G:ShPh.=[A`!Mm-3U+N _3(E +2//#9 1#;-F6 +9 =Z)/J5 )D)F"? %D:3P'EgR,G&?ZjX,=)" 3, :#>/'9 ?R(1H+C 3L?Ve|*Sf9[rg}bUnbtd{!e{Zkq|jq|:M CG.}-:rvzx~$NY6Ar7G>LZlD\ =YQj Au`i}!u}3=Dd%. +)4Zh$y Wg))n} soFU+"/ /( /+@)QhE%1(0 "+ +" + 60E,<yYjxO$ &Kd1Vp$>Z Ji /P'D3N>SGDn95#6,6J G\'A6O G]&=au1FX9K 2GM`>RMa"DVIZ"4E .=MH_"88*B8P.Ie|9J_ 38M 5K *? (=-D6P7Q,H )E +"<4"78K4F."3$;8 +/9 51 ) ' 2B!#% +)$%8K +8OATy:j/UmDW ZoOb 9N +&<%Sedw!Xm +E^r1G#7@RPe0E*'9Sd"&31AGTs?w?0G[pSgXm"aw,I] 3 TJcYu%;SBVQf*?R!7'= +. '=2H )?$3 :; +-L1N>]Lh'Dd/HVp%_v)c{&c|kQADS%  ) " +$2 +#1%6'$* )2B),7' +        $ ) % - &<- (! + #,-1)@*@*A2K8Q;N;K!1>N%&9/ 1 "6* +, +) +%1D%) -=&/.F_2@W.xyz#sy#6z)x#~'z#*it#qr#y-z-q$y,u)t'x)x.q%o t&o"r!z*s%l"jnv"nu'vutu+i.|%lfu%fr!s x%|&y$ 34yv+%xz({xupks"n t&z'+p&""-orw*v "nyv|~!z tiqpw{"(~%vx z#k+{tz}}}!c p(2~!}#x uz#w (k2voto,2ltmr*{!3rv!l**lkkkj(f (v,(h*mp4{'mhl7ow$},|)_{ ~,{)v'z)y&+0~){'y'|(-x"-*?s r2w$.))-)q%r{#%-u|$}#{!:~(t5w$u y'5kp!4Tq k([{];l%l'h#j'j 7jp#ZzaiieW~ m!] \ Lpb9.tb3x"s u#dgfv,b|-qm,jqyz)%yxy/(t6).q&0'1*y{!:){"|!)~#v(+)olpkp'yxvrp|)&|"z 'yn(*rnoly#c a +y#hsdt'j^fks"}.t(j$h!f#bd d u2Qvb"W{Qv^ b#X`$_ P{Tzx8TQ{VNwIqPx]$Z"L{Fs7d]&OuSwQtVyj/LpKtq7Is Ku;dx:\b ]_U|m(f"i%j&u-]zp(cNoz8j)W{[Bhh-Jc&f(i*Fa!d#X}Zg"j%[d$[}a#Y|VzAe NtBk _#]!Uze(x$3 -*:.@1B%1!51 $4K`70 7 +9Zv:,H :>Y!:Y,I12QFg8X(05 Jd=?^0+I (8V 3P/L2?W,/E`47P+.  +# ;P'?Y)$ (9+@ -D 5(7NP']ݖ^n3HX%&5 ;NrDD\#6;UC^;Y_y-)%=C/HI` +Oe ;P 8M>T5)C;UIa (A7K 8K 4F,?5JH]CV,=,=8I,;8H.HrGH`!-E Rf/"8H\!K]#Ph'=S(;"4&9 #7 .*@ +Sj4BY%?70 2'8* +3 $%1'"+ (  # $ + /@+?_t(8Q7L=QKam/CZ F\]GL%H\T7M0D5G':Mb`v&uVi$%3CQ:F/A CW+Dg-f}(XpDBW @V.ez1BV3G-DU' +3B!4.*?&)*%<&=)h~1'=T,&!) * %,@EZ6'5(-+FWu5#A0K :V+G Uo1=V4I|;@Y *=K   ()64B+ $3)8%  !,*( " +(3!  ! +     +  +! !#9L%(=- 0. +5JAS&I[1:M"5G'>3I-F-G-HNhM^r++/;0AXgBs_%3! $H`8-.  #2.!*  /8 z|qy%0t A?10rw&cicu(eljz'pk!o%el ms#w&o"acom(~".zzskwlo)(pllx%x&u!*$}"w({x|y{o-#p}!~#v5lmx$.nqoz$~$s} 'uz0/ {wee u}%&)lz |#*m,zy|o#|#tvx} $%$z2l>ty|$'v'"{ x|"+.|"}$z"ovtpx0sjc mvqkp)zwqjjt1sr!r#s&6t"23f3,w#y'j*z%/6x%m/}*v"1(/0*6y&%1}%:~&{$,y$-etwm-{&y$/~+m3/9/v.gx,\}cet+eo%7w*rtlx*Pv7u z#]c ix#p` )(~"~%f7/t9ofi~2r(q%|(z&|%tu:{#%#o%~#(#(!|&()v&&&5y &0$~"|$6,3o}*u"~((-z uv}"-w |%stz"us qoz$kb u!it'bS}\O{bh_eHmGnbo Xv dSwY\`a]Y}DiRvPtb$g'^RyYb#d$_ai(XJv_":cRzR|LxKxIuMyOxk2Pta(WyTvh.Z WT}PwSz[_s/Sx _aXY~]Rxcv,~6BSvi)[|WyTxTxRut:j,f'b"\|p-Z~Jm\p+;``q/j)Y}_m,YY^Z`g'_$Uz__`Nu [dci^cg#Yk'Mu +m(o*^o)]u0^p.q3o3d*KkKoCgY~Qvf$SxRw Rv SxV{b#Lta$Ov\\\ZZW}]"_"o.e$U~U|YQwa&XTy\m/V}V|RxCkS{RzS}S|RzBj PtXz Xyo6]#c(^!Wwb%Vw_%a)OQtRv^_XMii+Wt^~]}n2Lnn-YxYvz3|4r-t4Vv^Z}TwZ|q5z9e!z6q*a w8KhWo`y$YqQi3RTqEc +Prm([_!`![X}ak)s3Vpj%q)fk#c]|f%\{cu*~4n$t,j!t*s'p#bjmw'tp.|,x)Zil!s&jIjj$v0gb[ V}f\d_`V~GpEmDjLsBii$\r#cez&w%LyZV ^dccccfn k"ceeaaf#h'm*~8p):HmU{j#n'w0ImPty-k S{ ][W_W~Qyg&Lp +a"QsRuOth#_o%y2i"l"t*kc`U{ U} Xo!V~ dbJqHn\NvDl PyLvP~U>gOwbg$U{d"V|]]h#YR{Hq Z IrRvQtIlRsFg`%KmXz^#@aQpDc =^Oo]~X{]Ux]{u/Qn +^~`X~U} NsYea^Jw^m%_b]^n#%CBa "?:T &@$, 60O;[z\+H!P7    0#'5#)0?CYAZ=U*3K Sq\zJftR|*by WE &@)w 8KX ]p)Tj&bz83IMcIbKbH`8P>X[sVn >:VdL.:l|XD]F^;R#:$8G\ FZG\(9;L&66F-8Hd;/I0I +*!6+()="<;P;K.)"-+=+-!:.H,C $*((5^~I=Y*+ +   + iyVXfFQ_4GX?U 4I0$90FS+->WLd)Zt3MgP1I$9 !' 2@$1!79S2+": +7P+E7Q*E7, $+G 8Q-ESov<$8 +0-<   UbM"- +  , +  !  +     +   +% #  +""1( ,5 52 4 +*=*;)<-@$7 +';5J,E(A(B >XEaN;M!$  *B*    '51D'&4%3ShCPg={'ys6+}*3.1~)mu$c~ggw#ln lp y)t$gs&Aihr!fUs +4ku$s$(&m{".xnv{#p,j7u"qo~&z!""|%{ys~!#u{} {%'sv!z'pl0$s%"{~!} 2&v~y{"p}#u)(ztu~x|zsupu# x<$+2%&.ys2x+x(u6y-$#$}$~$|#&+1y!y }#t'nz#rlpj-vy"m.z)v$}(w"y&.kr!./1{(r`_aImVzPuJm Lp Txi"beBfkZ}fq$js$Y bkjp!{,^e_m Nx[ hw+fijm[ +gq cXhdhbc^ trs$l X W} V~ Y g\ TyddLq`Tyde\ ckch] gljU`beWosbWY Y~Dl>f9_CgRvXzCf =`VzGj8\>b Uy"Oq[z/.L5O5&; /- / .6C+ $/ )+ +#2#1#8L#^u>uOUo)Ql$Tn.Sk./L62R Zw3'B 4 +#- -HSr92 $/ !*'/?Z0(D'B50 +   # $ &&   / +:O9S-%> 8-IIfQpBa}n50]ruxM"~HG-~6Dhw$P^Q`,?Uk2E +CYG^?X_x*{->sjdyF1<CP4H(;/H>W6K{7XRffSLU2 # -</E"91 "=-H8 +7T%2G!  %.* *"$ ( >KJW:K#3 &;/!68Lgr5H`3IMfXpQiEZOb?R=TBZ +@\2M.E+B5M3H6J FZ )=!6(9,= *; !1*<%6239R2I%< +5=U 64L,1@\k@-=' 2 "3 69Mg)-D0E"62 +'= .EQ j:B\G^6M 5 $11GC_'+)B ">(E 8t:0P5_u+>1D>M*/=/@0 ;H*2<, 0='/      +       +   .&3"/  "- +"5/ )  / )(:L$1 / "8 + 7 '>(0 &  * 2,B!# %4"2Hc6(?2,<# .,ku,-1(m3|+0{(qn t%nu"mhv(kt$ohe`s&`^|2w*Qnn#dll#{w&1)%rw'hy"*h0y%w#ey#es{||-(| )'-%+!x(*{#}&ox || &{& m(qxz k 6|${z!u1|+rss/!$$-*|"/2'1*+{$%$||x9''*y#y"/pt~%spvneqv!|$+rtllw&r lcy&1,0u!*{$|%z$?z${#+9r>v0,}$~%y"+%#/y.z'(.~%~%{$37+1}*v#~+|&-v$~.|,4u#q0{'v o})|'3nn|-ns#v#w%,u"w!xvns~'u2/+x%y!%&4-(.%-2p<=~1|02sujd ;4k}#|%vzvk 30p:w"x!&~%xo'w"xrqoqcw$ks+or iko}'la i|(imj0go!m!iji\~i5b^~`h$[~b9f!cn'Or ^Tw ~/0{0af!r-V|`Y~>u3Qv^V{Mp q.;Hi_Y~^f!USO~_AjSyClIt ZEo +KuTAjKuOwQzS~X`_Y~f&_|\y9e p+Ou Nt +[g#Ov +g#_m&_f"d"Y{_ k/]"XyRts7f&h'_[ze#c!Svp+n)gdZk"o'm%l#o&_q)k#dgj"e!k)Z}j(V}s._ck"dbi*h&o-[Uzdm$l!y.bUzaUx[zt6s6[th+f(VzNv ZT{^WzYf$\}h(r3V{Mua OuSzb&\"|@S|i'b Fq_!m1SyV}YU}YUMwEm +WKsXXS~TQ~LyKq j,`#{@Z~n/m0]Uxk(X{[p0MrNt]"t8`!Zt6[ Z!a'k.g+SoJl0p7a~)`|$c%j+WuNmg,[}"[Z}`%LoQsMpo5Rq_$]"W}[~`a~ f&t3q1g$b w8k*d!r*|2fz7>p0FH_Ptq)p)9>|2|3o-t5Ol@Fd#j%ci$]ZxB{03q$_{Ssq(x5` OpQtX{d p)de`j%]zv/j$axu/VsLm g#[n(_Tw[m+o-r)`~e}r)q%o"k m"gt.n+y3s,m x(lmkt#pmj\hggu"{(q!w&im3x.dv-u(s'5u$t$kjhla]bj!s)T `Y[do"q^e c aZ in!\ gp!eXmq___q(k$\Rw U|Sy]_W aifd\W} hdi\ep&cU| T{ TyUyX|Rv=a[Za\adb^u'gm~*\ afc[ aeo%_p)[m*b Wc"^ \XS`T~Ug%Ow T~ZS}XWR|PxU{Ori/YzJmOs3Wm(djfw,d`ey,iT~ kt(fmnx%joy%dhhgfj_cMzbojoy'fjqtdmi_T\ \Hues_ T~Pz `cPw a_[ [ ^c\ lU{[ +mick````Z^T|P}T bRU ]cZ[^W~Xa"]!d)OsSuMpRw EiAe?c NrAbHgFc>X9P)?.)0 "2!1) Wj;M_5!3/( 6 7@W $=:1KLf7Q#BBbFd=Z00?X(Kj389X& )+#;&&$>/J-G&B0/I*           "LX:"7*C +B61J6Ru9DaR{#z+kwvm k~yrTf^oi|[qm=q}i[iJV2A;I,DU&=45p,>VVk r v*Vg0AT%5MB_Ja)ak9/4&-Ye:  + / !8/('D<[')I=[$$ % %+ +   bmIkw@3=OX&4-%AX&CZ D^\u7M#7AY0l8[j#_p&PgSiBYJb_y.G^8M 9N :N*=+=7I'8/A1A2 -? 4*A[%%@ .G ;G`+,0/H/F!5' =M(2D%7/=% @Y$8S1J 60H.H";": 1H% $ 7 +5G )      -dmwR4C(B.G0JQl' <7R)@%7-&8)5L,G>ap9DaBV/0B#6(!#4 =Q#Zp;5FCRDy5+++F 7S +-"6dDU -?O^"AL-g !2 +"8 +.FG_.*B.'-8P N*IY/    5 5 )  !7-3 ?W/.>&7! l 'x*|$-y#,63s m jo kw#fki^onr m^filjklfkv&b~ 4y$|"x-&$y}%rmm-~({&ikb n{({k'"(y1&yzxxp{{-z z"}z~z|&t ~zy~$~zs&/z&}!~v&tsjv~$~#xv~"rk|%u'*"1/v|$x}#wz 28&,-y(x&x'kt/qmmoa |%.u hgu"h~.t#:v&t&py$y#x"lp',y"y!|%1:)-5(/)xu{#~"q{{ p$s~"-"{!y}'~*|*+/0y(q!{,{+v&w)u&h07y#y!z"ilpssgu%mq 3u!jx$uvv%|%mlw"|r}%!#~|}#vy"zu.-y)lv#3&}!m}&{#uw!(8okyz~ xtn})qjvsw"(jtupcmgir"ccy&at"gu#rridjikhijn Yw ^zi!v)[xx+gkcr(ag$e }8z3St d8dMq o&6n il#ew5Qv j'Kn c"Pvi+l-Y~e$b fcj"i Kx`Q} DsKy +f#Js d ~=Ajc$Vg'S}a t2a!Wi(^l(i%a[yx4Aq1i'i&z7]i&Rx`q-Y~^y4f"Sy\\^"Tw_$Y}^"g(h&j)k,^n-m*bbXXi"v/o%r)x.h_fZl%y/q&do'j$]~}9]cf|5ds1f)k,g'g$^n(q*j haY^X{h&l+o.k*g']~h$SzcQw \^v2t0l(i(b!v9b"f&a!Zh(\XZe#W}XY\JnKnZ}!]&U}T}Z!OyMuHqOwLtRyKxM{i*d%UzY{j-s6_ _`q+e`f!Vyg%Hm AgU|Ho^'QzZ#_([|#Yxj1i1q9n6r:PmWt\zd$j/HgTve*Xy]#Y|Eg Jk[z y>s8QpTv[ [cfa|u,v1di$r0Y|Us|6u+r+8j't4l*c}!Fh&g cy2Bgx/t/i)d$_~{:d#^ew0z3Xxl&z0q%D4ea~9~:ZzOp g$o+w0j!dgw0ba~q$o!o#h[|k&XU{Y}h$j&p,h y.z/u*m!u'lm!lYw-n'h#t.chs%o"fy(hlojefhw#cy$o^ o"b~6n&d_iph`dl\ihdahgZk!Nu W{g`z%oiob\ffjoqhihfi__l%h m&s.s/em%Pziimcu)]\[gq$`gv,`V{t-^W|Ko>W|Ot ]Mr f k#ejV +deek] _`fTT~Xn$p'r'X\aW]U` ]d!l'YR{T}PyU~S}HqQ|ZIs PwS{MqOrMoJo Fln)z0jfw*gp"r"ls"m^:r!ku lkgjfU_ UR^o#^{,liq`dpw!a b u lp^ ` [ ^\eR{U}Kr^RzPw Ot W|m(LpPu_QwcX~ nkkjggV|Y ZXFn?dW}EpIvFuNzP~ R~ ^V~ R{ Sz bWQzIq HnInAfVy Wy#CgGkGjQu@d @bA_Hg=V 8 5 - -B3F)="8I%95IQVMe&K_ObBR n|K $" *;  "&"4 +3~ZFbsCqBB`C]!#:"-)EIc8. +_|J"@ aZ^}8[y9@`Ikg#z;#  - !2>)%  ) ),%<,>%  +  + +   + + +#!!  &   +% +.& ( "($ (&7>N2IY,OY6 #"62 - $35I/# +"-5.*<HZ4!5:K/o uz#z#|$01y#byv#n f~fm!ekinv'j]~ qm|*ecc2x$dkg{*hmx#}&{"$0p~ }x'rr)'-{#xw &$~x"+z}!|{&#&v%t%vx~y}}(v}{$!z&w{"u{}o &}}xv~y+sy#{#(n|$` +t /q |'ss}#)(v x$|)z&~(~(,9/~*~+z'oy's!kw!lx v z"w uv!qhjls$n!q#:gt$mz(0)jz!~"y8,z#5q}*~+-w}#/y!vn&h -|#nx ,t6&t+ut t#x(y(/v'hw(q$y+p$5{+,0)'{$ngfl{+4fj|+t!1+v}#uz"*|#vs% y){|%z zztozszv)sv!x#|$~ }"}(usl*&c po|"xmz"nlz#k|%insk'trln eky)fbcaeku$>qs"ona4p kx'h0u*fs'j~/^y{.~3Nah!s+Jbv.cm!:Szo)r*s+q*cyg]T~_g#`u7f'bcWy}7g!Ep*X{n,Mq6YKnHk:]Fg7WPkbz)\t"v>\u#Mg^z)k5\y!a~#Zyg,b)SpQp]|%Kk}Bp5m2VuDj-Em0]#b#^cv+nn!a|5bg!h'Vuy1v,l#ESlw5f}#e}!~;{:Wu^|n*j!bl#|6n*h$]}F`g"dVvt/q/r-u-|2t*|2;a~6>m(Xyau-o%o#ap$o#@cq#r s$jc]~cbv/r,`l%z0kp#t&p#|-v(gt'w*p&q(s-p*bt'o#x,^r%lZ~ `z.o#`k-])oi\v,y3v/_bz-mj_Y w#deX_f[daPwXcag `ZcgeelaR}`k ZR| Kw]^b`YYp'`Mx `XNy +[PzOxLrQyYU}KtIs c)KtLta&Qvl*Vzaw0q(dYV ]du#hrlhfgv#y&ipllifaamjiW `bc`mPw[ c3mt!\ d~*_ [ n}-[ dnlo#Sz i#W|X}Rv +Ot Rvl!d^Ovcihn] c[aQyGq +Ck>gGn BjDoFqO Q| bKvX?fRx Z~Qw KsNxVIqo6RwKnJkEjVz HlX~$MpOrDe+L3P/I &<2 2.*Qi,Rl0Yo2G\$&<AV.,C,CB[#Sq95&B/K1kE2P Ed;\0P ;W&@-H 0K 3N):X%-7 +XsE"=8 :/J &@1<4)EOiD,B!AX7  %(  $  $1_pS(5"@S$3#; 3Q0Wu%Vm)C^l.j./D*<#4=M 7L +H[}W#=NMa5I?Q?S*>*=,? +9J+= 1!/."3.!31=X*-G3':T"307O8 "Oh3.""3+ ++?e %/Xl;6LH^(4L 06 ++A,I_./E*BLc+0 +  +  Nd9\^xOeoDX)ANf"/D3.K`|4H_q51Th)p>4H .Xo9%; ^u.l-e_o.   K[,=J +am%ER@S @ZNkf4\x*Oe:O ar(**#,'     ':&0,E83L-B*9,=!$7+ &A"< +&A Lh0\{:+H.O ++9THa+4Ki v?@W*= ".&6)* 3C&9K,205. '      +8DP4mpNWC   (%. +'%1  +   +  $ &     .$ " 8F) +  9G#  +") +/C#5F* -*431 !8I"+</4J#1k mmw!s)$t.-;b|d}Xr `}2lojnu"v"ghpdv${&x g2j.nv{ r&px!{!}"{|zt3xrvy (u)x#}z"z'z"6sjv'(uj novx}|zq$m rpor%yxuuv}{{{p ~h1wlekow!oid^} o+y&6u%.bhpq~*,+9.2*nr}+|({'njt(muv onex)ffm{-q!rs u#v#4|$ruzvtkx$r ~.r },x"}%s|#%lkvw*qkz'|&r.~ !~$ty%mlo#mlflp${.t'/r~){%/6y"ogp!s"hq!nhv%j{&t!*-w~)u/#um ('$zu!#~!.xxyuj ,mu#or|!yr&.nVxv z!rflpfij qz!p%s~&x z$x&ov |")jmkihabikq#Xz -}(lfciinn w(6e6gt(z-{.lo!nv':fedl#l$X~c?7QyBm(m*n,~[z{>p2c%W}^f!d w/^\t.~7[|l(d r/f$n-f%u2dd}6dr,aYyk,[uPlEo.t1w5n,X{X|W|b$MoKo[Nrp2n3Gz>n5l3\~"Y{c$AeTxj2Kos2` Yf'g&y6~;c#]^Rt Hhr,r.g"Z|`!QuDfFiQp@^ D` =Y Yu(o7^w&r:Cu;Ph>W]w$Ab|#{;h*d})Wq[v%Zsi+q5a~$`#a}"u7j*i(VtZ}W{Rxo(t(~1HQq ku,d^~\{_zjx-l$[vw2k&q,s,v1u2Ywl*t-fgn%n%Tu +Vws/_`o&n&}7r1l*;i"w/y1p)ct(ju/w1q)u-m"2i]t'}/l/s}+`~x-bo$k![{h w0p(k!z,kkw*{+z,r'n!dd]p(w1db`{0W} cZW} U{ V| [hTz x,enx'_`o&bu-hehm"_]Mp] aiOwU}U~Oy`\ ]e\cilbV_^j_cU} +V} +fn"cboYo&k&^Szo+cX~n)f#_ZW`ibm!ek!Rzf_\|2m"^~2r&gt'p!Rx Qv Ty[;\k `^ hh`a_W d`R~ Iun(]]YcQxZm#`_V~Ow _f#`d%PvPsSzEk JsDj +IoUzSzDlMteo%e]Ygg>ibabZ gccndfkfcb enimmQ}y)ht$^NvbaQyJpW{ oior"SwRv[ +X\ +r ihKs]TvcX KqV{ ]u#dY~ +jfg]o%q!hifZXT{ _Z@i3ZGq U\\GtMwFq?gZU|GmUz^S{Qx[LtX SwTwWv Wz"SwLqV}#FkHmg1Gk:W +-I8,7qGFeIiLh";-(%#:)@;X!/7$@7(FJj#=_DgIh 9V*E!9Ha)4. +!("/ +0.J8V"?Z-5=3O &!   !  (5#Ug5-$;>X;V"; +!&<3;VSp7R)JSwKqdq%Tqf|+q.1OBbDg:WsO+ fq +5>Tc +})|$fv l b.Fo+g>o"i%:Odw'CS.(9O f+=RYUb~!% !!  %#/. 0 6Q#/%D1%/  +  *% +cu?BXE[ QiC\r/r1)>BRjx7iy2$8/B>O>QK^?Q;MAR:N+>#4!2);#3+0-@%6 *! ,0+G&%$9(,!2 ,+A`- +! ",B?W))2H/9/2 !)1G.F ";?R( ?X6Q +Kc ;7\uw2VrC]";-B[z@V,:H&:/ 7 )$8 +"!7v5+A^o&7C# "+6E*9 .&#05G1I +3P +He9QMb/\rK(!  +   + +!0! 3*A'B>ZAY&% +),1/30G(B+E.B].Lk&E]& "*0AnQ=S( %4 & +& ++ +' 7I)5&;%<=R+& $  +!,YjI5F&(8 !.#-(   %*  (%  + + ' +'0' + # -# LZ;$ # ! +  +  $ +( %$<2J47 -E4J!(%'0E4I!h s_ .x "%x)-2k!x0~6^}mrrTt'sy"b -6a|,r{ r~(sursuk}#wu{!+0%y,uA)v|#,$y-"v|y{~(z}}tqm.qs{'tt"$d$~xun ~}sn{h,}%w{~"| ez$ijg{(w$b]} v$u"k=vy}&if1v!q21+&{ 5pr~*u 0y#b a }&kntp\ +YlZ `l mv$6y%y'nt4/vz o-.1/z(-)*{$'yio}"''y.nos }&xz{j(v#fei m!{.dp#o"l{-jp|'1tq~'y'a`o|+r"p gm-}*p(y~#y"qs$ut'wyuvmx$~urv%|!vh&q,ssuyy}/&)juymg tklx(l(~zqz#r|"{'lkzrv!odw(3u#nln2_~/|)ls#q#Nus%h_p"6kr!q"ip"{-2x)v'|-g|.^acx1dw.}4o(w2t/^p1h+a!d k']Zp-h&p,f$VW`aZ[\nhcjQ|eZ +mp#beq'bkjfgZk"k dih Uf#\` b#_ Nr[n1e'Zzk-]zr0h'RvX}X~W|Qwe"m)Uyp.|;w5l)t1o0k*o0n/y9]X{h%y8n+^t/i$~r+s*8js&o"w)s&[gjf^o)e]PxRyn#_cGlPv +Rw eo&]Tzu,`s'W~ +g:[acVw l x-Y}fv,FhghKqjdZ[ ^^ y,W \[\fJ|U V ]XUR~ T| U{U{c\der'~7T{]ZT{^d!Rw_[ZW^Yeaf]Lti aY~kkjs&o dkhej Nt[l&fW Y\ fU~jn``VGuMy ]\T~i!Yj!V~ en$e]Pu Tyem&Ryl+KqGlOtRwKqFj Mp]!W|]w-/aQz +OxVKv^aLu\_v)l ejfOz^_ fehhmffe]drhdZk ly+ju&q"khv)]Tz +q%``kY V{ _X| _ki^Mr}-'jmcm ]\Wjm`bU} ]_Lr ]`"MwUd&US}Lu OwQy Nt YW}ImRvb cW|OyXYY}QrYy#Xz$HlZ~"Bi5\W*;Q,$o^:W&+G &?\03 1B% +(1% + + + !' +)=" %!  `k>hz;BW $ "5LUn#=Ro8cy,sS q<_u1 +"E^,GNeQbs!9?(/ * +# +"&zIt{B/ )!90-L!2 3C"  "%'6( +p=Sj]r"9O\o^o"t:R`GU6FOe_s/8L;L$6FX:K):)=7I*=,(8 "3%+)=*; ,('5 #?$(% +$,0 +%C6Q!(= 6 AZ&">*B&<"%6 , ++-#<4R'E +D\0Ul9?T5O#5L#8&;( (=+B*>6M9Zt,Ys#AY;Ud"]x(C ;0J ,J 5R,F^t#EY 3 '0Sm8hbz-4G*9 +?S%(7# ) #2(6K=YSk$?S+= %0> + +'+(  *' (  5K+DVp.Ph1#3,?&/ 4(<$:. +oaA[#AY$8N- ( 0?$& !/ "* N`< 'L[>@S3QeG+ + 3K $? .%6      $69O2E^t="9 4L;P%*B,A%7 #$  + *7#?H0"! )7!#  !8N 5M"= %A 'D'>"!0#3*8jsnm.4wv-.i4z17Qo}*r2tx l|&)iz'l0,&vojz)0)r?}$z(#~$x(&'} +&&#"zxw.|{v}#xz|xpxyyvy)!-{x{!l 4xs}}#z~mt{ys!|n y!~!}$e m-urdhiy vz &t2)/w z!|!&%,*qxswgz$/cgx!hw{$ekry&__k/z&cx&kly#/&'y#p/y%u"x%w%z"x tlqv$)gw iuq+qu!v wsg ymq~+bh]hs'V| +o#hdt!rv'{!z .}&qlpiy#lun}'d-ovvsry{#tqu*uxohz"%zpi .)w{wkw0?pj qotl .x&}%m|qj njm` z#wj }xwn(&{"kqi*2t{*{,lhg[ ls"=nz')lcgfTx|+t$x$}*gv$jgkp$ciy(}+z&[ W| _j q*o'68h!Fn*V|\X~[m*z2Yn(_a^bjo!bfW} +V| jhljbU~w*aif\s(en daiT} [i_T} c\Ry e#Xh$a^Zr2Hs5f(u3b~"j'a{ ^|[}Z}Y}Vzcj'd o.c}!_~i$a^|l'^~l+i$[y`~a}i%f!p+l+x7n-s5a$Rt`~"y9Yqk+Le{(Bm0l+Zwp1RuUxUz\YTyk)OrDk+`ct-?{7j*Z{`MmEdUrPna|$h+Xp0_xk2v;CCh(QnYva}g{/kcfZ~c?bo#gEp"7;]xl v*l=y0r)r)Fz1p)k(Qq b|5q%[gh^:t*gmmo%s)u+a~Zvi#_|l)p,Zvu)7p)Pn ^|bccm#k!n!y,}+z(w%2a}q%fs)t*n"m$s)k kbclu(o"ehbm#go&i!m#l W} kbifW| X~Zcp'X~KqggV| [_X c] pXzj1iWu Z{Tu fojej2t%mekfdY YdQ~O}T ]X U _My =hS}`Nz Oz +t*g [Tz`bXf"o,X}Ho b$a#[g$f eeu*V +QzX ]w)mv%6r&kv)_h|/k [V|^bt#gqd Iou!leQ~\Qz@jOyT~HsZ`\[Zbh ae#V~ak![k(SwKoSwNpNr]!RvHnb<^][Q~ +IuT~Oy U~\Qy`_V ec\T| Z \gZklm`[bX Q{~)qix$Qihjay*ku&lbo!acn`dYU|W +kk~,ckensl~%z&[f\U Z]X YQvg"`W}[Jt `!XTIt Y`Zf u2Yd'X|Y~LpX|X~NtXOwIm TvWw"NqHj?c Nr!LpKlc.NqOqd/Zz,/O^~.2S@`7V65$=%9$. .D. $ 'Zv5Hg@c +9^Ci5X =\$A D](:Q!-F%@+E #= /J(:P$ / ::(B#! * +CZ3vk   !, ) # 0A&rOl9Eb0:E_4Rk?B]/!9 3Hev:D>bu35Jf.NfPg_v#3If<[Vw'F=\ b.%GOmGe -%=6NNjc"\}ej"-"[/n}-hzE\ +-DQhMd}h$Yt!_x32J=U1D Qi$!: 6=V0H7Q^u@UQOY8 !@?kl#t{4IU*7l*+>5#3 + 0& ) +(@Tg|5s:L`Wi!:MBT.A);):Pc".D &;,> -? ):*;-=3D5F(3C:K*=(;,&3 1 $!0 (=+/(-%.A:L0)-!/ +./D2 /,C6S4a|AK`1+!4C T_D'4a{@:Sq*;Z2M)@)#"=6"? :36&!7+E3R*I 0J5A\Xr#8TW!4Wtf(?r@M_#0((CrB 422E%:8J1!4 + $ .>Ti)3JOb$/AS#c   +   0+%* + ' ! -6F""(:4L>U5M$"7!5% .A%  +Kc3(A &%> +VmB +  +  "(5%4   * ! +# (8-  @S:)=#/ 6!5 @P,.     AY)1J?Z3L*@ )A23 DQ.;M#BQ4))5   + +      %/  ' +%" +0"5 +",; +/MC/J!)$.1;$*5a~-don)sj1~,w({/9t(v'?~&/sjt ~,v!r+|'/',{%q1l*,x |"w+w2&z~#)"r}y|{wsttxvxyf|ssg rywu!w')}'}s|s$"{wmk| &$qm +x wo wz(xzywh*uqg.p|$q-*%v }$)~$ro#ps{~~ wrz!wqew!|%lswwd {#mfdmjt!n}.q!gj)|!y!u r+u qox$o+v u*py#x"v"n.mq_ lr+mnw"epognffn#i^q&u'nw &~"g u,|&lv%nj8u$v1f(oo-pr~#lw#t"w h2|!%d nv qfuuqoo`g*s{*kf | oh |#e b .*|ncr{#]qy%*e yvoo&&u1uko5likx(nf_hq"|)vv0?|,m0W}\,l/r!r#ar%w-h_dq v#gq!n$dafeer*i#Lr +Cgt7f)PyRw^ccaSzMx [Vls"dnehav)^7hMv`Z8feIehihv+aU|T}\f$Ox KqDk[QyW}__]t2Txe%Vyf'k*t2n,Vb#XwRrYze!m)Locn)l(s2]xx:Wtg(`}!Tr_}#MmUra|%Vo@l)r/y7l,v7y7{5K^~u/d y5j&q1l,l-u3h%x8y9x9t2C}3b|j%F`yAn-u4h(n-f'b#d&e)w8w5h(|=f%f'`e [ul&bm(y4l&ff=h$~7AXpl+Pg*_} `| Tt0@r-f}#=~;az l,e']{n.` h&dV{f o-m+\}b Fr.s1x8y;y9k,Zv`xu5g(e~'az r4H|9p/m,Yqby&?Ek*\r v9l)ECp+ew$n1ay%l.f"p.g%`9l!v'HUwy+u+n%i!9`}s'k7u'ks%l>r$s%4|3g~gq%{2di%Yzhm!jYin#]w.9dew%v)cR{0Mmn(i#x4`}p*{3o(n&VsYwYvo,d8m#Yv4HVr;z,n$~6w0h z1Yum$i!6q$r%jms"nr#q%e`Wzdhjap%dq"hj\ YaY[fKphn$\i _[u"pusdh_bY{l{,}0hbcs'h\ hidq%r#`XVZ\V `^_]XQ|T~O{ Q| cZQx NsIl b q/eRw]j*e'Lr t3]e]il[ gk cq"t"w's%t'iv,a^b^iv*{-cq 3j1trcX_t*Z=eKq QxMvcfJqU{ +ep$h GnT}g$Nv n%\m%eSuMoHjWJl Lq x4aT}P{P|f`P{ +U\>hMuEkZ PxQyKwNxg^cchUknhhZLsFogqx"t^ |-z-jt$Ikmhr#w't#x%z%~(igqpkqju{#jiilljpaZIq`]]`aU} Nt_Syd#OzGq [XCoVMx [cOu +PvPw\$Bi Ch Gk Hl HmAeHqU}JoZ|!JmEe9[ Hi:Z>^=<5NTv"Op"%B?]9V-H4PA^.;S*$ +# ,4J"3 TNfJg Vt6UQlf~"LQYc+w&Shz!(k/A^>Z$";0J5:N*$<"5 +   /C:J& m~@bw4%5 +DW +L^H]BV ;P 6K 4H 6J7L?SQ+; (7% :H))F :&C,KMh&"8 #-+D6Q,G2O51I5 3K-L< )52c|7+G1M8U7U?^A_83.E +LiD1:Oj'(<(NYmjx24G !6+>-?$2 "0 CUPf#9P -B.=EY*&6 *;,B ,# - R_?ʊ[l$(;#8;O:Q"I_8CV/*&  &   4P )A[+>T$" BU,*B"7 "3 " + (5+:R( -$  $AM80&:47R>X+B 5 +$/? +  -;3B(/9$    #   *. %. / 0 0.4O?b!9T% #!hw*Zw x(m}&fr}*t"y(s%;t%3--yxu jf:v!ta{ A1x"v w!z%|&u+p%uzu* "m $|4| 1%zvsr$et_lupp`v!mnc +h pmu$~r~xyz {|n"{j u2wm{(y%u%{(|$xx){"u|w$|(~#m,rw!n{&z#v y!v1zux!z#t'#+,ply0{2''4xoxqk~"(vz!piy*q%q!:w$f~0s#n~)|%wz&z)o}(2rv"u"k1|&s.f}(}'v"r})/7p+t+gfoeqfu$}0r(alq$go!knt*${"n{&s!y(d{*)/o1ry{!{!wz|%sqjba f m.vwfiwny"x!in#|!nqqh }ny"kvqhd i)ak7b ohrW}~0:t!s|%rx!i{"rr,oiot v$o_u%r"gt)p$v'7u6jqgn :n [Jp},p!i2p#gdt)x,u$1nr#|0]Zgj"Q{r*x0d Iqh(_U}Nu +X}y0_bR{UZXm!f\ 2q$s(ch|.mc];u+``r)}6_s-bT{ Px +T|XZ5^NxT|5\RyTzPu g"a^Mp c!Ot]Jo[}Wue"e$u4Kh p1Yy[|d Xy]ae j#:g'g([xi+z;Qms8tBm*<8x0s/?p+Iq1Yd}#az!{t2s/j'x4={8z7g%};w4Gy4>o,_xCt-s,Dq,a{l(Zvo)[za~r'fg;aOsOs3VY} Fh ]^Pz S WV +^WaR| +EmJsIqW|fSzt!E[ ] Z v&u&ggZsphp[ .s#Vqd drknt^ eu s"p-ukqsl{"vtsknomx(ichiv_c]_Y[]Qx S{]m+b!QyT}Ltf,OxFp +[[U|UxBfMrDj +W|@dAfFk NrSxW}QxV|Ot])KlHf7V2P +7Va8-K;/L Ml$159V/$1I'C(E@Y/'  G^6 %$<#=#@^Ff1Ui3\0*K /L,G 'E'D 0O:&@$@,4 2 3%5 3C*#"5#  +4C'$ $ "3;U*+KFh-"@ )E#>%?$A8W~GOh g~#]uSm Gdes$o!^yMj6U/N4QUq$AC` u5;UMd?Ui1:RB\Og n6%t\kugn`g]Ai}+uj}]p Fh~;0!0K FbWtLjDSeq{LsP \O3JFI+2 BO_.@"+8- +%8%8"4 1.%4(8(6#"2;LSd4xM*=>S NdLavGG[Kb?S Wl$>S 6J +K`#6J*>&8 #/>,-,# 0,"32C3F5G'.#;7 51K8Q#?$B :V(E.J)*< "4*/I%? 1M)E*J=Y!:+@ 3,G:V7S:W'?)(>Lb.&!9 400$+B: +0a}(rDs\CZMg(FpF$A +7Mk(4L/I0I +Pju%Zq_QiaxG_ o3fz5%4'8/>Q$4K&0 +;O0E5L>W !/1C5DU`:(. -`t7=SCZ@V"! )%6 .v/Tg +AY#9- +%#& %$" +( )('C4 ; :Q&2 ++ ?O-DV(*@:SB[ =V 7J % % "# +! .-9+;F3" .C'=&,' "  +( 0@". + ";R 9[q?*      +7& +5B/   +,@&( * , &=#;6*'@Lk4": Xt n$|4v*d/y$ly'3},ko=.=~%7wrooorex#-7/w!3-x$z#x"'~##}}-*-||}su zy{nqmyq|"b plkkh tnutc lshcawrportm5p)y|#~&)lV{U{fkl"Yyp!t~!y( 1~%ys~!vk3Fey%z&x"ur~${!to|'n'vtu$jw q$ry(x %{"~$uqr+$ts{%st$hox&z&y,{.t&w(y(9x"ow(n gy'q u"qx%u!s{"tunu qqnt u z$|$*mpeh_di^m Ux +~1}1t%n{,p }-{&'({"b 1v"j..}"qv${yw{h stvpt'j`{ |,lx x!` +y"y$|(mw"swsrf-"woh).na ` lhi3z#m6w'8i|4In x'{(poqy!{#b x$q eu%z(] r [ v%TsFo"lg1r/i3laigl"3mx)hn!}2Y|hcddeim3o"q%{0U w.gv.af!g#ZPy^Ot Y~q)\U} Ny f!eYp%fPws2<~7w/n$:s'M~6v0Dz4j#v.x0o(s(m$]{i4t w#pip#f`o&w.ei z1giv&jez&kgTxdaYycu.p)d~5y,i~'h{#r2~,r$q"ct lis"f]Ty YOwHnMs_S~ +`Rz T|?iKu +U[`r%u'`k{0g]`fq*Syf X~]ZQw T{V|cj![ho$`k^q#h__lk X _q%```\ +aOxYp#U} iRx +GpdmbZ WgfZ]i"m$lilj^[ +an\a[jKtn%dTz t.W}?bf*[|e$Ov Kv^Y c[mTR{W +s'w)p4Ptn1rd] dX` deot)vi^gR|rh'x"orkmy!4qoi prplagqnohisd] ZSyagebz)p$_aRzMv +ac!U{SxMvPyYY]#Uz[Tx]ZUySwEi RwTyRwEj Gl SwX~X}TxEiLoMo7W :V4P9;Y?["=*C?X+C!8 =;0P45 !> ;!;*A1 3'&5& ( + )     +  ,D`6Lh7<[ @`%: $!< 4;8W2T`{/2G_p+cGSo9T?[5QYv#7UId A[KeUpA[3LQj8N16MAWLbE\}5[u .}6OY #,Mo9PVh8/(9K;NiTbA6FĜL`+r?A_ t-)Ec]o-YcE"0eq1@K@Gsw,$ىbZi7L1!,/C.%  % ! ->-> :H-,:&8 +%7 Qd*H^\s#MeG^!82F +Yn)5KH\Ti+Ka!"9/(?& .&-)9$4("2 -&6&6*.%:7 ++06+I A`"A^!cGEe'&D )E94 +!/H_-7R$ :1L&@Jc19S$.?[@b=]0M +2K`vJ6H,24 +0>T)'=0K$; ++D$;'''   %"</Lb#zLf:U$A +.Nmp[u93'?Un06O`x]uE>RiF^:OH[*iy0GIU-.D63 +*':1F3N [v+.! RY7i/;h_m}BNd +CHdLflG'5{JBZw6Qer8t(iwVq!?W1H?TOa+ &&8&  # 4 .J3P(C&< )( 6K14'@!;Ja+DZ0' /-:I&) % +2>-'SdF- "6 18O&@R3  #0,:" +  ")  +&1  / $Ma4 +(    + +( 0!0  8F5(  ' 0%* +.FRcB6=,'%Wscp-v/^zv&}.hu(s$0x)1./).0}%eZvh/ra r}*}*o|&~(y$w!/'|""&zz$|"t|s zqz{"yxmqx%hnhc dfffne^ h`` Zgdckd-Uwky#jz&db +}'.t$mVy k!b_]5l}&w~wgxy|u~smis#p#dit!^ +u{!vvohw#pjf h/psdw"q4[{q y(/z"wz"u${{vy!t~+st"/z*u'q$o n!|+8nrs%ly&t#v$|)omt w!stqsu ogopx!sosLsk7u%fdmo{+u)d|.y+{-in11y z!}'+v"kfv"w!'y}yy{*Z~k |u`l xwypvultot{'fmmx!~#qhk1z{ku~*u0~%X}vt!l0fq1u'3hH;s(\u%g.et_gu"p"hkz*?er"r"d3x+m cv"np+og]p%h!V}^|0e_p'p%^XZg b_db8t(jj!p'l$^o'fh KvOy IsS{U|X~ca`S}]V^c[`_~<>by0>flv*s)GqZLzOz[VWn,]R|ag&R|S}b!n,V}j*v4Svk)Ck&l(]p+fl$_c_\}c e#_~b%e$f#V{\i%dm$al%l(abGg&c|%t9Es6i+j){8x4|8GAo,z3o&r*k$a>r)r*~6k#o*p'p);q-b{>k%n(x3j#[wu.j&}:Wpf&Xw\}Tw`#s5i*u4m,Xtb#m+o-^}d n*]{WuEu-h"Qn w2Vp0\tv8COE>:u.gy3Ks7k0o4bx$m(:u2az}:azt5n-t4h%_xq.x5\tv.Wt=m,l.Lv.:~1t(m%s*r)9d~G{3j w.n&v,{0u%jw(llv*r'u)|.o s&1:d3ie}6y1y-2a{4qmo aj"r)o&z.u*{04<{.r)`;~0~/ihw({,eefkj!w/m"n$jAil!\W{k"o&t+q(l#|3ddc"Zxh(Zv]yn)q)k 3{/o$z2r+t0j%g`}v.y3dp(dgp"~-ft"nagm#o&chca\beRy,|'u cWzs$p"^~b~t*|5i$i#q&hhc ojcnu${'n~+dr rw(ZCiq*YPvQw3s(}4\YLsOx[k$Umhz*oks gnks'Zdq"KrJp`f]j},~*ldfjTb\^RV `kx-dggi] ex(\ Qz?hSx]EpX _g` bif_[ s&lcm}*fMvePx{'bjav%YY[ r%X~i$Kmx9d[Lsj!n"jimhZa nT~YMpe +i%*Eytd\ mk[lgc xZca ` }&pny{"xqpo#t6:%l^T| ^q"kjljs"U{6^m#ic\MqZXRz Ny Ov RwY~X|W{Tz_!U|[!V~NtY}SwQu]RtRvZ~a$Y}\X~[i-TyY~QuVz#Z~'Ce5U /O ;W?X6R7S So&^{2%A7=mEKi#%A4L  6 +(>) )* -JHf,%GKl*%G?`CerGd4rBBc$, ;6T7X9;$= +&&@*-% $2 / ( $(  *;!# ):Lg00K+G/ *: ,IC\+/L4)G '!>8P7H` cv"'UY?1;DQ$T.FR#zTKXilz WsT,EG`// 5!/   +'6#&4( )'!6F;M.@.@^p,4)?%;CX,+=Pd*8M7K/E,C,<)*"3 %&70 . ! + &' +*(>CY+Ib.1J&A$2.L;"=Kg-3O1J nKPi-&C+ 9@]0M&@=8W8X 7T C^*(>%%## RfA#! + 1 ?S'Sh3;R7N30/\w+~8^OlQg1UQonBx>Rg'?1+2M;5PMe+B-C;Q^t$Pd$%6=L^jy<9J/23+C G\ /DRk!SnN" Te#):uJ ҁE^Wv =_h,zG%4[j>KF_ j#]q>KR^* #TCGz.? +361M/7N#, # (AZ""6&;." + !/& +- +5E-9N/)+C "0#  +'*4# +$# ' ' ,  ,4$" + .0A!    !!, 0- 3"!! !0  Yqo-d%z6i s*;s*x-iu(n/t p/{'ev!hky'q}&nt"y$/s 2y#'y {"|"y}-rl {(u!|m n zty||tulw~!$vey"mjb pe\ _ _SzeZcX +X ]Uz Uyu$o s$q!jdlds~-n7lQrcn!f@`gcfy"y}rq-tsspkkhd=cw'z'-y rpg pghm)gl}'Zzt"gr }*jggv'pi~$z%}",pqzytsz",0kw,r'in#r"w%pkr#jpq!v$qoqsskhpolv!` z%klpmgw!^n9acdmnl_xr&m"q#u&u'kv%>vkst v"rks~""ytv{7] +Jpu|vwn m rtjh fy!b eslgv!-khp0hu -$9jqx!{#&nomnz&o2o!Lo#k!|3hdRyt$Z Y cm+oao"w+jomm\ho"z.m"q!q#8i'8eo!\T} S{ Mx +az/cQw X}q*d[_QaS ~3p&q&ck ]t,eY`:V[_aceOu +^_i#\f VNzi#OzU| _u2u4q-s,4u'r&q&`_SN{a!_Yc!p-e$y7Lv a Z[i$S}S|a!d$X{d#Pt ^^du*q&c]gW~h!=x4^~_}?g"`\^a[bv-t*Qt |5m*\xh&v3{;i)r0p-k(k$k"y0v-r*m#m"c]_~8g!j$i"agr*n&l%n%Wop,e#^{dv.b6q's)[zb b"XzQsGi +x8g$n*o,k*g%f!dr+w1o2n/e}$j'w1~6l"o"s'v.}8>~:k">:y.s)q(n$~6u2k&r+m&s+k"q(l {,hkr!p!o s({2u)ju(q%ir(jv-w0x1x08Ek6pl?jgp(n'z1v-t(;{*cbFm"d{0jcw,ikbbl!i"j%t,q*n$q"bbehgn#x,r'n#dp(u1p.^zWtf&c"Zv\yu+k 9o#k ?~7x1r(cj#z2t-x2m'd`o ?\z oh_ZcY~ U{ bn*[W} s%W~ltjsz$mnn jl!l#Ibba] qo5lgt$s t!nfmr^V| Dil"c^F^W}u/\^LrMw Ydcoegqhn})1iU~3dpEn.gnb o%d fcw%]V M}V N|h_`gnoa +m(z%hgkHqFm[YLrfa vo%)qw$jmegy$kfpY*Ovk` hc c dkJqOsPs u/\~ u)ggmjihe g k z$*y'.%g?Toz gTzhd imYajx#` +6mj !sk |qk {#keor[MsMsb\W}Z|Z{ m Sz Xi"\bY~JnBgd\Ls +RzUzW|GlZ}Za#V{TzT{U|Lpc(WyZ}^!TvZY}_ ]_UyV|^#c)Ej Wz$Rt!6X +K2Q Xx6;W$?Uo0Ni)E_?Y=V5 =/I46O Of,r 5L/ ++ " !"4O>] Nq.DAfFj0TCg'I>`Fn&F[x>aB8U2P@_&6R0G '5 #& - $ !  +  ".=S(B : + !21K!< 1$? +<+E"= 2M #%<JePlUpKeUn F^D[m3F]v-E3I2M-I,E8QfYsp,Mc Ia C^Ki ]|Iey%6p*LhrS_Q)4t}*0dtk|B1GcxMY +fx=8No6Ecm]w8I mv7` 17M*?%Vw>K%3 +))(C)= IM#!*IUz68?U\ )09G BXRL^#1*'.#3!2&!22C!( +$ +!$"#, %-+-BY)"7P%,2L Sl!/M0J +6R(A 6N-F6gN4J+sM7T m9Gd3O3-0 & !( ! +"4CW$%:3%<6P 6s0rj5Pi+]Wp%Li]~-"8F^#50Rn/#? :-E7K 1F %7/& #)< %> !9(?!7,@Ka,B?W^u&  + ,j@\p=N FV +FW Nd{q;Fb^t/M^#): .:5I +D`xRf BP:^o1[sNdTf,2C);(0C&$>"=4L&02,BOd>qx^aD\.BU8K /=%1 +0 +6D& (5,,+9% :S xV +Q[>LY5hwK5<) +=P.Pf4/ + % %  +& -$. +!$'*:    &1* ! +,' .:L6 !- %5Uq `i+c%s/bk)l(^s+8r#r n},su#l{)Wx7x%~(z%qv"s"~+frq}#y'sru#r}n !xzsqz|vsof] (ua t\l] +c aKrdV}U|T{TzfQwQyZ[QvGj\}Yz Hg\w~0b}ijnr5lqdcgqkk!ZyY{`v %{xuujqwjbu$m gp&r%0i}%xvtjh gpfjdtorkelw"ne]~{,~)'~%z '`~wqv~"~$xw~")rn jeq%v*mpomy%pcq s!jem{%w!\ ofkomjrijz!|#c p` 17X{_ w$+{)hex.e3qz+kkl-v nbqw#l{$ou|tw,b j}!ts!&n_w4Es-c~d"^|z5eias(v,gr*Om~8{8o-q1f~'Iw9l-g'`|=r+i!o&n'j%p-y6d|#n.n0yx2n(y3z3dx4=s)}1r-r/t2t/n(g"v2g|2w)[ ep!hq&?k#b|:7B{2>m$Mr*h!8q'it#4hfq"ks(w/_m&w.n#1s#eck$w0^i!p)e~6ZSy dV| +fn#Ps gq)ci}.`Vt s'\{Ss3lc_{i"l(d#f%q1b"t1h"q'7p"w)p!_{n$8p&p&y0|4g\y-{0o#Zz mx+fnp\ `_m`dg]`fU}_*mz x!rmodkq%Z}dMr_Ov'Pu"<_ bN[|)+KXz-_,X{+1,Lj,:&B&A14#=2+ 1!$.&$95E(1>& I\<.>##<3K(2O$iuM.G!804 07 +&> : +*G4 +45 2 +3J$2 +?UY?Z8Q.E-E=T:Q`u'2Tj!?U5O q:]{Yu"Uma{Soi$Kd VoJg [{Mk +Hb 2Nq.;EJ+,,@(!23F @T8L4*"2% # &&&(,. ++ $"1* #( "AT01E+@!J_(@Zg5#=+H -)8N%1F"50Lf&Vr#A^Mi=W/F0D    3$(! )? I]Vm.'@QJjX~4{ FcPgIdMh-F +2/J/H7O(Ys3$=&?6.I,ILg#/H Lc$2G*$&  &:5 Oh-$<=S-@)+>d{&XlC^&6% j}C7NQj +EYWk%Sc 7G  [o:]o!->%1A(#4!43- Yy=c&e'_Z~Tx^e$h l$u%v%r t$nv'dbes$bnpv#g_ nm'}#wotyrs uuvzfpvu_tts|j ls!^qlncgaq"cnb]c\[`^jaQwNtVz j p'Ys _y\t@axk b~m 7:lx&om`| +t#m!ajq!k&e}"tkc +{ ljcdJtv,_t',}!tz |{bsuppihw qpsjv"wpas$.}%'xw"~'lkqrtnwk 'y$lei jo$jlpo+ehhkgit nfHus$kgkgv qrfjz"gfk2o\ crrq"bdt%Zy +~-llkt"3q),dnx#pu ` ow~n{xa'f kmi +zw}tp~"rooov"+tu,~(u!hs#w&`=pnoh}%t 0ih'_ rmccs#v$gjf4l^ _u%T|~0gZo i{1s*is+w3e e"x0d}3u)ju"v"iv sdw&Rr(R}Jv]^T{ +x.@ieTzV|FnXUKvGol&o+Ry BhDi!>fm$n$5go"}0o"dTzRwz9YS}Q}c[]f%8bj#Qs @l&s*`b^s'cih#YR}=Py]_d!^W~j%_m'u3\R|b p-k'cgs+5dl ]z.hu.\dr+k#g"a_ }:c_e"ZaadibQyr-_i&_bk!eq&_q&fp!z*dmmn[Pv |8l'k$Mpefci{-n r#s'@~f~&j+o1r3{ ,C^,5 $= %>,60-$ 3*.G# +7(+E0(A $, &C SXo!Sj&<&<M`cv4:J8N *6q;HjB_3NLe8SAZ Ha:V4SCcFcAY6P?W@Vkz/PT,#40DFKE1!isfr :JH]2KKb u)JIQ9g}r +Cd=V|XRr,Vny'LZAN_҄(A,I,"  &/, _qUd$elx&5E=Riu/~"`Wj7M 48MIXm'/SdMb%>)$6 '! ':+1*,*%:%>S,BU3 ) /C&:1)  +' EY9Q2K#;,=  (>?Y4R(F. %'   +  /2"9&;"7k;Mo1PB_=?AW?Y,DZu3Gbm>%B#<.F#;If7U >Y9T-:U-F6N'J]), '<4%6.JI_!g}3f7G:LXp,BK]sNbj|0HZXn6La;s7  ", & ER3CP3%6@V@X Pj]q/". +}QL~K7G #. +2 +.L 3J3*>,E=QwLQj0 $ (fVLg7?W9U]b"6P,-KB\.  #73/ ++!1Mc8:T7S[t@(!1+;*%#+. 1D$  *  ,)&   !/ +#5$3&3 #. # .#7#20  !.- 1) fv3[}c([|m1QvRxd&b!x/v)w)s#d~2hclekd1dfi_ u!ntlzti ~ fpl +l +vnk xsg +jj)!o}na cbglq h_dz,X}t"Z}nX} \ {,[ ` eo&]Qw Qtk{-h\tg~]so%l&m#l^t9v#~+t^{`} oiei Uu `{"!sqo6(y!qhx&n] +t Zrw($m($mjxx!'g j~%ww rbz$t)m7k3!q0){%wjjj$&k #4d2w*aU} ids3nz#jfv&i^ h|)pcdmv"eo+^h&sd okb f+{&epz"z#pnko#l3tx#[~})z$tolm+a&qpwt{ xyz /k_pfvw(u!jkkov}&tw"oi{"qmhalMuZ po|&_ qy$t!Z^ +s ax"w ~-h5/C~*t|#sdOxqW~^e`jp"ct-_g!Uym,c#e$g"dn"ifocfx"qlb]Z]eYfz0;e\Qv:aJpGl Y~Y}GiCcf `Bi=Goe$Ei_jbZ ~1Sy Sx]Y|^s.MxXb^Eti$Bn4dz/9ev)v'g^au*[Ir Dn]_ T{`f!n)Z5TIko-^EdKj)jJB^"-I]'a{4DZ$*A'20!53-- &  I^A2I,!: :A_'/M +,GtH\n4&;6 +(B/ &17S%1(* *)7Zh6N\%'7;M fu;0AE\/G3LYsMfi0g.Ys7OH` dwEW]V^uw, lj7} `imz OUTj.Fw4M Zmr#|~!wm ZU9XTtp*Y,!<OhEBW,!&GZ$`r)1B^o[l ;HPaG\* '9Pf.bz>_Je*/KHd,2K.I+C8O& + !$1 '%  ;O!#< +;V2)(! %2NDd$;<Mg,:R%0F^5$@Sp9qX+H"+.G#8*""/ +?V/;X ?Id Sn Rn1L,D 6N(C#>h:>[ *>3J y8d|(EW% +%0 +.<   +"8$>AZ!0M0-FY^t,Pe"l#8'4*C4:K*    : !8 G[3. '<1I"-F6L#880 + /A"&.:)+(';( +   +(-A&?T' 7 .* +! ",  ."7) +".' ! + + 0*.%";3L#BW6%6#*: +@dd [}!Hjc']!VyPtXxd$~9p&w-m!fn$fcgd]V{ [e^ jomo&iynph ~!)m fnvl rif p{bjo#swhd3]~0mdw#eht#] fr_ Svu$Z} Vwx&v'dQv +X}u's"v%kTk s*t,Zs^wx/n"r"|*u tlo)q` mm Pp{)&vpe }#}#hmqkanp@~z&(&onswo~!)-mtt|!o}!g){&z}!q|&v+)s3y#`jyu'"p]v$t#aV~oqo2vvjhnc\ +d~*ri(kz$mk}%pkgolu\ulx"sry vv/|'pef~+s sir*pw ;'rs$v} q|%*+nlc lksiuv~&x"invm}'lo9ix!lmjoZjaNv/rk/dgc kqeoo}'z$r!Pvp"w(dbe qqa +f}*\Wgy2x,dUzy6`dBk`!Hp +Xf%e_t%u'T}m.lv$PxnW +X [][z1fKt]Jq@eVz_!d&Yv]~y1bKnIn|9.VCh Kl x/^[ +1Y dFkCgKn gq&gIuk"XUh X `\|,5s"lpp\eX Rz HpQzi-Py?gIpT~ \{7`Up&W} r,S}BmP}s)j`bX~Ns +Qx a]_gw.al$j#c>dr/_YS|g&Oz R~N|Z^eadq+c|5`gq$bgTyc`peg^Os \Dge$[~^fj}0kx(oVv_`\zYui$l%u)d\| x)b{1Vri&i'Wql*@j#8q'ceu({,2fy+\} ;cl#Ut =};v6p4z@i1Vm#5OFeVv"Ehk3l2uA^,Im/t-|5Nmq2'A9Tu.7Zk- + ! 4%D Vx11T zIfg/SLFy?mD*HFgGi*D:XXu4#/% +$6#   +"Mh3iI0P9V!;oI.$<16 9 ;-H3-EF\'Tf4*;'%  &RLV 0-/Dd}26Q4LIc1KGb bw%\r#Zq(-F\u!VpPh,C.\t(%CKc ^qG\Qc4Ahn4W*]sLh?[Up a|NJ^ FZ CRm|#0m}y(-Uh 8X Ha\vs%dE`UoDZ>@ +"<!:36 +" Qe#p7;jyjwq~#N`]r?O@RC\Jddx,BX +Yn"]s!Qe@U0C4H$8:R\q'K_ M`5Yi8Qe1F]#0K )D?Z!-J9 ;=U* - +B 6* + @Z$7THg((?./DYqB!= %E4S >5S1I +0E+!>wV#>9W (HEc* +!$>/&8 ^p<;S9P5r;Zu'+G70hB%AHb;U6P(@0I 9"@(D 1O 0N -I5M&@\w5'B3 2 !2 /'(;0+*? Tj2/(<eyp"w27MWpXp&EO_*&,5G7L&8'?czas,!:IDS$"4CCM0mnwLr2J +&BkK7 '>)1C&:#g  7H%,-)&/F&B >[!3O6%> 04($7,* "5 $   " +>N+,/G34 +-2F$(9  + ! !,   +    +"  "% +-,! #1  !  + *' #986 )>- +9H&(; + Zu\zYzXym1YuLm^~#SpZwo.a|k"_|`v-j"[V}YX~aRxfm4eoj|&qyqq1j u\W~6siqe}&gz!~%aurgmfou#iqe]~ w!h_ oboy$e b=Z}tky)gXzba]z t$^{gy,|1s+t*}1t%y$(.)g z xqifs{&mzr!} r,gr-s u!h(.r#}#jr'is{!s)#)tq($xy#}y| }!}"'x%,*r-y#jiwvqv}lmrx&d~*~)o&v|#q/x r^ +mv!uuvvvsi(qw|$qz%b g5)ut,s|!wmh^ nrpgv o{$o6u1i<-,stw$}(odor,bm`my'v"u uv!hnldu wv*gqvt` q"ho u!y'lkz&{'dh}+Z n\7e] Z +q neNvl-iXcrm_am'UV KtXHqZTJt 3\KtHqRy hq#_y)u$}+jp[ ecu*`Irh V} T{ +bm&Loeem(cCz,i_zp(Ah {5T~In +Os Ux{0hc]X~ [{7e i o"kar$kjfv)bk{*3t!_ w#sffnY YMwV~AhPx +\W Y h m%^w+Qyl"Ox YCjjb[~e}6y1DJq?`Ys)ho%w-r(Px j#R{ ^XY]Ht>lJv UdYV{bZdT|l(dX}^ZZ db] nW} GlMp +>aSv[~b j"[~m bZ{ +/lb}1]{gc~o&a4^}fp$p(Qm r/o.t3t1l&v-`h\^o b/_}dm }0n$9r+g!h%o0_v#Jl5H`Wr)\ODT)!&+2kt/Xb(;H1CXp(f/;X Xt 5R=[ A^Zv Tl5O5LF^.F-Ma0=R&<k6  +8I,8H#,>Nc: 0H]:-'"AZ)-6Q=Z7Q0.1.2 &:X(F&@@V&8M&/@Y.&C1M ?]:6S$ 3- ""2^sO62 ,:U'7#;T^G*8" ( +/B$$ '(? .LA^8V}T91'>5 +'-#)A!6.Ec~E";G^)Wj:   + #8 5#6+ +$* 0 $5'9*   #! ( +  + #4# +$ .0 +  + +   +1A%=V$3L/C2 #*A4I^xh'Ppp3]y[vp2h'i(\xZxe_|Vs >t.Tz_ZGmV{Z~Rw +X{ x*dmjdv!ttjh tl#rY 6^ .t#0a +m,i}xvmd v!mv#du"kiikcdeu!n-q(Vvpx!rm[}kr&s%:u't%u'|1o%g7s/|"4.y{#xr+sqmwzw}!pw+` rnu r-me .+|!&}$)1y#1}x#%,!~"s}"ux *w}"~&z$v$ovt{ wx+}y|!,nmw x"oq5[(%otuz&v!ojf rjuf~'y"{#myntmb p*gx#|(q}!uqiy%z%n}*u!0kw!g~&-touuulbemoprw$z'bw(2`w&ha})x$kc [z#upxyrrsmo n Z _{+ofhjz+pkg|,|-not ~,k^W~ s#aY}&hc/[ +i[]P| O{ XEpHs8bbbaAjS|Ks +f$bbp!jh_t#h_Z ju'_\m&j!gp%q$s&q$jr&w)c~fu(n#c[YbJpZ^gap%`]u)m%m'o'Z ;emdngfVYdu!w"vV6_ na~,diW} a^eq#[IqdkPwl _blJnv/z1p-e(]}o+d:~1s$d2w0]{Wud }7o)n'x,x,Tv`djas#gtv$b}?Tqy.n#Xy dUs Ha{Yrhq(z/p%f^|l3mUx +[X|V| f]br!hb uomrv^|[} }"ra_ u!v"|(\ +`U|9`^Twij`s"n^ m`r(n#Y}j"X}Sx Msf!eTz au$bz%tkns#s"ebf7ix"kx$v%\~ ix'jl,z%o|(+nnw$hdcvlrz)*&t|F>,tlwrh i&05m {.+`w p$+r&09]A07zm wxcg d[\_g \c [Yi ryra eS{gamz~j>^BaQsSvgX QyXfk,w*wpd Z|-{xr ( 3*|d x!.d ~$7-v*)_3&[ymg XxiXr$pn3_QzT}8a@bJhOm RqKn EhMt TwTxh*Ko]"OpVvYx`{]x]xaz!Zt\yYyUxTyGn Dji*]Jq Nrg([Ko Osb%a$b'MpLpRxSwY}j,Uxdv.MnTwQsCe PrQt 8\8[Nr&H3,Mg#:.x(d|7g*tAgm!h%Af CfEe`,h6Ba%wFxI=\GxJ(9   #!2( * +0 &= $1H8O9T,G *#"1/E f05-I$A, $,D0!1 "1  + ]bN[d0U`;HI[ ^t C\Yv f26Sd4c~.UnVn*E];U GdVqs$w-g"]zj%j!t)fs&m Mo\~ 2X} n hw$~+~,c~s%cbnoSsdbgb~^yha~Vs^~dz(jb6`Joo"gYf_ gdrf ]y obd +ws|)aSyshLr\_ddx.e~0v)x,ib]V~ YcfPr^k$SxUy`V{x0u)o!kjkfZ~ ^ ] fcMvdsu!hdnb8x(1|,u'k^ +x"kpt-@+}bvm sm bbix} uk m &Yh +k~%kQ{d e +z4wyo !+C5crs"/*o r }q 0Xurlrj^jpe g t^{kq[oNvdQvdxw_p x Gf5TSqW{EjLrT}ib kblocTx eRtq,xv%/y~}4/qTn b{ q)g +xyyl p [z"}&t~)q=Q~Yza^JpFmk"i_zQn +Np ?cY~WxEjHn o0DUv^}a~q0f%Xrk-j,c$]}a Mre)_ X~]W}[Mr Nte%Tk/[}RsKn]UzSx^|8j"|-y,ZZ|9m0GkFjFke)J:Iz-Rt Oo>_AcLV=j)Il8Mj4g>6X ?^LHl `?l5Eio:Xx"Ss EfWy':\ *LHfvI"9 "' "7L"+/G& &3 #^{DW?Y%G^/2&Yi2A1FVl),#8.I/eDmLuQ,-dtK!*)7(m{HXk%Rh9QCZ '@,G 9*@8NZmVm Vo @e$]xz(]y 7$? 6+ :M#+?*A #\s@;O! (*#7 >,J#@ 4kPi(a}2/5P-I1L/I7P =V&$? LjD^`y/F[#9).A 1D%$ 2 #9,C (>*>#0A 8H" !6&;*)'# %3   + +Jb2@T +':eu#ObZr&@X +B6M:PXk dx qr6HI^K0D~Q9G en2\j-iw4p:G %5(96I+*=mUx[=R9M&7:K+  # +.. "1E!+G+3@\1OB]4L(A 9.F 8OGa#)1E I_1.A"7 +Uf8 3L^4. +' +-(@1    + 0 +%&:H(=K0$J`; #8(F?W%8O#  .1 !   +*9& +   +  +  $ #) &'!9 +4>T",pen%u1Nl Ol +g"u)To;_~_~{)RnfjQom[x i^~Yw ou)Mmj a\zbeo*v|%mux!mf^ku&ilrmny}y}tjv%x%e~,mlr ian^~jz)p` q+z!.w"1ps"3jht%-tus!bs$~+x {$w{$}#w k{"z"%~#t)o.ohontl&,v,u*'~%(&}!($y,({z%|%~v$zzz tx#elx"ow#v#itwlqn}"~#)w)vviou"kx'ooiomsnklw&ou*iora ))i| oow y#y!{$*}+v!m3){$z$y npsy#tstr&~#'qz"&1s^ 6Z icr!z'c 6idoU}ek}%d z!qmsjnpdD\ nW U z3d\m#aC=cd i%[U{ \\W} Szkfz+r"cp%__jcNvQzy,q'_\Gu`]kt!Blr 4cS|`Ox R|\Dn_^T_Y cr%Y hcqhin"p$w(jkz)lciu)p 0a^ex)6] blgl"`s&iPxv"~._x,6hZ mlZ `Ticpz!fa u!is!_ ` })s {'u#7oz'b1z(0}+v%s"~-x(Mu&R2@9}0~3}1n"l ft)r+o(x1Px +Jp_h `hp'_k r+`_Ck HnJl?ag `x8n0@di)` =cr,_j6r)Lp7ZJo`\IkDgRx Rv +ec~,t"]| Q]~m!q$fep&Vt p'aYyo'\|fr,^VwbbVz w+T{S{`s&1hiY~8\yWrx4p,Fbz#`z#h,RkJuDf2_|(Qno7{@FTtq.|5s)v*y*h5u(s'{/c~;y-5ffk%cZxAp*do(h5z3q,=R\uNm.j)Vzi"{2g_~Jii#u0a~y5If^}!x;]{e&o1Hll)h |4x-jn%r'p!r&jhy+s%y,t*[ts*l!m"o&e l'@n#q"jmt"bJ}4Ojv+~1dYw +z+t$Iknmkn z)jpy#mg`iimpigeXx^~gnZ{ kdvdRuc[c0d]pkd qe qZ{wjg g qUyjV{X|q}(Z +eKpQt x2k!7Ir(r+Tx In<`]{%)r2)d pqX^ b f,4z"-%8Bsp?z&j_)m^p7g i tvbj lmgu_ h q&lnti +ctW|SxY| eaexl]_;[Gf +Z{^iw eMub lmhGgV| QvY{ ba} _z t y%wrP&+10/3)u{"H;:_ N)7;2[mNob&7p_ V{ +T{Iqlj]Rqh!g"Pt +TxMr +GmBhHoW|Twy7f(Ji \vOlRnNlRq\X}^ZNt U|Pu i$LqOu Ko z;x6GgSuPr Km _m'cei|-gSyev/z7RyDk?e9]Gl f#Ty?Y|CeVwiZ|[WW`/+G%;@Z*xT1Sk7p4};u4FI`*XzCNnPrXy(NqSti47X ?W&/ . )@(CD_',A3!970Ge#Jl+I  "&77Edu.f|_wRi-A +/C+";4Pm&[s//C %41BQ+>:O?T'YoD&C3P6$; +* ( `sL' ,:!* / + :)F2 +$?W'd8Ib/K.M <">3"><%A.!?zB5N]v'1Pe&-@%7 1,>'<=QRg5w$6+9 ! +%% $# #1.= ++0 +.;W@Y(?!6 %#?Le9R4GJ]=O,=Oco1BV{?^s!g''@Ys}=Ne=R$!7.E5K )?!9Ri!0'<J$^>J "6i4{Q/6& + +pxW$$ +    =E+#0&2 +     +' *A!9 $?2L09:S5M nAD\%<%=:SAS >N%!  !23 *A0J* #0A!".AFZ$-A(. !1 )@3 +(E,E2G M^>& +%'<2.%7 $1  +  + !.-"3&' # +"3:M%5K:T";Nd01(o^~ igVw 3Sj!_~o1Vsk}'^} +\| kZvx$oia}_| 5Zy 1idc\x_{{*,`~ nw tlgr!Yv kv&7+6x#tps}h ki5y'a}>9[w/y'nmf^} it![{m/ol~'+nsb~ou!s"{&r-y%^v)kr*mpkmo|&qo~%lla4ky$;z$r'+')&w*p}%z"y#||vu&tq* !zqvst|*n utd{&h})dhiiT|)qhnnfw ~&y"$)e fz'u"bopx#qca chhmo"t"r-Ynql'2o%w;tfj((y#.n}%1})t 'qmky$2r~!t{r(~!vz _ +_ s!Z^ _ at!]7ofu"Zw"lbd omh\dd{'t$d^q&Xm&Uh"es-aMr +}9EkGm`X|Y~aPv_s'kej^Yc^X [m!_Oxp%XXIv}4gop[ +mVR{eW~+Rca`jQ}^^c]c\Tbdqho cd[V}6b`s$w'ne^]^ `gw&dpv'^w(5Y go|(gl ^\o#[ x*c] Z hZu z!z#hc8r gfv!y$t!Bqru"` y$my(u!2;w)x)@5p$459;6:iq%t*y1x1w2o+ace!?bl&Jf6o$p*{4g'Tx_} Zq{3l#hZws*Tu?|5@dUy s&y)kfOo @ch"bh"\acUy w)ga~qs rx)q$3y)fk`ekX| mb_y)g]{%x Xxz$hi\ok[~rpmnq@y#gnlz#kmfkojwonwlo*b +%|$]]qrDchlrZ| Vy r*Oq s,t-bkk$e"l*:^g&>d@p'p!r y(p"ihPtX}W}KsKrflufib_ +htz%c{)s"z'`cafiq$[c`f{&t8rvqY|n hh/r rs,9o_2ctu8|'sm`kgf&Mk2.0 |%Zw +ekaoiQsdZ@X~ +Swd e +X}&ysi +nxw"}%iooopp!(i1w%{''u#(d QrTu \|lr| "~'mft!g[X|a\y$]zoFZIYas fz<~@j6g +m|$3Fc`|c2+}*x BE&5<~Ai emf|#6g eWyVzTyV|Lp@ZPvf&o0Lp NqGh +>_PqFf Jke%In _OvCiYNuSy gc_z6v3|6rr'dUvl!daLqY~ j`[LrXw.z0AhFkLrX}c%b$RvXi(Ez0fv,FkFhVw&#@ f 3k>P[Oqb)Tva-Z~%KLn3SKiHjY~&NwADc$!!5 ~XeA+L Li)+DE\7 b}F3QEic>% (6-; ex/rCO&: +Ja#Kc 6G^3Gbt8!-j1)>6JMb'*>  + +  ;K.2 +2*B*#' "/)'4+ +) + +%;66Mi*0L|M:T    + +    +!*!  +29S#<%40); /CW!qL,'9J$ ) +  +$- 77.H- ((-;/+ 5.E 7L!*,'+?- /D(    )$0(5 '<):   +  #&   " +    ! ",F 6Q9R%9@Rsjdfk!X{ dfmf_{*s_}\| +oy0q+}5z/x,|/v*y-v)v*}5o'K|9z6}=h,w9s5Bb{%b~#z7d~t%7r)y2g%m0i'i!=x,;Ni{-t$ljRv6hp1q%Vu`n's+feW{ an#>d~s 2c~s!(lx"~*1oq$o!Sto s u"/lk[z h*mcqPy]b\} x0n$]a\}s.Sno-n,q1z;h(~Aj1q9UXwd(h-g)^n&o&m%?w*~1s%i{.x,l t(q$6v+t)u*i!av/o'l"cn&p*u1o0s2q3|@f}*|`a'Rvp*i})?mv![| +[~ _Y{ caZnp+jqipmi2fo q!gs^WghW} +fl9vuoozo z(0wq o k w/bV}(*lhwd,jolY QyVv!4{,,$\~VuY~ LoJkq#}()wUsXz]d_\5b(n v&',kmrklosdyo q(y!{ 4+{:-lr fp){#u~+hQyYe6ZOs +][~,46;uho6%i t|!c~ f]xc~[uc}~&k |3+l lv}{E)t4OohVw Xy[}Sv V}X~_Ux_g'Ot4ZDjFk =`KnGg +Lp]UzYPw bl$j jo hs(n'm&o%>Yt4_ad[a io cde`E@X~ Z_$Bf :\<_JmY|Tv>dXNsUy?a*IFg*gh=Qzg2TzCfA` f1e-`,Oth5f3LlDfUz!Wzh1g6d}BmKJm Mq#Wx3 )6.H  UQir58KYj$$81I @V iy"u~3dk +Xjki^6m{!R^BOmz#|* Mbbwo$Ws:SK_cuQ[pk|Bu1I[akfcfHJ +]art6xZ^Yf3AN ]m;^r#x+Jcd=VSq )IY]p=(73G.B+?#: +&:%<++4!< )E4'>"56G*: 0B .    "  +"  5 "6 BP5   '51?+ + Oe>*B#?:W!:V/I3M:Q5N )@=WJd2K( '.K9S&$= Qh%h1I_>V -C*%5 +(</2 TdC # ,9!% !52J2*A. #7zXBW!*B&;6K#FY4(>Vm;0H+D*C=S# +"/B;ODX7L;O .Bh,\uFZLa';6N5N*A+F3K =R&< +@\Ba @]*An?Wm"Tiu:p;Xe*8E=G"'Wp:e}K-@& +);(:( + +         *$8#:.GG]*G`,:Q"#!$ (7K\0'DS-#-#, +    (.KF^*&>0 0G): 30A  + *B,C (>/GV1    + )    ' # 3('& +   " -$9 2K+E*Bz)t#h{&luqgv%=X} `cV} ^q&u)s,x5y5j)j(g(As:xAk4n5Ikr5Ux}7e\x75p#1n {+r#~-it)j:r#w'q%v*a}x/v-[wc~3Tp |7a b|"c{$}@r3={8Ip+7Qz1b~EbLk r2SxMoZ{f+GbRn _{,f,m.Wxa!v3s,}47j)p1QoYvm1s3=k${2>Tu +s)igx 1_ |+lt v )q}#un0|*-~&lpqx"8qr!8hw%}*[}~%j+qz@wf ~ w0wqinpojkz*s"~-o`fiXf n^cbe glroao/kojmq 3bq"n"Bby.u*hjOn<^Lo:^Z~ Lq:]q#o-o|&.t!Tz4m Oyu$grqqlmkw!as"p%p#x*^ r"jU~hlJq[ V|Lnvw!wovt p%|8eo,d}'qc QxIq*u#v%d%e Lr\q /|)uttg Is%w&y%t$'=WB_|Xwn)s&x7A-A>=\!bd]tv{"y ^X|k$b n+ji%}!pjhp~'wwz]\~dY^ [ +t!em2;A-x"2;d|c~o +:jlq6g0J9R4Lc}olrgip +za#cX|k ) bTv(z"Kp]cCjKu +`W~^i&OtAgQt'@obu{3VqNkK>;rMcC\x*rb_jWF0fm68eeAc/6b*9 +av39GcXuF`Cc9Vm= +kyY"*/#'+8.$>8 +/6 %;$4 - 4CXf<'5#2/ % + /&6!#   1="1 0A1G! $9%; &= 1H m=Po7B\F[PhNfe~3/H A\ BV)07M/CPgQj"Rc.+< -0Wl>2 + 0 !4 5 <3K2%+ 7J131 +0 % & $ /! +&,BV2EH ?M-WgA9N($/J\wJ[kHݫ !,'<1 + + # 3?3  '5" ' $ 3'?. !2 # +.?"0    >U*4R&C8(C$:( 0 $*A&A #<3 ( % +,(      +   + #  ;'D* /C 4 ' +# +-Qj6#<. hzz^gc on|g `|~ z1wvqz"zr)ivqrv%j`}r"s!-joh?t&t"qu|"z ry&fnpu!-now#~+x#u!z"pz~"h zx.t{ Afz#~%|!~ yzm%h #%vy"t frkb tvm{$x!it(}&z$ky)d2t/&u})t&~v#y%l]1bimrv%w{ ` +dob ifsxf uztqm0c^0+dhow%qz"'uy"/|$}%~)y".q{'y&pk1qt0hhhs!z&2r w&s!o!x+dfog|"ya +dl0y!w$sypls"r dkqu ihvs-h +muql +m |e w^rnr}%{%i.|#-Xdy&v"cl]X s(X [lpT|~0eo#[^^`l&gagaci%m$k#m#f]ent#lm\[Q| MxgW\ ew&YKqYZdJuT~figYMu@j9cY\lb\ U _Q X [ af^_]hq+bf`bfoqa1c vngkfpi^.|%nr!mM+j%Vbmt%lu'fBmaW`t![x%mjnez%hh.u",42.j})2ll/6n$p):/u#s#6@3x,w,H?a~|-O7m&_o-w2q-~9q+h i"r+v-{.}0>{6x17dyx'qh5et%h:~+w(jho!4h:knr%X| bs#j~(o"}y{{'(*y"{'+lsq2y&x'x)v'p q.lx!6g0hu-]g r&`~x.=:9>q,`|p1w:v;]y#PRte*k*m(f`{>j7q n^z~-y%rni3x&:}0v,5}1m$f~3jt*o'p,v6Wpi+q/{8l'~9y2Lg +|3v1<`[}] y9n+k+Hg5Rz[zg*E`NWw\}d"Z{r)p$dUu~/~*s~(is#2iq2h -z!e19kxi nun0>~,v$y'/sb )#2#+0xw%a|"y|&iplv!o fRxkcq!ZXc Yf c +i.ij^ f`eh_ev%t#_hw%cLj3WwBax({&` t"t*?e;a] `Sy +2p1|r`*tt"5]bogx"klfnhb`h@z+{*\ d(d :\Hf1P4U{Sx~6Cn(Dj +1TJk%1)K 0SOs^&d+]y$Rm HfOo%:^Tv(Tv)4Wj:LVxXy!j49_ Tr(,FF`>\Bbe:Ea"#"6 -K7,F*E4 #9_E_ FaRnYvBNI_ +ttEBm!KcQh +>S^~0Zsr"S_5/s{%LQ!%&(A@BMbOa DZMBi!?UvCT?ZTn`u 6:&wz 4Jy;TWPb8Y3KEbv+y/Gd.-J ^4I1A 3.(%6#,4 ;2"= +(?01DCV*2A!1 +. 5F&7O.!. ' 2ABR): J>On7Tj'>T6NE\ ]q%m|3AN'3,;&>P,, 2 +)1:VFO/.:]m?0&$6I`,,H7$ ;U'- 4!    + #% + +;*;(;%8!%4$  %/)72>%!. 0D"(90C,<  + +  .G]3Sl9:S{,y)nq!r!?6r#|.w(6{(/?J759eq =:ks!@z2v,l!M~1H}17;o$:}/t't*e}ku&v%4c+r {*v$nz'rs q!|)1t$|+sv!u x&Vyy%{$Cw%zw0ut|"y#.w#|&j37|+t$61njooinkp]h![j"\}t0>s.Ds/:n)r/t1c~"_|#d+a)Wya j&aRo +m#o!z(gilosmv$r%lboo ky,g^}[xB_}k!n&k%>`z{8b{x1{3z3x2Rl o(w1k%[|RtRun,u/p-j.IeB^ C|:KsW|_#j-]b$Z{8Ug(NPmWxXz\jgp$Z{m |*UuEqn^{ v$v#8#x|!u z(*7~%vl_z y%;..v${)0x#y"j#~!,0 )$_}1,q}'j-C|(o"u*_lr!cmW})s})hl*f fkZ[ t"ejg`Twfy%u!Vs`~JjY{ +mprx!dOv +|5U}Rz kt-wn ta mgedn ngjt!w#fh|)qk_Y} +[ +g.\\(qtwn)9/9b va}'0(RH0s/]t4r(-cin"o"t 4(3:NQpa y&h|&Z{'|=5~w 3#&,&<1w$|)E29rh8CD!op =rT|Mtct"npf^z]zb{.Oq5v})0r&g^l-|#,%xh g +yna] +bphe~(Wm\r <15x!NfTm_sk(c~z)H*jg,Do6z.+td d a} +Yu}#wPtp\Z|jbZz');_ZY_U}SzNq +Uw Xy X~halb{)s!`^`!NsFi =_:[0QJl Jk|,[}r8hg]z Tp /53`EjHnHnLqQwQv LpAdLo VzJofp(i'~B2ZHkk;8c8CfZ~!l,a X{A"FFhY{3OrMt\)d/De7S*HNm"St'Su%HkuJEg:&EGgIj"DHkKiD`6S/O?a\z5He&,UlCL`7yEd"'D"'1=P$&;zHVp#]x)Lf C]1Ks4Zrl+Plz.9.@m';TBZ\uD^,KQn%Ec>_ 8ZHe:R;Qbs!O`J^Th ]tbzp#l ?@Q+:1DBW 6y%{nr)# +'6I0?hyueq|'/8:jm ws5%NYRbF(vp@V +*>/.x8Zr?X%C_w#NgBV+m~cs&K]i } hyq~&jv EOlnIBO/ +?Zj'CaGb*E.H =W %, . + 0 $ %+ ,04 "<!."9 &= #7 2%6 +&"& ,0@"2C##('  + "(3H, + +%+! $ +"!1HW7 $ +*94E%&!/kM7TLe&4NPg&^w3Lf$8@\A[$5K))?(Me(I`Ld',Dy[7 8'> 1IeyN5 ?T?V,":?W>U!9Q%:N% '99J(-3C(9K(" )88F#=NRe#:I +2L`)':,?br*b'4+08#%/qzPY"7C 3B\lI 5L$ '. %6L"!7O#%;CX2 +  + ! +7F% J\41D)!1 + ! +  . *mQWgD  +   +   ,# 1E"$ "'&*BE`,":!"( , $%2 +    $- -#/) +0D)3I % 00',B%0(:'%?S)#1 g {xy{i uuums&o}|!|j lt&>x|"%>(z&)|$08*&/o~'uzx|~$x~}sl~qvpi +uwouu{"u}!yqmc`m$ay(s"qirqrkj(iv \i,u&)ztxvttt"(lz&s!0(*p't}x"!"{~yu#qfjx"y#x usop+ef_ z"(ot){ q~)kmz${%r)cx%qj4s{#)wvt|#|#q~$c h/t!lmr,2trvw{$opff[s)o#T{r!t ir"ma] oe}zk (|#}&qqrivnz!`(wh Ynulnp#()j,ues |)x&h0-hl^^r!gS_^Dqe_gv%s%`lz)g[jb dv#\ kaLww%ikw&gq#ks$jopiv qqs` +m[ t!ga[ajmUgeLz[ Z o!o ] tjklhkiqnovl|s|sg `TUNzPypj^og ${,}%slp:)w!u!S{{,o{&lv!}*goW~ `z+~.2jdfp"s'r&2}+qtfp`aw'ov$~+ly)?5fx)|-7p,w%t(vx%.2~.{-z-p 5?{"j,qgm--j6p!|.}0p#p#4nla{qis!nx"6mu"v%/npz"8})u#x%w#sto8s!x$*x}"~s"y.|s6y%|(~)6w$p f}-rt"w'ffjqv"ot)j#j%u.ae!y5r+q*q(q(v.k%du1o1e)v;_$Vzn-a p+|2v'y(j~*hgk51g}.gfs w'Qubefi!r(a~v0[w_y`yo)h o%x-}5p)n'q*l%_{Mk r5e'b [y|:i*-KweTzZZ~ hnphlik vl.k_ +^ls%EeOiAXjd{h m;TVn 88Ja3Kd,Ns kA(.@3Zz Tr `~Id9ojQxYJpAan (C,.ax$_ulfPw>b0RBdQs4VIk\~Xx]~nNYxrR+Vy4b[Kq] Pkh6Bb\}.?\"- ,Y%I :Y!b|L!8#$ $7) 8?VZq(,GLg3Q.LIi EaKfSlKeV_yHa3K-L>` +Tx9W\w'WoqJZ VjUl8QCx"lLgv$Zl!j|5_orp} j~ThEG]iVh +/@00E>V/l~~.w~ GUSiu":Ib@VWl \qt08RTn +s$1L=Xu0s-Aw)FOfnWq [x pwPg+\o ;N_u(eF]nH+@e/o.?\!;4N1I!;1 +3() +(6& +$/ -.#Ni;1K":*B -C3I2$   + #   +-  !  0!4) '4  "$ $  .4-H#>*F:R:T2LNj0-L(GeG;X$A[$4;TLc+>U5N'A %< &?4L>U#/ +(13I1G":&&<7R)4J4L%);1/#'7 +* 1 %8! /A$#/!"1 %5%4!2(: #"!->OBR"+  Vd&Vf%Sa2;J"Qd>Ðr]/ + 1/C8K0        $ %2 #"3%6**9(7-%2)4!   (*93L*"5 )8     #7#80) + #$; +Hd)5 + *% (,=!%(#4*   0C4L&4)>( & +%77J$0  ( *; 4 +>P##7$7 ' '90A7E# |!mowkj i qoj y!py!'zu|zy&qw2n'1~(+)%u'u*t$|"} ~!'%wuxy|k +~ n } |y/ht|wvtz xeU~e_`[xkqrg qedtz{"tXv7xt}us3vx{%&|!*pw#rmqoxyz!z{s$~rzu'` hgrrm}$xw l|'{&y&a x"2jlkts~,mjoy%mx"|'qx%e/v jut4xux~$zp,o7jlh3_{&jdnhrfW} bk bhh{+Ry_ku%ic zuqthzq#rlU}mklml(ang 0ohz"jikjtd b ]jieTyy#jlhak`h]bbg It`q'q"Ho\ nlq`fb z$q[x&o] s tmz%s!w&__W Y \ +hkofkmo`t"qZs!-] c^ay)x%^eY +cdw!gp^ +qgm] orjlovv"d +^ieio0notky ,qih_u#gPuSzy,Rw Sx t'OZz |.aa4`]}\z 5p!r o j7t(|0z,m+(z$z&9Z de20|+w)69w-9z.v'4otv vgx${(8{(3},u#J0n'1n}+0kv"y!)]x +t#3q"@z)x$s}(~(y"((qrxuiTevqz ?|*/p1jiG=b/s~r q wzz8)-{%omt z&nnls#t#kp!ds"hz&ps%Abq*w-Y|]`\zek"s)y0k#l'g!]{Z{Y{Hm Jo f&V{\Z|aoclef[~*rY~q` ` kp[[Y|Ru a;`ZwZwt1d!Fj q$kMd|v.w/o(}6WuXxd$i$g$;z;Zy]|n/Tt?d(LOq>^ SrVsi2RmRjw6y8[{Qs\h!j!^UzY{w/iodhx'o.m}%1}$omw!//A1-x$1z(3sy#-q{#-&|%,{#te xw"5d 3z '4p*v#~.q"u"sy#c 2r `cnp!gu$bt hfkaao$c_0hwi o{w1kf_ +kT}^ h\ _hGn5`|ruppgt#hT{aX gdbdUzanq s#w$4oB{.u XQxw"/jx,-uvtc| sjhr18Q"F5}%r~(u"`~r'Mj n*BYE/(t')d=b +t3Mq @eCgx,1rZ}hw+-@)RwgjKqElGm +`7T .K!=GcGg<[ Op5X +9]Uz2XJq&p@^|/Kh` ++Eh{FKl*FxB>`Kk:V>U%>(fR^:HmlHIj-cGIi):V % 3H?O)1BC\3M)E<[.OVvc'f(]uHaXsGbYs=V CZ'84Q[{/[~ Xz4R?u+l`t89Os/Jd0Mz+n'VhXH^h{>Q"%-h~ctRZk`HV bt'`s +[p)|'ns!"pu[d +R,%)7,  + #  +   "   +.>"': & %7J%$9  )2B'1G!AY&Wt90L&C)F4O <.K/K?^%? ,J1O2NC\Mh*4L,12$$8)>EW1#)1 +''CY*"84?X"<B\*3L 8=VRi7&!3 +1 +#) +"1 + +&< #4K"#$4 $+ +Rd@5I)- -/@ -Te9Rg-Tg9.$,?Tf&Nb%6 $esG' )9 3D# (s` 7 +%;/ # +  +#4%  +         ##3. %6 &4B""/*%}+phx'49-vo/r,{&05|'})9w y!}$tqy"q/~(x%/+b r~-{)9n{#*{!}#x~&7|+w$k1s{&t"j`hp#x*gQxJsmglmchgnadkzzxpi wa t 3nYiu 5y)ei0dsuvq jcfdcV} z*`hmnt V{aNpckmk%=_@ea8y!B+$qqpa +j 55fLlx"@\rz#/&+0Wo`{o|RlSl]ssE8?$Zq+H9#%81jo.++5+gQh:PVo |)mPzDJl&( #qqk\v eZs `zVo v-q&y+r|&4b}jQoVygSu JlOq Tt Zwt#mgn| Q%cp%enfe}/m&C1QlMg HbRlr)1F?Y +*A?U @X_~.;~#Uj+wy+:PCY Kb Jcd~ETr\{f@e4YNs?w0=FLoJmEfl5MoVza,FhHhXw/7VFeHe Gc=W8SA_8CdpBn>_-Gl(:_Ab0Na"V$\n@K[-Pi=Z Zw"DfCdMnFd _~r/a|If=Y;ULf:S[s7\x$Hh Gj CaQjA[{+oYnDZ\t1L<~6'5H\K[!4Qrng| 65'M!4=.Yk|(eyaj"bJW *=WoB_ Jk~6c} ?Sk1DG]=UB`Hi PmTiD]YqFNj Mj a}So p$F9b{|*w!2EZv)@.Fi%kQkZ]q$&&>1 *%& +)!# $1 3 :2)@%9 +<   +&'   1>$  " #"3& +0F"%4L0H=P! 5 -WlG 7 +CJe*Mh)c #; 6P0-E 4Wo>-C%$4-(/6P#XnC62J4M2J2Q*E :-I0M :Q +) % -*)72J"< C](-F( " ($((#4/@*GZ5 3I1,G](Sk)Um+'AXo#7J+H\OeqM7KkHHa&+G '2 *?+@!';'3F1%  + &% +  %!$(8"0 $5 #*8EY/;O&-A)>3 56@XJb+HX2     %55H !AW+ 3(/=#&- '.4L9D% +4  , + + +    +  #/#2("0@ >T$w/{04`jv&ko ~+z%lA7|%o*q}&x"sx$qklx"+43p"5r"x$fsir,kuu# qz$sw~$~#%~'sw"rj55y+y)qt!s c lda ts nlfr|$z$,-` +i{'j] +dp~.map#_[ea]Ntj$o(a_gfhgc_d WZdl*] TzSyLr _!HnYU| ZhV} +QwYX]Ovf^SzSzX~ w*V~ Zx3X|VzY}VxSt Zzq+l%w*lliov*r$z.a|n#m'f Tt_8n#y0Z{\|`q+j,_}"k-f'\xe%:TOk^zdk"_[zq*[s'V~f"Rt o)3g.s"rs$q!y'{(|&8636/Bjp~*.8ut(o,}&{&s@g.z#my'D1{+b~}(-smxqy%6r!iaigY kq'_am#IohPxLtmafdp$h~(qp\d { { &ywCDv$[ m#{1nW}@loy"X|d%&w%o ]ghcXBtme jdt fOArj+lnT|p o ;|$m3L.Su] g0#%n(|"-_u^t f|_t ~*e~}+:fH`B[RZb{^t$1f} ~+IKc| 4<;&x:Zf j*}'u#Yt PiF^^xq.1(z)Ojk{%,1 ")/!}"x#w$1or p![w /~&ltmgkfUuUvSuhn{){%/:}"g#8-lqclz-n$6VAn}!Gq%f#To)@DYVn}6hGS.?QUeDVegyYpj+.GR_CUNd Tj{} anmy kzg=O@Z]x@,~%(@$@V MRnB_?U\s(_u$XoD[ [wKh`}PoA]Sl +WoZsk4 +6ZpH ':+C b|A'E Ok5l; 6 )-H--G?W($>-G /H&=(. %2 ,@ %@Z."=*G0J$>3M0L,H1LJi!&E;@X&! K]7+ ! ('C +6 %* #:!5#6$;1 !7(/-.)!2 )/1Mb.6'@=U'?3Lj?U5:TIe'gQl4$>4 .DX1,A " IaA +  + + !   *&2! + +0A$9 6.)&:.@#5&&@T-)?&</C%/>(8053>T)  &8Lb:DX.'=& .7#  )( +3H! (9-D59Zp<.#=L+ + +       +   *8"+;1D?P%6E! HeFo"nqihk{+w(r"t$v$qn.any"z!'7oo!i[} y-fdSvim!_ rpwu{!q/wvv}s{p"!ulpzrqoyqtst(mqxni5Sxfz*^ fcekdu}!wp%.mxsvrmolyppkp}#f iukrc r`l ""} n zw_n%tkb +kd `nsnnukt lhx!vqptb c}*pa}(gtjx$rjppv v#z| )*{!isqv"kqnerxqoa`oh-aht$mYm"aw+o[w"fpmojyqxu"{nm`lj+n[~*q jq`] llu$o[ +rmhud59lxoXdp"mb bW;m_UM}[ahfblx s=imj,,lncd^p$n!fo"t&o p!ccbM{V^Tu#lng,}#qro(emX\^:gT l_l!WYQy +Goi S~ _p%Blo"`Xo]oXu%]Fre\X~o"] Z +OxfW~NtY hfgd7r([dj9r%u+t)//y-Bl";y,{/m u'u&80|(1o3w"/w'r)X~b^chPo#b;wPGg#/Q @fTx-_7c8'J]|15R 7U f;~PCc7P,#!7N_!uCK $=X$5 +C]'/J!8K`/BWf|-*EYt 8S.G9R Hc1M5R=\'FJiXv%2PDaUq r7kUoJe 8U?\7#>>WTyd Hc^w;SSl@XWn :M6VCi}1_w[w y`j7!;DLY k#.}$z'nvx~<#dw^uOHwy"+b~"7E[*@F]&Qh'3Ka s3~WWpf#d#Zv7P+B4MA] +^tJcVrWq^x.\t9Se'3 P]2#,C%1 +" / "1"64*<, + +"1#2&$3% + *:J5(!  $#; 5Ga2eUyda~G_|A.J Ql19U,!< .2 (5;V"Lf4)F8+#6;N1( .?R/?U'+5 ),%B2K #=/K3P)Ao[`M|Yt&Lws(JqZOu ;`h&Oz gKwHsg g[Rz^ey/l!NtT| ldV o$bagi\w&h1kl!.r#n$57q%q$n!gn/},}*,C0.})5{-h"b[6t&t&Z{<8v+q&h aq'k!t+Z}n'{4^}r'|.y%w"5z%trdVjbp2v".y&3{$~+s$@4l7w#k{),{)}'x!owp+tz!sh *''1hc tl1},st 7Z| +do#do w(}-Mqf` q,\~pnnklgdmq!p"m w+hi#h u-adi"ao._;[|UwXwDej,v7e#Rx `Uzn)i'Lq RvX~Kvr5NvT{8HnW} FkEjQt ^dSy Pv _`V{bbbl"cU{g`Uy Or{/k+niv}&1\w fu)gZ{j#Z~V{s)3mv'n e|3i!t+gcz._[}u0v*m Y{ gn [| 8^Z`m$?u(a~u%y%3s$}-9q0z'px&4;8pnv''|{&'&pw%>w$|&~#h/a| 8x*3g{(ggkZ|{%8}1ahx+^~Ru `HmIn +Kp +QtUzJmacOu[gt)al ^qsv!RyU}mq li{)qr"c1`Z iy(n4ww\%o_[ w(\cCw+=>%t][k_y#*w!mZqt"X +lnz'}'=Q*Yw bz#)|B]0%;!`}D`rs"t jw"%E5hp|%54a{]z]zLi b~x;Ct Li]xDCQ-inr)XoMh +@\b{!SlUqOjg.DH`N:kofx$u &A({+yu~%im~${ |2C ,'8i h l5%:59x%0Hr6rwPopPphdAeJR:Jf+D>U'=>V?S/D7O:U9WSnE^K-6ft)J!oD_y%z 3RtCYUv92$Wop {d~ +A:;YWx04[{8%}gZi[}RuBg`!@n$l^`]ib_Lo@dQv p"aZ|o-Jd*e.)NHkW{.D_&3(D 2P + ==[=`Ln"On%;8W1P+KMj+9P(@.K">Qn.+I .M <]-N#C@3TOn%3R DdUr+Jh! >5T6T3P6T#D/T Mp#zO1S1Q %C C_;.O;X4PRl+F^sk~Sgz4]t>Kc(D[$E[&!94 3J)A Vo"j.r1Om:WA]0H4M OjHdIfn9=^ Nl9U +^u1.E6PNf H_@R5I.D*AA]Tq`)Vu=z$7Le$<^x{+-"s>ZmoC]_wZq0~} +6P^UjKdQl,A0Df~r fws 4Zne} +Nf;_xrZs\r"JbG`!:Mc$[o*k,G^)A+?_u!Pi3N*D-HEa q7Nd>ULf 9P'?Pif} c{]s h3]pn} dm9B #  +% &6,<") # 5F& +  # +*, ;O(3 -@"/'2G#*!8 %?!8";,G 89,J0K$=8/ ,-6#:.DBZ,)>&8 /- +4 /*A1K &-65.G4K'A $Lc-- +"%,, +9O1) 4/.F$.H"7'E+FQk@9O'.A0F"/E2G 5 $ !1 ,37< 4N%?T0 404 *D0I5 &0";!<%; !" +'0 (>2J(A (A --CDX,1C+@ 2+BAW".G.G/ 3$8!7( '$ 1 +%: +Ld1FW1# +   )1 /(+= 1 % J[7& +! ) (&  )$6 5=T&?Y&Wq50O +.J5\s9Sd< &8*=(: MW?')%5/ >W(+4Q+I $DHh".L"8   "+,  +     +   nf1midr mr#dt$hny)6s gx).qyonffedjjOux+`r c }#sg=bmu%'{,vxs#vorvuve z| w} #ri {oe ly!wumchy&ft!x#irnd|)v"eswg rw!g$lz!_~#r"qxxss~cvvu{|#xx~ ~ni n!qxypmnz sww{!'%rnll}%q2o|"-uu!q!r%n!n!s%|)pun$}"po|&ov!d-ulsufxc w!p_ im~)x#i-u2n*r+[jmz$i'{moq!qidbrsz(] ^o m0nfcv#fu kYy&q!]b\nz g +vz oz#[qox%hc4v"hx)ilhem~&j~-w'mci`_k VR\ZWTg#[WFnRy R| fm!r"fhcfk}(rb-mtf bR +]ZS [\UgX +Gm@fn+y4[HsRz +k Rynr` +k^Zo En[R{[bd~.hfaQx^a{(\ X `]s!pWhn,}+gs%{+ems%{,ljt$ly&v$07y(5nvw"v%s':i,Z||3F4fr&x+u+gj"m!p"n!g`n*r*Ay:,{$z%b no)y"z#wpleu$d]r%7p dDph+ov$iofnr efqp"av.dr,g"k&\z6p+]c ^f!Vz]WzMo{?t8^!i(Jn\Z~u1PrPsOuCi a*QvGlTy Vz n_IlOpPs k*X|Mr +dn)Rw Sv Vy Uz +Ty ZX~br'fjk~*lojc |(t$x&u'hfh"Rw ^\X~ }*ddNtUy +x,aq p"nh^eb{,v(ea~/dkn ^gcE|.w'gu$5ov$b|u3?6t!w#ow%x%-|$$0;9r}(q ?5j24(~%6x(3c}x'lqg^} AK5j5}1t(ck&Rw@ce%e%^MqQu p*eddRty.n ihz'z&U{^ i`[ +fiu!.0q$fSzu'w36PPlB`Dd(Eb ;TSmax\sSmE^Wtdhmx"|!jr(>gwrrs%rz*ld +f:R$+2w%Xs`|LmEs,-P;j'Lf-!9:O8Q]Cbo>#@Sm!Fb6QFb1Q Om%80F)<Wn*?V LcVgu*GYl5KGb]x-*G;'Awfy$7OUl'Ea_yu#m!+y!5PPm8RYn]mZ2w *l}Weq%t,YoZow.g}670c}_yQid{'Vo3 $ BZ.Faz!Do)Kf!:{>?ZRd\o!{:6NOd +e}'Qg_u$(-z)*2!* +' $ +( + +.#  +)9.(     !1(=1.";a{K + )>-B.$2%< 1 %\uD!<7T"!=.51 *!9'> 8 ;U+(A3L"6 *D 8 &);-)'3H'!;0/E&=-EJe042H'8 +;P*0E0G*C.H%& $/F(!;6 ?4( $'' +{e* +,?2 "/:92!8 /D.IOl3: &/" (7M"(A .G !+   -!8,A + !6 +(@(@!+CS+31G5L2H6Md( 2 +) +)<%0'2*C3;R!7  + 0?+# #  +  $4+ +- *'  '!7 6 +=V#,H":0  %5/@"- EZ. +8J!)9  " #0&A >[Ki%Ga)-B  +!)* %3  /<*   +    +jb s*-k/aov&y*lq w%p8Qoq })z 'u|%qv)i2_{,`q"r#s y$a)/muz!xxrw{tqmkxmpmq-1qxw[smpufPwrmuiqe^1fes!Pxebbf] dus"mgppqbtQyk r|p l +$|'w}zq~w z!zrrtpm y!o jvq{uyn} vzu~ {swo&z!-|%uku-|%v$u&iq"mq-ur{!xy"my#mqv x!svuq.utusms x#)on0{"wx")qseslvnxo0vw!g y#`\z+6q mlj5iii;x%b y$f] +iX eU\\wqf +mgp6|)t#iZ Hwou!fcR~e]]cns!midV `_q"]GtIweS}Qx S}dGm[{5aJshf^ ie] ha] ] Ryi] c +c lS2aQ} dc` ^ x%iy(|0`d9[Y lOv+omgm]w%mp"Z v%ont"ncbAiQxn!t!.s$Y_lu!ds4xy"|*t#x%s!p s!~+w%u#qju!or!t"1o~%(vz"p{0x/r+|2@2o"fz)o cx,r%is"js)l%eu+N|/x'y)Gx)/l_d\_z-ffs%`c61t$p$z.{+ohiz'8r)2x*h']6(--|#0*kplqko}!nRvlg] nh,d +ob az'|&Ci;)~$kw$k00(3Bn v`|mp[t3jr1'{!I;Yq5KLe>M-md~Ph{,J;Vo/ophSl cz!G^ +f{+Wn*B UnLf _{r&]B[Rj dXsNkFcWs4Q8U4RRo[w[y^|;U@`9YXwZyKhJfb}1h jrxmq{"|!)ksqpy!o'qgy"t${,n v09Nt27N}^[{7:\2R =Y4P uPIe&9%< %zD[x] Om+O(K*M.M3S Bd/LGdFd1N .G@WDY7K.4O-B5Kev!Qr)(@8P(B6Xp=ShBWd|F_G`A[u&s4!v$7o-PiPf6I;M5{#5z'x*MlKf BY=THaPem1AYWoBZo7OhB{xKtD}:h&^xZs6Pd!Fz6So2M-G4IAYi3l*IZ4ES"(:3I0G[t*Oi5O',bom;n}XGT3   & +    '  + +   !  + . +A\*22'E #@"< 8!7 48 +&/ 39(B 2,F(> +G-8S%#=%>.2 + ;)C6-&?(B.D2 1,=#?S40F!  & (4 <*C8 9P!5K&A6R#)!5*+ 1 6K,!6#  $&S.  + # !7K'3 6 !:$=*(4 +Tp6>_ &"; ! ) 6mR  + *:!4G' "' ,E&A&=72KD['$< ,:T3!:Xn9_tA2B?M&`nCe{E- "6 +&  */J7=U"0.?*6) +3C"      +* )!3 #% *:0 + &DV2WfE (;&=.|&`jio~'z$b| ru%t$qqx!x r04~ys|2{#l.m@1m?~%v)vuri8m b59t s0.|*oomt(q*Lk}WId^yn%GgJh 7SJ8V +d"7R:RWq-:P !6`w!g-/J72O_} f7:eg g k0r$[wE^r&>v$v rOKnUuo(lJVwmDj;,( (u|cJkmMraY|al(s5t:RvSu^Z{(Km Yx >YUu o?Lp!=bX|+<^ d82T)N8Z%C 4Q*F <Ba'#D4S=\ /2H/#< +&C Us:: #?*Ss2 @6T,E Rn&Mk"Tr+31G4FEX'1%> )A 5Q-N;a8\5T)D +HfAay<7P,#:S&?NbHc} 7h iWp~<>TjMiq9Vy Y}#Dc ?aKnDgBe`2Ed;[Vu/@\ +%"Qe/M_"0E.G0M 2Vl'FWWkCMd]v"Qh?Z Sk;R:OLV+y)UoE`Hc6r0:1K9SKc+?4K?TSm_wg\xRnJe_t?S +K`2KLb[r,k#9(=`x/!>3LOdi~:ݓ2NHePn^zTmQiJbTme-=Z7U"B2M,F1I>X [r0*_9L:7THQjMe-I0Or042z$CU +?Vi{:qVpIX,+*/'   '" %87O%2 +(_}>Cf2Q$B ,2K6 7 $?#< */-;!< $4M$? 13K;V$"< 4/J?]*8S#@\-"; %2/E/ . 5M*1 +5/#>)B + "8P$ 72 0 #"+ ) 0(<" 4) %Id=$=">$=?T7 + '  + +!!5%*@&:. + +%=Z0O8 9 -(,2J/G)A,Xh= & " + "-$:1 4H 8 9P!:L +%9 ';7P/I  /5@"3 Md1?U&6H(1(3,15L 2G/D"'76I4E.>#47NF^%[t71I1H "<'< "(7* +  ''*<& " +N\D6L&%8+>       +    mm,x!}+sorx%o}&j$+(t u+y+pTq(~"ys8z!s.v #!$sp/}'w frie sq}"o,kT~}/imned1hz#z#jht!})tonx#z%am_Ms~-komeb9sb +v{y }%zqpvs~#yt%zv}~p |qy(#)z}|%ruum n f oq}mz rpk e +W$rjwsr*mkdnjx&w#xqVzmedx&eegv"y#(i'f{ r}$quy#f5(4et#n.gv.rk|(kus|'v~'pem|%dx(s#iv"knpmw'lw$v!haRyk\ `~-0p x&popi_ jc{.]X~JrZ\Y dcLu^p"]l!q#_}1x)w*q%m#:z/je7do$[}l#v,^zn'fk!q*o'ax-ibj"c g#j%d"[{l.j+e%x4gik"j&Tr9Zj,KmVw -%a~ohu'Pu +j(Gk Ei a"m*SxbLs eU ^X~RxNri&]]~oitlXxl0w!ojW} jp"X +[ tlrRyT{]r!ou{)hny)mppnu$Klfuv)hnv#o?a v%z!ln_} t"v'y,{/kv",s+)&/9wzw6|&p.42c} s-q(nxnr#t'`{'0.:1x#|&~(+,L`9=b4RH_+,?BZ%Sn19WMk/1*Ft"4#t07|'kb~WpQmMkNp?b3RLj*-M A*MASr)*H 1OB\%!7+>'_u0iE%A +>XuI*,B\u>&*wm1G?Y:V=Se q:J:WlYjVg$P]20? / 1$  & - &/'F ?]#D)H 5R$; 4 +&@"<Mi3/3R ?@^' (& Kd%>W@YKd+)A=X)Hb31K;Y(9T$9 21 +"$Nb8vd*A2I$";4";!:3N&5)=&71&+ !( % +(7(  +1C'5!47L&i}V +(:*B5L*RlE3  +    + + '$).+F < -Wn>0&6',.C+= ,= M^.4BO\4*9 / ) +&9>Q-*=!4 '*< -/ +-D%9 5 EY8   5K&Kb1I`24K)(h+GD\)[s8-I Hb"#+*C &- +*-& + $9-)?.DW&Sc@#%->%8,@+/  +! 0A! '<$=6!=.G ,8OFY% + (#5)   +%2     + +       + ns)i +w%p{!tr)4pv&$|!ry!$i } +y$&}%3%~!wmk0(v{"vulttn|$rmX +r&m!gcbfj0nqvlqomms m+ut!nms ilf{(c~+9jf2` ens1`o]VX~uq|!{ {hzw }-~){~~#rpmslqyT~mmb +me +vl yl oquswtj pop||!nw"mswv{$g] o hd\ dk/r nx!~"~mwt~%oqlp2m5bc}+z'qnmejo^ v#riokdfsx#pr ok.j.l6u&^{ s 9kh_x)dqt!ooy'2b geifOtVz Vz Qw a@mRbidZ m(YzYxWzu-Rv Uzo%a`n0a]'nX cgkfV} +lv*e`phvp| qsqo|!rc x%[-lZx#t"Zika qc tkpps%ihz$if0Yu!] rs|'a pv t~#tws|(o~,kdy&halcjrrb hsy%mCs#3t%q"0ia_ g\ caf`` nj {knpis#kjdjz#ji_ r!si`9kGp6usjnirsfp^ fY _]^r'egn!=<(v"jloj-mphq l~*60PwQz`)g +vuowg ic +_ijs**Uw_~m#f36}3t'm2v#}(~'-|yvxtw 8{&|(|'+V-FSnWw +Dgsr%sy"ZwTp>WB[ `{+^y(TpKg E`Je ;X`}#0Mu.So.#m a#6ir)5O6y1l'v4[u]x9bz Smn's$t$Je8QOe +{6s3@\(A%?">0L:W4%<:O&?":2H )?+BBXVo&Wp$-G>[Kj Qq 7s%0,aa%,).kUu`t!`~ l~%pp|ww{!x!~#mqm]xe~fusXrVp }0w(lhOiQKm2bz(Qi`w(/AZ3JbQiQi:Q +Nf^z.A_a*ډ&A2K)E%=T Ha~_|7CbXx?q_x +Rl*p\zzs)5O@X?Ye}ov&]uF`qSnSo =l_{ @_]}aqWI <+ 5.s # 5v%UyW{_;_SwOrFi 6U?\ 7UfHj~7^y93#@/L'F (F +a8/N 8X4Z@^& ('-#<5*59 +2 !0 2 8U!?^ 5(/\y=Ff~M@Z'AV_3Ps'3F`%8&C5 +@Y'D_)9Y7Y^8@\0+/ %E Wy7:5.k-jNkYvOi4XtGcYrD]C`Hf 2R !A4N:9&E0P/N =&B3?\# $Oi/Ldaz"Ri13941Kkp+6cSjZp+BU 9OWl=R#9DY m%Jf[u]sRg]x@Z*=;P^u$}=To9TE^ ^yd}u2*Bi$~%vq%Fao-BYzU'6 hY*ERnB!?W),ClSSh9/ 4 0 &0 +,0G#$9!7=S- +&& ( "& '.*   & (E'@;P. )7! 0H'>6 '6P";7 + !( $#%5,<)8(LY.K\/%W" ''FRs*Gf(;*E Hc2"&#) (3C[. $ %6.,%-/. +"# +'!3"7M6P5O!<*?Od0(:gwU)  .=#  ,7+ ;J(-7#     +   +ei0w &z_~.lsjy"s{||xd~'+uw$%+*z#o5wf w lvvx&onnlrrwW}kiy+n nqonjsx"pms{"y{ rjv!sp|*qmy#ohhpphg` +^ j] b kd \c eVb sinjlwm ~vyws)rto t]rryofnkvzngz'nmxp| qms}"vtwyp#r{kt/tw{ }sz"h] [ u#knrr2z(up|"sk}%nou+lu u y&ct&lz'i}+W{\b_kjY +u$fnz+z(_ nqtqhw%qs1ll!l8|)py(Vwifnf2p!X +\ +w#Xd~0X s(_Rxv'},Pzhx"qeY j"Yz\}ZzkVuHjZ} p os X~^ -w 4s"Z +w#qmU|\ T|hktly ruo1/oi mlR|dvrmw |'^ b pib qqw!v#s#p!|+y&gofdidX +S|Wz%bkob {"`oV~y%hgcfby&qjaz(_ cb Q{nr"`-ajr%x*bhs` W~rae] Wyy&oi t|h\tb jqmoeddjV}ll^pj}'S{3lyViYh.jpi.e_gft'n"W| ^_ripknjy%i}**oehoz&Ag|-_p,(wtormc feoy#)w"Zz +x+k!hz,y)Olq&:w,7z,y)ow);8t(ijjY /fy&mu"w$u$ggm!\ [dab|.a|+y(_Wy s(aftz y\q!^^o u$+z}#tdt"`c[PvQz U} p(AgQw [(stm4}'mv%s#u)o%y0z/nx'}+jz"'yvjtqox#m60*-3uxqm*jbenk.o/+r#*v~&u!sk] Wwv(gWx +An"gl!j!x,StE=})v.Yvp0\|z3RuLpKnWz a?}3{.x,hmct%do Gl>_Ols9Qnt-XzKpx-r%iw6Gd3+FB]r%Z| Wzeoa5<\JK]u(+F20LQmTo?P6RPlp2n&o%Uo9l ,kp ^yTnSmo,?] ]y$s;Tm^v+~t!)%m #+ji|"hd n1wt'wz(|$z (/7sv{&y#h}&ZYq`xt,x4i&D]D[F^5M4L`|/O`|0_z+PiDE] ,k8NkNm0MNj)C8Q,F 4a8l:Yo%9d]~A]GcUp/5#k%Vol.K;>XMeYr Tr$@Ga +[t})Bf n`i>(*U|j$iqu)wvo a d]\aW{Ad6X>\2NQn|6fj%a+F ,H(D: =)F +,K@]%&C'%1*@ $4 %@AV0$7 %Rl:C_%Hd1.53QPq#j> '7Q3T LmPr"7W$? >Y#vZDd'44N4N"@<[Ml(cD .EjdC(C:R'E`XxXxD_@X}5?XBXLaNgNkI^WmJb v;s1E`;WEaWr(+C=U\s":5NXpAVUlQjIa6O9M)<"2pIdu5ZG\Kb(^w<3K0H 5";3)/A >S'=44*C <3R +@^)G2P!= /(Tl)3NhJdKaN.[q +Xm9O@Ibo>~ |!Qi|:Tg'&5 8O%%=*@1J!( "BT8!# +' =P-4G$3J0 2 .B0!11*0 .I2QA];U9O"6-&; )> '=#:' $<4 !8+6L!,C  "%* "9!7*C +0 "' $ +#3+ +   ( + +B[97H0\qRF[<! !-+92C$!6BY&4P6M 84#;+0 + "2;"'1)    *  (7Wg57N0%= *D9 J`5E^2+@ ">Z)eB-.I?X)? +Ga+CY'- !7-EZv6Vv11R :`'G'D $!  + DV7, "8*  $0' /)7 8P@X'3(*$ !7G, + \o4_u-Ld6NQk;O 4&7(*;;J"% +$5+ 1 ET3   +   +     k)9vx ziudgpw$m,{{ vq x~,;/y"uwz)or mvx9cnh rup{ ~%0ebikr_ +f}'h.kx w ~%ps4qz$#7qp+&w)}"epoimXv c\/{!{$rtv!pulz%nlx{yq"pj gW~jf +l din `q|yqhukbh (kmdfc tb^]b ok r"ofr!jmi{%t%&|tkz%dd/l*{!}'s/nj(kb 8shr)dq3t] {(u#-adV{ +aTxU{ e\ +Rziz(x,o$2irly%g/s&v$|)=b}t)^vz+l;5ep#`p&Yw~1n!r"gkX t"p!]^Z~ ba lfwnnsa Z} Behp 22@w&ma1U{ `m,{'t#ortz&mv$7dn{&v25yxvsz`{c f*qypkpir[_ qlohw%Y s#kpW] +\ _X +RzJqW~ {*Z r ipx"k$~%ow$w$ncw#u!{)t"edih})YWmV~ +` nnn~0na 3hmnx$] +\ x(n_ g`qddnnt_gf yc dlQy_u$lhf +me +i !`s{!+s&mkkb jfa`m5x)\W} \t!opy!qtw uv8|(npfsz&bx&[ +fw r3#pksft!bs"3u"y&z*v'w,[w5o!ip&m"s(~0}.u!|&y%~0?gfj0bjg%xp|$|&ljv)y$h klcrg0ojRshkmt$uvpz%}%oxA||#gjen\ X MvS| +bIqTz +`i aKrSzd}7j#eehXy Xy +jY{**qsa Msikv!d mU}gX~Sw`lewj/q lp&~5r'Nlly'Qpz&h[z /2jv+(ug *mllo"73)#&n}(o[ +filjCp+%"/((z!lw3gbnu'l";]}o"p#p'hp+vkNh&?Fc Kj +Ji^8DWy Dd;Z?^@`=]Yyn"ft-V{ Ci/TMp:Yt6k=DUv gffFi +':MiPo +pBL5*ACt,e$He e|*RnIdTrN=A\l)u5ZwPmWtg$Tn +l14u UsTp l&JGc +h+,IKfNg#I`h}2Lh}4:NXE,C;Q Sj.CPh RmWt'+D0F +'9/B +9M*!8$AQl1*#9 :M"!:-E,m2Ii BUs;Vn&Qv'kj 1AyYq}#x%~$)0twt$j}&myz xqyvl0b} c}Zs +d} YqA[7OOhG_Zs)e1_u*;V A^h5JhNCLRj =W5Ra!b"_{ B[ .F,GNf w8=fNRp +TZ}G?:Yd.L:2d}b{ uq;[v*Cj9YA`b!d(ESoa{Qso}&GkVvk&Y{k=(1a{s {GjLn^cZ~GhRsmz/!=i'=[b*o*/E+50K3&A +=&E0 %$ +,1 +0E(& >Q1>W&(B !']z:>^AaLd#2IKh#Hg?_Us,-F 8--L.-+'@ +3Q9X;X1(?;R!F`.;X0N 2 + (Ed/AcLi#>]w\v4IF[ Uk7Q;X6U '@4%A'D !B8Z*M2;=V8S 4O IiNm8Uk9Kf%B`%=:T+>:OQ \s$G`Tj+45H3H5K Is4MWt!.KMi1KG^WnQPm8S`{SjTn q(^v6J!,$]+A_0IYp3e7b~5Jf,Qj08=M4H&9)=4#:73P0Fa!7Q4M"<7&= +&?Tq)?Ye~!CYZry${24KGa >U6R?YD^Rnc|a{)H\*q{B[f)[ *1 ! +9 3N#/ # !34H+$7' *! +&6J&2 )F/F(%5 ):),-AT.+ ". ."7%9. -%4G.6)+% ) ./1!& & & $    +   +$       + # 7L, 9-J/4, $*/ +;S$&= +  &3 +-7%   + +9B01GT= "-?/0K$?0MqQ5%9'<.Uo4=^Op DfB` .BJc/)A +_u7)A*C *40SFk(Vy64N  &=Q1/FG`.L`9(;2A)  $#&(&?8S7/I2I&9R^x8wR`s>E['_u,NcLbry!sn{jugmz .i+x |"rXdqvpsjtpipjx$r&x{qz%lee i>&dtx!pry\~{qij`[c xg j i {r)iZ}p~'c vrln`~ egfgY1m` l h wpvpvnqw-3$/"$zppz h %#ys3uz$+u!i}*eq!n-Y|3c dtt sa^ +qpX~ ZTz kcgx)t$nnfVvo 72mw'lew+u(1x(kkm{(u%o!q'x.w-s(|0z.o#w)jkcw%ag|,hfojg2m|":-}*~.1|&1z$r)nr\ Y~ 2mt!,rg.|(y%9reonpqdl_h ykyxsw`d c gmQm!e^ bjkciKus"\ +\fR}ib\ Q}[v-p$Szx+`^ r!gigk,lebej[ t"j^ i\ +33r!z-jmqmdq`\ w%^ gYr#b_p#n cc e i k wrmee de ja ]W[bb\jf h j rj i ti%pleqinlQ[bj}.am`Uz{'juyooz#y }'+vqodlos!ZdCy!y#5,p(y'u$1m!6.ju$-y*s#f~/|/Qq$h3|,}+{)qqz"|(u$jr 2u"or/&'{$y#f{)/slj%u6qx|#[k>nx w||t} %~$um uzwyk +Dsp^cbu#4u)Vhm9Oum(l$fOqSw Mpo&Tw mhukz+z"] +[ Pxbf\la +y%i`n"fW|a j~(v%x(jRn ~3\v w%qUtlZ|`},4[wp~%~"qmBn96h|.s'^ Oqi-i?Bjo0t!@dJm Nm OmQnC`c~%Fd u8m'TqKg Qpc#QnG9Xs_xVq;cq*[wFSom.\w$Pm&B\oA7O5M5I +G[(!4&8M`[o&:O-@W2J4I /HF]{FM7T8'?B[]x\{>s d~.zM'qy3xrm.q{#t#i_y y|r j lnp)*7r"GdWu^{ MiPj$?+FYs.Kc/-H8T 1L1BFMg8TLk?`n/qYu"+'<9L,AWRobyYz)^ +CWs,H8S.>.I_kjph =XIv-[zj"|-JH&ABCDiMs +t*QrSruWvo.rKkOoa+zgIj\ %JmXz +z$b jMn +@`8'AFdLi8S#>7S"? +3P\zF4M5P0K3P#B,EayF|2H6O*G 3Pb{I  =ZFc=Z $>Qi>V-C7LI__y(\x'Ol?^(D7R"=$@"@Gf))G +Hh#8X.L +yCFf;\=_Tr1MD]5+1->  +Vm0Wq-p=_z-Qk!1#6+>Og%5M 2"7bv;0/B +{N$<+F,G:T Lf3MB[=VTmJdWr3Nq'a{Ojo^1D+ ;KI\f|0)@%)BIb&;T0@X!:@XxW': +#; .C3K )8Pn3+H 0H+3M,H 8P6Tl"?c}Xp5Nj.e,X)C,H2M7U Lhn/f,=T -6R`.3<"':: %6 " %$10%;4 +#0 )E]+F[!&'0)>0 (0 2 4 ";6%6.& +  4(@$TpJ;U."<6 +I%B&B 'D +eDlL>T'/G(>)C`~/a+>{l / 6Jg~6]r/6QNj4A]'Ok2Y{+-}){$jy+u&v*A4P9},|,08z)hu*}1C_zu)hgGkps{!k|)cn.kq$uxwo~)y%tqijjod!Y~v{!w umn}!{!sc trztw"q s &q 0wx"liqp+ferw$^ +Tza^8Z}SwZm!Sxhqg +n {t.Hpv+T} _Z 1ax&|*Y| +ccnv!w!m2mZu Vq|1imp\{aVx _gs \wz#ux&qv"x&pAx%x(i~9p w*x*r!knf1e2l~-x%m/1z&~*jw z!0p0ew$c[ _Np6$($xw!lw{!sk1s qz%sz(z&{',39jeg^ p!;ei%[U{QxW|k!om.^}n(j%aa~Xwo.Be2VEiVu]|'Jl w1Y|Wz8YNmYxNnHkYz$h2q4Qt \dZkbkSx j%Z}[{b#DbNZ<[,LA_Tph*z;If Kf_{>>q4ZvZwn/Pj]wSm-H,J.JD_^w#I`H_*?I[Yk-Qb)VWq/$>Ga#Yr =:W c~/f,Ps%Sq^P12&@c.Zzd\{nE,.7e #Kmbgj[} 7XSu+F'!6 +@[!6 9U%=.4 %</E"'4 +&$8.+<"  &;$.E)AHa&Le4 +"7N*5 $Mg!=V RhAW CX?U0E9S :/L/O'FEb$%?7O>X$@ ;$A89=1Q])=_ +)K4S:S DX"Ra9\lC6G%- +[rB0H 3GNe!4K .I8N 6aDZE];/IYr"G`5M]_bv}Hy\$2x0F:P@X"3J1&: /2>R!5 #5K6 -HYuB&B 8;H_&#? 9T 7R- %.F_wX%>(AE[Vn.!= +$@ qF;Hj$AX wfCR,Pb/BS'; Ne81G"# /K#/3P ";.H" 3H!@W*2 4%96 4O*H3+ ,)9".?&iyYUp@5M2H Qf&0G +h|=0,*(?'"!"0K"<8) #!: +$$=!;. 3P'4 +&C&D,F":- .2I-% +& -004&:* )  !. ' -:$    $  +  6F* +( +*7-$ +6>1    +  +(9, !,   * v[64 +69#?%=)+CH`.Lb3BY0EcIf"-FnMا%y$6Zl4Ld4%<- "*9!HX; +  + .. &  % "3EY*&< 6( !7AU+,A0#1 LX0~&+rstz(y)cx#jjhQwtw$x(~1o t#iqy pnw"hVu!gvc +f f qm m nc]n,ujdd}+bI{T M~mlEl\ nnlfy!a fs hiw$v$W| +Nt@f[bn%?bImTv Ps^Tuk;kbQwp%diy+l},t!i\ nT} \dnk^r!U~ HpYO{]U W[Gf1v&{({&Fy ^Kp1lnbx ^b ggAnU W]Z fQ}~$$pyWYmj{")ng]omfcWSepgfx!m_d z"he` jbq-w!v#[?snq"w$pv"~)|+3t#}0x+m$9<.1r!8v#meq!gj v-y0eq$w*s$kn{#y|$}*hu"imug k~#f c +mfds/urmu%wz)w}#|'fv} r(pj rgq^p r )"s q {vu$dma[_ a_ +s!e_ s ny$q{',lqq`foi{qrPw\YKqKpz/gRugPrD[cf]{%,Ac}o"ds"w"1c z'Z} hr.Zv{&-d 3u957>o4z-5=v)Bz+\z |(|$x$|+q#jp"iu'3r#0:w$3mq fdv&eW~ Tz_c |{-4|"rv{"7]z.gv$_ 3h~,et 1w%rcz#8{$,m_fajmVy>^C`i.JkXyx1q)B`e$WvN^)De@] 3Q[z!=` PrFho5^{);XWs%e6HhDd'Hm1Z|Rv:^fl!Rx`Im Jlp/_|u-fZzn.GdA] 3Rj*l+\wePkRm x0a}`~!8S>YRlqF]w.HCA\AYTkvAK_EW4F"18F >N @P=O0B0E 3G/B +:Om,YQkOg^u"p:/G9RtLQi+Me%Ib 3Jez9;Q1J^y!:Ql NhMVqn42JG^0H}"x&&q~ m ws&3pvs/'}s+vb} }%jw#Ar(JgC`w=Uq`|*Zu.6P1J8N(@YC]4N )D 3MVw=4TUwH^_|4]]r344LLg'cX 9>W6K /B-@ *; /@ @P'Mc!$>>T 4JO1>U\uSkh}3 #'<d|DTg;J an</1F --!8 2 + 0 . 3M!%= &@$>(C <,?[=Y9T 0KF_ +y3J +7OI`,[t:Fa'/<9X3O +0Pg)); 5?LZ2΅/B $' 4H)&>!3 )D 9 28P5.L6R$ #)F)E &<     *?Y?W.D Qg&1F 7O'>1,+@0&95L') +" 91J(,7B\4!>6 @&E2 <:$>/ . 9*/G+ #*D%$;)< -*:#9K5 $$$#/1"5   ! +  + '$4   '*:# ) +:    +  +  /A/& J+6>$& #          u -8qu'x'~!zttyktroi+z%s|%qphz)v!~%*{$}'p"%|}qvryy#"{|.z${s){~|"y~$m~"y%hu} d +|#nl}#|$lus] +e`uuarntye l0}y/y(&0;s%(z{+(!'xj/./h:o'Rvvva k[nrfgv sd a +neft!kc`pJ^ g3s!jn![pq{%pfssrpv-6f uy{ mnolo|'im}-dm k{)s Rse<\fifeg`Z} Kod}-mnw'qnr~'{#heZWr(:P /FRkx1>p*E@Do4K`/%EV ;LVi-ctB 29L %/D Pfs)Kg|,7c|AW40H&?=V":_u01G,A7ONhie2TnKb[qCX i(Skq$wwml oq o xazwu1t}#l vph Li+\|8=[*F :3!: +$. . '!5+=#9 (> :R %<#  - %&/(C Je%Pg$E[ThOd`w& _tE%7D^)3MkC-L2N :T 0G;P Pi6CZ +yAp9j~8H[-'82>^r0F_g7,EWo70g@X6L6KQAV +H\I^ *9Ta)MY-IU("EXyYm1+ - "!7G"=N+6HLb50-F9;S,DD]A`">2M5TB_2R'C*FC\2J[p8"5-B]u<-E +)B/P2R JiYu#d7|iIh)F-Bg#584+G8 $3*D7T/K$@4U.L!<0J#>&<& +!  1J'D%A +,E (<$8$3J5 / +/ !# +?[0!A"A-N @9 -L!0O&;565% "-"7 $!;0J(5' .>&    # +*@#.F)3-& +     .;)    # +  +  )8"-4L+DNg2zXy?W5P6Q6RMe)Ke(6O)A +Gc->\!a{ArSRe8, +'6 %6 )8 +%2IQk.T*AKaZtGAg>4L/F{NBX +\ugj$x4k t&{"!t$-"!gf +sSnh#Ur6PKfF`Oi"Pj%D\5N3#6N-E b?^v$On}1Ph +Kb2HUlhGC_Qm?]ToA\Lh6J7Q QkCz({"v$Kb9QB\x4WqD^*D]zLo`Ik^Nq`Ll6l34SkNjRnPl%@7RA\ Ts"݌#@;X#ZuA &    Ui@#   +(#  +'3 =T >M"/@ K]!?Q1C "['G.M'F.N 3PIf$E\#( %24<^kb2Pk9T Ka!Pg.Pie{&?7Yo+B8U B]/5 $>'B +1Wt6:8@\(A[*     UkDNi26Q%@ +H'D [">[,7   +  + &,< #4 +%   + #    $<Sk:Ld,:N!fXjMc(7Q7Q 2I)": '>*D)DD[ Wj>!%41$9$; +2D&.D!0I*C>U$u[,=Zs.yD\y*n  +'3   !      +5%    +    +   +    MT;=B/  &  nu"r"96m}'pxmjsmo5kgVy ab}-ons&`z.s tx#y"kj$w{wcpmrn $|x+h ui %wt| rv%rzvzt]z$mos-nsb~!v#nplz7tsywl&zz|))x%(&yz|$*&y-)stjhey*s$aa{$iiv#omnf tponx$z(og`npnv&jr jv%n!kcmb a pgnjmro&qs.wu}&m.qw!t!/5nWu.|+qv!9dy&x%klkt!dg.y&s!w")_}*|$rw y"(rw"ojjl\1rvy"_(i}#(#}!p[~qu!|+lbll`l%h#l(l)w5a~fp&o#Z~ cx)x&mq2{"yxi (0knt{$nu^z^ptdU{ {&_ +.mvpz hj yts4r }+}'f~*T~[ jv$on h`]dpx"'xlybinWLyhQ~kqV}opgip#d a u qq0n$n"y&~%nicji xq/gu u` it(sz%p/snf (u}|ZsZRZFu[EbUrh!Mjk"u+~4|/l{(r)~$y"ol2z$+y!v /w#*nnv"lpp;rz qqYv}*q qr!t#p t'[v }/2)r!hx'To~.9q hq u#d15cgh7^ybmj.m;}"y%Gy,As#-p-c 3ea +4|#A9fVv Ig 8SH``~ Wx{5\u'{,ChGj]Pq?2Q<o=WAZH^BZKb%Mg+D\'3QzI[u$QmGd PkMig2g1Ln0Ah&Mle,E^>W Tk tBYp,7M ,C'?0H E]#Ic&1J,C *5Hq:c{,c|"o/Ynq2q3s2_vq1L8O=RnpL9T8S*G D]E\ E_ +y(5}3Ne:M ,AI^$Id 4M 3Nb-4I4I SQk@[\v|1?g":Q +E[\r,4Kp$g):*4.~xz!p $r}!.+3iu~v;y+k&}@>Y a|-F`2L(BUm/Kc(4H>S(? 631yJr\y!B`.PBd ;\OrSt@[Wp&+^]vr1fu4@^/u-o_}[@Eay6UpAO9SC&8'81GLde|m_x8Q끙?2|&6t*D6OFx$WkUiYpcy i,_w(Qi-Gc}(3.wBeOp4HjnDdOl2?V +p(r/(@4+"<A #,B8/G )   !Tl;pU?M7( 6G%  + ( & %,-1M!< #=Ia=Li? f"2 *@0F6SlSTm9$< 4 (<.(<=WI_4KDZ Wm%4M;B` <\;Y3S4%A 82)= ,0Ff~<[x)u?1Q(F$?%E<)D. :N(oF@SUj$:O+C8S6R,H0O 5XuDNo>a +Wv!9V +D/E]t)=USlXp>V7KnL7H"76NWs5Qp(4Ui#0sX|!--dM] OZyG8DBP&5%5 4F 2I[!Zk00 +.<%#"0?$!(*?/.D+B 7/H (@"@+HNl&Jh%"?9-I :/6w/ ,1E48QFd/2&=Lc&.6*Bf%cyz"w!2G* +G\.)(4 7 2L-H1K6P*E 0*01 ?Q-  O[C2 +;W;$?0M2O %7'=+ ""7 # DU@BZ55#D.Q )?\3, 7"=0K#7  #"6&;'@' * g{^+8$ "(;P4 #!=4$4 +.J3M0,G $BVv6Nm523%4& +  +    +##*  ! +UjD+CSk;+ +4?V$$< ".A 4CoyK+;=R"Yq.gʋWm4'? ":,C*mBL_(x!& ' 43M-,)1F>V.XnC% XjA).AYp?'@#<yG!  &*4#"( + +  +       + +     +   +  x#x&{,y+z'z&})y"$z"k1j})lZ}e Z}l"legeu%|-{,klv!spj(|v)a nlowg {vw|pjlynlxojo{"j|!wqZwrhuup~%|$jspuxumsxxyzxn{wo {}*"&$yh!w~vups(xw)|"'b~ |%86m?j5o` 0qlZb~,v!mq}$tw"s#_~ f[}w"hjjSvlssmhanq'f` hnojxt%8s'suwl.x#inm|+5s"p!w%jv9v!+oxp^o e8rn.~(z#v,mk*tx"oms _w"7bknq}'fhn*vx|%uYu!Qy`jm!/ct)p(^f!s-q*8ebj[fgx&pq(!ylz!e 2rqsy**#XX)v#-[ +dt sivnupck {aab sgceplik[ r%s$l}1i \[x,r {%rowk[+&dViNza 7z#1*qqqt&t&vwxvpm &#zr3^ co~%xw:+u{!rmd +wy,rxq){lnyn m es[ [ h X+W5bGr[[h_il ciTp#jiJx_]f] O{W \ +`bhsXnri\ egf`rbe_^ fs\f+c kpnuv&y y#mn<[~vt[|tFjZ \~1m|${$)o}$w+jmz"{%i[~q)ab[ h[ +exqe q\^moVxngmZ~ fV}nXjvqf} Yhz'[m7quo~y()e$w}on*i +r m smtj;usf re je p_+.q_phd!Uvm/YvVry1jQmlr$fq!houjwrrup.s}+h.qo{'t"ku$c tstp9f t~+mh})y(e0de~9u s#01d9v#y(a^Cc\z |,\z t(k/w)^|~0g+~"7#xs G85z)pdis*ur}$mUtHi-pj Hd Lg*ECab(d!InjKg!mdTxGj}HVv$:Y 0M1LD`So#Nk DaQm&3HGVt FaYo&=U@X9P:P;P+@+D +AY '?"<)B +;Q3F)=E[xCn6|IG\Uj)2Fg|.}Be{'9R}IOe"Le Pj&-H1M^|9%B6N :O f|.WoIcc{!,B3D5I KcB^,HCZ_u0K`DZ{GA[ Lf u6Ga AZKf]u&-C'C!>&D#?2O 7S$@ 2/^v82I(?H_?Y1L0Rp&:3P1P6R%A22 17K .FJcIaKg)D4P"$B.O4V6X^&Hg_{'h-`|&Xp=V]wVoK`&: '118Ca'8Ts3Pk 'AVpQlbp$Sa6A6A.; +0@4D(9 fyJ0A.> 1@%5 %3* *0 +0 - / "3?U&+Sj7$? 5 >+J$? +'9SWq72J +!. #8!7-5(B@\ B[j&:*=*&%;f;Jc\Pd]q9O)6E" + 5K%0'> $< +/5N>T#53BpV.EW'  +'2 + 7 0H6. +$32 #    =K;+@[,3P-HZuNQhD +5?X2 :-"9 . ) ) "/ 0'    +    +     %6$ "-A'G^9-@" 3 ",'A2  yj3K #  " # # !2 "+?3+ >Q*,A&< BU(,F=X:8T = B\"+C +>V9O2C&x`& +^u2/EV*nFG\>V "zLgwG0 +>M0, #66I,';';J6H%(% !/)9'8. 1B4I/Cɘ:I$ +   +   +   +    +{"z&7/{(dy&(s*rpu!nbIlEkl%d^ (hd~v'p#z,nm.t gnts&z!a vrksc +suhpush ocwouk xhmwmmub ++v&+ly!vskx!ta j_u }(v'"j ppz +q&l {i pl u{vm}#mukpt}"|"o}'n+s!lz'kw$^~ omwv&`Psau%e|'g^|jcTpr/_w"aepcpiqW}Xz'u/vj,|'su'} 20s-u;mourc ldiDor"qi3mx!-q~#qe[} jox)y(hy$of 3fqjpprhS|tz'pbcoq+mV{htj r|{r_ q]^Pua;t'6g i#x1ft*o&`5dv(c}-.r{!+&j +jJuX +oku op"'zltvytr.(w#y$e kvjquyfdX] v#fX~dfa +[gt*Ns[fcj#Xir!mstnx]wp +}n.tsi'##zxrrl y|z~u| ktnxss#{'ax*t!vrtx#1tj{p%ol +url `Zpfk \c Vam!Ap7d;fj$l!\ daKvLv`XcR~VY GwXM|kZ +`l XV]Jwh\ +gc fNvq}'PylgpVdcW}or[1gf2g ) x}pmqr[ 9-j0}$ic\hq'dt'nc uw{jf |$z!cl `Ijgl_S}eh !^n^ne 'f9iei@d\u#m|&1b ^/MseV~|(pr{!{ba#|h |ytsq-ouok svp,h"k[h ]^p_5' bi`Hkd!g#f Sp Zsn#lz)32x*w%s k<'0y09o5{*s'lu$he~v(ip!Spmrmo'+ii~,lx"oj^zs z(/r"u${/jfphk[s%2r!w&|-fr%n!r$w)fu*dr{ /~!mw&s*h!x-5o~*pmx e *'y a Moopq h",IC\Kg8YHl VLrMp=^[|]Jo\;^Y{%+LA`k7Vr%So$Ol Pp'8R.A +5E,E!6(CU!BRSf&L`Yn"UfQe%=P5K >W\B`]{!h~-WB[ +@X ao6:R-Ep/=upE/}&-|)m #t8sq kkO!>az8Ru aRsb'79V6S?Y)DKf( / 2*)BLd(kF"4.,@_bz%.H3PLh8We#6U@c[zNl CaZy p$3SZzIl8{,TTrVp;da~)dk*f:AAV iu>bq5ZmL` DZ$;'0H|4r"Tp9R:q3z>#7&;;Q +3GUeXm$=R.Dm@4K-=Uh/_)G)FCb KgKf|@Up!H]aw./D'2Fq4Ge+F 4#8 '> !%%&#2  +#-K[wC/ *B9 %>#:"']iF|Ybx15LܑLȇ+ $3t|Y !9WE\4!8%= #<*AId1%? 0(B }M1M1P@^%C1Q4N847P1sEBZ\s&E[%:4C\!*Lh(,H+I<[5(D$<:Q8K-@9M RjH]Lc!9.H[u3Fb%]{29\<^&F!CGf[y&=X ,ILdl1s5AZ Lb$;;SJd+%< ":(.=ZC^Hg +FhHfFd?9T>jUc!(%2 "5 DZ*.&4"/ 2 $' +;H,#. !2 6 +3 /)!9$? +<2NB`#>8(@3J." /F(@1I6Xs:5SNn&Sh(8MBU'9 +L`2 $#? 8H_#$<0D0FNe( +!#), +0D64 6M5L5*< =N"$ (? +J[8LZEObK(-A'=6 $=:+ +  + =P5-(B (7)Rc= #@W./ _xU1)1 ;*E # +  #  +   --9( &5J-+ +%A2!= $! ! #"  4( ! +2G#8I_+\t1e34N Sj-Rk?$@ lKIi(8#?(F#=@[ܐ\2?&CR5h}HeyI$-nHeBQ&,7" .-& +$   )3# + 9F.6@2+.@ ' # %*    +   +  + +  ' +  +        +4~.Jz*5y&iuv!nstw lq[yVv p$v$uvlmv$s =low#iqpnkQsX{X|+mqhIk0)b +Rynoif _ zz!s`nkoh li nhmti~&y!gqhp4kgcm u's$qql+kkhnp{#hk/qod |{!j}%1fmt+pwxssz)iv!x"dc` pt] [ fg^kb}*nsfaqp|!n| )u)t o',|&Zpovy{!+kw!*'i| ''| %y"nny|"woid+y(seg|%pkm*+t` dw%v$w%{&n{%la 0lm|'ugx$^ iiv"h_z(q!W{jrgojoc"xz] +gW +em&j"eYk$dj#i$bi"[}gnv$n!x(1or!1vcugRzNwm9b wn|%}$+mir|!qpk2mf1yala +a |#_ +z#^ z&IkZ{dip[lIm^y.q(gt+Sv ^hgjo` +}%fm *'v|&mlp_ r^)|meu}!xjce +_)sgn tu|jogls f}#u-!t~ pV~l'}vn +o*^g t|`_au`O{M}?mWMy`r#X Xe;dOy +JvJvKuAkUdPP Tj]Y +k^TjZ +V_ y!mcRya{(magrYny&7ty$e)y$v|!y#3zo2ktu#|+~-prvq|#_ cbg.v!l)kc m-blire[~TvOog{,m XSzi\Pxma)f d menip` it!YV}&dYp>fiv&d_ n{ ip 5n~#olb wmz*rqov'rd &#`a wpoof qr"ko;;[Nfo(k#s)w+n=X:U +?ZFa/K 3F.A1A +ZKc77Qm14K^u)1HBU3J +u6j1&?. + ./FUn8  && 1*DW2~]St-rP3G%  &,'B+F")2'<BW +fy%5/I:Y~@6 6>WSo'0Pl$]y/e%< D\-'@iEWu3>\*G0I:X.M Wx-7XBbEe"1R 6O5Q <7Tp*3BZ3M+C5L '?/G(A $4P/M&F9W%>0G(=3)>5K +2HJer:Lt5Xt#Hf3TFh4W 4V =^%E<[ @YJf'G^1I9RB[ 5P=WGb AZD`Jd'2MMj'KgEdCc e,t4[z[y&u5F\H[`[h#&JZ4!2)> !0%'$) $ / * +.A (qV5J*B-E4&B +5S;3PVr;Uo:'@4O2J8M(&--'[qC$ ""0CS'/>-Mc2:T!+%@R+,<"3(51C"$3'':":(0F,- $;L$8ILd6Ic4@V22 &3 "7" +   +   PaFMa=8L,4H)#6HW/5G 7I,(1,;T5%=7O,77M,PaH     &  +) \x #0 + +  + + +   +* +    + ) +". + +     +    v)v'0/t"8Xnx"u!v!usrkt})]x +gtvnxx {%w"v"tx&Cru}$mwpeX|tqlYzVvx!b pRy+eUyemopnpg `y#ylrkqdilfwlbw'kw%a~|.;r(y.r#l.lrea fz#c t!y(so^ nl&zq}(t pz"$#zwuoy$p!{&gkp=j` +nh`o Vz W{ +t&qrquy$x!w!rrs{*uz#nmp mi7vrlsxrtx!tz!%-0t5w!tu~ sw p.'w t3blpn{ r'&x ^ \ @dy#rjx$hm{$y#w#pw!` pw)emfmhjijit]X] w"c th[\kan&XY`DlQy q*_o)Twi$?c\ w'ca:t%hNqw hpfJpKnk9i0.~$,{"` +rrutfa tlcglsm` kf~$h Z~f*Y{~'}&oa ay%Uy^q)p&|4t)t&i{%[ r` +Lusrbmttkv"gm ]c\,zi \|] v^uhc `nl sr}$yv0j_)?&~!0tk qh i_su}e}ytyutkabl0mNz`r"^ \ V GrPzQx>gbUHtPzU|TfV +g DsWZ Wr\ ] b` td kw!\ V ]}-X`k|&Ybv#ku!t#y$ix$qo()wxr;hs hm"r%oqrntv"[T{ u)pnjrfZc mbupqTzt&jcp#aik[c jV}j }wlop|!xp+ig})Ku` jwmfJqclqcb zk ycqs}(n` _ mz&fdy&it mfomjkqZsim^ +v"]c w&mt/w,Hc~r,A}16z-Dt(t*2t&y*u#u >Cu"|&~*sddnb*/glo-h+y$=y%hp5%gZ~i`Z~ f}+rqtx&nn4dW| t&fr"y&w#bx({+D{/e^|f[w +Zt cw!ix(82q 3^)v#~#,oA0u.Kl=prc(,BQ52?$, + 5I,.GY%Uh2ct52$5+;>S/G E`"Rp20I0I6KCUWk#a|=;S$2dz Es+1g!,DTl\u#,@X^t3:N2E 7L/C ,B:Q-Le!Ke +b~$VpQj5LWp0eMiB\p+SnFb +?\Wt!:V +>[Uq#JeWs*8X EfzCh4_y0Qk-1N1'fzFJ\#!5+@=V=S8O1"43)n9%B=UF\d28Ji#?8>][wF_)GOr_'1T&E>E`ay*4Lj2^y+Pk.G?ZL?&?N` [r1BZi(m Uu a@Z3MCUl|;F8L 5HWl.h\x+CX. 3!3+'=#9 +]y7r;=[/K,DF[2H2F aw-H_5I*@#7/I Yu!*F$;" 0 d{HdB]~7?_&*F1 +$#3+G`$e;Uv)Sr+#= !(*B?V,, {dPe1Zrq)E[Xp#@/M4Q Sk:j,Xd2(C\v4'? 6O3LA`Yu82M"(EXw,d2:[ \;Ed =_!>@\64EdXs);V Pj#B[Wp&'@+?^r@-E6O9S &F=Y4+4H h9]u*D\Tk 5R-L4PC\KgOm ]4-N3Sb73T I[w%k5^u/"7i4I^!;?Z8U-? [mE.*9&7'7,9   + * 2('>*A";: =7T9%? +*%Xt7b~ARp6>T%)/,.H"!7!  09I"L`52  +I\5*?X)'%$6 0 / 37&?(@*9*0F(":5 !$3 (/ 4+Gb7)($!     +  " -" e~R.7K$!  4( &=&@f}\3<1  +  +  +    + +   !08V!'E6,N0O.. +, +) -* +  / !6 -C/)F +(pFwR.I$? "B?[ Xx*Y>W~)n2n(/,ttsq}'-pa} ou!pp%|!wlv"y'e2*kwqtj` lnk_b hu^[X~fz$ofdnonn){#{ 0+c fc ooqa t*g^|,q 7w%hUo +z0=icrluib }%}$c nq jd[}|'l|#62st!u w os&ov|%(g q*oqunny#us is$m|)] +nz%nmwpsi sz#qw {%jm&jb rkj]m| sm+nv/|-vzww&wvy.u1tlj.l~)z$+qtjaUy:g-:z%ou#1rr,{&oW|fy)] [ +t j[fphdjb cdX^W~#qLuckZ~W|_Qvc Ze SzVzdSv ^er&w-~7k";x.mh_ uqY ]^cx*x'v#}(t3,Y|tcgomq|"k^ehalhfhwsi)ktlmnT{fknWx e\|Liv,}1k|'z vza +c rU/TwPvOs^CgV| _Y a {"}$z"vd d |tjcLuq$sfv%rtviw 1}(sv+|qws/hz3\"^qor~wlj^ iY\ ^ +\TX`lbpl\ +__Velg`Dr`Y ] y!lV_^g i]_ +pXPygXijv jfkdct&k]| /hr*(oVuk4ms%r(c`p g-\`eKwT~ lrt8xb +a }$n"|$rb hnp"v'ce\ a lrZntb +mm|+i g tv}"jS~kw$d a +a rg Yf wxi poe +}!qsz6*deo p!_kjm w+jr!z$shl` g`r!\ ^X|pl|%e-2r&Go(Ms*y/~2y/}3z4q(s)=2:3ks"+/|%2nv!mher~(v2lUp=t!ky"~(d hy!pRvfTzAnpo*}%~(^} gz+Z v'p dri}&w!_}3fr!es%l`|Hfv$s#/3;d}&=w!i}"{!~(~).+%-<2"b iRqNof):]^\ 'F!<E`Uq" ;/NJjRr$Ur- =%*A !:*DQ#6)AFe=` Z}(k:MhTq'Qo&XJb'Sk0:RG_$Yr68Q3I0(< 2"6)/A6H/;M?R(K^'7K-A 32-+= +1DzQCW3F /B F^#D^9TDa-F 4"6(<>T QiOfV#&>8O~LUXq%2H 0Nh0N:Jh$*Jgu7r8&ASr-Q=`Yy*2S 9&>7L5L*CMg p0k/p)}*n2Zq~2쇋B?YzBgcu-}.Gbm"*A-_t%BXJ]3(> 50J F^Kh![}&&G!B=8X6\z/9Y7V3RLj#4M 7S 0JEZ%<t?H^3Lc 7Q ;,E X4O &=/C3N +38U5S#B+HD^6898P(?CXL_Vg`o{T1 #%% +%$& +"  .+1 2 +B39 <-J/:5R;Y,I :%@XPi8+.:Q)!5*?7J%  ",  +jt`( (3 -BZ- ' (3Gb15 &9.A&6)0+cvW3C& +  ,>/)B(0 ./9O&+(   +  +    +,5 +):   +# 6J1  N_M$6n$r)iha{|)v$:dc 2oabo#mfy'jijr|'pu"?r$zv[foi&&-y!uttod nliu X}oTwc njnv'qsuurz!do8W~{ l&,b_ Zw yvorma r x"y#s7ufy"uw(,x!ooou w$pvh` t"gw!s+~*-z'y)u"{(bsy$u Er#k h`hZ0t h` bijigT|l[qthck!cW{MrZ Bgg&TyaPr v/Aat&a`~a|m(f~$m(m%_{ofhUzTw|/^Xz o!1l,u -c ds ahkh'tpT|Zd g d a +qajkt|%z#rpyZ}kx%aY +j\~r"iis$}+|$eh mpsZo#i W{W{KlNnOt +`r%irp'4pl}%%nXgx"_q%eb6j tws(A6P'A .GQg39LYj=.<"!$Tl8͑  !5_u>Pg0iI0C8LF\LZyYz$A`0L:V/N1O +776Q6Q4L+C +6-D/%;,@ *> ,(8/->#4 *9(: %7 I[%_r;DV"2D-B5I;Q': +&: &9.YPf!7K Ob'2G 3M6R xQIb&>T! Wj-PeNdb?R AQDW +4ZWq ^x/GSlD^RnZv(Rk(%=$8 *%=548L0F 4LPko:Mj$=9PUn5O5PQl!7TLg5P1L#A@[,G?ZC_6R%B5"? 1L,C&< #7 +5 + ".E.B:L1F AZ&FIf$%B.IRk4.I )F1NKa!%8EZKg"0L +6VZz4Nl$h:9S D`=XyEZ{(1P 5S +Tv"9T +9S 3K 0IVqb}Pjv-6O0LFWw%1MjZ}JmBOHVq[^Qw8x@+E%; CY,D G`$AXvJ_0G6J'$>(@!9Ke$;S'?-2$-C 1% Rk)Mg 2K5,$ "6; Jj'0WLp*Gf-,I :";8S#>\!]{0j;HiMl(2N6Oj2-tSPl,Xp<"#?T6Q64$ +Us?Xx5Qi<^RPm Yt+1M:U";6 !7 ( VoBC\1 >X%,I7V p8Sq \}-^}1e7?_ HhLk Kj#6Q 8S6O +.1EMc6L;R Tlm1Oi $;?V0.C[ Wn'3KTmLe>V'?>USj)2IF]E`>Y9(C'EFcKh1L;W 2I&<7L +-B(@4N +ESh)Zn/1I ?X?W#?,IE_Ha-E2HF^!22K &;r34!6%$ #"(!/+( ( (# & =L. #-A  (6)7  +(;#.%4,<& #2 +% +3?) + +&5E/4F%.A"$' !51':)8:I4    + *  ,3+  +.7' + 3%%(nt~$'e +| u)ox!.y qz#h~'/o"/y/9w|z ~ 8(r/}&o5nno')_~ +w trgonfha +te `xu*{"wto mdft%155})(oz"k9w*^t0jeIr%gTqo~&w u"cdfjop.6,olqt kzg c_cfd p|!.h&z"me |#j_uw fpo6x"mjkw qf` +s|'Y~e3~+Nty"|&df jm^~Sux)cjw+ar- {w|*pf /xrt/)rlr"g06v%t"nv$3kv"w#{({)my(n!r&mlLko1q$k^dBecs g_ T^ +^ Z +_^]hgb^o#l!f"E5W@eJlLq Xx;Lq)Ys|0:j"o+j)d$i'=y5t/v-y1y-`vr&74t(~1pz'l3eiy pu{!d +v})bw$s^| +gc(+y"v#o!w+X Ziiil}"x qcq!p!W hos$}wVg|(y'F:t'B{/8n%E?86~(op} hsn+B>y%p#7vy x"q[l#[XBe(:X +f;LfZt)1J8QA[7S84K!9 *E;V!6QvU3':/ -).%5 #3,? 1';&/45J#9 !7,)> -G&>CYEZ2E 7MC[C]94LQf1.B +9OF\ShYp=Nbu+`rcxi6Sl!42Ii9Mg"/&>09L%: +H_*,D !'< Pd%mF?WNj$?Yb{#p/Zrk*?Z*E1K>\0NLjLlMj%Yu(-HOk'C_+G-H; +)!80 -(.E2JCX*BU$;O(=E];VF`&7&? 5M5;V #=-A/'<(Af9St)0P <71NIf;X?]|Gm=1O 6RRl(B[)EIh2;BY`vAX +NhIfNk]f~F` +$Fh+Np%D@[og>W5M@Z_zg$XuOk`v4&: / 3/'FY'DV%8I]n=9R/!7#9>T6+3IWn6%= - 6sO78Q2H /E+( %534B_#GihDFe(4 x`*c:Fb.N}QJj ]}:4.HHd&3T;W*B#6 H_-Oi*4MMc;oY2Q i&u6*F[tk8tF)E3[w-D_+D +&>1H/4N$ +# ! *.IWt/\,I4Q2M +6N4Sf"@Tct-=S6M5L?W XqH_+/12J6M?W +Wo Jbn:Ka';%:gD_ C_^w-`y1?X&@7Ga?XAZ*C/F2J>V .A%9"0Pd>/+)D$? +&!?3;Y) '2NkL1M ;  %5b}M Tf=F],%= 3L Ja:"6LbC*7" +(4$!    3K#9 +(A !65 +(&>(B4 "8F]4#+ ) +:!  l}Y4G;N(  3 % ( .5 ),(& "  + '/vn& "     + + $ ! " 1$:&8& 1E+9U.5T$A!< + =.I$A +98T*GOn-A_)/ $4 ! +'-]w /ov|*y*^b^~,ot"h=ks!r!miy"q\ +ZU~ +Tz n{#z$r|%sd +ep|%mo~&ljx${%qjhv#mgo{(hiY~pkgLrjjg-u w&dy)~(|&dm cUy +Wv+{z)qma|0y&s!~,k<1o#i |/r%ent"s!v$r"w%.h}/Esq1c~nx'Xvo-px!~)g[b\}\~ceihpmdi4cm!o!^ %yrm },y&4~1;7Bx,=[rz3:r%v$.e +(+mr0y-$y+!*[wm0hSp Ml QvAc#C^|!zA=Y=Z:X2K +E^#+B !4%<6I8N"(D*F$@ He) 5"5-;K"HY.5HkFJj(Zz,Vt)Og*&_w9@V8PE_He9U/G#4JJ`E^7Uq'1C/7I,."3.A[n8Ma.()=#: %<8O$<1G 6MXs-Tm(4J1j}F6LuPVm3"9 $< +?V Si+qD1EXg},`v@TOcW+E%>40G)G]%8j~AWl.((??Y3K OgELdW,JTvp(q0Rm FOu.3J5KPiJeg i&4M!"!,: 3B4H"-?N`.%9(>$4H"Rl- +Ri;v$@6+? )<+>$5*9& * '<5 +4O;XSr/Y|HGb8POe ?W G_e}6>Y5PJdIb%C4M $?$?4R,2J9M$9]w42N 8S $@80G[!DY"Rpb%Oj~9d~k dQm >o%o#v#q.9ix$},}+mo mniu#l)h &}$kp2o"p$;Vx PwT}Cc n` ^ \uea +]Yqs`Z~{"ruvnklanYe#&Vuu}#,]-ZXi^i }c]{iw)Ge~ox&x%sqp{!4y 2yrl/[}Nol\ eea l~&i{'ugb +W|rf|%nlx&ddi +u x t {xw{~zpR~oo#n"i V}nk *%h{rl zunx(t~}tt +zq vwj qkggmZ \ eiiV~ h Pw j!\~as`ne wd iy(] z%hiAc6n z&qqzpqgYa +uu5y[rR}jj vwxx!vn"oh /r^fj{)\{&u h qu{nxv"$vp({ }({(is!orkhorlmhfw!hnx vw't+]p-X}X}o+f!w0v/dY~hz(gz/?p%Ps.q.q,w4i&Nd"h$}9_vx8`|"p5]{!k+=y3s+g4gu"Dw$w&x*a~Ew {"~#kajm)rsl}+Voe~w({*x&[wy#ahf^[{XxMn[|t'01y dnr$\~ ``n!,+-}#ysz#+n!Km#w+w)3g6D>o"x&y#qsujuox p#j8$h p!=_xUqJgq71TMl Ni8+!:-H;1I5 5 2FJ\5 +,/WqA9U8Ia+11,2AAX=V s:D{>zA&C-G f2To$@DbOn$FdGbyCg*`v,'<Mcn4~DWsRjYw$=[ +Bc/O :[6U 9T 2M.%>"9,A $;;P 6M(?DV'#52 +'8,=.@(*E\'7M8O!,D76O9QVq0|R=U)=3EL_**":,"6 +7JAS0D>Ag{ t4r29KXss.Id_z ܄XqQp2vtUm ,2G/B;Q13L E]Umcy(2HK^:OPf+7N2K+FLf%(F:W+G 4M7N(C1!= &-#;5 "5 #'</*A(/DoQ!:&BW%2E. 3  "90'&D n?&F -tKHf=\,K*E&<vE]w,]u4Yq8Zs-w:XYzUrSG\C]v;$@HVq_{YuDaLk +Fh?]j_m=V.Bez9'<[y1!A:If!,K.N=^qHEc2-HfK%B Ut-2M5)CkGRl')@"0%>MjVq"Ys&0M0H%:Kc)Zr;&=,)D,,I%D -LjEXv3Ww,Ec &) +"D]2M +/GC\ 6Xm Vm)%<Zu*)FEaKef{Nd G_Ogl9Xs)&AHb,H)C,E&?/I:T 4%9&=(A5P +GC]:S0,.G'@J`Nd%^q4?T?U)B"6&:&?V(*E8*C 50M#= +)@5J"8M$AV.*E&A4 -I)E/4 1 ! 5 /27+%?,F@Y$17'5D&'= = Ea,1I +*2G:P/1I( " j{`  +6*69L($!9 (?&8 $ +" +   + ) !>N));+ ?Q)F\.-C, * %4$2 9M2 2,>"!/  +    " FTD /<,5C2#    /H/H GX+p'ez#2{ p.z$toan p"jg0kkt(vPrnbo"Y{ klRwejrp` [a kaf ao}"p}!}!.l +x%l.~*hwrun c vayd +ldU|u%kqyl +(x!t#y.x.gfs qz(f2*p|oy x$_]elih` {&kr!w$z&hp^ Os.id vhj-\}xwo nyv vsT_f +f ai 3'vz )z6w.$zvr }}v p +nuqk k t}vpnrjc +b n1s&x.go#gc` ojf +ri 1jev!\_ 2=Tvw*irtnwk mgw"` +hwg-z` beyrtyv%zy|\{q_w%q-iolp|f +pd pv tsvvy!CFfdmtahjlliax"a c -h {&XG_ +@X(CY)+g"F6T +A\ }>AEYOc"I\"gE#<0G 4H%7$5 / ("50B[-H +::7!9-4 /2.I// !7 .B0 +*)1 2D[) $*C>T1) 2 jwZOf.Nl$Uo+(@k2Hj3T38ULi(:Z&E0-&:;QgsARmB_>[7V +0Ow,f}!i*D]Ifq:(Da}1N8ULmY{PmOkZ B]+/4Ol/vTA\@^Yw3Dd8Z?]Ws*H6 3$8 -B !-2!8 *(@;* +%x` +(3#5 +&Kd6 !,-3IbyN5"!4*?P+0 ?T$,A , +% 7J/ + !/>!6E+ *$   +'M`A$;,);--#4 1CZ4 * 0 / .A!'8/  +!     +   + +   + +   "4 )B0C$  %9Re7#4 FU1%.  +    + +(#& . 8J!#2!# :N+/G ?U/Oi:/GSi:(>  "3. !$ +# -$7!5KZ<5@.    + +        &?N2#0           &(4# &  +    +  +     +  +  +  x$nt#hhx%e~8z&s ku'onjlzqjgjiq&_[~n"k!o%]n#_bu&{)r%hf1^s"jk.-z#ri +t'z~#zg w!oz"p'&2m>iE}(%&{wp(qnoz'9u%648tx"|&i)x%|*j~(hty"x r4(qiskv#dT|hnilldq!h3u#kmpy$ul|*de`Yiw jdrw~#skny$}+gx#~)a c]en"9h"a U}j&Whq"r&NuOy \Fmbglr'h`f7w%v%eepx%s!`|Tv o"z+u"q~+z)0l i~4Z}j!h!j&Kr +Ko s4y3p%y+jhb3mnmv's c^qqr}'|'qk~,t"je+s 73}.t't&a{ix(.cjm6{2h7e]ygPl5kms$z.l"gdcs*d{}0[q z+|,5x)x+w*z/b|z-y+{-4{/p!cp gmg.z$ey&gl3m!Fdi-Yu =X@Z)C+GFcUr$sAB`3M#;!;;TKe.)E626+= ?Q!5H(#3#9--CUh<1"3 asB&:6N>]Y{,h9Z}1Z{1j?D_؀a~'WuYv$A^Fb:W %=DZ Jb `sJaE[AZ?X Zu"If:V6T +:Z >]>\,JIh >V3K E]*COg/*B +*A *A K`)'= 1-"4 0L`2*= ,F @Z.&@6O$9Q!h~E)AOg~S2 +AX]u!_w+Ng!Ka#/C .?9J(7 FT)$2 />/? 1D1EEXDV,?S D\ E]Jbk87O 5M3I3F5J8O>TQw9EZm,$ 3-+ ):0A /D&@Qk2$@A]$Nj17"> + +&+/0J!,4 $<)! *#5 2E 2E+B+Bh0E  9M$4K!Wp,Kj7UXx&Z| <^Uw^*Bb=/3Ut+4LK`!)> r:DaZ(51 E`)(Gi$`;a835R<[Lk,2O/N +(6R Kg/(>% +tf-~:%A7N7Q%=,,%< 6 +?^ Ss.]/Ps7Y_-L *7TB^On)FcCa7U (B&@-HA[ 0(> +(= (:#%9;RZq&MhRn$*@ G\&BX%:\r:#/B%94H$ $/A /(6 10*+     1#& +# ;I-* +  + ,  +%5! !)A' +       "%  +#32J$;   =L,  MXG$,+ !! , +&  $2)8$3, 02 +,H'B' &7P%@Z'() #.&?';-D!( * +* + 1E!&9  +    +   +  +     +5&4A0    + +   +  +  %- !# +   +  +    +  +     mlt$lnj8z!1,r!u%}*y%ny!'jgfZb^XIoi e^p(HlWz s$s$gd\er"ne|+mipc e + ~)}ukwps)v0r/q|)z(au"y!v)0p$%7m7<~.p~,{ 'xymbnz#fl}$rqqny";|!rnu'{%2z+qldu#w(]jiicu ilec] Q}dcpYiurmfx"w+nkl~,rRw1z$nx*`p$l#i'Ov@kk)`fnKrm!T| OwZj` jouqeb5w#{'x$n})y(x'nh}/|+s6w%z(dn faCNt `aSxHmLoVsSk>s b}v'fz'onz*6Otc_ o] +ijltu"] +d-ls*x#p&f p&p#o%Ip$x,z.w(t}#x+)!.*f ^ lbjeg] rspv} hiGWt +s"y*cdl&U~Z^ bnphtknE2p;!g-zg c +ju!iYhuyyusl5b +dnmj{ywrg ox$x q(x~&sx!+sp}y(rcU{&wrwg ogk ko,qhzx2 h +v|h ] R|qcndrwrvtuyid&x~ u1vs.{'{"330"*w rromkof lb +wms}!alqupg\[\~Rsa{,{*_q#d^ c f yw'{olfjrf6ddW| y)ib_ +` W`gilWhn] ec +gkx {{zw{s%~%qhv$tpw ry' ~"ja q;}"sspy%vp_ nnk^ V_R{ aDm\n[ hjkp`p"m"3u&z.n#p%8w-et,s.Ekm)8s+|6v2p,f!j!m&aj'g&Mpd$g(s3Jh;YPjFx/t.e~ Vs i a~dt!f/rluj l omij6w(~,t#6y*p$7z.t'Ys /qij~35?\wx-F=RlDg{,~2;g MVn y0H>Af|%XlEz-ms&{/fk y/i}.o 8Av#x'lk] +0okj0p"fS*)(: ++> !+ 2&=0G'&44D%7 +'> 3MB`-KBc.L+H=W2J:S'B&A@]-H >X-H;S&;cy-3H4(>@\>X =X -J>Z +%6 +-?V4M +(^5P$)3@),8" + 0& + "9*F:X )#)( #==]5T!ABaC`A]!*H 6R00M]z=1 ?Y2) +-E?V&o[6F&Gf'4(FOk$>Z8Tn6b}E:U+LWy.Ih7X 'G&E8">-J 6S_~4Ur%_{.C_.J+E$=2I0E%9-? -EX!d{60IXr+Nk7S MhAH` +2K3K T;UAZ `zKc ^s[o{,`w&&*4L6O:Q,$6(=)@)A Lc.'5$;63-D H_M`(2F 1#6 +*. HX.!5 6J0GQf:) +;2E/E AW/H 0-,K +A^,H Hb%E`!`sD$3!7*D%-EY*:08Pin8t{O"AM"_t3cv?B\?YVr4xZ + 0Ea2Ec,7&<9JW^go5/=  - . +0 5J$2 +6R(9T)4M%-6 FY56 ?W2&; ) )   + +   (   +  (&)k{`=R4 +      '6 /   !0.G(>*:*    + "28M+?T4 +  +3?&# + +'//B, CX/(2N*A 5L &B_"Pn1$; 3D" +, 1 +8Q B]'\tA3K$ +9N&>W)t$9*3H"% zi*$         +!)   *;) + !/!&4#    &,#           +b} |'eok{&lv&nt"/npdegaUw_Wz c[~Rv OrebLsp$b_jijU~ RzX} u&dl1q}'u huw|ys1px!o}#{#m),s m-{&o|$` +}%tq1s#kd qku#+'zzztwNkuq5|!r-0rjst0{&v!mx(m1j{*x+fn t'fy,v%x&i[a^ `cje6Ty}'~)`_fju!pab|('hwq+{(hlmbLqInLu [aJo4b~1\bV|5a jlxp\q%gqy#{&{&z%|(ji5z+jjfa t"u#dby0EiZg!g#Qv PsJl l'l},`u a|ccii`v*Y YV +mmW|5]|*qw!V{k` oq|`| Sup$t%fv%y&os"u$r x 5n"surt\aY jalm!X| Svw!xsyuh|+c~{+jo^ nh] `\c}0_cu%{)gu!7~&uws/uv[w%\ +gZizvp}rbfqo^dz|p `~"yw|uy+j&&nk tm +s{_o~!]mrdj[k!m#dnv)f +l rqqqy^tld[mlp{"ef !#o)h zz%&}!z |#{$}%1-7(yj^OwuIq6lcdcYphip` ihigp`Y ?3kpx'idmllid h |wj +n #d inncQrm&bYy x'Fk^kT~\ glt!b^ +thnmfsl*|*}ttlx^Ss6hgx&@':yvobj~'u| i e b mx#fu!ejaVaXZPxaw$`ae2tkos&u%3t'o$n#6m$s,[e Nq^}dm&t.n)g#i"ho(GgPt^Y}Rth&z:MnPoAu/cxp)q,7s(x)]{ hn,zu"| d +f v 3[u u%iz(1q&i"f!x3s-^vE?FM\sH_Y}2y+p$8u*Tp +m"s's%g5il+t$St hOvcq&l e}3<]SrUq6/I12]Yu2'C&C%@.F&>$: 8P%=+FHb"%AjIuX$CX&<'-.E44 ##1F"9 :UC]$*E 2L5L!7:O+C +";*C +AX$: 97O!9+6Lh}8tF0Baz90I+FWp+FbUo$Qn!2O9U4O 5!;$<+ 8 4+-%9 %: )? (> +/-?L`1+=,1H=T%/G"< -I/I 0+*":*B!7 "./3I#9$ 3#3! # +$ ++> QZs90C +4wDc.JJl Or/RKn ;^Bd`3Cd;[Nn$Xz'k3c5Fb;YA_@^0oD>Wo4TpHa8NJd;W +a'St&H:] +-O]|5v0Vx~%aUqdI} 3&7G##2Re! % !Uf=%#4 " +"/'* ,1F+B:g#?+.&7 +  " 1  2#<Xt= >Ll-fGMj2"Tas2^t$NSk q!M`,AkVj(&;7K,A . ):AT`u>/C *';0 ,C0H0)B wP0FVi'ex620 4G#% +'8Qd9#6*; &6* 3-A>X]y:Fd.;YRp-Ml*1M,E4 9H.O]A%8+:S) zK^6E ;E@G# + ,9G&9M%*A#=&@) 2&%?4< Ke.K]"Zg/LV9GDT$+2 ., D^9Ib<7O, &./  " .<'-;&   + $. +  + + 3A+/&$% +)' 1@(/ +"3 +6tS&A <.-  p`%,C\%ns ,f Fmx&l'g m\z t#ikgp,%y nw1rx{uomhiGo\X|X{4[fkenh5*irt qffa`GmKsV}m$m&u*d|,}/6n"k2Yy/i(l]/Ydd] lm sfn ihX~ `[ +mOukwl fx}"x}wx'pg}wgv wcf kce xeSxYe n(Vv kcC`v$tipi kpQz[bd@Am]|mtpprzmu|!uz"{!u8{%w$<1t b T|x(]h] eZ oihnl_ a +Vv!e f^ ] [ ] +x%y#t dx$oanz'mo'pu)g x)atnde\|aeb^Ot^r"Qx`Y [ aa\ +mt#Myoe gh ovl.o x{t7{b%s,p%]tt,v)iy'[{a v&cx(l[hy*hl8/3:3qmo q#z,Wp\v l/okc}6gy-}2z-{,@>IFl)=?i%|8j'Sd} }6Qh dyO~4~3j x/t*x-m na|So~,~(w!\x eh`Ioi!m'l$KmUu VD;VRd,I-H3K@Y#~WRIb3K`tERh9(+>#21E#5K%4 %@'?=U#2F$8 +$7 +3F+B 2L?W(2M G_1(?1J2HC\!9O'/E :;W(B_w7Wq''A)D;ULh%8Q.G3L3L62("!2#5H1 6 7)@*=5I2F8K6L^u?9QW)BOic(D ;1K*J8X?` 0R0T +&F`?n-aDf8Ts9@I\ +$5ԂIZkDdxF)&:sXOd1~W1 +% .#2+ " ( 1 8'A uL`}=/J7 AU.#   + +5K(9S$!>@_%'C8T@[$?]/M Ig')F+*, Mj-PpClnpA^S-KZw"\w2Vq$Tm-/ DeBbL@_Cb2Q 8V;Y5SA\5F`%/I:";[u0'E3P "? &C=9TE`$>%A <;T%=4MPf,!607P7P/I Qm(:U b{'D^C]dNc$Th j}'"@S +r Hcg}&l/Lb5LLb$8 >R#$Nc,/A +gvAj5)0#. %.  +!(/^/E7U$%B 1/4 ;O(@M);Drao5$4. +7L),0E%4H- ". % + ,   &*A'/"4-$4!0%0  "4D'$ & #/    +  +  +'   *    +    (# +" ') JXE" -"4 +#<&B !? -'" &DT5 +  &% &   #$ +"/< ++=11)B$>0f?G_^s@ + $/9U6Q;V:R)FV0+9 -<# cuM->34L&;0D(  +   +  + +      !$,   +    +  + +      +    gYu fe_~ +ttt6qiy$qv%q"s ^ {,cZ|Uu `[~Wyq(_}d`~nx+kz&s'dZr,r(]zz/cefjj\mi m 4c` nm)ot|(y vn{"w!rq^ _eoTv+x-f oby&msh/m{$}$t-.u/{!jx&mn0qgr w#kx$ms"}-q&i!t,r(f|0idglt!c[ +v*[h_`x&r v",akgy*jlos#g}$g voMsfs'adEUt Tyl$G@v,k$|1;eiu&h9fw#nZx |,Pq+4nsup|&qnhrpy"ekoI0Kf'<0*%* "*0( "% 4M(/K!(C"=+C&$:3 !2# +(vCu:$Sk40:Jf3N :WVt#Y~ Z r:2P0 ><]:\ )K @a%<].P4S `D^C^1L6Qb*?b7XwDXy%[~)t<%I?Jlb;-Hc.*? y=Zt4q>fi(99:dSre~!Qj!bJY) +-.&)#,=&(B\#7T "")"  +-Od? #5 :V%2L,DXr;2 =w/]t>I|5y3t1t1Aj~(w6u3w4@Y}6w1_|6p#x(8BKv+s&r#p w&k2y&{#z z&/CMn~9cHh \zLe [tZy(u(w5e"^z=St2Q +<< +lO.Dbg;<[ & ( ; Ea&9U(@ 8 #*A,B"2 + !/#"5H]G_=X 7T/J/6Lg(Wq.?X2M2I'D[%&=Og..E$7 % "1 =Mh>Sm'Pj [u.*DF\#"6:L0!6#7!60(;BT#N`0DU#`s4Vl'7L/DL`'M%-6I 7H#)%7G!%n}X#3,#6.B%G[8$62 );'9&9(;"#",)  " 1K&5*D1L!"<'! 0I"(#!4(  zI>C6:o)r\|''E//52V>bFd$=Tm!QWs,[y0Yy4(H Mn*'H5S -L &/J"<;V(DUvd09\<`?e{Dq5Im1T1Q45Mi+/BYy5\LhFcTo&Uo+4Q Wq2,D 4M5N+C &B*E !=!7 + +/J].5G&: +WCZwMWn2(C5N :8O(? AV+,B"4'.AQ1* " ++/ #&7&-/4Mf.%>6+*   ]kJAS&"7OXk'Pm{&DT-B5IˆQ[;5A7BR6F +$2Ym0qZ &E[|$?a-N>4Sv5b>/Wr6Oj.(=T*;WEb$B^$ 9 >U+-C'#&  + !& +&')>+ $#9 7/ 3!Sn0Wv2Kh01?W$9SIa$Wm,QgIcNjVr&uEUr ;]z6@[*D+C /F( 3:Q'< "8.'6+Te1[t1KbAY?ZF_&?;OFZ&-@4.C),A/BT!,=N Xl,dy6H_2E 1B07J->%4 $/ '& ')** )%53DC[%Ib*^u?!8 +*!%  +  5I#7M#+@"51%&6:J+(<,%6-#  & 2+ +1/F#4 !%8!55 #  !q>j!q&RFdOn@bDf UQj,Zv9'I +:v_,`c .64R ?^\|88]y8Vn,@Y*D f7<\St6Y=a +Be'M?b :^4WQp&6^yBLid{/Mc t7HcJg:T4Oh$u1J&?xp+ZuIf +f$@Y Ic^|Ttȃ 1 +$ - )"8!  &)@U7S 0O Ef":U#   ( jGRw0uSYz5.L 0KMh18Rq!nA69A^ p8GiY{:j'n5g+c=ZFe.L"?1M +A_/L/F.H +.G ;X4O 0M lH22Nb}<8S)A 62J%< )A 0 83'? '4M.4271.D&*%; #7(;,E E^6O-GDYDXVVq)Wq+61K )B:Q-C ";2+ !4>V!CZ'Nf2>S(   #      0 ',&"/1J$>Jd3$:!/& ! _SKb-B'Wlh6qD" d +KWe0<fu8؆H]nS\6CX^{._o)֒,-$   )8  + +,A#/D"  4   $  +  +     !1 2%:.     + +  +  (/!paho&Tt`$;W'@*@Ja"6O i3`|,-O#DhG,J/K%?0%02EBX.:S$-F/ !#) % )%%8A-)5-= 3,;R(, ( Gc/Yy4Ca>,I[uA*B3$: $8J^8N*E;.IKjk5^~19,!9/)()=DX$?T=P!"2+ +#-< -#6M +Pj$oFF]:O@U1H-C.Ul6.F,> =NcpF'3+=2G,@$7/"2'8 2C(7 DO',7 +$ +&/' + $ +.* ^tK?['Rm50JH_,0CV%EV$}S\q8 (=+ )')(  ,  %4"38K. % +(  q +  Е$61Fw=So>^gXw>d(`++GOi(lA;]GBhN)2Ie>[#>)F +&B +<If!Tn-4L;S}Oq<9\3R ;[:\-M +4V \~+Ok#*)Cz=8VFC[&/E:UFd.JHb2G@XHb c|%Qmd(w;Qm5MPfBZLhS2 + ' <3N+ +& , +7I*E 9WAb#,L +  +  9QOs/5Z +Bc`~>Ic8]uOVr>fDwC\e.!=!9uA[|#]|.]~Km[i$cUrTs]{$1PLk"38Ib,Ok-'BVs+Kj"B,M_8Xv0f:?_$A)B #: 8Q1J3'=6 +1G$; +1I-C*B.J$@ I^1*-A,%7Ui8 7 8 32L"=*G>\2QLb5J Ul9S$:5N +=Z B]*HwMOi(+EUp/%.F7P58RF`Idc}9.H +D,E-#6 +. +'#&<+-F !'9! " * ?O+7H!$9. %'DX+)5N;V .2 , +- 8O,6,vUo Vm27V +<CjZv5K:Q!/_l*(1MQcmVW`9K Yk{>`x)5E6H!+0 # +" + +!&  "  +- "&,$9(    +(  + +   + +  +" "  2     +     +   +- !' +& + +,= + 3 +6(E 2Q">5N3Q.!/% +(>##6%<$     ! #  +):   + ) &6&81)0' , +  +   (:#);+$' !/+ + 15 6H)9 +'7   + !-#   + + , 4'='>    +  4C($3!67L!*E 8 +$<5%;+  . +  +    + :F1 +  khr$q"a}2spz(a{bx%$&$,uwA|)?5Ax*=4jt'o#;x.p&s)u,v+y/|3E|9GQk j#s,k!b|t'4.988,9=|&|&*6(')f3^~WvdMlg0Tm%5.Ic8T -K-Ca!l:1T<_ Cf@a-M $0 50 "6G$): "!>[:$>3 3!4;W0&3 +6!/> +2G%:$;7 (>W!On&0O +-L3O2M5O9*C -@ -BzEf+>Z +`|.8U'D8Y-M;Y7SPh58L1A"3 !3 6H%7"'1IYp@1I2Og48"8&- + +  %->! . +"0  +WlLnFZ3&DWF{K:S E`IdVtKl9X*H4?Zy@Ea:Kl:Y]uV?IRsyC$?73L/ID^:X &4O /OXx+%B?[7S5.J+G4*!;;W Ba~! 5M0I3M#;4I=SLdߗ5O4P +GfIh"?]jA'E@]*D)/ <:96/4O#:]s@*> * +( + * '$0E*'&>*7)6'& -<2K1L1O=Y!B\0,H!*DB_)7,DSg'O^" =U7t:Vq Yo.IhTo^sTgZ8Cs'dn% Rb*H]t"Nf -3Rh")V",!$#7#/ ##$" +(@".)<$.);$ +9 "/I&*  +! +  +' .5,  # +   +  +  +    + +  +  +   "$: +"!:#< 2 7-6 -C:P!)B8*E ;&C'?0 +"##2"0(9"/UgQ  +     ! + +$*#0 !  + )*+%       +   TdO& $ +HX3-'73C$ #1'      + + # 8M'7M!$9?S0" $  +'<?S,,B3GCW/-`pU&  +  +  +  + *GQ@& + + ht$o!ft&w%lv#kw+c\xXy a[Xy^~VyZ~Y}Qv +Pt +b[ack~/eg?[~p&n)]ahj_q#/ege[} !"3\w8sJ<^, ! * &B>X0/C$  #1/&@X._z@$BBa3M"</F2H2'@ +@Y#J^)'@-Rm">Z:U 0JqFkA`}8C]!C]%$< .1(!$*(7-3B 0?@T!7Ng1IQh,5K!65JOg,Nf'Xq.HanB5JK^'5D!, 10B,A +('=.DX$4";;W MD]/D`(H7U "> + $2P8WH4N ,C@YGe @_ Hg.M3N9Q5N fQ/D+B6,IA_!B$D&E3OTq35) +##2OC`$Fe) >1N.!$ +* + "& !2/ 3 /I)H-BX//? + - 2C%,=!AZ&*F Gc/5 ,!? +1D_([s6?VxG=QV=U 8UHi +^| Um i3Ik +F_ VojOas+6O\Wg$KbKf9S >WEZET  +' #6*0 - /F#!5( + +?$ #7(?$0("   +     + /    +      + .$  +   !  -*)5&?  !    + + +  - .?"!43C BS(/ $:K? +  + +%4(64E%): ) +  *+ /$  -5F$*=*.@"*" +  (  + + 2*<   +    %3" +    +  +  %   ha}np"bo!klhw+p&fMUsz8s0Uu^X{y/Wzh`b[y\xe8em!,}!,z wxy"t@_Hg Sp :U 1Ki7U-I5, +$5AaS]+7[ +,L?bBb/0%A 5Uo?b~J!9 - !$@b!=a02 4N'0 $ +&',  +  ++B:P;R 5"4 -$'468N,DPj#k@=V/E 6'; + +"2 %,"6#7 39Q!4 +00C6K!G^*=UxDT(<'8*"4 (; 53LpFD]+E1) 4J't3O-2 :.J=W5,G-IxLgJ' ;/N A`Kd+CCY :U+H;:Y)D2L +/I&B;X:U/K(B 3Q7R6M,A*9Q&? $< +'< +5 ., 9 +#;*@ Re9+@(?#< 7!9%< 5 %(A?Z'-K7P:Q/.()e|E-5K3G -C=V9T\z4&D3P >%@('. 8 +7Q"b}L2M/K>1N1K"   (54(72I ('9 .?_pFTe<+9MZC!,   -!6 ,B/H1 +D5P 3O0S<1M27S gi1Pn:Ad\}].%xg <+FC-8+.3B4JF]5Pe3DHYp:(1 $ * 64 +!$:&9  + + :I5  %4/ &     L[D# *7$      +   $$ # %8-   + + + +   8C4$6$6)& % AR/03(/& ,#: %?40!6+(       "1 'Ka5c4 (=1F &7 +1,=6D*& %  +"/  + $   +-0+2DR@ )0* ' '  @N3DS7  %)  %6[jU  + + $6BW2-* + +) +@S-)AT.+8   + (#;F3    +   +   +  + /?$(.1"+[yboz,Wv v+m!kv#Aa|n%<^z^{w5agk#k!fl!u,`{j!inimjdeWxi%l&Y~[_W}T{ [OvPuiV|ldNuU{ ^]Nvp n"JsMrv*bs#Lkv.EkN{ Z[OzYk `^r&^s%p [ +^\__p$s&dlZ} +y)X{ _m#o#abiZ| u(n Z| cgm$Quhr e+_ `dlv"fMul!jhX| [q$^g8Q~_]cPv\r&mPwSzX~ t'_llfw*t*Nsc5d]blmiit&\{fp's*i}z*C8ib~{#X{*{#4v^/(cm\VZ +Wbfve )j*od +dpkgfsqjlu%hX}[!t~o^ U~[ +p"V +gbk$[~v6a&p5{q~%,4/0&3!$,)**jq;@^6S?_MX9T !]T^%)M].8ZLn,5U/O Ji,:86Rm:)D '9N,0v/O)J5RE_58 $ $)   +  + .&:'<3G1F)?/ /,<* /AQi3Ib&Zs4=V$=>U $$"0- '82B'#7 + 3bvAJ`'*B.C+3G;PkA1K +C %?Ql%CY"47H\m@"4 ': 1-C 'A*D+E=R/CT%#_nC;NI_!/F 2I Md(xP6M -?V9O#!, 1B #4(  #1> #4 K[7HX4$/#/La)Nc,1I ,2j-EWl$8 ? ((1 4,C%=  +   + .   + Yn<~[ !.Iۈ9VA`?Z5O>X9Sc=Q0%9 ( #"5 Po,n"2=88V2QVq#%?a1b2.O+5N;VwGGe#74P >X/I )jHV)H/I 3K k1;HKk&D7S!<'C1NHf$;+H:#B+G Xq,U]zI.EV7S8!; &A1I 1&@Yv)ނ$@({NY\x).]y If*A[E_Zu.Ts,*H+F@[-IOkDc + ' +#7+A3I,>Q1     +  + +  +)0 ($ "#!/v#-~0jVw<4Jh{'u\~r n#x,c?3q#emgue qrp~ &Rut)f$hll-/+t)+{&-x#|(_@t'w*s&>Yun0v y"w|%z!w#)('$sw})y&89v/eTrm;f*&DMl +Qv>a2VBc/=6M$2.?7I0A3Ui26M.E Uk1CY;M+:  Qc7CY")B;R@W $)G]/ 3 '/ 0=% $ +  . ?W +c{-E]*exJ`x4^w"~+MiDf +qt1 1-6I) "  IW:+<Mc*hA $l7z:^|,H $&>Tl3&*=*Pc7l~a q>/R@8Z~3FpAj14Up+Ki4SB[xciyo!|@WUp+;V 1 $*G3R :YSpZvSpLk2Q&B : +%'"9$;.) 1J "(>-+?+:9M0F) 0F!5 .'> Zr91 .  + 7H( %,!. 4 +3N. ;;9XCd =\90O 1M?[ Sl8QQf BMgc}9S]uu*h{n+5GOi(][w4vEez7wHJ(EL4 "1)/6J-( )D# + + +    +   !!    +  +  + + +       (  *9# + +  CV9$   +     % ! #&82@*      +#2(9,? 5 %:#7 /A  ?O2 0##$- -?S'1E- *  +)A"*?"-  3@-1;+ $ /<"  + + (4&% ( +?DZ1 ./B/)>3O#%E-K7N"3#6E,  +(%4!0 +)2.5 + 35( #   ,9(#+   + +#    + )%5D+$72 08'?0ps!lbc~6dihw$k7e^}dm$dYxce\}fkhj#n \za~Pss)dYzVpKeNi SqVtIhNqV{Sz8]PtQvSy^KqMt\Jq\NuS{R{Yj Mugq.q$}4Px\]fm%`Ovk#a\UzZMrSxInNpTtl+p+Z~cj!]|6Lqd\ _ft ejey$W| OuMq Lm Fcp koMmjek!{-s&aSy gY}cm"r'Ow]bW`ebS~z*Ov\T{m cz+u&Uwnoe^ms mjaq$o"k{(his%z+]yz/v'y'ohcph~*ivmxutp_ g_bZpd{'OvRzLt{(eY +[ +n^ Oz\ kq^z&U}RyRx4Pt~(Y}e w!l] ghdics&j&n*d$f(c"o+eeLr d ?bLl j,d"g!WsFh:fev,9fh5m-ce|-Uxe@Rtv,l*] aOop$n$fTv iw*mlewltp6` -{f rcjhwz#$zx!,20|*q#D_8:/8m8y(1-v.|x|qzqlyz8j,u4m.*}'z#qg_ [}{'u~&nqgo,mlkb{*q` f +rgjXNyOz Lv Epm!AkQz t%fih~t z|Unpefaw+u&\} u't'z*k'$q}$xtqj w#m &uj ~Y~x*pnU{Nmv".*tc'qu#.#{p }sv,}y jg lb *n m +rt}l rb(m m m +pr vdr yuxkvtmplwkpr-bs%Yy-m r&]gn${/8Jp]Ost*STy5au(dWw :Z eUx>d=Elr(S ey*\ b_ Yqje &mhu!-l[ nnc~1Ekd^n!gLr Uz[U{ k Y~ Sx}.Jk[e!TxXzUvV~s,^~2e`u%h`onu'v*|-z+j5{%8;|({(pv%+Lo4|*<|-q$0}/5z+81mr$w+u&pqpt"jx'p w#my'4Dp"55|1kc}d1~'~'|$~({$~-('!1p%}pRq3WNqjw1X`8\}Wx C3V1R:"A ?\$%@51/ ' & * 0 .F1!*!33I%1 ":8O(#;* 5E^3>X*Fc5.I)C0J2(,&4)*2C0&#6 +$7"  +% #!0 + ,.@F["'<6JOa*4G%6:L(; 8M4LEbBZ"5&5DK]$7F(7#:N-B +H]"3&8-<%&3@ 2-DYώLa&   - !;S&>YXs+>[ i)]z'B2I.F4L2J'@"3 53LC]+f6x>ImJm :>]z&]o,!>7-F 0G>XM-+YcCdBa?Z*3153N2M%?VpcF9We/Wz/If"2O:3 #560Q7>W=S3qoZ"0! !" 5+=# #"'<%;/2.,32 2M5O7$< 4Egh.Z}-Mo&?c e(E%>[u2E^-DE^#Ga=UG^#>W9Wv$/O~?YvXu$1P,. #/86+E>Y#9W2L,*+? #8 5,E3 +$39O##;%< $$5*"80,+'3&>563.$:1F"#   3G! + +   +AY.-G*+C8S!!CHe-Pk5Qg;&;;Q+7M#1I +% *   4 $@.J55QGd2M 6 + " .$ )6!'7"(# !3N)C%?!:-"<6R#8V#<9*DKa93' + " +() ++ +!2,  + !!#11@N`1&9(= +)< .%EU'+: +/L`*mB+C8P+(8!1K[-3A/? 6HM`/,BH],eyH6J#3 >P!^p@ % # % 1F _z&Ka-0B _v=cz0D\ k(r*Acj'A[@_zz*q Ts+[v@{\fJO`Gii|J)85D(3 ;Sm;o.n=Kb)*.Lf/mJ3O1I@X#0 j^ u(+K7 /AUh3zw&6]xu0RyRXu(-G +9SG`m@y;VB]@[Hq#Hf8W-N#@ <8 ++@\(1PDdQ7V Jd"0J 8P!;  + + +@Q4*>,450)( !0H Xq<9f1q; +2I$7Q 2M (A-D ;TTs)9ZKg)D#>)If7X;Z3O'B81L-I 9,3L'B 5>U$%< ''6-,8O$+B !B\++H2-$=*? 'Ne877/FMc:(?4 Qo;#B /M:[!6,D65L 2#, +$4+-B0-1&C2Ca" <6Q.Qj1T0G}K1H-L^H]Kb"3MLd/E2F;OUo2Uq&-J6Ony*]m$zR1:+   + + /"2 +    " )'1           +  /@"-#     #$9 (9#* '= ( + !7#  $"+;& " +! + '3!  /6"?H. 2?+  + +-A#< 0Ha+oL`u>"2%/;U]w5ay>z^ +  ,=%)!:*C'.$/ "$   !  "0% +  . + ".&:"2!4*6I"%8%<)Pe?!3*?(? :1 &0F1H6L +?K[9"1! %.    # +   + $    +   + +*7%   !.*" ")*'?$<,F1 +%<ht"u$z)lm j]|:w%u'bdgp hk]~ gjs#er!v'b``u#] 0\ Zv,o&gjp%q'Nv@iGlCiSzPw Oud?fZ ddmkPyfq lib[ eS Bi`Ms^QyZOw;bh!h#X~Ota[[]StJh c"r,Psl"Hg]}fY} [ [ aqptnjw(k^RuKg +;rv"}-r&Jlg cq%_n$hLrk$KvT~ ia\U{qpg +lpjdr$foy$sw%p!y j{'nx%l{+s$kh.] Wb] +igd[o\ Ipktff d }#c qnw%`P|p"hX _cSuPt]je;gAlV T`[Xj yu khRy b^l f`:_ [V [X x(kjWy +z-es'V{ =[|p'W{HjGfIg +Rma{"C]bY}c^Ggm*>`z@~,y'w%afv$emrb ~'q|"lbZ V{ir'=aGkihjz-na `lek!i:|'vnknm+|(<*={+|/z+8x+x.b5}2r'u,v/|023&u4jnx"kgv!rd i lws~"i zx<~"w*] V} +|2e=n!x&.x&ji~0n u)|6Y{`eb%\pesrdi~"1&eKurth o v + t(NxXdlejq!r o!RxZ\ +l{havun }u$'{rg zo'.xt5-ihr!r#y"sy#ox#loj~)] +~'re jym,W~T{bOw` 1Uo x%m +m vsrQ{Pwa e +wt l~s}y'jy t eyojklXkz'Fm}1\@gt/v1Ty{6f!V|eew,^Rx Vyg`fgx#fqh}+o"iv&v$s"u"ks 0u!kp` +c eh s}#,x$/ms0po!el>z(<=7jIKnpd5q#h`D^~[zVqg!{/o6oev'w(x,r%{-<|-}-t#|)` r)Au{'1.C]s:B2jt(_x|1z-t't$n*;s${0x+{-u#k5v&r#n }-12ft'glq${3M_x}/hu"q0hx"&tzus#/wkwy!,}1s,9[9YD9GNXC^ Op Vx!+L!>!?32!9.16L$+ + *>0D %9&8 !=!< 4/J%A6 9">#?"=64?S-@O+! ." + 7D#6F"%43B#:I.' !8K5H)@*@ ,0@"3)9 ($5 2"4G-B FW.. /?;JR^3)< +DTMb"b(;>O /%.(:2D 4!8)(1.ABU02I>X(7Q"8,*?5jL)jG+B +rQXn<-(6  ( " ctUjHLf_y;  1E0G*Gk%TUt k$Xu!1IKdDC_OJhQkZs"P樄c6Oi;f|A;N$3 z]xYnD 5I-Tm7*B +C HUj,B( If.o8Y GgTo)`zA&:( .2 (:2Tk*@e%;ay"I=)> @WOo/o6m0Gi %b4"?#<2*Xq"l):u*{0g(]}/*K 6;S2( '>A[&Eb!;f1Zx(Ro!>\Om&)F &?9( +   $/ 2 ΆP9Q3IuCNb.V]{$h9[z)n2EbtHt1D*C +Sm/3O.IIc0-F+F 5S >\$?+E4+H >^!'D)DGe&6T%>. '"3+ +-? "4#7%: +  +`tFj";4Pm1/L(E + < :94L0 ",=`lP# $%> 4N ;U-G .I+D*,*3 ,-1A#1A$ 9N/cyQ+-Ig))A-A0H*>*   +-G6Hf21K**"72!=6T<Tu?57$>9T'+ )8 +.B3K: +%F -N$B ^{;.K1E\")<DX=S0F!3:OSg*Qd+JU3 +    +    + +4 =[+1N.I% + )1:)  +  &$);-6K#.B+3)BF^"KaQf#&( *= 9Q$$$=>Uqu&s$mmhgiit(o#Yxko#joqTyZ}*`pkw$d[ +Xiehemv+iu(e{-fo#HOu`[`]ojhe`x'ilh___ a^ l_N| +\bd`~3UMvXY cRyer&Y}UzXw[zVs}6en$hr*eq1lU{cptd |"_w{&g^ o1r#kLpY| d^iaOv]fk&]~=;3z&m}*sw#5,6m/t%o!jt#q%|-x*lv+w,n#v,}/v(}+~'r/mx'cXgffwdm%q)uvw.?|$u ]do"q%o#ejgop3}-h|1t+z2o%bc`{s{ywprv{ynh owtk zoc_qdsl\q_ffv%Ya +ohBj }qa1|~ |%uc _$e '.2t6:] +/1g6y&q"r"Pzi4r#^>~.ocvi{#PtbHoDlz$swfu7pmyqg +ih\ +X~L{xo idg-mvtsm{#] +_ Nx]q$U{ ]r*Wt+i#g UxDh n'u,^Px]Qx ]Dnbq/on)v.et"ka _ +u"qs,-.v#~-t%y.>z/f|g80j]ugg9{*ajg8Ahr*7{0p"28mr:tqptvxf +*,lr{%Tv ,NRt#CLi0M`}o(CVq5Of_/N9X!> 'D)C-E50( +$$:9T zEc|.kW5I_3Ib.%@ +)Kd"39O*Fb'/H(A\%a83PD\)@ ( Ed(UJm#@Dg Nsp"InV|+?\h3Tl#zCxB:OHn6ZYu6e=B`܈4@Z6QPn b$e58VKk(,I$": +&? 8.1_z1PmF!?2PDtkT4$  +(0#  #csF?`u*au.fy:T9OOYWv ^{=XYt.;a}3/I (C!=A_.G9<9.;82N67Wr4"<*4J"%;)"+@ 6 )'2HdzBYu9^5A^/6/ /)6OYl@, +%K]0' &8[s72+A\ %?(@ 0*= 7M&"% "D]$9U[x:2N1I! + (#-0:'5+8   +   + )      +($/(4$  #"2" + ) +' ! 5.&A4O#?.Igt%y*cngit(n"h3l {,|-jz)ebTzlYiinhbehbjX jgj ]bv)mfw(NujPX dmo Y ahaVGpf@hbS{W_n#RW[fblfaT bi`eZ\ Ty^X|Ss8fch dicX}_]Hj1a ikV}icRxMr X|e \{So Sm fYu7UYy`k \k iU{ jt$X~Z r%PzUeOwol` jZ}y$^ y#hxk r{mebbbIn 0SDj:^?bX~_ `2cKrV~x#qhZ W =em!amXh]^ Nqs!SsOt Nt T|?h[_cTw/-mk-gj^igcn} jqjmZc|(^ adfqiPyGnead!c e&s2CM8q&o!s(=z*`u /}+x&u#inmkmg{-4x*w+r#;t st o~.u#y&ilu"[rz!peq+w1m|*z&{'~&w#_ +mZ~ +{)u$t#q{(hnv$u#pv(z0aq)o"t"d +vodps.~!pw~h m vnl on p `d,~"pqd (npi{ h lx&.l yvzm mr %)rhnju(;tgu!^~ 1x$kq"s&Uz}0Qz]v*h|/s%t%[ +q.st{%hkTzr5)tLn&zoodkZk|zxzr l pk +ym +k xkmoS{iv)z.r&bY x-dZ gl!8Os +d_}1s)f[S| l"jroulq{(qpb {'x#lb at*)ou }*L-q+zru%v#7u%_ 5]fNt +Jnl$mai7z$ov&l|,hn8p%;?G8W~234`{:y*k3jH0w$4y#(.4*jEZo B~+-Gsx%i|*t |)p{,oz%o>7z/ezs%jqs$8_w}1y,Unhdq$t(>6~6bzSKio!q*bz x#pvrrd1s(sqpn#^@b 54R 5+Fk0R`|hqD&qUIh#45R5$< +7 ") ! + -,6 ,I/ +_|H.M2 &(7+?,A$ !' >Q#,F,E+E-EPg<"4 %  #/"/&91#&9 -/?$ DX"2>RYl6'->7I-@ ?Q$0D%:'= +': ,-;++6H%8'9"43F'-'%5(,<*?% 81G' ' ):!6 hM(C [u8 $: '8 /+ O`?i|X'    "1# ':T4QYk-M Ko8[Xy:1Re&ފZMd,5>Z[YPmt7v;Dad6m?/I'AZv-h4Ifm6BY uC@ZKd%Nd--=9R3P0N,J pHd"%?Qk6 #Pj*Ld!(>: Uv8b@c7Vy%Dc" %Dg0o!~+3WKg%7KJ_AV ?TQ'A+H78[+HccUU݇Utm,q+F01O9VQl;+Ja3.9Lid7R[v!s>8RL5J6H'* "  +KYA  Qc.ar.8HSb&BQ%Zr#*C:Zm+~;` /Lx>Mj+*DPkC^g/ =Po1O'F@]-H"=.I +Xs*%C#A9"=&?b=+KcARp/Mk21ISm48.E2J"; +E\/'=0 ) +5%91 < ,J2 !%3A8M$;rI1S 3R1H!# #c|GD_&()-BKb/D]%,>"$.DBZ6P -G52J1F$: 1 $4.#2:I)=R%>U!/JHf?R6OSk%Vp&A^ ]y%[!i|V,>% +f#4  + +  4A*& +( %4D+  +$8!4% +' +8$5=/  ##! +$, '  ) + $  -  +  & .E0,; 19[!#F7%Fc*!>8,8O1I +G/J- +  +  + +,0 + '8*A *D +)C4P#: +  + &) + "  /"=/I1 :W#/B0D  0 5>!    +  +  +#0B%-   - /0> LX<" + +    + "     1=(-9#   +    '341 (0 +'2Hb5/L,I1L $B0M/MUug{$1w.^"-OaPq1C`$'@ 16 3 2. " :\zC[zB,F)0,B]3B\55 9!8. ( + 1 5U5R  ! '5#2  ?O&]r9K`(Pf.9L! /6H^*F G^Ў;Sf~E9R$*F2N 9U2O(F9X$@4M#; 7N4J2K5O5LF]+;T$Sp5Dc"A_ 6:3.OYLr9^BfOu l)M]~ 8AX46MBU8O!Da(6U+H\zPqx5Yz!*Hj3c,Eb Pk),H #> *@ .AS+%.E>W Xp) +u,DBYmxTTn72  " (7)+ +(& + HY,( G\'MRn7R1*HUv.L$@-Ha{2C]::V +@aAb 2P<"?6Rd3a|0Aa)G-M=\.M +2Qh>1P,L8S;R)B*B/H,)- 2G$v;U*816 '[uH* +(9!021M4*L5R1+2 +>Y%(@ !:N'+"68L$65M(A/)+ +*B5 *+ ##$ +$ )?6NNh(!>!>7O!%:  #1 +  "" + 8L.6F&%7# $ +!.4*1JMh40K7 WuM./?# 1%6% ! ++   +    ! #  +$   /:" + #,  + +  !  + +  $1   + +, - , %?#=6*3 +-G&A2Ono{,hdgas"Xuflil n"hjcpas"cfmcbeig?kg"VR{ UU~U~Ow cElw)hb\ h][Ov DiJn NtNsDkT{p,Nr;`@e=a;`Mt[N|]}-Qwt&\NyWKvw-X~ 9deg[ [aU~ ]eOw\X `}+\ X +V~ +aW}mdtjOuX~ _Hml)Ln^MlSn Qp>]SsVxFW] w"Jppx!pbU}WHrKu bW{ +IoajPw_ d |#)j ca.i^CiDk@f;_LpJmIk0T*N=aU} S{ UQzZkv Hon!hr+\][ X` hW,^IpFm\x,brV[op4kcR{^ n[ +hZX_Tz Jos&v+Bg]Xz{5l#Osq'dIm3VEjV{]Wzl!g_}lz#lf h qefse/3jfy)bt'iZ} )z y{k'hSzIoY]tb vl]u3AaKiMmLl"<^'1%; 4 '19) + $=+G28V"5'D1I$'=6&='>, 6+ )1?+ +   3W#@ # -3 30EJ\4%$5 +02L6 *? 1.Xh55E1F[t DZ"8/&5 8F JV,7D@P &/>S Rf1J\(?P!7H3E. DV'$5(: /A / %6, 4E"-=.A* " $ ) ((%7$5'8Qd@!'  ) ! -"   ++"7 'i(I1S>_Pr*L6X :[2QNi -F>W +$(B^6U 5iITu,j>Nj#3P +Nq 4V5Xx:rT6 $.(_vD %45%A$B 6U&B +8O#: "8*4@[ >WLf,+A\( (#? 9 Qp)r_#{8l&X}m,?c#EB`f'f1P>Y +6N 1G +n!:Y!/L .F0!&$8Ti9/E /E]t*i #>'C "= To:.9;X-?[*17T66Q -9M1B, r~Hlu2mx=%1 ,"=M8LuB[u2g}1yD:L s8-<Na !5g~9ߡ7A/      +! # +  $) '4#2-1$9/"            +  +  (4" +  +   +Mb;AW,kULa3%6*%< "8 + $=Q/F 0I&A#> +( $   + +! 4,-%+#?  4 '=,B./-4K   +" '92652 $-3&C5SRp0Nn.Lk**I ,G3O3L)?50I 1 +Qa4((+ )+ +(   ,8&%0 +-* +  + +        +      "    + /9'  !   +' + + $-A!''?*B,05 0 -)(@#<pt!@elbw*s%Figdkb]fj`XZ Wbo lsniVcOz]X_g#PzR|ao#JsfsoDkr"cGm>b Kp >d}?;aFl>dZDg0T/SRvJnBk8dIr[jm ^BmMw W[Rw ^WdhUY]W [n!Muk^7_|)T{~.`gfEl[ ohV|X Msas-KpX|Z{l"PtZ|Nt Sxerx%no)ib v!`o"U~ YiX| V|_i_]_ +cZZc hCpOyDpJtNwIo1VAe AEi1UKnj%aXEmKt h#gh[VfeaR{W ]X~ _v'Mv\ a^IpcQtz++w!efr#Y +Z qQwX}i$n-B^[u]yj#:w3d{!h'g}$s*|.)rrkxw1do"ilk4jr%JpkNllkZ} +dedg{+dgy"lbg qo}(ljs/kot [ +` dk*+f #j i rtsw}juol tuk 0}twskq +t|to }xp wxzm0<-"l xl t1n{"%"muXz!zpgo[~&}7|x3%Xt,~(0u v#it2jgw#o|*;.{$ju.| y4}&\{"{x uvpY~f Zx /_ gt dhh+x"pi{(qoonuglidMv&iVrf1hld+|$gop{'t"lp!.x$nqja_alyoi trx l5u rhz&o}$oi1s#ojss%#"~%pJfn%q$x%g1v)ak1\nWvv*r%r%;8D=l|l#j~&Fu(=z'>q.8+}%|%/x ,z#./,*$\ris#}(eka +<6t"4]w 1i9z/5[sMkz73_x`zk`{mfZt;Zuz%9u"paz .jt+z\v}"/l~&+w3X3(H8V"A?^)H'C/M 'E-L7W =^*K4 *6"=- +J`9.I*F7+ 3+-'(A" -@"6.  7C04G*Ie9,!' +6)@5O:SBZ"(' +/Nc%wL;T63%9ATWh'ESEYF[:N0E'6 \h;!12B.A (< )> +0D1DGW* 2 +'$94IDW,EW0->#$ !2 0#6*$ ' %4 "* !&'6#0 +1?+& /  &,;Qp.n )Rs+HjSz%9]=qe"/V +='E(D<\Gh8W#@ +Hd/VjH +DV-.2 +Rg?UhA-$> :-!: %<3HNe1^wD&B-H ++(B!9/.\x3Njl^;^]~Qr7~IQpUoVty1`{.4NYo%/H.Rp7Om6#@. <(JTw0VEi 6?av+8Rs9Si#^t7$9 *7*CG`7M$:@U$j?^pF&B2O=,+E;RD\)^u=Yr53K%< $;Pg.#<F]&)? ++#:2J/L0)@3 %  + $ +/)B. + 6G)&)=&<-$0          +  +Kc54Q'D(D%@ +Nk3=Z"Dc!; +'4 57/J6O,CoF7J%5 +9DT %6Qe*1B)*> /F6JN_GHYe!%4 CT%jyO,4)   "   (;*@/@)=#9 +)?U83/B#%*   +    #+                )",71<"(+*+ 'I^ 1H 8"<=P,, +   &. $. ,(4K-%>4)A 4"7Xo8Zm:1"9 2EH[4 5A(#%) =N5  AO2/<  +      + +  +  +  +$      +  +      (:N`B (%& Ka::,2 24.F ", +!#6pt#s$5fgw*lgeUzX}Sx ZdW~ bV\W TTbet&io%n#bT x,YW_q*FqT Nx] c_ +mQxV} +\Ou Tx?dEOwAgKp 4Z>cMp_!d ^+9\>b ,P-SLr[Bi8_DlZ~KqZ ZU}lY X HrJrSy ijYT~MvcS~i]o x*\?0z'lLt_O}`mEl"LwEpW5[cep!b;aW{g^Zj]m!^RwOtV}Y~Sv k#z2Tx +Mpdf~*mHjMn]^ f((-r@kgr qu&gw)a gZ k_ keW| +r'F`a81c .b a 5l^ ^s*i$HB]Qj|6g7|3l%d|k!q$m'nr-ytx!y%k]zar#jqhOtk'rsubfi^ pKrUy8gec ud +b lnkC4x%{+kp|(v#/jruojf~#sqprctbuwo ,!m j h}o doql tkeutt |vy93*+xi vzz!fkr| ^hv!h,nh tkk*% !xp5jA,>|%q&{ .py$sx#h\nuy$kN5sx/ypue %| xxlo` hgcbZ R|lq$R{q"ioqlqtr jbr /gffj1s pisw!z%km|+ku(ip ffaW +U>T}WOx{'vlunw"vrim`s>}&o9r$m_ wryk&zD:z0iA|*u)s+No`cffBYy v&>04tp@5<}.0E<{1=m%9f{@<),7}$%rkv'&&"')(d +,tehtrYwc{(M5~).dku%p#s%x*-2|&})oFkom}&'jy!x#n2n.xvtr{"3mw-Wuiv$5Ua%+M*K mE0O *I)G0O ?&F0P 2S Dd`6Gg*7 (?%G`7D_1' 9%#7&#!!/ $ +* 2" +'4$7J*" !!4#66 7=X6SWp-': 5I2D8M! 5#: 2%+ & +% +&!1"EU: + + 6.,1 1, #)4F%.= &6  + %20 +!(90FEb 3 +&?`MmW}"]5]6U9U=[n(t/]"Cfg\Kg/7S!:W">#? Le:' "0:P%&; +.5- + 5P$@B^'1L +" 9/2JE_9AOk]w^} @\4O@] 0LPx>{AJeW4O %=03!8<[o?^_u@Y5PKi!D`9R=U*'? +3(D Om,e=Li($>'= +$+<G^-nN6'<%4G-A ) +1+E$< .  !+>V)E#D?\:VC\' 1/L@]!^|8kC#>1H'?$)A4-3 &6 AV&'#iFe>'C' "&2Izp%1!& +VoA$< !9(?1 - ,*  / 0 -4L"0 +  "  + + ' .D0F#87R7UGe$2N)D86)G 7S1M-41MQ2%;$&!4'<)5 &" !"3 +#- +  + ! (    ( +    + "          +   BP.#+$6+8JDW FY*Ob>    # ()9 -  "*4 'B7S"6N$!7 +)<-:"  + $!.&90 $**INl1_|=4Q"@Mh* +!1 +'6 -4+    ,>#      +!!   + +      +    + +   + + +   "AO9"0,8&!'<3&#0 ##3% #33M(1 +5+BW5!-& nu#d0w)hw'ci\V|KpY~Jr[Mt^\X `@i\V~feX~^YLvr(hQ| X[o&P|P{kNvbqp R{jz-ddOt~5HnEni#$LZ5]Hq ]Af[ZRyIs +U~Ir^]dR} +HrMvMt[aocgQN~ +_V~ fX~0Tznf$gU~r pgz(YgW +Irjb]`h_ pev(e_V_^] VJrV~x&W[Pw KuCk_Y| +1W~x'_\ w*o dt#^ZQ| 6`Dn7] &L=a4YMp@cFkFmPvf$bBh^>d7]IpTzcR{i] Z P{cl!9Ns=dr'l W~bcNwNuX~ @e^dHpt"Hst)=jPz +Rzv*X Co\Cpc\ `R|Mx_k X +b^Jp\dMsd]Pv EjPs GkbLrbheden3}.da .7hZ}bt%lkdriwn z^ ak[ hX7^z/y1EY|y-={'qf lvz%mjr$o(}50Ou+|23t&4p#d~|,q!jw kk)glkkeIlYy +8tw]{$vqot-mhW}[+h9^\ KSzZ Wpprc +z$z$.By%y&hz+|,{)5cz$iqtmvy%lnmso yo qftxw`vi +p%pmnhpj i0jo vn x'2*q&'%nz#u!j`)o{$qn6q'} w' ,.#Zy%2.~#w z spihsjWd +l/lupf t)r*svs,}##zvB| te^u'faMucCMudYey)_ lp.r!s#t%py&qa qij0mw(/n4\ dlW l d^U} v)^Oz Lxeaat%q ak`z(p nj7smro;lkkj` w+&zn0z$u#:nmTWv y3Y{w,hdinr<*(,u%x!m:y*{,2|0{0Hg{u*q#5*x!/#q~#q&,wx|!qq Es`k\iqpl3k/4{$n~'v=x$s!j+|#w6~#drFr(4(+lx"^z sb} +0{mvpnre p~(z(Yu a:]FTAb;.M #A'D6T6V-M +L 3VAe*M%#0 , /,!;$=( %8( $4 (*'5),  ' &7 $% .-'  *5Of3c}C9T,D10F$: &3GMd`v)F`1J +",E }K^2)6KTe4'4 %IW/@P/!45LLa)%= 1D))(@-D(A-&), % /$* 0 /.+@#% +RbB/!2(!%53F( +,9*  ,?, +$ % ++/EvRFeJi&|W"@4T Mn:!D%D9U >^[| DgY~$@c.Q 1S3U!@,K:045Q1M_{H,(3 FQ8i~V#' +* ,;1CfzC6G4 ! 67T4P2M&Bt_(B//+ !72K Ic(n5Fa C{>Ni%AIf2B\:RKRh*<;PF` B^$Jf: 08&A&+J"Cb#DGfbC[%Xm, %h?1fE_}7/Md(A Tb|=&(B U&=j)iMj3I &    +  +    3@9J @T-+C63:5O F_!3K)7#:#<": 9R4*)D+C+C&"85( %-I 68Mf2(>%,@'= )(< 5J!70%<-Pe=$ ++1/   2E"'<&9\u6E` _z>-C.2F2D2G1 #d:3Q82J(/('% !'- -0G$= +;T5K"% ++ dyR8>V '5K% .  4$;$A:V#0 %  +      !:B]'@]!3Eb$1%? +1*,J4Q#?5Q,7 #= .G],%%00B9M-8u&HZ'6EZ +r3bw-&6&-?cw>*aN   +) +,?$ !&9,  (&    ;J4#& !4         +   $ +  +  +    + 9H- 9 &:'?O'2A .  + 0>(CV9 !+ -/ 0 -D&G[5KZA  +   + + &3 +$+0 %5 +#:5 &2Up46PBZ GX/ +  0 /EK`?'#    /)       + +      "   +  #  % + +!5"1%  ' %-H. %!;!8". +!%!'65G+^~ kij\ncX} +_f\LsYf!Rz +X hn#T~ VVU{TzX|p#[h [V|4WSw9]4[] f] /KvP|FqjQtj 8Rv +cV}Z T}\ w&`Kp@]Z]hHpV>kQ| [=cFrGsUPz\\ ^ cn\ BlLuc[S| ci{.Qu `YKp f$Sv?cYd[ X +`y+4B0ha sx!hoZ l] 4vp +nxkyr^[ {+osmj] }2>Sw l"Fz/1F*',2u#miz/~5e{-n{+j-q|%u kaq~&Puy&a~)x"Zp#t(9_dyri qg t{a(o`ttlV|:K:a`ax%gtc lfv"(z#kTvft#o-1l|#~#f +m{rsjrt\b nfe#vpmoplz"gv$v$qpnm "wrsppq".zzxssw!gs+1z+=-z!y x~t !**{$++mz!e j|$ou~"wr`ibk~$s~&(V}~%h xnpprp"srd 6ks#]s%chq!m#j![c{,]qfof|.1enc hhit"y(l_bz)t"mY do"dgaZ=U=R2f|4}C/G 3JC]1L8Q 3)=,@ %7'! FX)$;@Y""7"7F^#Le*C[ 0F2E"6 %< "; 402 31  !% " % /"68N0&:6I- ?O0* , +):$. "5)>OcD!5,$# +"& +@d:q;i1Vw&;ZKჄP.Q=a?_Tv&8ZOs$L=7U"?:YA_% , (6*$BmE/Iל) !&<$:J)>M))=.]`v9  +!Fd.7S!< +XYy!W +&$ 0\x5,G~B>[Qn|9Kg7U+I$</E~7~8Y =R[u:2 ia0G%()0*B +Poc*Sv^{%Ig/.C':/!="@A`Yz#Pm!i@Sq%yP7S!9SlB\Vw(flC"t[  + + +&    + #9 #.?Z%8Q4M!6!4 +& +)6$Sb=&2F+ "- +# ,)> +:SIe $B&@4I#".) *3H + %3'< . 5 ,B 7G]1 , 1 $@X)(+A0 !1! SfB&Da566P#:M& +!,.2,B6Jer8@ +   +( %3 #$ , !);    +   ( &( !       +  +  +   +  +   +     #    ( 4K'*G)3E?P&%   +  +&7-E%9 !  .2 14  '  !.      #2 #"5/8N*1$=3,f+8! + " 9H%+$=":* +6H-%9  '  %       +  +   +         + # +  +   + 4B-   #@S/AV3!)  +/Nk;A^-67R)-E",A*'!9"5'1%$7Wvml!p$m!a_Z {)hj OvNsQy YFm[Pvau-:_k&Yzdak!W}Sy_Yg#Q{a;U~ 8^@iIniX `X Di>r"fW k!_l_[cQ~ R~ +^XeLt]e`v%ShVY w+Xbv(Xfu"VZ fcigZ +o{(_h nc f c rpgieZ +jS| iPw\ Lut"d^n"FoVEnBkDjNv[]iq$r(NsTzV|V{DiJmX~PxJpMq^FkeW} LtTxFk] AgT|%C:^8Lr)d/"C.R-S9^Ch +q./Tg]n`^j] YVYhA>@dVze|/dDhY4[fp$V} PvNsaBbcX}+t_E~,ap!\ _ pm e#`r]_e` gfnnlhhr r"m{+nz#misgkcg_w)u#gm}-s!u"td }%sf pbc9`grW~gGoPxqn#_vgx|oh s} rrqxqz&jifo$o_ +lehb mzn`er|'ly(^ekh*ehnbihse Wl^g g ~%molz%d v gw(m3w%{+y&qo8td inu~xqn2-1gqh7r%5w!8Q2zy$|8%'8-}$43zn)qxqzh d +j|%e lotyoe idhj l qpy (x4p0ot&_s'W~ :t)cAx1p&finz'5ke|*z%qsr)w cs#z,c\l `p$]T~ }4Q{_L`daMs +Ry j dd_p$\m!k#w-}1{-1x*] m] ^p!j<\v v$w$jKr?eTy*L'H0N$A c9] <\*' ( 23I4 "0M^B  + - + +(E +5TSr4Fa)6Q/I "%= 75G.>$&8 -? +?\q8,@ :M9N7LHa$7 /K 0I &;GZ!CV!+O_39Q:R=R&? Kb*-F3K2J.E!8 +#; 71K)C8 +,D% +..E"- 3* %4$ !-  # +#0( * ) -)1 ,?!$ 6D'_zB1L, ( &>]*In'I^|*bJj<:%C Uu-DeQr'kEJl%-J !? 18U?]&3,5 +,b~DRk.@\4L3J2I2%4F$$5 (&)@-E45Q -L&rW&kEIg-MXr&@/%CFc Tuj1+KWs.u9@] +.M@Z(Xrt(9a{ 7eZtJ  +# &05SNnDfNolF9V]u3Pg0(;R2O8W [|%3O"=uZx73Wp/Nu3i*c7Nf/3c9a +'  + + #2+;R'lBPh(Ys70-9 Wq;7(@4( 7/D2CEX'/E 2I7%< ;TgN'? 5#;*?(<.=T2K,D 1A- ',#=59+I Oi0{], !-&0 +2E#( atA%944C_-KKg+ ( +& +(&2 8fJ5/1 .&=;U%mS"; Lf/&?*A'- *   (%=-E-J#? +$$;(?%  +  +  + @"@(F 1O< :6Q &> =)C) 4 +1AV)1 1 -C& $@X!Qj,7R4QId!'a[x&oM_ +\k)79D>NNbYn%i~$j{ m쑈]foTm   HX=2% )    " , '(! + +  )  %      +  +     +     +   !+ "3! +#  *'.C!)5  + *;+F(>)-5!   3#b Cg +ImaNvw)f] \j^ lQ{Oyi!|2bHpPxZZh_R} +au#dv#dFHqbq!SYOx ^FKqm%ePxcjZn`|'of goAkfVW JtXTzi"FmLtfjYgDmp)Lq`hLlv&q\ +Szrkp`hNtbY_mqp tw{g kntiqc'qtsr_ kjb gb y&gb|1dfs x!{%w$mq pvx i&hlemd`mv"ldeR{Ws{ h tj row%e d{$rh riea_cj_ b h{nj mjkw jjj] Y W U~rR[ \W +_ +b y"mZ h%tns!ailru#lz0{-p ow)LyX +X \UX fb lutsS|S|a kpu t&'tz"z&=>ld {,@$4&9=M,=4E/+> 3E(..F +@Y7Wv/ZJe &:?S1B% >O"%9&/G *C ,B 7P,C&;,E5 $;;S$&>6 < +*C" %$=,&. -:*  , ,) * - '<':' .#7 !)<# +)g|E@].F** "a}@%E 2UUt'A_n.j"j2&B +:52RIh#6S%B +*I (E 0-H)E '$@1,GfH/H,F =UKb,Zq=.E!4')8 7$< aGiYx&Rtg-e,Mk {i(p1tklYQv$A t]+,%BU`k9yOMg]zh39* +    -)D!<-I +&E:2OGc% "807 +-$< (?/$:*>6I$# /=h}KcyE $+ 1 !;1MHe15R11DPb2&8 Qd7aqL   9G$BY&Yq9=W"%/Lf&Zs/;uI-IPn.%A_wAJ`/ !mUQm001LdIa@Vs<5 13M 2 + $Hb)@Z#)B($6)(%"1I#)F(! + "0G$4   + + +  % Nl74Oj1$A;,25N2P&? *# %0)&@`|7 3@   $ (<&7$ +  5H'"3 :G06B( $ + +   + +  +% &#*"    +     + +   +  /  "3 #*        +    +,A/(4#@4  +)#3$ "62,  + + *5"  +  +       %"';)2D  (,A *#.($;, + + + +   ,)  +  +    +   +       +.@"%8&% ,?Pj85( $ - 3Q(F4P`|A+G6!< + !3*2/ -  )6&iNtjX~ _aew-Y~ [a_YePv cFjOt +}8eXzt-X{\kig^UKx +O}GsFpClNuHq?j:bUzUzNuht*cY _ZU} +^_lX~v(o$=gNxab`YKvIwJ}@pO}LscaGoS} _Q}`i=f^aadpWrz$c h tse ]om.(e +m_}-~-cAl] g^ Fmp#}3iX[ PwaQyY RyKqQw\NuRx U} aHoT{ dDkPuNtNxT} +T{Rx U{Ot bi'j)Q{Wa1JS~GqHq5^Hm +Ko W~7_XY g@iKsX [ {*]a^p%cU~]@ka]O{WFmgw(_ gv$cdy%lZe Tx`Gih!k"n#Z +z&^ edpe fmsMvw"emVa-ZU~w*Goh_fMv:a[Dj]_jp"]Owjqf {"b[ ko"fsd adkc\4?g_ gpbj qtj+mv!f\ ;b +RznTz[ YcT| +cr!hz'y#lvopk(~&cNC|*[ v(dv%jIqf] ee +e +f +ron{!ysme |!kne +kojZ Q}\ kYmq#stqmxe ^d c Ipf^ Z os$eOzis%z&f4T~Sh4z(9v'bw&ky(gs(w-c2:dq(k S~EsY]ahmby#d _ +gp_ {'nps.os/{(1-rg &sw#++wx'|/x+!,6q|(q-onsmprps}$p2tsqx"_ +6z'pf})y$}'~&u0e2gku(z,w$K^gw(t%nt#16nm-~+\dqk`W~ +cf`_T]Xv3b]Y~r)r)k"r(_~UyIMn=`hY |/{/y*gg}*r jhl}',o] +iT8u(?j~82A|2r({*.{$}"}"l.<{(;9z&tJ:*+x+4ku>49.0s#~+|'-s"g[~&v"oril oj zsl%..jrxcw_l~(}+_| .b{ 0pq*kro"wmxr2~"",'|"r~qx~Y{@cNrZz#JhIj?^7W +'F>_Ce8Y *J:W;Y6$B7VLp298XIh04Q3P0J"? ,G$D>-O B ,!,6': 20D'&9+  + "0.@(#%BY'G`!1MPm)Ca Kj&8.I8R;!9 (@Mf7*-$BT->Xi1*;1,= *<1!9Pk&,I +Yw.D`E]8L%90E+@e}EeCoJ&B9V(%>1I$ ?P&;M!3G>T% "Ib1Ja04J*%9&A*(#7#2 +! 1+ V 3L3:=W>XSja'NHh "3)AQm 0aa( ?Lc~/(EӌÏ  +     ) ,C<[1QTs,3Q,J&C&B&@6 3M6O#9#"8I!*>*4 1 + '% (4A("2';55 1&B!= /1 ("$5 +La/+A12K'?+ !%$&2 0I5'-7N&1E ( %1 4*4 8Sd~T 3D*$    +   + + E\.#>; 8/+I=X*.+0'+% 7Eb,(C (Ie 6U 6VId"93P:RCWiw*IT [z,hx"6kz%CQ/>o"Nbp~6$1)0IT._g9  +.'   ) , # +            +    %' ) *(7!  ,!,      + " - + $1/ -) + ' %  +  "*)1#  !1"8 9U#3(?&   ( +--4I) '("        + + + $  + "0"%%7!\mDDU/' +" ^qO,:  "    (   +  + +  +        +  2@*-:)  '/ + *?0,/H6 +DZ2''Uo=2$> Jd24L #0 + ,4-   _Lrr'[]jZTyNrZ~Uy q%iRv 0Ti!j gt+cbcX}V~ Z +f\YYHxZHv =hGpKqd^GpLr d NvW|[Di]OuJoZ bae\ r&<3ZU}TQ| +TTJzWTT 9fY|4u-FnWo%T~ `\ gf]nW3sjpgwh k`^kmi ska p^ jg0eU}] ifZ ^X En7hgQyLtbX~ jRyEKtZEnIrW~Nw\@fp$Wjb\e``OvQzP|hIy \ZT Eq>hNw +9`Ls Is HqHpaVLt`7_ay,1YU} v*DmY_q&U +XP|CmS} +DmCmDmh{*ld\ faW|V{XQuJo_Ns3SyV~ZJwX_ e^ +a 3z%gik7][ `ahe|&YU}Gpm!Wcnl] Hq^-e xKti[ Z z*My] a c ka +eo] pr he qenfdffj^w%a Z |$Uz +Y~ ]]Uz ]|.Z} q u\|poitw1\~g?iMrASz:`Cj\W~ Qxng[fqYV}r|$mdYe c e h h s]a`Qy] +co)mk |t\~$sXhqMts4o#`hX Q{cy+0p] jVdv(z,t)u*hz,np _w.x0j"v,[fWBm[P{ +Q}x6Te^imkim\ +Kqaje^ g_ r#z(-0{(nly"-5uu|vxsxu}"{!*0,v!{)u#krqof_nq!W||*_r#hp lft&l#Tz +leBp z*-6y&~0y)?r"_jm3h;y*{,r#w%0,w'v't&1g_ p![i Zr,bb Sy_`"X|Zd$v1@WxZ|5~0OoW{`Vxk%c7e ]{*1kjlgtsb +lklcw&FCFs+w/7x/w.z.`x~)/v%qt7v%8v"/2plt}!vxA8ju"{*H33|&+8oez%d i vwog |j u1^]{ov|%x!Mpqte midg0Sor#z'q7p2mmw w xn m !or|,p /s})z)o )YCeAa@`5U$D%D'E$BB` *G8)5(=.$.&:+  "%  !$7 "   ,;% +!62H?X-G8R+H 'D+?*> 5 $*7 4 #9[pG'6  # IX=*9-  +%5$+- !4,@#  +7F!@YUs"HjyL(/'? Mk"IkBe ] 8W -@\-$?(C'G5UKka6$C +d./Q +)F" +%Ok4-14!=)B ՈOgh.Wy#D!=%=67;WWxS>]Tx LlJi4G#Kd7qa%".(  +   &2C$, ?\%Aa"A6,+- '/K57MK]1&8 $2/*0),EZ.+   )7 "3 +, +.+0+E9R") ) 0?-? 4=TtP(;E])'=+BKa0Uo;4Lbz=3I%< $5(A'@)C345 4)?)/ +,45 ""!-,C#< +,A-$4$0D$;6P$/E,:W #@$DEa-F+E 9S:VD_RiReZkWh s)ML[ +LZQ_px0Bmk"]W~Qx][[ fponipe ,i `rfb +inXjb_nzr`pZ` w%q[ _ ka^mPyMv_}*ia ^o s"r"r#\ b:gT~ WEk:hTz bU~v'R}OxT| JrSxU|W ^V >qT W N O|u+V NvYKv Dm;cs%q:bU|Qxd[6\r'w)?g^T~ +RL|[XS|Juo%f@jr'FqcWLvFqYJpk&Sx_ W}_h"fKrlGqQ}d[ _ x$hu$~*s"du%r%Yl r$p{x`Voe{*\f[njJtx!}"sfhNwafeLvWbl_ +[epfapp[k` qT}apdjOxu%op78x$Y| ~3y/gfeh>Yyf4n;v#erv shgMsPuIno$w/b>d]iq!g}/^dkpmg ar^ {#m]xWev&`S{rcnbj h ye i`Ry_ m~&e?Mt\bgLw}2g3~+TqrgI9={.o"3t%u#ln#s(Uy Po'k&g"dj#p*ar1[XR| ^t!k_ qet%eUzq w&/\ 9>2A~,cz)ps r"jirto}|.}$p|$b t3blj`_x*fbq%z-Y~u+gi!V|ifefXZ\8jx+6p 2k7ey+v)a[~ ` -x)`r'cm m!-p!ij9z.`d`VYg#[Lt e&Jr Qwi+Hk <_?`c _|a[ p!q ^PvSf#IlW{q,\[jy#{%p}",~#5Msl)mp}+;|1v+w*p"{.m!{-o|(w0.s|(}*{+p t#.sy)u!-rp+~(s9:pw#4*{%l(){$y!mpx${e mc Rzz":u[y 88kx#ldjWzpa iz(s0_mi+0>nhx$,fi j}:vy|i3wkzzlsMpLm4Q &E=\6U-J2O*C +:Q2N3O0K8 + *"D "C 5= *: ;- +/ *%9(2 )G7S,:U16L.10C!.  1F.6O*Md>2 + +%9J-7M"2J6MAZ(A 5P:'? *C3L+B 1&&*A*-@H_+%? Ic#H_!(>6:SHdp?E_Rj&6L+<0/B-E)D6S)HIe4N Yu57D\g|;Ug-4G 5G ->4F"4/-->:K&!0.4 0#8 v    +$ ""2-@!* ++& +#/Pi.Xu'g,ZJg!Y4I Qp&:\ &ITwplu#C++H/}XwFd.[x-Ig"5$A6-I5UKj*Hg*gIIf301 8j\+AJ^7.B"Ne;3E%.-F6Id@l?WqBZ x6HQn!h{1e[Ad5 @"@*IsKIf($=E]Ys Rk-KGeds XzH7<<IiZ{(/Q:[St zRPm1.WsC[z>#> 3)#> 38S,HLiu@;\/'=]u> +d@Kgo4Pq(HKl\}.k?Rp%?\)I`+Oe=  -    8T&Ge&5T#A4-"=1K5Ca(9T%<.*>)<3H?U66L3L&=&<AV-3  + !#$*&8!5*=R(+(* '&-9`{9Qm%D^ !;3 / + !(.9P +%: /G/G%> +9S=U*G-% 6 %; +:O"+$ @Q..<   2$<%9(1C/-.7,  7H%)<(   ")/   +   +# '-  H_5*E2C_!/L +D7Q%< ;O2.(? 2.D`%g=B](@*?SDZMfXo'\r'DX Odq4,B,BXm 6H!%9]v!f#q4K\*?T5HWi;18*x`lI  +   +  *  .E%/F2-E + 9C.      #. ,:$# +!  + "  + " ) #(   &(  , . +  $"$)  .=##2) >S.2#9 ,+--B41  ** %#< 5M#6 4?,"  +     % 3+ + "&6"3.,@BY$0L6N&! " :N,#;7 .   + + +$ # -/    +   (  + +      #1&4    3L'B634fNuk#dX ]r&YRxYbde]jid {'Ykp` _]~-~-mleOvoqkV}[kv#\qQxZ dB1\W t%faw#6+npp`5Bw)z-r$n z*ir!hr%dBu-W~n)o(V}V}dd"`[Qz r"u$6ocao!g:;r"E:FF39Gbb4] b~.Yfgz&y$tBvk6m|+clia;gRv n(u/Ffm"YT~Qxj(m'bp+x6e"j&h#r+u,v*fbcledw*p#p#^hz*8_y.h]kk]cfo&;g [^a>hm+m,R|e(=f_e$VyUwl)dTs l#s$s"Mty*Py]^bTwSv_chtw"x~#ovr!{q3vh w*|+y'4D]y +-v$|(w*~&;lor y'w&k,5|*v".sg0p8y!x#??|!sjpx -}'[{e q!v]a cg+`|$tw}(onm^\r`.r ilvjWzentiw#eqi +qz@kjn+e|z{k+jq {xv x%` In 9\(D8%C 2:1L3J7N 7Mg3Lh7&B+pY&H)J7 (>$B-I!1 %* )0 $  ++ /% 32M#2# ) '   /B  +#%;7M@Z ,D ,E#: +.E1H1&9L ?R(( $:F`(!@ =#>(@ '@(9 gH|Os6bJFeoDf}OmHc5IkKm:Z_'Oq#F<];X!; ,+J 9Y 2SIg Yu4=Z/L ;Y)G3Vv4De":Y#@4- #( ( 8 -B 4*kQ%@ yY.La@Hk#:T%<EbQl +s5Bx/XTk(L3SCi[+`.4<!AWv4Nk0+F D^1Jm9Zt#>] m'>b@r$Cd3P"=Rp# = >Tr*Ljc._{8:V?\ %D+F7 +)F3RQo3-I 69a@rL-D "Ld+/]y)HhNm*J&FNl)8VMg+Ni+Sj:VfE'  +  + JV>Ŕ(C1N/L +)G(E '671L%>\!<+D2"7^u91G %<6MAM;  +)9Q1$DV/3 1*G5";PfD- *:*4   &# "  -" !$-"   +/&:2 +0    $* 5. !.  " + !* $)C-* 4 4 '&4H,*$6.E:P!,DRm*J_"FW     +  +   +. &%*5$+ 3E!#Tm< +6 )!0  +(!8/5 ?U) !&  %4! % *%5 $7",@"2% 3 8P&20K5 + 91E( 0!) +)#    + ,ql_Y km kcm#V~U{MsLsJp`]ciy.^eT Z[_smqhXVff YVKuQ~d!MxaU~X^dfs!kmYjey%pm \|1;bf`Gr\7hUw"^ +[q^ Yw#n^xf +)Y^d c]aeW^ ^ cmRRdP}d^Z fVVx&ngcw#baq^ S|jmndjf` pw"j] +v&fV~`p"s'Sy 3YJsIrkJvX Mw~0})MsbVST] \ JzHtZ ~+nj\le{#pbtv"lV~Jrv!o^ +t!mj{&U~ +bcQ~]hHwR +z.`_j9Ny^ohbr$Oz\ S}fd[ [ZCof@QwNvEnKvU ]u(AoKwDpf` c o}#o\ _h\ [^s[sh g de moj0kno_ ` Ul] T{y(e] s!] {*W 2h`[ Z jp h]m mr$`}7l$at)p$X{ f3Lojn a9]{ `d*qU~+NxW hFnEkYnc}0VEoXl$Ydt"s#q Vacenc YQ~S|c_ m_ gsm1Oxlx"T|W~ rElsqiebVk_ ` :y"^ \ jgn/`r$]aZo#gx*a;gfU| dk$W{f q+u2q-q,`f!6lx+u,r*q,k%dF:J|2y1k$Uq n$w-t-aY do fWQ|jY fm jv3u!_t$53bkRy[Jpx1j"|6o&a\KrPyd'aPv +]^\~o)g!l#edq&z.cmcu"r .7it&w%l\ p$bk!W X`Sz Y~Uz{6^]^cF|8WDkYXb"X~Sw}9Qt=x/r&ay*u%hn ZQz ]c]V{F5Y8sojl~#x~ ~!nu}#9f4$uy#vta~&)o)p{#mhi5z(Zwr~*u!gx$op,tmv,c +tu~"nqi,e v"kqpz!o[OyOwt /lrx q{"<7bqi yg wx!7q/1uodUrw {).a|.` b +pz!vk'rmtn +)k +yvr ${Y}VxJn5X.MGf@_ #@?]!!=- - #/-3)J/P.O&CCb8,J"4 ?Z20 * !*""3! #0C 4 $ $% "1';#63FZ:f{R&>*A )? $7$612K>V 1)@((*@Sk)=O%, DX,+<+- +-!%8*,@%5 8A3(&3      + $&5*  6K)"  + +B]+AbDhY{>`&<Zy*BbHhKmCd-K<Pt0S@> A.MTs. ?8)F0L%D >;2%E ++)I8X9,1 2N$0H&*#92#Kd@ =[3Nk#Qs!>aHir>gx3/J@X 36 8$< $1G+? +(   % " ,#40*  ((6 5 + 1?[Gg8W#BjAc $ ! 5%-1 2I20;R.& ";( + 92 , +*C$:%:K#-=**2&+?4J ^rK !3=K+8E,  ->'4 %'$        (6 CO8Ui;+C <eCOm(:Y%A45L7P j 1L 3OA[#8kD/ DW&?W,@ 4 .A*?%; "$2 ""3 !,$?8Q&4 'D)E8:T%7Mc;-C($65C*     $ $6&9* ) +! ' &5& +#0E'" 8E1   +  + +"7.3 +4 3' +"   .( +". +-"3) & Mb@8P$#= *D#; +,.- -  &68M,L[A  %83G2:O;M(;I(FW8  +        **&.'  5A$.=(5 -8M$3 (C^1H[3+ )#2 !53 4M (< #"6(  1B&"1* 2 ' )$81A%    +  +!   +     )" , #8 $(A3O'A$=(,8A\1+1=X,&A9)B#I`F ! # "2   +ek]babip%ItQ|KwdgMwR{aTVS +My +Q|[Ku JuV[Q|[Vn"Hte[j\`` ]gt"ajLsdmk^ CqHrMwP{^ +c r\jifa +jh sv[[ph^^ Q{X +] U_X ]YLy LydHtHvgr#Emw'cT|^ n^ +] +j~'|'3] jX jT|mta Owq ioT|KpY|\NuLs^p\ `Kx_w$gtCku%Gsv"^ +Yj9dx)3ugw"is xhq^ linqsseoy&dz+WEqas']Gy}0hadU kls)s(,sx!4y%qZ O| @jZ[>dQyKv LvHsNxkr%ekXpbb .]v#|+kjkdrg h ks` kf_ \]e [Zm\] hden l}(*oaIrmh.alz(q ` Ipl#l#gQq ]}w5:Yck\q#5Bhc[m iahum]g q`\ +}0b[b^h_hLvU~\as%ey+hQxIq`_ phP{EqEqw!z!}"[h r$kc ` rrXl~%g<1s|&flhnow ] V s%n T}p imz,hbu)ekq$fn&a_k&x4Qeh!q,l(y4l'Sp%9cbd(Gc&Z~g ;j"u/s/n+_}p+\}TvRx RwjY +eeT n'Mym'Gm9r})u(Ssw*fx0Uz `=Ekv.]k#fm!ax+^IpZ_ e[X}Y}No_Nt +ief^Nt2krfq`|0s#2w&dm T{IOun#i!Rvu+Z|p+m*]X}8<[Gj]]l&@efl$gm%l$<5fv'p!iRy@}9Qu dk$W|\Os ~/w#Nqlb d (h zvws)$y:~#t#h vzvs+~#b +1.p3,o{$pt jlj2`~ m*nill-!`|#qZbebnea mj] +.f _|$kxs,-5[wklxjr~%'#~!dsprbzt$o d~o_z v!t slr{!nli"ahb}o ~m eu"\}[}V{!EeGg%$A1N6; 6 7K$+.4= ++M)I!2OlB2#<% +0$< +!#  !)$; !0%&$2!7 !, ,B!*5%!6* 3(6 +4,( '6*8!GU*'8 -EshF)!8&= )A =X6T 1O 8+0G6 7*!6)6-22)>)Wj/BR(74C ,$8 *<' 4H%)>* -*7 +   " ", +;G/ -<((;+A#"  A[(a^)Xye)TuQs2Gk2VEe!@7U=YIhjPr* ._@sKSt*Ab)F6!=8Ts0{]'Bh?`9Y0O"0"#3 +#8",F`.3O3NVt2Md]'Db}:lVtDe XzG[z)'Ed2m/Or Vxs,4 _4T</N Fc$ 7D(*5!  + +DV2   ,IcPl%2N 0M 712J=U/I07R ; +3OLg+5N!8 +3M#? = )='B.0HI`$&@/Eg{I8=X%+49Og)by(+cw(]t&.D&::J`"$8 +K]"9,E';v_7N #0>Q!'h{P +w7I#0+,"8"7 )4!@4 +96 +- 6/ !"WkC#?4&6  + +" +" + + +1A)!&% $#5$  +! " # 1#7 !)!.%1<%7C,$ $ " )1 -D.G!$6'* $ =M0);. $ /  '4$1 &>43K.+A &$#   (7 1D+<GQ.%5 2B +  + + +  +  +  " +  +'4%65G)+CS(Pd3!,D]/06O@V=X(3E"  BK84&?Hb86P$0M6+0 +  )%", '$          +  26R"3#>$</G '3L'&@(B!:1 +.J!;W.7Q-*   + +    +'  , +2 +p2RzW~^cs"Ybbggr&br!WKsU}SzU d[V`=iU U S}Q|`a[>i?jBlf!O~ KyY r'l$j f[ ] ` mlQyV~u dru%cbijsea delnoaqa\ \ +T}b}(c sUkXbo_rSzgU~ +_d_Isp-IoMx m%R~dr#Z r&bV} cadt#frw#_[ +s!RzY bhmgKsY +dmT{^x"[ds%Muu"dfR}bdNtafu%ft nZWSb}*sTy00uv &hu` nb rson-t\hq$>jU __[ \ TYfP|Y^e &oztxT}_ +rlg]gBY jLyNwX~3o$T}NnSw|FV{_$qLo i$Ttm*f"}<:]|i!Qp Z6ocSKw @mEqSXJpjUw +@i"dHm:a{3U} v+beilw'\ +dKr>9u-8jp#71Z @hmdz'q{'b ju!ngoy*~.v't$}*`lcs&[Ew*l!m >v.e d!p,i!al"fo)h l#m#X[FkhWy ^ _z+gTzW| +:k\| a]Rwj"ipVz~)0lnlosz!vapx|%goq~!"vo{3o+\zf {%c{&ouosrqk|)Sqjpztig )t(`1o] Wgqbjo^`c 7hU}e*ni {j b{rmtnlq)qyx!nz#jz)6[w u&StXrq6kfjr4(bzho$m xv{]zj )`Ef +0XIk+I "?/K1P4 +#$ !0 3 3N"0M .I!0 +.H#"A!%:M2!"3$3!  + '8,? +/2N(/H#/! (" + 2I%#<-&*AMd5U+2 jPV2I3H1 e}M-K9!9 ;T+F 5R-K -E +31)=* +$<,EHa->U'/B%1  +  +10I"@U2   +   +   +*$&#4%""'auH)4@Y#1L0*J_0N`, , ( !6-Rih-%;4K&mQ-B\(7Od{E.>!)Sf="52 +E].$ %2}l3F) AS's0A  #0$/  "'$:1 &>?W /3$ !  + + #08&=I0#*^2)B + 9@YNh,2N4Q.I>X&(* +0 -- +(84H"=S(,C3- +!7"5+ &  +   !    '  # &#2PcC{R0O3U%D)H1Q 3R Rr&0P <,I d-=Z?]=2P +&!%5 5/1L5^3VAd=_ +Zy$Ea *&H;[}DTu\}0 >4^yG)>  2%F>Po#-SS\*A#e\G[4K/61K +)<F]& 8 Sn7$A /M:Vd|)BO5+C OGB`;ZkG^* +  + + /1JIc(Z^r1b#9433420+ &2$8 ."*#? 'B +B*5M6:Q!"" !  n(C $B UqB)?    +0'?6-2&< +=T0)=*$!2 7 442N$A>V @Z"&> '= Pf1 "%/H:Z )E0J0 +."7/!:'; +!3 2CW ?WSl4!= 2 !3 "  +! ',F)F$C ' +^`;',   +  +  * +/ &!' % $2 -D(D,#;$& (  .Xm6^q1NbCW-C$>G]r=$;+A24)F*65OYq?0?)# 0Tn7>_{ZwX nV TeEwOd`@fbSzUyW{\;b bkaKxWmjFqcw'Y +jT{ XWZ]6W s0Rx m(:`l#fX} Qs\m&Ow7aX +m[X}oSy Uzu.>d o"96;4|+6Ci$Iw,ba;YSy\cdW cgoe Or:v#^c?j{$orbov"0`k[ d[qkbs"Xgthh +xxguj iZ~l~&X~ssb| 6td o[~tid `o]dp_]i |ppmlkgkp\XjnUXYTTte mYt_f\Bpc-m(bn{v#+!nqger|+s!u$nXx=z%*fg.}#jxte)dg lkG]xa| ]z=s*)E,J,-Jd79/L"&6L /G. . $ +7 7( ! +!    '#6-H !/* +) '&  5)H%A16uEMk.L9S 1K(; + *6 +,Bj-Wsa{,0H2F6L +#*E)EwR4QA_Yw.fAF^#0P Gg-NCb>YTr,>\84R =\5T%C5S!:W*21H &=3D`)k@m@>]Ea-cق7Db!Pk,22D[D\3L8P-D/ %`wE$= +)A4/K !<E_f&1C]B^=q8wG|yFQo)0E  ppS0D_&4:%B3L!7 253#8-C' + !?Z,1 $'(? 5'.#87O1KGc._|; +3#-2%   (8 8 +3;Y8"bl,9A Tb3^]l9 +#"1   %$:5 *Ka9  )'5Vi71C %7#6>T^u/8N Qe&EV$3F(<.2H'o3N'#;&;2E / - +BR- (@-Tq4">4M"4AO0m~N "/#<*D!=Y42 #4I-,/$ +/% $& +  !4$ # # $*$ +'    +"1!.& %  + + +   !1+ ) +  +-<&      +  + +"   !#2)*!1"  '!3 +5=)       + $.=, +  .++<(.%)<0C#)  . +0/H'BF[26I    +!     + + ))>!#6% / 0*>"( $ 0>%  16 Md=2I#9) /B\:4M/( ") % ! " +" +#7 !1 !&/ #<EiBgk(Ox +S|XFoe$P| WS{ W}gZO{T Rzafr`i]X Y aR~ U +c`hTg[Yf`dcg`hS O{Fl[`Pw [T| SyLrq(d_^Zd\We{%nnarUR^ ]ck[x)v&W 9s(Gldhamt WZ ohZp'l$\ElKql&m!eeail`h~)_0_hau)[^df[U}T|_ Ip\ V e\ VS}Z^ [` osc x &i gYN{GtY ^ e1h/l{%sw$n\ lwr)f gvf hb }$~'d l^oO|cSXV]u&WVT kT^ +vss'AT~mou#deaiv'eQxTmY +]w)bt(o!Kv^Jwb`MfGr}(|'mFjdpubfp"R]T]_R}\ Z 2RxXXNzJvW^JqNv Ir AjCkJuS| \r'b!Jp/:kfd nix"~,s%Nt fv$h x"ntg +d +j}#f ab +mhmW`^i\bS~q t"\ +Ylc\ o#j`fiiggf] nc[ Xgb\y#Ios gvsrYT}hT|gUiln_ Lebk1B^>MxUR W []m&Pvai&c ^8i d[SzD7s(UrEe x8]~p-u4x9[z\| q9p 'Gd!Tq+,I #<-E4#;&?1 "(/*)?Ni:037, +% !'@3O42w^Lj&8>Y*2N *G3ORo!1N!>Aa#B'F@\$=%B:X 'Ec9< ,4Nm1]8U\w5Up//1L*F"B8X +1M6Ys FY܊9T[u0&<%YP2-2,DY2xB^,o$:=V#5%A )FA[(> C[!TIga"Sp@\ 2S]z58U!Jd80E!* +)FXv96R+{Pb\bl"[06*6MU7vMpJZ\l+CO* !!0# * / +3 ,F !0 +$4Rd=5G 2G4I8K$76K oAXm2 , & +#(7PF`!Jc 0EUg)AP:J (Lg1Fc&7UA\H^*#8Vk0i}DgwL +  '#2 + ) )$.3H*- !2s'cFoR}^Zha kcekd[ gdY +U|\ Ltj^b}.u)R{PyY[NxeeXVi` +Sy{"ych c[ \px"dlgPzrvd dfjw"e |$sisi`\~s{#Qxni\ +flZDqMxJt]eR~W V[ +boke ln&a +n] s eZ NxZNuccFqU jr$[ \s'ZAmAo^|0bo5w"g7_ +d0fn]z-N}T PR^Z u%Y R{ngk l&Iu?h5\V|GoL{ Q~ ?`bn&eLxBmQ| ]Whhhry!lu-x$p_iwfh md lvdXd nj_ Kss$Dj3ZRy T~Syl/c]Kq{,ft%~0z,\ gl"m^ oZV|;Biq `a \ +?=a{-W} +isyqmWr!t)Q|eqp:qie_4b.4Nz +f!Q^W]f#haet"kT ^v,h!fHhu$l ^__j!y1h"dy0n"e|.z(\zq!`et-Tz ^m"g[]Y v)bcms1|$~re ` +] onqm )kgqrmUOvz-KscXp(ZPwZ [ +nlf mh geYbc] S|hp*,sniCobw$o zxa~%qk p +n $pr/jxOxprKyTTYGuHvVd` ^ u!`emj k $mkwxlqp$u%w~&y ~%qpnljkMm4y pprlZv,y mk\ o~(sx!pc qbPq"B*F '+1TBd&De(-I.  &!<"<- %+ 1G%&8) %#% +  ! + -D$J[=/ +%?Q2,! +-DT5".;TDbFe;.M9[Km*3PBZ-!6)? $9*=2F'7 0 *7 6( 1 0Ue90F567T5Q)F)B 7O/F'>;R(2L 6Q%%@"<0L'@.B#%9%9%9&( + .    +&2=,$,      brGtxN#+ 6-M+P%ECb)8Y Y{4Ad9[ Df:[&Ok3 *,.: +4- (1Vm?9S# : 6 $1), +(@ +Cf.<]#:! $Kf8*F*(B8 7Ml%pHpHQm)0M&B!<Nh"e?$C6W1N42Lm4P&B_}5Gg!A;#B:Z7UuC?\ 5M7 &!<Uq-[x-K{2Rk Nh'4 6,>V)0G0.- Ib0sMc 9&!&8+);0 -0 6J%"3#" + +- >V'?X( # !+Kd(*> &  !5=[%f'C'A mFd* +*<8N 2H&8S$+*>->$! )  '  F\1"9 +0 !3 '<-C7M#0 #+.*D7/*@ !" (-@!,)"9[s<:N+   +# $%9qXNi83 "=)A'@,)(3^kMY 21j08Vd%N]O^'!, HW6 + # 58N&1G/.G $'"8mRf>i=-E 7O /DTiv;DZ1GD[1D# +! +:J'@Y)Hb)7OkHaOeS~Bf|>$8)A )::1M8P0F$.'3'(<#MbH## +)   $( ! # ( % ' / (GU?0;*$   !         +  +  &'; !6#62F#',"  +        $6>S0)<$  +   +" + +2 #2  +         +( +$       +  +!($-%;XpA%:U%)@ + *:   +     -0 $+ *@#( +& "80' &, * / 2E*9N/4 , +(B#:)) + 7   0"   /',"=/ 0 !; :29 ]{RsHjQtQt:aLrGnX}_ax*x)s'kX]_t(f_Ry ~5Go^]_eVjbYSbY[`]HsAnPT S T w+~17_3^XjW~[l#l"`U}gU| U{ +l!c`Qx+_ j]eVTOzPz j!NzMx^`n"5`^ll_ gx(hn \ +ft%`EoRys'X GsNzR} +Fnx-Y dV~T~Zp[f`Ry`@gClS{b!  + + 1=V31J%.<*&6  +# ,B,(<+ Rk1?\g8Hf""@-N :X+/B$73F %- +:P%* !2gwP 6 +8R"X'Hf+$E)J"No+Ff!%G*L4&F3Q6&D '/3M &,)1 + '.,:R,/!(D ? /RhM)OgLcEpW#9($>&Ab}K#@ 2P6S1)F 1Lg)C_)H>@bB`!<'@*5O3,HGe8WNo,Qp2Ss5"B,I >2OUq4*B +Qk,ǟ_r3Abl":qV~Hxc *?,"8*; 4NQo,vL4#&, 9%C2=[Gd*Hd+7W]|.$EJg6TGgSr("9  3J%|Rq2 ("?=ZGesK'A(>&5);/ !- &-)  !7(;N$$ ( $2' +&&( (5 Sl6?X6.J +5F_"5R# Hf,Fc+7% $3%9+ )' +2C*<*=&>*B-E'&00 5,%'2E##8dzL:P56L1G/(==N(,?(2C:M&5M3jCYs3. + +$    + $ %$KX#P^$dp'hxh{w=Ub24B Z 6M,BJ^L7! +      /A*): % -"5:N0* D`@0$     #  + + + &    + # ", . %  +  + + ## +& +.A' + +* +#5)!6/D +-B!/ &7 5 +>  + +  + +     +    *   (82 as3`_iSw eWz +>Ts ~4;j"j"l&JUof'Zx8Wd%C]wJ:|.?Cr'5\|bPg#Z{}.Rv*o%q+u/9Z{6`:y%Hp9o#YzUv z2Kq@g]|4Bi!v+_/dnn?nb2kt%cEie8Z\dgb_hPvcNt`W}[ +n2tmlk4'nwogyywpvXltle +vhe k}#Rzbbh_ ] ]T eW_ d w"kmb +pjikgYzw UwX{ GiPr^^ +]\~x %jrijlgZ~qn#|zg +{ jUxTwds!r crgvFsn_Gk]&Ko(K ;Z/PC?+P=#&A&A&A016 +.5 # %), TnD-H#>1K" ;.0 )C/ +(1?# +. 88P.F"; #Md::PHa(Ia, &>54*D%< #: 4J *@5 7 0K?W' +BW0*>"5&-=" !0   +  $2  ,;* ,' &4:K!e}DO)\r .I"; <$BQq03Y1Q 4!8P2&3F'/H qFb}?5Q;;7 1/J&B >YE`E[#9!8CZ%)E'?2+ +GW0:J$gzM0I +#-J +2M 4K g|:?QBWCX\w86PQl=0G8K-Dkp``d_ +#!-#  + ( " *4$#/#     %1"   !-!  ' + -,2, 6* =42!>3P7 @ /K">KnB@eLr Ks Qy[LqTv[yo,q,Pke[U~ l l_{fYxImec}6DcVyd#p+Itw/R +S~ EqO{ Y^k t)p`k!ZO}ho$Kvby)\ ac\WHrZo(V|Y|Tuf$0Rl&GlW{ OtEmMvYRMxGrS} V \m#2Z_.Y^Uflv"Jr^Qw>eX~ t!}%UW 6ch~/t!Y j!a0XY[_ePxZ eq er#r%^S{ KtDpMuU~rkg\ gmbam\ +ssfs|'` hR{y%dfbskv!gZ ek |pf d`^ld k&]Ud e Y S{Xz!]g|&[ k[]8u,r%igz'w!cfw%aoqa z&] +k[ jv[{'t$pu-5kf_cCtL{X P}k^e^ v#q~'piXdes+mop]` +mo` j{%U}ec~.S~Qx^gJwV~ YY Z ] cgiErUYeocdf ri owia +`x"Stv"lbh rpq$q"wf 8z&tBuF4v!k?z%1t/)z%.oOx`YU{fOt^Qy +lIjZw AYH]?ax\ud}/p2d}6G=d6Pw*;bYh`dn#w+JxVFsEsDoFpl%r%w)am!l&ai%^f}1adk t+q*~5Pks*VnG@YqYuc$q2BXre~M;3}.Vu)m#v-o$k#[}f=okccYzagjy&lfZ k_Eg{1[[Hp^:u&MoPzq*l.}-gqqfkfe8x$z'{'y%:Sz |7Ny^f&P}N{\Q{g-Y|OtV{XRv6\h_r d "^6Ts]z.\JrDpLvl#l!7OugV} eahrj` U|W}y!y j{%+X~sqz.\RvW{Su\}g_Pp7q%Eh}4y2KjUwQuU} Qwl \^Qz^]LsZ +r nc Trtmoxcbnrmbh ij] +ly#f|%f_ QybYojZ fS|VX Fphv"oa_d[| PrkcAd\~TuLnTv] aZ}Npd[}jTvom} |#Vxz"z#tilz|s'{ne<`Yy ibpfm}"'YwStfLk(FHg&I'HB&G.T&I:(65Q$Ga5! )53%F5U-J$A)A$VqL$B'D-7 @\+-,%<(   +&;H'&3(7.=)@ Mf+ :!8 *@V.*7G$8L^x>(C4O)41KcE\v8WRm(Tq#8ZKl%0P 'C6A[%!;">&D + \D%C!A"AyQi@7Z@a=2AGmHl$Eh))J5Qq/<A nLqGtL2 4) $+/.+D9/>T+.$ !*"> )/K6:"< ( +.S @ < +4M";+C.&mK<#AwP:CfMn"Z{5Hj6X2PxVkVo +:a"+KIfa6H*@%= +/0GȖ 33.G6-I gE8& + 1  4mxuH*G -J)F 9X@_c:Tv-,Nh6n6P zWRc:' .I!)C #(?">.G5J  !8?Z*8U$$5 '& $09P#1A! % ( &5 . &7,B+DfM;U#)' 6,>$ +Le525QPo)4Me@8W.MCf*>]Ie*"82 "2*:/@. `|E|]D\*% K[=?W"7P. ;XuB3M":.( &, 3%3' + +*  BP/!"1D0E$3   + # *;  "6) ,53!:5 +@U#* ANTd&5FCX:MYi$Qc!ASgz*""13IwH\./E1A]";2G!51B7G5I2J 8Q @U)% ^pDJZ@    +! .A!,+!5 !-# +  +  ,3G- #( 5C-'*4D. # + %+ +   +   +    %   !- +   !   * +>8J8F*  ))  ' '"80F7 (D6P)@/BnT0A    +     2=& $  +  ( 2'    +)0!W_L         (7  + #2  '- ) ( % &##3' %8(% + + +%7. #(%&dp)U|SxCkW~X\ +Wr#JpX~w.aQz[-jaOvFlRzd\V} Qw_ osz(Ssf|*efU{ l!KubYfow(DvYmckq uk +f +)cqfknj oQ|\}#l[pkns"`v-t-Lt[Z[ dNuq eJqho ] c8]e?^usz%~+ckw%ljadey)0z(,4bf^`UcaP|Y b\ f\ j[[t5inic jEXY_ hr$HqhMyt*Z7`y/U` +jqq!b`kjy"id.{i g x"N~_c f[]y`z^lvh vvvstL2A:(~",&>0*;(*({#o3}(y'Z}14d{*T}dq{(G:5A2op!x(j~oS6273ex'u%m!o mkm!25EtfSJyLvOw +_2ns~-w+x.W| j!}1chm#`[~o(o(Ztw0Um8DfzG^Lm.u1^{fo {+:u'{-h`|Rnhcs-Ww 7y)|,jcOsbQs2r}'hOw8w(OsSy k!YZd|+z(SxV p#7-u'ilfd ^a monw#ow!x&r#Ms>ZS|TNx>hKtY}Mp@c Ilt56Vr*il` gmz#~%4aqGrEw1Z~2T{=cW~ S}WDla|(slZ[ ``Y}h1a_ [ GjMqX|8[[{Eew4i#gp(Wv aa9>QoHg`7RxZW} _l f^n!<_u!huun^rXT{yyo$hnz)})BjcPxFT} XJrfo _jFlEjAb[V~ U}[s%KrSx\}bBaWw +\}Fgfe?bRdLpb]v(Vz_ ^a rmnh fl` sac%)k l xihbUvv!le}m~!'zkn\zUs7S3NXw'9Y.N:/N<: +"?7024>>_&M*j(4 F_0Ib2Nh7>X%/**$/Jb52I,E :R - * .-G(@ Ca ,<\7[^~6Gj#=a*N F%I)L,SJr&Em$F9c$L!J;`:4O#) +*0 %1 00&;4K$0F##>#? }V^#A%&A 7R6TQr1?b ,L 65P+C/,)VVt-d<3TLp!b1%G+LHh4S 2P ++B_i!^w4P 2Pd~ a3K y4AY)ϊpD?XFa135.D )jH',. ' +*m\?V(%=4 +  /-GF`, + '(K\.N`)6LBX >O +ar(8I.A6H fvW  &/2M6Gf-;[<Oi+0F" %&7 +A4S+F +BZ4:T?V1-1 5 @R.1.h~I0H%>:PKb$]w1F]"/  +8-=':)B [uB'9%8+*=etW3:2' +HX<1 .F4M0G! )# *  #  !   %2 + +    + +    +  ) " + +!     , + ) +    + 0B5F0"2$4 +   $*7( + %"  48J+ #  +*       + + +%", !(*7&!. 4C-'5& DU;6G-1A*#  $#0  # +## &5!!54' # +( % + *&;#9  !.C&( F\>"5 +4":1 $"41(/Da-Qn9^|E5*F7X%@0M"? 8'E"B8V >Pu ^IqPwOv Flh$BgHn MtSxe$`{5]w-s%x&q!ez-Nq`>Qq Uth"[~TxCjXO{Y?jSBmBmIr +m,X}GnWi)IwXUFpLyR@ne@hQz Kr:`Y +gU}\ `U| a^`XqkY` pj]RwRx hOvCw#AfrrcVymfQxe\2^k` gg[Ex[k !ys %wcj a Ztodl +'ug +{.nqkP@ZZ:^>_EeKy+Suz)QveOv:hs![ 2}'4*Y ftQx0|.A.Ow FriT +p!] dZ e`Nyf^ LwQ{9`W ZZ S~Al\ ]Mteg\ ] q lMrRvu!a8hj] [W V _NvCiQxV~{$iUPQ^njrbg`atijiw`{ _~&tWd +ggn:&ruqi4f.yu%3k;u%oruquq~%v2Eoy%J)s]/=p7y$7y$w#6oZl:,Iq!}/x*c:w!hamW`i[K} p*fSvv$0jHf`w*j Hg9y-cx,ife@]uj!Agavm(d!f!f]}p"1|)kk\}MhAh Oo_~Oq~,r q!KqhPss$a>x qGjmSxOwV~ +cV} s#hf~1@fm#o"fh{.fm^ x!o^ lv%nmhx#.{,Sz ;2Xc R{MvU|W},Qh2>bd'x5chn"l_ng+{$A~"h`RzgU} +w(3jY +S~ h`fgh] Rx`[n>ebiclIm]X|aDc +Sp9u0e}!B\r%m`|Vr 9k%\0Lx2p"k_\mj33jr%mi?srhh r_Xe \n~,Ltz+jihYkfNxv(@iSyk@gPu +X{QugW~ +DmOu Jo;a]DiRt VwWw]}Vs^|io#Tt]|#f^ _}Xr gkNqUwVxo%rmVxv#m|#+bg x{i +n|$\ ^}b|!i g~~(B{tt 1Sp8VMi@[ +Om(!?<:;A`(= .K.NKk206Y?>=c1[Ci';= &!>4+*, / +%A#DEb70H#1 *. -4XaC^!9Vjl= 3D&7G) + 3-F8O#7 )4  !:O1H /1B(7J]*'? +8(Vp?-J;V%4L/+9L' )=%; $= 2H0 +( +"6Of5Jb.#& 3B#," " +  #  "# % /. )A1 -5 (=! 0 7H%DZ_y0b~-*J 3/M< /L5O$BkGVu*{Oa;-M !?&C ?]!@^'E61U@a#Vt/:\!E<`(M=`6_$M)PNu-9^>;_"Nq1w^/I"  +#, !4L%(&>5UqA@^ $C?_ Hf(,J 2L0&D +(D ">/ %!j=&E7WCdn@ZEh(G 8VHf">qETv9Y}:s;5Ni ~?c{1J`'#7Lg%d9 +)Tp6}JtSn/:$?vXGb'x[ZA^{@W4O#> 6S"(E/M +WbO"% /G_3CX4) !6#: &9/ +!*  +2F*2C( / + # 1?$ +&&6-2"<%<+9 %65F1 ,FEa+-"& " (1&3/ ?\sM0 ( *,+  3- +8P1 3 2 SiB1 +&=2K$=/ 0/-#    +  "2 *1" +  + + %6/g>hKs;^6[Ag9b@kHt-ZKvOxR|Kw S[S}f%Py __\fLsUx +t&Uy m[ QxJpY Szea S}]W~VS~ bhdkXgVhLuQz_Uiaa ~"tCo` \ ` M|W +P{bw*] PiYWnHrS|Sz i!Rz S{pNxejHqy$mdV}Pvdb] c nT}ez#TyXgoh~)aR|t g[ga f x'pxp ds_qq*S}kpujz)l\W}ift$KrbRzIp +=^7Y8Hrk|iz4pnkl{&1Wspt{$snjn"Q d k&Nwij|$Vt9nb3Wv H4A},]v(Ssl TGp&Rf l#CJk%d_~5l2pu;fhLl7\Y{Op|,w#vk6ofpq+pzsPs{&u!w&QyZPypi{)s&gU~ Zp#|.;ImOu[ h{&s\ +^ 7ljv$r!a`YGn[S}Ox YDSx[MrZ{Rt =_~ko"ia{%b w!qj mucYz':y$T|] Y,gve _ s#^`^bf|+i_`lh[IlTt y1H:Qi%\wev+k!Rkx+g~^wl&Uu \}cc-kc~~+3Suz+_Xymcnti s]mc` u q$Hn}/W~T{YS|\ +[b UYx&hQxXZd{.o%Hm=eNty1<`Sw l$Xz \}Nm\~Uu +gs#\| +g9*ZxTny.8Wv IhQqy!n#)Xx2fb z.nAi }{v'z$Yu f*$~$cw +3"C.jLia~d`}y3ToHf%D,1]~@-0(E'E +_~=;\2T"E 1SLq0$G; "==Z24)B*& & -3 <" $ %n\֜au9%mm2b+'F0H)='; .. +-@&:/7I".B# IZ2xL~@B_ >YQFZ%8); 0EF_(cF=W3N"-.C0 )  #1D&9M,2D& +)"7&7O#). 6G&! *# %   %) - 3'+4) +$ ' * +* Rk'=Y:W/J:5 < 9 %CPo+4U #C/O!?8#>>\+I +9+I Ru'j@+K9Qu&Bg2T ?/Q;] 331(E07=Y(*@&$'' )$9' 3L9*";6Q :,C *B+C #,?W2\w?=XEe9X +6U "CJk3'. 'D#_z/^{#f(Sp%Yv)(FWxb#@a3Q '`z?nBVt.E_'vYxS X)E\x|MQn+;\y7C`[y!Dc 8Wa~*j='2 +:!(D D`!,H9TqCozT@R;\u?$6$   (+>"(>4I  +    +&#$ $ .# - "7Kf12%EPm554P4M$E\20F#6 % ,>V('@2&8."0 +! +  +, ?]:X.!<;UZqBLa9*@:I*%$!$; 2 +';': &:2 +'<#<3*=3E!(iy\0@!   $'7#7  +(, +% +#:#) "8#8+ 1&?vCp6La>R 9INdUi*AV)>Xm*,.B'& )7,I0 $1G/*A CY"H]*  +(8,=!(<0#? ;9 &<)%,A/ %(=?V,,:Q,C +$<7I   ( 0F#Ob>k|Z8E'0@3F ZoM=G: + + % (:!(  +8H1 +"4&  *'8# + +    +   ) .&   +  + %"0* + +  );), ++ ( 6B%+<3!9 *D!3 + !'  +#     %%.   "&  +#2!     !  +$$ +$% +! &*( 1-?!/B# !+?"' ' *$'# ' )  # $-*3 ,,@!% +.( +!91$@* !%6 : %@1 )2O>\%'D &B.5$?'D3N0K; +Pz Mw Hq^p/p-k)cg!MpEi`k'l'Av,_nw)bs.Ps v5V|p'j |1W}Jt +=fU >j :h9bRa(:c0V3V0S)M'MMw P|p%ItDn=gDng#Oy Eop.;eLv +p*Ftjt"{)w$z(k`Nx]Rz y'OxiUnLsQ}cGo@eU{k\ FpkHsOzNwgd[ +of mj`jiRc^ +Cnj[fh]july rMwRz^oP{[Ozx!a +gmEkAh]w)w(ba\ch_xwmd n6\ja uzuphspubh sdmrpibS}` +dPzFnjn"Dn@hge8N|hLzf[ FpX :`h&{6V{ PxU|W}Z Dh4y(X~Qw ^qjBPrQv_cW| HnQw `Pw +l%1ocii`i"cZ ZpXXh'd np+\1Zoa ^ +i~"{"(4k{IpxqXny&l}%gov}"rl cs72>d 0e f hv)rmv hk9t)1F{#)?//(*\m?s$lv t69px}$b ] \Xg _\Fmcppse k0c[zu+{..lt"476gN:Qf{W8q&t*3Npo{(c}%.a&sn"6W} e@kp/1{#x"rpr-` +*k|!c ov-j] +Ow'cTx In^t0Pv[~i#v2bS{ _gSxsc^ _ U}oj[cg ]Yj(R{ R{b^LsZePpXwdLl`Z~\~j^x#n#olf])j{!_qgmyi q[[ +kJo7qdgi.4hn!h`a5@WmSl j$d|z3kt)_Ph TmVr HibDl::o>heRtBeRy k\|zi knpQ{o,LqJp^b_`egc ^d kiz&_ |(Ns6]X| g!?dk+Rv ]CfEgcX| [~]Sy HmjUrd ztuw rJf~w(Sq**t)|"sod^{`|_|e +%b| v|f %[rz x({/s}ay]w\yy )p}(^x To +Yz^Vu=^!B / (!> 9V(F<'G *I-N7!@ -H2 2"5*="().4R(1J(-  + IT8 #4k,8Qd{1%8(< ':2RAf 2S0O =Ty4pN8X+H!=3,#= 1&( ) +40 +) +@   %*< ' +=8M#*A(A2L1Mf3 ++=4$ %A$C4PXv/3R5S(F2Se=Ba )F7T6p>Eb'D=XW/IXr251K.;X3P5O21J +#15 6- 3O)5P=U!;Sm'p>\v+"/L-G  &91  $1 + +   ?M+&3)'(# +%6 +Eb/Po4&H 5!? %;U)' * &!1$.  5 . %2*6$ +  '4( 5C%^zD@^:Z Ed\{36 !6cuF+='#85K,C +!);R(,B'>0 "&F`4!; +-     2>R4 +  + +$Pk=0 +, :NfAU+G,AYm)"63FM`+!4Oe4Jp=Sj&BY{U%(9T"-H4#>+D'> +F_%=U6>S!"5]lK&:&?%A55 ." AX5,4'. $8#8 .D';!2 $8*> + +#.&& & + + & '# !2"    8F3       + '+& /;,   ! )#    +  / ('!) +. !+B+D(:9K &@45 "= #>7= 259#@ /E5,/( '+7`ZXS|o-f$\~d ^Pu\:Qr k"~4]|fmek"CeRuWzp-ce\PzFq +;e+U/\6`IrLs=e2[`&d)Ps0Te&ZZby.U HrBk6^Mv Fnz7Kt LqHr?gSv.Jev"eVT`DmNwcZlglP{N|PW @iXZiqg`hX X lid Zroe qe od V[lrz"d]hsu]oamk}'}'dix XqjoqQw_a8on~&prl y{^xpf jYkjo_ ea Yb Z_h Cmb +(o\ ^XoWQ|Ox(Op1Z|Efr(jc_Cf[s+MrU|-)mrrotbpDfJi_~~.<]}~-az&jx$iz!ZtLs`m`PzX\ \ +U}-Y U +_q I17^V<Fl @f :05 & ( +"2' %+"8?XsNIY#.A "4/ "+B9 3L5N-$(  " x + &*; 4 #) + + 0[51P?a-M +9X$E3V@=4P (,!;0)1F!+,' 21H"3 + "(-( " +  +   + /C Jb1$>4 +EB_-13N-I 8*H +=]Xw/Hg2P7U.7QGc`}Sq:W:U<;]6YHXmDJc&Ddrs<^y?Xx,I"<Ha6P %>0G]#'6O;UHbYr2 79O$$9 "< $!<: +,/E/FQj)Id/L +yTbCTs66SQj/:L)  -(!33 # $/    #+ +*  )/#C/1OPl8 9 Pd>EZ41F!, 8M$/   '%.b}B4R2RzM=.K E\( 3 "$5 L_<"83*0# ("="=[t<*E7R 3 ,G :R'!4(&5$    &#6' 1( SiA\tJ7%<-6H Qf5:R=V3@T-C +e|A 6*>.A@Tez5*>8N~NtK1I'? ;V%A +-1,H9*9Wq3Of+,>`pJ*  -(D0M. +  /, 4)# !)<"3'(9H-AY0*?/="  + +FOC`n]   +   """        +      + +   +,*8- 0 *5 ) '    + + ! " +,  +    + ,"' +"2 * 3F#3M"8-$7 &0=-    +   ) &+#6):(<&  &3+   +   .$6)  55P' /=%'+<HY4   +   *   + + % ,/#,D"71 *0 $9% + " ++$8" ! 1.2(5 % -,A"(!( 1 %2OFc,<$A *E%?#? < < 39 $A7)D*/& 5, +# +$5 [TSQ|YX|l)c!RuIlg%_{+Z| ge?4Zr"*q&h(iVz(*Tv8=^7j~,pIj0Fi1]msn8.wvhl~)rhw"mv \ f.j-yB`IoIoJfh1z%;:{'e{&p/-}'u{%r^ 7l9Qu +bcu(iElKNxWcOwDmcS{d tu"V|mmb c-nl\ v"|"x]|{1ss~dm rk-/ssxxl}p +~wimc d o"wy qsx#hlZ faBhw$~,Ckn(o-wi >z:e`6t|$rae z"v!Z Fpj KuIu:dIpV}y-_ ,y$~${(r"Yx {)*)"$Wz~-3*HlXwssvb {#o+;1w"51&'K|!trgx"d qBgPvv,S| +Gnit'fYx,Lse|(kiw#z'^ X [ iRv +Bf\f#RyOv `W{Jpd7c;8l%j!n$bDfbl IlWyz"_ +PvJoZ~uo l w`wg,zh8~'kw a ,0ir}%legv j.v%qo w,Oj_y~6v-Iid\|>?cv*iw&n1b qspi=s(Imy4X~_ho\u w!g^ msno k)hJr] +{i +l i hhQxfV| +QwCjoX~^ t\}$ka +hc] _%w(w,&+1+d\~o}x~xqz"i u so {z(*{{ !!{r x{8tlm p +'*~W|Jk w6Mp7[2; 2 +1 C]4+F+H1Q(>*>!    +$<*D&?*> #)$ ),9  ѢIV6{kH_2/LYy5cB7\> Ih;1 )8!/ + +  + xc'9 +* +7E2C)<1AI`'#>&BD['3K"#/(4   8A% !'r\EZ( ! '!6N'C3)F 1N;.L 5T16T$A%CJfC_[wK8VNl +JjQpm8b.q?t[v&Wq-9VDcb~)d/I,E4K-EQl0v_uE!1K]8)?.Ge|D-E. & ", 3OJj/)E ./D.:I#+ +   #7 9 3H5J" *$ &#'% +   +1H.=$   +  + + +   + +  + 5G,*;!& ,3E*%5          %  "-%(=' +' +0<1 " 1#1 ' " +'5+%0  4 . (%6 + + (3#  +  +#'*+ +$"); +$ +-   ,4$0 "3-- ;6.2#;R'$9% % $:3L)!1#1  # +8%*  +#   * / +(/.C 3K(@/40!6- ' - # ",A"0- - 0/''5,!9 "(3 &%5!=$@0M;"?.J"=-K:)E,9 : +2+G%9/ 0 !%<6 $dSMx `!Lp f$YX}Fj]f%^Rx +Jou(n#aRxhi"_3Vl,FlEi?dOu +V}^XHpWSLs CiHj%e!t-7[Ns +Pv `Qy[ oY] ]T} j![Z}Y}@\}Ge +Tmew']qZtr)T| _]FmY v'S{GpHsR} R +M}QX d][ R^ +fcjdbjs$s#W +Lv]b jf ^f qz!Yq\ldcHrP}fRJtsR{pYVb fa \fdt!gdigLz[ Z]ck} ~!wr~%P~Z cMz] VY +Q}fO{ebZUVWYOya LrJqPwAgQx >Tz=aj [}t$kiRvLNs-ym smd k_p{)9] m>a +x2sq~qru'tv"%)s,e +*Xb jYMt~.a*w+ib lijfw%q*zp(scmwqu(|#s7me{/o"Sz X~ 4Yh!n&=b4cEkYY h0]` b soux".{$mqz%d +ow2stmh cu1wq!{*%~sk +&wQ|gg ;nm]_ n^ +\ +\CiRxx.l[|*vmkrn|*(&iam] +qyvpV}kClmw(u*T{ [Dm +Jr>gX ;t#}*NuUv Xx:j,ug9z'33mhs"}.5;6fK}.v(p )yk4k?bu$m3}-Xx3^ +g}%u.a~ +)}&c{#xr{#.IRwt!_ +2kiKmGt!2fg_Y i\u#Sr.v"YOx]a^iPsPt +CNtFg Fl]]_~4`cRqcp Qsjs(InVz Uz .TwNvg'% q xf&3-ja ,)'?g xi]oRwf +w3ap.q)x#qfmmkiey&?`hf_n_~ Wuoo.~&q}"n$ks`_maz"u^soY|]` vrvjoh +Z}%W|lfk i vjc iOwKs` eopk [h k[DjdTwcx8(*y ~ mol pu^}k%h ,my|}(}w|s |mv} zi+ { xto(Ce =]%G2UId3* !625Ga2.. + " + +#6$=/J$ - ' +8*&- Kc/H`* 29: 7:+C"  &5%4 + "2BR)2BDVSh) +]u1Me!A]4Q7%< +"8$  +( +JZ0"1+ +!)MY> +k 7G:J%/.+<$6 +% +%8' ":%'%   +WiG53#(ED` k2u=t:PyI}Wm'EB]*C";ySWq68-J*J:7W@`6Y#A#> '#< $" )9W: *&5T&E +# $( "'>%!   %6,F5 $<(@  + ')# +( $14D + (6 "*!,9,& *>^&E8U5R07(6,H (C8T +j2j-ai}6^)DD^Qj)+0=QDY%*Hb" &8#= ( !!  +Q`=+ % +"/'C0`}N5R6 0I 9N(CX/Og42K1P @_ '/1J"/ GZ7$Md8jZwBe;e<|Xc?V+  %  ") #" %"(>9P$1L#= +-H)D-+<( % #EX3-@+>!% %  +anRSbD ++* ,C51'E +Mj*6OMc0;M.>"3 .@#5 3D[+"<=Y 8\^<^~=8U#<+C5 ( ( +8L' +EM; + .C"8Sg8.;$'6+"4  !(   +$ * 600LCZ+ /-A;L $47F"4); :O)B:S+Uq)Mj%1Q@]"(G2O931 ,<,.9"& +)/A 1 ,' ( 0 !&:., 3 ('%17C)-( (<,?+ + +  +  + +  +     &" +/B&*< /!   #   +/%;";)#; +   $ '    $ +  )@*'()(7!/  +  +  +.=$!"&!1%6,:"!.*' )4 + +7"1(9,00G$'9K)&;$:1E!+ +! =T(1F* 7E+ +     +   "1 + +  "*=$( +- 7/I!$;/=U*-6 /* %9U*'  #(B!/4&<& - %:*, / 2 7 +15 2 08(F'C =(E">@=/K: 8)E,H (--/* !+$8,\Lt CmGl +Que%^HlKn RuNr^Ot +Lsf\[V~ gPr h%=a`Kpc'Hmn*6\@\^^S]Ko `a[v-d{5h!l b9p nliLt\]s&]| u)`{Tndv#XjXhT{5SwW|>bIoW} +Nt[MuIu _Pw`K{Y +R^ WM{q$ha^V~ ][cS~ S{Py1me [ki]lt-LvZeT|heXpXz#pVohhnlw sfb@gOtZP~L|agP}\,tryvc =hX +>kR|qU` Z gx)ma] +)YiavnLun!iFk\~ `rAf8lf/sit!_ bq+``e mtl|Pq}#t'5%| ||h ,"ri$i +*rzlszi[` _EoCjbV~qjf^ _ nojgPvc#sbz#vhk~%x#j('c wm_ hiLul!:`?3TqZw-bbJrrp^9sk**kgb +pjg` +(qssv{`vx0ozvup~~q r t|"yz"c +os}g gA] Vamy+np!9i6y/k!D6k)d hrXxhn![}~/fnz#w|$',}$s[yp+1mmxlm3trr Ed3{"&plgv+fet#] ja Sy` //Mqv%0oWxOs\a\X~Ip`Uz gUu Uu \} oTv}'qgfQxAg-V}kvl~~ ug ;Y}y)Xy +\y +})2lpnyc t^rb>&2}v"co*r,v"\z ]| hRs+c oOp{!mre+sn?vfxbQsge1)x\{sdkmX}cllbmt`dbT{bg _`\rU~]fZe ax-fg V{lo&ehv!g+(u sn l{iwvfsty*t +e|zws{ |{w 3v}~z + +o*v*'HYx(7; 3 )%/ " 7": $' ER4q'  %. +'<'8 " +  Ue@=U Ni5.AW&B\( +"-01   + &6+ % #GR3.>*>'(=F\H]@V3M +7T1N *C ULa/ 15G) CS5q}bk{N\k7BR#'$ +/?)  +8O+ %( &,-+ >X&i?P_{1Sl%(D&?e})O2 +-1B#% +*   + $;w[= ;\>;.38 +%)Je,Sm)U,=U1,  ( 2C"-=#2 + +   +'#  +.H.J8V5 !$4 KZ1"?M,!M`7!9>Y Mo0C`()F'& 64G ! *8! %;j 0% %2$ ;K2 ("# & $$   9R'Kf70N,,( hxV8K'" ?O'hz@->8J#,CZ&c}>Po*!@8W)H 9<"?>ZLb0  ! 1C9I)  %" +$ +% + +'8'9 +      +%)( 3 1B#) " $4#   +      + + + /!>!7/";+ +!*7$    #-$ ++ $. +%1 + ! %72)*<% !),=$2'1  +& +& + +  + +' +  + 3:-&&0$ +  +  + &* 1A$9HDU-+ + +!5D&( +     + + 8G/% +,4))/-7 -+-"=1%@ ''E+F0* ) 15 ", ($ & )),$7-3Om<*H)D2 < +$@(D:4(E 2R<(E#@ !>7*E7-+# 5 " #Z~BiZHmDhr/`Pv Ot f$InW}[^iP}ItEmJqPu +dh%Ekc%Cg Mq D\:`=cLt W`baGnIpf c!>h@iEiFnHr}-{(kDn>f>fMve;bu*ZW~ ahdU}gm{%d^ +mht!fgmum.v"du$jt t!vXf^X [ +bc_pjxwikkpdksqy%]a e ]dmkW}k_ 6ayo%f +Xz3klDgQuc6w"S{fk poc ZpW{!#h c3Z~a =$|k ~ v#vsst|unkwtoc +ocrx'}S~ztj^ +v"h_ n[ +\Vm{%hw$jrw#d yr-t{#~)oYtr+s|(tiz)hciv'eHjg^bNv2lnv$~)wnp6"-)y t{$o'~!u/-pi}'$|u z,t *x!wghrs^ z$q}t2X~dfY +.a_Sz +m$]d'sZ/qw0r +z{uqlc}&r'u{r3jT~|&^ a5`@kUahu!OuYz.]}Ewgm6d{&d"= (%"4+ +. . 5HJa/3J^rC & )7+=.f}OnM ! evPH]7 $-> $0*3 )/?6+=V%Pj;6P% %97H,+( $q]ly_  7B(ER.Sd.1E)>6N]xg+4N:U8S/!7!5 _oK+:! +$ #  Oi'Ph,/:RYn75G'nEt,B!$+8I!# +!% ' + ")"5#%9"5  %&b+F0I1G "Vk-AVdy-u:Ni!=2oHWe}982 9 ';<^=a@V{3@0N7  #- 6*B /KJg+$C+129)  &  +  +   ?U8 $,%+# '  "  K[=( + +,5P3Q>5)J ,M#7 *)-I%@520<[ +QtPrMq Jl +_/9M($ '5 .+ +1 "-,!6 *Yk?$#J`19Q(#6 !3RhA+    )   3 1 (+6N$. + + dW~^XNvk%W~Y~l}.Zc`eY T{ bjz-a_`Os +=`UzLrU}lY-Agb] frig=dlw'jx#x+3{'] \ do"\U}ij`lqwuwb lc %pkhui +o"gmh { lr^kh` +e a +mktc)vUth +1~'ny(t&b\}+:` 8py(v e]zufbn%pwes~"l Rf'x&ntj nf srifpmws.fj ~ zh trylga ev"w"lj_ k]r\mksowikmtou&u mey%llp`PwX}n T{ YZW} W} +{*/w k!qp}!m~!x }&v hb)q#yr{!3'=u|w yu }qh .` g })h{#1Lff Qye` s$Rzied "q|v)gl h)vomc /]ytn1jp$[dm'Fu=kd a]mZg84jpw}(0O,c .pv!x#,w!qf}0m$EZ|}/n=z)x&+~)a Y Sx +t(j!b/K'< &8 + 2 +!40'>Um; 5 +5FFW( $34+F3Q)C%2*C 3 $'3 +4+E% + +7D0*9  %  VeD #]o<_s,;w(<;U6Q;T$>(= , + & " ' .6(AXs-Yq&3$3FJ_b}~L !0L_38L4-#9)  1=N*0 &,1 (  *># + & + 0$ + +&8CWLbD]2K +2K ^w.=W,CLf')A1OLl--Q Lt&c<5Z/Q"A)G4O4M1H:P* 4-%@:8U#A17Q!0&FDa72K%. , + 3 90C/  "0 .   + +#   4E&#   0-FWw9<"@4XtQj30 : =-LPm$%?)C HdTp5w\x2K. +, .- HZ/kB & $)VmC-E0 +9E.$0 +EFe!*K.L + ),E^4"  ,D-,1Rm5!5Q -2%A$#;ZGg)On.C`*#+HPp80 +8X; $>3F%)9#2#!,! +  !"5,%D 3S-G +  ( &   &    - + 4 $Gf+L$C.2H!BV)`rE* & +CQ5! "/?#7!28N#(0&8 WiB* + +  ** !/9N+,@,@[.0  + #  $L^9JY4% 5 08 2Iu^* % .>Tg;>RXo3(D< < 3#,C0M.G2HSd:!1"16E(-+;@Q.! +AO/-#%#)3H%<+ 24C"  -/##28E*EP=   #2    !$%  + '5+ )0F'1/    + +  + +  +    +           +*#/   + + + %;4'A%# +  + " ) ?U,0F&=$/2%&:,/ ".     %%% #*  2;$ +     +  %8B#9K"7#5#-"@Q0+; 1?&       + '  $% / +" * )@#8)1 908> @ .J-*A35-:5 2 01 +.K -8=["iS*F$>8 ++86 %?5'@43886;;=5'@:44,59 83- ( %, +!'"g\x5u4o+Y|UzAOu aacr(hgjfif@u*o&k$v1l(u,Do `R{ [h#u.f#[~Wzi'cbr"b}qv"(b RrvIlloas(Pu_ RzQw\ X x,Kn m"`?aF.qm>aMo^~^|n$Dd4du%V} s%Vy .NogunpiYp]5Lth\+vm +k g#\Z|n@*]kvvmrts(#q#Du} "i +yjye l ##~|*{}!~o'}s ~tm '~`m~}ou%q+x$pnh {p +}'{d&rj ri {]hk|_r p p sw%p +mruR{a~Yb'~(uPvYs"ekz)g y;y +{ d^t m +w}na&vwv~ynp mx y~ye.hct ry @`AcaDf65'C =gVq,\r5/D"75 +$,8H*  +CT%*:;K2Jb6`~;8!8--G/H +#2>'+UnA, 7J%- %  +brNv"2'0  + -GU4%u&aOl:VNh0L 9)?*;'57E*#  "_sK3M[w#k0VrI_%9ax&Vpx&I)%!+?-( :K0$ + ,!1,/ ++ % / +$8&);!)'6%9H!Sg/CYQj#3OGcMhUD^h4&E*J >b Ko)1X#F7 "D&F,J,+ #1(C/M9!89W%:  ).7O(0H!/     *4C&8G+%   + +   - +! +,%<Fb-Nl(3 *3(9Je+%C4Q +Mg"Qi25C%?T>Tc#:*(;" 7B0-NV{_{3'=!6!0 . 4 +zS/L (,J wRXsF.'RkF .4P&9 -I9"?.!<Ic3Pk:9VOl,t:[5T)F,!8 !D_-Ll,!A?^"*G,'6#     +36#'! byJPn5(A + !3KZ>$ !'.<%+' +  +  + +wcL\9 + :E02>%4D' +i~OzUb8Dc0N 9T=T#)=+ +)8"  / =N(hLH`.F[*3D"3  +01E"0J#/* +) "$ ( #%D_,;V%2   +   $ .?' 4 #19Q35NoQ Sa7 7 +#@7 > %C7$? .!;431-!= 9 +69 5%$$ +$##]RxGkPrQr_u2Z|Y|Ov x4j#i`Y_]V U{ +jl#Gkh q*Px_5U}r(e`n(P} e Lp j*g%o*j ~2dfWxVxh"dPr r)jv(x)u$n2x$smr"8z/w+_fz/gu)u)Puat(kt(8q!T{f,` osw$Vvv"_~ *l.Jfa@Lko"w)mqrsilol-(r"Nm%Us Bz.fl$]ei"k#f3fa^ hY|!pwFqa stp,nluqi +wf q{ 1b o\Ze^ fp*l^ \b Yz%cdr!gbfs#eUwn/cDZ~@Xp|&h ed|goo.mn${opvx u%_x t x#plt n+h mef]Wl h j f vwal{%mj*}(u"x#.pf b ll[$s(h nk s_ctla"n0hd b fElPxk!Tzx(c{&{![f\jpp-ccml)3frqeu!7'izu ~(|$n-o-XNth*t;5b pl<(l5u#c7y|lmme{n m k wx_ lS{mzvxz#fiX}-eZO|S |3XDnR} +n Z Ko\~f }#uz!~(k)`+.vkkv s5.h8\mgoy"e -rnb nbbE5V[|hLmI,j@/h +mwh +b^ ++/o v(Kp]Owe?bSs MnPtn&h{*u&t&^If_{7s)cp$x,_4[{05q&Cgs+/j +bz%h/lkkl dp i_\Giz%dv)v'onof &p*y} #~z t z&omu${j x2"!#u j~+lzs,~t s vyt k|'{ppapx`cg +p +u$|gtj pocaWkfn |q q cp cxn|wx~Px\io xnjdrz%phnkk Zz{~ un&[rxvrq zhdg boi +q ~{q mu khqp +p ml~ +'n7Ts1Q#A;-*F#@#>'-S`Tp"07+"tSN]: ;L"4mAc86V*E1 %#?#?C^,4KZp;,)5I(;U* 3 + &' MQFMU?5?)M^."7$8%5 + ez8YzRV{Mj>X ;U/K0I( ! + '5)7  wW#? c7rCZr+0I1F*@W{*2H ".. '%9+    "43L""*:"  +  ' ' +ES5 +0D/HD_(C&AU]369\/R(J+ 'Il0lE:\Ih!Ni1*D*C 9Ke81 9Y B!C-M3)E#A)HaG3V$5S'##+%  + +    $/9)    + -(  # %$"-=0P!=+'$; + Rm(:V% +#!0EN4/=0 +% km6(E@[0,/67 #A477"?,31!:*/2+)E63N!4 +"$ + +1&MpGj6\Vy:Z[yt0Lk RuBh^^X~ \AMwYW~U|H?d`Qu +k SzW~ V} +_P{Y~6dUPv Qs?aWz?Nrx+v.g 7l#e]bPW~ o!_ip0\y4Soes&W +ocQ{g\x,Z~ m!Feikccu!py%sbSyqf^~ 0w0oRqn"p&b[{\ Symb +hW[y$q~(-lu'k#i!y/|0bNt Kqs,@k!f`c] oqm)h 'qirw&nwz~ j mxs$r{{ Mpx"Vy+] aPz\^ +/X iVz'o] +g\W}lgi~/r"-ma Osx1s_ _ rknpq!h)h jnUvy ?z#|&'qnsju#m5u$Wi_\lXjXmf lgqhe +eT~c~&`hktu!o` }*jr`wp~(\jw{kp$_`q!ybnki\WRMteRwd lU|ymmsqpu ^ s'(y%$4vr3*)w{n q q +t r q \~jm]z"e e4_.gloqi|*im] \~{ ug|tpvqtpR{Qzip [i jmUs#Hp=`Cjs/f=f7]@gT}S{ QxNqw)ggywptz wy"|"v|!q~&nGjkm]_ ou!um,^}pe|*d.s#Ff7k'GgTomA\rnglpub?m},Sw2w%HlW|YaY{Rr +m$KkRu UxQsjds)Nj@u)]yRu +o%Krp%U} ?eWw{,eZ~wdf8Py\C5e azu{q[?v!Svbgfojpl{tte v,).}+w(or ntntk/g'~x%*u!qy n|q~vo mzbtf eylpgyxjjkrpc%^Zh ]T{i_`jz{p yu{wT{i +wti bu y + wde i &wr xlrx pl gljzn +(|Y}Oru]k +k +vkp sg/k ]y\y$n j +lt +x +sFaFb A\+J"?C^*,H;WKd"G` .JNnY}q$Qt-Nx7r2-F AV/:L +EY"0I +">-K; +#>73:W(+F*EҝRh1尢0*0 +" $-GKB +?D- /4 &7!-_r1MqQwc-K2J =UIc%.F8L&$! 7D0 "/D&: +g+p5Ug"ZJa0C (4%XmG*@'";(! ,  +4-  LTD )5$.$$: B\5G])!7 +Pg44)79V"-L[~FBh*lhPr"6U Ddf]}4A] :*GHj!Wy5r8GX-3F)7M $1J!: 7; +.I+"4!  #'#"/ '   + %   +-"9)@>U+_s@Ka.*<8G, +.G^.Kb.#> +D` /H% 8G+&4+9(5 +<%, !,Tg<:O(!2+%8"73K!*3 9R0 ! +  !!% (+0E&  ,Eye=\&+6O#%1'8K% .G6OIb*.I2QLf, 58J:N5B^!8)BSl4%?7> >6(F3N"2-$% ! .A%:&*$/  .B"    , + !@R1"4, +BX8#qg#.*B9O) (4!  @Q8& + +          + '*%  +       +   + +    :E7#5&;/F*?("1%"7.C+ "'+ P^D!,  ! 9I*.>2B"+:$)QYG)  &0' % .<%%6 2D ' +  + +*% ( + #/".  ! # +-)@Y)@R3+2< +$0C('6 $ '+&3#    +   $ ! +$7,0 +'?*'Ri6%?37/">0(*47 +0.6 7 +*G4+I &C -39%C*H+G)B !%'>768-,2:0.-''1/: 4;8 +* +* ! " + %>1 HjBeUxm1*Jy4WtLj +B_dPuv*^Yk T| bGp[i&CgLrl"b_\Lw\[Qy ]m'Bgn,f(^n(;u'Vy digil"Hmt+_ay+o"Y{ +daAy(h`_cbNy\x+dciw*mCu'_ fp|(9x%py%.prs)y"{%t x'u':7j_ etThise-}(kX{ Uvl#t)d[\Ry [}m!MRtcLta or*f +py|(q2lz#l(~!&*po{x]tpf 3~(.kXbBs"/] Kl,.z%Kngg[})lohpew#,kaonv"{&fxi lth +k_g Z{wrpy"u kjqj1lPpjkjd[ ][\` ^ a`LxoWnmu"_ S|\'Z6~%mehb +y$`)7o!!p $usztxj +x.m i +$jNyClT{ U} +U{]gf0a {%qntw}#kw!.zxwtm "||{#0?k br ws'd#(vx *]"t1AvY{V{*|)2ki^~Vwc 8(o %tdlaoi wpDnUbWXusofgEAaT{Ye&b Kn h%c im_u(=6v r&th -+-)K(-bk{(ea_p~*g',[xljd{+h:[v Lh=p+df+Vq5d +i d #%'iTxStMnjapmLop$ek$Hf^~Ww +`Z{ ]}k"4md~Jf9`SyU|heSvDg.T{ +ktvrcd |%d ox!##p z"a [} q0Src f +yi +rbqX|=)z v{~qsujl f d +n.W{o*t~~~wm |vzt tmyr{x"r +q stss wjhy|:jp o yc%YQy`` +Wytbnm hvj c{ymfdh +Xt z rx socj +pv $b~z iizsc jd +f +h xNtNur])n i +oh rSv)*xok txhdU{mj .D`.HVq!6P#= (A%,'\w&F*ofZ~q.lb?X +B1.'6   + +.;+ + $ R^LUdR$Th@F`%.N2S"G4Y+O7[&I <!? . &)?&2-"=#@ ?_"/3*C; *E ],Wk@g<7W*Vt< +      + +VeR3B,##1!  +*= ,TjAoUF^.$>9 !D <-!1 l5I& !3B$ #UdNgJj7A_c,B]xkh!m/[v(5M0$! "2&$  !/)A) (Oo1eFQv,>eCf[_/;\+MZ{(Ww/ = 7Ff$8Y 7N*   *?2 .E/2*D]xH(<   +  +?G/GS19E%'  +-6&! + $1  (:.B! 44 [r@?V$!'BW,Md/.,D^wB*i4EX3* %7+# $=R.1D*, +"$0!  +   "5' +!- # +  (F0/  +'789 0B&n~W+#cxV,C(A 1M4R">-/0/E(@ +%>5Vm=.G*>:L(* #+ + + +)mwa 8H),*,9K,# ! ( (:FZ3$2  '4&  +  &" " + +&1& @P1$4&6# ()% '"  +  +    ," !4*@,;Q((.E#= +(G,H$A/23Q:0 +#1 +$?,2P6T":#> )69!? 'H/.',(6M4'D35!< 4 . %3 ")!: 95$B'D1P + +%;!95 *: Vu Ch>bCgRs?^XvdfPt Z~Tx |2^T{ JrOwQyX Ou 5Y^Qw fZs)cR~ N} +P| W|eT{ cj&q0d o'i"_dW| `o#^Ms@fu-u.ak QyniY j1bqfr Yeu#ok8lBgy*igf ,wSxRx#xt^deHkhV{{$kb|z7rgiiy!u"2#rXzvmdg t\r7Ysio|rTz~p n +c$r xyph _dubsuwo +p +oss f)lzta|3l +_yqlb ii adt"utzk +hukn +sfu +njg +Wxz hk!ghm {| +rsshyj X]]] +` X`Symf}q +l es[Swe"nKl gxpwQvuhjs"Xpt3Ea7 ;P }7%`Wzp'sy-g w2*-1Z|Qc1 /7(- *-H$@6#>P4Sc@(F\6& & +  %6! " $ &6)9'-crP +'3) "2 4 "- !&< # +/$  !$+ +   +  `nOk{Y5C"%'94 27O;W#*4 :V%/K *Jj)sU8[B>73'E2K",  * -*A0K < -I (Gk(d3>a %HRu*2S +"BKm(n[sD&   +     $8!53/ &9+;.K+ &68a}Gp[ #  !1 !.<#!<B]=\EhTul'@h8S%;FZ5( 4=] ?.L 9Y>`Xw)Cc:Z4VZz,Vv/uQ.M0 ,ev8Jd 5H,-+#&> %"; =T(*D      +    + +&+9#"   .>!3G%. (1 ' +"8 4 =O*3DZ14I %CX2,9Q AY+B 6 .  E\'8O%9 -#1 CW8 )9$ / / +%; +  ud4 *   0 // ##/IZ=,B'?$< +3: ;T"*A!6 +$: (Pf:+-2* " !&:!3 &+ 1G" # +( "6,#+*;&1F'4K'$.U%Lb24P!: +,K2%B$@6)K/N;W#">8/6W$58 4)E$@6*FD;Z!0&2+C":)%?9 4 15 *-/ 2,"> *F3/(G#B +4 (6 !; 05NmKo +FkW|i$Tu [zdj[}eSw +fbdS{k [S} T{[g$W~ +OvU}k=iR q+Drk"^hw.<]ej$k"Pt j#k#l ]x,q'l `g$_jXmSykn%w/bphjsd|(h~+~+[ 8j>w%fs!ajz)chz(bi{'p~'rx":u"5348W|qfYv,aSyz(i1;;x*u(f] 0Hpz-ps"r!eoWkq[6~&mq.`bl{*\b |"t/o'wuukxt0v!ft$~.t%~.Xt5>x*_fZz +InsY^ +*i@bZz +mGkr |#,j u/w$z'i9r!w&u%{)v%pNjw!z#dh[|)8d +w!W~u'334jep--] 2R[^HrkQxd_ ] hgU|` 'a e n=p]t,TsZYukAZ(?\ c 1d fb +g5UB_ 86RUq fVsJ1'@0  K`=7%*@ 7 (9Sf58H    quSAE& + -9~Y!$"-  +! + +/D)E A]*,0B:P+ )'0D'2B*) -A# 4(=Md8 $ 5H%(( )  .' "2?T*3Ka:!8$< ,  ,  +  +   6I5J&:0 0 3(!:1.^{?#B35>(H 01"=-##   &)7>X)1 . 4R Qp+Cd,M2SDcPm("> *G?\\tH        4H+, (/  6 8N'#  )17!; &.3 # 5% cMOf='9 *COj19Ur ,C0F%'A?\$ 0IUk:1 %  XnK>W')A("7# +    )/   #3!4 !   +F[8:O(/6M#-6M ./ /C*3 "*%* "' 0 ':N*&4=L5 CV5, 6'?4)=%+9T -, +  TiI-A$?T6.C%# #,A0H %B_6@X0_|Q(@ =^1*LLl: ,>Z1$@/\rA('  + +   $ ! $0    '4 6I-   % +#    +     '8$AM1     ) +'6?N($. + +.E,DCY1 + +-$ % ' + +  # + +!$ "3 /;J0 +#2 2,-. +, +    +   %81F3 */*D"? 'E'G2P0: +A ,L$C1M4 %+: +73/618U; +Di">$> +3)&<;. /%>5 20 // '.7$C .N .#E &I Ig.8T"67.I6 &C"@[~ dIp :aW|chkn X| cl$r*k Tz W~ +b:d[GqKqW~aHNvZ j!W=lP{XPybr*De^aQkm"q!m)~)j^x7dawm!|0]z o4t(k eet;gVs$]~d })Y~Y| Nr^gZlZ~jWxfcFiTv Vy PrTt eOj Xs h(phw!ijYgxIrj2Nui|"Y~^Tc +~(gu W|k:_[ y"l`z ^vkv`y,vz{][cX|))r~&Y{\~ d^ri laorcwdxu atnXz e ierwRtcOv\ +bZ +OvU{^ ` ]_m jq wg^y_vytm4jgyxizaouyebk z\m `em +izw[pvbrx!}zvlo(q^fel gf [WZ @hdgl`j 8sm7ElQz#uvv wo }}k,1kq$1Ju#~ ri~2Vs4k m j ek }E8U"=ga{0>W +"=,#>D\5 =R* #8%@ 67N&9 )%- S]D\gPT`B@K5;C3 egP +48# EL/DO1-8Wa>$!, $1BR0&;: ++D* 1( DW7/ 3 %:N* !"' +#) ,A);R2* . /& #'2 4(;1;N6%%   + +FV3v[#) )A ; 4:53T*L $HdCDh$7&E>Z)'@ % %!23`{L4 5/K#A Ff&DdA`1N"?"(C%B 7" +&.! +    ( +@ % '    + + Tv/5U&EBb(9,,(/C  +Oi?8 9 x\wTiG? )5W-Mv_2R-J<^Dh/TFh\z:-MOm(Da";U!5,?3G1+A';-=$  + + + &?:ZNm6- $0 *- +BP:+-4D$"7XlG&; ! (6. +  ,C#'< #3+;"*&= 6 +6 "8 *;,<" &7'dK5%3ER>'JWF  0A($ %4 o]3"<"CSx>8^'@c-Be.3O$@ B^'8S-  (8%  +        ) ( $;(# &3/;M-$'3  "   )'% $   +   ++3" +  +   .=)4F " 17N(>>N++'5     ,9* ) +HX7&#$(''  ! (8K\>GZ>PcJ +  '   &  ( +$-* +'?4H!&VkC3I!8 [uK : +4993O8U#=  27 +8 .-7:,I>^!52V Op*"@4+#:,"64 +&=).%.!4L&3O ''F =C1T*K :7V=["371:%B5 UzEk_Pud!U{ kq"Z +Z_AMq]bNsaHrR{;de"_gmZ T~bMxiV\\Em@Utr*`[Y~``ejY^u,_l$o"`q"j^Y bU hZT} S ]h\^cjYZ ~.on.iafn$]NvdchhiU{ +i{)` Qqj:lo\ X} bc\r(y.eu&fhl~0] +jy |'YHpRzhXa te\cY +QwX~ edtiNx_k|(+p` bp#Wyx"og v\f '~$1~+t!y(da~Sovw"pncKp^d] w!hk}$3infCiy'b d,|#z%::p$Uo hw+w+n$Ke|,ds gVx;jhm}%u Px#<~*/=x'BPTjOiSm Mhf_}6v!Ie9x*fv(axPTr9t ge~3is#dsf i +vtVyY|e[}Lp Kp\ w xwWx` +`g_h_l]x w#5R\zvo}#`n]Nv~$g xbns(]w| Pr3V{ KsjlFt#jy&z!spa)Ywtml qOwHmp_ 9zpstt!`o` ji uXm`rh\`s'mHpvjd^WV{b :l![eQx:_DjMo>ao_ qddf baZe^`8Y| \~y$s eh~m je o !|gvq o u}tbf$s j ve k `tbjuk +y %z xS{v_sn #!#n^ Z`,b |!"\~qs{ ntj o-lkwx |pz2oe^zC\y(v!=_~Yua~5Xzk +d -_&A\.&A!8Vn$?W ++Hay7$;CX(+@18U7V +&"<_y4Pg>V,? (  +'-' +  $ %Wa;$1$+B(>''<)@R/+@!&6P#B]1H\4"! +" + .) "#)<"/ 7$$ )"& !RhF!.# +   +  "*"    " + +$;Ok8.J[y>Ol59Y3WjKBc&/(IDe"5V$$@ 1(% % + +3+H/*G@[%83']zI/G%   +   + +(-3 +- 1 %5(# >O-^xK0P .O4T" +$,)& + +/ 'Vs6q3Q:3Dh#;c":3X88\95WuDJnHm\5N?W%;dM`9!5?\(2$A`/ + 5* ) + ( +#0 '/ < 0P$F +PVw&C5X<_ +i?@^@]0JxCj<\wF-F>\$Qp8>U/ +% -%%)''3.:M- "2HX< 1?$  6B,    5@+'3*# +  .?X6*@07U!9L' % #6Md8@Z 19T!'- (8DY+Kb1/Uq<:R!*B,A4 ?Z('C 0. &3R(HCa/K "= -7G&  + "4 '-LOo26U(0   ..&  ! !!4/ 0 (#3 '/ 4' ! '  "'4'> Oc9!7 !1   &2M"+!78H)!*   & "3#5  + +1 %<pe*I1!C +42O$B+ ,)2w_ 7$ *   +   3 0 9&@ +#$!,(%'6 0 #%         +! #6"4   +  +  +   +  !;E. 4I58K1C#,9 $3$1A!. + % - -8 $1 + ##0#2'0A 4D #4, 4EBV/%*>F2&$! +!2! + + "  +* #* /' , #%3 !(@D[0-C)>3 +*!>6T#+M= >+I/%@ 1N&B ; + +$E*K;4;2O!@@6Z9_,M +? (+ +".1*2)*@3 ; + +)/Q3V?2R:.2 +gRv Y}AiQx_emX~ k#z3cQu aW}dSy Jsh%Mx ]d Sy +q'[[r'cv,`o$]Lqd"cX|h#]`g!d!m'h"ei!n(Uzdm&Xr'iZLy\[XR} +U[[r'XX aV}v,X~ 6t$eq `V[ ehU~ +t,T~ U +q)j"V} KnX{ iMmgo"Zz 0UzeX +\fw+Z x,oXeer eoihb},Z Z u!_ +]h` c^Y Qw^ bb ;V~{/|-jJNqbu&mXy +hhae_ a }(iu5-~,{&u#n*kheq"gY} +p_ c m|"dusk_ r!u x"10})10778Pjf7LmchQol a~x(~+}'\vd0n++*pk6%&in:p#OtZ~n |&xvYxoen}&kces4(t#-!!s_]a rb~"vcb np~cepV~V~U}qu%agw%d ^}%fn\mjdtvyl|&s"Ors4vr9[_ _ai*ZYd^S}Yoa`3X{ ,a +XYY^Nv3eeNtIn:YR_Mq +m%8]9jW{q3RzU~Mwy"yjdWyd )v nq$xvyW{W|luuz|x +| u o zhj p|ZZp +#fch gwe{{g V~sb q,g ezjn__=qd|"7enSwb{&bq|k mfbc,dzy)Ru\zB]>9LjPo` h~\}}l gHl\n?WuR'{P]r+=SpBAU5G1 xQ1"8%4 +0)<Rh<6N$ "  +% ! (,#+ !/) ! + %!$ &/D. + +      +    2K">Y*,H9 +J98 +Li1-L!@8866,'6 . + $8Q"": "9 Db2(6,5 +&&2+@XkI     +    +@" 6(#:I(5K!)DHc!8VwU3 5 && (B]&B_$;6,K Z{33W2V\=Or-:^7Jl :^:^KqSwd8 "$8)= +2Uq2:Z nA3RPp1Nn+;WbxO3F  + *# +3 #2 '71 ( *}Y_}88X?`\6;Y/.I *C 6)sK4G$"0+&7&C !&  +#.DO:'4 %#  # !*8!-:"%& +( 6F$% +  +  -GKj2Ke0  )Fp/4I( 4  "fO$= +#7 + C\-Pk:e{K2JPj5Om*-LMi,+";&  +=L1=O1%3  *9$3F%.6T70$9!5%   + ! +  %  ,4G .#5 ++>CV#BY%9P$'( + !0# AV:8I/%0H:R0*@2 0,;")B0K,OdCAM5 + ( ( , - , 1Je99 +9&B*:6If/$% o^ /B+8L)&   + +  ;O22H)-53(+F ') ' '$"#7?Q-'>D^52) +)5 7),  +     +    + +;R%)C8P7M$ :I*"$4  /? 2@) + %**8    +%-;$! ,7'&0'"/ +!   * +  # & " +( ++&'r"{,Y +g_^~o#\u&] +W~thr3s2&5ldwj00f>geond (ekw0ongx$c ` +ut,ho 5u&nj2\|e1n] ~*F-uo*sa5scnmaY~`Nq +Rv Sy +uDj]gXyZ{heV}X~`z*;bQwfdStk_\eV|t$me Z>mEll{)acX~ +-qc ihqtto~1eiigp&Rx m'Ot LVy\~Z} Hc{&z g uh pY}!l n vsxmr|~}zyyr9{#%p6%*(%y#YR|lfk9y*RxfBhq-W~JtPOxFo VNu\Jt^Qx k!]\YMwSCj]r%f~.o"7gs.Z}YzB`9ky#,ADt#09Cts{%0:3|,kq#~0Sn|/~0<7z'Dl__ki5Vw]|elp:gPn+m.y~$`}eu?c|(y%Ns[[Z{ z(w$@sllr2u+<-mw!+xplmie yWq&fU}gdfuj}'i`o^2Vy lfty"sjhy!pqvt-{Uzf-}*s}-m ]U|HoU}Qza UanbmT~hT|i&UtrSybpmdHoInrMq`Z~ MqX|>aKmKnh CiPu QrTtL[ qMun"UwXvgbvjs)v hjm ox'&|8~ | (w n)q aa&n04l{vvh "xm#d `WyTvx%Hm.Txvpe7civ%k`z&jr StbX|` ZpaHnY~X~_ +*^5bIgcGix%l UsHgwsz}| ,|an!MkKh3Ws(f0g1=ULaNb"1C,0Zo.Mgg%AeW| +Sl'4Nm9R&#9)A '=:L'7BS,@ $'Yk:=E6   "  @E-$ #8 8M!$9 'Ob21C2 !'%$ + #'$ - +< #53=S4!5 *2';& %83"85/   -    +   +   4C2`tX%?0M *$A13N:, )$4$< 'A+)D= $@2.81[y@rO+M.O(C ?lT@^- ,# "  $ +   +  +    + !    #;&B  " 8 Uk:&=  " (QiA*1KFa' &)G 7!@::&G8]|7DgBd9_4v6f5&='? 7S+I +*I1S +>b'L1Ua|B Qh7$6 +%+,*=%/) + **C34 "$9/ 0Mc.(E$@]$ &'/)?*B #8 #1A&7L%1F=N, $ 1<(FR7+#- !$ +  + +& .(:H-&4$0 +" + + m[, E_11L6P2. 0G"/$3 !6 16 +1 . +9P+) "0H1M6"> 6 ", !-?$(9  , +&=9 +'B6 &>#0! %+#'.#   +    +       +' E[/1I*@UiB CP1#3       +            ;J2:K/+:( -* +)( +$   %3) '?/ '7"8Nd>>S10($ #(96 658X(%B "> (%?48 ; < #$A %.52:X1@Ce'>@+G- !".5 .314$? +$9 7R"1; +64. ; ;Z&.L0 997 21NsLtPy@jOyV`V{ _h[Rvfch eR|Yd"ZZLv p/ZS}p.o+Ov 9Tx k%m(UzSwl*_!QuBhb*SvX}d&\SyV{SwBhTzFr7`Gs@pL{O~o*L{ \WL| Iz o.3^S{[YRyi%_bcX \^o&^ey2s2^u9c&_ YXY iMsSy^u)Yr#bq"bglt"a`dfw\jlpeZ +<`Y^f^fh]fm_[\a[ +~.Z ]`o$Jn#aS| +Qum o#u(r%w'=x#o1/sw!{!iknif>fgx*4|)x"sks4rf yz#v$^ _ |)Wzq&qt!eo{'b ,n fp"Ntp}'nwsmpmmxm&rn:PvJp +c!KnY{ p|#iBgZ}{+epTxe<\ eX gZ +d\ kv&Ahw'LsjegU~w(Y7f_QwLt_Uynu#t >c fg~.|/RvbDOuSs*u1c#^Nr Su`h[ q*f +i nlr%t)tsqxss s +&q {i +\Xrnu)4|$z!qlbh~.p Xlr"l1\~q&u+Nr +Nr +n,_#1YY|8\Tyc e"Mtl ?Y +a\]W +hx&oea\ @d;|5Z{s(N0g)5t"1v%u$@8ty#642-g}o!\Vqld1f?gV|q-p z\d`Rz}-Vw +X| Vzoxkkihn0hkx7uoqosgq[{&x|f hSyHnuh spYyn oZ` u0jv"JvZ ^ld_ kh`cGkQu\_Mp +Sy^w*fHbpplPvQyFhPy&]x +n$}bc|kubt *!}{}#:t@ip "lzi-%aj ^~}ky2%xd|b{ b}iE`'^| Vt x .gf^ b}1_} QoTqUn ME]/HEGfXzb iQxCj-SJn MrAfMpa ,FbocY{/"yy+{lzhDa>]3RRp-K2ONj M3 +"2& &9 " '6+ +% -B$$  $ " "     8E4& +    +  $; +9SB[*5#8 (@5O- & "6L$4K +!8 +!> +5-K8&AlQ3S%7&M_?Ou;!/45 *; * % +, , +  +#  .    +(<%>9 +H (   ' ^|>e>kIxQnT../ 6@_"#A 33 +8V >4Qn8@\9[$78W`>;aRw%Nq*Yx;jT"8+35( - +%+1 +8+  & +A)E0#< %"'5n[RdD(  +>K8 /CO`;ZiJ$   + ;H, -&7, 5P'n^+     )&*!:N(">  2 )Rp/:Y (EQr,Rq,)E 9T% . 6M%.('  #-A;R6N $$<AY"Vn=.IF])Kb0&  -$  !31  XiJ%-E*G5-J5R;!;4 /H$$:#: 3+!7'/ !6/B& * !21G*4Ki6Ie4,C#  +   +=F;,A"!4  2) +' +* #>!=#B? )+H' 0% 0 ""   +  +    + +  "+  + +% 1A$" 6+ % +* !0L9,3 ++C #1C&1 +Hb/,F4R%A&@ + & +# );#&;/0H9 -F!  +     "   +% +  (; +*9& &  --  ,      $,    0#64G0   1 #80#   0 / "; +"%( /$7.A#14 "+476%B + $456)D/ ,,I)#>3)G 'C 9;6?$E!? /1K*'0 : ) (!<2)E:3 +%)D..*F-+!<0 ":&(;%9Ipg!PyTXTn*n'Zi$V}m"fo$bIt d#NzUWFq +a Lw ] Eo +c&Dm +Rxc%ZUy|;ZTw`"`%MrInKng-QyMtJqNux:l+@gCn`"EqSYOd"bUZN}Gx +BrEv +8eQzClWKr V}c\w.h!Ye#o+i%WaW\!Ug,] f#V v'^s$^`ff_cfbdT_np lj] xlhde_s#^ 7x-U gr)l"es%hcbm!u,]kp!\]c^^gNtr&= #7 +"7N!8 3N#2 + %&A $?:W: )!. 2TIp&5_.T!C:Y!4Bd/# &:9L*$6( $&7CT0DW6PaF& ++#  ) +.3: >U--="    - +1M /Sm.^+=_ > +(?%;`zC'H Fh)Uv4(J "@=Z%Ea-0?[("=%C]#3 #6:T.I# <(C2P*K$FJm$Bf /T@d5T5"> 1 )&56&5D*'9O&58 $8 +:N2%3F&I[<      (&*8 +*E"=$="72F'-    C[,(F8T$)C)C0Ml'Yx/Yy+Fc"+Gf#-M;Y %4J$/F'> Rp; )*%>';0A!(:=O#I\*"7 +%9!5) -D)(4KRe6& .  :K7E[;3L oV64 +&5' ! 1;.&;/H'0 '8S* &- ?X5$/ )>$ (6# +!1'!4  % ,'$ .:( + +  % +%  +,(" #8(  , & * + % ' +#+@ , - 0- +&(@&)+"?#;3Pza./*/K2N.M-8 /9 1 =/M85V6W"C@ = (9 +'<!=: 8 +3 )n7414Li84-)7R+. #1 1%(:I].MtMx `!O}UU~Ym-U{\Qwh"bQ{ +TEpYIvEqItGrOy_Ep >iAkS}LtQwY~"Or`(Gl X{o3Sy>g 9`Gl_&MwNwVKtT}Kt \z:R}P~Iw YN{O}GwO}Ix +Jz Qk/i/YUpNfAo+9N1EI=ps=ERsy%c n%}$' s1!$zw{ w_z}\yE`Ib8Yy +br!{0eWn:Qf~%Xq)5L%=3Lt6So8O2Id{7*-:V$=!6 3:"# +2|TN_.# :B-;E& $- ,B ,I$>*(*  2B#*!5AV3.#':( $7( %$#$52D$2& "6$9%  *+ -@1$-!    + !     *1#* / #:S1!) 6 -1 ++&B]zF2M8U/H5 +-< @a!Sw0rIPu%+P@b#:7Wga?b8*(<.!' &   !-,* * + +,   ' -@ 8%@"    %?tFq*t%Y#G["pE'B *A 6*3Or3+7%H )G&$ " +E[4Ul23. (/%6 ,.(CA]$;,J*I;]Dd>_Ce"C(H Vy7cE;V)--1 - !5$ +!( =L03 =V(0) 'P`G':WiLNg@2$ #& +  + .5##/&5"1& &) "-3"7&+8,   +   %2$9-I;%A(A,GEd#,M>_:XOl(*E 386R3 4 2 3O)H0 $%>)0E!^tC7L(;*$5 $5% + %;1!; 8-1(C,*+# +  $5" .(8$% 0 /?%#  +'C4SHl6-J9R.%  +  %3 #$ - + +  + +   + + (&5+<' *A 7 %'   -;" 1"2#+' $ ++  (/ * * /=! / " ''. -; ,  $  &62*?$9#1 3 C]1$15@'   /@0B+ +   %! 31 5 5 .  ' !- !.#1 '!  &2 + %. +%  " , +  +   #04+&-?$++!4 #  9U 9WKk,<@]! &#= 9 "> 6=5S > 316(E&E/02 @9= -,*)503 Hc3.8.,0.;8-'5,*' % AT' \ No newline at end of file From 12f5630595ef0d8d50bf3dea47d33606bcff4133 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Mon, 4 Feb 2019 15:23:27 +0000 Subject: [PATCH 05/69] Tutorial 3 and 4 (draft) --- CMakeLists.txt | 3 +- Tutorial 1/CMakeLists.txt | 2 +- Tutorial 3/CMakeLists.txt | 43 +++++++ Tutorial 3/src/Tutorial 3.cpp | 126 ++++++++++++++++++++ Tutorial 3/src/kernels/my_kernels_3.cl | 158 +++++++++++++++++++++++++ Tutorial 4/CMakeLists.txt | 33 ++++++ Tutorial 4/src/Tutorial 4.cpp | 48 ++++++++ 7 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 Tutorial 3/CMakeLists.txt create mode 100644 Tutorial 3/src/Tutorial 3.cpp create mode 100644 Tutorial 3/src/kernels/my_kernels_3.cl create mode 100644 Tutorial 4/CMakeLists.txt create mode 100644 Tutorial 4/src/Tutorial 4.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b7d5520..e3f9cad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,5 +3,6 @@ cmake_minimum_required(VERSION 2.6) project("OpenCL Tutorials") add_subdirectory("Tutorial 1") - add_subdirectory("Tutorial 2") +add_subdirectory("Tutorial 3") +add_subdirectory("Tutorial 4") diff --git a/Tutorial 1/CMakeLists.txt b/Tutorial 1/CMakeLists.txt index d0eeb13..d83c256 100644 --- a/Tutorial 1/CMakeLists.txt +++ b/Tutorial 1/CMakeLists.txt @@ -40,4 +40,4 @@ add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" kernels) -endif(MSVC) \ No newline at end of file +endif(MSVC) diff --git a/Tutorial 3/CMakeLists.txt b/Tutorial 3/CMakeLists.txt new file mode 100644 index 0000000..cf24497 --- /dev/null +++ b/Tutorial 3/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 2.6) + +project("Tutorial 3") + +set(TARGET_NAME Tutorial_3) + +set(CMAKE_CXX_STANDARD 11) + +set(EXTRA_INCLUDE_DIRS ../include) +set(EXTRA_LIBRARY_DIRS "") +set(EXTRA_LIBRARIES "") + +find_package(OpenCL REQUIRED) + +set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) + +set (SOURCES + ../include/Utils.h + "src/Tutorial 3.cpp" + src/kernels/my_kernels_3.cl) + +include_directories(${EXTRA_INCLUDE_DIRS}) +link_directories(${EXTRA_LIBRARY_DIRS}) + +add_executable(${TARGET_NAME} ${SOURCES}) + +target_link_libraries (${TARGET_NAME} ${EXTRA_LIBRARIES}) + +#copy kernel files to target directory +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" + $/kernels/) + +#additional command to copy kernels into a working directory allowing debugging directly from VS +if (MSVC) +add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/src/kernels/" + kernels) +endif(MSVC) diff --git a/Tutorial 3/src/Tutorial 3.cpp b/Tutorial 3/src/Tutorial 3.cpp new file mode 100644 index 0000000..bf279b5 --- /dev/null +++ b/Tutorial 3/src/Tutorial 3.cpp @@ -0,0 +1,126 @@ +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define __CL_ENABLE_EXCEPTIONS + +#include +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "Utils.h" + +void print_help() { + std::cerr << "Application usage:" << std::endl; + + std::cerr << " -p : select platform " << std::endl; + std::cerr << " -d : select device" << std::endl; + std::cerr << " -l : list all platforms and devices" << std::endl; + std::cerr << " -h : print this message" << std::endl; +} + +int main(int argc, char **argv) { + //Part 1 - handle command line options such as device selection, verbosity, etc. + int platform_id = 0; + int device_id = 0; + + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } + else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } + else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } + else if (strcmp(argv[i], "-h") == 0) { print_help(); return 0;} + } + + //detect any potential exceptions + try { + //Part 2 - host operations + //2.1 Select computing devices + cl::Context context = GetContext(platform_id, device_id); + + //display the selected device + std::cout << "Runinng on " << GetPlatformName(platform_id) << ", " << GetDeviceName(platform_id, device_id) << std::endl; + + //create a queue to which we will push commands for the device + cl::CommandQueue queue(context); + + //2.2 Load & build the device code + cl::Program::Sources sources; + + AddSources(sources, "kernels/my_kernels_3.cl"); + + cl::Program program(context, sources); + + //build and debug the kernel code + try { + program.build(); + } + catch (const cl::Error& err) { + std::cout << "Build Status: " << program.getBuildInfo(context.getInfo()[0]) << std::endl; + std::cout << "Build Options:\t" << program.getBuildInfo(context.getInfo()[0]) << std::endl; + std::cout << "Build Log:\t " << program.getBuildInfo(context.getInfo()[0]) << std::endl; + throw err; + } + + typedef int mytype; + + //Part 3 - memory allocation + //host - input + std::vector A(10, 1);//allocate 10 elements with an initial value 1 - their sum is 10 so it should be easy to check the results! + + //the following part adjusts the length of the input vector so it can be run for a specific workgroup size + //if the total input length is divisible by the workgroup size + //this makes the code more efficient + size_t local_size = 10; + + size_t padding_size = A.size() % local_size; + + //if the input vector is not a multiple of the local_size + //insert additional neutral elements (0 for addition) so that the total will not be affected + if (padding_size) { + //create an extra vector with neutral values + std::vector A_ext(local_size-padding_size, 0); + //append that extra vector to our input + A.insert(A.end(), A_ext.begin(), A_ext.end()); + } + + size_t input_elements = A.size();//number of input elements + size_t input_size = A.size()*sizeof(mytype);//size in bytes + size_t nr_groups = input_elements / local_size; + + //host - output + std::vector B(input_elements); + size_t output_size = B.size()*sizeof(mytype);//size in bytes + + //device - buffers + cl::Buffer buffer_A(context, CL_MEM_READ_ONLY, input_size); + cl::Buffer buffer_B(context, CL_MEM_READ_WRITE, output_size); + + //Part 4 - device operations + + //4.1 copy array A to and initialise other arrays on device memory + queue.enqueueWriteBuffer(buffer_A, CL_TRUE, 0, input_size, &A[0]); + queue.enqueueFillBuffer(buffer_B, 0, 0, output_size);//zero B buffer on device memory + + //4.2 Setup and execute all kernels (i.e. device code) + cl::Kernel kernel_1 = cl::Kernel(program, "reduce_add_1"); + kernel_1.setArg(0, buffer_A); + kernel_1.setArg(1, buffer_B); +// kernel_1.setArg(2, cl::Local(local_size*sizeof(mytype)));//local memory size + + //call all kernels in a sequence + queue.enqueueNDRangeKernel(kernel_1, cl::NullRange, cl::NDRange(input_elements), cl::NDRange(local_size)); + + //4.3 Copy the result from device to host + queue.enqueueReadBuffer(buffer_B, CL_TRUE, 0, output_size, &B[0]); + + std::cout << "A = " << A << std::endl; + std::cout << "B = " << B << std::endl; + } + catch (cl::Error err) { + std::cerr << "ERROR: " << err.what() << ", " << getErrorString(err.err()) << std::endl; + } + + return 0; +} \ No newline at end of file diff --git a/Tutorial 3/src/kernels/my_kernels_3.cl b/Tutorial 3/src/kernels/my_kernels_3.cl new file mode 100644 index 0000000..50d54bf --- /dev/null +++ b/Tutorial 3/src/kernels/my_kernels_3.cl @@ -0,0 +1,158 @@ +//fixed 4 step reduce +kernel void reduce_add_1(global const int* A, global int* B) { + int id = get_global_id(0); + int N = get_global_size(0); + + B[id] = A[id]; //copy input to output + + barrier(CLK_GLOBAL_MEM_FENCE); //wait for all threads to finish copying + + //perform reduce on the output array + //modulo operator is used to skip a set of values (e.g. 2 in the next line) + //we also check if the added element is within bounds (i.e. < N) + if (((id % 2) == 0) && ((id + 1) < N)) + B[id] += B[id + 1]; + + barrier(CLK_GLOBAL_MEM_FENCE); + + if (((id % 4) == 0) && ((id + 2) < N)) + B[id] += B[id + 2]; + + barrier(CLK_GLOBAL_MEM_FENCE); + + if (((id % 8) == 0) && ((id + 4) < N)) + B[id] += B[id + 4]; + + barrier(CLK_GLOBAL_MEM_FENCE); + + if (((id % 16) == 0) && ((id + 8) < N)) + B[id] += B[id + 8]; +} + +//flexible step reduce +kernel void reduce_add_2(global const int* A, global int* B) { + int id = get_global_id(0); + int N = get_global_size(0); + + B[id] = A[id]; + + barrier(CLK_GLOBAL_MEM_FENCE); + + for (int i = 1; i < N; i *= 2) { //i is a stride + if (!(id % (i * 2)) && ((id + i) < N)) + B[id] += B[id + i]; + + barrier(CLK_GLOBAL_MEM_FENCE); + } +} + +//reduce using local memory (so called privatisation) +kernel void reduce_add_3(global const int* A, global int* B, local int* scratch) { + int id = get_global_id(0); + int lid = get_local_id(0); + int N = get_local_size(0); + + //cache all N values from global memory to local memory + scratch[lid] = A[id]; + + barrier(CLK_LOCAL_MEM_FENCE);//wait for all local threads to finish copying from global to local memory + + for (int i = 1; i < N; i *= 2) { + if (!(lid % (i * 2)) && ((lid + i) < N)) + scratch[lid] += scratch[lid + i]; + + barrier(CLK_LOCAL_MEM_FENCE); + } + + //copy the cache to output array + B[id] = scratch[lid]; +} + +//reduce using local memory + accumulation of local sums into a single location +//works with any number of groups - not optimal! +kernel void reduce_add_4(global const int* A, global int* B, local int* scratch) { + int id = get_global_id(0); + int lid = get_local_id(0); + int N = get_local_size(0); + + //cache all N values from global memory to local memory + scratch[lid] = A[id]; + + barrier(CLK_LOCAL_MEM_FENCE);//wait for all local threads to finish copying from global to local memory + + for (int i = 1; i < N; i *= 2) { + if (!(lid % (i * 2)) && ((lid + i) < N)) + scratch[lid] += scratch[lid + i]; + + barrier(CLK_LOCAL_MEM_FENCE); + } + + //we add results from all local groups to the first element of the array + //serial operation! but works for any group size + //copy the cache to output array + if (!lid) { + atomic_add(&B[0],scratch[lid]); + } +} + +//a very simple histogram implementation +kernel void hist_simple(global const int* A, global int* H) { + int id = get_global_id(0); + + //assumes that H has been initialised to 0 + int bin_index = A[id];//take value as a bin index + + atomic_inc(&H[bin_index]);//serial operation, not very efficient! +} + +//a double-buffered version of the Hillis-Steele inclusive scan +//requires two additional input arguments which correspond to two local buffers +kernel void scan_add(__global const int* A, global int* B, local int* scratch_1, local int* scratch_2) { + int id = get_global_id(0); + int lid = get_local_id(0); + int N = get_local_size(0); + local int *scratch_3;//used for buffer swap + + //cache all N values from global memory to local memory + scratch_1[lid] = A[id]; + + barrier(CLK_LOCAL_MEM_FENCE);//wait for all local threads to finish copying from global to local memory + + for (int i = 1; i < N; i *= 2) { + if (lid >= i) + scratch_2[lid] = scratch_1[lid] + scratch_1[lid - i]; + else + scratch_2[lid] = scratch_1[lid]; + + barrier(CLK_LOCAL_MEM_FENCE); + + //buffer swap + scratch_3 = scratch_2; + scratch_2 = scratch_1; + scratch_1 = scratch_3; + } + + //copy the cache to output array + B[id] = scratch_1[lid]; +} + +//calculates the block sums +kernel void block_sum(global const int* A, global int* B, int local_size) { + int id = get_global_id(0); + B[id] = A[(id+1)*local_size-1]; +} + +//simple exclusive serial scan based on atomic operations - sufficient for small number of elements +kernel void scan_add_atomic(global int* A, global int* B) { + int id = get_global_id(0); + int N = get_global_size(0); + for (int i = id+1; i < N; i++) + atomic_add(&B[i], A[id]); +} + +//adjust the values stored in partial scans by adding block sums to corresponding blocks +kernel void scan_add_adjust(global int* A, global const int* B) { + int id = get_global_id(0); + int gid = get_group_id(0); + A[id] += B[gid]; +} \ No newline at end of file diff --git a/Tutorial 4/CMakeLists.txt b/Tutorial 4/CMakeLists.txt new file mode 100644 index 0000000..47cb1cf --- /dev/null +++ b/Tutorial 4/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 2.6) + +project("Tutorial 4") + +set(TARGET_NAME Tutorial_4) + +set(CMAKE_CXX_STANDARD 11) + +set(EXTRA_INCLUDE_DIRS ../include) +set(EXTRA_LIBRARY_DIRS "") +set(EXTRA_LIBRARIES "") + +find_package(Boost REQUIRED) + +set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) + +find_package(OpenCL REQUIRED) + +set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) + +message(${Boost_INCLUDE_DIRS}) + +set (SOURCES + "src/Tutorial 4.cpp") + +include_directories(${EXTRA_INCLUDE_DIRS}) +link_directories(${EXTRA_LIBRARY_DIRS}) + +add_executable(${TARGET_NAME} ${SOURCES}) + +target_link_libraries (${TARGET_NAME} ${EXTRA_LIBRARIES}) diff --git a/Tutorial 4/src/Tutorial 4.cpp b/Tutorial 4/src/Tutorial 4.cpp new file mode 100644 index 0000000..de3038b --- /dev/null +++ b/Tutorial 4/src/Tutorial 4.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +namespace compute = boost::compute; +using namespace std; + +//print out stl vectors +template +ostream& operator<< (ostream& out, const vector& v) { + if (!v.empty()) { + out << '['; + copy(v.begin(), v.end(), ostream_iterator(out, ", ")); + out << "\b\b]"; + } + return out; +} + +int main() { + typedef int mytype; + + // create vectors on the host + vector A = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + vector B = { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 }; + vector C(A.size()); + + // create vectors on the device + compute::vector devA(A.size()); + compute::vector devB(B.size()); + compute::vector devC(C.size()); + + // copy input data to the device + compute::copy(A.begin(), A.end(), devA.begin()); + compute::copy(B.begin(), B.end(), devB.begin()); + + // perform C = A + B + compute::transform(devA.begin(), devA.end(), devB.begin(), devC.begin(), compute::plus()); + + // copy data back to the host + compute::copy(devC.begin(), devC.end(), C.begin()); + + cout << "A = " << A << endl; + cout << "B = " << B << endl; + cout << "C = " << C << endl; + + return 0; +} From 8c0f846923aae8929b6ead86c8b423c79620e010 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Thu, 7 Feb 2019 15:29:07 +0000 Subject: [PATCH 06/69] Tutorial 4 working on Win --- Tutorial 4/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tutorial 4/CMakeLists.txt b/Tutorial 4/CMakeLists.txt index 47cb1cf..6c411fa 100644 --- a/Tutorial 4/CMakeLists.txt +++ b/Tutorial 4/CMakeLists.txt @@ -13,11 +13,12 @@ set(EXTRA_LIBRARIES "") find_package(Boost REQUIRED) set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) +set (EXTRA_LIBRARY_DIRS ${EXTRA_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) find_package(OpenCL REQUIRED) set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) -set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) +set (EXTRA_LIBRARY_DIRS ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) message(${Boost_INCLUDE_DIRS}) From e2b9efc59b5feb0c2594172e859bbc3de5f0b9cc Mon Sep 17 00:00:00 2001 From: Grzegorz Cielniak Date: Fri, 8 Feb 2019 12:18:41 +0000 Subject: [PATCH 07/69] Tutorial 2 Max OSX fix --- Tutorial 2/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tutorial 2/CMakeLists.txt b/Tutorial 2/CMakeLists.txt index 6677c9a..153b8ec 100644 --- a/Tutorial 2/CMakeLists.txt +++ b/Tutorial 2/CMakeLists.txt @@ -13,7 +13,7 @@ set(EXTRA_LIBRARIES "") find_package(OpenCL REQUIRED) set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) -set (EXTRA_LIBRARIES ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) +set (EXTRA_LIBRARY_DIRS ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) if (NOT MSVC) @@ -21,6 +21,7 @@ find_package(Threads REQUIRED) set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) find_package(X11 REQUIRED) +set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${X11_LIBRARIES}) endif (NOT MSVC) From f98314e187820f178dd36f72f300c92d6c6eba8d Mon Sep 17 00:00:00 2001 From: gcielniak Date: Sat, 9 Feb 2019 17:19:59 +0000 Subject: [PATCH 08/69] CL2 dependency and improved project structure --- Tutorial 1/CMakeLists.txt | 2 +- Tutorial 1/src/Tutorial 1.cpp | 13 +- .../{my_kernels_1.cl => my_kernels.cl} | 0 Tutorial 2/CMakeLists.txt | 2 +- Tutorial 2/src/Tutorial 2.cpp | 11 +- .../{my_kernels_2.cl => my_kernels.cl} | 0 Tutorial 3/CMakeLists.txt | 2 +- Tutorial 3/src/Tutorial 3.cpp | 11 +- .../{my_kernels_3.cl => my_kernels.cl} | 0 Tutorial 4/CMakeLists.txt | 8 +- Tutorial 4/src/Tutorial 4.cpp | 12 +- include/CL/cl2.hpp | 9570 +++++++++++++++++ include/Utils.h | 10 +- 13 files changed, 9589 insertions(+), 52 deletions(-) rename Tutorial 1/src/kernels/{my_kernels_1.cl => my_kernels.cl} (100%) rename Tutorial 2/src/kernels/{my_kernels_2.cl => my_kernels.cl} (100%) rename Tutorial 3/src/kernels/{my_kernels_3.cl => my_kernels.cl} (100%) create mode 100644 include/CL/cl2.hpp diff --git a/Tutorial 1/CMakeLists.txt b/Tutorial 1/CMakeLists.txt index d83c256..07bb136 100644 --- a/Tutorial 1/CMakeLists.txt +++ b/Tutorial 1/CMakeLists.txt @@ -19,7 +19,7 @@ set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) set (SOURCES ../include/Utils.h "src/Tutorial 1.cpp" - src/kernels/my_kernels_1.cl) + src/kernels/my_kernels.cl) include_directories(${EXTRA_INCLUDE_DIRS}) link_directories(${EXTRA_LIBRARY_DIRS}) diff --git a/Tutorial 1/src/Tutorial 1.cpp b/Tutorial 1/src/Tutorial 1.cpp index ec52f90..8f78753 100644 --- a/Tutorial 1/src/Tutorial 1.cpp +++ b/Tutorial 1/src/Tutorial 1.cpp @@ -1,15 +1,6 @@ -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#define __CL_ENABLE_EXCEPTIONS - #include #include -#ifdef __APPLE__ -#include -#else -#include -#endif - #include "Utils.h" void print_help() { @@ -30,7 +21,7 @@ int main(int argc, char **argv) { if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } - else if (strcmp(argv[i], "-h") == 0) { print_help(); } + else if (strcmp(argv[i], "-h") == 0) { print_help(); return 0; } } //detect any potential exceptions @@ -48,7 +39,7 @@ int main(int argc, char **argv) { //2.2 Load & build the device code cl::Program::Sources sources; - AddSources(sources, "kernels/my_kernels_1.cl"); + AddSources(sources, "kernels/my_kernels.cl"); cl::Program program(context, sources); diff --git a/Tutorial 1/src/kernels/my_kernels_1.cl b/Tutorial 1/src/kernels/my_kernels.cl similarity index 100% rename from Tutorial 1/src/kernels/my_kernels_1.cl rename to Tutorial 1/src/kernels/my_kernels.cl diff --git a/Tutorial 2/CMakeLists.txt b/Tutorial 2/CMakeLists.txt index 153b8ec..1949769 100644 --- a/Tutorial 2/CMakeLists.txt +++ b/Tutorial 2/CMakeLists.txt @@ -28,7 +28,7 @@ endif (NOT MSVC) set (SOURCES ../include/Utils.h "src/Tutorial 2.cpp" - src/kernels/my_kernels_2.cl) + src/kernels/my_kernels.cl) include_directories(${EXTRA_INCLUDE_DIRS}) link_directories(${EXTRA_LIBRARY_DIRS}) diff --git a/Tutorial 2/src/Tutorial 2.cpp b/Tutorial 2/src/Tutorial 2.cpp index b5f2044..313837d 100644 --- a/Tutorial 2/src/Tutorial 2.cpp +++ b/Tutorial 2/src/Tutorial 2.cpp @@ -1,15 +1,6 @@ -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#define __CL_ENABLE_EXCEPTIONS - #include #include -#ifdef __APPLE__ -#include -#else -#include -#endif - #include "Utils.h" #include "CImg.h" @@ -64,7 +55,7 @@ int main(int argc, char **argv) { //3.2 Load & build the device code cl::Program::Sources sources; - AddSources(sources, "kernels/my_kernels_2.cl"); + AddSources(sources, "kernels/my_kernels.cl"); cl::Program program(context, sources); diff --git a/Tutorial 2/src/kernels/my_kernels_2.cl b/Tutorial 2/src/kernels/my_kernels.cl similarity index 100% rename from Tutorial 2/src/kernels/my_kernels_2.cl rename to Tutorial 2/src/kernels/my_kernels.cl diff --git a/Tutorial 3/CMakeLists.txt b/Tutorial 3/CMakeLists.txt index cf24497..1b46132 100644 --- a/Tutorial 3/CMakeLists.txt +++ b/Tutorial 3/CMakeLists.txt @@ -19,7 +19,7 @@ set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) set (SOURCES ../include/Utils.h "src/Tutorial 3.cpp" - src/kernels/my_kernels_3.cl) + src/kernels/my_kernels.cl) include_directories(${EXTRA_INCLUDE_DIRS}) link_directories(${EXTRA_LIBRARY_DIRS}) diff --git a/Tutorial 3/src/Tutorial 3.cpp b/Tutorial 3/src/Tutorial 3.cpp index bf279b5..9b9aede 100644 --- a/Tutorial 3/src/Tutorial 3.cpp +++ b/Tutorial 3/src/Tutorial 3.cpp @@ -1,15 +1,6 @@ -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#define __CL_ENABLE_EXCEPTIONS - #include #include -#ifdef __APPLE__ -#include -#else -#include -#endif - #include "Utils.h" void print_help() { @@ -48,7 +39,7 @@ int main(int argc, char **argv) { //2.2 Load & build the device code cl::Program::Sources sources; - AddSources(sources, "kernels/my_kernels_3.cl"); + AddSources(sources, "kernels/my_kernels.cl"); cl::Program program(context, sources); diff --git a/Tutorial 3/src/kernels/my_kernels_3.cl b/Tutorial 3/src/kernels/my_kernels.cl similarity index 100% rename from Tutorial 3/src/kernels/my_kernels_3.cl rename to Tutorial 3/src/kernels/my_kernels.cl diff --git a/Tutorial 4/CMakeLists.txt b/Tutorial 4/CMakeLists.txt index 6c411fa..4638b2e 100644 --- a/Tutorial 4/CMakeLists.txt +++ b/Tutorial 4/CMakeLists.txt @@ -10,20 +10,20 @@ set(EXTRA_INCLUDE_DIRS ../include) set(EXTRA_LIBRARY_DIRS "") set(EXTRA_LIBRARIES "") -find_package(Boost REQUIRED) +find_package(Boost REQUIRED system) set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) set (EXTRA_LIBRARY_DIRS ${EXTRA_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) +set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${Boost_LIBRARIES}) find_package(OpenCL REQUIRED) set (EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} ${OpenCL_INCLUDE_DIRS}) -set (EXTRA_LIBRARY_DIRS ${EXTRA_LIBRARY_DIRS} ${OPENCL_LIBRARY}) +set (EXTRA_LIBRARY_DIRS ${EXTRA_LIBRARY_DIRS} ${OpenCL_LIBRARY}) set (EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${OpenCL_LIBRARIES}) -message(${Boost_INCLUDE_DIRS}) - set (SOURCES + ../include/Utils.h "src/Tutorial 4.cpp") include_directories(${EXTRA_INCLUDE_DIRS}) diff --git a/Tutorial 4/src/Tutorial 4.cpp b/Tutorial 4/src/Tutorial 4.cpp index de3038b..8f6caa2 100644 --- a/Tutorial 4/src/Tutorial 4.cpp +++ b/Tutorial 4/src/Tutorial 4.cpp @@ -2,21 +2,11 @@ #include #include #include +#include "Utils.h" namespace compute = boost::compute; using namespace std; -//print out stl vectors -template -ostream& operator<< (ostream& out, const vector& v) { - if (!v.empty()) { - out << '['; - copy(v.begin(), v.end(), ostream_iterator(out, ", ")); - out << "\b\b]"; - } - return out; -} - int main() { typedef int mytype; diff --git a/include/CL/cl2.hpp b/include/CL/cl2.hpp new file mode 100644 index 0000000..0d6e805 --- /dev/null +++ b/include/CL/cl2.hpp @@ -0,0 +1,9570 @@ +/******************************************************************************* + * Copyright (c) 2008-2016 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/*! \file + * + * \brief C++ bindings for OpenCL 1.0 (rev 48), OpenCL 1.1 (rev 33), + * OpenCL 1.2 (rev 15) and OpenCL 2.0 (rev 29) + * \author Lee Howes and Bruce Merry + * + * Derived from the OpenCL 1.x C++ bindings written by + * Benedict R. Gaster, Laurent Morichetti and Lee Howes + * With additions and fixes from: + * Brian Cole, March 3rd 2010 and April 2012 + * Matt Gruenke, April 2012. + * Bruce Merry, February 2013. + * Tom Deakin and Simon McIntosh-Smith, July 2013 + * James Price, 2015- + * + * \version 2.0.10 + * \date 2016-07-20 + * + * Optional extension support + * + * cl_ext_device_fission + * #define CL_HPP_USE_CL_DEVICE_FISSION + * cl_khr_d3d10_sharing + * #define CL_HPP_USE_DX_INTEROP + * cl_khr_sub_groups + * #define CL_HPP_USE_CL_SUB_GROUPS_KHR + * + * Doxygen documentation for this header is available here: + * + * http://khronosgroup.github.io/OpenCL-CLHPP/ + * + * The latest version of this header can be found on the GitHub releases page: + * + * https://github.com/KhronosGroup/OpenCL-CLHPP/releases + * + * Bugs and patches can be submitted to the GitHub repository: + * + * https://github.com/KhronosGroup/OpenCL-CLHPP + */ + +/*! \mainpage + * \section intro Introduction + * For many large applications C++ is the language of choice and so it seems + * reasonable to define C++ bindings for OpenCL. + * + * The interface is contained with a single C++ header file \em cl2.hpp and all + * definitions are contained within the namespace \em cl. There is no additional + * requirement to include \em cl.h and to use either the C++ or original C + * bindings; it is enough to simply include \em cl2.hpp. + * + * The bindings themselves are lightweight and correspond closely to the + * underlying C API. Using the C++ bindings introduces no additional execution + * overhead. + * + * There are numerous compatibility, portability and memory management + * fixes in the new header as well as additional OpenCL 2.0 features. + * As a result the header is not directly backward compatible and for this + * reason we release it as cl2.hpp rather than a new version of cl.hpp. + * + * + * \section compatibility Compatibility + * Due to the evolution of the underlying OpenCL API the 2.0 C++ bindings + * include an updated approach to defining supported feature versions + * and the range of valid underlying OpenCL runtime versions supported. + * + * The combination of preprocessor macros CL_HPP_TARGET_OPENCL_VERSION and + * CL_HPP_MINIMUM_OPENCL_VERSION control this range. These are three digit + * decimal values representing OpenCL runime versions. The default for + * the target is 200, representing OpenCL 2.0 and the minimum is also + * defined as 200. These settings would use 2.0 API calls only. + * If backward compatibility with a 1.2 runtime is required, the minimum + * version may be set to 120. + * + * Note that this is a compile-time setting, and so affects linking against + * a particular SDK version rather than the versioning of the loaded runtime. + * + * The earlier versions of the header included basic vector and string + * classes based loosely on STL versions. These were difficult to + * maintain and very rarely used. For the 2.0 header we now assume + * the presence of the standard library unless requested otherwise. + * We use std::array, std::vector, std::shared_ptr and std::string + * throughout to safely manage memory and reduce the chance of a + * recurrance of earlier memory management bugs. + * + * These classes are used through typedefs in the cl namespace: + * cl::array, cl::vector, cl::pointer and cl::string. + * In addition cl::allocate_pointer forwards to std::allocate_shared + * by default. + * In all cases these standard library classes can be replaced with + * custom interface-compatible versions using the CL_HPP_NO_STD_ARRAY, + * CL_HPP_NO_STD_VECTOR, CL_HPP_NO_STD_UNIQUE_PTR and + * CL_HPP_NO_STD_STRING macros. + * + * The OpenCL 1.x versions of the C++ bindings included a size_t wrapper + * class to interface with kernel enqueue. This caused unpleasant interactions + * with the standard size_t declaration and led to namespacing bugs. + * In the 2.0 version we have replaced this with a std::array-based interface. + * However, the old behaviour can be regained for backward compatibility + * using the CL_HPP_ENABLE_SIZE_T_COMPATIBILITY macro. + * + * Finally, the program construction interface used a clumsy vector-of-pairs + * design in the earlier versions. We have replaced that with a cleaner + * vector-of-vectors and vector-of-strings design. However, for backward + * compatibility old behaviour can be regained with the + * CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY macro. + * + * In OpenCL 2.0 OpenCL C is not entirely backward compatibility with + * earlier versions. As a result a flag must be passed to the OpenCL C + * compiled to request OpenCL 2.0 compilation of kernels with 1.2 as + * the default in the absence of the flag. + * In some cases the C++ bindings automatically compile code for ease. + * For those cases the compilation defaults to OpenCL C 2.0. + * If this is not wanted, the CL_HPP_CL_1_2_DEFAULT_BUILD macro may + * be specified to assume 1.2 compilation. + * If more fine-grained decisions on a per-kernel bases are required + * then explicit build operations that take the flag should be used. + * + * + * \section parameterization Parameters + * This header may be parameterized by a set of preprocessor macros. + * + * - CL_HPP_TARGET_OPENCL_VERSION + * + * Defines the target OpenCL runtime version to build the header + * against. Defaults to 200, representing OpenCL 2.0. + * + * - CL_HPP_NO_STD_STRING + * + * Do not use the standard library string class. cl::string is not + * defined and may be defined by the user before cl2.hpp is + * included. + * + * - CL_HPP_NO_STD_VECTOR + * + * Do not use the standard library vector class. cl::vector is not + * defined and may be defined by the user before cl2.hpp is + * included. + * + * - CL_HPP_NO_STD_ARRAY + * + * Do not use the standard library array class. cl::array is not + * defined and may be defined by the user before cl2.hpp is + * included. + * + * - CL_HPP_NO_STD_UNIQUE_PTR + * + * Do not use the standard library unique_ptr class. cl::pointer and + * the cl::allocate_pointer functions are not defined and may be + * defined by the user before cl2.hpp is included. + * + * - CL_HPP_ENABLE_DEVICE_FISSION + * + * Enables device fission for OpenCL 1.2 platforms. + * + * - CL_HPP_ENABLE_EXCEPTIONS + * + * Enable exceptions for use in the C++ bindings header. This is the + * preferred error handling mechanism but is not required. + * + * - CL_HPP_ENABLE_SIZE_T_COMPATIBILITY + * + * Backward compatibility option to support cl.hpp-style size_t + * class. Replaces the updated std::array derived version and + * removal of size_t from the namespace. Note that in this case the + * new size_t class is placed in the cl::compatibility namespace and + * thus requires an additional using declaration for direct backward + * compatibility. + * + * - CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY + * + * Enable older vector of pairs interface for construction of + * programs. + * + * - CL_HPP_CL_1_2_DEFAULT_BUILD + * + * Default to OpenCL C 1.2 compilation rather than OpenCL C 2.0 + * applies to use of cl::Program construction and other program + * build variants. + * + * + * \section example Example + * + * The following example shows a general use case for the C++ + * bindings, including support for the optional exception feature and + * also the supplied vector and string classes, see following sections for + * decriptions of these features. + * + * \code + #define CL_HPP_ENABLE_EXCEPTIONS + #define CL_HPP_TARGET_OPENCL_VERSION 200 + + #include + #include + #include + #include + #include + + const int numElements = 32; + + int main(void) + { + // Filter for a 2.0 platform and set it as the default + std::vector platforms; + cl::Platform::get(&platforms); + cl::Platform plat; + for (auto &p : platforms) { + std::string platver = p.getInfo(); + if (platver.find("OpenCL 2.") != std::string::npos) { + plat = p; + } + } + if (plat() == 0) { + std::cout << "No OpenCL 2.0 platform found."; + return -1; + } + + cl::Platform newP = cl::Platform::setDefault(plat); + if (newP != plat) { + std::cout << "Error setting default platform."; + return -1; + } + + // Use C++11 raw string literals for kernel source code + std::string kernel1{R"CLC( + global int globalA; + kernel void updateGlobal() + { + globalA = 75; + } + )CLC"}; + std::string kernel2{R"CLC( + typedef struct { global int *bar; } Foo; + kernel void vectorAdd(global const Foo* aNum, global const int *inputA, global const int *inputB, + global int *output, int val, write_only pipe int outPipe, queue_t childQueue) + { + output[get_global_id(0)] = inputA[get_global_id(0)] + inputB[get_global_id(0)] + val + *(aNum->bar); + write_pipe(outPipe, &val); + queue_t default_queue = get_default_queue(); + ndrange_t ndrange = ndrange_1D(get_global_size(0)/2, get_global_size(0)/2); + + // Have a child kernel write into third quarter of output + enqueue_kernel(default_queue, CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange, + ^{ + output[get_global_size(0)*2 + get_global_id(0)] = + inputA[get_global_size(0)*2 + get_global_id(0)] + inputB[get_global_size(0)*2 + get_global_id(0)] + globalA; + }); + + // Have a child kernel write into last quarter of output + enqueue_kernel(childQueue, CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange, + ^{ + output[get_global_size(0)*3 + get_global_id(0)] = + inputA[get_global_size(0)*3 + get_global_id(0)] + inputB[get_global_size(0)*3 + get_global_id(0)] + globalA + 2; + }); + } + )CLC"}; + + // New simpler string interface style + std::vector programStrings {kernel1, kernel2}; + + cl::Program vectorAddProgram(programStrings); + try { + vectorAddProgram.build("-cl-std=CL2.0"); + } + catch (...) { + // Print build info for all devices + cl_int buildErr = CL_SUCCESS; + auto buildInfo = vectorAddProgram.getBuildInfo(&buildErr); + for (auto &pair : buildInfo) { + std::cerr << pair.second << std::endl << std::endl; + } + + return 1; + } + + typedef struct { int *bar; } Foo; + + // Get and run kernel that initializes the program-scope global + // A test for kernels that take no arguments + auto program2Kernel = + cl::KernelFunctor<>(vectorAddProgram, "updateGlobal"); + program2Kernel( + cl::EnqueueArgs( + cl::NDRange(1))); + + ////////////////// + // SVM allocations + + auto anSVMInt = cl::allocate_svm>(); + *anSVMInt = 5; + cl::SVMAllocator>> svmAllocReadOnly; + auto fooPointer = cl::allocate_pointer(svmAllocReadOnly); + fooPointer->bar = anSVMInt.get(); + cl::SVMAllocator> svmAlloc; + std::vector>> inputA(numElements, 1, svmAlloc); + cl::coarse_svm_vector inputB(numElements, 2, svmAlloc); + + // + ////////////// + + // Traditional cl_mem allocations + std::vector output(numElements, 0xdeadbeef); + cl::Buffer outputBuffer(begin(output), end(output), false); + cl::Pipe aPipe(sizeof(cl_int), numElements / 2); + + // Default command queue, also passed in as a parameter + cl::DeviceCommandQueue defaultDeviceQueue = cl::DeviceCommandQueue::makeDefault( + cl::Context::getDefault(), cl::Device::getDefault()); + + auto vectorAddKernel = + cl::KernelFunctor< + decltype(fooPointer)&, + int*, + cl::coarse_svm_vector&, + cl::Buffer, + int, + cl::Pipe&, + cl::DeviceCommandQueue + >(vectorAddProgram, "vectorAdd"); + + // Ensure that the additional SVM pointer is available to the kernel + // This one was not passed as a parameter + vectorAddKernel.setSVMPointers(anSVMInt); + + // Hand control of coarse allocations to runtime + cl::enqueueUnmapSVM(anSVMInt); + cl::enqueueUnmapSVM(fooPointer); + cl::unmapSVM(inputB); + cl::unmapSVM(output2); + + cl_int error; + vectorAddKernel( + cl::EnqueueArgs( + cl::NDRange(numElements/2), + cl::NDRange(numElements/2)), + fooPointer, + inputA.data(), + inputB, + outputBuffer, + 3, + aPipe, + defaultDeviceQueue, + error + ); + + cl::copy(outputBuffer, begin(output), end(output)); + // Grab the SVM output vector using a map + cl::mapSVM(output2); + + cl::Device d = cl::Device::getDefault(); + + std::cout << "Output:\n"; + for (int i = 1; i < numElements; ++i) { + std::cout << "\t" << output[i] << "\n"; + } + std::cout << "\n\n"; + + return 0; + } + * + * \endcode + * + */ +#ifndef CL_HPP_ +#define CL_HPP_ + +/* Handle deprecated preprocessor definitions. In each case, we only check for + * the old name if the new name is not defined, so that user code can define + * both and hence work with either version of the bindings. + */ +#if !defined(CL_HPP_USE_DX_INTEROP) && defined(USE_DX_INTEROP) +# pragma message("cl2.hpp: USE_DX_INTEROP is deprecated. Define CL_HPP_USE_DX_INTEROP instead") +# define CL_HPP_USE_DX_INTEROP +#endif +#if !defined(CL_HPP_USE_CL_DEVICE_FISSION) && defined(USE_CL_DEVICE_FISSION) +# pragma message("cl2.hpp: USE_CL_DEVICE_FISSION is deprecated. Define CL_HPP_USE_CL_DEVICE_FISSION instead") +# define CL_HPP_USE_CL_DEVICE_FISSION +#endif +#if !defined(CL_HPP_ENABLE_EXCEPTIONS) && defined(__CL_ENABLE_EXCEPTIONS) +# pragma message("cl2.hpp: __CL_ENABLE_EXCEPTIONS is deprecated. Define CL_HPP_ENABLE_EXCEPTIONS instead") +# define CL_HPP_ENABLE_EXCEPTIONS +#endif +#if !defined(CL_HPP_NO_STD_VECTOR) && defined(__NO_STD_VECTOR) +# pragma message("cl2.hpp: __NO_STD_VECTOR is deprecated. Define CL_HPP_NO_STD_VECTOR instead") +# define CL_HPP_NO_STD_VECTOR +#endif +#if !defined(CL_HPP_NO_STD_STRING) && defined(__NO_STD_STRING) +# pragma message("cl2.hpp: __NO_STD_STRING is deprecated. Define CL_HPP_NO_STD_STRING instead") +# define CL_HPP_NO_STD_STRING +#endif +#if defined(VECTOR_CLASS) +# pragma message("cl2.hpp: VECTOR_CLASS is deprecated. Alias cl::vector instead") +#endif +#if defined(STRING_CLASS) +# pragma message("cl2.hpp: STRING_CLASS is deprecated. Alias cl::string instead.") +#endif +#if !defined(CL_HPP_USER_OVERRIDE_ERROR_STRINGS) && defined(__CL_USER_OVERRIDE_ERROR_STRINGS) +# pragma message("cl2.hpp: __CL_USER_OVERRIDE_ERROR_STRINGS is deprecated. Define CL_HPP_USER_OVERRIDE_ERROR_STRINGS instead") +# define CL_HPP_USER_OVERRIDE_ERROR_STRINGS +#endif + +/* Warn about features that are no longer supported + */ +#if defined(__USE_DEV_VECTOR) +# pragma message("cl2.hpp: __USE_DEV_VECTOR is no longer supported. Expect compilation errors") +#endif +#if defined(__USE_DEV_STRING) +# pragma message("cl2.hpp: __USE_DEV_STRING is no longer supported. Expect compilation errors") +#endif + +/* Detect which version to target */ +#if !defined(CL_HPP_TARGET_OPENCL_VERSION) +# pragma message("cl2.hpp: CL_HPP_TARGET_OPENCL_VERSION is not defined. It will default to 200 (OpenCL 2.0)") +# define CL_HPP_TARGET_OPENCL_VERSION 200 +#endif +#if CL_HPP_TARGET_OPENCL_VERSION != 100 && CL_HPP_TARGET_OPENCL_VERSION != 110 && CL_HPP_TARGET_OPENCL_VERSION != 120 && CL_HPP_TARGET_OPENCL_VERSION != 200 +# pragma message("cl2.hpp: CL_HPP_TARGET_OPENCL_VERSION is not a valid value (100, 110, 120 or 200). It will be set to 200") +# undef CL_HPP_TARGET_OPENCL_VERSION +# define CL_HPP_TARGET_OPENCL_VERSION 200 +#endif + +#if !defined(CL_HPP_MINIMUM_OPENCL_VERSION) +# define CL_HPP_MINIMUM_OPENCL_VERSION 200 +#endif +#if CL_HPP_MINIMUM_OPENCL_VERSION != 100 && CL_HPP_MINIMUM_OPENCL_VERSION != 110 && CL_HPP_MINIMUM_OPENCL_VERSION != 120 && CL_HPP_MINIMUM_OPENCL_VERSION != 200 +# pragma message("cl2.hpp: CL_HPP_MINIMUM_OPENCL_VERSION is not a valid value (100, 110, 120 or 200). It will be set to 100") +# undef CL_HPP_MINIMUM_OPENCL_VERSION +# define CL_HPP_MINIMUM_OPENCL_VERSION 100 +#endif +#if CL_HPP_MINIMUM_OPENCL_VERSION > CL_HPP_TARGET_OPENCL_VERSION +# error "CL_HPP_MINIMUM_OPENCL_VERSION must not be greater than CL_HPP_TARGET_OPENCL_VERSION" +#endif + +#if CL_HPP_MINIMUM_OPENCL_VERSION <= 100 && !defined(CL_USE_DEPRECATED_OPENCL_1_0_APIS) +# define CL_USE_DEPRECATED_OPENCL_1_0_APIS +#endif +#if CL_HPP_MINIMUM_OPENCL_VERSION <= 110 && !defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) +# define CL_USE_DEPRECATED_OPENCL_1_1_APIS +#endif +#if CL_HPP_MINIMUM_OPENCL_VERSION <= 120 && !defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS) +# define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#endif +#if CL_HPP_MINIMUM_OPENCL_VERSION <= 200 && !defined(CL_USE_DEPRECATED_OPENCL_2_0_APIS) +# define CL_USE_DEPRECATED_OPENCL_2_0_APIS +#endif + +#ifdef _WIN32 + +#include + +#if defined(CL_HPP_USE_DX_INTEROP) +#include +#include +#endif +#endif // _WIN32 + +#if defined(_MSC_VER) +#include +#endif // _MSC_VER + + // Check for a valid C++ version + +// Need to do both tests here because for some reason __cplusplus is not +// updated in visual studio +#if (!defined(_MSC_VER) && __cplusplus < 201103L) || (defined(_MSC_VER) && _MSC_VER < 1700) +#error Visual studio 2013 or another C++11-supporting compiler required +#endif + +// +#if defined(CL_HPP_USE_CL_DEVICE_FISSION) || defined(CL_HPP_USE_CL_SUB_GROUPS_KHR) +#include +#endif + +#if defined(__APPLE__) || defined(__MACOSX) +#include +#else +#include +#endif // !__APPLE__ + +#if (__cplusplus >= 201103L) +#define CL_HPP_NOEXCEPT_ noexcept +#else +#define CL_HPP_NOEXCEPT_ +#endif + +#if defined(_MSC_VER) +# define CL_HPP_DEFINE_STATIC_MEMBER_ __declspec(selectany) +#else +# define CL_HPP_DEFINE_STATIC_MEMBER_ __attribute__((weak)) +#endif // !_MSC_VER + +// Define deprecated prefixes and suffixes to ensure compilation +// in case they are not pre-defined +#if !defined(CL_EXT_PREFIX__VERSION_1_1_DEPRECATED) +#define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED +#endif // #if !defined(CL_EXT_PREFIX__VERSION_1_1_DEPRECATED) +#if !defined(CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED) +#define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED +#endif // #if !defined(CL_EXT_PREFIX__VERSION_1_1_DEPRECATED) + +#if !defined(CL_EXT_PREFIX__VERSION_1_2_DEPRECATED) +#define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED +#endif // #if !defined(CL_EXT_PREFIX__VERSION_1_2_DEPRECATED) +#if !defined(CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED) +#define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED +#endif // #if !defined(CL_EXT_PREFIX__VERSION_1_2_DEPRECATED) + +#if !defined(CL_CALLBACK) +#define CL_CALLBACK +#endif //CL_CALLBACK + +#include +#include +#include +#include +#include +#include + + +// Define a size_type to represent a correctly resolved size_t +#if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY) +namespace cl { + using size_type = ::size_t; +} // namespace cl +#else // #if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY) +namespace cl { + using size_type = size_t; +} // namespace cl +#endif // #if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY) + + +#if defined(CL_HPP_ENABLE_EXCEPTIONS) +#include +#endif // #if defined(CL_HPP_ENABLE_EXCEPTIONS) + +#if !defined(CL_HPP_NO_STD_VECTOR) +#include +namespace cl { + template < class T, class Alloc = std::allocator > + using vector = std::vector; +} // namespace cl +#endif // #if !defined(CL_HPP_NO_STD_VECTOR) + +#if !defined(CL_HPP_NO_STD_STRING) +#include +namespace cl { + using string = std::string; +} // namespace cl +#endif // #if !defined(CL_HPP_NO_STD_STRING) + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + +#if !defined(CL_HPP_NO_STD_UNIQUE_PTR) +#include +namespace cl { + // Replace unique_ptr and allocate_pointer for internal use + // to allow user to replace them + template + using pointer = std::unique_ptr; +} // namespace cl +#endif +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 +#if !defined(CL_HPP_NO_STD_ARRAY) +#include +namespace cl { + template < class T, size_type N > + using array = std::array; +} // namespace cl +#endif // #if !defined(CL_HPP_NO_STD_ARRAY) + +// Define size_type appropriately to allow backward-compatibility +// use of the old size_t interface class +#if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY) +namespace cl { + namespace compatibility { + /*! \brief class used to interface between C++ and + * OpenCL C calls that require arrays of size_t values, whose + * size is known statically. + */ + template + class size_t + { + private: + size_type data_[N]; + + public: + //! \brief Initialize size_t to all 0s + size_t() + { + for (int i = 0; i < N; ++i) { + data_[i] = 0; + } + } + + size_t(const array &rhs) + { + for (int i = 0; i < N; ++i) { + data_[i] = rhs[i]; + } + } + + size_type& operator[](int index) + { + return data_[index]; + } + + const size_type& operator[](int index) const + { + return data_[index]; + } + + //! \brief Conversion operator to T*. + operator size_type* () { return data_; } + + //! \brief Conversion operator to const T*. + operator const size_type* () const { return data_; } + + operator array() const + { + array ret; + + for (int i = 0; i < N; ++i) { + ret[i] = data_[i]; + } + return ret; + } + }; + } // namespace compatibility + + template + using size_t = compatibility::size_t; +} // namespace cl +#endif // #if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY) + +// Helper alias to avoid confusing the macros +namespace cl { + namespace detail { + using size_t_array = array; + } // namespace detail +} // namespace cl + + +/*! \namespace cl + * + * \brief The OpenCL C++ bindings are defined within this namespace. + * + */ +namespace cl { + class Memory; + +#define CL_HPP_INIT_CL_EXT_FCN_PTR_(name) \ + if (!pfn_##name) { \ + pfn_##name = (PFN_##name) \ + clGetExtensionFunctionAddress(#name); \ + if (!pfn_##name) { \ + } \ + } + +#define CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, name) \ + if (!pfn_##name) { \ + pfn_##name = (PFN_##name) \ + clGetExtensionFunctionAddressForPlatform(platform, #name); \ + if (!pfn_##name) { \ + } \ + } + + class Program; + class Device; + class Context; + class CommandQueue; + class DeviceCommandQueue; + class Memory; + class Buffer; + class Pipe; + +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + /*! \brief Exception class + * + * This may be thrown by API functions when CL_HPP_ENABLE_EXCEPTIONS is defined. + */ + class Error : public std::exception + { + private: + cl_int err_; + const char * errStr_; + public: + /*! \brief Create a new CL error exception for a given error code + * and corresponding message. + * + * \param err error code value. + * + * \param errStr a descriptive string that must remain in scope until + * handling of the exception has concluded. If set, it + * will be returned by what(). + */ + Error(cl_int err, const char * errStr = NULL) : err_(err), errStr_(errStr) + {} + + ~Error() throw() {} + + /*! \brief Get error string associated with exception + * + * \return A memory pointer to the error message string. + */ + virtual const char * what() const throw () + { + if (errStr_ == NULL) { + return "empty"; + } + else { + return errStr_; + } + } + + /*! \brief Get error code associated with exception + * + * \return The error code. + */ + cl_int err(void) const { return err_; } + }; +#define CL_HPP_ERR_STR_(x) #x +#else +#define CL_HPP_ERR_STR_(x) NULL +#endif // CL_HPP_ENABLE_EXCEPTIONS + + +namespace detail +{ +#if defined(CL_HPP_ENABLE_EXCEPTIONS) +static inline cl_int errHandler ( + cl_int err, + const char * errStr = NULL) +{ + if (err != CL_SUCCESS) { + throw Error(err, errStr); + } + return err; +} +#else +static inline cl_int errHandler (cl_int err, const char * errStr = NULL) +{ + (void) errStr; // suppress unused variable warning + return err; +} +#endif // CL_HPP_ENABLE_EXCEPTIONS +} + + + +//! \cond DOXYGEN_DETAIL +#if !defined(CL_HPP_USER_OVERRIDE_ERROR_STRINGS) +#define __GET_DEVICE_INFO_ERR CL_HPP_ERR_STR_(clGetDeviceInfo) +#define __GET_PLATFORM_INFO_ERR CL_HPP_ERR_STR_(clGetPlatformInfo) +#define __GET_DEVICE_IDS_ERR CL_HPP_ERR_STR_(clGetDeviceIDs) +#define __GET_PLATFORM_IDS_ERR CL_HPP_ERR_STR_(clGetPlatformIDs) +#define __GET_CONTEXT_INFO_ERR CL_HPP_ERR_STR_(clGetContextInfo) +#define __GET_EVENT_INFO_ERR CL_HPP_ERR_STR_(clGetEventInfo) +#define __GET_EVENT_PROFILE_INFO_ERR CL_HPP_ERR_STR_(clGetEventProfileInfo) +#define __GET_MEM_OBJECT_INFO_ERR CL_HPP_ERR_STR_(clGetMemObjectInfo) +#define __GET_IMAGE_INFO_ERR CL_HPP_ERR_STR_(clGetImageInfo) +#define __GET_SAMPLER_INFO_ERR CL_HPP_ERR_STR_(clGetSamplerInfo) +#define __GET_KERNEL_INFO_ERR CL_HPP_ERR_STR_(clGetKernelInfo) +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __GET_KERNEL_ARG_INFO_ERR CL_HPP_ERR_STR_(clGetKernelArgInfo) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __GET_KERNEL_WORK_GROUP_INFO_ERR CL_HPP_ERR_STR_(clGetKernelWorkGroupInfo) +#define __GET_PROGRAM_INFO_ERR CL_HPP_ERR_STR_(clGetProgramInfo) +#define __GET_PROGRAM_BUILD_INFO_ERR CL_HPP_ERR_STR_(clGetProgramBuildInfo) +#define __GET_COMMAND_QUEUE_INFO_ERR CL_HPP_ERR_STR_(clGetCommandQueueInfo) + +#define __CREATE_CONTEXT_ERR CL_HPP_ERR_STR_(clCreateContext) +#define __CREATE_CONTEXT_FROM_TYPE_ERR CL_HPP_ERR_STR_(clCreateContextFromType) +#define __GET_SUPPORTED_IMAGE_FORMATS_ERR CL_HPP_ERR_STR_(clGetSupportedImageFormats) + +#define __CREATE_BUFFER_ERR CL_HPP_ERR_STR_(clCreateBuffer) +#define __COPY_ERR CL_HPP_ERR_STR_(cl::copy) +#define __CREATE_SUBBUFFER_ERR CL_HPP_ERR_STR_(clCreateSubBuffer) +#define __CREATE_GL_BUFFER_ERR CL_HPP_ERR_STR_(clCreateFromGLBuffer) +#define __CREATE_GL_RENDER_BUFFER_ERR CL_HPP_ERR_STR_(clCreateFromGLBuffer) +#define __GET_GL_OBJECT_INFO_ERR CL_HPP_ERR_STR_(clGetGLObjectInfo) +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __CREATE_IMAGE_ERR CL_HPP_ERR_STR_(clCreateImage) +#define __CREATE_GL_TEXTURE_ERR CL_HPP_ERR_STR_(clCreateFromGLTexture) +#define __IMAGE_DIMENSION_ERR CL_HPP_ERR_STR_(Incorrect image dimensions) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR CL_HPP_ERR_STR_(clSetMemObjectDestructorCallback) + +#define __CREATE_USER_EVENT_ERR CL_HPP_ERR_STR_(clCreateUserEvent) +#define __SET_USER_EVENT_STATUS_ERR CL_HPP_ERR_STR_(clSetUserEventStatus) +#define __SET_EVENT_CALLBACK_ERR CL_HPP_ERR_STR_(clSetEventCallback) +#define __WAIT_FOR_EVENTS_ERR CL_HPP_ERR_STR_(clWaitForEvents) + +#define __CREATE_KERNEL_ERR CL_HPP_ERR_STR_(clCreateKernel) +#define __SET_KERNEL_ARGS_ERR CL_HPP_ERR_STR_(clSetKernelArg) +#define __CREATE_PROGRAM_WITH_SOURCE_ERR CL_HPP_ERR_STR_(clCreateProgramWithSource) +#define __CREATE_PROGRAM_WITH_BINARY_ERR CL_HPP_ERR_STR_(clCreateProgramWithBinary) +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR CL_HPP_ERR_STR_(clCreateProgramWithBuiltInKernels) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __BUILD_PROGRAM_ERR CL_HPP_ERR_STR_(clBuildProgram) +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __COMPILE_PROGRAM_ERR CL_HPP_ERR_STR_(clCompileProgram) +#define __LINK_PROGRAM_ERR CL_HPP_ERR_STR_(clLinkProgram) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __CREATE_KERNELS_IN_PROGRAM_ERR CL_HPP_ERR_STR_(clCreateKernelsInProgram) + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +#define __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR CL_HPP_ERR_STR_(clCreateCommandQueueWithProperties) +#define __CREATE_SAMPLER_WITH_PROPERTIES_ERR CL_HPP_ERR_STR_(clCreateSamplerWithProperties) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 200 +#define __SET_COMMAND_QUEUE_PROPERTY_ERR CL_HPP_ERR_STR_(clSetCommandQueueProperty) +#define __ENQUEUE_READ_BUFFER_ERR CL_HPP_ERR_STR_(clEnqueueReadBuffer) +#define __ENQUEUE_READ_BUFFER_RECT_ERR CL_HPP_ERR_STR_(clEnqueueReadBufferRect) +#define __ENQUEUE_WRITE_BUFFER_ERR CL_HPP_ERR_STR_(clEnqueueWriteBuffer) +#define __ENQUEUE_WRITE_BUFFER_RECT_ERR CL_HPP_ERR_STR_(clEnqueueWriteBufferRect) +#define __ENQEUE_COPY_BUFFER_ERR CL_HPP_ERR_STR_(clEnqueueCopyBuffer) +#define __ENQEUE_COPY_BUFFER_RECT_ERR CL_HPP_ERR_STR_(clEnqueueCopyBufferRect) +#define __ENQUEUE_FILL_BUFFER_ERR CL_HPP_ERR_STR_(clEnqueueFillBuffer) +#define __ENQUEUE_READ_IMAGE_ERR CL_HPP_ERR_STR_(clEnqueueReadImage) +#define __ENQUEUE_WRITE_IMAGE_ERR CL_HPP_ERR_STR_(clEnqueueWriteImage) +#define __ENQUEUE_COPY_IMAGE_ERR CL_HPP_ERR_STR_(clEnqueueCopyImage) +#define __ENQUEUE_FILL_IMAGE_ERR CL_HPP_ERR_STR_(clEnqueueFillImage) +#define __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR CL_HPP_ERR_STR_(clEnqueueCopyImageToBuffer) +#define __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR CL_HPP_ERR_STR_(clEnqueueCopyBufferToImage) +#define __ENQUEUE_MAP_BUFFER_ERR CL_HPP_ERR_STR_(clEnqueueMapBuffer) +#define __ENQUEUE_MAP_IMAGE_ERR CL_HPP_ERR_STR_(clEnqueueMapImage) +#define __ENQUEUE_UNMAP_MEM_OBJECT_ERR CL_HPP_ERR_STR_(clEnqueueUnMapMemObject) +#define __ENQUEUE_NDRANGE_KERNEL_ERR CL_HPP_ERR_STR_(clEnqueueNDRangeKernel) +#define __ENQUEUE_NATIVE_KERNEL CL_HPP_ERR_STR_(clEnqueueNativeKernel) +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __ENQUEUE_MIGRATE_MEM_OBJECTS_ERR CL_HPP_ERR_STR_(clEnqueueMigrateMemObjects) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + +#define __ENQUEUE_ACQUIRE_GL_ERR CL_HPP_ERR_STR_(clEnqueueAcquireGLObjects) +#define __ENQUEUE_RELEASE_GL_ERR CL_HPP_ERR_STR_(clEnqueueReleaseGLObjects) + +#define __CREATE_PIPE_ERR CL_HPP_ERR_STR_(clCreatePipe) +#define __GET_PIPE_INFO_ERR CL_HPP_ERR_STR_(clGetPipeInfo) + + +#define __RETAIN_ERR CL_HPP_ERR_STR_(Retain Object) +#define __RELEASE_ERR CL_HPP_ERR_STR_(Release Object) +#define __FLUSH_ERR CL_HPP_ERR_STR_(clFlush) +#define __FINISH_ERR CL_HPP_ERR_STR_(clFinish) +#define __VECTOR_CAPACITY_ERR CL_HPP_ERR_STR_(Vector capacity error) + +/** + * CL 1.2 version that uses device fission. + */ +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __CREATE_SUB_DEVICES_ERR CL_HPP_ERR_STR_(clCreateSubDevices) +#else +#define __CREATE_SUB_DEVICES_ERR CL_HPP_ERR_STR_(clCreateSubDevicesEXT) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + +/** + * Deprecated APIs for 1.2 + */ +#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) +#define __ENQUEUE_MARKER_ERR CL_HPP_ERR_STR_(clEnqueueMarker) +#define __ENQUEUE_WAIT_FOR_EVENTS_ERR CL_HPP_ERR_STR_(clEnqueueWaitForEvents) +#define __ENQUEUE_BARRIER_ERR CL_HPP_ERR_STR_(clEnqueueBarrier) +#define __UNLOAD_COMPILER_ERR CL_HPP_ERR_STR_(clUnloadCompiler) +#define __CREATE_GL_TEXTURE_2D_ERR CL_HPP_ERR_STR_(clCreateFromGLTexture2D) +#define __CREATE_GL_TEXTURE_3D_ERR CL_HPP_ERR_STR_(clCreateFromGLTexture3D) +#define __CREATE_IMAGE2D_ERR CL_HPP_ERR_STR_(clCreateImage2D) +#define __CREATE_IMAGE3D_ERR CL_HPP_ERR_STR_(clCreateImage3D) +#endif // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) + +/** + * Deprecated APIs for 2.0 + */ +#if defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS) +#define __CREATE_COMMAND_QUEUE_ERR CL_HPP_ERR_STR_(clCreateCommandQueue) +#define __ENQUEUE_TASK_ERR CL_HPP_ERR_STR_(clEnqueueTask) +#define __CREATE_SAMPLER_ERR CL_HPP_ERR_STR_(clCreateSampler) +#endif // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) + +/** + * CL 1.2 marker and barrier commands + */ +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#define __ENQUEUE_MARKER_WAIT_LIST_ERR CL_HPP_ERR_STR_(clEnqueueMarkerWithWaitList) +#define __ENQUEUE_BARRIER_WAIT_LIST_ERR CL_HPP_ERR_STR_(clEnqueueBarrierWithWaitList) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + +#endif // CL_HPP_USER_OVERRIDE_ERROR_STRINGS +//! \endcond + + +namespace detail { + +// Generic getInfoHelper. The final parameter is used to guide overload +// resolution: the actual parameter passed is an int, which makes this +// a worse conversion sequence than a specialization that declares the +// parameter as an int. +template +inline cl_int getInfoHelper(Functor f, cl_uint name, T* param, long) +{ + return f(name, sizeof(T), param, NULL); +} + +// Specialized for getInfo +// Assumes that the output vector was correctly resized on the way in +template +inline cl_int getInfoHelper(Func f, cl_uint name, vector>* param, int) +{ + if (name != CL_PROGRAM_BINARIES) { + return CL_INVALID_VALUE; + } + if (param) { + // Create array of pointers, calculate total size and pass pointer array in + size_type numBinaries = param->size(); + vector binariesPointers(numBinaries); + + for (size_type i = 0; i < numBinaries; ++i) + { + binariesPointers[i] = (*param)[i].data(); + } + + cl_int err = f(name, numBinaries * sizeof(unsigned char*), binariesPointers.data(), NULL); + + if (err != CL_SUCCESS) { + return err; + } + } + + + return CL_SUCCESS; +} + +// Specialized getInfoHelper for vector params +template +inline cl_int getInfoHelper(Func f, cl_uint name, vector* param, long) +{ + size_type required; + cl_int err = f(name, 0, NULL, &required); + if (err != CL_SUCCESS) { + return err; + } + const size_type elements = required / sizeof(T); + + // Temporary to avoid changing param on an error + vector localData(elements); + err = f(name, required, localData.data(), NULL); + if (err != CL_SUCCESS) { + return err; + } + if (param) { + *param = std::move(localData); + } + + return CL_SUCCESS; +} + +/* Specialization for reference-counted types. This depends on the + * existence of Wrapper::cl_type, and none of the other types having the + * cl_type member. Note that simplify specifying the parameter as Wrapper + * does not work, because when using a derived type (e.g. Context) the generic + * template will provide a better match. + */ +template +inline cl_int getInfoHelper( + Func f, cl_uint name, vector* param, int, typename T::cl_type = 0) +{ + size_type required; + cl_int err = f(name, 0, NULL, &required); + if (err != CL_SUCCESS) { + return err; + } + + const size_type elements = required / sizeof(typename T::cl_type); + + vector value(elements); + err = f(name, required, value.data(), NULL); + if (err != CL_SUCCESS) { + return err; + } + + if (param) { + // Assign to convert CL type to T for each element + param->resize(elements); + + // Assign to param, constructing with retain behaviour + // to correctly capture each underlying CL object + for (size_type i = 0; i < elements; i++) { + (*param)[i] = T(value[i], true); + } + } + return CL_SUCCESS; +} + +// Specialized GetInfoHelper for string params +template +inline cl_int getInfoHelper(Func f, cl_uint name, string* param, long) +{ + size_type required; + cl_int err = f(name, 0, NULL, &required); + if (err != CL_SUCCESS) { + return err; + } + + // std::string has a constant data member + // a char vector does not + if (required > 0) { + vector value(required); + err = f(name, required, value.data(), NULL); + if (err != CL_SUCCESS) { + return err; + } + if (param) { + param->assign(begin(value), prev(end(value))); + } + } + else if (param) { + param->assign(""); + } + return CL_SUCCESS; +} + +// Specialized GetInfoHelper for clsize_t params +template +inline cl_int getInfoHelper(Func f, cl_uint name, array* param, long) +{ + size_type required; + cl_int err = f(name, 0, NULL, &required); + if (err != CL_SUCCESS) { + return err; + } + + size_type elements = required / sizeof(size_type); + vector value(elements, 0); + + err = f(name, required, value.data(), NULL); + if (err != CL_SUCCESS) { + return err; + } + + // Bound the copy with N to prevent overruns + // if passed N > than the amount copied + if (elements > N) { + elements = N; + } + for (size_type i = 0; i < elements; ++i) { + (*param)[i] = value[i]; + } + + return CL_SUCCESS; +} + +template struct ReferenceHandler; + +/* Specialization for reference-counted types. This depends on the + * existence of Wrapper::cl_type, and none of the other types having the + * cl_type member. Note that simplify specifying the parameter as Wrapper + * does not work, because when using a derived type (e.g. Context) the generic + * template will provide a better match. + */ +template +inline cl_int getInfoHelper(Func f, cl_uint name, T* param, int, typename T::cl_type = 0) +{ + typename T::cl_type value; + cl_int err = f(name, sizeof(value), &value, NULL); + if (err != CL_SUCCESS) { + return err; + } + *param = value; + if (value != NULL) + { + err = param->retain(); + if (err != CL_SUCCESS) { + return err; + } + } + return CL_SUCCESS; +} + +#define CL_HPP_PARAM_NAME_INFO_1_0_(F) \ + F(cl_platform_info, CL_PLATFORM_PROFILE, string) \ + F(cl_platform_info, CL_PLATFORM_VERSION, string) \ + F(cl_platform_info, CL_PLATFORM_NAME, string) \ + F(cl_platform_info, CL_PLATFORM_VENDOR, string) \ + F(cl_platform_info, CL_PLATFORM_EXTENSIONS, string) \ + \ + F(cl_device_info, CL_DEVICE_TYPE, cl_device_type) \ + F(cl_device_info, CL_DEVICE_VENDOR_ID, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_COMPUTE_UNITS, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_WORK_GROUP_SIZE, size_type) \ + F(cl_device_info, CL_DEVICE_MAX_WORK_ITEM_SIZES, cl::vector) \ + F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_CLOCK_FREQUENCY, cl_uint) \ + F(cl_device_info, CL_DEVICE_ADDRESS_BITS, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_READ_IMAGE_ARGS, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_WRITE_IMAGE_ARGS, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_MEM_ALLOC_SIZE, cl_ulong) \ + F(cl_device_info, CL_DEVICE_IMAGE2D_MAX_WIDTH, size_type) \ + F(cl_device_info, CL_DEVICE_IMAGE2D_MAX_HEIGHT, size_type) \ + F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_WIDTH, size_type) \ + F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_HEIGHT, size_type) \ + F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_DEPTH, size_type) \ + F(cl_device_info, CL_DEVICE_IMAGE_SUPPORT, cl_bool) \ + F(cl_device_info, CL_DEVICE_MAX_PARAMETER_SIZE, size_type) \ + F(cl_device_info, CL_DEVICE_MAX_SAMPLERS, cl_uint) \ + F(cl_device_info, CL_DEVICE_MEM_BASE_ADDR_ALIGN, cl_uint) \ + F(cl_device_info, CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE, cl_uint) \ + F(cl_device_info, CL_DEVICE_SINGLE_FP_CONFIG, cl_device_fp_config) \ + F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHE_TYPE, cl_device_mem_cache_type) \ + F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE, cl_uint)\ + F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE, cl_ulong) \ + F(cl_device_info, CL_DEVICE_GLOBAL_MEM_SIZE, cl_ulong) \ + F(cl_device_info, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, cl_ulong) \ + F(cl_device_info, CL_DEVICE_MAX_CONSTANT_ARGS, cl_uint) \ + F(cl_device_info, CL_DEVICE_LOCAL_MEM_TYPE, cl_device_local_mem_type) \ + F(cl_device_info, CL_DEVICE_LOCAL_MEM_SIZE, cl_ulong) \ + F(cl_device_info, CL_DEVICE_ERROR_CORRECTION_SUPPORT, cl_bool) \ + F(cl_device_info, CL_DEVICE_PROFILING_TIMER_RESOLUTION, size_type) \ + F(cl_device_info, CL_DEVICE_ENDIAN_LITTLE, cl_bool) \ + F(cl_device_info, CL_DEVICE_AVAILABLE, cl_bool) \ + F(cl_device_info, CL_DEVICE_COMPILER_AVAILABLE, cl_bool) \ + F(cl_device_info, CL_DEVICE_EXECUTION_CAPABILITIES, cl_device_exec_capabilities) \ + F(cl_device_info, CL_DEVICE_PLATFORM, cl_platform_id) \ + F(cl_device_info, CL_DEVICE_NAME, string) \ + F(cl_device_info, CL_DEVICE_VENDOR, string) \ + F(cl_device_info, CL_DRIVER_VERSION, string) \ + F(cl_device_info, CL_DEVICE_PROFILE, string) \ + F(cl_device_info, CL_DEVICE_VERSION, string) \ + F(cl_device_info, CL_DEVICE_EXTENSIONS, string) \ + \ + F(cl_context_info, CL_CONTEXT_REFERENCE_COUNT, cl_uint) \ + F(cl_context_info, CL_CONTEXT_DEVICES, cl::vector) \ + F(cl_context_info, CL_CONTEXT_PROPERTIES, cl::vector) \ + \ + F(cl_event_info, CL_EVENT_COMMAND_QUEUE, cl::CommandQueue) \ + F(cl_event_info, CL_EVENT_COMMAND_TYPE, cl_command_type) \ + F(cl_event_info, CL_EVENT_REFERENCE_COUNT, cl_uint) \ + F(cl_event_info, CL_EVENT_COMMAND_EXECUTION_STATUS, cl_int) \ + \ + F(cl_profiling_info, CL_PROFILING_COMMAND_QUEUED, cl_ulong) \ + F(cl_profiling_info, CL_PROFILING_COMMAND_SUBMIT, cl_ulong) \ + F(cl_profiling_info, CL_PROFILING_COMMAND_START, cl_ulong) \ + F(cl_profiling_info, CL_PROFILING_COMMAND_END, cl_ulong) \ + \ + F(cl_mem_info, CL_MEM_TYPE, cl_mem_object_type) \ + F(cl_mem_info, CL_MEM_FLAGS, cl_mem_flags) \ + F(cl_mem_info, CL_MEM_SIZE, size_type) \ + F(cl_mem_info, CL_MEM_HOST_PTR, void*) \ + F(cl_mem_info, CL_MEM_MAP_COUNT, cl_uint) \ + F(cl_mem_info, CL_MEM_REFERENCE_COUNT, cl_uint) \ + F(cl_mem_info, CL_MEM_CONTEXT, cl::Context) \ + \ + F(cl_image_info, CL_IMAGE_FORMAT, cl_image_format) \ + F(cl_image_info, CL_IMAGE_ELEMENT_SIZE, size_type) \ + F(cl_image_info, CL_IMAGE_ROW_PITCH, size_type) \ + F(cl_image_info, CL_IMAGE_SLICE_PITCH, size_type) \ + F(cl_image_info, CL_IMAGE_WIDTH, size_type) \ + F(cl_image_info, CL_IMAGE_HEIGHT, size_type) \ + F(cl_image_info, CL_IMAGE_DEPTH, size_type) \ + \ + F(cl_sampler_info, CL_SAMPLER_REFERENCE_COUNT, cl_uint) \ + F(cl_sampler_info, CL_SAMPLER_CONTEXT, cl::Context) \ + F(cl_sampler_info, CL_SAMPLER_NORMALIZED_COORDS, cl_bool) \ + F(cl_sampler_info, CL_SAMPLER_ADDRESSING_MODE, cl_addressing_mode) \ + F(cl_sampler_info, CL_SAMPLER_FILTER_MODE, cl_filter_mode) \ + \ + F(cl_program_info, CL_PROGRAM_REFERENCE_COUNT, cl_uint) \ + F(cl_program_info, CL_PROGRAM_CONTEXT, cl::Context) \ + F(cl_program_info, CL_PROGRAM_NUM_DEVICES, cl_uint) \ + F(cl_program_info, CL_PROGRAM_DEVICES, cl::vector) \ + F(cl_program_info, CL_PROGRAM_SOURCE, string) \ + F(cl_program_info, CL_PROGRAM_BINARY_SIZES, cl::vector) \ + F(cl_program_info, CL_PROGRAM_BINARIES, cl::vector>) \ + \ + F(cl_program_build_info, CL_PROGRAM_BUILD_STATUS, cl_build_status) \ + F(cl_program_build_info, CL_PROGRAM_BUILD_OPTIONS, string) \ + F(cl_program_build_info, CL_PROGRAM_BUILD_LOG, string) \ + \ + F(cl_kernel_info, CL_KERNEL_FUNCTION_NAME, string) \ + F(cl_kernel_info, CL_KERNEL_NUM_ARGS, cl_uint) \ + F(cl_kernel_info, CL_KERNEL_REFERENCE_COUNT, cl_uint) \ + F(cl_kernel_info, CL_KERNEL_CONTEXT, cl::Context) \ + F(cl_kernel_info, CL_KERNEL_PROGRAM, cl::Program) \ + \ + F(cl_kernel_work_group_info, CL_KERNEL_WORK_GROUP_SIZE, size_type) \ + F(cl_kernel_work_group_info, CL_KERNEL_COMPILE_WORK_GROUP_SIZE, cl::detail::size_t_array) \ + F(cl_kernel_work_group_info, CL_KERNEL_LOCAL_MEM_SIZE, cl_ulong) \ + \ + F(cl_command_queue_info, CL_QUEUE_CONTEXT, cl::Context) \ + F(cl_command_queue_info, CL_QUEUE_DEVICE, cl::Device) \ + F(cl_command_queue_info, CL_QUEUE_REFERENCE_COUNT, cl_uint) \ + F(cl_command_queue_info, CL_QUEUE_PROPERTIES, cl_command_queue_properties) + + +#define CL_HPP_PARAM_NAME_INFO_1_1_(F) \ + F(cl_context_info, CL_CONTEXT_NUM_DEVICES, cl_uint)\ + F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF, cl_uint) \ + F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR, cl_uint) \ + F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT, cl_uint) \ + F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_INT, cl_uint) \ + F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG, cl_uint) \ + F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT, cl_uint) \ + F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE, cl_uint) \ + F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF, cl_uint) \ + F(cl_device_info, CL_DEVICE_DOUBLE_FP_CONFIG, cl_device_fp_config) \ + F(cl_device_info, CL_DEVICE_HALF_FP_CONFIG, cl_device_fp_config) \ + F(cl_device_info, CL_DEVICE_OPENCL_C_VERSION, string) \ + \ + F(cl_mem_info, CL_MEM_ASSOCIATED_MEMOBJECT, cl::Memory) \ + F(cl_mem_info, CL_MEM_OFFSET, size_type) \ + \ + F(cl_kernel_work_group_info, CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, size_type) \ + F(cl_kernel_work_group_info, CL_KERNEL_PRIVATE_MEM_SIZE, cl_ulong) \ + \ + F(cl_event_info, CL_EVENT_CONTEXT, cl::Context) + +#define CL_HPP_PARAM_NAME_INFO_1_2_(F) \ + F(cl_program_info, CL_PROGRAM_NUM_KERNELS, size_type) \ + F(cl_program_info, CL_PROGRAM_KERNEL_NAMES, string) \ + \ + F(cl_program_build_info, CL_PROGRAM_BINARY_TYPE, cl_program_binary_type) \ + \ + F(cl_kernel_info, CL_KERNEL_ATTRIBUTES, string) \ + \ + F(cl_kernel_arg_info, CL_KERNEL_ARG_ADDRESS_QUALIFIER, cl_kernel_arg_address_qualifier) \ + F(cl_kernel_arg_info, CL_KERNEL_ARG_ACCESS_QUALIFIER, cl_kernel_arg_access_qualifier) \ + F(cl_kernel_arg_info, CL_KERNEL_ARG_TYPE_NAME, string) \ + F(cl_kernel_arg_info, CL_KERNEL_ARG_NAME, string) \ + F(cl_kernel_arg_info, CL_KERNEL_ARG_TYPE_QUALIFIER, cl_kernel_arg_type_qualifier) \ + \ + F(cl_device_info, CL_DEVICE_PARENT_DEVICE, cl::Device) \ + F(cl_device_info, CL_DEVICE_PARTITION_PROPERTIES, cl::vector) \ + F(cl_device_info, CL_DEVICE_PARTITION_TYPE, cl::vector) \ + F(cl_device_info, CL_DEVICE_REFERENCE_COUNT, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_INTEROP_USER_SYNC, size_type) \ + F(cl_device_info, CL_DEVICE_PARTITION_AFFINITY_DOMAIN, cl_device_affinity_domain) \ + F(cl_device_info, CL_DEVICE_BUILT_IN_KERNELS, string) \ + \ + F(cl_image_info, CL_IMAGE_ARRAY_SIZE, size_type) \ + F(cl_image_info, CL_IMAGE_NUM_MIP_LEVELS, cl_uint) \ + F(cl_image_info, CL_IMAGE_NUM_SAMPLES, cl_uint) + +#define CL_HPP_PARAM_NAME_INFO_2_0_(F) \ + F(cl_device_info, CL_DEVICE_QUEUE_ON_HOST_PROPERTIES, cl_command_queue_properties) \ + F(cl_device_info, CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES, cl_command_queue_properties) \ + F(cl_device_info, CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE, cl_uint) \ + F(cl_device_info, CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_ON_DEVICE_QUEUES, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_ON_DEVICE_EVENTS, cl_uint) \ + F(cl_device_info, CL_DEVICE_MAX_PIPE_ARGS, cl_uint) \ + F(cl_device_info, CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS, cl_uint) \ + F(cl_device_info, CL_DEVICE_PIPE_MAX_PACKET_SIZE, cl_uint) \ + F(cl_device_info, CL_DEVICE_SVM_CAPABILITIES, cl_device_svm_capabilities) \ + F(cl_device_info, CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT, cl_uint) \ + F(cl_device_info, CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT, cl_uint) \ + F(cl_command_queue_info, CL_QUEUE_SIZE, cl_uint) \ + F(cl_mem_info, CL_MEM_USES_SVM_POINTER, cl_bool) \ + F(cl_program_build_info, CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE, size_type) \ + F(cl_pipe_info, CL_PIPE_PACKET_SIZE, cl_uint) \ + F(cl_pipe_info, CL_PIPE_MAX_PACKETS, cl_uint) + +#define CL_HPP_PARAM_NAME_DEVICE_FISSION_(F) \ + F(cl_device_info, CL_DEVICE_PARENT_DEVICE_EXT, cl_device_id) \ + F(cl_device_info, CL_DEVICE_PARTITION_TYPES_EXT, cl::vector) \ + F(cl_device_info, CL_DEVICE_AFFINITY_DOMAINS_EXT, cl::vector) \ + F(cl_device_info, CL_DEVICE_REFERENCE_COUNT_EXT , cl_uint) \ + F(cl_device_info, CL_DEVICE_PARTITION_STYLE_EXT, cl::vector) + +template +struct param_traits {}; + +#define CL_HPP_DECLARE_PARAM_TRAITS_(token, param_name, T) \ +struct token; \ +template<> \ +struct param_traits \ +{ \ + enum { value = param_name }; \ + typedef T param_type; \ +}; + +CL_HPP_PARAM_NAME_INFO_1_0_(CL_HPP_DECLARE_PARAM_TRAITS_) +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 +CL_HPP_PARAM_NAME_INFO_1_1_(CL_HPP_DECLARE_PARAM_TRAITS_) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +CL_HPP_PARAM_NAME_INFO_1_2_(CL_HPP_DECLARE_PARAM_TRAITS_) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +CL_HPP_PARAM_NAME_INFO_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_) +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 + + +// Flags deprecated in OpenCL 2.0 +#define CL_HPP_PARAM_NAME_INFO_1_0_DEPRECATED_IN_2_0_(F) \ + F(cl_device_info, CL_DEVICE_QUEUE_PROPERTIES, cl_command_queue_properties) + +#define CL_HPP_PARAM_NAME_INFO_1_1_DEPRECATED_IN_2_0_(F) \ + F(cl_device_info, CL_DEVICE_HOST_UNIFIED_MEMORY, cl_bool) + +#define CL_HPP_PARAM_NAME_INFO_1_2_DEPRECATED_IN_2_0_(F) \ + F(cl_image_info, CL_IMAGE_BUFFER, cl::Buffer) + +// Include deprecated query flags based on versions +// Only include deprecated 1.0 flags if 2.0 not active as there is an enum clash +#if CL_HPP_TARGET_OPENCL_VERSION > 100 && CL_HPP_MINIMUM_OPENCL_VERSION < 200 && CL_HPP_TARGET_OPENCL_VERSION < 200 +CL_HPP_PARAM_NAME_INFO_1_0_DEPRECATED_IN_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_) +#endif // CL_HPP_MINIMUM_OPENCL_VERSION < 110 +#if CL_HPP_TARGET_OPENCL_VERSION > 110 && CL_HPP_MINIMUM_OPENCL_VERSION < 200 +CL_HPP_PARAM_NAME_INFO_1_1_DEPRECATED_IN_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_) +#endif // CL_HPP_MINIMUM_OPENCL_VERSION < 120 +#if CL_HPP_TARGET_OPENCL_VERSION > 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 200 +CL_HPP_PARAM_NAME_INFO_1_2_DEPRECATED_IN_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_) +#endif // CL_HPP_MINIMUM_OPENCL_VERSION < 200 + +#if defined(CL_HPP_USE_CL_DEVICE_FISSION) +CL_HPP_PARAM_NAME_DEVICE_FISSION_(CL_HPP_DECLARE_PARAM_TRAITS_); +#endif // CL_HPP_USE_CL_DEVICE_FISSION + +#ifdef CL_PLATFORM_ICD_SUFFIX_KHR +CL_HPP_DECLARE_PARAM_TRAITS_(cl_platform_info, CL_PLATFORM_ICD_SUFFIX_KHR, string) +#endif + +#ifdef CL_DEVICE_PROFILING_TIMER_OFFSET_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_PROFILING_TIMER_OFFSET_AMD, cl_ulong) +#endif + +#ifdef CL_DEVICE_GLOBAL_FREE_MEMORY_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_FREE_MEMORY_AMD, vector) +#endif +#ifdef CL_DEVICE_SIMD_PER_COMPUTE_UNIT_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SIMD_PER_COMPUTE_UNIT_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_SIMD_WIDTH_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SIMD_WIDTH_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_SIMD_INSTRUCTION_WIDTH_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SIMD_INSTRUCTION_WIDTH_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_WAVEFRONT_WIDTH_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_WAVEFRONT_WIDTH_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_GLOBAL_MEM_CHANNELS_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNELS_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_GLOBAL_MEM_CHANNEL_BANKS_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNEL_BANKS_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_GLOBAL_MEM_CHANNEL_BANK_WIDTH_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNEL_BANK_WIDTH_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_LOCAL_MEM_SIZE_PER_COMPUTE_UNIT_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_LOCAL_MEM_SIZE_PER_COMPUTE_UNIT_AMD, cl_uint) +#endif +#ifdef CL_DEVICE_LOCAL_MEM_BANKS_AMD +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_LOCAL_MEM_BANKS_AMD, cl_uint) +#endif + +#ifdef CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV, cl_uint) +#endif +#ifdef CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV, cl_uint) +#endif +#ifdef CL_DEVICE_REGISTERS_PER_BLOCK_NV +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_REGISTERS_PER_BLOCK_NV, cl_uint) +#endif +#ifdef CL_DEVICE_WARP_SIZE_NV +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_WARP_SIZE_NV, cl_uint) +#endif +#ifdef CL_DEVICE_GPU_OVERLAP_NV +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GPU_OVERLAP_NV, cl_bool) +#endif +#ifdef CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV, cl_bool) +#endif +#ifdef CL_DEVICE_INTEGRATED_MEMORY_NV +CL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_INTEGRATED_MEMORY_NV, cl_bool) +#endif + +// Convenience functions + +template +inline cl_int +getInfo(Func f, cl_uint name, T* param) +{ + return getInfoHelper(f, name, param, 0); +} + +template +struct GetInfoFunctor0 +{ + Func f_; const Arg0& arg0_; + cl_int operator ()( + cl_uint param, size_type size, void* value, size_type* size_ret) + { return f_(arg0_, param, size, value, size_ret); } +}; + +template +struct GetInfoFunctor1 +{ + Func f_; const Arg0& arg0_; const Arg1& arg1_; + cl_int operator ()( + cl_uint param, size_type size, void* value, size_type* size_ret) + { return f_(arg0_, arg1_, param, size, value, size_ret); } +}; + +template +inline cl_int +getInfo(Func f, const Arg0& arg0, cl_uint name, T* param) +{ + GetInfoFunctor0 f0 = { f, arg0 }; + return getInfoHelper(f0, name, param, 0); +} + +template +inline cl_int +getInfo(Func f, const Arg0& arg0, const Arg1& arg1, cl_uint name, T* param) +{ + GetInfoFunctor1 f0 = { f, arg0, arg1 }; + return getInfoHelper(f0, name, param, 0); +} + + +template +struct ReferenceHandler +{ }; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +/** + * OpenCL 1.2 devices do have retain/release. + */ +template <> +struct ReferenceHandler +{ + /** + * Retain the device. + * \param device A valid device created using createSubDevices + * \return + * CL_SUCCESS if the function executed successfully. + * CL_INVALID_DEVICE if device was not a valid subdevice + * CL_OUT_OF_RESOURCES + * CL_OUT_OF_HOST_MEMORY + */ + static cl_int retain(cl_device_id device) + { return ::clRetainDevice(device); } + /** + * Retain the device. + * \param device A valid device created using createSubDevices + * \return + * CL_SUCCESS if the function executed successfully. + * CL_INVALID_DEVICE if device was not a valid subdevice + * CL_OUT_OF_RESOURCES + * CL_OUT_OF_HOST_MEMORY + */ + static cl_int release(cl_device_id device) + { return ::clReleaseDevice(device); } +}; +#else // CL_HPP_TARGET_OPENCL_VERSION >= 120 +/** + * OpenCL 1.1 devices do not have retain/release. + */ +template <> +struct ReferenceHandler +{ + // cl_device_id does not have retain(). + static cl_int retain(cl_device_id) + { return CL_SUCCESS; } + // cl_device_id does not have release(). + static cl_int release(cl_device_id) + { return CL_SUCCESS; } +}; +#endif // ! (CL_HPP_TARGET_OPENCL_VERSION >= 120) + +template <> +struct ReferenceHandler +{ + // cl_platform_id does not have retain(). + static cl_int retain(cl_platform_id) + { return CL_SUCCESS; } + // cl_platform_id does not have release(). + static cl_int release(cl_platform_id) + { return CL_SUCCESS; } +}; + +template <> +struct ReferenceHandler +{ + static cl_int retain(cl_context context) + { return ::clRetainContext(context); } + static cl_int release(cl_context context) + { return ::clReleaseContext(context); } +}; + +template <> +struct ReferenceHandler +{ + static cl_int retain(cl_command_queue queue) + { return ::clRetainCommandQueue(queue); } + static cl_int release(cl_command_queue queue) + { return ::clReleaseCommandQueue(queue); } +}; + +template <> +struct ReferenceHandler +{ + static cl_int retain(cl_mem memory) + { return ::clRetainMemObject(memory); } + static cl_int release(cl_mem memory) + { return ::clReleaseMemObject(memory); } +}; + +template <> +struct ReferenceHandler +{ + static cl_int retain(cl_sampler sampler) + { return ::clRetainSampler(sampler); } + static cl_int release(cl_sampler sampler) + { return ::clReleaseSampler(sampler); } +}; + +template <> +struct ReferenceHandler +{ + static cl_int retain(cl_program program) + { return ::clRetainProgram(program); } + static cl_int release(cl_program program) + { return ::clReleaseProgram(program); } +}; + +template <> +struct ReferenceHandler +{ + static cl_int retain(cl_kernel kernel) + { return ::clRetainKernel(kernel); } + static cl_int release(cl_kernel kernel) + { return ::clReleaseKernel(kernel); } +}; + +template <> +struct ReferenceHandler +{ + static cl_int retain(cl_event event) + { return ::clRetainEvent(event); } + static cl_int release(cl_event event) + { return ::clReleaseEvent(event); } +}; + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 120 +// Extracts version number with major in the upper 16 bits, minor in the lower 16 +static cl_uint getVersion(const vector &versionInfo) +{ + int highVersion = 0; + int lowVersion = 0; + int index = 7; + while(versionInfo[index] != '.' ) { + highVersion *= 10; + highVersion += versionInfo[index]-'0'; + ++index; + } + ++index; + while(versionInfo[index] != ' ' && versionInfo[index] != '\0') { + lowVersion *= 10; + lowVersion += versionInfo[index]-'0'; + ++index; + } + return (highVersion << 16) | lowVersion; +} + +static cl_uint getPlatformVersion(cl_platform_id platform) +{ + size_type size = 0; + clGetPlatformInfo(platform, CL_PLATFORM_VERSION, 0, NULL, &size); + + vector versionInfo(size); + clGetPlatformInfo(platform, CL_PLATFORM_VERSION, size, versionInfo.data(), &size); + return getVersion(versionInfo); +} + +static cl_uint getDevicePlatformVersion(cl_device_id device) +{ + cl_platform_id platform; + clGetDeviceInfo(device, CL_DEVICE_PLATFORM, sizeof(platform), &platform, NULL); + return getPlatformVersion(platform); +} + +static cl_uint getContextPlatformVersion(cl_context context) +{ + // The platform cannot be queried directly, so we first have to grab a + // device and obtain its context + size_type size = 0; + clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &size); + if (size == 0) + return 0; + vector devices(size/sizeof(cl_device_id)); + clGetContextInfo(context, CL_CONTEXT_DEVICES, size, devices.data(), NULL); + return getDevicePlatformVersion(devices[0]); +} +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 120 + +template +class Wrapper +{ +public: + typedef T cl_type; + +protected: + cl_type object_; + +public: + Wrapper() : object_(NULL) { } + + Wrapper(const cl_type &obj, bool retainObject) : object_(obj) + { + if (retainObject) { + detail::errHandler(retain(), __RETAIN_ERR); + } + } + + ~Wrapper() + { + if (object_ != NULL) { release(); } + } + + Wrapper(const Wrapper& rhs) + { + object_ = rhs.object_; + detail::errHandler(retain(), __RETAIN_ERR); + } + + Wrapper(Wrapper&& rhs) CL_HPP_NOEXCEPT_ + { + object_ = rhs.object_; + rhs.object_ = NULL; + } + + Wrapper& operator = (const Wrapper& rhs) + { + if (this != &rhs) { + detail::errHandler(release(), __RELEASE_ERR); + object_ = rhs.object_; + detail::errHandler(retain(), __RETAIN_ERR); + } + return *this; + } + + Wrapper& operator = (Wrapper&& rhs) + { + if (this != &rhs) { + detail::errHandler(release(), __RELEASE_ERR); + object_ = rhs.object_; + rhs.object_ = NULL; + } + return *this; + } + + Wrapper& operator = (const cl_type &rhs) + { + detail::errHandler(release(), __RELEASE_ERR); + object_ = rhs; + return *this; + } + + const cl_type& operator ()() const { return object_; } + + cl_type& operator ()() { return object_; } + + const cl_type get() const { return object_; } + + cl_type get() { return object_; } + + +protected: + template + friend inline cl_int getInfoHelper(Func, cl_uint, U*, int, typename U::cl_type); + + cl_int retain() const + { + if (object_ != nullptr) { + return ReferenceHandler::retain(object_); + } + else { + return CL_SUCCESS; + } + } + + cl_int release() const + { + if (object_ != nullptr) { + return ReferenceHandler::release(object_); + } + else { + return CL_SUCCESS; + } + } +}; + +template <> +class Wrapper +{ +public: + typedef cl_device_id cl_type; + +protected: + cl_type object_; + bool referenceCountable_; + + static bool isReferenceCountable(cl_device_id device) + { + bool retVal = false; +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +#if CL_HPP_MINIMUM_OPENCL_VERSION < 120 + if (device != NULL) { + int version = getDevicePlatformVersion(device); + if(version > ((1 << 16) + 1)) { + retVal = true; + } + } +#else // CL_HPP_MINIMUM_OPENCL_VERSION < 120 + retVal = true; +#endif // CL_HPP_MINIMUM_OPENCL_VERSION < 120 +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + return retVal; + } + +public: + Wrapper() : object_(NULL), referenceCountable_(false) + { + } + + Wrapper(const cl_type &obj, bool retainObject) : + object_(obj), + referenceCountable_(false) + { + referenceCountable_ = isReferenceCountable(obj); + + if (retainObject) { + detail::errHandler(retain(), __RETAIN_ERR); + } + } + + ~Wrapper() + { + release(); + } + + Wrapper(const Wrapper& rhs) + { + object_ = rhs.object_; + referenceCountable_ = isReferenceCountable(object_); + detail::errHandler(retain(), __RETAIN_ERR); + } + + Wrapper(Wrapper&& rhs) CL_HPP_NOEXCEPT_ + { + object_ = rhs.object_; + referenceCountable_ = rhs.referenceCountable_; + rhs.object_ = NULL; + rhs.referenceCountable_ = false; + } + + Wrapper& operator = (const Wrapper& rhs) + { + if (this != &rhs) { + detail::errHandler(release(), __RELEASE_ERR); + object_ = rhs.object_; + referenceCountable_ = rhs.referenceCountable_; + detail::errHandler(retain(), __RETAIN_ERR); + } + return *this; + } + + Wrapper& operator = (Wrapper&& rhs) + { + if (this != &rhs) { + detail::errHandler(release(), __RELEASE_ERR); + object_ = rhs.object_; + referenceCountable_ = rhs.referenceCountable_; + rhs.object_ = NULL; + rhs.referenceCountable_ = false; + } + return *this; + } + + Wrapper& operator = (const cl_type &rhs) + { + detail::errHandler(release(), __RELEASE_ERR); + object_ = rhs; + referenceCountable_ = isReferenceCountable(object_); + return *this; + } + + const cl_type& operator ()() const { return object_; } + + cl_type& operator ()() { return object_; } + + const cl_type get() const { return object_; } + + cl_type get() { return object_; } + +protected: + template + friend inline cl_int getInfoHelper(Func, cl_uint, U*, int, typename U::cl_type); + + template + friend inline cl_int getInfoHelper(Func, cl_uint, vector*, int, typename U::cl_type); + + cl_int retain() const + { + if( object_ != nullptr && referenceCountable_ ) { + return ReferenceHandler::retain(object_); + } + else { + return CL_SUCCESS; + } + } + + cl_int release() const + { + if (object_ != nullptr && referenceCountable_) { + return ReferenceHandler::release(object_); + } + else { + return CL_SUCCESS; + } + } +}; + +template +inline bool operator==(const Wrapper &lhs, const Wrapper &rhs) +{ + return lhs() == rhs(); +} + +template +inline bool operator!=(const Wrapper &lhs, const Wrapper &rhs) +{ + return !operator==(lhs, rhs); +} + +} // namespace detail +//! \endcond + + +using BuildLogType = vector::param_type>>; +#if defined(CL_HPP_ENABLE_EXCEPTIONS) +/** +* Exception class for build errors to carry build info +*/ +class BuildError : public Error +{ +private: + BuildLogType buildLogs; +public: + BuildError(cl_int err, const char * errStr, const BuildLogType &vec) : Error(err, errStr), buildLogs(vec) + { + } + + BuildLogType getBuildLog() const + { + return buildLogs; + } +}; +namespace detail { + static inline cl_int buildErrHandler( + cl_int err, + const char * errStr, + const BuildLogType &buildLogs) + { + if (err != CL_SUCCESS) { + throw BuildError(err, errStr, buildLogs); + } + return err; + } +} // namespace detail + +#else +namespace detail { + static inline cl_int buildErrHandler( + cl_int err, + const char * errStr, + const BuildLogType &buildLogs) + { + (void)buildLogs; // suppress unused variable warning + (void)errStr; + return err; + } +} // namespace detail +#endif // #if defined(CL_HPP_ENABLE_EXCEPTIONS) + + +/*! \stuct ImageFormat + * \brief Adds constructors and member functions for cl_image_format. + * + * \see cl_image_format + */ +struct ImageFormat : public cl_image_format +{ + //! \brief Default constructor - performs no initialization. + ImageFormat(){} + + //! \brief Initializing constructor. + ImageFormat(cl_channel_order order, cl_channel_type type) + { + image_channel_order = order; + image_channel_data_type = type; + } + + //! \brief Assignment operator. + ImageFormat& operator = (const ImageFormat& rhs) + { + if (this != &rhs) { + this->image_channel_data_type = rhs.image_channel_data_type; + this->image_channel_order = rhs.image_channel_order; + } + return *this; + } +}; + +/*! \brief Class interface for cl_device_id. + * + * \note Copies of these objects are inexpensive, since they don't 'own' + * any underlying resources or data structures. + * + * \see cl_device_id + */ +class Device : public detail::Wrapper +{ +private: + static std::once_flag default_initialized_; + static Device default_; + static cl_int default_error_; + + /*! \brief Create the default context. + * + * This sets @c default_ and @c default_error_. It does not throw + * @c cl::Error. + */ + static void makeDefault(); + + /*! \brief Create the default platform from a provided platform. + * + * This sets @c default_. It does not throw + * @c cl::Error. + */ + static void makeDefaultProvided(const Device &p) { + default_ = p; + } + +public: +#ifdef CL_HPP_UNIT_TEST_ENABLE + /*! \brief Reset the default. + * + * This sets @c default_ to an empty value to support cleanup in + * the unit test framework. + * This function is not thread safe. + */ + static void unitTestClearDefault() { + default_ = Device(); + } +#endif // #ifdef CL_HPP_UNIT_TEST_ENABLE + + //! \brief Default constructor - initializes to NULL. + Device() : detail::Wrapper() { } + + /*! \brief Constructor from cl_device_id. + * + * This simply copies the device ID value, which is an inexpensive operation. + */ + explicit Device(const cl_device_id &device, bool retainObject = false) : + detail::Wrapper(device, retainObject) { } + + /*! \brief Returns the first device on the default context. + * + * \see Context::getDefault() + */ + static Device getDefault( + cl_int *errResult = NULL) + { + std::call_once(default_initialized_, makeDefault); + detail::errHandler(default_error_); + if (errResult != NULL) { + *errResult = default_error_; + } + return default_; + } + + /** + * Modify the default device to be used by + * subsequent operations. + * Will only set the default if no default was previously created. + * @return updated default device. + * Should be compared to the passed value to ensure that it was updated. + */ + static Device setDefault(const Device &default_device) + { + std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_device)); + detail::errHandler(default_error_); + return default_; + } + + /*! \brief Assignment operator from cl_device_id. + * + * This simply copies the device ID value, which is an inexpensive operation. + */ + Device& operator = (const cl_device_id& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Device(const Device& dev) : detail::Wrapper(dev) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Device& operator = (const Device &dev) + { + detail::Wrapper::operator=(dev); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Device(Device&& dev) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(dev)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Device& operator = (Device &&dev) + { + detail::Wrapper::operator=(std::move(dev)); + return *this; + } + + //! \brief Wrapper for clGetDeviceInfo(). + template + cl_int getInfo(cl_device_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetDeviceInfo, object_, name, param), + __GET_DEVICE_INFO_ERR); + } + + //! \brief Wrapper for clGetDeviceInfo() that returns by value. + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_device_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + /** + * CL 1.2 version + */ +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + //! \brief Wrapper for clCreateSubDevices(). + cl_int createSubDevices( + const cl_device_partition_property * properties, + vector* devices) + { + cl_uint n = 0; + cl_int err = clCreateSubDevices(object_, properties, 0, NULL, &n); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR); + } + + vector ids(n); + err = clCreateSubDevices(object_, properties, n, ids.data(), NULL); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR); + } + + // Cannot trivially assign because we need to capture intermediates + // with safe construction + if (devices) { + devices->resize(ids.size()); + + // Assign to param, constructing with retain behaviour + // to correctly capture each underlying CL object + for (size_type i = 0; i < ids.size(); i++) { + // We do not need to retain because this device is being created + // by the runtime + (*devices)[i] = Device(ids[i], false); + } + } + + return CL_SUCCESS; + } +#elif defined(CL_HPP_USE_CL_DEVICE_FISSION) + +/** + * CL 1.1 version that uses device fission extension. + */ + cl_int createSubDevices( + const cl_device_partition_property_ext * properties, + vector* devices) + { + typedef CL_API_ENTRY cl_int + ( CL_API_CALL * PFN_clCreateSubDevicesEXT)( + cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + static PFN_clCreateSubDevicesEXT pfn_clCreateSubDevicesEXT = NULL; + CL_HPP_INIT_CL_EXT_FCN_PTR_(clCreateSubDevicesEXT); + + cl_uint n = 0; + cl_int err = pfn_clCreateSubDevicesEXT(object_, properties, 0, NULL, &n); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR); + } + + vector ids(n); + err = pfn_clCreateSubDevicesEXT(object_, properties, n, ids.data(), NULL); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR); + } + // Cannot trivially assign because we need to capture intermediates + // with safe construction + if (devices) { + devices->resize(ids.size()); + + // Assign to param, constructing with retain behaviour + // to correctly capture each underlying CL object + for (size_type i = 0; i < ids.size(); i++) { + // We do not need to retain because this device is being created + // by the runtime + (*devices)[i] = Device(ids[i], false); + } + } + return CL_SUCCESS; + } +#endif // defined(CL_HPP_USE_CL_DEVICE_FISSION) +}; + +CL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag Device::default_initialized_; +CL_HPP_DEFINE_STATIC_MEMBER_ Device Device::default_; +CL_HPP_DEFINE_STATIC_MEMBER_ cl_int Device::default_error_ = CL_SUCCESS; + +/*! \brief Class interface for cl_platform_id. + * + * \note Copies of these objects are inexpensive, since they don't 'own' + * any underlying resources or data structures. + * + * \see cl_platform_id + */ +class Platform : public detail::Wrapper +{ +private: + static std::once_flag default_initialized_; + static Platform default_; + static cl_int default_error_; + + /*! \brief Create the default context. + * + * This sets @c default_ and @c default_error_. It does not throw + * @c cl::Error. + */ + static void makeDefault() { + /* Throwing an exception from a call_once invocation does not do + * what we wish, so we catch it and save the error. + */ +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + try +#endif + { + // If default wasn't passed ,generate one + // Otherwise set it + cl_uint n = 0; + + cl_int err = ::clGetPlatformIDs(0, NULL, &n); + if (err != CL_SUCCESS) { + default_error_ = err; + return; + } + if (n == 0) { + default_error_ = CL_INVALID_PLATFORM; + return; + } + + vector ids(n); + err = ::clGetPlatformIDs(n, ids.data(), NULL); + if (err != CL_SUCCESS) { + default_error_ = err; + return; + } + + default_ = Platform(ids[0]); + } +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + catch (cl::Error &e) { + default_error_ = e.err(); + } +#endif + } + + /*! \brief Create the default platform from a provided platform. + * + * This sets @c default_. It does not throw + * @c cl::Error. + */ + static void makeDefaultProvided(const Platform &p) { + default_ = p; + } + +public: +#ifdef CL_HPP_UNIT_TEST_ENABLE + /*! \brief Reset the default. + * + * This sets @c default_ to an empty value to support cleanup in + * the unit test framework. + * This function is not thread safe. + */ + static void unitTestClearDefault() { + default_ = Platform(); + } +#endif // #ifdef CL_HPP_UNIT_TEST_ENABLE + + //! \brief Default constructor - initializes to NULL. + Platform() : detail::Wrapper() { } + + /*! \brief Constructor from cl_platform_id. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * This simply copies the platform ID value, which is an inexpensive operation. + */ + explicit Platform(const cl_platform_id &platform, bool retainObject = false) : + detail::Wrapper(platform, retainObject) { } + + /*! \brief Assignment operator from cl_platform_id. + * + * This simply copies the platform ID value, which is an inexpensive operation. + */ + Platform& operator = (const cl_platform_id& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + static Platform getDefault( + cl_int *errResult = NULL) + { + std::call_once(default_initialized_, makeDefault); + detail::errHandler(default_error_); + if (errResult != NULL) { + *errResult = default_error_; + } + return default_; + } + + /** + * Modify the default platform to be used by + * subsequent operations. + * Will only set the default if no default was previously created. + * @return updated default platform. + * Should be compared to the passed value to ensure that it was updated. + */ + static Platform setDefault(const Platform &default_platform) + { + std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_platform)); + detail::errHandler(default_error_); + return default_; + } + + //! \brief Wrapper for clGetPlatformInfo(). + cl_int getInfo(cl_platform_info name, string* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetPlatformInfo, object_, name, param), + __GET_PLATFORM_INFO_ERR); + } + + //! \brief Wrapper for clGetPlatformInfo() that returns by value. + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_platform_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + /*! \brief Gets a list of devices for this platform. + * + * Wraps clGetDeviceIDs(). + */ + cl_int getDevices( + cl_device_type type, + vector* devices) const + { + cl_uint n = 0; + if( devices == NULL ) { + return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_DEVICE_IDS_ERR); + } + cl_int err = ::clGetDeviceIDs(object_, type, 0, NULL, &n); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_DEVICE_IDS_ERR); + } + + vector ids(n); + err = ::clGetDeviceIDs(object_, type, n, ids.data(), NULL); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_DEVICE_IDS_ERR); + } + + // Cannot trivially assign because we need to capture intermediates + // with safe construction + // We must retain things we obtain from the API to avoid releasing + // API-owned objects. + if (devices) { + devices->resize(ids.size()); + + // Assign to param, constructing with retain behaviour + // to correctly capture each underlying CL object + for (size_type i = 0; i < ids.size(); i++) { + (*devices)[i] = Device(ids[i], true); + } + } + return CL_SUCCESS; + } + +#if defined(CL_HPP_USE_DX_INTEROP) + /*! \brief Get the list of available D3D10 devices. + * + * \param d3d_device_source. + * + * \param d3d_object. + * + * \param d3d_device_set. + * + * \param devices returns a vector of OpenCL D3D10 devices found. The cl::Device + * values returned in devices can be used to identify a specific OpenCL + * device. If \a devices argument is NULL, this argument is ignored. + * + * \return One of the following values: + * - CL_SUCCESS if the function is executed successfully. + * + * The application can query specific capabilities of the OpenCL device(s) + * returned by cl::getDevices. This can be used by the application to + * determine which device(s) to use. + * + * \note In the case that exceptions are enabled and a return value + * other than CL_SUCCESS is generated, then cl::Error exception is + * generated. + */ + cl_int getDevices( + cl_d3d10_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d10_device_set_khr d3d_device_set, + vector* devices) const + { + typedef CL_API_ENTRY cl_int (CL_API_CALL *PFN_clGetDeviceIDsFromD3D10KHR)( + cl_platform_id platform, + cl_d3d10_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d10_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint* num_devices); + + if( devices == NULL ) { + return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_DEVICE_IDS_ERR); + } + + static PFN_clGetDeviceIDsFromD3D10KHR pfn_clGetDeviceIDsFromD3D10KHR = NULL; + CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(object_, clGetDeviceIDsFromD3D10KHR); + + cl_uint n = 0; + cl_int err = pfn_clGetDeviceIDsFromD3D10KHR( + object_, + d3d_device_source, + d3d_object, + d3d_device_set, + 0, + NULL, + &n); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_DEVICE_IDS_ERR); + } + + vector ids(n); + err = pfn_clGetDeviceIDsFromD3D10KHR( + object_, + d3d_device_source, + d3d_object, + d3d_device_set, + n, + ids.data(), + NULL); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_DEVICE_IDS_ERR); + } + + // Cannot trivially assign because we need to capture intermediates + // with safe construction + // We must retain things we obtain from the API to avoid releasing + // API-owned objects. + if (devices) { + devices->resize(ids.size()); + + // Assign to param, constructing with retain behaviour + // to correctly capture each underlying CL object + for (size_type i = 0; i < ids.size(); i++) { + (*devices)[i] = Device(ids[i], true); + } + } + return CL_SUCCESS; + } +#endif + + /*! \brief Gets a list of available platforms. + * + * Wraps clGetPlatformIDs(). + */ + static cl_int get( + vector* platforms) + { + cl_uint n = 0; + + if( platforms == NULL ) { + return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_PLATFORM_IDS_ERR); + } + + cl_int err = ::clGetPlatformIDs(0, NULL, &n); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_PLATFORM_IDS_ERR); + } + + vector ids(n); + err = ::clGetPlatformIDs(n, ids.data(), NULL); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_PLATFORM_IDS_ERR); + } + + if (platforms) { + platforms->resize(ids.size()); + + // Platforms don't reference count + for (size_type i = 0; i < ids.size(); i++) { + (*platforms)[i] = Platform(ids[i]); + } + } + return CL_SUCCESS; + } + + /*! \brief Gets the first available platform. + * + * Wraps clGetPlatformIDs(), returning the first result. + */ + static cl_int get( + Platform * platform) + { + cl_int err; + Platform default_platform = Platform::getDefault(&err); + if (platform) { + *platform = default_platform; + } + return err; + } + + /*! \brief Gets the first available platform, returning it by value. + * + * \return Returns a valid platform if one is available. + * If no platform is available will return a null platform. + * Throws an exception if no platforms are available + * or an error condition occurs. + * Wraps clGetPlatformIDs(), returning the first result. + */ + static Platform get( + cl_int * errResult = NULL) + { + cl_int err; + Platform default_platform = Platform::getDefault(&err); + if (errResult) { + *errResult = err; + } + return default_platform; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + //! \brief Wrapper for clUnloadCompiler(). + cl_int + unloadCompiler() + { + return ::clUnloadPlatformCompiler(object_); + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +}; // class Platform + +CL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag Platform::default_initialized_; +CL_HPP_DEFINE_STATIC_MEMBER_ Platform Platform::default_; +CL_HPP_DEFINE_STATIC_MEMBER_ cl_int Platform::default_error_ = CL_SUCCESS; + + +/** + * Deprecated APIs for 1.2 + */ +#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) +/** + * Unload the OpenCL compiler. + * \note Deprecated for OpenCL 1.2. Use Platform::unloadCompiler instead. + */ +inline CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int +UnloadCompiler() CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; +inline cl_int +UnloadCompiler() +{ + return ::clUnloadCompiler(); +} +#endif // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) + +/*! \brief Class interface for cl_context. + * + * \note Copies of these objects are shallow, meaning that the copy will refer + * to the same underlying cl_context as the original. For details, see + * clRetainContext() and clReleaseContext(). + * + * \see cl_context + */ +class Context + : public detail::Wrapper +{ +private: + static std::once_flag default_initialized_; + static Context default_; + static cl_int default_error_; + + /*! \brief Create the default context from the default device type in the default platform. + * + * This sets @c default_ and @c default_error_. It does not throw + * @c cl::Error. + */ + static void makeDefault() { + /* Throwing an exception from a call_once invocation does not do + * what we wish, so we catch it and save the error. + */ +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + try +#endif + { +#if !defined(__APPLE__) && !defined(__MACOS) + const Platform &p = Platform::getDefault(); + cl_platform_id defaultPlatform = p(); + cl_context_properties properties[3] = { + CL_CONTEXT_PLATFORM, (cl_context_properties)defaultPlatform, 0 + }; +#else // #if !defined(__APPLE__) && !defined(__MACOS) + cl_context_properties *properties = nullptr; +#endif // #if !defined(__APPLE__) && !defined(__MACOS) + + default_ = Context( + CL_DEVICE_TYPE_DEFAULT, + properties, + NULL, + NULL, + &default_error_); + } +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + catch (cl::Error &e) { + default_error_ = e.err(); + } +#endif + } + + + /*! \brief Create the default context from a provided Context. + * + * This sets @c default_. It does not throw + * @c cl::Error. + */ + static void makeDefaultProvided(const Context &c) { + default_ = c; + } + +public: +#ifdef CL_HPP_UNIT_TEST_ENABLE + /*! \brief Reset the default. + * + * This sets @c default_ to an empty value to support cleanup in + * the unit test framework. + * This function is not thread safe. + */ + static void unitTestClearDefault() { + default_ = Context(); + } +#endif // #ifdef CL_HPP_UNIT_TEST_ENABLE + + /*! \brief Constructs a context including a list of specified devices. + * + * Wraps clCreateContext(). + */ + Context( + const vector& devices, + cl_context_properties* properties = NULL, + void (CL_CALLBACK * notifyFptr)( + const char *, + const void *, + size_type, + void *) = NULL, + void* data = NULL, + cl_int* err = NULL) + { + cl_int error; + + size_type numDevices = devices.size(); + vector deviceIDs(numDevices); + + for( size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) { + deviceIDs[deviceIndex] = (devices[deviceIndex])(); + } + + object_ = ::clCreateContext( + properties, (cl_uint) numDevices, + deviceIDs.data(), + notifyFptr, data, &error); + + detail::errHandler(error, __CREATE_CONTEXT_ERR); + if (err != NULL) { + *err = error; + } + } + + Context( + const Device& device, + cl_context_properties* properties = NULL, + void (CL_CALLBACK * notifyFptr)( + const char *, + const void *, + size_type, + void *) = NULL, + void* data = NULL, + cl_int* err = NULL) + { + cl_int error; + + cl_device_id deviceID = device(); + + object_ = ::clCreateContext( + properties, 1, + &deviceID, + notifyFptr, data, &error); + + detail::errHandler(error, __CREATE_CONTEXT_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! \brief Constructs a context including all or a subset of devices of a specified type. + * + * Wraps clCreateContextFromType(). + */ + Context( + cl_device_type type, + cl_context_properties* properties = NULL, + void (CL_CALLBACK * notifyFptr)( + const char *, + const void *, + size_type, + void *) = NULL, + void* data = NULL, + cl_int* err = NULL) + { + cl_int error; + +#if !defined(__APPLE__) && !defined(__MACOS) + cl_context_properties prop[4] = {CL_CONTEXT_PLATFORM, 0, 0, 0 }; + + if (properties == NULL) { + // Get a valid platform ID as we cannot send in a blank one + vector platforms; + error = Platform::get(&platforms); + if (error != CL_SUCCESS) { + detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR); + if (err != NULL) { + *err = error; + } + return; + } + + // Check the platforms we found for a device of our specified type + cl_context_properties platform_id = 0; + for (unsigned int i = 0; i < platforms.size(); i++) { + + vector devices; + +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + try { +#endif + + error = platforms[i].getDevices(type, &devices); + +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + } catch (Error) {} + // Catch if exceptions are enabled as we don't want to exit if first platform has no devices of type + // We do error checking next anyway, and can throw there if needed +#endif + + // Only squash CL_SUCCESS and CL_DEVICE_NOT_FOUND + if (error != CL_SUCCESS && error != CL_DEVICE_NOT_FOUND) { + detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR); + if (err != NULL) { + *err = error; + } + } + + if (devices.size() > 0) { + platform_id = (cl_context_properties)platforms[i](); + break; + } + } + + if (platform_id == 0) { + detail::errHandler(CL_DEVICE_NOT_FOUND, __CREATE_CONTEXT_FROM_TYPE_ERR); + if (err != NULL) { + *err = CL_DEVICE_NOT_FOUND; + } + return; + } + + prop[1] = platform_id; + properties = &prop[0]; + } +#endif + object_ = ::clCreateContextFromType( + properties, type, notifyFptr, data, &error); + + detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Context(const Context& ctx) : detail::Wrapper(ctx) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Context& operator = (const Context &ctx) + { + detail::Wrapper::operator=(ctx); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Context(Context&& ctx) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(ctx)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Context& operator = (Context &&ctx) + { + detail::Wrapper::operator=(std::move(ctx)); + return *this; + } + + + /*! \brief Returns a singleton context including all devices of CL_DEVICE_TYPE_DEFAULT. + * + * \note All calls to this function return the same cl_context as the first. + */ + static Context getDefault(cl_int * err = NULL) + { + std::call_once(default_initialized_, makeDefault); + detail::errHandler(default_error_); + if (err != NULL) { + *err = default_error_; + } + return default_; + } + + /** + * Modify the default context to be used by + * subsequent operations. + * Will only set the default if no default was previously created. + * @return updated default context. + * Should be compared to the passed value to ensure that it was updated. + */ + static Context setDefault(const Context &default_context) + { + std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_context)); + detail::errHandler(default_error_); + return default_; + } + + //! \brief Default constructor - initializes to NULL. + Context() : detail::Wrapper() { } + + /*! \brief Constructor from cl_context - takes ownership. + * + * This effectively transfers ownership of a refcount on the cl_context + * into the new Context object. + */ + explicit Context(const cl_context& context, bool retainObject = false) : + detail::Wrapper(context, retainObject) { } + + /*! \brief Assignment operator from cl_context - takes ownership. + * + * This effectively transfers ownership of a refcount on the rhs and calls + * clReleaseContext() on the value previously held by this instance. + */ + Context& operator = (const cl_context& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + //! \brief Wrapper for clGetContextInfo(). + template + cl_int getInfo(cl_context_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetContextInfo, object_, name, param), + __GET_CONTEXT_INFO_ERR); + } + + //! \brief Wrapper for clGetContextInfo() that returns by value. + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_context_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + /*! \brief Gets a list of supported image formats. + * + * Wraps clGetSupportedImageFormats(). + */ + cl_int getSupportedImageFormats( + cl_mem_flags flags, + cl_mem_object_type type, + vector* formats) const + { + cl_uint numEntries; + + if (!formats) { + return CL_SUCCESS; + } + + cl_int err = ::clGetSupportedImageFormats( + object_, + flags, + type, + 0, + NULL, + &numEntries); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_SUPPORTED_IMAGE_FORMATS_ERR); + } + + if (numEntries > 0) { + vector value(numEntries); + err = ::clGetSupportedImageFormats( + object_, + flags, + type, + numEntries, + (cl_image_format*)value.data(), + NULL); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __GET_SUPPORTED_IMAGE_FORMATS_ERR); + } + + formats->assign(begin(value), end(value)); + } + else { + // If no values are being returned, ensure an empty vector comes back + formats->clear(); + } + + return CL_SUCCESS; + } +}; + +inline void Device::makeDefault() +{ + /* Throwing an exception from a call_once invocation does not do + * what we wish, so we catch it and save the error. + */ +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + try +#endif + { + cl_int error = 0; + + Context context = Context::getDefault(&error); + detail::errHandler(error, __CREATE_CONTEXT_ERR); + + if (error != CL_SUCCESS) { + default_error_ = error; + } + else { + default_ = context.getInfo()[0]; + default_error_ = CL_SUCCESS; + } + } +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + catch (cl::Error &e) { + default_error_ = e.err(); + } +#endif +} + +CL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag Context::default_initialized_; +CL_HPP_DEFINE_STATIC_MEMBER_ Context Context::default_; +CL_HPP_DEFINE_STATIC_MEMBER_ cl_int Context::default_error_ = CL_SUCCESS; + +/*! \brief Class interface for cl_event. + * + * \note Copies of these objects are shallow, meaning that the copy will refer + * to the same underlying cl_event as the original. For details, see + * clRetainEvent() and clReleaseEvent(). + * + * \see cl_event + */ +class Event : public detail::Wrapper +{ +public: + //! \brief Default constructor - initializes to NULL. + Event() : detail::Wrapper() { } + + /*! \brief Constructor from cl_event - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * This effectively transfers ownership of a refcount on the cl_event + * into the new Event object. + */ + explicit Event(const cl_event& event, bool retainObject = false) : + detail::Wrapper(event, retainObject) { } + + /*! \brief Assignment operator from cl_event - takes ownership. + * + * This effectively transfers ownership of a refcount on the rhs and calls + * clReleaseEvent() on the value previously held by this instance. + */ + Event& operator = (const cl_event& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + //! \brief Wrapper for clGetEventInfo(). + template + cl_int getInfo(cl_event_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetEventInfo, object_, name, param), + __GET_EVENT_INFO_ERR); + } + + //! \brief Wrapper for clGetEventInfo() that returns by value. + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_event_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + //! \brief Wrapper for clGetEventProfilingInfo(). + template + cl_int getProfilingInfo(cl_profiling_info name, T* param) const + { + return detail::errHandler(detail::getInfo( + &::clGetEventProfilingInfo, object_, name, param), + __GET_EVENT_PROFILE_INFO_ERR); + } + + //! \brief Wrapper for clGetEventProfilingInfo() that returns by value. + template typename + detail::param_traits::param_type + getProfilingInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_profiling_info, name>::param_type param; + cl_int result = getProfilingInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + /*! \brief Blocks the calling thread until this event completes. + * + * Wraps clWaitForEvents(). + */ + cl_int wait() const + { + return detail::errHandler( + ::clWaitForEvents(1, &object_), + __WAIT_FOR_EVENTS_ERR); + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 + /*! \brief Registers a user callback function for a specific command execution status. + * + * Wraps clSetEventCallback(). + */ + cl_int setCallback( + cl_int type, + void (CL_CALLBACK * pfn_notify)(cl_event, cl_int, void *), + void * user_data = NULL) + { + return detail::errHandler( + ::clSetEventCallback( + object_, + type, + pfn_notify, + user_data), + __SET_EVENT_CALLBACK_ERR); + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 + + /*! \brief Blocks the calling thread until every event specified is complete. + * + * Wraps clWaitForEvents(). + */ + static cl_int + waitForEvents(const vector& events) + { + return detail::errHandler( + ::clWaitForEvents( + (cl_uint) events.size(), (events.size() > 0) ? (cl_event*)&events.front() : NULL), + __WAIT_FOR_EVENTS_ERR); + } +}; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 +/*! \brief Class interface for user events (a subset of cl_event's). + * + * See Event for details about copy semantics, etc. + */ +class UserEvent : public Event +{ +public: + /*! \brief Constructs a user event on a given context. + * + * Wraps clCreateUserEvent(). + */ + UserEvent( + const Context& context, + cl_int * err = NULL) + { + cl_int error; + object_ = ::clCreateUserEvent( + context(), + &error); + + detail::errHandler(error, __CREATE_USER_EVENT_ERR); + if (err != NULL) { + *err = error; + } + } + + //! \brief Default constructor - initializes to NULL. + UserEvent() : Event() { } + + /*! \brief Sets the execution status of a user event object. + * + * Wraps clSetUserEventStatus(). + */ + cl_int setStatus(cl_int status) + { + return detail::errHandler( + ::clSetUserEventStatus(object_,status), + __SET_USER_EVENT_STATUS_ERR); + } +}; +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 + +/*! \brief Blocks the calling thread until every event specified is complete. + * + * Wraps clWaitForEvents(). + */ +inline static cl_int +WaitForEvents(const vector& events) +{ + return detail::errHandler( + ::clWaitForEvents( + (cl_uint) events.size(), (events.size() > 0) ? (cl_event*)&events.front() : NULL), + __WAIT_FOR_EVENTS_ERR); +} + +/*! \brief Class interface for cl_mem. + * + * \note Copies of these objects are shallow, meaning that the copy will refer + * to the same underlying cl_mem as the original. For details, see + * clRetainMemObject() and clReleaseMemObject(). + * + * \see cl_mem + */ +class Memory : public detail::Wrapper +{ +public: + //! \brief Default constructor - initializes to NULL. + Memory() : detail::Wrapper() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * Optionally transfer ownership of a refcount on the cl_mem + * into the new Memory object. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * + * See Memory for further details. + */ + explicit Memory(const cl_mem& memory, bool retainObject) : + detail::Wrapper(memory, retainObject) { } + + /*! \brief Assignment operator from cl_mem - takes ownership. + * + * This effectively transfers ownership of a refcount on the rhs and calls + * clReleaseMemObject() on the value previously held by this instance. + */ + Memory& operator = (const cl_mem& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Memory(const Memory& mem) : detail::Wrapper(mem) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Memory& operator = (const Memory &mem) + { + detail::Wrapper::operator=(mem); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Memory(Memory&& mem) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(mem)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Memory& operator = (Memory &&mem) + { + detail::Wrapper::operator=(std::move(mem)); + return *this; + } + + + //! \brief Wrapper for clGetMemObjectInfo(). + template + cl_int getInfo(cl_mem_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetMemObjectInfo, object_, name, param), + __GET_MEM_OBJECT_INFO_ERR); + } + + //! \brief Wrapper for clGetMemObjectInfo() that returns by value. + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_mem_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 + /*! \brief Registers a callback function to be called when the memory object + * is no longer needed. + * + * Wraps clSetMemObjectDestructorCallback(). + * + * Repeated calls to this function, for a given cl_mem value, will append + * to the list of functions called (in reverse order) when memory object's + * resources are freed and the memory object is deleted. + * + * \note + * The registered callbacks are associated with the underlying cl_mem + * value - not the Memory class instance. + */ + cl_int setDestructorCallback( + void (CL_CALLBACK * pfn_notify)(cl_mem, void *), + void * user_data = NULL) + { + return detail::errHandler( + ::clSetMemObjectDestructorCallback( + object_, + pfn_notify, + user_data), + __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR); + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 + +}; + +// Pre-declare copy functions +class Buffer; +template< typename IteratorType > +cl_int copy( IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer ); +template< typename IteratorType > +cl_int copy( const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator ); +template< typename IteratorType > +cl_int copy( const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer ); +template< typename IteratorType > +cl_int copy( const CommandQueue &queue, const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator ); + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +namespace detail +{ + class SVMTraitNull + { + public: + static cl_svm_mem_flags getSVMMemFlags() + { + return 0; + } + }; +} // namespace detail + +template +class SVMTraitReadWrite +{ +public: + static cl_svm_mem_flags getSVMMemFlags() + { + return CL_MEM_READ_WRITE | + Trait::getSVMMemFlags(); + } +}; + +template +class SVMTraitReadOnly +{ +public: + static cl_svm_mem_flags getSVMMemFlags() + { + return CL_MEM_READ_ONLY | + Trait::getSVMMemFlags(); + } +}; + +template +class SVMTraitWriteOnly +{ +public: + static cl_svm_mem_flags getSVMMemFlags() + { + return CL_MEM_WRITE_ONLY | + Trait::getSVMMemFlags(); + } +}; + +template> +class SVMTraitCoarse +{ +public: + static cl_svm_mem_flags getSVMMemFlags() + { + return Trait::getSVMMemFlags(); + } +}; + +template> +class SVMTraitFine +{ +public: + static cl_svm_mem_flags getSVMMemFlags() + { + return CL_MEM_SVM_FINE_GRAIN_BUFFER | + Trait::getSVMMemFlags(); + } +}; + +template> +class SVMTraitAtomic +{ +public: + static cl_svm_mem_flags getSVMMemFlags() + { + return + CL_MEM_SVM_FINE_GRAIN_BUFFER | + CL_MEM_SVM_ATOMICS | + Trait::getSVMMemFlags(); + } +}; + +// Pre-declare SVM map function +template +inline cl_int enqueueMapSVM( + T* ptr, + cl_bool blocking, + cl_map_flags flags, + size_type size, + const vector* events = NULL, + Event* event = NULL); + +/** + * STL-like allocator class for managing SVM objects provided for convenience. + * + * Note that while this behaves like an allocator for the purposes of constructing vectors and similar objects, + * care must be taken when using with smart pointers. + * The allocator should not be used to construct a unique_ptr if we are using coarse-grained SVM mode because + * the coarse-grained management behaviour would behave incorrectly with respect to reference counting. + * + * Instead the allocator embeds a Deleter which may be used with unique_ptr and is used + * with the allocate_shared and allocate_ptr supplied operations. + */ +template +class SVMAllocator { +private: + Context context_; + +public: + typedef T value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + template + struct rebind + { + typedef SVMAllocator other; + }; + + template + friend class SVMAllocator; + + SVMAllocator() : + context_(Context::getDefault()) + { + } + + explicit SVMAllocator(cl::Context context) : + context_(context) + { + } + + + SVMAllocator(const SVMAllocator &other) : + context_(other.context_) + { + } + + template + SVMAllocator(const SVMAllocator &other) : + context_(other.context_) + { + } + + ~SVMAllocator() + { + } + + pointer address(reference r) CL_HPP_NOEXCEPT_ + { + return std::addressof(r); + } + + const_pointer address(const_reference r) CL_HPP_NOEXCEPT_ + { + return std::addressof(r); + } + + /** + * Allocate an SVM pointer. + * + * If the allocator is coarse-grained, this will take ownership to allow + * containers to correctly construct data in place. + */ + pointer allocate( + size_type size, + typename cl::SVMAllocator::const_pointer = 0) + { + // Allocate memory with default alignment matching the size of the type + void* voidPointer = + clSVMAlloc( + context_(), + SVMTrait::getSVMMemFlags(), + size*sizeof(T), + 0); + pointer retValue = reinterpret_cast( + voidPointer); +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + if (!retValue) { + std::bad_alloc excep; + throw excep; + } +#endif // #if defined(CL_HPP_ENABLE_EXCEPTIONS) + + // If allocation was coarse-grained then map it + if (!(SVMTrait::getSVMMemFlags() & CL_MEM_SVM_FINE_GRAIN_BUFFER)) { + cl_int err = enqueueMapSVM(retValue, CL_TRUE, CL_MAP_READ | CL_MAP_WRITE, size*sizeof(T)); + if (err != CL_SUCCESS) { + std::bad_alloc excep; + throw excep; + } + } + + // If exceptions disabled, return null pointer from allocator + return retValue; + } + + void deallocate(pointer p, size_type) + { + clSVMFree(context_(), p); + } + + /** + * Return the maximum possible allocation size. + * This is the minimum of the maximum sizes of all devices in the context. + */ + size_type max_size() const CL_HPP_NOEXCEPT_ + { + size_type maxSize = std::numeric_limits::max() / sizeof(T); + + for (Device &d : context_.getInfo()) { + maxSize = std::min( + maxSize, + static_cast(d.getInfo())); + } + + return maxSize; + } + + template< class U, class... Args > + void construct(U* p, Args&&... args) + { + new(p)T(args...); + } + + template< class U > + void destroy(U* p) + { + p->~U(); + } + + /** + * Returns true if the contexts match. + */ + inline bool operator==(SVMAllocator const& rhs) + { + return (context_==rhs.context_); + } + + inline bool operator!=(SVMAllocator const& a) + { + return !operator==(a); + } +}; // class SVMAllocator return cl::pointer(tmp, detail::Deleter{alloc, copies}); + + +template +class SVMAllocator { +public: + typedef void value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + + template + struct rebind + { + typedef SVMAllocator other; + }; + + template + friend class SVMAllocator; +}; + +#if !defined(CL_HPP_NO_STD_UNIQUE_PTR) +namespace detail +{ + template + class Deleter { + private: + Alloc alloc_; + size_type copies_; + + public: + typedef typename std::allocator_traits::pointer pointer; + + Deleter(const Alloc &alloc, size_type copies) : alloc_{ alloc }, copies_{ copies } + { + } + + void operator()(pointer ptr) const { + Alloc tmpAlloc{ alloc_ }; + std::allocator_traits::destroy(tmpAlloc, std::addressof(*ptr)); + std::allocator_traits::deallocate(tmpAlloc, ptr, copies_); + } + }; +} // namespace detail + +/** + * Allocation operation compatible with std::allocate_ptr. + * Creates a unique_ptr by default. + * This requirement is to ensure that the control block is not + * allocated in memory inaccessible to the host. + */ +template +cl::pointer> allocate_pointer(const Alloc &alloc_, Args&&... args) +{ + Alloc alloc(alloc_); + static const size_type copies = 1; + + // Ensure that creation of the management block and the + // object are dealt with separately such that we only provide a deleter + + T* tmp = std::allocator_traits::allocate(alloc, copies); + if (!tmp) { + std::bad_alloc excep; + throw excep; + } + try { + std::allocator_traits::construct( + alloc, + std::addressof(*tmp), + std::forward(args)...); + + return cl::pointer>(tmp, detail::Deleter{alloc, copies}); + } + catch (std::bad_alloc b) + { + std::allocator_traits::deallocate(alloc, tmp, copies); + throw; + } +} + +template< class T, class SVMTrait, class... Args > +cl::pointer>> allocate_svm(Args... args) +{ + SVMAllocator alloc; + return cl::allocate_pointer(alloc, args...); +} + +template< class T, class SVMTrait, class... Args > +cl::pointer>> allocate_svm(const cl::Context &c, Args... args) +{ + SVMAllocator alloc(c); + return cl::allocate_pointer(alloc, args...); +} +#endif // #if !defined(CL_HPP_NO_STD_UNIQUE_PTR) + +/*! \brief Vector alias to simplify contruction of coarse-grained SVM containers. + * + */ +template < class T > +using coarse_svm_vector = vector>>; + +/*! \brief Vector alias to simplify contruction of fine-grained SVM containers. +* +*/ +template < class T > +using fine_svm_vector = vector>>; + +/*! \brief Vector alias to simplify contruction of fine-grained SVM containers that support platform atomics. +* +*/ +template < class T > +using atomic_svm_vector = vector>>; + +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + + +/*! \brief Class interface for Buffer Memory Objects. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class Buffer : public Memory +{ +public: + + /*! \brief Constructs a Buffer in a specified context. + * + * Wraps clCreateBuffer(). + * + * \param host_ptr Storage to be used if the CL_MEM_USE_HOST_PTR flag was + * specified. Note alignment & exclusivity requirements. + */ + Buffer( + const Context& context, + cl_mem_flags flags, + size_type size, + void* host_ptr = NULL, + cl_int* err = NULL) + { + cl_int error; + object_ = ::clCreateBuffer(context(), flags, size, host_ptr, &error); + + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! \brief Constructs a Buffer in the default context. + * + * Wraps clCreateBuffer(). + * + * \param host_ptr Storage to be used if the CL_MEM_USE_HOST_PTR flag was + * specified. Note alignment & exclusivity requirements. + * + * \see Context::getDefault() + */ + Buffer( + cl_mem_flags flags, + size_type size, + void* host_ptr = NULL, + cl_int* err = NULL) + { + cl_int error; + + Context context = Context::getDefault(err); + + object_ = ::clCreateBuffer(context(), flags, size, host_ptr, &error); + + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! + * \brief Construct a Buffer from a host container via iterators. + * IteratorType must be random access. + * If useHostPtr is specified iterators must represent contiguous data. + */ + template< typename IteratorType > + Buffer( + IteratorType startIterator, + IteratorType endIterator, + bool readOnly, + bool useHostPtr = false, + cl_int* err = NULL) + { + typedef typename std::iterator_traits::value_type DataType; + cl_int error; + + cl_mem_flags flags = 0; + if( readOnly ) { + flags |= CL_MEM_READ_ONLY; + } + else { + flags |= CL_MEM_READ_WRITE; + } + if( useHostPtr ) { + flags |= CL_MEM_USE_HOST_PTR; + } + + size_type size = sizeof(DataType)*(endIterator - startIterator); + + Context context = Context::getDefault(err); + + if( useHostPtr ) { + object_ = ::clCreateBuffer(context(), flags, size, static_cast(&*startIterator), &error); + } else { + object_ = ::clCreateBuffer(context(), flags, size, 0, &error); + } + + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + + if( !useHostPtr ) { + error = cl::copy(startIterator, endIterator, *this); + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } + } + + /*! + * \brief Construct a Buffer from a host container via iterators using a specified context. + * IteratorType must be random access. + * If useHostPtr is specified iterators must represent contiguous data. + */ + template< typename IteratorType > + Buffer(const Context &context, IteratorType startIterator, IteratorType endIterator, + bool readOnly, bool useHostPtr = false, cl_int* err = NULL); + + /*! + * \brief Construct a Buffer from a host container via iterators using a specified queue. + * If useHostPtr is specified iterators must be random access. + */ + template< typename IteratorType > + Buffer(const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, + bool readOnly, bool useHostPtr = false, cl_int* err = NULL); + + //! \brief Default constructor - initializes to NULL. + Buffer() : Memory() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with earlier versions. + * + * See Memory for further details. + */ + explicit Buffer(const cl_mem& buffer, bool retainObject = false) : + Memory(buffer, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + Buffer& operator = (const cl_mem& rhs) + { + Memory::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Buffer(const Buffer& buf) : Memory(buf) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Buffer& operator = (const Buffer &buf) + { + Memory::operator=(buf); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Buffer(Buffer&& buf) CL_HPP_NOEXCEPT_ : Memory(std::move(buf)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Buffer& operator = (Buffer &&buf) + { + Memory::operator=(std::move(buf)); + return *this; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 + /*! \brief Creates a new buffer object from this. + * + * Wraps clCreateSubBuffer(). + */ + Buffer createSubBuffer( + cl_mem_flags flags, + cl_buffer_create_type buffer_create_type, + const void * buffer_create_info, + cl_int * err = NULL) + { + Buffer result; + cl_int error; + result.object_ = ::clCreateSubBuffer( + object_, + flags, + buffer_create_type, + buffer_create_info, + &error); + + detail::errHandler(error, __CREATE_SUBBUFFER_ERR); + if (err != NULL) { + *err = error; + } + + return result; + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 +}; + +#if defined (CL_HPP_USE_DX_INTEROP) +/*! \brief Class interface for creating OpenCL buffers from ID3D10Buffer's. + * + * This is provided to facilitate interoperability with Direct3D. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class BufferD3D10 : public Buffer +{ +public: + + + /*! \brief Constructs a BufferD3D10, in a specified context, from a + * given ID3D10Buffer. + * + * Wraps clCreateFromD3D10BufferKHR(). + */ + BufferD3D10( + const Context& context, + cl_mem_flags flags, + ID3D10Buffer* bufobj, + cl_int * err = NULL) : pfn_clCreateFromD3D10BufferKHR(nullptr) + { + typedef CL_API_ENTRY cl_mem (CL_API_CALL *PFN_clCreateFromD3D10BufferKHR)( + cl_context context, cl_mem_flags flags, ID3D10Buffer* buffer, + cl_int* errcode_ret); + PFN_clCreateFromD3D10BufferKHR pfn_clCreateFromD3D10BufferKHR; +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + vector props = context.getInfo(); + cl_platform platform = -1; + for( int i = 0; i < props.size(); ++i ) { + if( props[i] == CL_CONTEXT_PLATFORM ) { + platform = props[i+1]; + } + } + CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, clCreateFromD3D10BufferKHR); +#elif CL_HPP_TARGET_OPENCL_VERSION >= 110 + CL_HPP_INIT_CL_EXT_FCN_PTR_(clCreateFromD3D10BufferKHR); +#endif + + cl_int error; + object_ = pfn_clCreateFromD3D10BufferKHR( + context(), + flags, + bufobj, + &error); + + detail::errHandler(error, __CREATE_GL_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } + + //! \brief Default constructor - initializes to NULL. + BufferD3D10() : Buffer() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit BufferD3D10(const cl_mem& buffer, bool retainObject = false) : + Buffer(buffer, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + BufferD3D10& operator = (const cl_mem& rhs) + { + Buffer::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + BufferD3D10(const BufferD3D10& buf) : + Buffer(buf) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + BufferD3D10& operator = (const BufferD3D10 &buf) + { + Buffer::operator=(buf); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + BufferD3D10(BufferD3D10&& buf) CL_HPP_NOEXCEPT_ : Buffer(std::move(buf)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + BufferD3D10& operator = (BufferD3D10 &&buf) + { + Buffer::operator=(std::move(buf)); + return *this; + } +}; +#endif + +/*! \brief Class interface for GL Buffer Memory Objects. + * + * This is provided to facilitate interoperability with OpenGL. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class BufferGL : public Buffer +{ +public: + /*! \brief Constructs a BufferGL in a specified context, from a given + * GL buffer. + * + * Wraps clCreateFromGLBuffer(). + */ + BufferGL( + const Context& context, + cl_mem_flags flags, + cl_GLuint bufobj, + cl_int * err = NULL) + { + cl_int error; + object_ = ::clCreateFromGLBuffer( + context(), + flags, + bufobj, + &error); + + detail::errHandler(error, __CREATE_GL_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } + + //! \brief Default constructor - initializes to NULL. + BufferGL() : Buffer() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit BufferGL(const cl_mem& buffer, bool retainObject = false) : + Buffer(buffer, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + BufferGL& operator = (const cl_mem& rhs) + { + Buffer::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + BufferGL(const BufferGL& buf) : Buffer(buf) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + BufferGL& operator = (const BufferGL &buf) + { + Buffer::operator=(buf); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + BufferGL(BufferGL&& buf) CL_HPP_NOEXCEPT_ : Buffer(std::move(buf)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + BufferGL& operator = (BufferGL &&buf) + { + Buffer::operator=(std::move(buf)); + return *this; + } + + //! \brief Wrapper for clGetGLObjectInfo(). + cl_int getObjectInfo( + cl_gl_object_type *type, + cl_GLuint * gl_object_name) + { + return detail::errHandler( + ::clGetGLObjectInfo(object_,type,gl_object_name), + __GET_GL_OBJECT_INFO_ERR); + } +}; + +/*! \brief Class interface for GL Render Buffer Memory Objects. + * + * This is provided to facilitate interoperability with OpenGL. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class BufferRenderGL : public Buffer +{ +public: + /*! \brief Constructs a BufferRenderGL in a specified context, from a given + * GL Renderbuffer. + * + * Wraps clCreateFromGLRenderbuffer(). + */ + BufferRenderGL( + const Context& context, + cl_mem_flags flags, + cl_GLuint bufobj, + cl_int * err = NULL) + { + cl_int error; + object_ = ::clCreateFromGLRenderbuffer( + context(), + flags, + bufobj, + &error); + + detail::errHandler(error, __CREATE_GL_RENDER_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } + + //! \brief Default constructor - initializes to NULL. + BufferRenderGL() : Buffer() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit BufferRenderGL(const cl_mem& buffer, bool retainObject = false) : + Buffer(buffer, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + BufferRenderGL& operator = (const cl_mem& rhs) + { + Buffer::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + BufferRenderGL(const BufferRenderGL& buf) : Buffer(buf) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + BufferRenderGL& operator = (const BufferRenderGL &buf) + { + Buffer::operator=(buf); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + BufferRenderGL(BufferRenderGL&& buf) CL_HPP_NOEXCEPT_ : Buffer(std::move(buf)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + BufferRenderGL& operator = (BufferRenderGL &&buf) + { + Buffer::operator=(std::move(buf)); + return *this; + } + + //! \brief Wrapper for clGetGLObjectInfo(). + cl_int getObjectInfo( + cl_gl_object_type *type, + cl_GLuint * gl_object_name) + { + return detail::errHandler( + ::clGetGLObjectInfo(object_,type,gl_object_name), + __GET_GL_OBJECT_INFO_ERR); + } +}; + +/*! \brief C++ base class for Image Memory objects. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class Image : public Memory +{ +protected: + //! \brief Default constructor - initializes to NULL. + Image() : Memory() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image(const cl_mem& image, bool retainObject = false) : + Memory(image, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + Image& operator = (const cl_mem& rhs) + { + Memory::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image(const Image& img) : Memory(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image& operator = (const Image &img) + { + Memory::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image(Image&& img) CL_HPP_NOEXCEPT_ : Memory(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image& operator = (Image &&img) + { + Memory::operator=(std::move(img)); + return *this; + } + + +public: + //! \brief Wrapper for clGetImageInfo(). + template + cl_int getImageInfo(cl_image_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetImageInfo, object_, name, param), + __GET_IMAGE_INFO_ERR); + } + + //! \brief Wrapper for clGetImageInfo() that returns by value. + template typename + detail::param_traits::param_type + getImageInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_image_info, name>::param_type param; + cl_int result = getImageInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } +}; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +/*! \brief Class interface for 1D Image Memory objects. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class Image1D : public Image +{ +public: + /*! \brief Constructs a 1D Image in a specified context. + * + * Wraps clCreateImage(). + */ + Image1D( + const Context& context, + cl_mem_flags flags, + ImageFormat format, + size_type width, + void* host_ptr = NULL, + cl_int* err = NULL) + { + cl_int error; + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE1D, + width, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + object_ = ::clCreateImage( + context(), + flags, + &format, + &desc, + host_ptr, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != NULL) { + *err = error; + } + } + + //! \brief Default constructor - initializes to NULL. + Image1D() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image1D(const cl_mem& image1D, bool retainObject = false) : + Image(image1D, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + Image1D& operator = (const cl_mem& rhs) + { + Image::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image1D(const Image1D& img) : Image(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image1D& operator = (const Image1D &img) + { + Image::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image1D(Image1D&& img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image1D& operator = (Image1D &&img) + { + Image::operator=(std::move(img)); + return *this; + } + +}; + +/*! \class Image1DBuffer + * \brief Image interface for 1D buffer images. + */ +class Image1DBuffer : public Image +{ +public: + Image1DBuffer( + const Context& context, + cl_mem_flags flags, + ImageFormat format, + size_type width, + const Buffer &buffer, + cl_int* err = NULL) + { + cl_int error; + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE1D_BUFFER, + width, + 0, 0, 0, 0, 0, 0, 0, + buffer() + }; + object_ = ::clCreateImage( + context(), + flags, + &format, + &desc, + NULL, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != NULL) { + *err = error; + } + } + + Image1DBuffer() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image1DBuffer(const cl_mem& image1D, bool retainObject = false) : + Image(image1D, retainObject) { } + + Image1DBuffer& operator = (const cl_mem& rhs) + { + Image::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image1DBuffer(const Image1DBuffer& img) : Image(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image1DBuffer& operator = (const Image1DBuffer &img) + { + Image::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image1DBuffer(Image1DBuffer&& img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image1DBuffer& operator = (Image1DBuffer &&img) + { + Image::operator=(std::move(img)); + return *this; + } + +}; + +/*! \class Image1DArray + * \brief Image interface for arrays of 1D images. + */ +class Image1DArray : public Image +{ +public: + Image1DArray( + const Context& context, + cl_mem_flags flags, + ImageFormat format, + size_type arraySize, + size_type width, + size_type rowPitch, + void* host_ptr = NULL, + cl_int* err = NULL) + { + cl_int error; + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE1D_ARRAY, + width, + 0, 0, // height, depth (unused) + arraySize, + rowPitch, + 0, 0, 0, 0 + }; + object_ = ::clCreateImage( + context(), + flags, + &format, + &desc, + host_ptr, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != NULL) { + *err = error; + } + } + + Image1DArray() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image1DArray(const cl_mem& imageArray, bool retainObject = false) : + Image(imageArray, retainObject) { } + + + Image1DArray& operator = (const cl_mem& rhs) + { + Image::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image1DArray(const Image1DArray& img) : Image(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image1DArray& operator = (const Image1DArray &img) + { + Image::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image1DArray(Image1DArray&& img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image1DArray& operator = (Image1DArray &&img) + { + Image::operator=(std::move(img)); + return *this; + } + +}; +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 120 + + +/*! \brief Class interface for 2D Image Memory objects. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class Image2D : public Image +{ +public: + /*! \brief Constructs a 2D Image in a specified context. + * + * Wraps clCreateImage(). + */ + Image2D( + const Context& context, + cl_mem_flags flags, + ImageFormat format, + size_type width, + size_type height, + size_type row_pitch = 0, + void* host_ptr = NULL, + cl_int* err = NULL) + { + cl_int error; + bool useCreateImage; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 120 + // Run-time decision based on the actual platform + { + cl_uint version = detail::getContextPlatformVersion(context()); + useCreateImage = (version >= 0x10002); // OpenCL 1.2 or above + } +#elif CL_HPP_TARGET_OPENCL_VERSION >= 120 + useCreateImage = true; +#else + useCreateImage = false; +#endif + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + if (useCreateImage) + { + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE2D, + width, + height, + 0, 0, // depth, array size (unused) + row_pitch, + 0, 0, 0, 0 + }; + object_ = ::clCreateImage( + context(), + flags, + &format, + &desc, + host_ptr, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != NULL) { + *err = error; + } + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +#if CL_HPP_MINIMUM_OPENCL_VERSION < 120 + if (!useCreateImage) + { + object_ = ::clCreateImage2D( + context(), flags,&format, width, height, row_pitch, host_ptr, &error); + + detail::errHandler(error, __CREATE_IMAGE2D_ERR); + if (err != NULL) { + *err = error; + } + } +#endif // CL_HPP_MINIMUM_OPENCL_VERSION < 120 + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + /*! \brief Constructs a 2D Image from a buffer. + * \note This will share storage with the underlying buffer. + * + * Wraps clCreateImage(). + */ + Image2D( + const Context& context, + ImageFormat format, + const Buffer &sourceBuffer, + size_type width, + size_type height, + size_type row_pitch = 0, + cl_int* err = nullptr) + { + cl_int error; + + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE2D, + width, + height, + 0, 0, // depth, array size (unused) + row_pitch, + 0, 0, 0, + // Use buffer as input to image + sourceBuffer() + }; + object_ = ::clCreateImage( + context(), + 0, // flags inherited from buffer + &format, + &desc, + nullptr, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != nullptr) { + *err = error; + } + } +#endif //#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + /*! \brief Constructs a 2D Image from an image. + * \note This will share storage with the underlying image but may + * reinterpret the channel order and type. + * + * The image will be created matching with a descriptor matching the source. + * + * \param order is the channel order to reinterpret the image data as. + * The channel order may differ as described in the OpenCL + * 2.0 API specification. + * + * Wraps clCreateImage(). + */ + Image2D( + const Context& context, + cl_channel_order order, + const Image &sourceImage, + cl_int* err = nullptr) + { + cl_int error; + + // Descriptor fields have to match source image + size_type sourceWidth = + sourceImage.getImageInfo(); + size_type sourceHeight = + sourceImage.getImageInfo(); + size_type sourceRowPitch = + sourceImage.getImageInfo(); + cl_uint sourceNumMIPLevels = + sourceImage.getImageInfo(); + cl_uint sourceNumSamples = + sourceImage.getImageInfo(); + cl_image_format sourceFormat = + sourceImage.getImageInfo(); + + // Update only the channel order. + // Channel format inherited from source. + sourceFormat.image_channel_order = order; + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE2D, + sourceWidth, + sourceHeight, + 0, 0, // depth (unused), array size (unused) + sourceRowPitch, + 0, // slice pitch (unused) + sourceNumMIPLevels, + sourceNumSamples, + // Use buffer as input to image + sourceImage() + }; + object_ = ::clCreateImage( + context(), + 0, // flags should be inherited from mem_object + &sourceFormat, + &desc, + nullptr, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != nullptr) { + *err = error; + } + } +#endif //#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + + //! \brief Default constructor - initializes to NULL. + Image2D() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image2D(const cl_mem& image2D, bool retainObject = false) : + Image(image2D, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + Image2D& operator = (const cl_mem& rhs) + { + Image::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image2D(const Image2D& img) : Image(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image2D& operator = (const Image2D &img) + { + Image::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image2D(Image2D&& img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image2D& operator = (Image2D &&img) + { + Image::operator=(std::move(img)); + return *this; + } + +}; + + +#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) +/*! \brief Class interface for GL 2D Image Memory objects. + * + * This is provided to facilitate interoperability with OpenGL. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + * \note Deprecated for OpenCL 1.2. Please use ImageGL instead. + */ +class CL_EXT_PREFIX__VERSION_1_1_DEPRECATED Image2DGL : public Image2D +{ +public: + /*! \brief Constructs an Image2DGL in a specified context, from a given + * GL Texture. + * + * Wraps clCreateFromGLTexture2D(). + */ + Image2DGL( + const Context& context, + cl_mem_flags flags, + cl_GLenum target, + cl_GLint miplevel, + cl_GLuint texobj, + cl_int * err = NULL) + { + cl_int error; + object_ = ::clCreateFromGLTexture2D( + context(), + flags, + target, + miplevel, + texobj, + &error); + + detail::errHandler(error, __CREATE_GL_TEXTURE_2D_ERR); + if (err != NULL) { + *err = error; + } + + } + + //! \brief Default constructor - initializes to NULL. + Image2DGL() : Image2D() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image2DGL(const cl_mem& image, bool retainObject = false) : + Image2D(image, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + *c + * See Memory for further details. + */ + Image2DGL& operator = (const cl_mem& rhs) + { + Image2D::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image2DGL(const Image2DGL& img) : Image2D(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image2DGL& operator = (const Image2DGL &img) + { + Image2D::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image2DGL(Image2DGL&& img) CL_HPP_NOEXCEPT_ : Image2D(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image2DGL& operator = (Image2DGL &&img) + { + Image2D::operator=(std::move(img)); + return *this; + } + +} CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; +#endif // CL_USE_DEPRECATED_OPENCL_1_1_APIS + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +/*! \class Image2DArray + * \brief Image interface for arrays of 2D images. + */ +class Image2DArray : public Image +{ +public: + Image2DArray( + const Context& context, + cl_mem_flags flags, + ImageFormat format, + size_type arraySize, + size_type width, + size_type height, + size_type rowPitch, + size_type slicePitch, + void* host_ptr = NULL, + cl_int* err = NULL) + { + cl_int error; + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE2D_ARRAY, + width, + height, + 0, // depth (unused) + arraySize, + rowPitch, + slicePitch, + 0, 0, 0 + }; + object_ = ::clCreateImage( + context(), + flags, + &format, + &desc, + host_ptr, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != NULL) { + *err = error; + } + } + + Image2DArray() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image2DArray(const cl_mem& imageArray, bool retainObject = false) : Image(imageArray, retainObject) { } + + Image2DArray& operator = (const cl_mem& rhs) + { + Image::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image2DArray(const Image2DArray& img) : Image(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image2DArray& operator = (const Image2DArray &img) + { + Image::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image2DArray(Image2DArray&& img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image2DArray& operator = (Image2DArray &&img) + { + Image::operator=(std::move(img)); + return *this; + } +}; +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 120 + +/*! \brief Class interface for 3D Image Memory objects. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class Image3D : public Image +{ +public: + /*! \brief Constructs a 3D Image in a specified context. + * + * Wraps clCreateImage(). + */ + Image3D( + const Context& context, + cl_mem_flags flags, + ImageFormat format, + size_type width, + size_type height, + size_type depth, + size_type row_pitch = 0, + size_type slice_pitch = 0, + void* host_ptr = NULL, + cl_int* err = NULL) + { + cl_int error; + bool useCreateImage; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 120 + // Run-time decision based on the actual platform + { + cl_uint version = detail::getContextPlatformVersion(context()); + useCreateImage = (version >= 0x10002); // OpenCL 1.2 or above + } +#elif CL_HPP_TARGET_OPENCL_VERSION >= 120 + useCreateImage = true; +#else + useCreateImage = false; +#endif + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + if (useCreateImage) + { + cl_image_desc desc = + { + CL_MEM_OBJECT_IMAGE3D, + width, + height, + depth, + 0, // array size (unused) + row_pitch, + slice_pitch, + 0, 0, 0 + }; + object_ = ::clCreateImage( + context(), + flags, + &format, + &desc, + host_ptr, + &error); + + detail::errHandler(error, __CREATE_IMAGE_ERR); + if (err != NULL) { + *err = error; + } + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +#if CL_HPP_MINIMUM_OPENCL_VERSION < 120 + if (!useCreateImage) + { + object_ = ::clCreateImage3D( + context(), flags, &format, width, height, depth, row_pitch, + slice_pitch, host_ptr, &error); + + detail::errHandler(error, __CREATE_IMAGE3D_ERR); + if (err != NULL) { + *err = error; + } + } +#endif // CL_HPP_MINIMUM_OPENCL_VERSION < 120 + } + + //! \brief Default constructor - initializes to NULL. + Image3D() : Image() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image3D(const cl_mem& image3D, bool retainObject = false) : + Image(image3D, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + Image3D& operator = (const cl_mem& rhs) + { + Image::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image3D(const Image3D& img) : Image(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image3D& operator = (const Image3D &img) + { + Image::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image3D(Image3D&& img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image3D& operator = (Image3D &&img) + { + Image::operator=(std::move(img)); + return *this; + } +}; + +#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) +/*! \brief Class interface for GL 3D Image Memory objects. + * + * This is provided to facilitate interoperability with OpenGL. + * + * See Memory for details about copy semantics, etc. + * + * \see Memory + */ +class Image3DGL : public Image3D +{ +public: + /*! \brief Constructs an Image3DGL in a specified context, from a given + * GL Texture. + * + * Wraps clCreateFromGLTexture3D(). + */ + Image3DGL( + const Context& context, + cl_mem_flags flags, + cl_GLenum target, + cl_GLint miplevel, + cl_GLuint texobj, + cl_int * err = NULL) + { + cl_int error; + object_ = ::clCreateFromGLTexture3D( + context(), + flags, + target, + miplevel, + texobj, + &error); + + detail::errHandler(error, __CREATE_GL_TEXTURE_3D_ERR); + if (err != NULL) { + *err = error; + } + } + + //! \brief Default constructor - initializes to NULL. + Image3DGL() : Image3D() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit Image3DGL(const cl_mem& image, bool retainObject = false) : + Image3D(image, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + Image3DGL& operator = (const cl_mem& rhs) + { + Image3D::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image3DGL(const Image3DGL& img) : Image3D(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Image3DGL& operator = (const Image3DGL &img) + { + Image3D::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Image3DGL(Image3DGL&& img) CL_HPP_NOEXCEPT_ : Image3D(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Image3DGL& operator = (Image3DGL &&img) + { + Image3D::operator=(std::move(img)); + return *this; + } +}; +#endif // CL_USE_DEPRECATED_OPENCL_1_1_APIS + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +/*! \class ImageGL + * \brief general image interface for GL interop. + * We abstract the 2D and 3D GL images into a single instance here + * that wraps all GL sourced images on the grounds that setup information + * was performed by OpenCL anyway. + */ +class ImageGL : public Image +{ +public: + ImageGL( + const Context& context, + cl_mem_flags flags, + cl_GLenum target, + cl_GLint miplevel, + cl_GLuint texobj, + cl_int * err = NULL) + { + cl_int error; + object_ = ::clCreateFromGLTexture( + context(), + flags, + target, + miplevel, + texobj, + &error); + + detail::errHandler(error, __CREATE_GL_TEXTURE_ERR); + if (err != NULL) { + *err = error; + } + } + + ImageGL() : Image() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * See Memory for further details. + */ + explicit ImageGL(const cl_mem& image, bool retainObject = false) : + Image(image, retainObject) { } + + ImageGL& operator = (const cl_mem& rhs) + { + Image::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + ImageGL(const ImageGL& img) : Image(img) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + ImageGL& operator = (const ImageGL &img) + { + Image::operator=(img); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + ImageGL(ImageGL&& img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + ImageGL& operator = (ImageGL &&img) + { + Image::operator=(std::move(img)); + return *this; + } +}; +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +/*! \brief Class interface for Pipe Memory Objects. +* +* See Memory for details about copy semantics, etc. +* +* \see Memory +*/ +class Pipe : public Memory +{ +public: + + /*! \brief Constructs a Pipe in a specified context. + * + * Wraps clCreatePipe(). + * @param context Context in which to create the pipe. + * @param flags Bitfield. Only CL_MEM_READ_WRITE and CL_MEM_HOST_NO_ACCESS are valid. + * @param packet_size Size in bytes of a single packet of the pipe. + * @param max_packets Number of packets that may be stored in the pipe. + * + */ + Pipe( + const Context& context, + cl_uint packet_size, + cl_uint max_packets, + cl_int* err = NULL) + { + cl_int error; + + cl_mem_flags flags = CL_MEM_READ_WRITE | CL_MEM_HOST_NO_ACCESS; + object_ = ::clCreatePipe(context(), flags, packet_size, max_packets, nullptr, &error); + + detail::errHandler(error, __CREATE_PIPE_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! \brief Constructs a Pipe in a the default context. + * + * Wraps clCreatePipe(). + * @param flags Bitfield. Only CL_MEM_READ_WRITE and CL_MEM_HOST_NO_ACCESS are valid. + * @param packet_size Size in bytes of a single packet of the pipe. + * @param max_packets Number of packets that may be stored in the pipe. + * + */ + Pipe( + cl_uint packet_size, + cl_uint max_packets, + cl_int* err = NULL) + { + cl_int error; + + Context context = Context::getDefault(err); + + cl_mem_flags flags = CL_MEM_READ_WRITE | CL_MEM_HOST_NO_ACCESS; + object_ = ::clCreatePipe(context(), flags, packet_size, max_packets, nullptr, &error); + + detail::errHandler(error, __CREATE_PIPE_ERR); + if (err != NULL) { + *err = error; + } + } + + //! \brief Default constructor - initializes to NULL. + Pipe() : Memory() { } + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with earlier versions. + * + * See Memory for further details. + */ + explicit Pipe(const cl_mem& pipe, bool retainObject = false) : + Memory(pipe, retainObject) { } + + /*! \brief Assignment from cl_mem - performs shallow copy. + * + * See Memory for further details. + */ + Pipe& operator = (const cl_mem& rhs) + { + Memory::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Pipe(const Pipe& pipe) : Memory(pipe) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Pipe& operator = (const Pipe &pipe) + { + Memory::operator=(pipe); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Pipe(Pipe&& pipe) CL_HPP_NOEXCEPT_ : Memory(std::move(pipe)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Pipe& operator = (Pipe &&pipe) + { + Memory::operator=(std::move(pipe)); + return *this; + } + + //! \brief Wrapper for clGetMemObjectInfo(). + template + cl_int getInfo(cl_pipe_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetPipeInfo, object_, name, param), + __GET_PIPE_INFO_ERR); + } + + //! \brief Wrapper for clGetMemObjectInfo() that returns by value. + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_pipe_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } +}; // class Pipe +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 200 + + +/*! \brief Class interface for cl_sampler. + * + * \note Copies of these objects are shallow, meaning that the copy will refer + * to the same underlying cl_sampler as the original. For details, see + * clRetainSampler() and clReleaseSampler(). + * + * \see cl_sampler + */ +class Sampler : public detail::Wrapper +{ +public: + //! \brief Default constructor - initializes to NULL. + Sampler() { } + + /*! \brief Constructs a Sampler in a specified context. + * + * Wraps clCreateSampler(). + */ + Sampler( + const Context& context, + cl_bool normalized_coords, + cl_addressing_mode addressing_mode, + cl_filter_mode filter_mode, + cl_int* err = NULL) + { + cl_int error; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_sampler_properties sampler_properties[] = { + CL_SAMPLER_NORMALIZED_COORDS, normalized_coords, + CL_SAMPLER_ADDRESSING_MODE, addressing_mode, + CL_SAMPLER_FILTER_MODE, filter_mode, + 0 }; + object_ = ::clCreateSamplerWithProperties( + context(), + sampler_properties, + &error); + + detail::errHandler(error, __CREATE_SAMPLER_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } +#else + object_ = ::clCreateSampler( + context(), + normalized_coords, + addressing_mode, + filter_mode, + &error); + + detail::errHandler(error, __CREATE_SAMPLER_ERR); + if (err != NULL) { + *err = error; + } +#endif + } + + /*! \brief Constructor from cl_sampler - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * This effectively transfers ownership of a refcount on the cl_sampler + * into the new Sampler object. + */ + explicit Sampler(const cl_sampler& sampler, bool retainObject = false) : + detail::Wrapper(sampler, retainObject) { } + + /*! \brief Assignment operator from cl_sampler - takes ownership. + * + * This effectively transfers ownership of a refcount on the rhs and calls + * clReleaseSampler() on the value previously held by this instance. + */ + Sampler& operator = (const cl_sampler& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Sampler(const Sampler& sam) : detail::Wrapper(sam) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Sampler& operator = (const Sampler &sam) + { + detail::Wrapper::operator=(sam); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Sampler(Sampler&& sam) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(sam)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Sampler& operator = (Sampler &&sam) + { + detail::Wrapper::operator=(std::move(sam)); + return *this; + } + + //! \brief Wrapper for clGetSamplerInfo(). + template + cl_int getInfo(cl_sampler_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetSamplerInfo, object_, name, param), + __GET_SAMPLER_INFO_ERR); + } + + //! \brief Wrapper for clGetSamplerInfo() that returns by value. + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_sampler_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } +}; + +class Program; +class CommandQueue; +class DeviceCommandQueue; +class Kernel; + +//! \brief Class interface for specifying NDRange values. +class NDRange +{ +private: + size_type sizes_[3]; + cl_uint dimensions_; + +public: + //! \brief Default constructor - resulting range has zero dimensions. + NDRange() + : dimensions_(0) + { + sizes_[0] = 0; + sizes_[1] = 0; + sizes_[2] = 0; + } + + //! \brief Constructs one-dimensional range. + NDRange(size_type size0) + : dimensions_(1) + { + sizes_[0] = size0; + sizes_[1] = 1; + sizes_[2] = 1; + } + + //! \brief Constructs two-dimensional range. + NDRange(size_type size0, size_type size1) + : dimensions_(2) + { + sizes_[0] = size0; + sizes_[1] = size1; + sizes_[2] = 1; + } + + //! \brief Constructs three-dimensional range. + NDRange(size_type size0, size_type size1, size_type size2) + : dimensions_(3) + { + sizes_[0] = size0; + sizes_[1] = size1; + sizes_[2] = size2; + } + + /*! \brief Conversion operator to const size_type *. + * + * \returns a pointer to the size of the first dimension. + */ + operator const size_type*() const { + return sizes_; + } + + //! \brief Queries the number of dimensions in the range. + size_type dimensions() const + { + return dimensions_; + } + + //! \brief Returns the size of the object in bytes based on the + // runtime number of dimensions + size_type size() const + { + return dimensions_*sizeof(size_type); + } + + size_type* get() + { + return sizes_; + } + + const size_type* get() const + { + return sizes_; + } +}; + +//! \brief A zero-dimensional range. +static const NDRange NullRange; + +//! \brief Local address wrapper for use with Kernel::setArg +struct LocalSpaceArg +{ + size_type size_; +}; + +namespace detail { + +template +struct KernelArgumentHandler; + +// Enable for objects that are not subclasses of memory +// Pointers, constants etc +template +struct KernelArgumentHandler::value>::type> +{ + static size_type size(const T&) { return sizeof(T); } + static const T* ptr(const T& value) { return &value; } +}; + +// Enable for subclasses of memory where we want to get a reference to the cl_mem out +// and pass that in for safety +template +struct KernelArgumentHandler::value>::type> +{ + static size_type size(const T&) { return sizeof(cl_mem); } + static const cl_mem* ptr(const T& value) { return &(value()); } +}; + +// Specialization for DeviceCommandQueue defined later + +template <> +struct KernelArgumentHandler +{ + static size_type size(const LocalSpaceArg& value) { return value.size_; } + static const void* ptr(const LocalSpaceArg&) { return NULL; } +}; + +} +//! \endcond + +/*! Local + * \brief Helper function for generating LocalSpaceArg objects. + */ +inline LocalSpaceArg +Local(size_type size) +{ + LocalSpaceArg ret = { size }; + return ret; +} + +/*! \brief Class interface for cl_kernel. + * + * \note Copies of these objects are shallow, meaning that the copy will refer + * to the same underlying cl_kernel as the original. For details, see + * clRetainKernel() and clReleaseKernel(). + * + * \see cl_kernel + */ +class Kernel : public detail::Wrapper +{ +public: + inline Kernel(const Program& program, const char* name, cl_int* err = NULL); + + //! \brief Default constructor - initializes to NULL. + Kernel() { } + + /*! \brief Constructor from cl_kernel - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + * This effectively transfers ownership of a refcount on the cl_kernel + * into the new Kernel object. + */ + explicit Kernel(const cl_kernel& kernel, bool retainObject = false) : + detail::Wrapper(kernel, retainObject) { } + + /*! \brief Assignment operator from cl_kernel - takes ownership. + * + * This effectively transfers ownership of a refcount on the rhs and calls + * clReleaseKernel() on the value previously held by this instance. + */ + Kernel& operator = (const cl_kernel& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Kernel(const Kernel& kernel) : detail::Wrapper(kernel) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Kernel& operator = (const Kernel &kernel) + { + detail::Wrapper::operator=(kernel); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Kernel(Kernel&& kernel) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(kernel)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Kernel& operator = (Kernel &&kernel) + { + detail::Wrapper::operator=(std::move(kernel)); + return *this; + } + + template + cl_int getInfo(cl_kernel_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetKernelInfo, object_, name, param), + __GET_KERNEL_INFO_ERR); + } + + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_kernel_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + template + cl_int getArgInfo(cl_uint argIndex, cl_kernel_arg_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetKernelArgInfo, object_, argIndex, name, param), + __GET_KERNEL_ARG_INFO_ERR); + } + + template typename + detail::param_traits::param_type + getArgInfo(cl_uint argIndex, cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_kernel_arg_info, name>::param_type param; + cl_int result = getArgInfo(argIndex, name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + + template + cl_int getWorkGroupInfo( + const Device& device, cl_kernel_work_group_info name, T* param) const + { + return detail::errHandler( + detail::getInfo( + &::clGetKernelWorkGroupInfo, object_, device(), name, param), + __GET_KERNEL_WORK_GROUP_INFO_ERR); + } + + template typename + detail::param_traits::param_type + getWorkGroupInfo(const Device& device, cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_kernel_work_group_info, name>::param_type param; + cl_int result = getWorkGroupInfo(device, name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +#if defined(CL_HPP_USE_CL_SUB_GROUPS_KHR) + cl_int getSubGroupInfo(const cl::Device &dev, cl_kernel_sub_group_info name, const cl::NDRange &range, size_type* param) const + { + typedef clGetKernelSubGroupInfoKHR_fn PFN_clGetKernelSubGroupInfoKHR; + static PFN_clGetKernelSubGroupInfoKHR pfn_clGetKernelSubGroupInfoKHR = NULL; + CL_HPP_INIT_CL_EXT_FCN_PTR_(clGetKernelSubGroupInfoKHR); + + return detail::errHandler( + pfn_clGetKernelSubGroupInfoKHR(object_, dev(), name, range.size(), range.get(), sizeof(size_type), param, nullptr), + __GET_KERNEL_ARG_INFO_ERR); + } + + template + size_type getSubGroupInfo(const cl::Device &dev, const cl::NDRange &range, cl_int* err = NULL) const + { + size_type param; + cl_int result = getSubGroupInfo(dev, name, range, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } +#endif // #if defined(CL_HPP_USE_CL_SUB_GROUPS_KHR) +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + /*! \brief setArg overload taking a shared_ptr type + */ + template + cl_int setArg(cl_uint index, const cl::pointer &argPtr) + { + return detail::errHandler( + ::clSetKernelArgSVMPointer(object_, index, argPtr.get()), + __SET_KERNEL_ARGS_ERR); + } + + /*! \brief setArg overload taking a vector type. + */ + template + cl_int setArg(cl_uint index, const cl::vector &argPtr) + { + return detail::errHandler( + ::clSetKernelArgSVMPointer(object_, index, argPtr.data()), + __SET_KERNEL_ARGS_ERR); + } + + /*! \brief setArg overload taking a pointer type + */ + template + typename std::enable_if::value, cl_int>::type + setArg(cl_uint index, const T argPtr) + { + return detail::errHandler( + ::clSetKernelArgSVMPointer(object_, index, argPtr), + __SET_KERNEL_ARGS_ERR); + } +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + + /*! \brief setArg overload taking a POD type + */ + template + typename std::enable_if::value, cl_int>::type + setArg(cl_uint index, const T &value) + { + return detail::errHandler( + ::clSetKernelArg( + object_, + index, + detail::KernelArgumentHandler::size(value), + detail::KernelArgumentHandler::ptr(value)), + __SET_KERNEL_ARGS_ERR); + } + + cl_int setArg(cl_uint index, size_type size, const void* argPtr) + { + return detail::errHandler( + ::clSetKernelArg(object_, index, size, argPtr), + __SET_KERNEL_ARGS_ERR); + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + /*! + * Specify a vector of SVM pointers that the kernel may access in + * addition to its arguments. + */ + cl_int setSVMPointers(const vector &pointerList) + { + return detail::errHandler( + ::clSetKernelExecInfo( + object_, + CL_KERNEL_EXEC_INFO_SVM_PTRS, + sizeof(void*)*pointerList.size(), + pointerList.data())); + } + + /*! + * Specify a std::array of SVM pointers that the kernel may access in + * addition to its arguments. + */ + template + cl_int setSVMPointers(const std::array &pointerList) + { + return detail::errHandler( + ::clSetKernelExecInfo( + object_, + CL_KERNEL_EXEC_INFO_SVM_PTRS, + sizeof(void*)*pointerList.size(), + pointerList.data())); + } + + /*! \brief Enable fine-grained system SVM. + * + * \note It is only possible to enable fine-grained system SVM if all devices + * in the context associated with kernel support it. + * + * \param svmEnabled True if fine-grained system SVM is requested. False otherwise. + * \return CL_SUCCESS if the function was executed succesfully. CL_INVALID_OPERATION + * if no devices in the context support fine-grained system SVM. + * + * \see clSetKernelExecInfo + */ + cl_int enableFineGrainedSystemSVM(bool svmEnabled) + { + cl_bool svmEnabled_ = svmEnabled ? CL_TRUE : CL_FALSE; + return detail::errHandler( + ::clSetKernelExecInfo( + object_, + CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM, + sizeof(cl_bool), + &svmEnabled_ + ) + ); + } + + template + void setSVMPointersHelper(std::array &pointerList, const pointer &t0, Ts... ts) + { + pointerList[index] = static_cast(t0.get()); + setSVMPointersHelper(ts...); + } + + template + typename std::enable_if::value, void>::type + setSVMPointersHelper(std::array &pointerList, T0 t0, Ts... ts) + { + pointerList[index] = static_cast(t0); + setSVMPointersHelper(ts...); + } + + template + void setSVMPointersHelper(std::array &pointerList, const pointer &t0) + { + pointerList[index] = static_cast(t0.get()); + } + + template + typename std::enable_if::value, void>::type + setSVMPointersHelper(std::array &pointerList, T0 t0) + { + pointerList[index] = static_cast(t0); + } + + template + cl_int setSVMPointers(const T0 &t0, Ts... ts) + { + std::array pointerList; + + setSVMPointersHelper<0, 1 + sizeof...(Ts)>(pointerList, t0, ts...); + return detail::errHandler( + ::clSetKernelExecInfo( + object_, + CL_KERNEL_EXEC_INFO_SVM_PTRS, + sizeof(void*)*(1 + sizeof...(Ts)), + pointerList.data())); + } +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 +}; + +/*! \class Program + * \brief Program interface that implements cl_program. + */ +class Program : public detail::Wrapper +{ +public: +#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + typedef vector> Binaries; + typedef vector Sources; +#else // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + typedef vector > Binaries; + typedef vector > Sources; +#endif // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + + Program( + const string& source, + bool build = false, + cl_int* err = NULL) + { + cl_int error; + + const char * strings = source.c_str(); + const size_type length = source.size(); + + Context context = Context::getDefault(err); + + object_ = ::clCreateProgramWithSource( + context(), (cl_uint)1, &strings, &length, &error); + + detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR); + + if (error == CL_SUCCESS && build) { + + error = ::clBuildProgram( + object_, + 0, + NULL, +#if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD) + "-cl-std=CL2.0", +#else + "", +#endif // #if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD) + NULL, + NULL); + + detail::buildErrHandler(error, __BUILD_PROGRAM_ERR, getBuildInfo()); + } + + if (err != NULL) { + *err = error; + } + } + + Program( + const Context& context, + const string& source, + bool build = false, + cl_int* err = NULL) + { + cl_int error; + + const char * strings = source.c_str(); + const size_type length = source.size(); + + object_ = ::clCreateProgramWithSource( + context(), (cl_uint)1, &strings, &length, &error); + + detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR); + + if (error == CL_SUCCESS && build) { + error = ::clBuildProgram( + object_, + 0, + NULL, +#if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD) + "-cl-std=CL2.0", +#else + "", +#endif // #if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD) + NULL, + NULL); + + detail::buildErrHandler(error, __BUILD_PROGRAM_ERR, getBuildInfo()); + } + + if (err != NULL) { + *err = error; + } + } + + /** + * Create a program from a vector of source strings and the default context. + * Does not compile or link the program. + */ + Program( + const Sources& sources, + cl_int* err = NULL) + { + cl_int error; + Context context = Context::getDefault(err); + + const size_type n = (size_type)sources.size(); + + vector lengths(n); + vector strings(n); + + for (size_type i = 0; i < n; ++i) { +#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + strings[i] = sources[(int)i].data(); + lengths[i] = sources[(int)i].length(); +#else // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + strings[i] = sources[(int)i].first; + lengths[i] = sources[(int)i].second; +#endif // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + } + + object_ = ::clCreateProgramWithSource( + context(), (cl_uint)n, strings.data(), lengths.data(), &error); + + detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR); + if (err != NULL) { + *err = error; + } + } + + /** + * Create a program from a vector of source strings and a provided context. + * Does not compile or link the program. + */ + Program( + const Context& context, + const Sources& sources, + cl_int* err = NULL) + { + cl_int error; + + const size_type n = (size_type)sources.size(); + + vector lengths(n); + vector strings(n); + + for (size_type i = 0; i < n; ++i) { +#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + strings[i] = sources[(int)i].data(); + lengths[i] = sources[(int)i].length(); +#else // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + strings[i] = sources[(int)i].first; + lengths[i] = sources[(int)i].second; +#endif // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + } + + object_ = ::clCreateProgramWithSource( + context(), (cl_uint)n, strings.data(), lengths.data(), &error); + + detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR); + if (err != NULL) { + *err = error; + } + } + + /** + * Construct a program object from a list of devices and a per-device list of binaries. + * \param context A valid OpenCL context in which to construct the program. + * \param devices A vector of OpenCL device objects for which the program will be created. + * \param binaries A vector of pairs of a pointer to a binary object and its length. + * \param binaryStatus An optional vector that on completion will be resized to + * match the size of binaries and filled with values to specify if each binary + * was successfully loaded. + * Set to CL_SUCCESS if the binary was successfully loaded. + * Set to CL_INVALID_VALUE if the length is 0 or the binary pointer is NULL. + * Set to CL_INVALID_BINARY if the binary provided is not valid for the matching device. + * \param err if non-NULL will be set to CL_SUCCESS on successful operation or one of the following errors: + * CL_INVALID_CONTEXT if context is not a valid context. + * CL_INVALID_VALUE if the length of devices is zero; or if the length of binaries does not match the length of devices; + * or if any entry in binaries is NULL or has length 0. + * CL_INVALID_DEVICE if OpenCL devices listed in devices are not in the list of devices associated with context. + * CL_INVALID_BINARY if an invalid program binary was encountered for any device. binaryStatus will return specific status for each device. + * CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required by the OpenCL implementation on the host. + */ + Program( + const Context& context, + const vector& devices, + const Binaries& binaries, + vector* binaryStatus = NULL, + cl_int* err = NULL) + { + cl_int error; + + const size_type numDevices = devices.size(); + + // Catch size mismatch early and return + if(binaries.size() != numDevices) { + error = CL_INVALID_VALUE; + detail::errHandler(error, __CREATE_PROGRAM_WITH_BINARY_ERR); + if (err != NULL) { + *err = error; + } + return; + } + + + vector lengths(numDevices); + vector images(numDevices); +#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + for (size_type i = 0; i < numDevices; ++i) { + images[i] = binaries[i].data(); + lengths[i] = binaries[(int)i].size(); + } +#else // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + for (size_type i = 0; i < numDevices; ++i) { + images[i] = (const unsigned char*)binaries[i].first; + lengths[i] = binaries[(int)i].second; + } +#endif // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY) + + vector deviceIDs(numDevices); + for( size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) { + deviceIDs[deviceIndex] = (devices[deviceIndex])(); + } + + if(binaryStatus) { + binaryStatus->resize(numDevices); + } + + object_ = ::clCreateProgramWithBinary( + context(), (cl_uint) devices.size(), + deviceIDs.data(), + lengths.data(), images.data(), (binaryStatus != NULL && numDevices > 0) + ? &binaryStatus->front() + : NULL, &error); + + detail::errHandler(error, __CREATE_PROGRAM_WITH_BINARY_ERR); + if (err != NULL) { + *err = error; + } + } + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + /** + * Create program using builtin kernels. + * \param kernelNames Semi-colon separated list of builtin kernel names + */ + Program( + const Context& context, + const vector& devices, + const string& kernelNames, + cl_int* err = NULL) + { + cl_int error; + + + size_type numDevices = devices.size(); + vector deviceIDs(numDevices); + for( size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) { + deviceIDs[deviceIndex] = (devices[deviceIndex])(); + } + + object_ = ::clCreateProgramWithBuiltInKernels( + context(), + (cl_uint) devices.size(), + deviceIDs.data(), + kernelNames.c_str(), + &error); + + detail::errHandler(error, __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR); + if (err != NULL) { + *err = error; + } + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + + Program() { } + + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + */ + explicit Program(const cl_program& program, bool retainObject = false) : + detail::Wrapper(program, retainObject) { } + + Program& operator = (const cl_program& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + Program(const Program& program) : detail::Wrapper(program) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + Program& operator = (const Program &program) + { + detail::Wrapper::operator=(program); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + Program(Program&& program) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(program)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + Program& operator = (Program &&program) + { + detail::Wrapper::operator=(std::move(program)); + return *this; + } + + cl_int build( + const vector& devices, + const char* options = NULL, + void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL, + void* data = NULL) const + { + size_type numDevices = devices.size(); + vector deviceIDs(numDevices); + + for( size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) { + deviceIDs[deviceIndex] = (devices[deviceIndex])(); + } + + cl_int buildError = ::clBuildProgram( + object_, + (cl_uint) + devices.size(), + deviceIDs.data(), + options, + notifyFptr, + data); + + return detail::buildErrHandler(buildError, __BUILD_PROGRAM_ERR, getBuildInfo()); + } + + cl_int build( + const char* options = NULL, + void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL, + void* data = NULL) const + { + cl_int buildError = ::clBuildProgram( + object_, + 0, + NULL, + options, + notifyFptr, + data); + + + return detail::buildErrHandler(buildError, __BUILD_PROGRAM_ERR, getBuildInfo()); + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + cl_int compile( + const char* options = NULL, + void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL, + void* data = NULL) const + { + cl_int error = ::clCompileProgram( + object_, + 0, + NULL, + options, + 0, + NULL, + NULL, + notifyFptr, + data); + return detail::buildErrHandler(error, __COMPILE_PROGRAM_ERR, getBuildInfo()); + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + + template + cl_int getInfo(cl_program_info name, T* param) const + { + return detail::errHandler( + detail::getInfo(&::clGetProgramInfo, object_, name, param), + __GET_PROGRAM_INFO_ERR); + } + + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_program_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + template + cl_int getBuildInfo( + const Device& device, cl_program_build_info name, T* param) const + { + return detail::errHandler( + detail::getInfo( + &::clGetProgramBuildInfo, object_, device(), name, param), + __GET_PROGRAM_BUILD_INFO_ERR); + } + + template typename + detail::param_traits::param_type + getBuildInfo(const Device& device, cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_program_build_info, name>::param_type param; + cl_int result = getBuildInfo(device, name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + /** + * Build info function that returns a vector of device/info pairs for the specified + * info type and for all devices in the program. + * On an error reading the info for any device, an empty vector of info will be returned. + */ + template + vector::param_type>> + getBuildInfo(cl_int *err = NULL) const + { + cl_int result = CL_SUCCESS; + + auto devs = getInfo(&result); + vector::param_type>> + devInfo; + + // If there was an initial error from getInfo return the error + if (result != CL_SUCCESS) { + if (err != NULL) { + *err = result; + } + return devInfo; + } + + for (cl::Device d : devs) { + typename detail::param_traits< + detail::cl_program_build_info, name>::param_type param; + result = getBuildInfo(d, name, ¶m); + devInfo.push_back( + std::pair::param_type> + (d, param)); + if (result != CL_SUCCESS) { + // On error, leave the loop and return the error code + break; + } + } + if (err != NULL) { + *err = result; + } + if (result != CL_SUCCESS) { + devInfo.clear(); + } + return devInfo; + } + + cl_int createKernels(vector* kernels) + { + cl_uint numKernels; + cl_int err = ::clCreateKernelsInProgram(object_, 0, NULL, &numKernels); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __CREATE_KERNELS_IN_PROGRAM_ERR); + } + + vector value(numKernels); + + err = ::clCreateKernelsInProgram( + object_, numKernels, value.data(), NULL); + if (err != CL_SUCCESS) { + return detail::errHandler(err, __CREATE_KERNELS_IN_PROGRAM_ERR); + } + + if (kernels) { + kernels->resize(value.size()); + + // Assign to param, constructing with retain behaviour + // to correctly capture each underlying CL object + for (size_type i = 0; i < value.size(); i++) { + // We do not need to retain because this kernel is being created + // by the runtime + (*kernels)[i] = Kernel(value[i], false); + } + } + return CL_SUCCESS; + } +}; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 +inline Program linkProgram( + Program input1, + Program input2, + const char* options = NULL, + void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL, + void* data = NULL, + cl_int* err = NULL) +{ + cl_int error_local = CL_SUCCESS; + + cl_program programs[2] = { input1(), input2() }; + + Context ctx = input1.getInfo(&error_local); + if(error_local!=CL_SUCCESS) { + detail::errHandler(error_local, __LINK_PROGRAM_ERR); + } + + cl_program prog = ::clLinkProgram( + ctx(), + 0, + NULL, + options, + 2, + programs, + notifyFptr, + data, + &error_local); + + detail::errHandler(error_local,__COMPILE_PROGRAM_ERR); + if (err != NULL) { + *err = error_local; + } + + return Program(prog); +} + +inline Program linkProgram( + vector inputPrograms, + const char* options = NULL, + void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL, + void* data = NULL, + cl_int* err = NULL) +{ + cl_int error_local = CL_SUCCESS; + + vector programs(inputPrograms.size()); + + for (unsigned int i = 0; i < inputPrograms.size(); i++) { + programs[i] = inputPrograms[i](); + } + + Context ctx; + if(inputPrograms.size() > 0) { + ctx = inputPrograms[0].getInfo(&error_local); + if(error_local!=CL_SUCCESS) { + detail::errHandler(error_local, __LINK_PROGRAM_ERR); + } + } + cl_program prog = ::clLinkProgram( + ctx(), + 0, + NULL, + options, + (cl_uint)inputPrograms.size(), + programs.data(), + notifyFptr, + data, + &error_local); + + detail::errHandler(error_local,__COMPILE_PROGRAM_ERR); + if (err != NULL) { + *err = error_local; + } + + return Program(prog, false); +} +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + +// Template specialization for CL_PROGRAM_BINARIES +template <> +inline cl_int cl::Program::getInfo(cl_program_info name, vector>* param) const +{ + if (name != CL_PROGRAM_BINARIES) { + return CL_INVALID_VALUE; + } + if (param) { + // Resize the parameter array appropriately for each allocation + // and pass down to the helper + + vector sizes = getInfo(); + size_type numBinaries = sizes.size(); + + // Resize the parameter array and constituent arrays + param->resize(numBinaries); + for (size_type i = 0; i < numBinaries; ++i) { + (*param)[i].resize(sizes[i]); + } + + return detail::errHandler( + detail::getInfo(&::clGetProgramInfo, object_, name, param), + __GET_PROGRAM_INFO_ERR); + } + + return CL_SUCCESS; +} + +template<> +inline vector> cl::Program::getInfo(cl_int* err) const +{ + vector> binariesVectors; + + cl_int result = getInfo(CL_PROGRAM_BINARIES, &binariesVectors); + if (err != NULL) { + *err = result; + } + return binariesVectors; +} + +inline Kernel::Kernel(const Program& program, const char* name, cl_int* err) +{ + cl_int error; + + object_ = ::clCreateKernel(program(), name, &error); + detail::errHandler(error, __CREATE_KERNEL_ERR); + + if (err != NULL) { + *err = error; + } + +} + +enum class QueueProperties : cl_command_queue_properties +{ + None = 0, + Profiling = CL_QUEUE_PROFILING_ENABLE, + OutOfOrder = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, +}; + +inline QueueProperties operator|(QueueProperties lhs, QueueProperties rhs) +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +/*! \class CommandQueue + * \brief CommandQueue interface for cl_command_queue. + */ +class CommandQueue : public detail::Wrapper +{ +private: + static std::once_flag default_initialized_; + static CommandQueue default_; + static cl_int default_error_; + + /*! \brief Create the default command queue returned by @ref getDefault. + * + * It sets default_error_ to indicate success or failure. It does not throw + * @c cl::Error. + */ + static void makeDefault() + { + /* We don't want to throw an error from this function, so we have to + * catch and set the error flag. + */ +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + try +#endif + { + int error; + Context context = Context::getDefault(&error); + + if (error != CL_SUCCESS) { + default_error_ = error; + } + else { + Device device = Device::getDefault(); + default_ = CommandQueue(context, device, 0, &default_error_); + } + } +#if defined(CL_HPP_ENABLE_EXCEPTIONS) + catch (cl::Error &e) { + default_error_ = e.err(); + } +#endif + } + + /*! \brief Create the default command queue. + * + * This sets @c default_. It does not throw + * @c cl::Error. + */ + static void makeDefaultProvided(const CommandQueue &c) { + default_ = c; + } + +public: +#ifdef CL_HPP_UNIT_TEST_ENABLE + /*! \brief Reset the default. + * + * This sets @c default_ to an empty value to support cleanup in + * the unit test framework. + * This function is not thread safe. + */ + static void unitTestClearDefault() { + default_ = CommandQueue(); + } +#endif // #ifdef CL_HPP_UNIT_TEST_ENABLE + + + /*! + * \brief Constructs a CommandQueue based on passed properties. + * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified. + */ + CommandQueue( + cl_command_queue_properties properties, + cl_int* err = NULL) + { + cl_int error; + + Context context = Context::getDefault(&error); + detail::errHandler(error, __CREATE_CONTEXT_ERR); + + if (error != CL_SUCCESS) { + if (err != NULL) { + *err = error; + } + } + else { + Device device = context.getInfo()[0]; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, properties, 0 }; + if ((properties & CL_QUEUE_ON_DEVICE) == 0) { + object_ = ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error); + } + else { + error = CL_INVALID_QUEUE_PROPERTIES; + } + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } +#else + object_ = ::clCreateCommandQueue( + context(), device(), properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR); + if (err != NULL) { + *err = error; + } +#endif + } + } + + /*! + * \brief Constructs a CommandQueue based on passed properties. + * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified. + */ + CommandQueue( + QueueProperties properties, + cl_int* err = NULL) + { + cl_int error; + + Context context = Context::getDefault(&error); + detail::errHandler(error, __CREATE_CONTEXT_ERR); + + if (error != CL_SUCCESS) { + if (err != NULL) { + *err = error; + } + } + else { + Device device = context.getInfo()[0]; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, static_cast(properties), 0 }; + + object_ = ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error); + + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } +#else + object_ = ::clCreateCommandQueue( + context(), device(), static_cast(properties), &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR); + if (err != NULL) { + *err = error; + } +#endif + } + } + + /*! + * \brief Constructs a CommandQueue for an implementation defined device in the given context + * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified. + */ + explicit CommandQueue( + const Context& context, + cl_command_queue_properties properties = 0, + cl_int* err = NULL) + { + cl_int error; + vector devices; + error = context.getInfo(CL_CONTEXT_DEVICES, &devices); + + detail::errHandler(error, __CREATE_CONTEXT_ERR); + + if (error != CL_SUCCESS) + { + if (err != NULL) { + *err = error; + } + return; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, properties, 0 }; + if ((properties & CL_QUEUE_ON_DEVICE) == 0) { + object_ = ::clCreateCommandQueueWithProperties( + context(), devices[0](), queue_properties, &error); + } + else { + error = CL_INVALID_QUEUE_PROPERTIES; + } + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } +#else + object_ = ::clCreateCommandQueue( + context(), devices[0](), properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR); + if (err != NULL) { + *err = error; + } +#endif + + } + + /*! + * \brief Constructs a CommandQueue for an implementation defined device in the given context + * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified. + */ + explicit CommandQueue( + const Context& context, + QueueProperties properties, + cl_int* err = NULL) + { + cl_int error; + vector devices; + error = context.getInfo(CL_CONTEXT_DEVICES, &devices); + + detail::errHandler(error, __CREATE_CONTEXT_ERR); + + if (error != CL_SUCCESS) + { + if (err != NULL) { + *err = error; + } + return; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, static_cast(properties), 0 }; + object_ = ::clCreateCommandQueueWithProperties( + context(), devices[0](), queue_properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } +#else + object_ = ::clCreateCommandQueue( + context(), devices[0](), static_cast(properties), &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR); + if (err != NULL) { + *err = error; + } +#endif + + } + + /*! + * \brief Constructs a CommandQueue for a passed device and context + * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified. + */ + CommandQueue( + const Context& context, + const Device& device, + cl_command_queue_properties properties = 0, + cl_int* err = NULL) + { + cl_int error; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, properties, 0 }; + object_ = ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } +#else + object_ = ::clCreateCommandQueue( + context(), device(), properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR); + if (err != NULL) { + *err = error; + } +#endif + } + + /*! + * \brief Constructs a CommandQueue for a passed device and context + * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified. + */ + CommandQueue( + const Context& context, + const Device& device, + QueueProperties properties, + cl_int* err = NULL) + { + cl_int error; + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, static_cast(properties), 0 }; + object_ = ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } +#else + object_ = ::clCreateCommandQueue( + context(), device(), static_cast(properties), &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR); + if (err != NULL) { + *err = error; + } +#endif + } + + static CommandQueue getDefault(cl_int * err = NULL) + { + std::call_once(default_initialized_, makeDefault); +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + detail::errHandler(default_error_, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); +#else // CL_HPP_TARGET_OPENCL_VERSION >= 200 + detail::errHandler(default_error_, __CREATE_COMMAND_QUEUE_ERR); +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 200 + if (err != NULL) { + *err = default_error_; + } + return default_; + } + + /** + * Modify the default command queue to be used by + * subsequent operations. + * Will only set the default if no default was previously created. + * @return updated default command queue. + * Should be compared to the passed value to ensure that it was updated. + */ + static CommandQueue setDefault(const CommandQueue &default_queue) + { + std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_queue)); + detail::errHandler(default_error_); + return default_; + } + + CommandQueue() { } + + + /*! \brief Constructor from cl_mem - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + */ + explicit CommandQueue(const cl_command_queue& commandQueue, bool retainObject = false) : + detail::Wrapper(commandQueue, retainObject) { } + + CommandQueue& operator = (const cl_command_queue& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + CommandQueue(const CommandQueue& queue) : detail::Wrapper(queue) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + CommandQueue& operator = (const CommandQueue &queue) + { + detail::Wrapper::operator=(queue); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + CommandQueue(CommandQueue&& queue) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(queue)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + CommandQueue& operator = (CommandQueue &&queue) + { + detail::Wrapper::operator=(std::move(queue)); + return *this; + } + + template + cl_int getInfo(cl_command_queue_info name, T* param) const + { + return detail::errHandler( + detail::getInfo( + &::clGetCommandQueueInfo, object_, name, param), + __GET_COMMAND_QUEUE_INFO_ERR); + } + + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_command_queue_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + cl_int enqueueReadBuffer( + const Buffer& buffer, + cl_bool blocking, + size_type offset, + size_type size, + void* ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueReadBuffer( + object_, buffer(), blocking, offset, size, + ptr, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_READ_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueWriteBuffer( + const Buffer& buffer, + cl_bool blocking, + size_type offset, + size_type size, + const void* ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueWriteBuffer( + object_, buffer(), blocking, offset, size, + ptr, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_WRITE_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueCopyBuffer( + const Buffer& src, + const Buffer& dst, + size_type src_offset, + size_type dst_offset, + size_type size, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueCopyBuffer( + object_, src(), dst(), src_offset, dst_offset, size, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQEUE_COPY_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueReadBufferRect( + const Buffer& buffer, + cl_bool blocking, + const array& buffer_offset, + const array& host_offset, + const array& region, + size_type buffer_row_pitch, + size_type buffer_slice_pitch, + size_type host_row_pitch, + size_type host_slice_pitch, + void *ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueReadBufferRect( + object_, + buffer(), + blocking, + buffer_offset.data(), + host_offset.data(), + region.data(), + buffer_row_pitch, + buffer_slice_pitch, + host_row_pitch, + host_slice_pitch, + ptr, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_READ_BUFFER_RECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueWriteBufferRect( + const Buffer& buffer, + cl_bool blocking, + const array& buffer_offset, + const array& host_offset, + const array& region, + size_type buffer_row_pitch, + size_type buffer_slice_pitch, + size_type host_row_pitch, + size_type host_slice_pitch, + const void *ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueWriteBufferRect( + object_, + buffer(), + blocking, + buffer_offset.data(), + host_offset.data(), + region.data(), + buffer_row_pitch, + buffer_slice_pitch, + host_row_pitch, + host_slice_pitch, + ptr, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_WRITE_BUFFER_RECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueCopyBufferRect( + const Buffer& src, + const Buffer& dst, + const array& src_origin, + const array& dst_origin, + const array& region, + size_type src_row_pitch, + size_type src_slice_pitch, + size_type dst_row_pitch, + size_type dst_slice_pitch, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueCopyBufferRect( + object_, + src(), + dst(), + src_origin.data(), + dst_origin.data(), + region.data(), + src_row_pitch, + src_slice_pitch, + dst_row_pitch, + dst_slice_pitch, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQEUE_COPY_BUFFER_RECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + /** + * Enqueue a command to fill a buffer object with a pattern + * of a given size. The pattern is specified as a vector type. + * \tparam PatternType The datatype of the pattern field. + * The pattern type must be an accepted OpenCL data type. + * \tparam offset Is the offset in bytes into the buffer at + * which to start filling. This must be a multiple of + * the pattern size. + * \tparam size Is the size in bytes of the region to fill. + * This must be a multiple of the pattern size. + */ + template + cl_int enqueueFillBuffer( + const Buffer& buffer, + PatternType pattern, + size_type offset, + size_type size, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueFillBuffer( + object_, + buffer(), + static_cast(&pattern), + sizeof(PatternType), + offset, + size, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_FILL_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + + cl_int enqueueReadImage( + const Image& image, + cl_bool blocking, + const array& origin, + const array& region, + size_type row_pitch, + size_type slice_pitch, + void* ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueReadImage( + object_, + image(), + blocking, + origin.data(), + region.data(), + row_pitch, + slice_pitch, + ptr, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_READ_IMAGE_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueWriteImage( + const Image& image, + cl_bool blocking, + const array& origin, + const array& region, + size_type row_pitch, + size_type slice_pitch, + const void* ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueWriteImage( + object_, + image(), + blocking, + origin.data(), + region.data(), + row_pitch, + slice_pitch, + ptr, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_WRITE_IMAGE_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueCopyImage( + const Image& src, + const Image& dst, + const array& src_origin, + const array& dst_origin, + const array& region, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueCopyImage( + object_, + src(), + dst(), + src_origin.data(), + dst_origin.data(), + region.data(), + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_COPY_IMAGE_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + /** + * Enqueue a command to fill an image object with a specified color. + * \param fillColor is the color to use to fill the image. + * This is a four component RGBA floating-point color value if + * the image channel data type is not an unnormalized signed or + * unsigned data type. + */ + cl_int enqueueFillImage( + const Image& image, + cl_float4 fillColor, + const array& origin, + const array& region, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueFillImage( + object_, + image(), + static_cast(&fillColor), + origin.data(), + region.data(), + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_FILL_IMAGE_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + /** + * Enqueue a command to fill an image object with a specified color. + * \param fillColor is the color to use to fill the image. + * This is a four component RGBA signed integer color value if + * the image channel data type is an unnormalized signed integer + * type. + */ + cl_int enqueueFillImage( + const Image& image, + cl_int4 fillColor, + const array& origin, + const array& region, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueFillImage( + object_, + image(), + static_cast(&fillColor), + origin.data(), + region.data(), + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_FILL_IMAGE_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + /** + * Enqueue a command to fill an image object with a specified color. + * \param fillColor is the color to use to fill the image. + * This is a four component RGBA unsigned integer color value if + * the image channel data type is an unnormalized unsigned integer + * type. + */ + cl_int enqueueFillImage( + const Image& image, + cl_uint4 fillColor, + const array& origin, + const array& region, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueFillImage( + object_, + image(), + static_cast(&fillColor), + origin.data(), + region.data(), + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_FILL_IMAGE_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + + cl_int enqueueCopyImageToBuffer( + const Image& src, + const Buffer& dst, + const array& src_origin, + const array& region, + size_type dst_offset, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueCopyImageToBuffer( + object_, + src(), + dst(), + src_origin.data(), + region.data(), + dst_offset, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueCopyBufferToImage( + const Buffer& src, + const Image& dst, + size_type src_offset, + const array& dst_origin, + const array& region, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueCopyBufferToImage( + object_, + src(), + dst(), + src_offset, + dst_origin.data(), + region.data(), + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + void* enqueueMapBuffer( + const Buffer& buffer, + cl_bool blocking, + cl_map_flags flags, + size_type offset, + size_type size, + const vector* events = NULL, + Event* event = NULL, + cl_int* err = NULL) const + { + cl_event tmp; + cl_int error; + void * result = ::clEnqueueMapBuffer( + object_, buffer(), blocking, flags, offset, size, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL, + &error); + + detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + if (event != NULL && error == CL_SUCCESS) + *event = tmp; + + return result; + } + + void* enqueueMapImage( + const Image& buffer, + cl_bool blocking, + cl_map_flags flags, + const array& origin, + const array& region, + size_type * row_pitch, + size_type * slice_pitch, + const vector* events = NULL, + Event* event = NULL, + cl_int* err = NULL) const + { + cl_event tmp; + cl_int error; + void * result = ::clEnqueueMapImage( + object_, buffer(), blocking, flags, + origin.data(), + region.data(), + row_pitch, slice_pitch, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL, + &error); + + detail::errHandler(error, __ENQUEUE_MAP_IMAGE_ERR); + if (err != NULL) { + *err = error; + } + if (event != NULL && error == CL_SUCCESS) + *event = tmp; + return result; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + /** + * Enqueues a command that will allow the host to update a region of a coarse-grained SVM buffer. + * This variant takes a raw SVM pointer. + */ + template + cl_int enqueueMapSVM( + T* ptr, + cl_bool blocking, + cl_map_flags flags, + size_type size, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler(::clEnqueueSVMMap( + object_, blocking, flags, static_cast(ptr), size, + (events != NULL) ? (cl_uint)events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*)&events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_MAP_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + + /** + * Enqueues a command that will allow the host to update a region of a coarse-grained SVM buffer. + * This variant takes a cl::pointer instance. + */ + template + cl_int enqueueMapSVM( + cl::pointer &ptr, + cl_bool blocking, + cl_map_flags flags, + size_type size, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler(::clEnqueueSVMMap( + object_, blocking, flags, static_cast(ptr.get()), size, + (events != NULL) ? (cl_uint)events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*)&events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_MAP_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + /** + * Enqueues a command that will allow the host to update a region of a coarse-grained SVM buffer. + * This variant takes a cl::vector instance. + */ + template + cl_int enqueueMapSVM( + cl::vector &container, + cl_bool blocking, + cl_map_flags flags, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler(::clEnqueueSVMMap( + object_, blocking, flags, static_cast(container.data()), container.size(), + (events != NULL) ? (cl_uint)events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*)&events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_MAP_BUFFER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + + cl_int enqueueUnmapMemObject( + const Memory& memory, + void* mapped_ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueUnmapMemObject( + object_, memory(), mapped_ptr, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + /** + * Enqueues a command that will release a coarse-grained SVM buffer back to the OpenCL runtime. + * This variant takes a raw SVM pointer. + */ + template + cl_int enqueueUnmapSVM( + T* ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueSVMUnmap( + object_, static_cast(ptr), + (events != NULL) ? (cl_uint)events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*)&events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + /** + * Enqueues a command that will release a coarse-grained SVM buffer back to the OpenCL runtime. + * This variant takes a cl::pointer instance. + */ + template + cl_int enqueueUnmapSVM( + cl::pointer &ptr, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueSVMUnmap( + object_, static_cast(ptr.get()), + (events != NULL) ? (cl_uint)events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*)&events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + /** + * Enqueues a command that will release a coarse-grained SVM buffer back to the OpenCL runtime. + * This variant takes a cl::vector instance. + */ + template + cl_int enqueueUnmapSVM( + cl::vector &container, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueSVMUnmap( + object_, static_cast(container.data()), + (events != NULL) ? (cl_uint)events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*)&events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + /** + * Enqueues a marker command which waits for either a list of events to complete, + * or all previously enqueued commands to complete. + * + * Enqueues a marker command which waits for either a list of events to complete, + * or if the list is empty it waits for all commands previously enqueued in command_queue + * to complete before it completes. This command returns an event which can be waited on, + * i.e. this event can be waited on to insure that all events either in the event_wait_list + * or all previously enqueued commands, queued before this command to command_queue, + * have completed. + */ + cl_int enqueueMarkerWithWaitList( + const vector *events = 0, + Event *event = 0) + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueMarkerWithWaitList( + object_, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_MARKER_WAIT_LIST_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + /** + * A synchronization point that enqueues a barrier operation. + * + * Enqueues a barrier command which waits for either a list of events to complete, + * or if the list is empty it waits for all commands previously enqueued in command_queue + * to complete before it completes. This command blocks command execution, that is, any + * following commands enqueued after it do not execute until it completes. This command + * returns an event which can be waited on, i.e. this event can be waited on to insure that + * all events either in the event_wait_list or all previously enqueued commands, queued + * before this command to command_queue, have completed. + */ + cl_int enqueueBarrierWithWaitList( + const vector *events = 0, + Event *event = 0) + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueBarrierWithWaitList( + object_, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_BARRIER_WAIT_LIST_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + /** + * Enqueues a command to indicate with which device a set of memory objects + * should be associated. + */ + cl_int enqueueMigrateMemObjects( + const vector &memObjects, + cl_mem_migration_flags flags, + const vector* events = NULL, + Event* event = NULL + ) + { + cl_event tmp; + + vector localMemObjects(memObjects.size()); + + for( int i = 0; i < (int)memObjects.size(); ++i ) { + localMemObjects[i] = memObjects[i](); + } + + + cl_int err = detail::errHandler( + ::clEnqueueMigrateMemObjects( + object_, + (cl_uint)memObjects.size(), + localMemObjects.data(), + flags, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 + + cl_int enqueueNDRangeKernel( + const Kernel& kernel, + const NDRange& offset, + const NDRange& global, + const NDRange& local = NullRange, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueNDRangeKernel( + object_, kernel(), (cl_uint) global.dimensions(), + offset.dimensions() != 0 ? (const size_type*) offset : NULL, + (const size_type*) global, + local.dimensions() != 0 ? (const size_type*) local : NULL, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_NDRANGE_KERNEL_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + +#if defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS) + CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int enqueueTask( + const Kernel& kernel, + const vector* events = NULL, + Event* event = NULL) const CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueTask( + object_, kernel(), + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_TASK_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } +#endif // #if defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS) + + cl_int enqueueNativeKernel( + void (CL_CALLBACK *userFptr)(void *), + std::pair args, + const vector* mem_objects = NULL, + const vector* mem_locs = NULL, + const vector* events = NULL, + Event* event = NULL) const + { + size_type elements = 0; + if (mem_objects != NULL) { + elements = mem_objects->size(); + } + vector mems(elements); + for (unsigned int i = 0; i < elements; i++) { + mems[i] = ((*mem_objects)[i])(); + } + + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueNativeKernel( + object_, userFptr, args.first, args.second, + (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0, + mems.data(), + (mem_locs != NULL && mem_locs->size() > 0) ? (const void **) &mem_locs->front() : NULL, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_NATIVE_KERNEL); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + +/** + * Deprecated APIs for 1.2 + */ +#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) + CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + cl_int enqueueMarker(Event* event = NULL) const CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueMarker( + object_, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_MARKER_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + cl_int enqueueWaitForEvents(const vector& events) const CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + { + return detail::errHandler( + ::clEnqueueWaitForEvents( + object_, + (cl_uint) events.size(), + events.size() > 0 ? (const cl_event*) &events.front() : NULL), + __ENQUEUE_WAIT_FOR_EVENTS_ERR); + } +#endif // defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) + + cl_int enqueueAcquireGLObjects( + const vector* mem_objects = NULL, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueAcquireGLObjects( + object_, + (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0, + (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *) &mem_objects->front(): NULL, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_ACQUIRE_GL_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueReleaseGLObjects( + const vector* mem_objects = NULL, + const vector* events = NULL, + Event* event = NULL) const + { + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueReleaseGLObjects( + object_, + (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0, + (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *) &mem_objects->front(): NULL, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_RELEASE_GL_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + +#if defined (CL_HPP_USE_DX_INTEROP) +typedef CL_API_ENTRY cl_int (CL_API_CALL *PFN_clEnqueueAcquireD3D10ObjectsKHR)( + cl_command_queue command_queue, cl_uint num_objects, + const cl_mem* mem_objects, cl_uint num_events_in_wait_list, + const cl_event* event_wait_list, cl_event* event); +typedef CL_API_ENTRY cl_int (CL_API_CALL *PFN_clEnqueueReleaseD3D10ObjectsKHR)( + cl_command_queue command_queue, cl_uint num_objects, + const cl_mem* mem_objects, cl_uint num_events_in_wait_list, + const cl_event* event_wait_list, cl_event* event); + + cl_int enqueueAcquireD3D10Objects( + const vector* mem_objects = NULL, + const vector* events = NULL, + Event* event = NULL) const + { + static PFN_clEnqueueAcquireD3D10ObjectsKHR pfn_clEnqueueAcquireD3D10ObjectsKHR = NULL; +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + cl_context context = getInfo(); + cl::Device device(getInfo()); + cl_platform_id platform = device.getInfo(); + CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, clEnqueueAcquireD3D10ObjectsKHR); +#endif +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 + CL_HPP_INIT_CL_EXT_FCN_PTR_(clEnqueueAcquireD3D10ObjectsKHR); +#endif + + cl_event tmp; + cl_int err = detail::errHandler( + pfn_clEnqueueAcquireD3D10ObjectsKHR( + object_, + (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0, + (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *) &mem_objects->front(): NULL, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_ACQUIRE_GL_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } + + cl_int enqueueReleaseD3D10Objects( + const vector* mem_objects = NULL, + const vector* events = NULL, + Event* event = NULL) const + { + static PFN_clEnqueueReleaseD3D10ObjectsKHR pfn_clEnqueueReleaseD3D10ObjectsKHR = NULL; +#if CL_HPP_TARGET_OPENCL_VERSION >= 120 + cl_context context = getInfo(); + cl::Device device(getInfo()); + cl_platform_id platform = device.getInfo(); + CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, clEnqueueReleaseD3D10ObjectsKHR); +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 120 +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 + CL_HPP_INIT_CL_EXT_FCN_PTR_(clEnqueueReleaseD3D10ObjectsKHR); +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 + + cl_event tmp; + cl_int err = detail::errHandler( + pfn_clEnqueueReleaseD3D10ObjectsKHR( + object_, + (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0, + (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *) &mem_objects->front(): NULL, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_RELEASE_GL_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; + } +#endif + +/** + * Deprecated APIs for 1.2 + */ +#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) + CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + cl_int enqueueBarrier() const CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + { + return detail::errHandler( + ::clEnqueueBarrier(object_), + __ENQUEUE_BARRIER_ERR); + } +#endif // CL_USE_DEPRECATED_OPENCL_1_1_APIS + + cl_int flush() const + { + return detail::errHandler(::clFlush(object_), __FLUSH_ERR); + } + + cl_int finish() const + { + return detail::errHandler(::clFinish(object_), __FINISH_ERR); + } +}; // CommandQueue + +CL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag CommandQueue::default_initialized_; +CL_HPP_DEFINE_STATIC_MEMBER_ CommandQueue CommandQueue::default_; +CL_HPP_DEFINE_STATIC_MEMBER_ cl_int CommandQueue::default_error_ = CL_SUCCESS; + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +enum class DeviceQueueProperties : cl_command_queue_properties +{ + None = 0, + Profiling = CL_QUEUE_PROFILING_ENABLE, +}; + +inline DeviceQueueProperties operator|(DeviceQueueProperties lhs, DeviceQueueProperties rhs) +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +/*! \class DeviceCommandQueue + * \brief DeviceCommandQueue interface for device cl_command_queues. + */ +class DeviceCommandQueue : public detail::Wrapper +{ +public: + + /*! + * Trivial empty constructor to create a null queue. + */ + DeviceCommandQueue() { } + + /*! + * Default construct device command queue on default context and device + */ + DeviceCommandQueue(DeviceQueueProperties properties, cl_int* err = NULL) + { + cl_int error; + cl::Context context = cl::Context::getDefault(); + cl::Device device = cl::Device::getDefault(); + + cl_command_queue_properties mergedProperties = + CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | static_cast(properties); + + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, mergedProperties, 0 }; + object_ = ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! + * Create a device command queue for a specified device in the passed context. + */ + DeviceCommandQueue( + const Context& context, + const Device& device, + DeviceQueueProperties properties = DeviceQueueProperties::None, + cl_int* err = NULL) + { + cl_int error; + + cl_command_queue_properties mergedProperties = + CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | static_cast(properties); + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, mergedProperties, 0 }; + object_ = ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! + * Create a device command queue for a specified device in the passed context. + */ + DeviceCommandQueue( + const Context& context, + const Device& device, + cl_uint queueSize, + DeviceQueueProperties properties = DeviceQueueProperties::None, + cl_int* err = NULL) + { + cl_int error; + + cl_command_queue_properties mergedProperties = + CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | static_cast(properties); + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, mergedProperties, + CL_QUEUE_SIZE, queueSize, + 0 }; + object_ = ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } + } + + /*! \brief Constructor from cl_command_queue - takes ownership. + * + * \param retainObject will cause the constructor to retain its cl object. + * Defaults to false to maintain compatibility with + * earlier versions. + */ + explicit DeviceCommandQueue(const cl_command_queue& commandQueue, bool retainObject = false) : + detail::Wrapper(commandQueue, retainObject) { } + + DeviceCommandQueue& operator = (const cl_command_queue& rhs) + { + detail::Wrapper::operator=(rhs); + return *this; + } + + /*! \brief Copy constructor to forward copy to the superclass correctly. + * Required for MSVC. + */ + DeviceCommandQueue(const DeviceCommandQueue& queue) : detail::Wrapper(queue) {} + + /*! \brief Copy assignment to forward copy to the superclass correctly. + * Required for MSVC. + */ + DeviceCommandQueue& operator = (const DeviceCommandQueue &queue) + { + detail::Wrapper::operator=(queue); + return *this; + } + + /*! \brief Move constructor to forward move to the superclass correctly. + * Required for MSVC. + */ + DeviceCommandQueue(DeviceCommandQueue&& queue) CL_HPP_NOEXCEPT_ : detail::Wrapper(std::move(queue)) {} + + /*! \brief Move assignment to forward move to the superclass correctly. + * Required for MSVC. + */ + DeviceCommandQueue& operator = (DeviceCommandQueue &&queue) + { + detail::Wrapper::operator=(std::move(queue)); + return *this; + } + + template + cl_int getInfo(cl_command_queue_info name, T* param) const + { + return detail::errHandler( + detail::getInfo( + &::clGetCommandQueueInfo, object_, name, param), + __GET_COMMAND_QUEUE_INFO_ERR); + } + + template typename + detail::param_traits::param_type + getInfo(cl_int* err = NULL) const + { + typename detail::param_traits< + detail::cl_command_queue_info, name>::param_type param; + cl_int result = getInfo(name, ¶m); + if (err != NULL) { + *err = result; + } + return param; + } + + /*! + * Create a new default device command queue for the default device, + * in the default context and of the default size. + * If there is already a default queue for the specified device this + * function will return the pre-existing queue. + */ + static DeviceCommandQueue makeDefault( + cl_int *err = nullptr) + { + cl_int error; + cl::Context context = cl::Context::getDefault(); + cl::Device device = cl::Device::getDefault(); + + cl_command_queue_properties properties = + CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT; + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, properties, + 0 }; + DeviceCommandQueue deviceQueue( + ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error)); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } + + return deviceQueue; + } + + /*! + * Create a new default device command queue for the specified device + * and of the default size. + * If there is already a default queue for the specified device this + * function will return the pre-existing queue. + */ + static DeviceCommandQueue makeDefault( + const Context &context, const Device &device, cl_int *err = nullptr) + { + cl_int error; + + cl_command_queue_properties properties = + CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT; + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, properties, + 0 }; + DeviceCommandQueue deviceQueue( + ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error)); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } + + return deviceQueue; + } + + /*! + * Create a new default device command queue for the specified device + * and of the requested size in bytes. + * If there is already a default queue for the specified device this + * function will return the pre-existing queue. + */ + static DeviceCommandQueue makeDefault( + const Context &context, const Device &device, cl_uint queueSize, cl_int *err = nullptr) + { + cl_int error; + + cl_command_queue_properties properties = + CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT; + cl_queue_properties queue_properties[] = { + CL_QUEUE_PROPERTIES, properties, + CL_QUEUE_SIZE, queueSize, + 0 }; + DeviceCommandQueue deviceQueue( + ::clCreateCommandQueueWithProperties( + context(), device(), queue_properties, &error)); + + detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR); + if (err != NULL) { + *err = error; + } + + return deviceQueue; + } +}; // DeviceCommandQueue + +namespace detail +{ + // Specialization for device command queue + template <> + struct KernelArgumentHandler + { + static size_type size(const cl::DeviceCommandQueue&) { return sizeof(cl_command_queue); } + static const cl_command_queue* ptr(const cl::DeviceCommandQueue& value) { return &(value()); } + }; +} // namespace detail + +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + + +template< typename IteratorType > +Buffer::Buffer( + const Context &context, + IteratorType startIterator, + IteratorType endIterator, + bool readOnly, + bool useHostPtr, + cl_int* err) +{ + typedef typename std::iterator_traits::value_type DataType; + cl_int error; + + cl_mem_flags flags = 0; + if( readOnly ) { + flags |= CL_MEM_READ_ONLY; + } + else { + flags |= CL_MEM_READ_WRITE; + } + if( useHostPtr ) { + flags |= CL_MEM_USE_HOST_PTR; + } + + size_type size = sizeof(DataType)*(endIterator - startIterator); + + if( useHostPtr ) { + object_ = ::clCreateBuffer(context(), flags, size, static_cast(&*startIterator), &error); + } else { + object_ = ::clCreateBuffer(context(), flags, size, 0, &error); + } + + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + + if( !useHostPtr ) { + CommandQueue queue(context, 0, &error); + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + + error = cl::copy(queue, startIterator, endIterator, *this); + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } +} + +template< typename IteratorType > +Buffer::Buffer( + const CommandQueue &queue, + IteratorType startIterator, + IteratorType endIterator, + bool readOnly, + bool useHostPtr, + cl_int* err) +{ + typedef typename std::iterator_traits::value_type DataType; + cl_int error; + + cl_mem_flags flags = 0; + if (readOnly) { + flags |= CL_MEM_READ_ONLY; + } + else { + flags |= CL_MEM_READ_WRITE; + } + if (useHostPtr) { + flags |= CL_MEM_USE_HOST_PTR; + } + + size_type size = sizeof(DataType)*(endIterator - startIterator); + + Context context = queue.getInfo(); + + if (useHostPtr) { + object_ = ::clCreateBuffer(context(), flags, size, static_cast(&*startIterator), &error); + } + else { + object_ = ::clCreateBuffer(context(), flags, size, 0, &error); + } + + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + + if (!useHostPtr) { + error = cl::copy(queue, startIterator, endIterator, *this); + detail::errHandler(error, __CREATE_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + } +} + +inline cl_int enqueueReadBuffer( + const Buffer& buffer, + cl_bool blocking, + size_type offset, + size_type size, + void* ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueReadBuffer(buffer, blocking, offset, size, ptr, events, event); +} + +inline cl_int enqueueWriteBuffer( + const Buffer& buffer, + cl_bool blocking, + size_type offset, + size_type size, + const void* ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueWriteBuffer(buffer, blocking, offset, size, ptr, events, event); +} + +inline void* enqueueMapBuffer( + const Buffer& buffer, + cl_bool blocking, + cl_map_flags flags, + size_type offset, + size_type size, + const vector* events = NULL, + Event* event = NULL, + cl_int* err = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + + void * result = ::clEnqueueMapBuffer( + queue(), buffer(), blocking, flags, offset, size, + (events != NULL) ? (cl_uint) events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL, + (cl_event*) event, + &error); + + detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR); + if (err != NULL) { + *err = error; + } + return result; +} + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +/** + * Enqueues to the default queue a command that will allow the host to + * update a region of a coarse-grained SVM buffer. + * This variant takes a raw SVM pointer. + */ +template +inline cl_int enqueueMapSVM( + T* ptr, + cl_bool blocking, + cl_map_flags flags, + size_type size, + const vector* events, + Event* event) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) { + return detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR); + } + + return queue.enqueueMapSVM( + ptr, blocking, flags, size, events, event); +} + +/** + * Enqueues to the default queue a command that will allow the host to + * update a region of a coarse-grained SVM buffer. + * This variant takes a cl::pointer instance. + */ +template +inline cl_int enqueueMapSVM( + cl::pointer ptr, + cl_bool blocking, + cl_map_flags flags, + size_type size, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) { + return detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR); + } + + return queue.enqueueMapSVM( + ptr, blocking, flags, size, events, event); +} + +/** + * Enqueues to the default queue a command that will allow the host to + * update a region of a coarse-grained SVM buffer. + * This variant takes a cl::vector instance. + */ +template +inline cl_int enqueueMapSVM( + cl::vector container, + cl_bool blocking, + cl_map_flags flags, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) { + return detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR); + } + + return queue.enqueueMapSVM( + container, blocking, flags, events, event); +} + +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + +inline cl_int enqueueUnmapMemObject( + const Memory& memory, + void* mapped_ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR); + if (error != CL_SUCCESS) { + return error; + } + + cl_event tmp; + cl_int err = detail::errHandler( + ::clEnqueueUnmapMemObject( + queue(), memory(), mapped_ptr, + (events != NULL) ? (cl_uint)events->size() : 0, + (events != NULL && events->size() > 0) ? (cl_event*)&events->front() : NULL, + (event != NULL) ? &tmp : NULL), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + + if (event != NULL && err == CL_SUCCESS) + *event = tmp; + + return err; +} + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +/** + * Enqueues to the default queue a command that will release a coarse-grained + * SVM buffer back to the OpenCL runtime. + * This variant takes a raw SVM pointer. + */ +template +inline cl_int enqueueUnmapSVM( + T* ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) { + return detail::errHandler(error, __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + } + + return detail::errHandler(queue.enqueueUnmapSVM(ptr, events, event), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + +} + +/** + * Enqueues to the default queue a command that will release a coarse-grained + * SVM buffer back to the OpenCL runtime. + * This variant takes a cl::pointer instance. + */ +template +inline cl_int enqueueUnmapSVM( + cl::pointer &ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) { + return detail::errHandler(error, __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + } + + return detail::errHandler(queue.enqueueUnmapSVM(ptr, events, event), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); +} + +/** + * Enqueues to the default queue a command that will release a coarse-grained + * SVM buffer back to the OpenCL runtime. + * This variant takes a cl::vector instance. + */ +template +inline cl_int enqueueUnmapSVM( + cl::vector &container, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) { + return detail::errHandler(error, __ENQUEUE_UNMAP_MEM_OBJECT_ERR); + } + + return detail::errHandler(queue.enqueueUnmapSVM(container, events, event), + __ENQUEUE_UNMAP_MEM_OBJECT_ERR); +} + +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + +inline cl_int enqueueCopyBuffer( + const Buffer& src, + const Buffer& dst, + size_type src_offset, + size_type dst_offset, + size_type size, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueCopyBuffer(src, dst, src_offset, dst_offset, size, events, event); +} + +/** + * Blocking copy operation between iterators and a buffer. + * Host to Device. + * Uses default command queue. + */ +template< typename IteratorType > +inline cl_int copy( IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer ) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) + return error; + + return cl::copy(queue, startIterator, endIterator, buffer); +} + +/** + * Blocking copy operation between iterators and a buffer. + * Device to Host. + * Uses default command queue. + */ +template< typename IteratorType > +inline cl_int copy( const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator ) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + if (error != CL_SUCCESS) + return error; + + return cl::copy(queue, buffer, startIterator, endIterator); +} + +/** + * Blocking copy operation between iterators and a buffer. + * Host to Device. + * Uses specified queue. + */ +template< typename IteratorType > +inline cl_int copy( const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer ) +{ + typedef typename std::iterator_traits::value_type DataType; + cl_int error; + + size_type length = endIterator-startIterator; + size_type byteLength = length*sizeof(DataType); + + DataType *pointer = + static_cast(queue.enqueueMapBuffer(buffer, CL_TRUE, CL_MAP_WRITE, 0, byteLength, 0, 0, &error)); + // if exceptions enabled, enqueueMapBuffer will throw + if( error != CL_SUCCESS ) { + return error; + } +#if defined(_MSC_VER) + std::copy( + startIterator, + endIterator, + stdext::checked_array_iterator( + pointer, length)); +#else + std::copy(startIterator, endIterator, pointer); +#endif + Event endEvent; + error = queue.enqueueUnmapMemObject(buffer, pointer, 0, &endEvent); + // if exceptions enabled, enqueueUnmapMemObject will throw + if( error != CL_SUCCESS ) { + return error; + } + endEvent.wait(); + return CL_SUCCESS; +} + +/** + * Blocking copy operation between iterators and a buffer. + * Device to Host. + * Uses specified queue. + */ +template< typename IteratorType > +inline cl_int copy( const CommandQueue &queue, const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator ) +{ + typedef typename std::iterator_traits::value_type DataType; + cl_int error; + + size_type length = endIterator-startIterator; + size_type byteLength = length*sizeof(DataType); + + DataType *pointer = + static_cast(queue.enqueueMapBuffer(buffer, CL_TRUE, CL_MAP_READ, 0, byteLength, 0, 0, &error)); + // if exceptions enabled, enqueueMapBuffer will throw + if( error != CL_SUCCESS ) { + return error; + } + std::copy(pointer, pointer + length, startIterator); + Event endEvent; + error = queue.enqueueUnmapMemObject(buffer, pointer, 0, &endEvent); + // if exceptions enabled, enqueueUnmapMemObject will throw + if( error != CL_SUCCESS ) { + return error; + } + endEvent.wait(); + return CL_SUCCESS; +} + + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 +/** + * Blocking SVM map operation - performs a blocking map underneath. + */ +template +inline cl_int mapSVM(cl::vector &container) +{ + return enqueueMapSVM(container, CL_TRUE, CL_MAP_READ | CL_MAP_WRITE); +} + +/** +* Blocking SVM map operation - performs a blocking map underneath. +*/ +template +inline cl_int unmapSVM(cl::vector &container) +{ + return enqueueUnmapSVM(container); +} + +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + +#if CL_HPP_TARGET_OPENCL_VERSION >= 110 +inline cl_int enqueueReadBufferRect( + const Buffer& buffer, + cl_bool blocking, + const array& buffer_offset, + const array& host_offset, + const array& region, + size_type buffer_row_pitch, + size_type buffer_slice_pitch, + size_type host_row_pitch, + size_type host_slice_pitch, + void *ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueReadBufferRect( + buffer, + blocking, + buffer_offset, + host_offset, + region, + buffer_row_pitch, + buffer_slice_pitch, + host_row_pitch, + host_slice_pitch, + ptr, + events, + event); +} + +inline cl_int enqueueWriteBufferRect( + const Buffer& buffer, + cl_bool blocking, + const array& buffer_offset, + const array& host_offset, + const array& region, + size_type buffer_row_pitch, + size_type buffer_slice_pitch, + size_type host_row_pitch, + size_type host_slice_pitch, + const void *ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueWriteBufferRect( + buffer, + blocking, + buffer_offset, + host_offset, + region, + buffer_row_pitch, + buffer_slice_pitch, + host_row_pitch, + host_slice_pitch, + ptr, + events, + event); +} + +inline cl_int enqueueCopyBufferRect( + const Buffer& src, + const Buffer& dst, + const array& src_origin, + const array& dst_origin, + const array& region, + size_type src_row_pitch, + size_type src_slice_pitch, + size_type dst_row_pitch, + size_type dst_slice_pitch, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueCopyBufferRect( + src, + dst, + src_origin, + dst_origin, + region, + src_row_pitch, + src_slice_pitch, + dst_row_pitch, + dst_slice_pitch, + events, + event); +} +#endif // CL_HPP_TARGET_OPENCL_VERSION >= 110 + +inline cl_int enqueueReadImage( + const Image& image, + cl_bool blocking, + const array& origin, + const array& region, + size_type row_pitch, + size_type slice_pitch, + void* ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueReadImage( + image, + blocking, + origin, + region, + row_pitch, + slice_pitch, + ptr, + events, + event); +} + +inline cl_int enqueueWriteImage( + const Image& image, + cl_bool blocking, + const array& origin, + const array& region, + size_type row_pitch, + size_type slice_pitch, + const void* ptr, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueWriteImage( + image, + blocking, + origin, + region, + row_pitch, + slice_pitch, + ptr, + events, + event); +} + +inline cl_int enqueueCopyImage( + const Image& src, + const Image& dst, + const array& src_origin, + const array& dst_origin, + const array& region, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueCopyImage( + src, + dst, + src_origin, + dst_origin, + region, + events, + event); +} + +inline cl_int enqueueCopyImageToBuffer( + const Image& src, + const Buffer& dst, + const array& src_origin, + const array& region, + size_type dst_offset, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueCopyImageToBuffer( + src, + dst, + src_origin, + region, + dst_offset, + events, + event); +} + +inline cl_int enqueueCopyBufferToImage( + const Buffer& src, + const Image& dst, + size_type src_offset, + const array& dst_origin, + const array& region, + const vector* events = NULL, + Event* event = NULL) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.enqueueCopyBufferToImage( + src, + dst, + src_offset, + dst_origin, + region, + events, + event); +} + + +inline cl_int flush(void) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + return queue.flush(); +} + +inline cl_int finish(void) +{ + cl_int error; + CommandQueue queue = CommandQueue::getDefault(&error); + + if (error != CL_SUCCESS) { + return error; + } + + + return queue.finish(); +} + +class EnqueueArgs +{ +private: + CommandQueue queue_; + const NDRange offset_; + const NDRange global_; + const NDRange local_; + vector events_; + + template + friend class KernelFunctor; + +public: + EnqueueArgs(NDRange global) : + queue_(CommandQueue::getDefault()), + offset_(NullRange), + global_(global), + local_(NullRange) + { + + } + + EnqueueArgs(NDRange global, NDRange local) : + queue_(CommandQueue::getDefault()), + offset_(NullRange), + global_(global), + local_(local) + { + + } + + EnqueueArgs(NDRange offset, NDRange global, NDRange local) : + queue_(CommandQueue::getDefault()), + offset_(offset), + global_(global), + local_(local) + { + + } + + EnqueueArgs(Event e, NDRange global) : + queue_(CommandQueue::getDefault()), + offset_(NullRange), + global_(global), + local_(NullRange) + { + events_.push_back(e); + } + + EnqueueArgs(Event e, NDRange global, NDRange local) : + queue_(CommandQueue::getDefault()), + offset_(NullRange), + global_(global), + local_(local) + { + events_.push_back(e); + } + + EnqueueArgs(Event e, NDRange offset, NDRange global, NDRange local) : + queue_(CommandQueue::getDefault()), + offset_(offset), + global_(global), + local_(local) + { + events_.push_back(e); + } + + EnqueueArgs(const vector &events, NDRange global) : + queue_(CommandQueue::getDefault()), + offset_(NullRange), + global_(global), + local_(NullRange), + events_(events) + { + + } + + EnqueueArgs(const vector &events, NDRange global, NDRange local) : + queue_(CommandQueue::getDefault()), + offset_(NullRange), + global_(global), + local_(local), + events_(events) + { + + } + + EnqueueArgs(const vector &events, NDRange offset, NDRange global, NDRange local) : + queue_(CommandQueue::getDefault()), + offset_(offset), + global_(global), + local_(local), + events_(events) + { + + } + + EnqueueArgs(CommandQueue &queue, NDRange global) : + queue_(queue), + offset_(NullRange), + global_(global), + local_(NullRange) + { + + } + + EnqueueArgs(CommandQueue &queue, NDRange global, NDRange local) : + queue_(queue), + offset_(NullRange), + global_(global), + local_(local) + { + + } + + EnqueueArgs(CommandQueue &queue, NDRange offset, NDRange global, NDRange local) : + queue_(queue), + offset_(offset), + global_(global), + local_(local) + { + + } + + EnqueueArgs(CommandQueue &queue, Event e, NDRange global) : + queue_(queue), + offset_(NullRange), + global_(global), + local_(NullRange) + { + events_.push_back(e); + } + + EnqueueArgs(CommandQueue &queue, Event e, NDRange global, NDRange local) : + queue_(queue), + offset_(NullRange), + global_(global), + local_(local) + { + events_.push_back(e); + } + + EnqueueArgs(CommandQueue &queue, Event e, NDRange offset, NDRange global, NDRange local) : + queue_(queue), + offset_(offset), + global_(global), + local_(local) + { + events_.push_back(e); + } + + EnqueueArgs(CommandQueue &queue, const vector &events, NDRange global) : + queue_(queue), + offset_(NullRange), + global_(global), + local_(NullRange), + events_(events) + { + + } + + EnqueueArgs(CommandQueue &queue, const vector &events, NDRange global, NDRange local) : + queue_(queue), + offset_(NullRange), + global_(global), + local_(local), + events_(events) + { + + } + + EnqueueArgs(CommandQueue &queue, const vector &events, NDRange offset, NDRange global, NDRange local) : + queue_(queue), + offset_(offset), + global_(global), + local_(local), + events_(events) + { + + } +}; + + +//---------------------------------------------------------------------------------------------- + + +/** + * Type safe kernel functor. + * + */ +template +class KernelFunctor +{ +private: + Kernel kernel_; + + template + void setArgs(T0&& t0, T1s&&... t1s) + { + kernel_.setArg(index, t0); + setArgs(std::forward(t1s)...); + } + + template + void setArgs(T0&& t0) + { + kernel_.setArg(index, t0); + } + + template + void setArgs() + { + } + + +public: + KernelFunctor(Kernel kernel) : kernel_(kernel) + {} + + KernelFunctor( + const Program& program, + const string name, + cl_int * err = NULL) : + kernel_(program, name.c_str(), err) + {} + + //! \brief Return type of the functor + typedef Event result_type; + + /** + * Enqueue kernel. + * @param args Launch parameters of the kernel. + * @param t0... List of kernel arguments based on the template type of the functor. + */ + Event operator() ( + const EnqueueArgs& args, + Ts... ts) + { + Event event; + setArgs<0>(std::forward(ts)...); + + args.queue_.enqueueNDRangeKernel( + kernel_, + args.offset_, + args.global_, + args.local_, + &args.events_, + &event); + + return event; + } + + /** + * Enqueue kernel with support for error code. + * @param args Launch parameters of the kernel. + * @param t0... List of kernel arguments based on the template type of the functor. + * @param error Out parameter returning the error code from the execution. + */ + Event operator() ( + const EnqueueArgs& args, + Ts... ts, + cl_int &error) + { + Event event; + setArgs<0>(std::forward(ts)...); + + error = args.queue_.enqueueNDRangeKernel( + kernel_, + args.offset_, + args.global_, + args.local_, + &args.events_, + &event); + + return event; + } + +#if CL_HPP_TARGET_OPENCL_VERSION >= 200 + cl_int setSVMPointers(const vector &pointerList) + { + return kernel_.setSVMPointers(pointerList); + } + + template + cl_int setSVMPointers(const T0 &t0, T1s... ts) + { + return kernel_.setSVMPointers(t0, ts...); + } +#endif // #if CL_HPP_TARGET_OPENCL_VERSION >= 200 + + Kernel getKernel() + { + return kernel_; + } +}; + +namespace compatibility { + /** + * Backward compatibility class to ensure that cl.hpp code works with cl2.hpp. + * Please use KernelFunctor directly. + */ + template + struct make_kernel + { + typedef KernelFunctor FunctorType; + + FunctorType functor_; + + make_kernel( + const Program& program, + const string name, + cl_int * err = NULL) : + functor_(FunctorType(program, name, err)) + {} + + make_kernel( + const Kernel kernel) : + functor_(FunctorType(kernel)) + {} + + //! \brief Return type of the functor + typedef Event result_type; + + //! \brief Function signature of kernel functor with no event dependency. + typedef Event type_( + const EnqueueArgs&, + Ts...); + + Event operator()( + const EnqueueArgs& enqueueArgs, + Ts... args) + { + return functor_( + enqueueArgs, args...); + } + }; +} // namespace compatibility + + +//---------------------------------------------------------------------------------------------------------------------- + +#undef CL_HPP_ERR_STR_ +#if !defined(CL_HPP_USER_OVERRIDE_ERROR_STRINGS) +#undef __GET_DEVICE_INFO_ERR +#undef __GET_PLATFORM_INFO_ERR +#undef __GET_DEVICE_IDS_ERR +#undef __GET_CONTEXT_INFO_ERR +#undef __GET_EVENT_INFO_ERR +#undef __GET_EVENT_PROFILE_INFO_ERR +#undef __GET_MEM_OBJECT_INFO_ERR +#undef __GET_IMAGE_INFO_ERR +#undef __GET_SAMPLER_INFO_ERR +#undef __GET_KERNEL_INFO_ERR +#undef __GET_KERNEL_ARG_INFO_ERR +#undef __GET_KERNEL_WORK_GROUP_INFO_ERR +#undef __GET_PROGRAM_INFO_ERR +#undef __GET_PROGRAM_BUILD_INFO_ERR +#undef __GET_COMMAND_QUEUE_INFO_ERR + +#undef __CREATE_CONTEXT_ERR +#undef __CREATE_CONTEXT_FROM_TYPE_ERR +#undef __GET_SUPPORTED_IMAGE_FORMATS_ERR + +#undef __CREATE_BUFFER_ERR +#undef __CREATE_SUBBUFFER_ERR +#undef __CREATE_IMAGE2D_ERR +#undef __CREATE_IMAGE3D_ERR +#undef __CREATE_SAMPLER_ERR +#undef __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR + +#undef __CREATE_USER_EVENT_ERR +#undef __SET_USER_EVENT_STATUS_ERR +#undef __SET_EVENT_CALLBACK_ERR +#undef __SET_PRINTF_CALLBACK_ERR + +#undef __WAIT_FOR_EVENTS_ERR + +#undef __CREATE_KERNEL_ERR +#undef __SET_KERNEL_ARGS_ERR +#undef __CREATE_PROGRAM_WITH_SOURCE_ERR +#undef __CREATE_PROGRAM_WITH_BINARY_ERR +#undef __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR +#undef __BUILD_PROGRAM_ERR +#undef __CREATE_KERNELS_IN_PROGRAM_ERR + +#undef __CREATE_COMMAND_QUEUE_ERR +#undef __SET_COMMAND_QUEUE_PROPERTY_ERR +#undef __ENQUEUE_READ_BUFFER_ERR +#undef __ENQUEUE_WRITE_BUFFER_ERR +#undef __ENQUEUE_READ_BUFFER_RECT_ERR +#undef __ENQUEUE_WRITE_BUFFER_RECT_ERR +#undef __ENQEUE_COPY_BUFFER_ERR +#undef __ENQEUE_COPY_BUFFER_RECT_ERR +#undef __ENQUEUE_READ_IMAGE_ERR +#undef __ENQUEUE_WRITE_IMAGE_ERR +#undef __ENQUEUE_COPY_IMAGE_ERR +#undef __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR +#undef __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR +#undef __ENQUEUE_MAP_BUFFER_ERR +#undef __ENQUEUE_MAP_IMAGE_ERR +#undef __ENQUEUE_UNMAP_MEM_OBJECT_ERR +#undef __ENQUEUE_NDRANGE_KERNEL_ERR +#undef __ENQUEUE_TASK_ERR +#undef __ENQUEUE_NATIVE_KERNEL + +#undef __UNLOAD_COMPILER_ERR +#undef __CREATE_SUB_DEVICES_ERR + +#undef __CREATE_PIPE_ERR +#undef __GET_PIPE_INFO_ERR + +#endif //CL_HPP_USER_OVERRIDE_ERROR_STRINGS + +// Extensions +#undef CL_HPP_INIT_CL_EXT_FCN_PTR_ +#undef CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_ + +#if defined(CL_HPP_USE_CL_DEVICE_FISSION) +#undef CL_HPP_PARAM_NAME_DEVICE_FISSION_ +#endif // CL_HPP_USE_CL_DEVICE_FISSION + +#undef CL_HPP_NOEXCEPT_ +#undef CL_HPP_DEFINE_STATIC_MEMBER_ + +} // namespace cl + +#endif // CL_HPP_ diff --git a/include/Utils.h b/include/Utils.h index 652c644..777a051 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -5,10 +5,14 @@ #include #include +#define CL_HPP_ENABLE_EXCEPTIONS +#define CL_HPP_MINIMUM_OPENCL_VERSION 120 +#define CL_HPP_TARGET_OPENCL_VERSION 120 + #ifdef __APPLE__ -#include +#include #else -#include +#include #endif using namespace std; @@ -124,7 +128,7 @@ void AddSources(cl::Program::Sources& sources, const string& file_name) { //TODO: add file existence check ifstream file(file_name); string* source_code = new string(istreambuf_iterator(file), (istreambuf_iterator())); - sources.push_back(make_pair((*source_code).c_str(), source_code->length() + 1)); + sources.push_back((*source_code).c_str()); } string ListPlatformsDevices() { From ebae00b1facfa5ce0bbf71f244704eeca86efe78 Mon Sep 17 00:00:00 2001 From: Grzegorz Cielniak Date: Sat, 9 Feb 2019 18:02:48 +0000 Subject: [PATCH 09/69] Tutorial 4 and CL2 fix --- Tutorial 4/src/Tutorial 4.cpp | 2 +- include/Utils.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Tutorial 4/src/Tutorial 4.cpp b/Tutorial 4/src/Tutorial 4.cpp index 8f6caa2..b17efcc 100644 --- a/Tutorial 4/src/Tutorial 4.cpp +++ b/Tutorial 4/src/Tutorial 4.cpp @@ -1,8 +1,8 @@ +#include "Utils.h" #include #include #include #include -#include "Utils.h" namespace compute = boost::compute; using namespace std; diff --git a/include/Utils.h b/include/Utils.h index 777a051..0666c42 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -5,9 +5,10 @@ #include #include -#define CL_HPP_ENABLE_EXCEPTIONS +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS #define CL_HPP_MINIMUM_OPENCL_VERSION 120 #define CL_HPP_TARGET_OPENCL_VERSION 120 +#define CL_HPP_ENABLE_EXCEPTIONS #ifdef __APPLE__ #include From 4d5ebae5a5a58a3e9168319e902773db60a2ef27 Mon Sep 17 00:00:00 2001 From: Grzegorz Cielniak Date: Sat, 9 Feb 2019 18:30:06 +0000 Subject: [PATCH 10/69] macOS CL2 dependency fixed --- include/Utils.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/Utils.h b/include/Utils.h index 0666c42..80ec30a 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -10,11 +10,7 @@ #define CL_HPP_TARGET_OPENCL_VERSION 120 #define CL_HPP_ENABLE_EXCEPTIONS -#ifdef __APPLE__ -#include -#else #include -#endif using namespace std; From 5c3d9b2fa6fe85a5dcbef04baa680ef3cd927806 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Tue, 19 Feb 2019 17:39:44 +0000 Subject: [PATCH 11/69] T3: hs_scan --- Tutorial 3/src/kernels/my_kernels.cl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tutorial 3/src/kernels/my_kernels.cl b/Tutorial 3/src/kernels/my_kernels.cl index 50d54bf..36bceff 100644 --- a/Tutorial 3/src/kernels/my_kernels.cl +++ b/Tutorial 3/src/kernels/my_kernels.cl @@ -136,6 +136,23 @@ kernel void scan_add(__global const int* A, global int* B, local int* scratch_1, B[id] = scratch_1[lid]; } +//requires additional buffer B to avoid data overwrite +kernel void scan_hs(global int* A, global int* B) { + int id = get_global_id(0); + int N = get_global_size(0); + global int* C; + + for (int stride=1; stride= stride) + B[id] += A[id-stride]; + + barrier(CLK_GLOBAL_MEM_FENCE); //sync the step + + C = A; A = B; B = A; //swap A & B between steps + } +} + //calculates the block sums kernel void block_sum(global const int* A, global int* B, int local_size) { int id = get_global_id(0); From f9acaf1bfd1f0e88165fc0205b256f8a0ceecff7 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Tue, 19 Feb 2019 22:20:42 +0000 Subject: [PATCH 12/69] T3: Blelloch scan --- Tutorial 3/src/kernels/my_kernels.cl | 62 +++++++++++++++++++++------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/Tutorial 3/src/kernels/my_kernels.cl b/Tutorial 3/src/kernels/my_kernels.cl index 36bceff..1e8a9b0 100644 --- a/Tutorial 3/src/kernels/my_kernels.cl +++ b/Tutorial 3/src/kernels/my_kernels.cl @@ -105,6 +105,24 @@ kernel void hist_simple(global const int* A, global int* H) { atomic_inc(&H[bin_index]);//serial operation, not very efficient! } +//Hillis-Steele basic inclusive scan +//requires additional buffer B to avoid data overwrite +kernel void scan_hs(global int* A, global int* B) { + int id = get_global_id(0); + int N = get_global_size(0); + global int* C; + + for (int stride = 1; stride < N; stride *= 2) { + B[id] = A[id]; + if (id >= stride) + B[id] += A[id - stride]; + + barrier(CLK_GLOBAL_MEM_FENCE); //sync the step + + C = A; A = B; B = A; //swap A & B between steps + } +} + //a double-buffered version of the Hillis-Steele inclusive scan //requires two additional input arguments which correspond to two local buffers kernel void scan_add(__global const int* A, global int* B, local int* scratch_1, local int* scratch_2) { @@ -136,21 +154,35 @@ kernel void scan_add(__global const int* A, global int* B, local int* scratch_1, B[id] = scratch_1[lid]; } -//requires additional buffer B to avoid data overwrite -kernel void scan_hs(global int* A, global int* B) { - int id = get_global_id(0); - int N = get_global_size(0); - global int* C; - - for (int stride=1; stride= stride) - B[id] += A[id-stride]; - - barrier(CLK_GLOBAL_MEM_FENCE); //sync the step - - C = A; A = B; B = A; //swap A & B between steps - } +//Blelloch basic inclusive scan +kernel void scan_bl(global int* A) { + int id = get_global_id(0); + int N = get_global_size(0); + int t; + + //up-sweep + for (int stride = 1; stride < N; stride *= 2) { + if (((id + 1) % (stride*2)) == 0) + A[id] += A[id - stride]; + + barrier(CLK_GLOBAL_MEM_FENCE); //sync the step + } + + //down-sweep + if (id == 0) + A[N-1] = A[0];//or 0 for exclusive scan + + barrier(CLK_GLOBAL_MEM_FENCE); //sync the step + + for (int stride = N/2; stride > 0; stride /= 2) { + if (((id + 1) % (stride*2)) == 0) { + t = A[id]; + A[id] += A[id - stride]; //reduce + A[id - stride] = t; //move + } + + barrier(CLK_GLOBAL_MEM_FENCE); //sync the step + } } //calculates the block sums From bcb12bf1b3c820bdd41126dc3adb4d186ff14caa Mon Sep 17 00:00:00 2001 From: gcielniak Date: Wed, 20 Feb 2019 09:46:46 +0000 Subject: [PATCH 13/69] T3: Blelloch fix --- Tutorial 3/src/kernels/my_kernels.cl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tutorial 3/src/kernels/my_kernels.cl b/Tutorial 3/src/kernels/my_kernels.cl index 1e8a9b0..d303520 100644 --- a/Tutorial 3/src/kernels/my_kernels.cl +++ b/Tutorial 3/src/kernels/my_kernels.cl @@ -154,7 +154,7 @@ kernel void scan_add(__global const int* A, global int* B, local int* scratch_1, B[id] = scratch_1[lid]; } -//Blelloch basic inclusive scan +//Blelloch basic exclusive scan kernel void scan_bl(global int* A) { int id = get_global_id(0); int N = get_global_size(0); @@ -170,7 +170,7 @@ kernel void scan_bl(global int* A) { //down-sweep if (id == 0) - A[N-1] = A[0];//or 0 for exclusive scan + A[N-1] = 0;//exclusive scan barrier(CLK_GLOBAL_MEM_FENCE); //sync the step From 27c99953f2b04007381b2d97cb8e885b83367f44 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Wed, 20 Feb 2019 12:12:11 +0000 Subject: [PATCH 14/69] T3: scan_hs fix --- Tutorial 3/src/kernels/my_kernels.cl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tutorial 3/src/kernels/my_kernels.cl b/Tutorial 3/src/kernels/my_kernels.cl index d303520..06293b1 100644 --- a/Tutorial 3/src/kernels/my_kernels.cl +++ b/Tutorial 3/src/kernels/my_kernels.cl @@ -119,7 +119,7 @@ kernel void scan_hs(global int* A, global int* B) { barrier(CLK_GLOBAL_MEM_FENCE); //sync the step - C = A; A = B; B = A; //swap A & B between steps + C = A; A = B; B = C; //swap A & B between steps } } From cdc733bdf5a212969af9625e0d5a4e31c4f3f9b2 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Mon, 6 Jan 2020 16:55:42 +0000 Subject: [PATCH 15/69] Create README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce4e551 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Dependencies + +## Ubuntu + - OpenCL: `sudo apt install ocl-icd-opencl-dev` From 1d98c15e49e5d348b7b202f63334663ef1bd4ef3 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Tue, 7 Jan 2020 12:47:01 +0000 Subject: [PATCH 16/69] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ce4e551..9f8f665 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ # Dependencies ## Ubuntu - - OpenCL: `sudo apt install ocl-icd-opencl-dev` + - OpenCL development libs: `sudo apt install ocl-icd-opencl-dev` + - OpenCL runtime drivers + - Boost.Compute: from boost-1.61.0, it is part of the library. In particular situations, when you need to keep the older versions of boost (e.g. Ubuntu 16 + ROS Kinetic) you may need to apply the following ugly hack: + ``` + cd /tmp/ + git clone git://github.com/kylelutz/compute.git + sudo mv /tmp/compute/include/boost/compute /usr/include/boost + sudo mv /tmp/compute/include/boost/compute.hpp /usr/include/boost/ +``` From 4f917c080a3d8dd58740d2497abe1d52f87d6cf7 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 13 Jan 2020 15:33:30 +0000 Subject: [PATCH 17/69] Renamed directories and added empty CMake --- CMakeLists.txt | 2 ++ {Tutorial 1 => tutorial_1}/Tutorial 1.cpp | 0 {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj | 0 {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj.filters | 0 {Tutorial 1 => tutorial_1}/kernels/my_kernels.cl | 0 {Tutorial 2 => tutorial_2}/Tutorial 2.cpp | 0 {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj | 0 {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj.filters | 0 {Tutorial 2 => tutorial_2}/kernels/my_kernels.cl | 0 {Tutorial 2 => tutorial_2}/test.ppm | 0 {Tutorial 2 => tutorial_2}/test_large.ppm | 0 {Tutorial 3 => tutorial_3}/Tutorial 3.cpp | 0 {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj | 0 {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj.filters | 0 {Tutorial 3 => tutorial_3}/kernels/my_kernels.cl | 0 15 files changed, 2 insertions(+) create mode 100644 CMakeLists.txt rename {Tutorial 1 => tutorial_1}/Tutorial 1.cpp (100%) rename {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj (100%) rename {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj.filters (100%) rename {Tutorial 1 => tutorial_1}/kernels/my_kernels.cl (100%) rename {Tutorial 2 => tutorial_2}/Tutorial 2.cpp (100%) rename {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj (100%) rename {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj.filters (100%) rename {Tutorial 2 => tutorial_2}/kernels/my_kernels.cl (100%) rename {Tutorial 2 => tutorial_2}/test.ppm (100%) rename {Tutorial 2 => tutorial_2}/test_large.ppm (100%) rename {Tutorial 3 => tutorial_3}/Tutorial 3.cpp (100%) rename {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj (100%) rename {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj.filters (100%) rename {Tutorial 3 => tutorial_3}/kernels/my_kernels.cl (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..95ef3e6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,2 @@ +find_package(OpenCL REQUIRED) +# add_executable() diff --git a/Tutorial 1/Tutorial 1.cpp b/tutorial_1/Tutorial 1.cpp similarity index 100% rename from Tutorial 1/Tutorial 1.cpp rename to tutorial_1/Tutorial 1.cpp diff --git a/Tutorial 1/Tutorial 1.vcxproj b/tutorial_1/Tutorial 1.vcxproj similarity index 100% rename from Tutorial 1/Tutorial 1.vcxproj rename to tutorial_1/Tutorial 1.vcxproj diff --git a/Tutorial 1/Tutorial 1.vcxproj.filters b/tutorial_1/Tutorial 1.vcxproj.filters similarity index 100% rename from Tutorial 1/Tutorial 1.vcxproj.filters rename to tutorial_1/Tutorial 1.vcxproj.filters diff --git a/Tutorial 1/kernels/my_kernels.cl b/tutorial_1/kernels/my_kernels.cl similarity index 100% rename from Tutorial 1/kernels/my_kernels.cl rename to tutorial_1/kernels/my_kernels.cl diff --git a/Tutorial 2/Tutorial 2.cpp b/tutorial_2/Tutorial 2.cpp similarity index 100% rename from Tutorial 2/Tutorial 2.cpp rename to tutorial_2/Tutorial 2.cpp diff --git a/Tutorial 2/Tutorial 2.vcxproj b/tutorial_2/Tutorial 2.vcxproj similarity index 100% rename from Tutorial 2/Tutorial 2.vcxproj rename to tutorial_2/Tutorial 2.vcxproj diff --git a/Tutorial 2/Tutorial 2.vcxproj.filters b/tutorial_2/Tutorial 2.vcxproj.filters similarity index 100% rename from Tutorial 2/Tutorial 2.vcxproj.filters rename to tutorial_2/Tutorial 2.vcxproj.filters diff --git a/Tutorial 2/kernels/my_kernels.cl b/tutorial_2/kernels/my_kernels.cl similarity index 100% rename from Tutorial 2/kernels/my_kernels.cl rename to tutorial_2/kernels/my_kernels.cl diff --git a/Tutorial 2/test.ppm b/tutorial_2/test.ppm similarity index 100% rename from Tutorial 2/test.ppm rename to tutorial_2/test.ppm diff --git a/Tutorial 2/test_large.ppm b/tutorial_2/test_large.ppm similarity index 100% rename from Tutorial 2/test_large.ppm rename to tutorial_2/test_large.ppm diff --git a/Tutorial 3/Tutorial 3.cpp b/tutorial_3/Tutorial 3.cpp similarity index 100% rename from Tutorial 3/Tutorial 3.cpp rename to tutorial_3/Tutorial 3.cpp diff --git a/Tutorial 3/Tutorial 3.vcxproj b/tutorial_3/Tutorial 3.vcxproj similarity index 100% rename from Tutorial 3/Tutorial 3.vcxproj rename to tutorial_3/Tutorial 3.vcxproj diff --git a/Tutorial 3/Tutorial 3.vcxproj.filters b/tutorial_3/Tutorial 3.vcxproj.filters similarity index 100% rename from Tutorial 3/Tutorial 3.vcxproj.filters rename to tutorial_3/Tutorial 3.vcxproj.filters diff --git a/Tutorial 3/kernels/my_kernels.cl b/tutorial_3/kernels/my_kernels.cl similarity index 100% rename from Tutorial 3/kernels/my_kernels.cl rename to tutorial_3/kernels/my_kernels.cl From b3309102152e3325bd5fea6c9035e9091ae8e574 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 21 Jan 2020 10:32:31 +0000 Subject: [PATCH 18/69] Renamed tut 4 dir --- {Tutorial 4 => tutorial_4}/Tutorial 4.cpp | 0 {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj | 0 {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj.filters | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {Tutorial 4 => tutorial_4}/Tutorial 4.cpp (100%) rename {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj (100%) rename {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj.filters (100%) diff --git a/Tutorial 4/Tutorial 4.cpp b/tutorial_4/Tutorial 4.cpp similarity index 100% rename from Tutorial 4/Tutorial 4.cpp rename to tutorial_4/Tutorial 4.cpp diff --git a/Tutorial 4/Tutorial 4.vcxproj b/tutorial_4/Tutorial 4.vcxproj similarity index 100% rename from Tutorial 4/Tutorial 4.vcxproj rename to tutorial_4/Tutorial 4.vcxproj diff --git a/Tutorial 4/Tutorial 4.vcxproj.filters b/tutorial_4/Tutorial 4.vcxproj.filters similarity index 100% rename from Tutorial 4/Tutorial 4.vcxproj.filters rename to tutorial_4/Tutorial 4.vcxproj.filters From 8685ad9c7db08eebfa480b589566ec96170cf197 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 21 Jan 2020 10:44:53 +0000 Subject: [PATCH 19/69] Added basic opencl files --- tutorial_1/CMakeLists.txt | 3 +++ tutorial_1/main.cpp | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 tutorial_1/CMakeLists.txt create mode 100644 tutorial_1/main.cpp diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt new file mode 100644 index 0000000..d4a7700 --- /dev/null +++ b/tutorial_1/CMakeLists.txt @@ -0,0 +1,3 @@ +find_package(OpenCL REQUIRED) +add_executable(test_tgt main.cpp) +target_link_libraries(test_tgt OpenCL::OpenCL) diff --git a/tutorial_1/main.cpp b/tutorial_1/main.cpp new file mode 100644 index 0000000..78cab2e --- /dev/null +++ b/tutorial_1/main.cpp @@ -0,0 +1,9 @@ +#include +#include +#include + +int main(int argc, char **argv) { + + std::cout << "Hello someone, SAVE ME!" << std::endl; + return 0; +} From 7754ba0cad574daa0267f140364bc354b559e7e0 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 21 Jan 2020 10:46:42 +0000 Subject: [PATCH 20/69] Removing these intermediary files --- tutorial_1/Tutorial 1.vcxproj | 194 -------------------------- tutorial_1/Tutorial 1.vcxproj.filters | 24 ---- 2 files changed, 218 deletions(-) delete mode 100644 tutorial_1/Tutorial 1.vcxproj delete mode 100644 tutorial_1/Tutorial 1.vcxproj.filters diff --git a/tutorial_1/Tutorial 1.vcxproj b/tutorial_1/Tutorial 1.vcxproj deleted file mode 100644 index 7f2d077..0000000 --- a/tutorial_1/Tutorial 1.vcxproj +++ /dev/null @@ -1,194 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - Debug - x64 - - - Release - x64 - - - - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D} - Tutorial 1 - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - Unicode - true - - - Application - true - v142 - Unicode - - - Application - false - v142 - Unicode - true - - - - - - - - - - - - - - - - - false - - - true - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - - 0 - - - $(INTELOCLSDKROOT)include;%(AdditionalIncludeDirectories) - Win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x86;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - If exist "*.cl" copy "*.cl" "$(OutDir)\" - - - - - 0 - - - $(INTELOCLSDKROOT)include;%(AdditionalIncludeDirectories) - Win32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x86;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - If exist "*.cl" copy "*.cl" "$(OutDir)\" - - - - - 0 - - - $(INTELOCLSDKROOT)include;..\include;%(AdditionalIncludeDirectories) - __x86_64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MaxSpeed - false - Default - MultiThreadedDLL - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - true - true - - - If exist "*.cl" copy "*.cl" "$(OutDir)\" - - - - - 0 - - - $(INTELOCLSDKROOT)include;..\include;%(AdditionalIncludeDirectories) - __x86_64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - Disabled - false - EnableFastChecks - MultiThreadedDebugDLL - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - xcopy /s /i /y "kernels" "$(OutDir)kernels" - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tutorial_1/Tutorial 1.vcxproj.filters b/tutorial_1/Tutorial 1.vcxproj.filters deleted file mode 100644 index 724c351..0000000 --- a/tutorial_1/Tutorial 1.vcxproj.filters +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - {11e52fc2-3f25-4b94-920c-911c1673bf6c} - - - {5586229f-c9c4-4574-89bd-30ba09061ac6} - - - - - kernels - - - - - include - - - \ No newline at end of file From d5dc6fcccc1149c8fba755e6209ff21973676568 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 21 Jan 2020 11:04:15 +0000 Subject: [PATCH 21/69] Changed to whitelist .gitignore --- .gitignore | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 230a2ff..9db1534 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,3 @@ -#CMake -build - -#Visual Studio -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/* -x86/* -bld/ -[Oo]bj/ -.vs -desktop.ini - -*.opensdf -*.sdf -*.suo -*.user - -!x64/glut32.dll -!x86/glut32.dll - - +# Ignore everything that isnt already tracked +# please use "git add -f foo.txt" to force into tracking. +/* From 220574811d42c56154d8f7adc1d04037474a62f6 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 21 Jan 2020 12:15:55 +0000 Subject: [PATCH 22/69] Changed name and minor compiler guard --- tutorial_1/CMakeLists.txt | 4 ++-- tutorial_1/main.cpp | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt index d4a7700..952bcac 100644 --- a/tutorial_1/CMakeLists.txt +++ b/tutorial_1/CMakeLists.txt @@ -1,3 +1,3 @@ find_package(OpenCL REQUIRED) -add_executable(test_tgt main.cpp) -target_link_libraries(test_tgt OpenCL::OpenCL) +add_executable(tut1 main.cpp) +target_link_libraries(tut1 OpenCL::OpenCL) diff --git a/tutorial_1/main.cpp b/tutorial_1/main.cpp index 78cab2e..7f69d45 100644 --- a/tutorial_1/main.cpp +++ b/tutorial_1/main.cpp @@ -1,9 +1,18 @@ +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define __CL_ENABLE_EXCEPTIONS + #include #include -#include + +// Include guarded openCL libs +#ifdef __APPLE__ + #include +#else + #include +#endif int main(int argc, char **argv) { - std::cout << "Hello someone, SAVE ME!" << std::endl; + std::cout << "Hello" << std::endl; return 0; } From 79e71c36dc71e74356ac7481ddfcddef190e8394 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Sun, 26 Jan 2020 23:56:26 +0000 Subject: [PATCH 23/69] Added cmake version and title --- tutorial_1/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt index 952bcac..ce5d32a 100644 --- a/tutorial_1/CMakeLists.txt +++ b/tutorial_1/CMakeLists.txt @@ -1,3 +1,6 @@ +cmake_minimum_required(VERSION 3.7) +project(opencl_tutorial_1) + find_package(OpenCL REQUIRED) add_executable(tut1 main.cpp) target_link_libraries(tut1 OpenCL::OpenCL) From d69d0f19912d74e4411d2803cddbe62722952f25 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 00:03:45 +0000 Subject: [PATCH 24/69] Added automated builder for speed This short bash file should build up everything and tear everything down allowing us to test faster. --- tutorial_1/build.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 tutorial_1/build.sh diff --git a/tutorial_1/build.sh b/tutorial_1/build.sh new file mode 100755 index 0000000..f41ebc1 --- /dev/null +++ b/tutorial_1/build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +mkdir build +cd build/ +cmake .. +make +./tut1 +cd .. +rm -r build/ From 5cb6d3a2c0ce8c74ed89673f117cebee44d9d43d Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 00:39:40 +0000 Subject: [PATCH 25/69] Added some helpful vars to CMAKE --- tutorial_1/CMakeLists.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt index ce5d32a..813f048 100644 --- a/tutorial_1/CMakeLists.txt +++ b/tutorial_1/CMakeLists.txt @@ -1,6 +1,16 @@ +# sets versions and names of project cmake_minimum_required(VERSION 3.7) project(opencl_tutorial_1) +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) +# specify our compiled binaries name +set(BINARY_NAME tut1) +# specify all our cpp files names +set(CPP_FILES main.cpp) + +# build our project executable, and link opencl lib find_package(OpenCL REQUIRED) -add_executable(tut1 main.cpp) +add_executable(${BINARY_NAME} ${CPP_FILES}) target_link_libraries(tut1 OpenCL::OpenCL) From 1e64f0b6a18779c6a1acf6feca8c862541476a9e Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 11:10:46 +0000 Subject: [PATCH 26/69] Updated tut1 build no fail on exist dir --- tutorial_1/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial_1/build.sh b/tutorial_1/build.sh index f41ebc1..13fe4fb 100755 --- a/tutorial_1/build.sh +++ b/tutorial_1/build.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -mkdir build +mkdir -p build cd build/ cmake .. make From 750ee4060ec5d90e112722fb4a89a737cef57a18 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 11:22:11 +0000 Subject: [PATCH 27/69] Renamed tutorial file to be short and no space --- tutorial_1/{Tutorial 1.cpp => tut1.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tutorial_1/{Tutorial 1.cpp => tut1.cpp} (100%) diff --git a/tutorial_1/Tutorial 1.cpp b/tutorial_1/tut1.cpp similarity index 100% rename from tutorial_1/Tutorial 1.cpp rename to tutorial_1/tut1.cpp From 1e403de8033b3a101d3e361e573f35a3480fd35e Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 11:22:56 +0000 Subject: [PATCH 28/69] Adding local version of utils for testing This will be eventually removed back to a seperate directory but for now while I am testing I need it in the same directory --- tutorial_1/Utils.h | 225 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 tutorial_1/Utils.h diff --git a/tutorial_1/Utils.h b/tutorial_1/Utils.h new file mode 100644 index 0000000..80ec30a --- /dev/null +++ b/tutorial_1/Utils.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define CL_HPP_MINIMUM_OPENCL_VERSION 120 +#define CL_HPP_TARGET_OPENCL_VERSION 120 +#define CL_HPP_ENABLE_EXCEPTIONS + +#include + +using namespace std; + +template +ostream& operator<< (ostream& out, const vector& v) { + if (!v.empty()) { + out << '['; + copy(v.begin(), v.end(), ostream_iterator(out, ", ")); + out << "\b\b]"; + } + return out; +} + +string GetPlatformName(int platform_id) { + vector platforms; + cl::Platform::get(&platforms); + return platforms[platform_id].getInfo(); +} + +string GetDeviceName(int platform_id, int device_id) { + vector platforms; + cl::Platform::get(&platforms); + vector devices; + platforms[platform_id].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + return devices[device_id].getInfo(); +} + +const char *getErrorString(cl_int error) { + switch (error){ + // run-time and JIT compiler errors + case 0: return "CL_SUCCESS"; + case -1: return "CL_DEVICE_NOT_FOUND"; + case -2: return "CL_DEVICE_NOT_AVAILABLE"; + case -3: return "CL_COMPILER_NOT_AVAILABLE"; + case -4: return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; + case -5: return "CL_OUT_OF_RESOURCES"; + case -6: return "CL_OUT_OF_HOST_MEMORY"; + case -7: return "CL_PROFILING_INFO_NOT_AVAILABLE"; + case -8: return "CL_MEM_COPY_OVERLAP"; + case -9: return "CL_IMAGE_FORMAT_MISMATCH"; + case -10: return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; + case -11: return "CL_BUILD_PROGRAM_FAILURE"; + case -12: return "CL_MAP_FAILURE"; + case -13: return "CL_MISALIGNED_SUB_BUFFER_OFFSET"; + case -14: return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST"; + case -15: return "CL_COMPILE_PROGRAM_FAILURE"; + case -16: return "CL_LINKER_NOT_AVAILABLE"; + case -17: return "CL_LINK_PROGRAM_FAILURE"; + case -18: return "CL_DEVICE_PARTITION_FAILED"; + case -19: return "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; + + // compile-time errors + case -30: return "CL_INVALID_VALUE"; + case -31: return "CL_INVALID_DEVICE_TYPE"; + case -32: return "CL_INVALID_PLATFORM"; + case -33: return "CL_INVALID_DEVICE"; + case -34: return "CL_INVALID_CONTEXT"; + case -35: return "CL_INVALID_QUEUE_PROPERTIES"; + case -36: return "CL_INVALID_COMMAND_QUEUE"; + case -37: return "CL_INVALID_HOST_PTR"; + case -38: return "CL_INVALID_MEM_OBJECT"; + case -39: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; + case -40: return "CL_INVALID_IMAGE_SIZE"; + case -41: return "CL_INVALID_SAMPLER"; + case -42: return "CL_INVALID_BINARY"; + case -43: return "CL_INVALID_BUILD_OPTIONS"; + case -44: return "CL_INVALID_PROGRAM"; + case -45: return "CL_INVALID_PROGRAM_EXECUTABLE"; + case -46: return "CL_INVALID_KERNEL_NAME"; + case -47: return "CL_INVALID_KERNEL_DEFINITION"; + case -48: return "CL_INVALID_KERNEL"; + case -49: return "CL_INVALID_ARG_INDEX"; + case -50: return "CL_INVALID_ARG_VALUE"; + case -51: return "CL_INVALID_ARG_SIZE"; + case -52: return "CL_INVALID_KERNEL_ARGS"; + case -53: return "CL_INVALID_WORK_DIMENSION"; + case -54: return "CL_INVALID_WORK_GROUP_SIZE"; + case -55: return "CL_INVALID_WORK_ITEM_SIZE"; + case -56: return "CL_INVALID_GLOBAL_OFFSET"; + case -57: return "CL_INVALID_EVENT_WAIT_LIST"; + case -58: return "CL_INVALID_EVENT"; + case -59: return "CL_INVALID_OPERATION"; + case -60: return "CL_INVALID_GL_OBJECT"; + case -61: return "CL_INVALID_BUFFER_SIZE"; + case -62: return "CL_INVALID_MIP_LEVEL"; + case -63: return "CL_INVALID_GLOBAL_WORK_SIZE"; + case -64: return "CL_INVALID_PROPERTY"; + case -65: return "CL_INVALID_IMAGE_DESCRIPTOR"; + case -66: return "CL_INVALID_COMPILER_OPTIONS"; + case -67: return "CL_INVALID_LINKER_OPTIONS"; + case -68: return "CL_INVALID_DEVICE_PARTITION_COUNT"; + + // extension errors + case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; + case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; + case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; + case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; + case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; + case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; + default: return "Unknown OpenCL error"; + } +} + +void CheckError(cl_int error) { + if (error != CL_SUCCESS) { + cerr << "OpenCL call failed with error " << getErrorString(error) << endl; + exit(1); + } +} + +void AddSources(cl::Program::Sources& sources, const string& file_name) { + //TODO: add file existence check + ifstream file(file_name); + string* source_code = new string(istreambuf_iterator(file), (istreambuf_iterator())); + sources.push_back((*source_code).c_str()); +} + +string ListPlatformsDevices() { + + stringstream sstream; + vector platforms; + + cl::Platform::get(&platforms); + + sstream << "Found " << platforms.size() << " platform(s):" << endl; + + for (unsigned int i = 0; i < platforms.size(); i++) + { + sstream << "\nPlatform " << i << ", " << platforms[i].getInfo() << ", version: " << platforms[i].getInfo(); + + sstream << ", vendor: " << platforms[i].getInfo() << endl; + // sstream << ", extensions: " << platforms[i].getInfo() << endl; + + vector devices; + + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + sstream << "\n Found " << devices.size() << " device(s):" << endl; + + for (unsigned int j = 0; j < devices.size(); j++) + { + sstream << "\n Device " << j << ", " << devices[j].getInfo() << ", version: " << devices[j].getInfo(); + + sstream << ", vendor: " << devices[j].getInfo(); + cl_device_type device_type = devices[j].getInfo(); + sstream << ", type: "; + if (device_type & CL_DEVICE_TYPE_DEFAULT) + sstream << "DEFAULT "; + if (device_type & CL_DEVICE_TYPE_CPU) + sstream << "CPU "; + if (device_type & CL_DEVICE_TYPE_GPU) + sstream << "GPU "; + if (device_type & CL_DEVICE_TYPE_ACCELERATOR) + sstream << "ACCELERATOR "; + sstream << ", compute units: " << devices[j].getInfo(); + sstream << ", clock freq [MHz]: " << devices[j].getInfo(); + sstream << ", max memory size [B]: " << devices[j].getInfo(); + sstream << ", max allocatable memory [B]: " << devices[j].getInfo(); + + sstream << endl; + } + } + sstream << "----------------------------------------------------------------" << endl; + + return sstream.str(); +} + +cl::Context GetContext(int platform_id, int device_id) { + vector platforms; + + cl::Platform::get(&platforms); + + for (unsigned int i = 0; i < platforms.size(); i++) + { + vector devices; + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + for (unsigned int j = 0; j < devices.size(); j++) + { + if ((i == platform_id) && (j == device_id)) + return cl::Context({ devices[j] }); + } + } + + return cl::Context(); +} + +enum ProfilingResolution { + PROF_NS = 1, + PROF_US = 1000, + PROF_MS = 1000000, + PROF_S = 1000000000 +}; + +string GetFullProfilingInfo(const cl::Event& evnt, ProfilingResolution resolution) { + stringstream sstream; + + sstream << "Queued " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Submitted " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Executed " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Total " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + + switch (resolution) { + case PROF_NS: sstream << " [ns]"; break; + case PROF_US: sstream << " [us]"; break; + case PROF_MS: sstream << " [ms]"; break; + case PROF_S: sstream << " [s]"; break; + default: break; + } + + return sstream.str(); +} \ No newline at end of file From cf06b48fbe4901d02838ad3c4837c58a93856dd8 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 12:48:53 +0000 Subject: [PATCH 29/69] Changing entry point to tutorial file --- tutorial_1/CMakeLists.txt | 2 +- tutorial_1/Utils.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt index 813f048..d889d83 100644 --- a/tutorial_1/CMakeLists.txt +++ b/tutorial_1/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # specify our compiled binaries name set(BINARY_NAME tut1) # specify all our cpp files names -set(CPP_FILES main.cpp) +set(CPP_FILES tut1.cpp) # build our project executable, and link opencl lib find_package(OpenCL REQUIRED) diff --git a/tutorial_1/Utils.h b/tutorial_1/Utils.h index 80ec30a..1539094 100644 --- a/tutorial_1/Utils.h +++ b/tutorial_1/Utils.h @@ -222,4 +222,4 @@ string GetFullProfilingInfo(const cl::Event& evnt, ProfilingResolution resolutio } return sstream.str(); -} \ No newline at end of file +} From fbfe96f0b48d4d6b9ebf0cedb9ad40242440da80 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 13:37:20 +0000 Subject: [PATCH 30/69] Added checking for opencl version 2 --- tutorial_1/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt index d889d83..c88d852 100644 --- a/tutorial_1/CMakeLists.txt +++ b/tutorial_1/CMakeLists.txt @@ -11,6 +11,6 @@ set(BINARY_NAME tut1) set(CPP_FILES tut1.cpp) # build our project executable, and link opencl lib -find_package(OpenCL REQUIRED) +find_package(OpenCL 2 REQUIRED) add_executable(${BINARY_NAME} ${CPP_FILES}) target_link_libraries(tut1 OpenCL::OpenCL) From 5fb8c0fca9cc9607337a139c4986418d2c01cf28 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 27 Jan 2020 13:44:38 +0000 Subject: [PATCH 31/69] Updated cmake build.sh to clean before build This means we can now inspect thefiles after building as they arent instantly gone, and we can clean up in case something existed there before --- tutorial_1/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial_1/build.sh b/tutorial_1/build.sh index 13fe4fb..b9f66b3 100755 --- a/tutorial_1/build.sh +++ b/tutorial_1/build.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash +rm -r build/ mkdir -p build cd build/ cmake .. make ./tut1 cd .. -rm -r build/ From efce3cdc2d5d0a02a4fb7269e40ebcd0ab04440c Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 28 Jan 2020 13:30:56 +0000 Subject: [PATCH 32/69] Added first experimental dockerfile This dockerfile will copy all local code to the container and allow for easy use. There is still an issue with the opencl clinfo platforms but that is the next things that needs to be tackled. --- tutorial_1/.dockerignore | 5 +++++ tutorial_1/Dockerfile | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 tutorial_1/.dockerignore create mode 100644 tutorial_1/Dockerfile diff --git a/tutorial_1/.dockerignore b/tutorial_1/.dockerignore new file mode 100644 index 0000000..43b7577 --- /dev/null +++ b/tutorial_1/.dockerignore @@ -0,0 +1,5 @@ +# ignore everything using whitelist strategy +# * + +# you can selectiveley allow files using the ! at the beginning of the line +#!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/tutorial_1/Dockerfile b/tutorial_1/Dockerfile new file mode 100644 index 0000000..83aabe2 --- /dev/null +++ b/tutorial_1/Dockerfile @@ -0,0 +1,47 @@ +FROM archlinux:latest + +# variable for user username to use in the container +ARG user_name=archie + +# variable for user password to use in the container +ARG user_password=archer + +# creating basic gpu capable archlinux system +RUN pacman -Syyuu sudo nvidia git base-devel python python-pip pyalpm fish neovim --noconfirm + +# creating user with the desired permissions (NOPASS required for pikaur stages) +RUN useradd -m -p $(openssl passwd -1 ${user_password}) ${user_name} && \ + echo "${user_name} ALL=(ALL) ALL" >> /etc/sudoers && \ + echo "${user_name} ALL=(ALL) NOPASSWD:/usr/bin/pacman" >> /etc/sudoers && \ + echo "exec fish" >> /root/.bashrc + +# swapping to our newly created user +USER ${user_name} + +# clone, build, and install pikaur +RUN mkdir -p /home/${user_name}/git && \ + cd /home/${user_name}/git && \ + git clone "https://github.com/actionless/pikaur" && \ + cd /home/${user_name}/git/pikaur && \ + makepkg -s --noconfirm && \ + echo "${user_password}" | sudo -S pacman -U *pkg.tar.xz --noconfirm + +# swapping back to root to continue since we no longer desire to be a user for makepkg +USER root + +# install more specific packages from community and AUR as needed +# RUN sudo -u ${user_name} pikaur -S --noconfirm grpc-git + +# changing to final user in case interactivity is desired +USER ${user_name} + +# set up to build tensorflow (so that while we are still fiddling we dont stress archlinux servers) +RUN echo "${user_password}" | sudo -S pacman -S wget cmake opencl-nvidia ocl-icd opencl-headers clinfo cuda gcc --noconfirm + +RUN mkdir -p /home/${user_name}/git && \ + mkdir -p /home/${user_name}/opencl_tutorials && \ + echo "cd ~" >> /home/${user_name}/.bashrc && \ + echo "exec fish" >> /home/${user_name}/.bashrc && \ + clinfo + +COPY --chown=${user_name}:${user_name} ./* /home/${user_name}/opencl_tutorials/ From 547ae11799e69b41a804d2bd331e8d0f8642a417 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 28 Jan 2020 19:11:23 +0000 Subject: [PATCH 33/69] Finalised CMake and build.sh + license This should finalise, and streamline the build process using CMake for a much wider audience. I have also added licence files for all the files I have created new. All that remains is a complete Dockerfile and a readme. --- tutorial_1/CMakeLists.txt | 28 +++++++++++++++++++++++++++- tutorial_1/Dockerfile | 23 +++++++++++++++++++++++ tutorial_1/build.sh | 24 ++++++++++++++++++++++++ tutorial_1/main.cpp | 18 ------------------ tutorial_1/tut1.cpp | 6 +++--- tutorial_1/{Utils.h => utils.h} | 0 6 files changed, 77 insertions(+), 22 deletions(-) delete mode 100644 tutorial_1/main.cpp rename tutorial_1/{Utils.h => utils.h} (100%) diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt index c88d852..d03f26b 100644 --- a/tutorial_1/CMakeLists.txt +++ b/tutorial_1/CMakeLists.txt @@ -1,3 +1,26 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + # sets versions and names of project cmake_minimum_required(VERSION 3.7) project(opencl_tutorial_1) @@ -8,9 +31,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # specify our compiled binaries name set(BINARY_NAME tut1) # specify all our cpp files names -set(CPP_FILES tut1.cpp) +set(CPP_FILES tut1.cpp utils.h) # build our project executable, and link opencl lib find_package(OpenCL 2 REQUIRED) add_executable(${BINARY_NAME} ${CPP_FILES}) target_link_libraries(tut1 OpenCL::OpenCL) + +# copy kernel files over to binary directory so it can access them +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/kernels/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/kernels/) diff --git a/tutorial_1/Dockerfile b/tutorial_1/Dockerfile index 83aabe2..a1e4c6f 100644 --- a/tutorial_1/Dockerfile +++ b/tutorial_1/Dockerfile @@ -1,3 +1,26 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + FROM archlinux:latest # variable for user username to use in the container diff --git a/tutorial_1/build.sh b/tutorial_1/build.sh index b9f66b3..48f5705 100755 --- a/tutorial_1/build.sh +++ b/tutorial_1/build.sh @@ -1,4 +1,28 @@ #!/usr/bin/env bash + +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + rm -r build/ mkdir -p build cd build/ diff --git a/tutorial_1/main.cpp b/tutorial_1/main.cpp deleted file mode 100644 index 7f69d45..0000000 --- a/tutorial_1/main.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#define __CL_ENABLE_EXCEPTIONS - -#include -#include - -// Include guarded openCL libs -#ifdef __APPLE__ - #include -#else - #include -#endif - -int main(int argc, char **argv) { - - std::cout << "Hello" << std::endl; - return 0; -} diff --git a/tutorial_1/tut1.cpp b/tutorial_1/tut1.cpp index 8f78753..f5b633f 100644 --- a/tutorial_1/tut1.cpp +++ b/tutorial_1/tut1.cpp @@ -1,7 +1,7 @@ #include #include -#include "Utils.h" +#include "utils.h" void print_help() { std::cerr << "Application usage:" << std::endl; @@ -58,7 +58,7 @@ int main(int argc, char **argv) { //host - input std::vector A = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //C++11 allows this type of initialisation std::vector B = { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 }; - + size_t vector_elements = A.size();//number of elements size_t vector_size = A.size()*sizeof(int);//size in bytes @@ -96,4 +96,4 @@ int main(int argc, char **argv) { } return 0; -} \ No newline at end of file +} diff --git a/tutorial_1/Utils.h b/tutorial_1/utils.h similarity index 100% rename from tutorial_1/Utils.h rename to tutorial_1/utils.h From fe3f38013312ae8d92d04649a6501a53030f13e4 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 28 Jan 2020 19:31:50 +0000 Subject: [PATCH 34/69] Added README for build and use instructions This README.md explains briefly how to build and use these new files that have been adapted for much easier use. Now the only thing that can trip people up is their own knowledge ;) --- tutorial_1/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tutorial_1/README.md diff --git a/tutorial_1/README.md b/tutorial_1/README.md new file mode 100644 index 0000000..2129bde --- /dev/null +++ b/tutorial_1/README.md @@ -0,0 +1,24 @@ +# Tutorial 1 + +This directory "tutorial_1" is a completeley self contained OpenCL example. It has CMake to build itself and Docker to automate all the dependencies and building for you. To build this project without docker simply run the build.sh file. + +## universal Docker + +Docker allows us to install everything consistently between all operating systems, platforms, etc, in a light weight virtual machine, so that we dont have to worry about installing anything other than docker itself. + +This docker can be run on Linux by: + ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactiveley by connecting to it ```sudo docker run --gpus all -it archer/opencl bash``` All the local files can be found in ```~/opencl-tutorials``` directory + +## Linux + +On Linux simply: ```./build.sh``` to build the project in a folder called "build", and run it automatically for you for convenience. + +(it has a shebang which means it knows what program to run as its specified at the top of the file) + + + +If you would rather not have to install cmake or any other dependancies, or if you want to ensure it works consistently, or if you are forced to use windows but would like an actual useful OS in your hands, then simply use the Dockerfile. + +## Windows + +On windows in theory run ```cmake``` then ```make``` to build the project then run the file called ```tut1```. From fa3e93d28303ce8c77abeee5efc630832d0d4af6 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 28 Jan 2020 21:32:00 +0000 Subject: [PATCH 35/69] Fixed docker The docker now inherits from an nvidia docker container and we quickly configure it for our needs. Shape about the hacky cl2.hpp but it is functional. --- CMakeLists.txt | 2 -- tutorial_1/.dockerignore | 2 +- tutorial_1/Dockerfile | 60 +++++++++------------------------------- tutorial_1/README.md | 10 +++++-- 4 files changed, 21 insertions(+), 53 deletions(-) delete mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 95ef3e6..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -find_package(OpenCL REQUIRED) -# add_executable() diff --git a/tutorial_1/.dockerignore b/tutorial_1/.dockerignore index 43b7577..9eca48d 100644 --- a/tutorial_1/.dockerignore +++ b/tutorial_1/.dockerignore @@ -1,5 +1,5 @@ # ignore everything using whitelist strategy # * - +build/ # you can selectiveley allow files using the ! at the beginning of the line #!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/tutorial_1/Dockerfile b/tutorial_1/Dockerfile index a1e4c6f..75b2ad6 100644 --- a/tutorial_1/Dockerfile +++ b/tutorial_1/Dockerfile @@ -21,50 +21,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -FROM archlinux:latest - -# variable for user username to use in the container -ARG user_name=archie - -# variable for user password to use in the container -ARG user_password=archer - -# creating basic gpu capable archlinux system -RUN pacman -Syyuu sudo nvidia git base-devel python python-pip pyalpm fish neovim --noconfirm - -# creating user with the desired permissions (NOPASS required for pikaur stages) -RUN useradd -m -p $(openssl passwd -1 ${user_password}) ${user_name} && \ - echo "${user_name} ALL=(ALL) ALL" >> /etc/sudoers && \ - echo "${user_name} ALL=(ALL) NOPASSWD:/usr/bin/pacman" >> /etc/sudoers && \ - echo "exec fish" >> /root/.bashrc - -# swapping to our newly created user -USER ${user_name} - -# clone, build, and install pikaur -RUN mkdir -p /home/${user_name}/git && \ - cd /home/${user_name}/git && \ - git clone "https://github.com/actionless/pikaur" && \ - cd /home/${user_name}/git/pikaur && \ - makepkg -s --noconfirm && \ - echo "${user_password}" | sudo -S pacman -U *pkg.tar.xz --noconfirm - -# swapping back to root to continue since we no longer desire to be a user for makepkg -USER root - -# install more specific packages from community and AUR as needed -# RUN sudo -u ${user_name} pikaur -S --noconfirm grpc-git - -# changing to final user in case interactivity is desired -USER ${user_name} - -# set up to build tensorflow (so that while we are still fiddling we dont stress archlinux servers) -RUN echo "${user_password}" | sudo -S pacman -S wget cmake opencl-nvidia ocl-icd opencl-headers clinfo cuda gcc --noconfirm - -RUN mkdir -p /home/${user_name}/git && \ - mkdir -p /home/${user_name}/opencl_tutorials && \ - echo "cd ~" >> /home/${user_name}/.bashrc && \ - echo "exec fish" >> /home/${user_name}/.bashrc && \ - clinfo - -COPY --chown=${user_name}:${user_name} ./* /home/${user_name}/opencl_tutorials/ +FROM nvidia/opencl + +# setting up the place, making sure everything is installed, and getting the +# pesky CL/cl2.hpp +RUN mkdir -p /opencl_tutorials && \ + apt update -y && \ + apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev && \ + git clone https://github.com/KhronosGroup/opencl-clhpp && \ + cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ + rm -r /opencl-clhpp && \ + echo "cd /opencl_tutorials" >> /root/.bashrc + +COPY . /opencl_tutorials/ diff --git a/tutorial_1/README.md b/tutorial_1/README.md index 2129bde..25348d6 100644 --- a/tutorial_1/README.md +++ b/tutorial_1/README.md @@ -2,12 +2,16 @@ This directory "tutorial_1" is a completeley self contained OpenCL example. It has CMake to build itself and Docker to automate all the dependencies and building for you. To build this project without docker simply run the build.sh file. +Note: All following instructions are to be followed from the command line or terminal from inside this directory locally. + ## universal Docker Docker allows us to install everything consistently between all operating systems, platforms, etc, in a light weight virtual machine, so that we dont have to worry about installing anything other than docker itself. -This docker can be run on Linux by: - ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactiveley by connecting to it ```sudo docker run --gpus all -it archer/opencl bash``` All the local files can be found in ```~/opencl-tutorials``` directory +To use docker for opencl you will need only two dependancies [docker](https://wiki.archlinux.org/index.php/Docker) and the docker [NVIDIA container toolkit](https://wiki.archlinux.org/index.php/Docker#With_NVIDIA_Container_Toolkit_(recommended)). + +This docker can then be run on Linux by: + ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactiveley by connecting to it ```sudo docker run --gpus all -it archer/opencl bash```. When you log in you will be in your current directory again but this time inside a container where you can build/ test all you like, and then it gets wiped when you close. Enjoy the consistent sandbox! ## Linux @@ -21,4 +25,4 @@ If you would rather not have to install cmake or any other dependancies, or if y ## Windows -On windows in theory run ```cmake``` then ```make``` to build the project then run the file called ```tut1```. +On windows in theory run ```cmake``` then ```make``` to build the project then run the file called ```tut1```. CMake can also build visual studio solutions for you if you still desire them. I believe the incantation will be ```cmake -G "Visual Studio 10 Win64"``` From 52cef04bfb8d4281232afcbacacccccb25a4f0bf Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 28 Jan 2020 21:34:25 +0000 Subject: [PATCH 36/69] Fixed spelling in tut1 readme --- tutorial_1/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorial_1/README.md b/tutorial_1/README.md index 25348d6..43d118d 100644 --- a/tutorial_1/README.md +++ b/tutorial_1/README.md @@ -1,17 +1,17 @@ # Tutorial 1 -This directory "tutorial_1" is a completeley self contained OpenCL example. It has CMake to build itself and Docker to automate all the dependencies and building for you. To build this project without docker simply run the build.sh file. +This directory "tutorial_1" is a completely self contained OpenCL example. It has CMake to build itself and Docker to automate all the dependencies and building for you. To build this project without docker simply run the build.sh file. Note: All following instructions are to be followed from the command line or terminal from inside this directory locally. -## universal Docker +## Universal Docker Docker allows us to install everything consistently between all operating systems, platforms, etc, in a light weight virtual machine, so that we dont have to worry about installing anything other than docker itself. To use docker for opencl you will need only two dependancies [docker](https://wiki.archlinux.org/index.php/Docker) and the docker [NVIDIA container toolkit](https://wiki.archlinux.org/index.php/Docker#With_NVIDIA_Container_Toolkit_(recommended)). This docker can then be run on Linux by: - ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactiveley by connecting to it ```sudo docker run --gpus all -it archer/opencl bash```. When you log in you will be in your current directory again but this time inside a container where you can build/ test all you like, and then it gets wiped when you close. Enjoy the consistent sandbox! + ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactively by connecting to it ```sudo docker run --gpus all -it archer/opencl bash```. When you log in you will be in your current directory again but this time inside a container where you can build/ test all you like, and then it gets wiped when you close. Enjoy the consistent sandbox! ## Linux From 2245f893b5e499f388c377703e37ee8cae93a2e4 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Wed, 29 Jan 2020 09:25:53 +0000 Subject: [PATCH 37/69] Added updated instructions for ubuntu --- tutorial_1/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tutorial_1/README.md b/tutorial_1/README.md index 43d118d..cb3a08b 100644 --- a/tutorial_1/README.md +++ b/tutorial_1/README.md @@ -10,6 +10,16 @@ Docker allows us to install everything consistently between all operating system To use docker for opencl you will need only two dependancies [docker](https://wiki.archlinux.org/index.php/Docker) and the docker [NVIDIA container toolkit](https://wiki.archlinux.org/index.php/Docker#With_NVIDIA_Container_Toolkit_(recommended)). +On Ubuntu 18.04 to install docker: +``` +sudo apt install docker.io +distribution=$(. /etc/os-release;echo $ID$VERSION_ID) +curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - +curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list +sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit +sudo systemctl restart docker +``` + This docker can then be run on Linux by: ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactively by connecting to it ```sudo docker run --gpus all -it archer/opencl bash```. When you log in you will be in your current directory again but this time inside a container where you can build/ test all you like, and then it gets wiped when you close. Enjoy the consistent sandbox! From 7a565daddbf0190a1418353eb8d673ed9b9bb6af Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Wed, 29 Jan 2020 12:09:13 +0000 Subject: [PATCH 38/69] Added note on docker versions --- tutorial_1/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial_1/README.md b/tutorial_1/README.md index cb3a08b..8a98422 100644 --- a/tutorial_1/README.md +++ b/tutorial_1/README.md @@ -10,7 +10,7 @@ Docker allows us to install everything consistently between all operating system To use docker for opencl you will need only two dependancies [docker](https://wiki.archlinux.org/index.php/Docker) and the docker [NVIDIA container toolkit](https://wiki.archlinux.org/index.php/Docker#With_NVIDIA_Container_Toolkit_(recommended)). -On Ubuntu 18.04 to install docker: +On Ubuntu 19.03 to install docker (must be docker v19!): ``` sudo apt install docker.io distribution=$(. /etc/os-release;echo $ID$VERSION_ID) From ef812cab6d4aaaac04b20d151967c5adaa08de03 Mon Sep 17 00:00:00 2001 From: gcielniak Date: Mon, 10 Feb 2020 14:51:02 +0000 Subject: [PATCH 39/69] Tutorial 2 fix - to reflect different pixel arrangement in CImg (cherry picked from commit 2d586add2441f1616b269379cb7e883e0505f77f) --- tutorial_2/Tutorial 2.cpp | 4 ++-- tutorial_2/kernels/my_kernels.cl | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tutorial_2/Tutorial 2.cpp b/tutorial_2/Tutorial 2.cpp index 313837d..3475b7a 100644 --- a/tutorial_2/Tutorial 2.cpp +++ b/tutorial_2/Tutorial 2.cpp @@ -82,12 +82,12 @@ int main(int argc, char **argv) { // queue.enqueueWriteBuffer(dev_convolution_mask, CL_TRUE, 0, convolution_mask.size()*sizeof(float), &convolution_mask[0]); //4.2 Setup and execute the kernel (i.e. device code) - cl::Kernel kernel = cl::Kernel(program, "identityND"); + cl::Kernel kernel = cl::Kernel(program, "identity"); kernel.setArg(0, dev_image_input); kernel.setArg(1, dev_image_output); // kernel.setArg(2, dev_convolution_mask); - queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_input.width(), image_input.height(), image_input.spectrum()), cl::NullRange); + queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_input.size()), cl::NullRange); vector output_buffer(image_input.size()); //4.3 Copy the result from device to host diff --git a/tutorial_2/kernels/my_kernels.cl b/tutorial_2/kernels/my_kernels.cl index c29f64b..2705acc 100644 --- a/tutorial_2/kernels/my_kernels.cl +++ b/tutorial_2/kernels/my_kernels.cl @@ -9,6 +9,7 @@ kernel void filter_r(global const uchar* A, global uchar* B) { int image_size = get_global_size(0)/3; //each image consists of 3 colour channels int colour_channel = id / image_size; // 0 - red, 1 - green, 2 - blue + //this is just a copy operation, modify to filter out the individual colour channels B[id] = A[id]; } @@ -29,7 +30,7 @@ kernel void identityND(global const uchar* A, global uchar* B) { } //2D averaging filter -kernel void avg_filter2D(global const uchar* A, global uchar* B) { +kernel void avg_filterND(global const uchar* A, global uchar* B) { int width = get_global_size(0); //image width in pixels int height = get_global_size(1); //image height in pixels int image_size = width*height; //image size in pixels @@ -41,7 +42,7 @@ kernel void avg_filter2D(global const uchar* A, global uchar* B) { int id = x + y*width + c*image_size; //global id in 1D space - ushort result = 0; + uint result = 0; for (int i = (x-1); i <= (x+1); i++) for (int j = (y-1); j <= (y+1); j++) @@ -53,7 +54,7 @@ kernel void avg_filter2D(global const uchar* A, global uchar* B) { } //2D 3x3 convolution kernel -kernel void convolution2D(global const uchar* A, global uchar* B, constant float* mask) { +kernel void convolutionND(global const uchar* A, global uchar* B, constant float* mask) { int width = get_global_size(0); //image width in pixels int height = get_global_size(1); //image height in pixels int image_size = width*height; //image size in pixels @@ -65,7 +66,7 @@ kernel void convolution2D(global const uchar* A, global uchar* B, constant float int id = x + y*width + c*image_size; //global id in 1D space - ushort result = 0; + float result = 0; for (int i = (x-1); i <= (x+1); i++) for (int j = (y-1); j <= (y+1); j++) From a43d45e712a5a84851239cabbf0bbd3897971499 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 11 Feb 2020 12:12:09 +0000 Subject: [PATCH 40/69] Basic renaming and removing VS --- tutorial_2/CImg.h | 62163 ++++++++++++++++++++++ tutorial_2/CMakeLists.txt | 42 + tutorial_2/Dockerfile | 36 + tutorial_2/Tutorial 2.vcxproj | 197 - tutorial_2/Tutorial 2.vcxproj.filters | 27 - tutorial_2/{Tutorial 2.cpp => tut2.cpp} | 0 tutorial_2/utils.h | 225 + 7 files changed, 62466 insertions(+), 224 deletions(-) create mode 100644 tutorial_2/CImg.h create mode 100644 tutorial_2/CMakeLists.txt create mode 100644 tutorial_2/Dockerfile delete mode 100644 tutorial_2/Tutorial 2.vcxproj delete mode 100644 tutorial_2/Tutorial 2.vcxproj.filters rename tutorial_2/{Tutorial 2.cpp => tut2.cpp} (100%) create mode 100644 tutorial_2/utils.h diff --git a/tutorial_2/CImg.h b/tutorial_2/CImg.h new file mode 100644 index 0000000..d9d2a21 --- /dev/null +++ b/tutorial_2/CImg.h @@ -0,0 +1,62163 @@ +/* + # + # File : CImg.h + # ( C++ header file ) + # + # Description : The C++ Template Image Processing Toolkit. + # This file is the main component of the CImg Library project. + # ( http://cimg.eu ) + # + # Project manager : David Tschumperle. + # ( http://tschumperle.users.greyc.fr/ ) + # + # A complete list of contributors is available in file 'README.txt' + # distributed within the CImg package. + # + # Licenses : This file is 'dual-licensed', you have to choose one + # of the two licenses below to apply. + # + # CeCILL-C + # The CeCILL-C license is close to the GNU LGPL. + # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html ) + # + # or CeCILL v2.1 + # The CeCILL license is compatible with the GNU GPL. + # ( http://www.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". + # + # As a counterpart to the access to the source code and rights to copy, + # modify and redistribute granted by the license, users are provided only + # with a limited warranty and the software's author, the holder of the + # economic rights, and the successive licensors have only limited + # liability. + # + # In this respect, the user's attention is drawn to the risks associated + # with loading, using, modifying and/or developing or reproducing the + # software by the user in light of its specific status of free software, + # that may mean that it is complicated to manipulate, and that also + # therefore means that it is reserved for developers and experienced + # professionals having in-depth computer knowledge. Users are therefore + # encouraged to load and test the software's suitability as regards their + # requirements in conditions enabling the security of their systems and/or + # data to be ensured and, more generally, to use and operate it in the + # same conditions as regards security. + # + # The fact that you are presently reading this means that you have had + # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms. + # +*/ + +// Set version number of the library. +#ifndef cimg_version +#define cimg_version 250 + +/*----------------------------------------------------------- + # + # Test and possibly auto-set CImg configuration variables + # and include required headers. + # + # If you find that the default configuration variables are + # not adapted to your system, you can override their values + # before including the header file "CImg.h" + # (use the #define directive). + # + ------------------------------------------------------------*/ + +// Include standard C++ headers. +// This is the minimal set of required headers to make CImg-based codes compile. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Detect/configure OS variables. +// +// Define 'cimg_OS' to: '0' for an unknown OS (will try to minize library dependencies). +// '1' for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...). +// '2' for Microsoft Windows. +// (auto-detection is performed if 'cimg_OS' is not set by the user). +#ifndef cimg_OS +#if defined(unix) || defined(__unix) || defined(__unix__) \ + || defined(linux) || defined(__linux) || defined(__linux__) \ + || defined(sun) || defined(__sun) \ + || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__FreeBSD__) || defined (__DragonFly__) \ + || defined(sgi) || defined(__sgi) \ + || defined(__MACOSX__) || defined(__APPLE__) \ + || defined(__CYGWIN__) +#define cimg_OS 1 +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \ + || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) +#define cimg_OS 2 +#else +#define cimg_OS 0 +#endif +#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2) +#error CImg Library: Invalid configuration variable 'cimg_OS'. +#error (correct values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows'). +#endif +#ifndef cimg_date +#define cimg_date __DATE__ +#endif +#ifndef cimg_time +#define cimg_time __TIME__ +#endif + +// Disable silly warnings on some Microsoft VC++ compilers. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) +#pragma warning(disable:4244) +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4319) +#pragma warning(disable:4512) +#pragma warning(disable:4571) +#pragma warning(disable:4640) +#pragma warning(disable:4706) +#pragma warning(disable:4710) +#pragma warning(disable:4800) +#pragma warning(disable:4804) +#pragma warning(disable:4820) +#pragma warning(disable:4996) + +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#endif +#endif + +// Define correct string functions for each compiler and OS. +#if cimg_OS==2 && defined(_MSC_VER) +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#include +#if defined(__MACOSX__) || defined(__APPLE__) +#define cimg_sscanf cimg::_sscanf +#define cimg_sprintf cimg::_sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf snprintf +#define cimg_vsnprintf vsnprintf +#endif +#endif + +// Include OS-specific headers. +#if cimg_OS==1 +#include +#include +#include +#include +#include +#include +#elif cimg_OS==2 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#ifndef _WIN32_IE +#define _WIN32_IE 0x0400 +#endif +#include +#include +#include +#endif + +// Look for C++11 features. +#ifndef cimg_use_cpp11 +#if __cplusplus>201100 +#define cimg_use_cpp11 1 +#else +#define cimg_use_cpp11 0 +#endif +#endif +#if cimg_use_cpp11==1 +#include +#include +#endif + +// Convenient macro to define pragma +#ifdef _MSC_VER +#define cimg_pragma(x) __pragma(x) +#else +#define cimg_pragma(x) _Pragma(#x) +#endif + +// Define own types 'cimg_long/ulong' and 'cimg_int64/uint64' to ensure portability. +// ( constrained to 'sizeof(cimg_ulong/cimg_long) = sizeof(void*)' and 'sizeof(cimg_int64/cimg_uint64)=8' ). +#if cimg_OS==2 + +#define cimg_uint64 unsigned __int64 +#define cimg_int64 __int64 +#define cimg_ulong UINT_PTR +#define cimg_long INT_PTR +#ifdef _MSC_VER +#define cimg_fuint64 "%I64u" +#define cimg_fint64 "%I64d" +#else +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#endif + +#else + +#if UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX)) +#define cimg_uint64 unsigned long long +#define cimg_int64 long long +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#else +#define cimg_uint64 unsigned long +#define cimg_int64 long +#define cimg_fuint64 "%lu" +#define cimg_fint64 "%ld" +#endif + +#if defined(__arm__) || defined(_M_ARM) +#define cimg_ulong unsigned long long +#define cimg_long long long +#else +#define cimg_ulong unsigned long +#define cimg_long long +#endif + +#endif + +// Configure filename separator. +// +// Filename separator is set by default to '/', except for Windows where it is '\'. +#ifndef cimg_file_separator +#if cimg_OS==2 +#define cimg_file_separator '\\' +#else +#define cimg_file_separator '/' +#endif +#endif + +// Configure verbosity of output messages. +// +// Define 'cimg_verbosity' to: '0' to hide library messages (quiet mode). +// '1' to output library messages on the console. +// '2' to output library messages on a basic dialog window (default behavior). +// '3' to do as '1' + add extra warnings (may slow down the code!). +// '4' to do as '2' + add extra warnings (may slow down the code!). +// +// Define 'cimg_strict_warnings' to replace warning messages by exception throwns. +// +// Define 'cimg_use_vt100' to allow output of color messages on VT100-compatible terminals. +#ifndef cimg_verbosity +#if cimg_OS==2 +#define cimg_verbosity 2 +#else +#define cimg_verbosity 1 +#endif +#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4) +#error CImg Library: Configuration variable 'cimg_verbosity' is badly defined. +#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }). +#endif + +// Configure display framework. +// +// Define 'cimg_display' to: '0' to disable display capabilities. +// '1' to use the X-Window framework (X11). +// '2' to use the Microsoft GDI32 framework. +#ifndef cimg_display +#if cimg_OS==0 +#define cimg_display 0 +#elif cimg_OS==1 +#define cimg_display 1 +#elif cimg_OS==2 +#define cimg_display 2 +#endif +#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2) +#error CImg Library: Configuration variable 'cimg_display' is badly defined. +#error (should be { 0=none | 1=X-Window (X11) | 2=Microsoft GDI32 }). +#endif + +// Configure the 'abort' signal handler (does nothing by default). +// A typical signal handler can be defined in your own source like this: +// #define cimg_abort_test if (is_abort) throw CImgAbortException("") +// +// where 'is_abort' is a boolean variable defined somewhere in your code and reachable in the method. +// 'cimg_abort_test2' does the same but is called more often (in inner loops). +#if defined(cimg_abort_test) && defined(cimg_use_openmp) + +// Define abort macros to be used with OpenMP. +#ifndef _cimg_abort_init_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 + +// Include display-specific headers. +#if cimg_display==1 +#include +#include +#include +#include +#ifdef cimg_use_xshm +#include +#include +#include +#endif +#ifdef cimg_use_xrandr +#include +#endif +#endif +#ifndef cimg_appname +#define cimg_appname "CImg" +#endif + +// Configure OpenMP support. +// (http://www.openmp.org) +// +// Define 'cimg_use_openmp' to enable OpenMP support (requires OpenMP 3.0+). +// +// OpenMP directives are used in many 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/) +// +// Define 'cimg_use_opencv' to enable OpenCV support. +// +// OpenCV library may be used to access images from cameras +// (see method 'CImg::load_camera()'). +#ifdef cimg_use_opencv +#ifdef True +#undef True +#define _cimg_redefine_True +#endif +#ifdef False +#undef False +#define _cimg_redefine_False +#endif +#include +#include "cv.h" +#include "highgui.h" +#endif + +// Configure LibPNG support. +// (http://www.libpng.org) +// +// Define 'cimg_use_png' to enable LibPNG support. +// +// PNG library may be used to get a native support of '.png' files. +// (see methods 'CImg::{load,save}_png()'. +#ifdef cimg_use_png +extern "C" { +#include "png.h" +} +#endif + +// Configure LibJPEG support. +// (http://en.wikipedia.org/wiki/Libjpeg) +// +// Define 'cimg_use_jpeg' to enable LibJPEG support. +// +// JPEG library may be used to get a native support of '.jpg' files. +// (see methods 'CImg::{load,save}_jpeg()'). +#ifdef cimg_use_jpeg +extern "C" { +#include "jpeglib.h" +#include "setjmp.h" +} +#endif + +// Configure LibTIFF support. +// (http://www.libtiff.org) +// +// Define 'cimg_use_tiff' to enable LibTIFF support. +// +// TIFF library may be used to get a native support of '.tif' files. +// (see methods 'CImg[List]::{load,save}_tiff()'). +#ifdef cimg_use_tiff +extern "C" { +#define uint64 uint64_hack_ +#define int64 int64_hack_ +#include "tiffio.h" +#undef uint64 +#undef int64 +} +#endif + +// Configure LibMINC2 support. +// (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference) +// +// Define 'cimg_use_minc2' to enable LibMINC2 support. +// +// MINC2 library may be used to get a native support of '.mnc' files. +// (see methods 'CImg::{load,save}_minc2()'). +#ifdef cimg_use_minc2 +#include "minc_io_simple_volume.h" +#include "minc_1_simple.h" +#include "minc_1_simple_rw.h" +#endif + +// Configure Zlib support. +// (http://www.zlib.net) +// +// Define 'cimg_use_zlib' to enable Zlib support. +// +// Zlib library may be used to allow compressed data in '.cimgz' files +// (see methods 'CImg[List]::{load,save}_cimg()'). +#ifdef cimg_use_zlib +extern "C" { +#include "zlib.h" +} +#endif + +// Configure libcurl support. +// (http://curl.haxx.se/libcurl/) +// +// Define 'cimg_use_curl' to enable libcurl support. +// +// Libcurl may be used to get a native support of file downloading from the network. +// (see method 'cimg::load_network()'.) +#ifdef cimg_use_curl +#include "curl/curl.h" +#endif + +// Configure Magick++ support. +// (http://www.imagemagick.org/Magick++) +// +// Define 'cimg_use_magick' to enable Magick++ support. +// +// Magick++ library may be used to get a native support of various image file formats. +// (see methods 'CImg::{load,save}()'). +#ifdef cimg_use_magick +#include "Magick++.h" +#endif + +// Configure FFTW3 support. +// (http://www.fftw.org) +// +// Define 'cimg_use_fftw3' to enable libFFTW3 support. +// +// FFTW3 library may be used to efficiently compute the Fast Fourier Transform +// of image data, without restriction on the image size. +// (see method 'CImg[List]::FFT()'). +#ifdef cimg_use_fftw3 +extern "C" { +#include "fftw3.h" +} +#endif + +// Configure LibBoard support. +// (http://libboard.sourceforge.net/) +// +// Define 'cimg_use_board' to enable Board support. +// +// Board library may be used to draw 3D objects in vector-graphics canvas +// that can be saved as '.ps' or '.svg' files afterwards. +// (see method 'CImg::draw_object3d()'). +#ifdef cimg_use_board +#include "Board.h" +#endif + +// Configure OpenEXR support. +// (http://www.openexr.com/) +// +// Define 'cimg_use_openexr' to enable OpenEXR support. +// +// OpenEXR library may be used to get a native support of '.exr' files. +// (see methods 'CImg::{load,save}_exr()'). +#ifdef cimg_use_openexr +#include "ImfRgbaFile.h" +#include "ImfInputFile.h" +#include "ImfChannelList.h" +#include "ImfMatrixAttribute.h" +#include "ImfArray.h" +#endif + +// Configure TinyEXR support. +// (https://github.com/syoyo/tinyexr) +// +// Define 'cimg_use_tinyexr' to enable TinyEXR support. +// +// TinyEXR is a small, single header-only library to load and save OpenEXR(.exr) images. +#ifdef cimg_use_tinyexr +#ifndef TINYEXR_IMPLEMENTATION +#define TINYEXR_IMPLEMENTATION +#endif +#include "tinyexr.h" +#endif + +// Lapack configuration. +// (http://www.netlib.org/lapack) +// +// Define 'cimg_use_lapack' to enable LAPACK support. +// +// Lapack library may be used in several CImg methods to speed up +// matrix computations (eigenvalues, inverse, ...). +#ifdef cimg_use_lapack +extern "C" { + extern void sgetrf_(int*, int*, float*, int*, int*, int*); + extern void sgetri_(int*, float*, int*, int*, float*, int*, int*); + extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*); + extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*); + extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*); + extern void dgetrf_(int*, int*, double*, int*, int*, int*); + extern void dgetri_(int*, double*, int*, int*, double*, int*, int*); + extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*); + extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, + int*, double*, int*, double*, int*, int*); + extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*); + extern void dgels_(char*, int*,int*,int*,double*,int*,double*,int*,double*,int*,int*); + extern void sgels_(char*, int*,int*,int*,float*,int*,float*,int*,float*,int*,int*); +} +#endif + +// Check if min/max/PI macros are defined. +// +// CImg does not compile if macros 'min', 'max' or 'PI' are defined, +// because it redefines functions min(), max() and const variable PI in the cimg:: namespace. +// so it '#undef' these macros if necessary, and restore them to reasonable +// values at the end of this file. +#ifdef min +#undef min +#define _cimg_redefine_min +#endif +#ifdef max +#undef max +#define _cimg_redefine_max +#endif +#ifdef PI +#undef PI +#define _cimg_redefine_PI +#endif + +// Define 'cimg_library' namespace suffix. +// +// You may want to add a suffix to the 'cimg_library' namespace, for instance if you need to work +// with several versions of the library at the same time. +#ifdef cimg_namespace_suffix +#define __cimg_library_suffixed(s) cimg_library_##s +#define _cimg_library_suffixed(s) __cimg_library_suffixed(s) +#define cimg_library_suffixed _cimg_library_suffixed(cimg_namespace_suffix) +#else +#define cimg_library_suffixed cimg_library +#endif + +/*------------------------------------------------------------------------------ + # + # Define user-friendly macros. + # + # These CImg macros are prefixed by 'cimg_' and can be used safely in your own + # code. They are useful to parse command line options, or to write image loops. + # + ------------------------------------------------------------------------------*/ + +// Macros to define program usage, and retrieve command line arguments. +#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false) +#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0) +#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage) + +// Macros to define and manipulate local neighborhoods. +#define CImg_2x2(I,T) T I[4]; \ + T& I##cc = I[0]; T& I##nc = I[1]; \ + T& I##cn = I[2]; T& I##nn = I[3]; \ + I##cc = I##nc = \ + I##cn = I##nn = 0 + +#define CImg_3x3(I,T) T I[9]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \ + T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \ + T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \ + I##pp = I##cp = I##np = \ + I##pc = I##cc = I##nc = \ + I##pn = I##cn = I##nn = 0 + +#define CImg_4x4(I,T) T I[16]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \ + T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \ + T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \ + T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \ + I##pp = I##cp = I##np = I##ap = \ + I##pc = I##cc = I##nc = I##ac = \ + I##pn = I##cn = I##nn = I##an = \ + I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_5x5(I,T) T I[25]; \ + T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \ + T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \ + T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \ + T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \ + T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \ + I##bb = I##pb = I##cb = I##nb = I##ab = \ + I##bp = I##pp = I##cp = I##np = I##ap = \ + I##bc = I##pc = I##cc = I##nc = I##ac = \ + I##bn = I##pn = I##cn = I##nn = I##an = \ + I##ba = I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_2x2x2(I,T) T I[8]; \ + T& I##ccc = I[0]; T& I##ncc = I[1]; \ + T& I##cnc = I[2]; T& I##nnc = I[3]; \ + T& I##ccn = I[4]; T& I##ncn = I[5]; \ + T& I##cnn = I[6]; T& I##nnn = I[7]; \ + I##ccc = I##ncc = \ + I##cnc = I##nnc = \ + I##ccn = I##ncn = \ + I##cnn = I##nnn = 0 + +#define CImg_3x3x3(I,T) T I[27]; \ + T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \ + T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \ + T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \ + T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \ + T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \ + T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \ + T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \ + T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \ + T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \ + I##ppp = I##cpp = I##npp = \ + I##pcp = I##ccp = I##ncp = \ + I##pnp = I##cnp = I##nnp = \ + I##ppc = I##cpc = I##npc = \ + I##pcc = I##ccc = I##ncc = \ + I##pnc = I##cnc = I##nnc = \ + I##ppn = I##cpn = I##npn = \ + I##pcn = I##ccn = I##ncn = \ + I##pnn = I##cnn = I##nnn = 0 + +#define cimg_get2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_p1##x,y,z,c), I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), \ + I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), I[8] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get4x4(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_n2##x,_p1##y,z,c), I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), \ + I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), I[8] = (T)(img)(_p1##x,_n1##y,z,c), \ + I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \ + I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[15] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get5x5(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), I[8] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \ + I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), \ + I[15] = (T)(img)(_p2##x,_n1##y,z,c), I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), I[20] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get6x6(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), I[8] = (T)(img)(x,_p1##y,z,c), \ + I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), \ + I[15] = (T)(img)(_n1##x,y,z,c), I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), \ + I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), I[20] = (T)(img)(x,_n1##y,z,c), \ + I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), \ + I[27] = (T)(img)(_n1##x,_n2##y,z,c), I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), I[32] = (T)(img)(x,_n3##y,z,c), \ + I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get7x7(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), I[8] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), \ + I[15] = (T)(img)(_p2##x,_p1##y,z,c), I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), I[20] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \ + I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), \ + I[27] = (T)(img)(_n3##x,y,z,c), I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), I[32] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \ + I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), \ + I[39] = (T)(img)(_n1##x,_n2##y,z,c), I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), I[44] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[48] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get8x8(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), I[8] = (T)(img)(_p3##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \ + I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[15] = (T)(img)(_n4##x,_p2##y,z,c), I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), I[20] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), \ + I[27] = (T)(img)(x,y,z,c), I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), \ + I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), I[32] = (T)(img)(_p3##x,_n1##y,z,c), \ + I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \ + I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[39] = (T)(img)(_n4##x,_n1##y,z,c), I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), I[44] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[51] = (T)(img)(x,_n3##y,z,c), I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), I[56] = (T)(img)(_p3##x,_n4##y,z,c), \ + I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \ + I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), \ + I[63] = (T)(img)(_n4##x,_n4##y,z,c); + +#define cimg_get9x9(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), \ + I[3] = (T)(img)(_p1##x,_p4##y,z,c), I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), \ + I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), I[8] = (T)(img)(_n4##x,_p4##y,z,c), \ + I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \ + I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), \ + I[15] = (T)(img)(_n2##x,_p3##y,z,c), I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), \ + I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), I[20] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), \ + I[27] = (T)(img)(_p4##x,_p1##y,z,c), I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), I[32] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), \ + I[39] = (T)(img)(_p1##x,y,z,c), I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), \ + I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), I[44] = (T)(img)(_n4##x,y,z,c), \ + I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[51] = (T)(img)(_n2##x,_n1##y,z,c), I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), \ + I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), I[56] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[63] = (T)(img)(_p4##x,_n3##y,z,c), I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), \ + I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), I[68] = (T)(img)(_n1##x,_n3##y,z,c), \ + I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \ + I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), \ + I[75] = (T)(img)(_p1##x,_n4##y,z,c), I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), \ + I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), I[80] = (T)(img)(_n4##x,_n4##y,z,c) + +#define cimg_get2x2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c), I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), \ + I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +#define cimg_get3x3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), \ + I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), \ + I[5] = (T)(img)(_n1##x,y,_p1##z,c), I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), \ + I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), \ + I[11] = (T)(img)(_n1##x,_p1##y,z,c), I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), \ + I[14] = (T)(img)(_n1##x,y,z,c), I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), \ + I[17] = (T)(img)(_n1##x,_n1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), \ + I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), \ + I[23] = (T)(img)(_n1##x,y,_n1##z,c), I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), \ + I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +// Macros to perform various image loops. +// +// These macros are simpler to use than loops with C++ iterators. +#define cimg_for(img,ptrs,T_ptrs) \ + for (T_ptrs *ptrs = (img)._data, *_max##ptrs = (img)._data + (img).size(); ptrs<_max##ptrs; ++ptrs) +#define cimg_rof(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size() - 1; ptrs>=(img)._data; --ptrs) +#define cimg_foroff(img,off) for (cimg_ulong off = 0, _max##off = (img).size(); off<_max##off; ++off) +#define cimg_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) +#define cimg_forY(img,y) cimg_for1((img)._height,y) +#define cimg_forZ(img,z) cimg_for1((img)._depth,z) +#define cimg_forC(img,c) cimg_for1((img)._spectrum,c) +#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x) +#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x) +#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y) +#define cimg_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x) +#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y) +#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z) +#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y) +#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y) +#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z) +#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z) +#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) cimg_forXYZ(img,x,y,z) + +#define cimg_rof1(bound,i) for (int i = (int)(bound) - 1; i>=0; --i) +#define cimg_rofX(img,x) cimg_rof1((img)._width,x) +#define cimg_rofY(img,y) cimg_rof1((img)._height,y) +#define cimg_rofZ(img,z) cimg_rof1((img)._depth,z) +#define cimg_rofC(img,c) cimg_rof1((img)._spectrum,c) +#define cimg_rofXY(img,x,y) cimg_rofY(img,y) cimg_rofX(img,x) +#define cimg_rofXZ(img,x,z) cimg_rofZ(img,z) cimg_rofX(img,x) +#define cimg_rofYZ(img,y,z) cimg_rofZ(img,z) cimg_rofY(img,y) +#define cimg_rofXC(img,x,c) cimg_rofC(img,c) cimg_rofX(img,x) +#define cimg_rofYC(img,y,c) cimg_rofC(img,c) cimg_rofY(img,y) +#define cimg_rofZC(img,z,c) cimg_rofC(img,c) cimg_rofZ(img,z) +#define cimg_rofXYZ(img,x,y,z) cimg_rofZ(img,z) cimg_rofXY(img,x,y) +#define cimg_rofXYC(img,x,y,c) cimg_rofC(img,c) cimg_rofXY(img,x,y) +#define cimg_rofXZC(img,x,z,c) cimg_rofC(img,c) cimg_rofXZ(img,x,z) +#define cimg_rofYZC(img,y,z,c) cimg_rofC(img,c) cimg_rofYZ(img,y,z) +#define cimg_rofXYZC(img,x,y,z,c) cimg_rofC(img,c) cimg_rofXYZ(img,x,y,z) + +#define cimg_for_in1(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound) - 1; i<=_max##i; ++i) +#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img)._width,x0,x1,x) +#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img)._height,y0,y1,y) +#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img)._depth,z0,z1,z) +#define cimg_for_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c) +#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inZ(img,z0,z1,z) +#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z) +#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_inC(img,c0,c1,c) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_insideXYZ(img,x,y,z,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_insideXYZC(img,x,y,z,c,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) + +#define cimg_for_out1(boundi,i0,i1,i) \ + for (int i = (int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1) + 1:i) +#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \ + for (int j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \ + for (int k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \ + for (int l = 0; l<(int)(boundl); ++l) \ + for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1) + 1; \ + i<(int)(boundi); ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img)._width,x0,x1,x) +#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img)._height,y0,y1,y) +#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img)._depth,z0,z1,z) +#define cimg_for_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c) +#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img)._width,(img)._height,x0,y0,x1,y1,x,y) +#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img)._width,(img)._depth,x0,z0,x1,z1,x,z) +#define cimg_for_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c) +#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img)._height,(img)._depth,y0,z0,y1,z1,y,z) +#define cimg_for_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c) +#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c) +#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) \ + cimg_for_out3((img)._width,(img)._height,(img)._depth,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) \ + cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c) +#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) \ + cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c) +#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) \ + cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c) +#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) +#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_borderXYZ(img,x,y,z,n) \ + cimg_for_outXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_borderXYZC(img,x,y,z,c,n) \ + cimg_for_outXYZC(img,n,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n), \ + (img)._depth - 1 - (n),(img)._spectrum - 1 - (n),x,y,z,c) + +#define cimg_for_spiralXY(img,x,y) \ + for (int x = 0, y = 0, _n1##x = 1, _n1##y = (img).width()*(img).height(); _n1##y; \ + --_n1##y, _n1##x+=(_n1##x>>2) - ((!(_n1##x&3)?--y:((_n1##x&3)==1?(img)._width - 1 - ++x:\ + ((_n1##x&3)==2?(img)._height - 1 - ++y:--x))))?0:1) + +#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \ + for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \ + _dx=(x1)>(x0)?(int)(x1) - (int)(x0):(_sx=-1,(int)(x0) - (int)(x1)), \ + _dy=(y1)>(y0)?(int)(y1) - (int)(y0):(_sy=-1,(int)(y0) - (int)(y1)), \ + _counter = _dx, \ + _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \ + _counter>=0; \ + --_counter, x+=_steep? \ + (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \ + (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx)) + +#define cimg_for2(bound,i) \ + for (int i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + ++i, ++_n1##i) +#define cimg_for2X(img,x) cimg_for2((img)._width,x) +#define cimg_for2Y(img,y) cimg_for2((img)._height,y) +#define cimg_for2Z(img,z) cimg_for2((img)._depth,z) +#define cimg_for2C(img,c) cimg_for2((img)._spectrum,c) +#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x) +#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x) +#define cimg_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x) +#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y) +#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y) +#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z) +#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y) +#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z) +#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z) +#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) cimg_for2XYZ(img,x,y,z) + +#define cimg_for_in2(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + ++i, ++_n1##i) +#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img)._width,x0,x1,x) +#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img)._height,y0,y1,y) +#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img)._depth,z0,z1,z) +#define cimg_for_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c) +#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Z(img,z0,z1,z) +#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in2C(img,c0,c1,c) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i) +#define cimg_for3X(img,x) cimg_for3((img)._width,x) +#define cimg_for3Y(img,y) cimg_for3((img)._height,y) +#define cimg_for3Z(img,z) cimg_for3((img)._depth,z) +#define cimg_for3C(img,c) cimg_for3((img)._spectrum,c) +#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x) +#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x) +#define cimg_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x) +#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y) +#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y) +#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z) +#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y) +#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z) +#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z) +#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) cimg_for3XYZ(img,x,y,z) + +#define cimg_for_in3(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + _p1##i = i++, ++_n1##i) +#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img)._width,x0,x1,x) +#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img)._height,y0,y1,y) +#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img)._depth,z0,z1,z) +#define cimg_for_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c) +#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Z(img,z0,z1,z) +#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in3C(img,c0,c1,c) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for4(bound,i) \ + for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for4X(img,x) cimg_for4((img)._width,x) +#define cimg_for4Y(img,y) cimg_for4((img)._height,y) +#define cimg_for4Z(img,z) cimg_for4((img)._depth,z) +#define cimg_for4C(img,c) cimg_for4((img)._spectrum,c) +#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x) +#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x) +#define cimg_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x) +#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y) +#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y) +#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z) +#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y) +#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z) +#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z) +#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) cimg_for4XYZ(img,x,y,z) + +#define cimg_for_in4(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img)._width,x0,x1,x) +#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img)._height,y0,y1,y) +#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img)._depth,z0,z1,z) +#define cimg_for_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c) +#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Z(img,z0,z1,z) +#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in4C(img,c0,c1,c) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for5(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for5X(img,x) cimg_for5((img)._width,x) +#define cimg_for5Y(img,y) cimg_for5((img)._height,y) +#define cimg_for5Z(img,z) cimg_for5((img)._depth,z) +#define cimg_for5C(img,c) cimg_for5((img)._spectrum,c) +#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x) +#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x) +#define cimg_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x) +#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y) +#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y) +#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z) +#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y) +#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z) +#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z) +#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) cimg_for5XYZ(img,x,y,z) + +#define cimg_for_in5(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img)._width,x0,x1,x) +#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img)._height,y0,y1,y) +#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img)._depth,z0,z1,z) +#define cimg_for_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c) +#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Z(img,z0,z1,z) +#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in5C(img,c0,c1,c) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for6(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for6X(img,x) cimg_for6((img)._width,x) +#define cimg_for6Y(img,y) cimg_for6((img)._height,y) +#define cimg_for6Z(img,z) cimg_for6((img)._depth,z) +#define cimg_for6C(img,c) cimg_for6((img)._spectrum,c) +#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x) +#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x) +#define cimg_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x) +#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y) +#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y) +#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z) +#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y) +#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z) +#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z) +#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) cimg_for6XYZ(img,x,y,z) + +#define cimg_for_in6(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img)._width,x0,x1,x) +#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img)._height,y0,y1,y) +#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img)._depth,z0,z1,z) +#define cimg_for_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c) +#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Z(img,z0,z1,z) +#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in6C(img,c0,c1,c) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for7(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for7X(img,x) cimg_for7((img)._width,x) +#define cimg_for7Y(img,y) cimg_for7((img)._height,y) +#define cimg_for7Z(img,z) cimg_for7((img)._depth,z) +#define cimg_for7C(img,c) cimg_for7((img)._spectrum,c) +#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x) +#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x) +#define cimg_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x) +#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y) +#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y) +#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z) +#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y) +#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z) +#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z) +#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) cimg_for7XYZ(img,x,y,z) + +#define cimg_for_in7(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img)._width,x0,x1,x) +#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img)._height,y0,y1,y) +#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img)._depth,z0,z1,z) +#define cimg_for_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c) +#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Z(img,z0,z1,z) +#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in7C(img,c0,c1,c) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for8(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for8X(img,x) cimg_for8((img)._width,x) +#define cimg_for8Y(img,y) cimg_for8((img)._height,y) +#define cimg_for8Z(img,z) cimg_for8((img)._depth,z) +#define cimg_for8C(img,c) cimg_for8((img)._spectrum,c) +#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x) +#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x) +#define cimg_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x) +#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y) +#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y) +#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z) +#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y) +#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z) +#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z) +#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) cimg_for8XYZ(img,x,y,z) + +#define cimg_for_in8(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img)._width,x0,x1,x) +#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img)._height,y0,y1,y) +#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img)._depth,z0,z1,z) +#define cimg_for_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c) +#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Z(img,z0,z1,z) +#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in8C(img,c0,c1,c) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for9(bound,i) \ + for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(int)(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(int)(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(int)(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(int)(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for9X(img,x) cimg_for9((img)._width,x) +#define cimg_for9Y(img,y) cimg_for9((img)._height,y) +#define cimg_for9Z(img,z) cimg_for9((img)._depth,z) +#define cimg_for9C(img,c) cimg_for9((img)._spectrum,c) +#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x) +#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x) +#define cimg_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x) +#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y) +#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y) +#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z) +#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y) +#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z) +#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z) +#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) cimg_for9XYZ(img,x,y,z) + +#define cimg_for_in9(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p4##i = i - 4<0?0:i - 4, \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img)._width,x0,x1,x) +#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img)._height,y0,y1,y) +#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img)._depth,z0,z1,z) +#define cimg_for_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c) +#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Z(img,z0,z1,z) +#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[4] = (T)(img)(x,y,z,c)), \ + (I[7] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for4x4(img,x,y,z,c,I,T) \ + cimg_for4((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = I[5] = (T)(img)(0,y,z,c)), \ + (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in4((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = (T)(img)(_p1##x,y,z,c)), \ + (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[5] = (T)(img)(x,y,z,c)), \ + (I[9] = (T)(img)(x,_n1##y,z,c)), \ + (I[13] = (T)(img)(x,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for5x5(img,x,y,z,c,I,T) \ + cimg_for5((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \ + (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \ + (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in5((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[10] = (T)(img)(_p2##x,y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[11] = (T)(img)(_p1##x,y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[7] = (T)(img)(x,_p1##y,z,c)), \ + (I[12] = (T)(img)(x,y,z,c)), \ + (I[17] = (T)(img)(x,_n1##y,z,c)), \ + (I[22] = (T)(img)(x,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for6x6(img,x,y,z,c,I,T) \ + cimg_for6((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \ + (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \ + (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \ + (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in6((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p2##x,y,z,c)), \ + (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_p1##x,y,z,c)), \ + (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[8] = (T)(img)(x,_p1##y,z,c)), \ + (I[14] = (T)(img)(x,y,z,c)), \ + (I[20] = (T)(img)(x,_n1##y,z,c)), \ + (I[26] = (T)(img)(x,_n2##y,z,c)), \ + (I[32] = (T)(img)(x,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for7x7(img,x,y,z,c,I,T) \ + cimg_for7((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \ + (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \ + (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \ + (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \ + (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \ + (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in7((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[21] = (T)(img)(_p3##x,y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[22] = (T)(img)(_p2##x,y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[23] = (T)(img)(_p1##x,y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[10] = (T)(img)(x,_p2##y,z,c)), \ + (I[17] = (T)(img)(x,_p1##y,z,c)), \ + (I[24] = (T)(img)(x,y,z,c)), \ + (I[31] = (T)(img)(x,_n1##y,z,c)), \ + (I[38] = (T)(img)(x,_n2##y,z,c)), \ + (I[45] = (T)(img)(x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for8x8(img,x,y,z,c,I,T) \ + cimg_for8((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \ + (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \ + (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \ + (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \ + (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \ + (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \ + (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in8((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[24] = (T)(img)(_p3##x,y,z,c)), \ + (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_p2##x,y,z,c)), \ + (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_p1##x,y,z,c)), \ + (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[11] = (T)(img)(x,_p2##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,z,c)), \ + (I[27] = (T)(img)(x,y,z,c)), \ + (I[35] = (T)(img)(x,_n1##y,z,c)), \ + (I[43] = (T)(img)(x,_n2##y,z,c)), \ + (I[51] = (T)(img)(x,_n3##y,z,c)), \ + (I[59] = (T)(img)(x,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for9x9(img,x,y,z,c,I,T) \ + cimg_for9((img)._height,y) for (int x = 0, \ + _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \ + (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \ + (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \ + (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \ + (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \ + (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \ + (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \ + (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in9((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p4##x = x - 4<0?0:x - 4, \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \ + (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \ + (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \ + (I[36] = (T)(img)(_p4##x,y,z,c)), \ + (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \ + (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \ + (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \ + (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \ + (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[37] = (T)(img)(_p3##x,y,z,c)), \ + (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \ + (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[38] = (T)(img)(_p2##x,y,z,c)), \ + (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[39] = (T)(img)(_p1##x,y,z,c)), \ + (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[4] = (T)(img)(x,_p4##y,z,c)), \ + (I[13] = (T)(img)(x,_p3##y,z,c)), \ + (I[22] = (T)(img)(x,_p2##y,z,c)), \ + (I[31] = (T)(img)(x,_p1##y,z,c)), \ + (I[40] = (T)(img)(x,y,z,c)), \ + (I[49] = (T)(img)(x,_n1##y,z,c)), \ + (I[58] = (T)(img)(x,_n2##y,z,c)), \ + (I[67] = (T)(img)(x,_n3##y,z,c)), \ + (I[76] = (T)(img)(x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for2x2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + (I[4] = (T)(img)(0,y,_n1##z,c)), \ + (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in2((img)._depth,z0,z1,z) cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + (I[4] = (T)(img)(x,y,_n1##z,c)), \ + (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for3x3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \ + (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \ + (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \ + (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \ + (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in3((img)._depth,z0,z1,z) cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = (T)(img)(_p1##x,y,_p1##z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \ + (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,y,z,c)), \ + (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \ + (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \ + (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \ + (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \ + (I[4] = (T)(img)(x,y,_p1##z,c)), \ + (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \ + (I[10] = (T)(img)(x,_p1##y,z,c)), \ + (I[13] = (T)(img)(x,y,z,c)), \ + (I[16] = (T)(img)(x,_n1##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \ + (I[22] = (T)(img)(x,y,_n1##z,c)), \ + (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l) +#define cimglist_for_in(list,l0,l1,l) \ + for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width - 1; \ + l<=_max##l; ++l) + +#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn + +// Macros used to display error messages when exceptions are thrown. +// You should not use these macros is your own code. +#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::" +#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']' +#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::" +#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type() +#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::" +#define cimglist_instance _width,_allocated_width,_data,pixel_type() + +/*------------------------------------------------ + # + # + # Define cimg_library:: namespace + # + # + -------------------------------------------------*/ +//! Contains all classes and functions of the \CImg library. +/** + This namespace is defined to avoid functions and class names collisions + that could happen with the inclusion of other C++ header files. + Anyway, it should not happen often and you should reasonnably start most of your + \CImg-based programs with + \code + #include "CImg.h" + using namespace cimg_library; + \endcode + to simplify the declaration of \CImg Library objects afterwards. +**/ +namespace cimg_library_suffixed { + + // Declare the four classes of the CImg Library. + template struct CImg; + template struct CImgList; + struct CImgDisplay; + struct CImgException; + + // Declare cimg:: namespace. + // This is an uncomplete namespace definition here. It only contains some + // necessary stuff to ensure a correct declaration order of the classes and functions + // defined afterwards. + namespace cimg { + + // Define Ascii sequences for colored terminal output. +#ifdef cimg_use_vt100 + static const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 }; + static const char t_black[] = { 0x1b, '[', '0', ';', '3', '0', ';', '5', '9', 'm', 0 }; + static const char t_red[] = { 0x1b, '[', '0', ';', '3', '1', ';', '5', '9', 'm', 0 }; + static const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 }; + static const char t_yellow[] = { 0x1b, '[', '0', ';', '3', '3', ';', '5', '9', 'm', 0 }; + static const char t_blue[] = { 0x1b, '[', '0', ';', '3', '4', ';', '5', '9', 'm', 0 }; + static const char t_magenta[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 }; + static const char t_cyan[] = { 0x1b, '[', '0', ';', '3', '6', ';', '5', '9', 'm', 0 }; + static const char t_white[] = { 0x1b, '[', '0', ';', '3', '7', ';', '5', '9', 'm', 0 }; + static const char t_bold[] = { 0x1b, '[', '1', 'm', 0 }; + static const char t_underscore[] = { 0x1b, '[', '4', 'm', 0 }; +#else + static const char t_normal[] = { 0 }; + static const char *const t_black = cimg::t_normal, + *const t_red = cimg::t_normal, + *const t_green = cimg::t_normal, + *const t_yellow = cimg::t_normal, + *const t_blue = cimg::t_normal, + *const t_magenta = cimg::t_normal, + *const t_cyan = cimg::t_normal, + *const t_white = cimg::t_normal, + *const t_bold = cimg::t_normal, + *const t_underscore = cimg::t_normal; +#endif + + inline std::FILE* output(std::FILE *file=0); + inline void info(); + + //! Avoid warning messages due to unused parameters. Do nothing actually. + template + inline void unused(const T&, ...) {} + + // [internal] Lock/unlock a mutex for managing concurrent threads. + // 'lock_mode' can be { 0=unlock | 1=lock | 2=trylock }. + // 'n' can be in [0,31] but mutex range [0,15] is reserved by CImg. + inline int mutex(const unsigned int n, const int lock_mode=1); + + inline unsigned int& exception_mode(const unsigned int value, const bool is_set) { + static unsigned int mode = cimg_verbosity; + if (is_set) { cimg::mutex(0); mode = value<4?value:4; cimg::mutex(0,0); } + return mode; + } + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + inline FILE* _stdin(const bool throw_exception=true); + inline FILE* _stdout(const bool throw_exception=true); + inline FILE* _stderr(const bool throw_exception=true); + + // Mandatory because Microsoft's _snprintf() and _vsnprintf() do not add the '\0' character + // at the end of the string. +#if cimg_OS==2 && defined(_MSC_VER) + inline int _snprintf(char *const s, const size_t size, const char *const format, ...) { + va_list ap; + va_start(ap,format); + const int result = _vsnprintf(s,size,format,ap); + va_end(ap); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char *const format, va_list ap) { + int result = -1; + cimg::mutex(6); + if (size) result = _vsnprintf_s(s,size,_TRUNCATE,format,ap); + if (result==-1) result = _vscprintf(format,ap); + cimg::mutex(6,0); + return result; + } + + // Mutex-protected version of sscanf, sprintf and snprintf. + // Used only MacOSX, as it seems those functions are not re-entrant on MacOSX. +#elif defined(__MACOSX__) || defined(__APPLE__) + inline int _sscanf(const char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsscanf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _sprintf(char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsprintf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _snprintf(char *const s, const size_t n, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsnprintf(s,n,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char* format, va_list ap) { + cimg::mutex(6); + const int result = std::vsnprintf(s,size,format,ap); + cimg::mutex(6,0); + return result; + } +#endif + + //! Set current \CImg exception mode. + /** + The way error messages are handled by \CImg can be changed dynamically, using this function. + \param mode Desired exception mode. Possible values are: + - \c 0: Hide library messages (quiet mode). + - \c 1: Print library messages on the console. + - \c 2: Display library messages on a dialog window. + - \c 3: Do as \c 1 + add extra debug warnings (slow down the code!). + - \c 4: Do as \c 2 + add extra debug warnings (slow down the code!). + **/ + inline unsigned int& exception_mode(const unsigned int mode) { + return exception_mode(mode,true); + } + + //! Return current \CImg exception mode. + /** + \note By default, return the value of configuration macro \c cimg_verbosity + **/ + inline unsigned int& exception_mode() { + return exception_mode(0,false); + } + + 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; + } + + //! Set current \CImg openmp mode. + /** + The way openmp-based methods are handled by \CImg can be changed dynamically, using this function. + \param mode Desired openmp mode. Possible values are: + - \c 0: Never parallelize. + - \c 1: Always parallelize. + - \c 2: Adaptive parallelization mode (default behavior). + **/ + inline unsigned int openmp_mode(const unsigned int mode) { + return openmp_mode(mode,true); + } + + //! Return current \CImg openmp mode. + inline unsigned int openmp_mode() { + return openmp_mode(0,false); + } + +#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); + + // Evaluate math expression. + inline double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0); + + } + + /*--------------------------------------- + # + # Define the CImgException structures + # + --------------------------------------*/ + //! Instances of \c CImgException are thrown when errors are encountered in a \CImg function call. + /** + \par Overview + + CImgException is the base class of all exceptions thrown by \CImg (except \b CImgAbortException). + CImgException is never thrown itself. Derived classes that specify the type of errord are thrown instead. + These classes can be: + + - \b CImgAbortException: Thrown when a computationally-intensive function is aborted by an external signal. + This is the only \c non-derived exception class. + + - \b CImgArgumentException: Thrown when one argument of a called \CImg function is invalid. + This is probably one of the most thrown exception by \CImg. + For instance, the following example throws a \c CImgArgumentException: + \code + CImg img(100,100,1,3); // Define a 100x100 color image with float-valued pixels + img.mirror('e'); // Try to mirror image along the (non-existing) 'e'-axis + \endcode + + - \b CImgDisplayException: Thrown when something went wrong during the display of images in CImgDisplay instances. + + - \b CImgInstanceException: Thrown when an instance associated to a called \CImg method does not fit + the function requirements. For instance, the following example throws a \c CImgInstanceException: + \code + const CImg img; // Define an empty image + const float value = img.at(0); // Try to read first pixel value (does not exist) + \endcode + + - \b CImgIOException: Thrown when an error occured when trying to load or save image files. + This happens when trying to read files that do not exist or with invalid formats. + For instance, the following example throws a \c CImgIOException: + \code + const CImg img("missing_file.jpg"); // Try to load a file that does not exist + \endcode + + - \b CImgWarningException: Thrown only if configuration macro \c cimg_strict_warnings is set, and + when a \CImg function has to display a warning message (see cimg::warn()). + + It is not recommended to throw CImgException instances by yourself, + since they are expected to be thrown only by \CImg. + When an error occurs in a library function call, \CImg may display error messages on the screen or on the + standard output, depending on the current \CImg exception mode. + The \CImg exception mode can be get and set by functions cimg::exception_mode() and + cimg::exception_mode(unsigned int). + + \par Exceptions handling + + In all cases, when an error occurs in \CImg, an instance of the corresponding exception class is thrown. + This may lead the program to break (this is the default behavior), but you can bypass this behavior by + handling the exceptions by yourself, + using a usual try { ... } catch () { ... } bloc, as in the following example: + \code + #define "CImg.h" + using namespace cimg_library; + int main() { + cimg::exception_mode(0); // Enable quiet exception mode + try { + ... // Here, do what you want to stress CImg + } catch (CImgException& e) { // You succeeded: something went wrong! + std::fprintf(stderr,"CImg Library Error: %s",e.what()); // Display your custom error message + ... // Do what you want now to save the ship! + } + } + \endcode + **/ + struct CImgException : public std::exception { +#define _cimg_exception_err(etype,disp_flag) \ + std::va_list ap, ap2; \ + va_start(ap,format); va_start(ap2,format); \ + int size = cimg_vsnprintf(0,0,format,ap2); \ + if (size++>=0) { \ + delete[] _message; \ + _message = new char[size]; \ + cimg_vsnprintf(_message,size,format,ap); \ + if (cimg::exception_mode()) { \ + std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \ + if (cimg_display && disp_flag && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } \ + catch (CImgException&) {} \ + if (cimg::exception_mode()>=3) cimg_library_suffixed::cimg::info(); \ + } \ + } \ + va_end(ap); va_end(ap2); \ + + char *_message; + CImgException() { _message = new char[1]; *_message = 0; } + CImgException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgException",true); } + CImgException(const CImgException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgException() throw() { delete[] _message; } + CImgException& operator=(const CImgException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgAbortException class is used to throw an exception when + // a computationally-intensive function has been aborted by an external signal. + struct CImgAbortException : public std::exception { + char *_message; + CImgAbortException() { _message = new char[1]; *_message = 0; } + CImgAbortException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgAbortException",true); } + CImgAbortException(const CImgAbortException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgAbortException() throw() { delete[] _message; } + CImgAbortException& operator=(const CImgAbortException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgArgumentException class is used to throw an exception related + // to invalid arguments encountered in a library function call. + struct CImgArgumentException : public CImgException { + CImgArgumentException(const char *const format, ...) { _cimg_exception_err("CImgArgumentException",true); } + }; + + // The CImgDisplayException class is used to throw an exception related + // to display problems encountered in a library function call. + struct CImgDisplayException : public CImgException { + CImgDisplayException(const char *const format, ...) { _cimg_exception_err("CImgDisplayException",false); } + }; + + // The CImgInstanceException class is used to throw an exception related + // to an invalid instance encountered in a library function call. + struct CImgInstanceException : public CImgException { + CImgInstanceException(const char *const format, ...) { _cimg_exception_err("CImgInstanceException",true); } + }; + + // The CImgIOException class is used to throw an exception related + // to input/output file problems encountered in a library function call. + struct CImgIOException : public CImgException { + CImgIOException(const char *const format, ...) { _cimg_exception_err("CImgIOException",true); } + }; + + // The CImgWarningException class is used to throw an exception for warnings + // encountered in a library function call. + struct CImgWarningException : public CImgException { + CImgWarningException(const char *const format, ...) { _cimg_exception_err("CImgWarningException",false); } + }; + + /*------------------------------------- + # + # Define cimg:: namespace + # + -----------------------------------*/ + //! Contains \a low-level functions and variables of the \CImg Library. + /** + Most of the functions and variables within this namespace are used by the \CImg library for low-level operations. + You may use them to access specific const values or environment variables internally used by \CImg. + \warning Never write using namespace cimg_library::cimg; in your source code. Lot of functions in the + cimg:: namespace have the same names as standard C functions that may be defined in the global + namespace ::. + **/ + namespace cimg { + + // Define traits that will be used to determine the best data type to work in CImg functions. + // + template struct type { + static const char* string() { + static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24", + "unknown32", "unknown40", "unknown48", "unknown56", + "unknown64", "unknown72", "unknown80", "unknown88", + "unknown96", "unknown104", "unknown112", "unknown120", + "unknown128" }; + return s[(sizeof(T)<17)?sizeof(T):0]; + } + static bool is_float() { return false; } + static bool is_inf(const T) { return false; } + static bool is_nan(const T) { return false; } + static T min() { return ~max(); } + static T max() { return (T)1<<(8*sizeof(T) - 1); } + static T inf() { return max(); } + static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const T& val) { static const char *const s = "unknown"; cimg::unused(val); return s; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "bool"; return s; } + static bool is_float() { return false; } + static bool is_inf(const bool) { return false; } + static bool is_nan(const bool) { return false; } + static bool min() { return false; } + static bool max() { return true; } + static bool inf() { return max(); } + static bool is_inf() { return false; } + static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned char) { return false; } + static bool is_nan(const unsigned char) { return false; } + static unsigned char min() { return 0; } + static unsigned char max() { return (unsigned char)-1; } + static unsigned char inf() { return max(); } + static unsigned char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned char val) { return (unsigned int)val; } + }; + +#if defined(CHAR_MAX) && CHAR_MAX==255 + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return 0; } + static char max() { return (char)-1; } + static char inf() { return max(); } + static char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const char val) { return (unsigned int)val; } + }; +#else + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return ~max(); } + static char max() { return (char)((unsigned char)-1>>1); } + static char inf() { return max(); } + static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const char val) { return (int)val; } + }; +#endif + + template<> struct type { + static const char* string() { static const char *const s = "signed char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const signed char) { return false; } + static bool is_nan(const signed char) { return false; } + static signed char min() { return ~max(); } + static signed char max() { return (signed char)((unsigned char)-1>>1); } + static signed char inf() { return max(); } + static signed char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(signed char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const signed char val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned short) { return false; } + static bool is_nan(const unsigned short) { return false; } + static unsigned short min() { return 0; } + static unsigned short max() { return (unsigned short)-1; } + static unsigned short inf() { return max(); } + static unsigned short cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned short val) { return (unsigned int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const short) { return false; } + static bool is_nan(const short) { return false; } + static short min() { return ~max(); } + static short max() { return (short)((unsigned short)-1>>1); } + static short inf() { return max(); } + static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const short val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned int) { return false; } + static bool is_nan(const unsigned int) { return false; } + static unsigned int min() { return 0; } + static unsigned int max() { return (unsigned int)-1; } + static unsigned int inf() { return max(); } + static unsigned int cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const int) { return false; } + static bool is_nan(const int) { return false; } + static int min() { return ~max(); } + static int max() { return (int)((unsigned int)-1>>1); } + static int inf() { return max(); } + static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_uint64) { return false; } + static bool is_nan(const cimg_uint64) { return false; } + static cimg_uint64 min() { return 0; } + static cimg_uint64 max() { return (cimg_uint64)-1; } + static cimg_uint64 inf() { return max(); } + static cimg_uint64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; } + static const char* format() { return cimg_fuint64; } + static const char* format_s() { return cimg_fuint64; } + static unsigned long format(const cimg_uint64 val) { return (unsigned long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_int64) { return false; } + static bool is_nan(const cimg_int64) { return false; } + static cimg_int64 min() { return ~max(); } + static cimg_int64 max() { return (cimg_int64)((cimg_uint64)-1>>1); } + static cimg_int64 inf() { return max(); } + static cimg_int64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; + } + static const char* format() { return cimg_fint64; } + static const char* format_s() { return cimg_fint64; } + static long format(const long val) { return (long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const double val) { // Custom version that works with '-ffast-math' + if (sizeof(double)==8) { + cimg_uint64 u; + std::memcpy(&u,&val,sizeof(double)); + return ((unsigned int)(u>>32)&0x7fffffff) + ((unsigned int)u!=0)>0x7ff00000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static double min() { return -DBL_MAX; } + static double max() { return DBL_MAX; } + static double inf() { +#ifdef INFINITY + return (double)INFINITY; +#else + return max()*max(); +#endif + } + static double nan() { +#ifdef NAN + return (double)NAN; +#else + const double val_nan = -std::sqrt(-1.); return val_nan; +#endif + } + static double cut(const double val) { return val; } + static const char* format() { return "%.17g"; } + static const char* format_s() { return "%g"; } + static double format(const double val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "float"; return s; } + static bool is_float() { return true; } + static bool is_inf(const float val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const float val) { // Custom version that works with '-ffast-math' + if (sizeof(float)==4) { + unsigned int u; + std::memcpy(&u,&val,sizeof(float)); + return (u&0x7fffffff)>0x7f800000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static float min() { return -FLT_MAX; } + static float max() { return FLT_MAX; } + static float inf() { return (float)cimg::type::inf(); } + static float nan() { return (float)cimg::type::nan(); } + static float cut(const double val) { return (float)val; } + static float cut(const float val) { return (float)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const float val) { return (double)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "long double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const long double val) { +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static long double min() { return -LDBL_MAX; } + static long double max() { return LDBL_MAX; } + static long double inf() { return max()*max(); } + static long double nan() { const long double val_nan = -std::sqrt(-1.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"; } + static double format(const long double val) { return (double)val; } + }; + +#ifdef cimg_use_half + template<> struct type { + static const char* string() { static const char *const s = "half"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const half val) { // Custom version that works with '-ffast-math' + if (sizeof(half)==2) { + short u; + std::memcpy(&u,&val,sizeof(short)); + return (bool)((u&0x7fff)>0x7c00); + } + return cimg::type::is_nan((float)val); + } + 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.); return val_nan; } + static half cut(const double val) { return (half)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const half val) { return (double)val; } + }; +#endif + + template struct superset { typedef T type; }; + template<> struct superset { typedef unsigned char type; }; + template<> struct superset { typedef char type; }; + template<> struct superset { typedef signed char type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 float type; }; + template<> struct superset { typedef double 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 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; }; + template<> struct superset { typedef double type; }; +#ifdef cimg_use_half + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; +#endif + + template struct superset2 { + typedef typename superset::type>::type type; + }; + + template struct superset3 { + typedef typename superset::type>::type type; + }; + + template struct last { typedef t2 type; }; + +#define _cimg_Tt 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 { + unsigned int nb_wins; + pthread_t *events_thread; + pthread_cond_t wait_event; + pthread_mutex_t wait_event_mutex; + CImgDisplay **wins; + Display *display; + unsigned int nb_bits; + bool is_blue_first; + bool is_shm_enabled; + bool byte_order; +#ifdef cimg_use_xrandr + XRRScreenSize *resolutions; + Rotation curr_rotation; + unsigned int curr_resolution; + unsigned int nb_resolutions; +#endif + X11_info():nb_wins(0),events_thread(0),display(0), + nb_bits(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) { +#ifdef __FreeBSD__ + XInitThreads(); +#endif + wins = new CImgDisplay*[1024]; + pthread_mutex_init(&wait_event_mutex,0); + pthread_cond_init(&wait_event,0); +#ifdef cimg_use_xrandr + resolutions = 0; + curr_rotation = 0; + curr_resolution = nb_resolutions = 0; +#endif + } + + ~X11_info() { + delete[] wins; + /* + if (events_thread) { + pthread_cancel(*events_thread); + delete events_thread; + } + if (display) { } // XCloseDisplay(display); } + pthread_cond_destroy(&wait_event); + pthread_mutex_unlock(&wait_event_mutex); + pthread_mutex_destroy(&wait_event_mutex); + */ + } + }; +#if defined(cimg_module) + X11_info& X11_attr(); +#elif defined(cimg_main) + X11_info& X11_attr() { static X11_info val; return val; } +#else + inline X11_info& X11_attr() { static X11_info val; return val; } +#endif + +#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 +#define cimg_lock_display() cimg::mutex(15) +#define cimg_unlock_display() cimg::mutex(15,0) + + struct Mutex_info { +#ifdef _PTHREAD_H + pthread_mutex_t mutex[32]; + Mutex_info() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); } + void lock(const unsigned int n) { pthread_mutex_lock(&mutex[n]); } + void unlock(const unsigned int n) { pthread_mutex_unlock(&mutex[n]); } + int trylock(const unsigned int n) { return pthread_mutex_trylock(&mutex[n]); } +#elif 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; } +#else + Mutex_info() {} + void lock(const unsigned int) {} + void unlock(const unsigned int) {} + int trylock(const unsigned int) { return 0; } +#endif + }; +#if defined(cimg_module) + Mutex_info& Mutex_attr(); +#elif defined(cimg_main) + Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#else + inline Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#endif + +#if defined(cimg_use_magick) + static struct Magick_info { + Magick_info() { + Magick::InitializeMagick(""); + } + } _Magick_info; +#endif + +#if cimg_display==1 + // Define keycodes for X11-based graphical systems. + const unsigned int keyESC = XK_Escape; + const unsigned int keyF1 = XK_F1; + const unsigned int keyF2 = XK_F2; + const unsigned int keyF3 = XK_F3; + const unsigned int keyF4 = XK_F4; + const unsigned int keyF5 = XK_F5; + const unsigned int keyF6 = XK_F6; + const unsigned int keyF7 = XK_F7; + const unsigned int keyF8 = XK_F8; + const unsigned int keyF9 = XK_F9; + const unsigned int keyF10 = XK_F10; + const unsigned int keyF11 = XK_F11; + const unsigned int keyF12 = XK_F12; + const unsigned int keyPAUSE = XK_Pause; + const unsigned int key1 = XK_1; + const unsigned int key2 = XK_2; + const unsigned int key3 = XK_3; + const unsigned int key4 = XK_4; + const unsigned int key5 = XK_5; + const unsigned int key6 = XK_6; + const unsigned int key7 = XK_7; + const unsigned int key8 = XK_8; + const unsigned int key9 = XK_9; + const unsigned int key0 = XK_0; + const unsigned int keyBACKSPACE = XK_BackSpace; + const unsigned int keyINSERT = XK_Insert; + const unsigned int keyHOME = XK_Home; + const unsigned int keyPAGEUP = XK_Page_Up; + const unsigned int keyTAB = XK_Tab; + const unsigned int keyQ = XK_q; + const unsigned int keyW = XK_w; + const unsigned int keyE = XK_e; + const unsigned int keyR = XK_r; + const unsigned int keyT = XK_t; + const unsigned int keyY = XK_y; + const unsigned int keyU = XK_u; + const unsigned int keyI = XK_i; + const unsigned int keyO = XK_o; + const unsigned int keyP = XK_p; + const unsigned int keyDELETE = XK_Delete; + const unsigned int keyEND = XK_End; + const unsigned int keyPAGEDOWN = XK_Page_Down; + const unsigned int keyCAPSLOCK = XK_Caps_Lock; + const unsigned int keyA = XK_a; + const unsigned int keyS = XK_s; + const unsigned int keyD = XK_d; + const unsigned int keyF = XK_f; + const unsigned int keyG = XK_g; + const unsigned int keyH = XK_h; + const unsigned int keyJ = XK_j; + const unsigned int keyK = XK_k; + const unsigned int keyL = XK_l; + const unsigned int keyENTER = XK_Return; + const unsigned int keySHIFTLEFT = XK_Shift_L; + const unsigned int keyZ = XK_z; + const unsigned int keyX = XK_x; + const unsigned int keyC = XK_c; + const unsigned int keyV = XK_v; + const unsigned int keyB = XK_b; + const unsigned int keyN = XK_n; + const unsigned int keyM = XK_m; + const unsigned int keySHIFTRIGHT = XK_Shift_R; + const unsigned int keyARROWUP = XK_Up; + const unsigned int keyCTRLLEFT = XK_Control_L; + const unsigned int keyAPPLEFT = XK_Super_L; + const unsigned int keyALT = XK_Alt_L; + const unsigned int keySPACE = XK_space; + const unsigned int keyALTGR = XK_Alt_R; + const unsigned int keyAPPRIGHT = XK_Super_R; + const unsigned int keyMENU = XK_Menu; + const unsigned int keyCTRLRIGHT = XK_Control_R; + const unsigned int keyARROWLEFT = XK_Left; + const unsigned int keyARROWDOWN = XK_Down; + const unsigned int keyARROWRIGHT = XK_Right; + const unsigned int keyPAD0 = XK_KP_0; + const unsigned int keyPAD1 = XK_KP_1; + const unsigned int keyPAD2 = XK_KP_2; + const unsigned int keyPAD3 = XK_KP_3; + const unsigned int keyPAD4 = XK_KP_4; + const unsigned int keyPAD5 = XK_KP_5; + const unsigned int keyPAD6 = XK_KP_6; + const unsigned int keyPAD7 = XK_KP_7; + const unsigned int keyPAD8 = XK_KP_8; + const unsigned int keyPAD9 = XK_KP_9; + const unsigned int keyPADADD = XK_KP_Add; + const unsigned int keyPADSUB = XK_KP_Subtract; + const unsigned int keyPADMUL = XK_KP_Multiply; + const unsigned int keyPADDIV = XK_KP_Divide; + +#elif cimg_display==2 + // Define keycodes for Windows. + const unsigned int keyESC = VK_ESCAPE; + const unsigned int keyF1 = VK_F1; + const unsigned int keyF2 = VK_F2; + const unsigned int keyF3 = VK_F3; + const unsigned int keyF4 = VK_F4; + const unsigned int keyF5 = VK_F5; + const unsigned int keyF6 = VK_F6; + const unsigned int keyF7 = VK_F7; + const unsigned int keyF8 = VK_F8; + const unsigned int keyF9 = VK_F9; + const unsigned int keyF10 = VK_F10; + const unsigned int keyF11 = VK_F11; + const unsigned int keyF12 = VK_F12; + const unsigned int keyPAUSE = VK_PAUSE; + const unsigned int key1 = '1'; + const unsigned int key2 = '2'; + const unsigned int key3 = '3'; + const unsigned int key4 = '4'; + const unsigned int key5 = '5'; + const unsigned int key6 = '6'; + const unsigned int key7 = '7'; + const unsigned int key8 = '8'; + const unsigned int key9 = '9'; + const unsigned int key0 = '0'; + const unsigned int keyBACKSPACE = VK_BACK; + const unsigned int keyINSERT = VK_INSERT; + const unsigned int keyHOME = VK_HOME; + const unsigned int keyPAGEUP = VK_PRIOR; + const unsigned int keyTAB = VK_TAB; + const unsigned int keyQ = 'Q'; + const unsigned int keyW = 'W'; + const unsigned int keyE = 'E'; + const unsigned int keyR = 'R'; + const unsigned int keyT = 'T'; + const unsigned int keyY = 'Y'; + const unsigned int keyU = 'U'; + const unsigned int keyI = 'I'; + const unsigned int keyO = 'O'; + const unsigned int keyP = 'P'; + const unsigned int keyDELETE = VK_DELETE; + const unsigned int keyEND = VK_END; + const unsigned int keyPAGEDOWN = VK_NEXT; + const unsigned int keyCAPSLOCK = VK_CAPITAL; + const unsigned int keyA = 'A'; + const unsigned int keyS = 'S'; + const unsigned int keyD = 'D'; + const unsigned int keyF = 'F'; + const unsigned int keyG = 'G'; + const unsigned int keyH = 'H'; + const unsigned int keyJ = 'J'; + const unsigned int keyK = 'K'; + const unsigned int keyL = 'L'; + const unsigned int keyENTER = VK_RETURN; + const unsigned int keySHIFTLEFT = VK_SHIFT; + const unsigned int keyZ = 'Z'; + const unsigned int keyX = 'X'; + const unsigned int keyC = 'C'; + const unsigned int keyV = 'V'; + const unsigned int keyB = 'B'; + const unsigned int keyN = 'N'; + const unsigned int keyM = 'M'; + const unsigned int keySHIFTRIGHT = VK_SHIFT; + const unsigned int keyARROWUP = VK_UP; + const unsigned int keyCTRLLEFT = VK_CONTROL; + const unsigned int keyAPPLEFT = VK_LWIN; + const unsigned int keyALT = VK_LMENU; + const unsigned int keySPACE = VK_SPACE; + const unsigned int keyALTGR = VK_CONTROL; + const unsigned int keyAPPRIGHT = VK_RWIN; + const unsigned int keyMENU = VK_APPS; + const unsigned int keyCTRLRIGHT = VK_CONTROL; + const unsigned int keyARROWLEFT = VK_LEFT; + const unsigned int keyARROWDOWN = VK_DOWN; + const unsigned int keyARROWRIGHT = VK_RIGHT; + const unsigned int keyPAD0 = 0x60; + const unsigned int keyPAD1 = 0x61; + const unsigned int keyPAD2 = 0x62; + const unsigned int keyPAD3 = 0x63; + const unsigned int keyPAD4 = 0x64; + const unsigned int keyPAD5 = 0x65; + const unsigned int keyPAD6 = 0x66; + const unsigned int keyPAD7 = 0x67; + const unsigned int keyPAD8 = 0x68; + const unsigned int keyPAD9 = 0x69; + const unsigned int keyPADADD = VK_ADD; + const unsigned int keyPADSUB = VK_SUBTRACT; + const unsigned int keyPADMUL = VK_MULTIPLY; + const unsigned int keyPADDIV = VK_DIVIDE; + +#else + // Define random keycodes when no display is available. + // (should rarely be used then!). + const unsigned int keyESC = 1U; //!< Keycode for the \c ESC key (architecture-dependent) + const unsigned int keyF1 = 2U; //!< Keycode for the \c F1 key (architecture-dependent) + const unsigned int keyF2 = 3U; //!< Keycode for the \c F2 key (architecture-dependent) + const unsigned int keyF3 = 4U; //!< Keycode for the \c F3 key (architecture-dependent) + const unsigned int keyF4 = 5U; //!< Keycode for the \c F4 key (architecture-dependent) + const unsigned int keyF5 = 6U; //!< Keycode for the \c F5 key (architecture-dependent) + const unsigned int keyF6 = 7U; //!< Keycode for the \c F6 key (architecture-dependent) + const unsigned int keyF7 = 8U; //!< Keycode for the \c F7 key (architecture-dependent) + const unsigned int keyF8 = 9U; //!< Keycode for the \c F8 key (architecture-dependent) + const unsigned int keyF9 = 10U; //!< Keycode for the \c F9 key (architecture-dependent) + const unsigned int keyF10 = 11U; //!< Keycode for the \c F10 key (architecture-dependent) + const unsigned int keyF11 = 12U; //!< Keycode for the \c F11 key (architecture-dependent) + const unsigned int keyF12 = 13U; //!< Keycode for the \c F12 key (architecture-dependent) + const unsigned int keyPAUSE = 14U; //!< Keycode for the \c PAUSE key (architecture-dependent) + const unsigned int key1 = 15U; //!< Keycode for the \c 1 key (architecture-dependent) + const unsigned int key2 = 16U; //!< Keycode for the \c 2 key (architecture-dependent) + const unsigned int key3 = 17U; //!< Keycode for the \c 3 key (architecture-dependent) + const unsigned int key4 = 18U; //!< Keycode for the \c 4 key (architecture-dependent) + const unsigned int key5 = 19U; //!< Keycode for the \c 5 key (architecture-dependent) + const unsigned int key6 = 20U; //!< Keycode for the \c 6 key (architecture-dependent) + const unsigned int key7 = 21U; //!< Keycode for the \c 7 key (architecture-dependent) + const unsigned int key8 = 22U; //!< Keycode for the \c 8 key (architecture-dependent) + const unsigned int key9 = 23U; //!< Keycode for the \c 9 key (architecture-dependent) + const unsigned int key0 = 24U; //!< Keycode for the \c 0 key (architecture-dependent) + const unsigned int keyBACKSPACE = 25U; //!< Keycode for the \c BACKSPACE key (architecture-dependent) + const unsigned int keyINSERT = 26U; //!< Keycode for the \c INSERT key (architecture-dependent) + const unsigned int keyHOME = 27U; //!< Keycode for the \c HOME key (architecture-dependent) + const unsigned int keyPAGEUP = 28U; //!< Keycode for the \c PAGEUP key (architecture-dependent) + const unsigned int keyTAB = 29U; //!< Keycode for the \c TAB key (architecture-dependent) + const unsigned int keyQ = 30U; //!< Keycode for the \c Q key (architecture-dependent) + const unsigned int keyW = 31U; //!< Keycode for the \c W key (architecture-dependent) + const unsigned int keyE = 32U; //!< Keycode for the \c E key (architecture-dependent) + const unsigned int keyR = 33U; //!< Keycode for the \c R key (architecture-dependent) + const unsigned int keyT = 34U; //!< Keycode for the \c T key (architecture-dependent) + const unsigned int keyY = 35U; //!< Keycode for the \c Y key (architecture-dependent) + const unsigned int keyU = 36U; //!< Keycode for the \c U key (architecture-dependent) + const unsigned int keyI = 37U; //!< Keycode for the \c I key (architecture-dependent) + const unsigned int keyO = 38U; //!< Keycode for the \c O key (architecture-dependent) + const unsigned int keyP = 39U; //!< Keycode for the \c P key (architecture-dependent) + const unsigned int keyDELETE = 40U; //!< Keycode for the \c DELETE key (architecture-dependent) + const unsigned int keyEND = 41U; //!< Keycode for the \c END key (architecture-dependent) + const unsigned int keyPAGEDOWN = 42U; //!< Keycode for the \c PAGEDOWN key (architecture-dependent) + const unsigned int keyCAPSLOCK = 43U; //!< Keycode for the \c CAPSLOCK key (architecture-dependent) + const unsigned int keyA = 44U; //!< Keycode for the \c A key (architecture-dependent) + const unsigned int keyS = 45U; //!< Keycode for the \c S key (architecture-dependent) + const unsigned int keyD = 46U; //!< Keycode for the \c D key (architecture-dependent) + const unsigned int keyF = 47U; //!< Keycode for the \c F key (architecture-dependent) + const unsigned int keyG = 48U; //!< Keycode for the \c G key (architecture-dependent) + const unsigned int keyH = 49U; //!< Keycode for the \c H key (architecture-dependent) + const unsigned int keyJ = 50U; //!< Keycode for the \c J key (architecture-dependent) + const unsigned int keyK = 51U; //!< Keycode for the \c K key (architecture-dependent) + const unsigned int keyL = 52U; //!< Keycode for the \c L key (architecture-dependent) + const unsigned int keyENTER = 53U; //!< Keycode for the \c ENTER key (architecture-dependent) + const unsigned int keySHIFTLEFT = 54U; //!< Keycode for the \c SHIFTLEFT key (architecture-dependent) + const unsigned int keyZ = 55U; //!< Keycode for the \c Z key (architecture-dependent) + const unsigned int keyX = 56U; //!< Keycode for the \c X key (architecture-dependent) + const unsigned int keyC = 57U; //!< Keycode for the \c C key (architecture-dependent) + const unsigned int keyV = 58U; //!< Keycode for the \c V key (architecture-dependent) + const unsigned int keyB = 59U; //!< Keycode for the \c B key (architecture-dependent) + const unsigned int keyN = 60U; //!< Keycode for the \c N key (architecture-dependent) + const unsigned int keyM = 61U; //!< Keycode for the \c M key (architecture-dependent) + const unsigned int keySHIFTRIGHT = 62U; //!< Keycode for the \c SHIFTRIGHT key (architecture-dependent) + const unsigned int keyARROWUP = 63U; //!< Keycode for the \c ARROWUP key (architecture-dependent) + const unsigned int keyCTRLLEFT = 64U; //!< Keycode for the \c CTRLLEFT key (architecture-dependent) + const unsigned int keyAPPLEFT = 65U; //!< Keycode for the \c APPLEFT key (architecture-dependent) + const unsigned int keyALT = 66U; //!< Keycode for the \c ALT key (architecture-dependent) + const unsigned int keySPACE = 67U; //!< Keycode for the \c SPACE key (architecture-dependent) + const unsigned int keyALTGR = 68U; //!< Keycode for the \c ALTGR key (architecture-dependent) + const unsigned int keyAPPRIGHT = 69U; //!< Keycode for the \c APPRIGHT key (architecture-dependent) + const unsigned int keyMENU = 70U; //!< Keycode for the \c MENU key (architecture-dependent) + const unsigned int keyCTRLRIGHT = 71U; //!< Keycode for the \c CTRLRIGHT key (architecture-dependent) + const unsigned int keyARROWLEFT = 72U; //!< Keycode for the \c ARROWLEFT key (architecture-dependent) + const unsigned int keyARROWDOWN = 73U; //!< Keycode for the \c ARROWDOWN key (architecture-dependent) + const unsigned int keyARROWRIGHT = 74U; //!< Keycode for the \c ARROWRIGHT key (architecture-dependent) + const unsigned int keyPAD0 = 75U; //!< Keycode for the \c PAD0 key (architecture-dependent) + const unsigned int keyPAD1 = 76U; //!< Keycode for the \c PAD1 key (architecture-dependent) + const unsigned int keyPAD2 = 77U; //!< Keycode for the \c PAD2 key (architecture-dependent) + const unsigned int keyPAD3 = 78U; //!< Keycode for the \c PAD3 key (architecture-dependent) + const unsigned int keyPAD4 = 79U; //!< Keycode for the \c PAD4 key (architecture-dependent) + const unsigned int keyPAD5 = 80U; //!< Keycode for the \c PAD5 key (architecture-dependent) + const unsigned int keyPAD6 = 81U; //!< Keycode for the \c PAD6 key (architecture-dependent) + const unsigned int keyPAD7 = 82U; //!< Keycode for the \c PAD7 key (architecture-dependent) + const unsigned int keyPAD8 = 83U; //!< Keycode for the \c PAD8 key (architecture-dependent) + const unsigned int keyPAD9 = 84U; //!< Keycode for the \c PAD9 key (architecture-dependent) + const unsigned int keyPADADD = 85U; //!< Keycode for the \c PADADD key (architecture-dependent) + const unsigned int keyPADSUB = 86U; //!< Keycode for the \c PADSUB key (architecture-dependent) + const unsigned int keyPADMUL = 87U; //!< Keycode for the \c PADMUL key (architecture-dependent) + const unsigned int keyPADDIV = 88U; //!< Keycode for the \c PADDDIV key (architecture-dependent) +#endif + + const double PI = 3.14159265358979323846; //!< Value of the mathematical constant PI + + // Define a 10x13 binary font (small sans). + static const char *const data_font_small[] = { + " UwlwnwoyuwHwlwmwcwlwnw[xuwowlwmwoyuwRwlwnxcw Mw (wnwnwuwpwuypwuwoy" + "ZwnwmwuwowuwmwnwnwuwowuwfwuxnwnwmwuwpwuypwuwZwnwnwtwpwtwow'y Hw cwnw >{ 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 " + "rwswewnwuwowuwozswtwuwqwtwmwnwlwowuwuwowOxpxuxqwuwowswqwswoxpwlwjwqwswqwswy~}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 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

    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

    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 std::printf(). + \note If configuration macro \c cimg_strict_warnings is set, this function throws a + \c CImgWarningException instead. + \warning As the first argument is a format string, it is highly recommended to write + \code + cimg::warn("%s",warning_message); + \endcode + instead of + \code + cimg::warn(warning_message); + \endcode + if \c warning_message can be arbitrary, to prevent nasty memory access. + **/ + inline void warn(const char *const format, ...) { + if (cimg::exception_mode()>=1) { + char *const message = new char[16384]; + std::va_list ap; + va_start(ap,format); + cimg_vsnprintf(message,16384,format,ap); + va_end(ap); +#ifdef cimg_strict_warnings + throw CImgWarningException(message); +#else + std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s\n",cimg::t_red,cimg::t_normal,message); +#endif + delete[] message; + } + } + + // Execute an external system command. + /** + \param command C-string containing the command line to execute. + \param module_name Module name. + \return Status value of the executed command, whose meaning is OS-dependent. + \note This function is similar to std::system() + but it does not open an extra console windows + on Windows-based systems. + **/ + inline int system(const char *const command, const char *const module_name=0, const bool is_verbose=false) { + cimg::unused(module_name); +#ifdef cimg_no_system_calls + return -1; +#else + if (is_verbose) return std::system(command); +#if cimg_OS==1 + const unsigned int l = (unsigned int)std::strlen(command); + if (l) { + char *const ncommand = new char[l + 24]; + std::memcpy(ncommand,command,l); + 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; + std::memset(&pi,0,sizeof(PROCESS_INFORMATION)); + std::memset(&si,0,sizeof(STARTUPINFO)); + GetStartupInfo(&si); + si.cb = sizeof(si); + si.wShowWindow = SW_HIDE; + si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW; + const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi); + if (res) { + WaitForSingleObject(pi.hProcess,INFINITE); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; + } else return std::system(command); +#else + return std::system(command); +#endif +#endif + } + + //! Return a reference to a temporary variable of type T. + template + inline T& temporary(const T&) { + static T temp; + return temp; + } + + //! Exchange values of variables \c a and \c b. + template + inline void swap(T& a, T& b) { T t = a; a = b; b = t; } + + //! Exchange values of variables (\c a1,\c a2) and (\c b1,\c b2). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) { + cimg::swap(a1,b1); cimg::swap(a2,b2); + } + + //! Exchange values of variables (\c a1,\c a2,\c a3) and (\c b1,\c b2,\c b3). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) { + cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a4) and (\c b1,\c b2,...,\c b4). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) { + cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a5) and (\c b1,\c b2,...,\c b5). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a6) and (\c b1,\c b2,...,\c b6). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a7) and (\c b1,\c b2,...,\c b7). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a8) and (\c b1,\c b2,...,\c b8). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7, T8& a8, T8& b8) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8); + } + + //! Return the endianness of the current architecture. + /** + \return \c false for Little Endian or \c true for Big Endian. + **/ + inline bool endianness() { + const int x = 1; + return ((unsigned char*)&x)[0]?false:true; + } + + //! Reverse endianness of all elements in a memory buffer. + /** + \param[in,out] buffer Memory buffer whose endianness must be reversed. + \param size Number of buffer elements to reverse. + **/ + template + inline void invert_endianness(T* const buffer, const cimg_ulong size) { + if (size) switch (sizeof(T)) { + case 1 : break; + case 2 : { + for (unsigned short *ptr = (unsigned short*)buffer + size; ptr>(unsigned short*)buffer; ) { + const unsigned short val = *(--ptr); + *ptr = (unsigned short)((val>>8) | ((val<<8))); + } + } break; + case 4 : { + for (unsigned int *ptr = (unsigned int*)buffer + size; ptr>(unsigned int*)buffer; ) { + const unsigned int val = *(--ptr); + *ptr = (val>>24) | ((val>>8)&0xff00) | ((val<<8)&0xff0000) | (val<<24); + } + } break; + case 8 : { + const cimg_uint64 + m0 = (cimg_uint64)0xff, m1 = m0<<8, m2 = m0<<16, m3 = m0<<24, + m4 = m0<<32, m5 = m0<<40, m6 = m0<<48, m7 = m0<<56; + for (cimg_uint64 *ptr = (cimg_uint64*)buffer + size; ptr>(cimg_uint64*)buffer; ) { + const cimg_uint64 val = *(--ptr); + *ptr = (((val&m7)>>56) | ((val&m6)>>40) | ((val&m5)>>24) | ((val&m4)>>8) | + ((val&m3)<<8) |((val&m2)<<24) | ((val&m1)<<40) | ((val&m0)<<56)); + } + } break; + default : { + for (T* ptr = buffer + size; ptr>buffer; ) { + unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T); + for (int i = 0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe)); + } + } + } + } + + //! Reverse endianness of a single variable. + /** + \param[in,out] a Variable to reverse. + \return Reference to reversed variable. + **/ + template + inline T& invert_endianness(T& a) { + invert_endianness(&a,1); + return a; + } + + // Conversion functions to get more precision when trying to store unsigned ints values as floats. + inline unsigned int float2uint(const float f) { + int tmp = 0; + std::memcpy(&tmp,&f,sizeof(float)); + if (tmp>=0) return (unsigned int)f; + unsigned int u; + // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&u,&f,sizeof(float)); + return ((u)<<1)>>1; // set sign bit to 0 + } + + inline float uint2float(const unsigned int u) { + if (u<(1U<<19)) return (float)u; // Consider safe storage of unsigned int as floats until 19bits (i.e 524287) + float f; + const unsigned int v = u|(1U<<(8*sizeof(unsigned int)-1)); // set sign bit to 1 + // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&f,&v,sizeof(float)); + return f; + } + + //! Return the value of a system timer, with a millisecond precision. + /** + \note The timer does not necessarily starts from \c 0. + **/ + inline cimg_ulong time() { +#if cimg_OS==1 + struct timeval st_time; + gettimeofday(&st_time,0); + return (cimg_ulong)(st_time.tv_usec/1000 + st_time.tv_sec*1000); +#elif cimg_OS==2 + SYSTEMTIME st_time; + GetLocalTime(&st_time); + return (cimg_ulong)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour))); +#else + return 0; +#endif + } + + // Implement a tic/toc mechanism to display elapsed time of algorithms. + inline cimg_ulong tictoc(const bool is_tic); + + //! Start tic/toc timer for time measurement between code instructions. + /** + \return Current value of the timer (same value as time()). + **/ + inline cimg_ulong tic() { + return cimg::tictoc(true); + } + + //! End tic/toc timer and displays elapsed time from last call to tic(). + /** + \return Time elapsed (in ms) since last call to tic(). + **/ + inline cimg_ulong toc() { + return cimg::tictoc(false); + } + + //! Sleep for a given numbers of milliseconds. + /** + \param milliseconds Number of milliseconds to wait for. + \note This function frees the CPU ressources during the sleeping time. + It can be used to temporize your program properly, without wasting CPU time. + **/ + inline void sleep(const unsigned int milliseconds) { +#if cimg_OS==1 + struct timespec tv; + tv.tv_sec = milliseconds/1000; + tv.tv_nsec = (milliseconds%1000)*1000000; + nanosleep(&tv,0); +#elif cimg_OS==2 + Sleep(milliseconds); +#else + cimg::unused(milliseconds); +#endif + } + + inline unsigned int wait(const unsigned int milliseconds, cimg_ulong *const p_timer) { + if (!*p_timer) *p_timer = cimg::time(); + const cimg_ulong current_time = cimg::time(); + if (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; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \return Number of milliseconds elapsed since the last call to wait(). + \note Same as sleep() with a waiting time computed with regard to the last call + of wait(). It may be used to temporize your program properly, without wasting CPU time. + **/ + inline cimg_long wait(const unsigned int milliseconds) { + cimg::mutex(3); + static cimg_ulong timer = cimg::time(); + cimg::mutex(3,0); + return cimg::wait(milliseconds,&timer); + } + + // Custom random number generator (allow re-entrance). + inline cimg_ulong& rng() { // Used as a shared global number for rng + static cimg_ulong rng = 0xB16B00B5U; + return rng; + } + + inline unsigned int _rand(cimg_ulong *const p_rng) { + *p_rng = *p_rng*1103515245 + 12345U; + return (unsigned int)*p_rng; + } + + inline unsigned int _rand() { + cimg::mutex(4); + const unsigned int res = cimg::_rand(&cimg::rng()); + cimg::mutex(4,0); + return res; + } + + inline void srand(cimg_ulong *const p_rng) { +#if cimg_OS==1 + *p_rng = cimg::time() + (cimg_ulong)getpid(); +#elif cimg_OS==2 + *p_rng = cimg::time() + (cimg_ulong)_getpid(); +#endif + } + + inline void srand() { + cimg::mutex(4); + cimg::srand(&cimg::rng()); + cimg::mutex(4,0); + } + + inline void srand(const cimg_ulong seed) { + cimg::mutex(4); + cimg::rng() = seed; + cimg::mutex(4,0); + } + + inline double rand(const double val_min, const double val_max, cimg_ulong *const p_rng) { + const double val = cimg::_rand(p_rng)/(double)~0U; + return val_min + (val_max - val_min)*val; + } + + 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; + } + + inline double rand(const double val_max, cimg_ulong *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_ulong *const p_rng) { + double x1, w; + do { + 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.); + return x1*std::sqrt((-2*std::log(w))/w); + } + + 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_ulong *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.; 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; + } + + //! Bitwise-rotate value on the left. + template + inline T rol(const T& a, const unsigned int n=1) { + return n?(T)((a<>((sizeof(T)<<3) - n))):a; + } + + inline float rol(const float a, const unsigned int n=1) { + return (float)rol((int)a,n); + } + + inline double rol(const double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + + inline double rol(const long double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half rol(const half a, const unsigned int n=1) { + return (half)rol((int)a,n); + } +#endif + + //! Bitwise-rotate value on the right. + template + inline T ror(const T& a, const unsigned int n=1) { + return n?(T)((a>>n)|(a<<((sizeof(T)<<3) - n))):a; + } + + inline float ror(const float a, const unsigned int n=1) { + return (float)ror((int)a,n); + } + + inline double ror(const double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + + inline double ror(const long double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half ror(const half a, const unsigned int n=1) { + return (half)ror((int)a,n); + } +#endif + + //! Return absolute value of a value. + template + inline T abs(const T& a) { + return a>=0?a:-a; + } + inline bool abs(const bool a) { + return a; + } + inline int abs(const unsigned char a) { + return (int)a; + } + inline int abs(const unsigned short a) { + return (int)a; + } + inline int abs(const unsigned int a) { + return (int)a; + } + inline int abs(const int a) { + return std::abs(a); + } + inline cimg_int64 abs(const cimg_uint64 a) { + return (cimg_int64)a; + } + inline double abs(const double a) { + return std::fabs(a); + } + inline float abs(const float a) { + return (float)std::fabs((double)a); + } + + //! Return hyperbolic arcosine of a value. + inline double acosh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::acosh(x); +#else + return std::log(x + std::sqrt(x*x - 1)); +#endif + } + + //! Return hyperbolic arcsine of a value. + inline double asinh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::asinh(x); +#else + return std::log(x + std::sqrt(x*x + 1)); +#endif + } + + //! Return hyperbolic arctangent of a value. + inline double atanh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::atanh(x); +#else + return 0.5*std::log((1. + x)/(1. - x)); +#endif + } + + //! Return the sinc of a given value. + inline double sinc(const double x) { + return x?std::sin(x)/x:1; + } + + //! Return base-2 logarithm of a value. + inline double log2(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::log2(x); +#else + const double base2 = std::log(2.); + return std::log(x)/base2; +#endif + } + + //! Return square of a value. + template + inline T sqr(const T& val) { + return val*val; + } + + //! 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./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) { + return std::min(std::min(a,b),c); + } + + //! Return the minimum between four values. + template + inline t min(const t& a, const t& b, const t& c, const t& d) { + return std::min(std::min(a,b),std::min(c,d)); + } + + //! Return the maximum between three values. + template + inline t max(const t& a, const t& b, const t& c) { + return std::max(std::max(a,b),c); + } + + //! Return the maximum between four values. + template + inline t max(const t& a, const t& b, const t& c, const t& d) { + return std::max(std::max(a,b),std::max(c,d)); + } + + //! Return the sign of a value. + template + inline T sign(const T& x) { + return (T)(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; + while (x>i) i<<=1; + return i; + } + + //! Return the modulo of a value. + /** + \param x Input value. + \param m Modulo value. + \note This modulo function accepts negative and floating-points modulo numbers, as well as variables of any type. + **/ + template + inline T mod(const T& x, const T& m) { + const double dx = (double)x, dm = (double)m; + return (T)(dx - dm * std::floor(dx / dm)); + } + inline int mod(const bool x, const bool m) { + return m?(x?1:0):0; + } + inline int mod(const unsigned char x, const unsigned char m) { + return x%m; + } + inline int mod(const char x, const char m) { +#if defined(CHAR_MAX) && CHAR_MAX==255 + return x%m; +#else + return x>=0?x%m:(x%m?m + x%m:0); +#endif + } + inline int mod(const unsigned short x, const unsigned short m) { + return x%m; + } + inline int mod(const short x, const short m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline int mod(const unsigned int x, const unsigned int m) { + return (int)(x%m); + } + inline int mod(const int x, const int m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline cimg_int64 mod(const cimg_uint64 x, const cimg_uint64 m) { + return x%m; + } + inline cimg_int64 mod(const cimg_int64 x, const cimg_int64 m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + + //! Return the min-mod of two values. + /** + \note minmod(\p a,\p b) is defined to be: + - minmod(\p a,\p b) = min(\p a,\p b), if \p a and \p b have the same sign. + - minmod(\p a,\p b) = 0, if \p a and \p b have different signs. + **/ + template + inline T minmod(const T& a, const T& b) { + return a*b<=0?0:(a>0?(a + inline T round(const T& x) { + return (T)std::floor((_cimg_Tfloat)x + 0.5f); + } + + //! Return rounded value. + /** + \param x Value to be rounded. + \param y Rounding precision. + \param rounding_type Type of rounding operation (\c 0 = nearest, \c -1 = backward, \c 1 = forward). + \return Rounded value, having the same type as input value \c x. + **/ + template + inline T round(const T& x, const double y, const int rounding_type=0) { + if (y<=0) return x; + if (y==1) switch (rounding_type) { + case 0 : return cimg::round(x); + case 1 : return (T)std::ceil((_cimg_Tfloat)x); + default : return (T)std::floor((_cimg_Tfloat)x); + } + const double sx = (double)x/y, floor = std::floor(sx), delta = sx - floor; + return (T)(y*(rounding_type<0?floor:rounding_type>0?std::ceil(sx):delta<0.5?floor:std::ceil(sx))); + } + + // Code to compute fast median from 2,3,5,7,9,13,25 and 49 values. + // (contribution by RawTherapee: http://rawtherapee.com/). + template + inline T median(T val0, T val1) { + return (val0 + val1)/2; + } + + template + inline T median(T val0, T val1, T val2) { + return std::max(std::min(val0,val1),std::min(val2,std::max(val0,val1))); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = std::max(val0,tmp); val1 = std::min(val1,val4); tmp = std::min(val1,val2); val2 = std::max(val1,val2); + val1 = tmp; tmp = std::min(val2,val3); + return std::max(val1,tmp); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6) { + T tmp = std::min(val0,val5); + val5 = std::max(val0,val5); val0 = tmp; tmp = std::min(val0,val3); val3 = std::max(val0,val3); val0 = tmp; + tmp = std::min(val1,val6); val6 = std::max(val1,val6); val1 = tmp; tmp = std::min(val2,val4); + val4 = std::max(val2,val4); val2 = tmp; val1 = std::max(val0,val1); tmp = std::min(val3,val5); + val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); + val3 = std::max(tmp,val3); val3 = std::min(val3,val6); tmp = std::min(val4,val5); val4 = std::max(val1,tmp); + tmp = std::min(val1,tmp); val3 = std::max(tmp,val3); + return std::min(val3,val4); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8) { + T tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val7 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); + val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val6,val7); + val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val3 = std::max(val0,val3); val5 = std::min(val5,val8); + val7 = std::max(val4,tmp); tmp = std::min(val4,tmp); val6 = std::max(val3,val6); + val4 = std::max(val1,tmp); val2 = std::min(val2,val5); val4 = std::min(val4,val7); + tmp = std::min(val4,val2); val2 = std::max(val4,val2); val4 = std::max(val6,tmp); + return std::min(val4,val2); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8, T val9, T val10, T val11, + T val12) { + T tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = tmp; tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; + tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val5,val8); + val8 = std::max(val5,val8); val5 = tmp; tmp = std::min(val0,val12); val12 = std::max(val0,val12); + val0 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val2,val3); val3 = std::max(val2,val3); val2 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val8,val11); + val11 = std::max(val8,val11); val8 = tmp; tmp = std::min(val7,val12); val12 = std::max(val7,val12); val7 = tmp; + tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val10,val11); val11 = std::max(val10,val11); val10 = tmp; tmp = std::min(val1,val4); + val4 = std::max(val1,val4); val1 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val8); val8 = std::max(val7,val8); val7 = tmp; val11 = std::min(val11,val12); + tmp = std::min(val4,val9); val9 = std::max(val4,val9); val4 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; + tmp = std::min(val5,val6); val6 = std::max(val5,val6); val5 = tmp; val8 = std::min(val8,val9); + val10 = std::min(val10,val11); tmp = std::min(val1,val7); val7 = std::max(val1,val7); val1 = tmp; + tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; val3 = std::max(val1,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; val8 = std::min(val8,val10); + val5 = std::max(val0,val5); val5 = std::max(val2,val5); tmp = std::min(val6,val8); val8 = std::max(val6,val8); + val5 = std::max(val3,val5); val7 = std::min(val7,val8); val6 = std::max(val4,tmp); tmp = std::min(val4,tmp); + val5 = std::max(tmp,val5); val6 = std::min(val6,val7); + return std::max(val5,val6); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, + T val5, T val6, T val7, T val8, T val9, + T val10, T val11, T val12, T val13, T val14, + T val15, T val16, T val17, T val18, T val19, + T val20, T val21, T val22, T val23, T val24) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); val2 = std::min(tmp,val3); + val3 = std::max(tmp,val3); tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; + tmp = std::min(val5,val7); val7 = std::max(val5,val7); val5 = std::min(tmp,val6); val6 = std::max(tmp,val6); + tmp = std::min(val9,val10); val10 = std::max(val9,val10); val9 = tmp; tmp = std::min(val8,val10); + val10 = std::max(val8,val10); val8 = std::min(tmp,val9); val9 = std::max(tmp,val9); + tmp = std::min(val12,val13); val13 = std::max(val12,val13); val12 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val15,val16); val16 = std::max(val15,val16); val15 = tmp; tmp = std::min(val14,val16); + val16 = std::max(val14,val16); val14 = std::min(tmp,val15); val15 = std::max(tmp,val15); + tmp = std::min(val18,val19); val19 = std::max(val18,val19); val18 = tmp; tmp = std::min(val17,val19); + val19 = std::max(val17,val19); val17 = std::min(tmp,val18); val18 = std::max(tmp,val18); + tmp = std::min(val21,val22); val22 = std::max(val21,val22); val21 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = std::min(tmp,val21); val21 = std::max(tmp,val21); + tmp = std::min(val23,val24); val24 = std::max(val23,val24); val23 = tmp; tmp = std::min(val2,val5); + val5 = std::max(val2,val5); val2 = tmp; tmp = std::min(val3,val6); val6 = std::max(val3,val6); val3 = tmp; + tmp = std::min(val0,val6); val6 = std::max(val0,val6); val0 = std::min(tmp,val3); val3 = std::max(tmp,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = std::min(tmp,val4); val4 = std::max(tmp,val4); tmp = std::min(val11,val14); + val14 = std::max(val11,val14); val11 = tmp; tmp = std::min(val8,val14); val14 = std::max(val8,val14); + val8 = std::min(tmp,val11); val11 = std::max(tmp,val11); tmp = std::min(val12,val15); + val15 = std::max(val12,val15); val12 = tmp; tmp = std::min(val9,val15); val15 = std::max(val9,val15); + val9 = std::min(tmp,val12); val12 = std::max(tmp,val12); tmp = std::min(val13,val16); + val16 = std::max(val13,val16); val13 = tmp; tmp = std::min(val10,val16); val16 = std::max(val10,val16); + val10 = std::min(tmp,val13); val13 = std::max(tmp,val13); tmp = std::min(val20,val23); + val23 = std::max(val20,val23); val20 = tmp; tmp = std::min(val17,val23); val23 = std::max(val17,val23); + val17 = std::min(tmp,val20); val20 = std::max(tmp,val20); tmp = std::min(val21,val24); + val24 = std::max(val21,val24); val21 = tmp; tmp = std::min(val18,val24); val24 = std::max(val18,val24); + val18 = std::min(tmp,val21); val21 = std::max(tmp,val21); tmp = std::min(val19,val22); + val22 = std::max(val19,val22); val19 = tmp; val17 = std::max(val8,val17); tmp = std::min(val9,val18); + val18 = std::max(val9,val18); val9 = tmp; tmp = std::min(val0,val18); val18 = std::max(val0,val18); + val9 = std::max(tmp,val9); tmp = std::min(val10,val19); val19 = std::max(val10,val19); val10 = tmp; + tmp = std::min(val1,val19); val19 = std::max(val1,val19); val1 = std::min(tmp,val10); + val10 = std::max(tmp,val10); tmp = std::min(val11,val20); val20 = std::max(val11,val20); val11 = tmp; + tmp = std::min(val2,val20); val20 = std::max(val2,val20); val11 = std::max(tmp,val11); + tmp = std::min(val12,val21); val21 = std::max(val12,val21); val12 = tmp; tmp = std::min(val3,val21); + val21 = std::max(val3,val21); val3 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val13,val22); val22 = std::max(val13,val22); val4 = std::min(val4,val22); + val13 = std::max(val4,tmp); tmp = std::min(val4,tmp); val4 = tmp; tmp = std::min(val14,val23); + val23 = std::max(val14,val23); val14 = tmp; tmp = std::min(val5,val23); val23 = std::max(val5,val23); + val5 = std::min(tmp,val14); val14 = std::max(tmp,val14); tmp = std::min(val15,val24); + val24 = std::max(val15,val24); val15 = tmp; val6 = std::min(val6,val24); tmp = std::min(val6,val15); + val15 = std::max(val6,val15); val6 = tmp; tmp = std::min(val7,val16); val7 = std::min(tmp,val19); + tmp = std::min(val13,val21); val15 = std::min(val15,val23); tmp = std::min(val7,tmp); + val7 = std::min(tmp,val15); val9 = std::max(val1,val9); val11 = std::max(val3,val11); + val17 = std::max(val5,val17); val17 = std::max(val11,val17); val17 = std::max(val9,val17); + tmp = std::min(val4,val10); val10 = std::max(val4,val10); val4 = tmp; tmp = std::min(val6,val12); + val12 = std::max(val6,val12); val6 = tmp; tmp = std::min(val7,val14); val14 = std::max(val7,val14); + val7 = tmp; tmp = std::min(val4,val6); val6 = std::max(val4,val6); val7 = std::max(tmp,val7); + tmp = std::min(val12,val14); val14 = std::max(val12,val14); val12 = tmp; val10 = std::min(val10,val14); + tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val10,val12); + val12 = std::max(val10,val12); val10 = std::max(val6,tmp); tmp = std::min(val6,tmp); + val17 = std::max(tmp,val17); tmp = std::min(val12,val17); val17 = std::max(val12,val17); val12 = tmp; + val7 = std::min(val7,val17); tmp = std::min(val7,val10); val10 = std::max(val7,val10); val7 = tmp; + tmp = std::min(val12,val18); val18 = std::max(val12,val18); val12 = std::max(val7,tmp); + val10 = std::min(val10,val18); tmp = std::min(val12,val20); val20 = std::max(val12,val20); val12 = tmp; + tmp = std::min(val10,val20); + return std::max(tmp,val12); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, + T val7, T val8, T val9, T val10, T val11, T val12, T val13, + T val14, T val15, T val16, T val17, T val18, T val19, T val20, + T val21, T val22, T val23, T val24, T val25, T val26, T val27, + T val28, T val29, T val30, T val31, T val32, T val33, T val34, + T val35, T val36, T val37, T val38, T val39, T val40, T val41, + T val42, T val43, T val44, T val45, T val46, T val47, T val48) { + T tmp = std::min(val0,val32); + val32 = std::max(val0,val32); val0 = tmp; tmp = std::min(val1,val33); val33 = std::max(val1,val33); val1 = tmp; + tmp = std::min(val2,val34); val34 = std::max(val2,val34); val2 = tmp; tmp = std::min(val3,val35); + val35 = std::max(val3,val35); val3 = tmp; tmp = std::min(val4,val36); val36 = std::max(val4,val36); val4 = tmp; + tmp = std::min(val5,val37); val37 = std::max(val5,val37); val5 = tmp; tmp = std::min(val6,val38); + val38 = std::max(val6,val38); val6 = tmp; tmp = std::min(val7,val39); val39 = std::max(val7,val39); val7 = tmp; + tmp = std::min(val8,val40); val40 = std::max(val8,val40); val8 = tmp; tmp = std::min(val9,val41); + val41 = std::max(val9,val41); val9 = tmp; tmp = std::min(val10,val42); val42 = std::max(val10,val42); + val10 = tmp; tmp = std::min(val11,val43); val43 = std::max(val11,val43); val11 = tmp; + tmp = std::min(val12,val44); val44 = std::max(val12,val44); val12 = tmp; tmp = std::min(val13,val45); + val45 = std::max(val13,val45); val13 = tmp; tmp = std::min(val14,val46); val46 = std::max(val14,val46); + val14 = tmp; tmp = std::min(val15,val47); val47 = std::max(val15,val47); val15 = tmp; + tmp = std::min(val16,val48); val48 = std::max(val16,val48); val16 = tmp; tmp = std::min(val0,val16); + val16 = std::max(val0,val16); val0 = tmp; tmp = std::min(val1,val17); val17 = std::max(val1,val17); + val1 = tmp; tmp = std::min(val2,val18); val18 = std::max(val2,val18); val2 = tmp; tmp = std::min(val3,val19); + val19 = std::max(val3,val19); val3 = tmp; tmp = std::min(val4,val20); val20 = std::max(val4,val20); val4 = tmp; + tmp = std::min(val5,val21); val21 = std::max(val5,val21); val5 = tmp; tmp = std::min(val6,val22); + val22 = std::max(val6,val22); val6 = tmp; tmp = std::min(val7,val23); val23 = std::max(val7,val23); val7 = tmp; + tmp = std::min(val8,val24); val24 = std::max(val8,val24); val8 = tmp; tmp = std::min(val9,val25); + val25 = std::max(val9,val25); val9 = tmp; tmp = std::min(val10,val26); val26 = std::max(val10,val26); + val10 = tmp; tmp = std::min(val11,val27); val27 = std::max(val11,val27); val11 = tmp; + tmp = std::min(val12,val28); val28 = std::max(val12,val28); val12 = tmp; tmp = std::min(val13,val29); + val29 = std::max(val13,val29); val13 = tmp; tmp = std::min(val14,val30); val30 = std::max(val14,val30); + val14 = tmp; tmp = std::min(val15,val31); val31 = std::max(val15,val31); val15 = tmp; + tmp = std::min(val32,val48); val48 = std::max(val32,val48); val32 = tmp; tmp = std::min(val16,val32); + val32 = std::max(val16,val32); val16 = tmp; tmp = std::min(val17,val33); val33 = std::max(val17,val33); + val17 = tmp; tmp = std::min(val18,val34); val34 = std::max(val18,val34); val18 = tmp; + tmp = std::min(val19,val35); val35 = std::max(val19,val35); val19 = tmp; tmp = std::min(val20,val36); + val36 = std::max(val20,val36); val20 = tmp; tmp = std::min(val21,val37); val37 = std::max(val21,val37); + val21 = tmp; tmp = std::min(val22,val38); val38 = std::max(val22,val38); val22 = tmp; + tmp = std::min(val23,val39); val39 = std::max(val23,val39); val23 = tmp; tmp = std::min(val24,val40); + val40 = std::max(val24,val40); val24 = tmp; tmp = std::min(val25,val41); val41 = std::max(val25,val41); + val25 = tmp; tmp = std::min(val26,val42); val42 = std::max(val26,val42); val26 = tmp; + tmp = std::min(val27,val43); val43 = std::max(val27,val43); val27 = tmp; tmp = std::min(val28,val44); + val44 = std::max(val28,val44); val28 = tmp; tmp = std::min(val29,val45); val45 = std::max(val29,val45); + val29 = tmp; tmp = std::min(val30,val46); val46 = std::max(val30,val46); val30 = tmp; + tmp = std::min(val31,val47); val47 = std::max(val31,val47); val31 = tmp; tmp = std::min(val0,val8); + val8 = std::max(val0,val8); val0 = tmp; tmp = std::min(val1,val9); val9 = std::max(val1,val9); val1 = tmp; + tmp = std::min(val2,val10); val10 = std::max(val2,val10); val2 = tmp; tmp = std::min(val3,val11); + val11 = std::max(val3,val11); val3 = tmp; tmp = std::min(val4,val12); val12 = std::max(val4,val12); val4 = tmp; + tmp = std::min(val5,val13); val13 = std::max(val5,val13); val5 = tmp; tmp = std::min(val6,val14); + val14 = std::max(val6,val14); val6 = tmp; tmp = std::min(val7,val15); val15 = std::max(val7,val15); val7 = tmp; + tmp = std::min(val16,val24); val24 = std::max(val16,val24); val16 = tmp; tmp = std::min(val17,val25); + val25 = std::max(val17,val25); val17 = tmp; tmp = std::min(val18,val26); val26 = std::max(val18,val26); + val18 = tmp; tmp = std::min(val19,val27); val27 = std::max(val19,val27); val19 = tmp; + tmp = std::min(val20,val28); val28 = std::max(val20,val28); val20 = tmp; tmp = std::min(val21,val29); + val29 = std::max(val21,val29); val21 = tmp; tmp = std::min(val22,val30); val30 = std::max(val22,val30); + val22 = tmp; tmp = std::min(val23,val31); val31 = std::max(val23,val31); val23 = tmp; + tmp = std::min(val32,val40); val40 = std::max(val32,val40); val32 = tmp; tmp = std::min(val33,val41); + val41 = std::max(val33,val41); val33 = tmp; tmp = std::min(val34,val42); val42 = std::max(val34,val42); + val34 = tmp; tmp = std::min(val35,val43); val43 = std::max(val35,val43); val35 = tmp; + tmp = std::min(val36,val44); val44 = std::max(val36,val44); val36 = tmp; tmp = std::min(val37,val45); + val45 = std::max(val37,val45); val37 = tmp; tmp = std::min(val38,val46); val46 = std::max(val38,val46); + val38 = tmp; tmp = std::min(val39,val47); val47 = std::max(val39,val47); val39 = tmp; + tmp = std::min(val8,val32); val32 = std::max(val8,val32); val8 = tmp; tmp = std::min(val9,val33); + val33 = std::max(val9,val33); val9 = tmp; tmp = std::min(val10,val34); val34 = std::max(val10,val34); + val10 = tmp; tmp = std::min(val11,val35); val35 = std::max(val11,val35); val11 = tmp; + tmp = std::min(val12,val36); val36 = std::max(val12,val36); val12 = tmp; tmp = std::min(val13,val37); + val37 = std::max(val13,val37); val13 = tmp; tmp = std::min(val14,val38); val38 = std::max(val14,val38); + val14 = tmp; tmp = std::min(val15,val39); val39 = std::max(val15,val39); val15 = tmp; + tmp = std::min(val24,val48); val48 = std::max(val24,val48); val24 = tmp; tmp = std::min(val8,val16); + val16 = std::max(val8,val16); val8 = tmp; tmp = std::min(val9,val17); val17 = std::max(val9,val17); + val9 = tmp; tmp = std::min(val10,val18); val18 = std::max(val10,val18); val10 = tmp; + tmp = std::min(val11,val19); val19 = std::max(val11,val19); val11 = tmp; tmp = std::min(val12,val20); + val20 = std::max(val12,val20); val12 = tmp; tmp = std::min(val13,val21); val21 = std::max(val13,val21); + val13 = tmp; tmp = std::min(val14,val22); val22 = std::max(val14,val22); val14 = tmp; + tmp = std::min(val15,val23); val23 = std::max(val15,val23); val15 = tmp; tmp = std::min(val24,val32); + val32 = std::max(val24,val32); val24 = tmp; tmp = std::min(val25,val33); val33 = std::max(val25,val33); + val25 = tmp; tmp = std::min(val26,val34); val34 = std::max(val26,val34); val26 = tmp; + tmp = std::min(val27,val35); val35 = std::max(val27,val35); val27 = tmp; tmp = std::min(val28,val36); + val36 = std::max(val28,val36); val28 = tmp; tmp = std::min(val29,val37); val37 = std::max(val29,val37); + val29 = tmp; tmp = std::min(val30,val38); val38 = std::max(val30,val38); val30 = tmp; + tmp = std::min(val31,val39); val39 = std::max(val31,val39); val31 = tmp; tmp = std::min(val40,val48); + val48 = std::max(val40,val48); val40 = tmp; tmp = std::min(val0,val4); val4 = std::max(val0,val4); + val0 = tmp; tmp = std::min(val1,val5); val5 = std::max(val1,val5); val1 = tmp; tmp = std::min(val2,val6); + val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val8,val12); val12 = std::max(val8,val12); val8 = tmp; tmp = std::min(val9,val13); + val13 = std::max(val9,val13); val9 = tmp; tmp = std::min(val10,val14); val14 = std::max(val10,val14); + val10 = tmp; tmp = std::min(val11,val15); val15 = std::max(val11,val15); val11 = tmp; + tmp = std::min(val16,val20); val20 = std::max(val16,val20); val16 = tmp; tmp = std::min(val17,val21); + val21 = std::max(val17,val21); val17 = tmp; tmp = std::min(val18,val22); val22 = std::max(val18,val22); + val18 = tmp; tmp = std::min(val19,val23); val23 = std::max(val19,val23); val19 = tmp; + tmp = std::min(val24,val28); val28 = std::max(val24,val28); val24 = tmp; tmp = std::min(val25,val29); + val29 = std::max(val25,val29); val25 = tmp; tmp = std::min(val26,val30); val30 = std::max(val26,val30); + val26 = tmp; tmp = std::min(val27,val31); val31 = std::max(val27,val31); val27 = tmp; + tmp = std::min(val32,val36); val36 = std::max(val32,val36); val32 = tmp; tmp = std::min(val33,val37); + val37 = std::max(val33,val37); val33 = tmp; tmp = std::min(val34,val38); val38 = std::max(val34,val38); + val34 = tmp; tmp = std::min(val35,val39); val39 = std::max(val35,val39); val35 = tmp; + tmp = std::min(val40,val44); val44 = std::max(val40,val44); val40 = tmp; tmp = std::min(val41,val45); + val45 = std::max(val41,val45); val41 = tmp; tmp = std::min(val42,val46); val46 = std::max(val42,val46); + val42 = tmp; tmp = std::min(val43,val47); val47 = std::max(val43,val47); val43 = tmp; + tmp = std::min(val4,val32); val32 = std::max(val4,val32); val4 = tmp; tmp = std::min(val5,val33); + val33 = std::max(val5,val33); val5 = tmp; tmp = std::min(val6,val34); val34 = std::max(val6,val34); + val6 = tmp; tmp = std::min(val7,val35); val35 = std::max(val7,val35); val7 = tmp; + tmp = std::min(val12,val40); val40 = std::max(val12,val40); val12 = tmp; tmp = std::min(val13,val41); + val41 = std::max(val13,val41); val13 = tmp; tmp = std::min(val14,val42); val42 = std::max(val14,val42); + val14 = tmp; tmp = std::min(val15,val43); val43 = std::max(val15,val43); val15 = tmp; + tmp = std::min(val20,val48); val48 = std::max(val20,val48); val20 = tmp; tmp = std::min(val4,val16); + val16 = std::max(val4,val16); val4 = tmp; tmp = std::min(val5,val17); val17 = std::max(val5,val17); + val5 = tmp; tmp = std::min(val6,val18); val18 = std::max(val6,val18); val6 = tmp; + tmp = std::min(val7,val19); val19 = std::max(val7,val19); val7 = tmp; tmp = std::min(val12,val24); + val24 = std::max(val12,val24); val12 = tmp; tmp = std::min(val13,val25); val25 = std::max(val13,val25); + val13 = tmp; tmp = std::min(val14,val26); val26 = std::max(val14,val26); val14 = tmp; + tmp = std::min(val15,val27); val27 = std::max(val15,val27); val15 = tmp; tmp = std::min(val20,val32); + val32 = std::max(val20,val32); val20 = tmp; tmp = std::min(val21,val33); val33 = std::max(val21,val33); + val21 = tmp; tmp = std::min(val22,val34); val34 = std::max(val22,val34); val22 = tmp; + tmp = std::min(val23,val35); val35 = std::max(val23,val35); val23 = tmp; tmp = std::min(val28,val40); + val40 = std::max(val28,val40); val28 = tmp; tmp = std::min(val29,val41); val41 = std::max(val29,val41); + val29 = tmp; tmp = std::min(val30,val42); val42 = std::max(val30,val42); val30 = tmp; + tmp = std::min(val31,val43); val43 = std::max(val31,val43); val31 = tmp; tmp = std::min(val36,val48); + val48 = std::max(val36,val48); val36 = tmp; tmp = std::min(val4,val8); val8 = std::max(val4,val8); + val4 = tmp; tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val7,val11); val11 = std::max(val7,val11); val7 = tmp; + tmp = std::min(val12,val16); val16 = std::max(val12,val16); val12 = tmp; tmp = std::min(val13,val17); + val17 = std::max(val13,val17); val13 = tmp; tmp = std::min(val14,val18); val18 = std::max(val14,val18); + val14 = tmp; tmp = std::min(val15,val19); val19 = std::max(val15,val19); val15 = tmp; + tmp = std::min(val20,val24); val24 = std::max(val20,val24); val20 = tmp; tmp = std::min(val21,val25); + val25 = std::max(val21,val25); val21 = tmp; tmp = std::min(val22,val26); val26 = std::max(val22,val26); + val22 = tmp; tmp = std::min(val23,val27); val27 = std::max(val23,val27); val23 = tmp; + tmp = std::min(val28,val32); val32 = std::max(val28,val32); val28 = tmp; tmp = std::min(val29,val33); + val33 = std::max(val29,val33); val29 = tmp; tmp = std::min(val30,val34); val34 = std::max(val30,val34); + val30 = tmp; tmp = std::min(val31,val35); val35 = std::max(val31,val35); val31 = tmp; + tmp = std::min(val36,val40); val40 = std::max(val36,val40); val36 = tmp; tmp = std::min(val37,val41); + val41 = std::max(val37,val41); val37 = tmp; tmp = std::min(val38,val42); val42 = std::max(val38,val42); + val38 = tmp; tmp = std::min(val39,val43); val43 = std::max(val39,val43); val39 = tmp; + tmp = std::min(val44,val48); val48 = std::max(val44,val48); val44 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val1,val3); val3 = std::max(val1,val3); val1 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val5,val7); + val7 = std::max(val5,val7); val5 = tmp; tmp = std::min(val8,val10); val10 = std::max(val8,val10); val8 = tmp; + tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; tmp = std::min(val12,val14); + val14 = std::max(val12,val14); val12 = tmp; tmp = std::min(val13,val15); val15 = std::max(val13,val15); + val13 = tmp; tmp = std::min(val16,val18); val18 = std::max(val16,val18); val16 = tmp; + tmp = std::min(val17,val19); val19 = std::max(val17,val19); val17 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = tmp; tmp = std::min(val21,val23); val23 = std::max(val21,val23); + val21 = tmp; tmp = std::min(val24,val26); val26 = std::max(val24,val26); val24 = tmp; + tmp = std::min(val25,val27); val27 = std::max(val25,val27); val25 = tmp; tmp = std::min(val28,val30); + val30 = std::max(val28,val30); val28 = tmp; tmp = std::min(val29,val31); val31 = std::max(val29,val31); + val29 = tmp; tmp = std::min(val32,val34); val34 = std::max(val32,val34); val32 = tmp; + tmp = std::min(val33,val35); val35 = std::max(val33,val35); val33 = tmp; tmp = std::min(val36,val38); + val38 = std::max(val36,val38); val36 = tmp; tmp = std::min(val37,val39); val39 = std::max(val37,val39); + val37 = tmp; tmp = std::min(val40,val42); val42 = std::max(val40,val42); val40 = tmp; + tmp = std::min(val41,val43); val43 = std::max(val41,val43); val41 = tmp; tmp = std::min(val44,val46); + val46 = std::max(val44,val46); val44 = tmp; tmp = std::min(val45,val47); val47 = std::max(val45,val47); + val45 = tmp; tmp = std::min(val2,val32); val32 = std::max(val2,val32); val2 = tmp; tmp = std::min(val3,val33); + val33 = std::max(val3,val33); val3 = tmp; tmp = std::min(val6,val36); val36 = std::max(val6,val36); val6 = tmp; + tmp = std::min(val7,val37); val37 = std::max(val7,val37); val7 = tmp; tmp = std::min(val10,val40); + val40 = std::max(val10,val40); val10 = tmp; tmp = std::min(val11,val41); val41 = std::max(val11,val41); + val11 = tmp; tmp = std::min(val14,val44); val44 = std::max(val14,val44); val14 = tmp; + tmp = std::min(val15,val45); val45 = std::max(val15,val45); val15 = tmp; tmp = std::min(val18,val48); + val48 = std::max(val18,val48); val18 = tmp; tmp = std::min(val2,val16); val16 = std::max(val2,val16); + val2 = tmp; tmp = std::min(val3,val17); val17 = std::max(val3,val17); val3 = tmp; + tmp = std::min(val6,val20); val20 = std::max(val6,val20); val6 = tmp; tmp = std::min(val7,val21); + val21 = std::max(val7,val21); val7 = tmp; tmp = std::min(val10,val24); val24 = std::max(val10,val24); + val10 = tmp; tmp = std::min(val11,val25); val25 = std::max(val11,val25); val11 = tmp; + tmp = std::min(val14,val28); val28 = std::max(val14,val28); val14 = tmp; tmp = std::min(val15,val29); + val29 = std::max(val15,val29); val15 = tmp; tmp = std::min(val18,val32); val32 = std::max(val18,val32); + val18 = tmp; tmp = std::min(val19,val33); val33 = std::max(val19,val33); val19 = tmp; + tmp = std::min(val22,val36); val36 = std::max(val22,val36); val22 = tmp; tmp = std::min(val23,val37); + val37 = std::max(val23,val37); val23 = tmp; tmp = std::min(val26,val40); val40 = std::max(val26,val40); + val26 = tmp; tmp = std::min(val27,val41); val41 = std::max(val27,val41); val27 = tmp; + tmp = std::min(val30,val44); val44 = std::max(val30,val44); val30 = tmp; tmp = std::min(val31,val45); + val45 = std::max(val31,val45); val31 = tmp; tmp = std::min(val34,val48); val48 = std::max(val34,val48); + val34 = tmp; tmp = std::min(val2,val8); val8 = std::max(val2,val8); val2 = tmp; tmp = std::min(val3,val9); + val9 = std::max(val3,val9); val3 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val13); val13 = std::max(val7,val13); val7 = tmp; tmp = std::min(val10,val16); + val16 = std::max(val10,val16); val10 = tmp; tmp = std::min(val11,val17); val17 = std::max(val11,val17); + val11 = tmp; tmp = std::min(val14,val20); val20 = std::max(val14,val20); val14 = tmp; + tmp = std::min(val15,val21); val21 = std::max(val15,val21); val15 = tmp; tmp = std::min(val18,val24); + val24 = std::max(val18,val24); val18 = tmp; tmp = std::min(val19,val25); val25 = std::max(val19,val25); + val19 = tmp; tmp = std::min(val22,val28); val28 = std::max(val22,val28); val22 = tmp; + tmp = std::min(val23,val29); val29 = std::max(val23,val29); val23 = tmp; tmp = std::min(val26,val32); + val32 = std::max(val26,val32); val26 = tmp; tmp = std::min(val27,val33); val33 = std::max(val27,val33); + val27 = tmp; tmp = std::min(val30,val36); val36 = std::max(val30,val36); val30 = tmp; + tmp = std::min(val31,val37); val37 = std::max(val31,val37); val31 = tmp; tmp = std::min(val34,val40); + val40 = std::max(val34,val40); val34 = tmp; tmp = std::min(val35,val41); val41 = std::max(val35,val41); + val35 = tmp; tmp = std::min(val38,val44); val44 = std::max(val38,val44); val38 = tmp; + tmp = std::min(val39,val45); val45 = std::max(val39,val45); val39 = tmp; tmp = std::min(val42,val48); + val48 = std::max(val42,val48); val42 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); + val2 = tmp; tmp = std::min(val3,val5); val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val6,val8); + val8 = std::max(val6,val8); val6 = tmp; tmp = std::min(val7,val9); val9 = std::max(val7,val9); val7 = tmp; + tmp = std::min(val10,val12); val12 = std::max(val10,val12); val10 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = tmp; tmp = std::min(val14,val16); val16 = std::max(val14,val16); + val14 = tmp; tmp = std::min(val15,val17); val17 = std::max(val15,val17); val15 = tmp; + tmp = std::min(val18,val20); val20 = std::max(val18,val20); val18 = tmp; tmp = std::min(val19,val21); + val21 = std::max(val19,val21); val19 = tmp; tmp = std::min(val22,val24); val24 = std::max(val22,val24); + val22 = tmp; tmp = std::min(val23,val25); val25 = std::max(val23,val25); val23 = tmp; + tmp = std::min(val26,val28); val28 = std::max(val26,val28); val26 = tmp; tmp = std::min(val27,val29); + val29 = std::max(val27,val29); val27 = tmp; tmp = std::min(val30,val32); val32 = std::max(val30,val32); + val30 = tmp; tmp = std::min(val31,val33); val33 = std::max(val31,val33); val31 = tmp; + tmp = std::min(val34,val36); val36 = std::max(val34,val36); val34 = tmp; tmp = std::min(val35,val37); + val37 = std::max(val35,val37); val35 = tmp; tmp = std::min(val38,val40); val40 = std::max(val38,val40); + val38 = tmp; tmp = std::min(val39,val41); val41 = std::max(val39,val41); val39 = tmp; + tmp = std::min(val42,val44); val44 = std::max(val42,val44); val42 = tmp; tmp = std::min(val43,val45); + val45 = std::max(val43,val45); val43 = tmp; tmp = std::min(val46,val48); val48 = std::max(val46,val48); + val46 = tmp; val1 = std::max(val0,val1); val3 = std::max(val2,val3); val5 = std::max(val4,val5); + val7 = std::max(val6,val7); val9 = std::max(val8,val9); val11 = std::max(val10,val11); + val13 = std::max(val12,val13); val15 = std::max(val14,val15); val17 = std::max(val16,val17); + val19 = std::max(val18,val19); val21 = std::max(val20,val21); val23 = std::max(val22,val23); + val24 = std::min(val24,val25); val26 = std::min(val26,val27); val28 = std::min(val28,val29); + val30 = std::min(val30,val31); val32 = std::min(val32,val33); val34 = std::min(val34,val35); + val36 = std::min(val36,val37); val38 = std::min(val38,val39); val40 = std::min(val40,val41); + val42 = std::min(val42,val43); val44 = std::min(val44,val45); val46 = std::min(val46,val47); + val32 = std::max(val1,val32); val34 = std::max(val3,val34); val36 = std::max(val5,val36); + val38 = std::max(val7,val38); val9 = std::min(val9,val40); val11 = std::min(val11,val42); + val13 = std::min(val13,val44); val15 = std::min(val15,val46); val17 = std::min(val17,val48); + val24 = std::max(val9,val24); val26 = std::max(val11,val26); val28 = std::max(val13,val28); + val30 = std::max(val15,val30); val17 = std::min(val17,val32); val19 = std::min(val19,val34); + val21 = std::min(val21,val36); val23 = std::min(val23,val38); val24 = std::max(val17,val24); + val26 = std::max(val19,val26); val21 = std::min(val21,val28); val23 = std::min(val23,val30); + val24 = std::max(val21,val24); val23 = std::min(val23,val26); + return std::max(val23,val24); + } + + //! Return sqrt(x^2 + y^2). + template + inline T hypot(const T x, const T y) { + return std::sqrt(x*x + y*y); + } + + template + inline T hypot(const T x, const T y, const T z) { + return std::sqrt(x*x + y*y + z*z); + } + + template + inline T _hypot(const T x, const T y) { // Slower but more precise version + T nx = cimg::abs(x), ny = cimg::abs(y), t; + if (nx0) { t/=nx; return nx*std::sqrt(1 + t*t); } + return 0; + } + + //! Return the factorial of n + inline double factorial(const int n) { + if (n<0) return cimg::type::nan(); + if (n<2) return 1; + double res = 2; + for (int i = 3; i<=n; ++i) res*=i; + return res; + } + + //! Return the number of permutations of k objects in a set of n objects. + inline double permutations(const int k, const int n, const bool with_order) { + if (n<0 || k<0) return cimg::type::nan(); + if (k>n) return 0; + double res = 1; + for (int i = n; i>=n - k + 1; --i) res*=i; + return with_order?res:res/cimg::factorial(k); + } + + inline double _fibonacci(int exp) { + double + base = (1 + std::sqrt(5.))/2, + result = 1/std::sqrt(5.); + while (exp) { + if (exp&1) result*=base; + exp>>=1; + base*=base; + } + return result; + } + + //! Calculate fibonacci number. + // (Precise up to n = 78, less precise for n>78). + inline double fibonacci(const int n) { + if (n<0) return cimg::type::nan(); + if (n<3) return 1; + if (n<11) { + cimg_uint64 fn1 = 1, fn2 = 1, fn = 0; + for (int i = 3; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + if (n<75) // precise up to n = 74, faster than the integer calculation above for n>10 + return (double)((cimg_uint64)(_fibonacci(n) + 0.5)); + + if (n<94) { // precise up to n = 78, less precise for n>78 up to n = 93, overflows for n>93 + cimg_uint64 + fn1 = (cimg_uint64)1304969544928657ULL, + fn2 = (cimg_uint64)806515533049393ULL, + fn = 0; + for (int i = 75; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + return _fibonacci(n); // Not precise, but better than the wrong overflowing calculation + } + + //! Calculate greatest common divisor. + inline long gcd(long a, long b) { + while (a) { const long c = a; a = b%a; b = c; } + return b; + } + + //! Convert Ascii character to lower case. + inline char lowercase(const char x) { + return (char)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + inline double lowercase(const double x) { + return (double)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + + //! Convert C-string to lower case. + inline void lowercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = lowercase(*ptr); + } + + //! Convert Ascii character to upper case. + inline char uppercase(const char x) { + return (char)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + inline double uppercase(const double x) { + return (double)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + //! Convert C-string to upper case. + inline void uppercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uppercase(*ptr); + } + + //! Return \c true if input character is blank (space, tab, or non-printable character). + inline bool is_blank(const char c) { + return c>=0 && c<=' '; + } + + //! Read value in a C-string. + /** + \param str C-string containing the float value to read. + \return Read value. + \note Same as std::atof() extended to manage the retrieval of fractions from C-strings, + as in "1/2". + **/ + inline double atof(const char *const str) { + double x = 0, y = 1; + return str && cimg_sscanf(str,"%lf/%lf",&x,&y)>0?x/y:0; + } + + //! Compare the first \p l characters of two C-strings, ignoring the case. + /** + \param str1 C-string. + \param str2 C-string. + \param l Number of characters to compare. + \return \c 0 if the two strings are equal, something else otherwise. + \note This function has to be defined since it is not provided by all C++-compilers (not ANSI). + **/ + inline int strncasecmp(const char *const str1, const char *const str2, const int l) { + if (!l) return 0; + if (!str1) return str2?-1:0; + const char *nstr1 = str1, *nstr2 = str2; + int k, diff = 0; for (k = 0; kp && str[q]==delimiter; ) { --q; if (!is_iterative) break; } + } + const int n = q - p + 1; + if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; } + return false; + } + + //! Remove white spaces on the start and/or end of a C-string. + inline bool strpare(char *const str, const bool is_symmetric, const bool is_iterative) { + if (!str) return false; + const int l = (int)std::strlen(str); + int p, q; + if (is_symmetric) for (p = 0, q = l - 1; 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; } + return false; + } + + //! Replace reserved characters (for Windows filename) by another character. + /** + \param[in,out] str C-string to work with (modified at output). + \param[in] c Replacement character. + **/ + inline void strwindows_reserved(char *const str, const char c='_') { + for (char *s = str; *s; ++s) { + const char i = *s; + if (i=='<' || i=='>' || i==':' || i=='\"' || i=='/' || i=='\\' || i=='|' || i=='?' || i=='*') *s = c; + } + } + + //! Replace escape sequences in C-strings by their binary Ascii values. + /** + \param[in,out] str C-string to work with (modified at output). + **/ + inline void strunescape(char *const str) { +#define cimg_strunescape(ci,co) case ci : *nd = co; ++ns; break; + unsigned int val = 0; + for (char *ns = str, *nd = str; *ns || (bool)(*nd=0); ++nd) if (*ns=='\\') switch (*(++ns)) { + cimg_strunescape('a','\a'); + cimg_strunescape('b','\b'); + cimg_strunescape('e',0x1B); + cimg_strunescape('f','\f'); + cimg_strunescape('n','\n'); + cimg_strunescape('r','\r'); + cimg_strunescape('t','\t'); + cimg_strunescape('v','\v'); + cimg_strunescape('\\','\\'); + cimg_strunescape('\'','\''); + cimg_strunescape('\"','\"'); + cimg_strunescape('\?','\?'); + case 0 : *nd = 0; break; + case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : + cimg_sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns; + *nd = (char)val; break; + case 'x' : + cimg_sscanf(++ns,"%x",&val); + while ((*ns>='0' && *ns<='9') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns; + *nd = (char)val; break; + default : *nd = *(ns++); + } else *nd = *(ns++); + } + + // Return a temporary string describing the size of a memory buffer. + inline const char *strbuffersize(const cimg_ulong size); + + // Return string that identifies the running OS. + inline const char *stros() { +#if defined(linux) || defined(__linux) || defined(__linux__) + static const char *const str = "Linux"; +#elif defined(sun) || defined(__sun) + static const char *const str = "Sun OS"; +#elif defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined (__DragonFly__) + static const char *const str = "BSD"; +#elif defined(sgi) || defined(__sgi) + static const char *const str = "Irix"; +#elif defined(__MACOSX__) || defined(__APPLE__) + static const char *const str = "Mac OS"; +#elif defined(unix) || defined(__unix) || defined(__unix__) + static const char *const str = "Generic Unix"; +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \ + defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + static const char *const str = "Windows"; +#else + const char + *const _str1 = std::getenv("OSTYPE"), + *const _str2 = _str1?_str1:std::getenv("OS"), + *const str = _str2?_str2:"Unknown OS"; +#endif + return str; + } + + //! Return the basename of a filename. + inline const char* basename(const char *const s, const char separator=cimg_file_separator) { + const char *p = 0, *np = s; + while (np>=s && (p=np)) np = std::strchr(np,separator) + 1; + return p; + } + + // Return a random filename. + inline const char* filenamerand() { + cimg::mutex(6); + static char randomid[9]; + for (unsigned int k = 0; k<8; ++k) { + const int v = (int)cimg::rand(65535)%3; + randomid[k] = (char)(v==0?('0' + ((int)cimg::rand(65535)%10)): + (v==1?('a' + ((int)cimg::rand(65535)%26)): + ('A' + ((int)cimg::rand(65535)%26)))); + } + cimg::mutex(6,0); + return randomid; + } + + // Convert filename as a Windows-style filename (short path name). + inline void winformat_string(char *const str) { + if (str && *str) { +#if cimg_OS==2 + char *const nstr = new char[MAX_PATH]; + if (GetShortPathNameA(str,nstr,MAX_PATH)) std::strcpy(str,nstr); + delete[] nstr; +#endif + } + } + + // Open a file (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. + /** + \param path Path of the filename to open. + \param mode C-string describing the opening mode. + \return Opened file. + \note Same as std::fopen() but throw a \c CImgIOException when + the specified file cannot be opened, instead of returning \c 0. + **/ + inline std::FILE *fopen(const char *const path, const char *const mode) { + if (!path) + throw CImgArgumentException("cimg::fopen(): Specified file path is (null)."); + if (!mode) + throw CImgArgumentException("cimg::fopen(): File '%s', specified mode is (null).", + path); + std::FILE *res = 0; + if (*path=='-' && (!path[1] || path[1]=='.')) { + res = (*mode=='r')?cimg::_stdin():cimg::_stdout(); +#if cimg_OS==2 + if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode +#ifdef __BORLANDC__ + if (setmode(_fileno(res),0x8000)==-1) res = 0; +#else + if (_setmode(_fileno(res),0x8000)==-1) res = 0; +#endif + } +#endif + } else res = cimg::std_fopen(path,mode); + if (!res) throw CImgIOException("cimg::fopen(): Failed to open file '%s' with mode '%s'.", + path,mode); + return res; + } + + //! Close a file. + /** + \param file File to close. + \return \c 0 if file has been closed properly, something else otherwise. + \note Same as std::fclose() but display a warning message if + the file has not been closed properly. + **/ + inline int fclose(std::FILE *file) { + if (!file) { warn("cimg::fclose(): Specified file is (null)."); return 0; } + if (file==cimg::_stdin(false) || file==cimg::_stdout(false)) return 0; + const int errn = std::fclose(file); + if (errn!=0) warn("cimg::fclose(): Error code %d returned during file closing.", + errn); + return errn; + } + + //! Version of 'fseek()' that supports >=64bits offsets everywhere (for Windows). + inline int fseek(FILE *stream, cimg_long offset, int origin) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return _fseeki64(stream,(__int64)offset,origin); +#else + return std::fseek(stream,offset,origin); +#endif + } + + //! Version of 'ftell()' that supports >=64bits offsets everywhere (for Windows). + inline cimg_long ftell(FILE *stream) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return (cimg_long)_ftelli64(stream); +#else + return (cimg_long)std::ftell(stream); +#endif + } + + //! Check if a path is a directory. + /** + \param path Specified path to test. + **/ + inline bool is_directory(const char *const path) { + if (!path || !*path) return false; +#if cimg_OS==1 + struct stat st_buf; + return (!stat(path,&st_buf) && S_ISDIR(st_buf.st_mode)); +#elif cimg_OS==2 + const unsigned int res = (unsigned int)GetFileAttributesA(path); + return res==INVALID_FILE_ATTRIBUTES?false:(res&16); +#else + return false; +#endif + } + + //! Check if a path is a file. + /** + \param path Specified path to test. + **/ + inline bool is_file(const char *const path) { + if (!path || !*path) return false; + std::FILE *const file = cimg::std_fopen(path,"rb"); + if (!file) return false; + cimg::fclose(file); + return !is_directory(path); + } + + //! Get file size. + /** + \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 = cimg::std_fopen(filename,"rb"); + if (!file) return (cimg_int64)-1; + std::fseek(file,0,SEEK_END); + const cimg_int64 siz = (cimg_int64)std::ftell(file); + cimg::fclose(file); + return siz; + } + + //! Get last write time of a given file or directory (multiple-attributes version). + /** + \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). + \param nb_attr Number of attributes to read/write. + \return Latest read attribute. + **/ + template + inline int fdate(const char *const path, T *attr, const unsigned int nb_attr) { +#define _cimg_fdate_err() for (unsigned int i = 0; i + inline int date(T *attr, const unsigned int nb_attr) { + int res = -1; + cimg::mutex(6); +#if cimg_OS==2 + 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); + attr[i] = (T)res; + } +#endif + cimg::mutex(6,0); + return res; + } + + //! 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. + **/ + 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 dcraw binary. + inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c wget binary. + inline const char *wget_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c curl binary. + inline const char *curl_path(const char *const user_path=0, const bool reinit_path=false); + + //! Split filename into two C-strings \c body and \c extension. + /** + filename and body must not overlap! + **/ + inline const char *split_filename(const char *const filename, char *const body=0) { + if (!filename) { if (body) *body = 0; return 0; } + const char *p = 0; for (const char *np = filename; np>=filename && (p=np); np = std::strchr(np,'.') + 1) {} + if (p==filename) { + if (body) std::strcpy(body,filename); + return filename + std::strlen(filename); + } + const unsigned int l = (unsigned int)(p - filename - 1); + if (body) { if (l) std::memcpy(body,filename,l); body[l] = 0; } + return p; + } + + //! Generate a numbered version of a filename. + inline char* number_filename(const char *const filename, const int number, + const unsigned int digits, char *const str) { + if (!filename) { if (str) *str = 0; return 0; } + char *const format = new char[1024], *const body = new char[1024]; + const char *const ext = cimg::split_filename(filename,body); + if (*ext) cimg_snprintf(format,1024,"%%s_%%.%ud.%%s",digits); + else cimg_snprintf(format,1024,"%%s_%%.%ud",digits); + cimg_sprintf(str,format,body,number,ext); + delete[] format; delete[] body; + return str; + } + + //! Read data from file. + /** + \param[out] ptr Pointer to memory buffer that will contain the binary data read from file. + \param nmemb Number of elements to read. + \param stream File to read data from. + \return Number of read elements. + \note Same as std::fread() but may display warning message if all elements could not be read. + **/ + template + inline size_t fread(T *const ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fread(): Invalid reading request of %u %s%s from file %p to buffer %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",stream,ptr); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_read = nmemb, al_read = 0, l_to_read = 0, l_al_read = 0; + do { + l_to_read = (to_read*sizeof(T))0); + if (to_read>0) + warn("cimg::fread(): Only %lu/%lu elements could be read from file.", + (unsigned long)al_read,(unsigned long)nmemb); + return al_read; + } + + //! Write data to file. + /** + \param ptr Pointer to memory buffer containing the binary data to write on file. + \param nmemb Number of elements to write. + \param[out] stream File to write data on. + \return Number of written elements. + \note Similar to std::fwrite but may display warning messages if all elements could not be written. + **/ + template + inline size_t fwrite(const T *ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fwrite(): Invalid writing request of %u %s%s from buffer %p to file %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",ptr,stream); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_write = nmemb, al_write = 0, l_to_write = 0, l_al_write = 0; + do { + l_to_write = (to_write*sizeof(T))0); + if (to_write>0) + warn("cimg::fwrite(): Only %lu/%lu elements could be written in file.", + (unsigned long)al_write,(unsigned long)nmemb); + return al_write; + } + + //! Create an empty file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + **/ + inline void fempty(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::fempty(): Specified filename is (null)."); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!file) cimg::fclose(nfile); + } + + // Try to guess format from an image file. + inline const char *ftype(std::FILE *const file, const char *const filename); + + // Load file from network as a local temporary file. + inline char *load_network(const char *const url, char *const filename_local, + const unsigned int timeout=0, const bool try_fallback=false, + const char *const referer=0); + + //! Return options specified on the command line. + inline const char* option(const char *const name, const int argc, const char *const *const argv, + const char *const defaut, const char *const usage, const bool reset_static) { + static bool first = true, visu = false; + if (reset_static) { first = true; return 0; } + const char *res = 0; + if (first) { + first = false; + visu = cimg::option("-h",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("-help",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("--help",argc,argv,(char*)0,(char*)0,false)!=0; + } + if (!name && visu) { + if (usage) { + std::fprintf(cimg::output(),"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal); + std::fprintf(cimg::output(),": %s",usage); + std::fprintf(cimg::output()," (%s, %s)\n\n",cimg_date,cimg_time); + } + if (defaut) std::fprintf(cimg::output(),"%s\n",defaut); + } + if (name) { + if (argc>0) { + int k = 0; + while (k Operating System: %s%-13s%s %s('cimg_OS'=%d)%s\n", + cimg::t_bold, + cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"), + cimg::t_normal,cimg::t_green, + cimg_OS, + cimg::t_normal); + + std::fprintf(cimg::output()," > CPU endianness: %s%s Endian%s\n", + cimg::t_bold, + cimg::endianness()?"Big":"Little", + cimg::t_normal); + + std::fprintf(cimg::output()," > Verbosity mode: %s%-13s%s %s('cimg_verbosity'=%d)%s\n", + cimg::t_bold, + cimg_verbosity==0?"Quiet": + cimg_verbosity==1?"Console": + cimg_verbosity==2?"Dialog": + cimg_verbosity==3?"Console+Warnings":"Dialog+Warnings", + cimg::t_normal,cimg::t_green, + cimg_verbosity, + cimg::t_normal); + + std::fprintf(cimg::output()," > Stricts warnings: %s%-13s%s %s('cimg_strict_warnings' %s)%s\n", + cimg::t_bold, +#ifdef cimg_strict_warnings + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Support for C++11: %s%-13s%s %s('cimg_use_cpp11'=%d)%s\n", + cimg::t_bold, + cimg_use_cpp11?"Yes":"No", + cimg::t_normal,cimg::t_green, + (int)cimg_use_cpp11, + cimg::t_normal); + + std::fprintf(cimg::output()," > Using VT100 messages: %s%-13s%s %s('cimg_use_vt100' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_vt100 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Display type: %s%-13s%s %s('cimg_display'=%d)%s\n", + cimg::t_bold, + cimg_display==0?"No display":cimg_display==1?"X11":cimg_display==2?"Windows GDI":"Unknown", + cimg::t_normal,cimg::t_green, + (int)cimg_display, + cimg::t_normal); + +#if cimg_display==1 + std::fprintf(cimg::output()," > Using XShm for X11: %s%-13s%s %s('cimg_use_xshm' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xshm + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using XRand for X11: %s%-13s%s %s('cimg_use_xrandr' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xrandr + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); +#endif + std::fprintf(cimg::output()," > Using OpenMP: %s%-13s%s %s('cimg_use_openmp' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_openmp + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using PNG library: %s%-13s%s %s('cimg_use_png' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_png + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using JPEG library: %s%-13s%s %s('cimg_use_jpeg' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_jpeg + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using TIFF library: %s%-13s%s %s('cimg_use_tiff' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_tiff + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using Magick++ library: %s%-13s%s %s('cimg_use_magick' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_magick + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using FFTW3 library: %s%-13s%s %s('cimg_use_fftw3' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_fftw3 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using LAPACK library: %s%-13s%s %s('cimg_use_lapack' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_lapack + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + char *const tmp = new char[1024]; + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::imagemagick_path()); + std::fprintf(cimg::output()," > Path of ImageMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::graphicsmagick_path()); + std::fprintf(cimg::output()," > Path of GraphicsMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::medcon_path()); + std::fprintf(cimg::output()," > Path of 'medcon': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::temporary_path()); + std::fprintf(cimg::output()," > Temporary path: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + std::fprintf(cimg::output(),"\n"); + delete[] tmp; + } + + // Declare LAPACK function signatures if LAPACK support is enabled. +#ifdef cimg_use_lapack + template + inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) { + dgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) { + sgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + template + inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) { + dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) { + sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + template + inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN, + T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) { + dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN, + float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) { + sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + template + inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) { + int one = 1; + dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) { + int one = 1; + sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + template + inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) { + dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) { + ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + template + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, T* lapA, int &LDA, + T* lapB, int &LDB, T* WORK, int &LWORK, int &INFO){ + dgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, float* lapA, int &LDA, + float* lapB, int &LDB, float* WORK, int &LWORK, int &INFO){ + sgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + +#endif + + // End of the 'cimg' namespace + } + + /*------------------------------------------------ + # + # + # Definition of mathematical operators and + # external functions. + # + # + -------------------------------------------------*/ + +#define _cimg_create_ext_operators(typ) \ + template \ + inline CImg::type> operator+(const typ val, const CImg& img) { \ + return img + val; \ + } \ + template \ + inline CImg::type> operator-(const typ val, const CImg& img) { \ + typedef typename cimg::superset::type Tt; \ + return CImg(img._width,img._height,img._depth,img._spectrum,val)-=img; \ + } \ + template \ + inline CImg::type> operator*(const typ val, const CImg& img) { \ + return img*val; \ + } \ + template \ + inline CImg::type> operator/(const typ val, const CImg& img) { \ + return val*img.get_invert(); \ + } \ + template \ + inline CImg::type> operator&(const typ val, const CImg& img) { \ + return img & val; \ + } \ + template \ + inline CImg::type> operator|(const typ val, const CImg& img) { \ + return img | val; \ + } \ + template \ + inline CImg::type> operator^(const typ val, const CImg& img) { \ + return img ^ val; \ + } \ + template \ + inline bool operator==(const typ val, const CImg& img) { \ + return img == val; \ + } \ + template \ + inline bool operator!=(const typ val, const CImg& img) { \ + return img != val; \ + } + + _cimg_create_ext_operators(bool) + _cimg_create_ext_operators(unsigned char) + _cimg_create_ext_operators(char) + _cimg_create_ext_operators(signed char) + _cimg_create_ext_operators(unsigned short) + _cimg_create_ext_operators(short) + _cimg_create_ext_operators(unsigned int) + _cimg_create_ext_operators(int) + _cimg_create_ext_operators(cimg_uint64) + _cimg_create_ext_operators(cimg_int64) + _cimg_create_ext_operators(float) + _cimg_create_ext_operators(double) + _cimg_create_ext_operators(long double) + + template + inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg& img) { + return img + expression; + } + + template + inline CImg<_cimg_Tfloat> operator-(const char *const expression, const CImg& img) { + return CImg<_cimg_Tfloat>(img,false).fill(expression,true)-=img; + } + + template + inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg& img) { + return img*expression; + } + + template + inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg& img) { + return expression*img.get_invert(); + } + + template + inline CImg operator&(const char *const expression, const CImg& img) { + return img & expression; + } + + template + inline CImg operator|(const char *const expression, const CImg& img) { + return img | expression; + } + + template + inline CImg operator^(const char *const expression, const CImg& img) { + return img ^ expression; + } + + template + inline bool operator==(const char *const expression, const CImg& img) { + return img==expression; + } + + template + inline bool operator!=(const char *const expression, const CImg& img) { + return img!=expression; + } + + template + inline CImg transpose(const CImg& instance) { + return instance.get_transpose(); + } + + template + inline CImg<_cimg_Tfloat> invert(const CImg& instance) { + return instance.get_invert(); + } + + template + inline CImg<_cimg_Tfloat> pseudoinvert(const CImg& instance) { + return instance.get_pseudoinvert(); + } + +#define _cimg_create_ext_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) + + /*----------------------------------- + # + # Define the CImgDisplay structure + # + ----------------------------------*/ + //! Allow the creation of windows, display images on them and manage user events (keyboard, mouse and windows events). + /** + CImgDisplay methods rely on a low-level graphic library to perform: it can be either \b X-Window + (X11, for Unix-based systems) or \b GDI32 (for Windows-based systems). + If both libraries are missing, CImgDisplay will not be able to display images on screen, and will enter + a minimal mode where warning messages will be outputed each time the program is trying to call one of the + CImgDisplay method. + + The configuration variable \c cimg_display tells about the graphic library used. + It is set automatically by \CImg when one of these graphic libraries has been detected. + But, you can override its value if necessary. Valid choices are: + - 0: Disable display capabilities. + - 1: Use \b X-Window (X11) library. + - 2: Use \b GDI32 library. + + Remember to link your program against \b X11 or \b GDI32 libraries if you use CImgDisplay. + **/ + struct CImgDisplay { + cimg_ulong _timer, _fps_frames, _fps_timer; + unsigned int _width, _height, _normalization; + float _fps_fps, _min, _max; + bool _is_fullscreen; + char *_title; + unsigned int _window_width, _window_height, _button, *_keys, *_released_keys; + int _window_x, _window_y, _mouse_x, _mouse_y, _wheel; + bool _is_closed, _is_resized, _is_moved, _is_event, + _is_keyESC, _is_keyF1, _is_keyF2, _is_keyF3, _is_keyF4, _is_keyF5, _is_keyF6, _is_keyF7, + _is_keyF8, _is_keyF9, _is_keyF10, _is_keyF11, _is_keyF12, _is_keyPAUSE, _is_key1, _is_key2, + _is_key3, _is_key4, _is_key5, _is_key6, _is_key7, _is_key8, _is_key9, _is_key0, + _is_keyBACKSPACE, _is_keyINSERT, _is_keyHOME, _is_keyPAGEUP, _is_keyTAB, _is_keyQ, _is_keyW, _is_keyE, + _is_keyR, _is_keyT, _is_keyY, _is_keyU, _is_keyI, _is_keyO, _is_keyP, _is_keyDELETE, + _is_keyEND, _is_keyPAGEDOWN, _is_keyCAPSLOCK, _is_keyA, _is_keyS, _is_keyD, _is_keyF, _is_keyG, + _is_keyH, _is_keyJ, _is_keyK, _is_keyL, _is_keyENTER, _is_keySHIFTLEFT, _is_keyZ, _is_keyX, + _is_keyC, _is_keyV, _is_keyB, _is_keyN, _is_keyM, _is_keySHIFTRIGHT, _is_keyARROWUP, _is_keyCTRLLEFT, + _is_keyAPPLEFT, _is_keyALT, _is_keySPACE, _is_keyALTGR, _is_keyAPPRIGHT, _is_keyMENU, _is_keyCTRLRIGHT, + _is_keyARROWLEFT, _is_keyARROWDOWN, _is_keyARROWRIGHT, _is_keyPAD0, _is_keyPAD1, _is_keyPAD2, _is_keyPAD3, + _is_keyPAD4, _is_keyPAD5, _is_keyPAD6, _is_keyPAD7, _is_keyPAD8, _is_keyPAD9, _is_keyPADADD, _is_keyPADSUB, + _is_keyPADMUL, _is_keyPADDIV; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- + +#ifdef cimgdisplay_plugin +#include cimgdisplay_plugin +#endif +#ifdef cimgdisplay_plugin1 +#include cimgdisplay_plugin1 +#endif +#ifdef cimgdisplay_plugin2 +#include cimgdisplay_plugin2 +#endif +#ifdef cimgdisplay_plugin3 +#include cimgdisplay_plugin3 +#endif +#ifdef cimgdisplay_plugin4 +#include cimgdisplay_plugin4 +#endif +#ifdef cimgdisplay_plugin5 +#include cimgdisplay_plugin5 +#endif +#ifdef cimgdisplay_plugin6 +#include cimgdisplay_plugin6 +#endif +#ifdef cimgdisplay_plugin7 +#include cimgdisplay_plugin7 +#endif +#ifdef cimgdisplay_plugin8 +#include cimgdisplay_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + \note If the associated window is visible on the screen, it is closed by the call to the destructor. + **/ + ~CImgDisplay() { + assign(); + delete[] _keys; + delete[] _released_keys; + } + + //! Construct an empty display. + /** + \note Constructing an empty CImgDisplay instance does not make a window appearing on the screen, until + display of valid data is performed. + \par Example + \code + CImgDisplay disp; // Does actually nothing + ... + disp.display(img); // Construct new window and display image in it + \endcode + **/ + CImgDisplay(): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(); + } + + //! Construct a display with specified dimensions. + /** \param width Window width. + \param height Window height. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note A black background is initially displayed on the associated window. + **/ + CImgDisplay(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(width,height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image. + /** \param img Image used as a model to create the window. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note The pixels of the input image are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(img,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list. + /** \param list The images list to display. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note All images of the list, appended along the X-axis, are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(list,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of an existing one. + /** + \param disp Display instance to copy. + \note The pixel buffer of the input window is initially displayed on the associated window. + **/ + CImgDisplay(const CImgDisplay& disp): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(disp); + } + + //! Take a screenshot. + /** + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(CImg& img) { + return screenshot(0,0,cimg::type::max(),cimg::type::max(),img); + } + +#if cimg_display==0 + + static void _no_display_exception() { + throw CImgDisplayException("CImgDisplay(): No display available."); + } + + //! Destructor - Empty constructor \inplace. + /** + \note Replace the current instance by an empty display. + **/ + CImgDisplay& assign() { + return flush(); + } + + //! Construct a display with specified dimensions \inplace. + /** + **/ + CImgDisplay& assign(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + cimg::unused(width,height,title,normalization,is_fullscreen,is_closed); + _no_display_exception(); + return assign(); + } + + //! Construct a display from an image \inplace. + /** + **/ + template + CImgDisplay& assign(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list \inplace. + /** + **/ + template + CImgDisplay& assign(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of another one \inplace. + /** + **/ + CImgDisplay& assign(const CImgDisplay &disp) { + _no_display_exception(); + return assign(disp._width,disp._height); + } + +#endif + + //! Return a reference to an empty display. + /** + \note Can be useful for writing function prototypes where one of the argument (of type CImgDisplay&) + must have a default value. + \par Example + \code + void foo(CImgDisplay& disp=CImgDisplay::empty()); + \endcode + **/ + static CImgDisplay& empty() { + static CImgDisplay _empty; + return _empty.assign(); + } + + //! Return a reference to an empty display \const. + static const CImgDisplay& const_empty() { + static const CImgDisplay _empty; + return _empty; + } + +#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,256,-85,false), \ + CImgDisplay::_fitscreen(dx,dy,dz,256,-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 int + u = CImgDisplay::screen_width(), + v = CImgDisplay::screen_height(); + const float + mw = dmin<0?cimg::round(u*-dmin/100.f):(float)dmin, + mh = dmin<0?cimg::round(v*-dmin/100.f):(float)dmin, + Mw = dmax<0?cimg::round(u*-dmax/100.f):(float)dmax, + Mh = dmax<0?cimg::round(v*-dmax/100.f):(float)dmax; + float + w = (float)std::max(1U,dx), + h = (float)std::max(1U,dy); + if (dz>1) { w+=dz; h+=dz; } + if (wMw) { h = h*Mw/w; w = Mw; } + if (h>Mh) { w = w*Mh/h; h = Mh; } + if (wdisp = img is equivalent to disp.display(img). + **/ + template + CImgDisplay& operator=(const CImg& img) { + return display(img); + } + + //! Display list of images on associated window. + /** + \note disp = list is equivalent to disp.display(list). + **/ + template + CImgDisplay& operator=(const CImgList& list) { + return display(list); + } + + //! Construct a display as a copy of another one \inplace. + /** + \note Equivalent to assign(const CImgDisplay&). + **/ + CImgDisplay& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return \c false if display is empty, \c true otherwise. + /** + \note if (disp) { ... } is equivalent to if (!disp.is_empty()) { ... }. + **/ + operator bool() const { + return !is_empty(); + } + + //@} + //------------------------------------------ + // + //! \name Instance Checking + //@{ + //------------------------------------------ + + //! Return \c true if display is empty, \c false otherwise. + /** + **/ + bool is_empty() const { + return !(_width && _height); + } + + //! Return \c true if display is closed (i.e. not visible on the screen), \c false otherwise. + /** + \note + - When a user physically closes the associated window, the display is set to closed. + - A closed display is not destroyed. Its associated window can be show again on the screen using show(). + **/ + bool is_closed() const { + return _is_closed; + } + + //! Return \c true if associated window has been resized on the screen, \c false otherwise. + /** + **/ + bool is_resized() const { + return _is_resized; + } + + //! Return \c true if associated window has been moved on the screen, \c false otherwise. + /** + **/ + bool is_moved() const { + return _is_moved; + } + + //! Return \c true if any event has occured on the associated window, \c false otherwise. + /** + **/ + bool is_event() const { + return _is_event; + } + + //! Return \c true if current display is in fullscreen mode, \c false otherwise. + /** + **/ + bool is_fullscreen() const { + return _is_fullscreen; + } + + //! Return \c true if any key is being pressed on the associated window, \c false otherwise. + /** + \note The methods below do the same only for specific keys. + **/ + bool is_key() const { + return _is_keyESC || _is_keyF1 || _is_keyF2 || _is_keyF3 || + _is_keyF4 || _is_keyF5 || _is_keyF6 || _is_keyF7 || + _is_keyF8 || _is_keyF9 || _is_keyF10 || _is_keyF11 || + _is_keyF12 || _is_keyPAUSE || _is_key1 || _is_key2 || + _is_key3 || _is_key4 || _is_key5 || _is_key6 || + _is_key7 || _is_key8 || _is_key9 || _is_key0 || + _is_keyBACKSPACE || _is_keyINSERT || _is_keyHOME || + _is_keyPAGEUP || _is_keyTAB || _is_keyQ || _is_keyW || + _is_keyE || _is_keyR || _is_keyT || _is_keyY || + _is_keyU || _is_keyI || _is_keyO || _is_keyP || + _is_keyDELETE || _is_keyEND || _is_keyPAGEDOWN || + _is_keyCAPSLOCK || _is_keyA || _is_keyS || _is_keyD || + _is_keyF || _is_keyG || _is_keyH || _is_keyJ || + _is_keyK || _is_keyL || _is_keyENTER || + _is_keySHIFTLEFT || _is_keyZ || _is_keyX || _is_keyC || + _is_keyV || _is_keyB || _is_keyN || _is_keyM || + _is_keySHIFTRIGHT || _is_keyARROWUP || _is_keyCTRLLEFT || + _is_keyAPPLEFT || _is_keyALT || _is_keySPACE || _is_keyALTGR || + _is_keyAPPRIGHT || _is_keyMENU || _is_keyCTRLRIGHT || + _is_keyARROWLEFT || _is_keyARROWDOWN || _is_keyARROWRIGHT || + _is_keyPAD0 || _is_keyPAD1 || _is_keyPAD2 || + _is_keyPAD3 || _is_keyPAD4 || _is_keyPAD5 || + _is_keyPAD6 || _is_keyPAD7 || _is_keyPAD8 || + _is_keyPAD9 || _is_keyPADADD || _is_keyPADSUB || + _is_keyPADMUL || _is_keyPADDIV; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode Keycode to test. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.key(cimg::keyTAB)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool is_key(const unsigned int keycode) const { +#define _cimg_iskey_test(k) if (keycode==cimg::key##k) return _is_key##k; + _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3); + _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7); + _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11); + _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2); + _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6); + _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0); + _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME); + _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W); + _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y); + _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P); + _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN); + _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D); + _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J); + _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER); + _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C); + _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M); + _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT); + _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR); + _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT); + _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT); + _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2); + _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5); + _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8); + _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB); + _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV); + return false; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode C-string containing the keycode label of the key to test. + \note Use it when the key you want to test can be dynamically set by the user. + \par Example + \code + CImgDisplay disp(400,400); + const char *const keycode = "TAB"; + while (!disp.is_closed()) { + if (disp.is_key(keycode)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool& is_key(const char *const keycode) { + static bool f = false; + f = false; +#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(keycode,#k)) return _is_key##k; + _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3); + _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7); + _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11); + _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2); + _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6); + _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0); + _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME); + _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W); + _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y); + _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P); + _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN); + _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D); + _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J); + _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER); + _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C); + _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M); + _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT); + _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR); + _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT); + _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT); + _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2); + _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5); + _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8); + _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB); + _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV); + return f; + } + + //! Return \c true if specified key sequence has been typed on the associated window, \c false otherwise. + /** + \param keycodes_sequence Buffer of keycodes to test. + \param length Number of keys in the \c keycodes_sequence buffer. + \param remove_sequence Tells if the key sequence must be removed from the key history, if found. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + const unsigned int key_seq[] = { cimg::keyCTRLLEFT, cimg::keyD }; + while (!disp.is_closed()) { + if (disp.is_key_sequence(key_seq,2)) { ... } // Test for the 'CTRL+D' keyboard event + disp.wait(); + } + \endcode + **/ + bool is_key_sequence(const unsigned int *const keycodes_sequence, const unsigned int length, + const bool remove_sequence=false) { + if (keycodes_sequence && length) { + const unsigned int + *const ps_end = keycodes_sequence + length - 1, + *const pk_end = (unsigned int*)_keys + 1 + 128 - length, + k = *ps_end; + for (unsigned int *pk = (unsigned int*)_keys; pk[0,255]. + If the range of values of the data to display is different, a normalization may be required for displaying + the data in a correct way. The normalization type can be one of: + - \c 0: Value normalization is disabled. It is then assumed that all input data to be displayed by the + CImgDisplay instance have values in range [0,255]. + - \c 1: Value normalization is always performed (this is the default behavior). + Before displaying an input image, its values will be (virtually) stretched + in range [0,255], so that the contrast of the displayed pixels will be maximum. + Use this mode for images whose minimum and maximum values are not prescribed to known values + (e.g. float-valued images). + Note that when normalized versions of images are computed for display purposes, the actual values of these + images are not modified. + - \c 2: Value normalization is performed once (on the first image display), then the same normalization + coefficients are kept for next displayed frames. + - \c 3: Value normalization depends on the pixel type of the data to display. For integer pixel types, + the normalization is done regarding the minimum/maximum values of the type (no normalization occurs then + for unsigned char). + For float-valued pixel types, the normalization is done regarding the minimum/maximum value of the image + data instead. + **/ + unsigned int normalization() const { + return _normalization; + } + + //! Return title of the associated window as a C-string. + /** + \note Window title may be not visible, depending on the used window manager or if the current display is + in fullscreen mode. + **/ + const char *title() const { + return _title?_title:""; + } + + //! Return width of the associated window. + /** + \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual width of the associated window. + **/ + int window_width() const { + return (int)_window_width; + } + + //! Return height of the associated window. + /** + \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual height of the associated window. + **/ + int window_height() const { + return (int)_window_height; + } + + //! Return X-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_x() const { + return _window_x; + } + + //! Return Y-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_y() const { + return _window_y; + } + + //! Return X-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,width()-1]. + **/ + int mouse_x() const { + return _mouse_x; + } + + //! Return Y-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,height()-1]. + **/ + int mouse_y() const { + return _mouse_y; + } + + //! Return current state of the mouse buttons. + /** + \note Three mouse buttons can be managed. If one button is pressed, its corresponding bit in the returned + value is set: + - bit \c 0 (value \c 0x1): State of the left mouse button. + - bit \c 1 (value \c 0x2): State of the right mouse button. + - bit \c 2 (value \c 0x4): State of the middle mouse button. + + Several bits can be activated if more than one button are pressed at the same time. + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.button()&1) { // Left button clicked + ... + } + if (disp.button()&2) { // Right button clicked + ... + } + if (disp.button()&4) { // Middle button clicked + ... + } + disp.wait(); + } + \endcode + **/ + unsigned int button() const { + return _button; + } + + //! Return current state of the mouse wheel. + /** + \note + - The returned value can be positive or negative depending on whether the mouse wheel has been scrolled + forward or backward. + - Scrolling the wheel forward add \c 1 to the wheel value. + - Scrolling the wheel backward substract \c 1 to the wheel value. + - The returned value cumulates the number of forward of backward scrolls since the creation of the display, + or since the last reset of the wheel value (using set_wheel()). It is strongly recommended to quickly reset + the wheel counter when an action has been performed regarding the current wheel value. + Otherwise, the returned wheel value may be for instance \c 0 despite the fact that many scrolls have been done + (as many in forward as in backward directions). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.wheel()) { + int counter = disp.wheel(); // Read the state of the mouse wheel + ... // Do what you want with 'counter' + disp.set_wheel(); // Reset the wheel value to 0 + } + disp.wait(); + } + \endcode + **/ + int wheel() const { + return _wheel; + } + + //! Return one entry from the pressed keys history. + /** + \param pos Indice to read from the pressed keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a pressed key or \c 0 for a released key. + \note + - Each CImgDisplay stores a history of the pressed keys in a buffer of size \c 128. When a new key is pressed, + its keycode is stored in the pressed keys history. When a key is released, \c 0 is put instead. + This means that up to the 64 last pressed keys may be read from the pressed keys history. + When a new value is stored, the pressed keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int key(const unsigned int pos=0) const { + return pos<128?_keys[pos]:0; + } + + //! Return one entry from the released keys history. + /** + \param pos Indice to read from the released keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a released key or \c 0 for a pressed key. + \note + - Each CImgDisplay stores a history of the released keys in a buffer of size \c 128. When a new key is released, + its keycode is stored in the pressed keys history. When a key is pressed, \c 0 is put instead. + This means that up to the 64 last released keys may be read from the released keys history. + When a new value is stored, the released keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int released_key(const unsigned int pos=0) const { + return pos<128?_released_keys[pos]:0; + } + + //! Return keycode corresponding to the specified string. + /** + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + const unsigned int keyTAB = CImgDisplay::keycode("TAB"); // Return cimg::keyTAB + \endcode + **/ + static unsigned int keycode(const char *const keycode) { +#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k; + _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3); + _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7); + _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11); + _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2); + _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6); + _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0); + _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME); + _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W); + _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y); + _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P); + _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN); + _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D); + _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J); + _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER); + _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C); + _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M); + _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT); + _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR); + _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT); + _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT); + _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2); + _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5); + _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8); + _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB); + _cimg_keycode(PADMUL); _cimg_keycode(PADDIV); + return 0; + } + + //! Return the current refresh rate, in frames per second. + /** + \note Returns a significant value when the current instance is used to display successive frames. + It measures the delay between successive calls to frames_per_second(). + **/ + float frames_per_second() { + if (!_fps_timer) _fps_timer = cimg::time(); + const float delta = (cimg::time() - _fps_timer)/1000.f; + ++_fps_frames; + if (delta>=1) { + _fps_fps = _fps_frames/delta; + _fps_frames = 0; + _fps_timer = cimg::time(); + } + 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; + } + + //@} + //--------------------------------------- + // + //! \name Window Manipulation + //@{ + //--------------------------------------- + +#if cimg_display==0 + + //! Display image on associated window. + /** + \param img Input image to display. + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImg& img) { + return assign(img); + } + +#endif + + //! Display list of images on associated window. + /** + \param list List of images to display. + \param axis Axis used to append the images along, for the visualization (can be \c x, \c y, \c z or \c c). + \param align Relative position of aligned images when displaying lists with images of different sizes + (\c 0 for upper-left, \c 0.5 for centering and \c 1 for lower-right). + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImgList& list, const char axis='x', const float align=0) { + if (list._width==1) { + const CImg& img = list[0]; + if (img._depth==1 && (img._spectrum==1 || img._spectrum>=3) && _normalization!=1) return display(img); + } + CImgList::ucharT> visu(list._width); + 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]); + dims = std::max(dims,visu[l]._spectrum); + } + cimglist_for(list,l) if (visu[l]._spectrumimg.width() become equal, as well as height() and + img.height(). + - The associated window is also resized to specified dimensions. + **/ + template + CImgDisplay& resize(const CImg& img, const bool force_redraw=true) { + return resize(img._width,img._height,force_redraw); + } + + //! Resize display to the size of another CImgDisplay instance. + /** + \param disp Input display to take size from. + \param force_redraw Tells if the previous window content must be resized and updated as well. + \note + - Calling this method ensures that width() and disp.width() become equal, as well as height() and + disp.height(). + - The associated window is also resized to specified dimensions. + **/ + CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) { + return resize(disp.width(),disp.height(),force_redraw); + } + + // [internal] Render pixel buffer with size (wd,hd) from source buffer of size (ws,hs). + template + 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) { + typedef typename cimg::last::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; xstd::printf(). + \warning As the first argument is a format string, it is highly recommended to write + \code + disp.set_title("%s",window_title); + \endcode + instead of + \code + disp.set_title(window_title); + \endcode + if \c window_title can be arbitrary, to prevent nasty memory access. + **/ + CImgDisplay& set_title(const char *const format, ...) { + return assign(0,0,format); + } + +#endif + + //! Enable or disable fullscreen mode. + /** + \param is_fullscreen Tells is the fullscreen mode must be activated or not. + \param force_redraw Tells if the previous window content must be displayed as well. + \note + - When the fullscreen mode is enabled, the associated window fills the entire screen but the size of the + current display is not modified. + - The screen resolution may be switched to fit the associated window size and ensure it appears the largest + as possible. + For X-Window (X11) users, the configuration flag \c cimg_use_xrandr has to be set to allow the screen + resolution change (requires the X11 extensions to be enabled). + **/ + CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool force_redraw=true) { + if (is_empty() || _is_fullscreen==is_fullscreen) return *this; + return toggle_fullscreen(force_redraw); + } + +#if cimg_display==0 + + //! Toggle fullscreen mode. + /** + \param force_redraw Tells if the previous window content must be displayed as well. + \note Enable fullscreen mode if it was not enabled, and disable it otherwise. + **/ + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + return assign(_width,_height,0,3,force_redraw); + } + + //! Show mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& show_mouse() { + return assign(); + } + + //! Hide mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& hide_mouse() { + return assign(); + } + + //! Move mouse pointer to a specified location. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& set_mouse(const int pos_x, const int pos_y) { + return assign(pos_x,pos_y); + } + +#endif + + //! Simulate a mouse button release event. + /** + \note All mouse buttons are considered released at the same time. + **/ + CImgDisplay& set_button() { + _button = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a mouse button press or release event. + /** + \param button Buttons event code, where each button is associated to a single bit. + \param is_pressed Tells if the mouse button is considered as pressed or released. + **/ + CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) { + const unsigned int buttoncode = button==1U?1U:button==2U?2U:button==3U?4U:0U; + if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode; + _is_event = buttoncode?true:false; + if (buttoncode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all mouse wheel events. + /** + \note Make wheel() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_wheel() { + _wheel = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a wheel event. + /** + \param amplitude Amplitude of the wheel scrolling to simulate. + \note Make wheel() to return \c amplitude, if called afterwards. + **/ + CImgDisplay& set_wheel(const int amplitude) { + _wheel+=amplitude; + _is_event = amplitude?true:false; + if (amplitude) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all key events. + /** + \note Make key() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_key() { + std::memset((void*)_keys,0,128*sizeof(unsigned int)); + std::memset((void*)_released_keys,0,128*sizeof(unsigned int)); + _is_keyESC = _is_keyF1 = _is_keyF2 = _is_keyF3 = _is_keyF4 = _is_keyF5 = _is_keyF6 = _is_keyF7 = _is_keyF8 = + _is_keyF9 = _is_keyF10 = _is_keyF11 = _is_keyF12 = _is_keyPAUSE = _is_key1 = _is_key2 = _is_key3 = _is_key4 = + _is_key5 = _is_key6 = _is_key7 = _is_key8 = _is_key9 = _is_key0 = _is_keyBACKSPACE = _is_keyINSERT = + _is_keyHOME = _is_keyPAGEUP = _is_keyTAB = _is_keyQ = _is_keyW = _is_keyE = _is_keyR = _is_keyT = _is_keyY = + _is_keyU = _is_keyI = _is_keyO = _is_keyP = _is_keyDELETE = _is_keyEND = _is_keyPAGEDOWN = _is_keyCAPSLOCK = + _is_keyA = _is_keyS = _is_keyD = _is_keyF = _is_keyG = _is_keyH = _is_keyJ = _is_keyK = _is_keyL = + _is_keyENTER = _is_keySHIFTLEFT = _is_keyZ = _is_keyX = _is_keyC = _is_keyV = _is_keyB = _is_keyN = + _is_keyM = _is_keySHIFTRIGHT = _is_keyARROWUP = _is_keyCTRLLEFT = _is_keyAPPLEFT = _is_keyALT = _is_keySPACE = + _is_keyALTGR = _is_keyAPPRIGHT = _is_keyMENU = _is_keyCTRLRIGHT = _is_keyARROWLEFT = _is_keyARROWDOWN = + _is_keyARROWRIGHT = _is_keyPAD0 = _is_keyPAD1 = _is_keyPAD2 = _is_keyPAD3 = _is_keyPAD4 = _is_keyPAD5 = + _is_keyPAD6 = _is_keyPAD7 = _is_keyPAD8 = _is_keyPAD9 = _is_keyPADADD = _is_keyPADSUB = _is_keyPADMUL = + _is_keyPADDIV = false; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a keyboard press/release event. + /** + \param keycode Keycode of the associated key. + \param is_pressed Tells if the key is considered as pressed or released. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + CImgDisplay& set_key(const unsigned int keycode, const bool is_pressed=true) { +#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = is_pressed; + _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3); + _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7); + _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11); + _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2); + _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6); + _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0); + _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME); + _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W); + _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y); + _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P); + _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN); + _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D); + _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J); + _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER); + _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C); + _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M); + _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT); + _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR); + _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT); + _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT); + _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2); + _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5); + _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8); + _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB); + _cimg_set_key(PADMUL); _cimg_set_key(PADDIV); + if (is_pressed) { + if (*_keys) + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = keycode; + if (*_released_keys) { + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = 0; + } + } else { + if (*_keys) { + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = 0; + } + if (*_released_keys) + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = keycode; + } + _is_event = keycode?true:false; + if (keycode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all display events. + /** + \note Remove all passed events from the current display. + **/ + CImgDisplay& flush() { + set_key().set_button().set_wheel(); + _is_resized = _is_moved = _is_event = false; + _fps_timer = _fps_frames = _timer = 0; + _fps_fps = 0; + return *this; + } + + //! Wait for any user event occuring on the current display. + CImgDisplay& wait() { + wait(*this); + return *this; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \note Similar to cimg::wait(). + **/ + CImgDisplay& wait(const unsigned int milliseconds) { + cimg::wait(milliseconds,&_timer); + return *this; + } + + //! Wait for any event occuring on the display \c disp1. + static void wait(CImgDisplay& disp1) { + disp1._is_event = false; + while (!disp1._is_closed && !disp1._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1 or \c disp2. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2) { + disp1._is_event = disp2._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed) && + !disp1._is_event && !disp2._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) { + disp1._is_event = disp2._is_event = disp3._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4 or \c disp5. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, + CImgDisplay& disp5) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event) + wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp6. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp7. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp8. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp9. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp10. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9, + CImgDisplay& disp10) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = disp10._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event) + wait_all(); + } + +#if cimg_display==0 + + //! Wait for any window event occuring in any opened CImgDisplay. + static void wait_all() { + return _no_display_exception(); + } + + //! Render image into internal display buffer. + /** + \param img Input image data to render. + \note + - Convert image data representation into the internal display buffer (architecture-dependent structure). + - The content of the associated window is not modified, until paint() is called. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + template + CImgDisplay& render(const CImg& img) { + return assign(img); + } + + //! Paint internal display buffer on associated window. + /** + \note + - Update the content of the associated window with the internal display buffer, e.g. after a render() call. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + CImgDisplay& paint() { + return assign(); + } + + + //! Take a snapshot of the current screen content. + /** + \param x0 X-coordinate of the upper left corner. + \param y0 Y-coordinate of the upper left corner. + \param x1 X-coordinate of the lower right corner. + \param y1 Y-coordinate of the lower right corner. + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + cimg::unused(x0,y0,x1,y1,&img); + _no_display_exception(); + } + + //! Take a snapshot of the associated window content. + /** + \param[out] img Output snapshot. Can be empty on input. + **/ + template + const CImgDisplay& snapshot(CImg& img) const { + cimg::unused(img); + _no_display_exception(); + return *this; + } +#endif + + // X11-based implementation + //-------------------------- +#if cimg_display==1 + + Atom _wm_window_atom, _wm_protocol_atom; + Window _window, _background_window; + Colormap _colormap; + XImage *_image; + void *_data; +#ifdef cimg_use_xshm + XShmSegmentInfo *_shminfo; +#endif + + static int screen_width() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_width(): Failed to open X11 display."); + res = DisplayWidth(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width; + else res = DisplayWidth(dpy,DefaultScreen(dpy)); +#else + res = DisplayWidth(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static int screen_height() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_height(): Failed to open X11 display."); + res = DisplayHeight(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height; + else res = DisplayHeight(dpy,DefaultScreen(dpy)); +#else + res = DisplayHeight(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static void wait_all() { + if (!cimg::X11_attr().display) return; + pthread_mutex_lock(&cimg::X11_attr().wait_event_mutex); + pthread_cond_wait(&cimg::X11_attr().wait_event,&cimg::X11_attr().wait_event_mutex); + pthread_mutex_unlock(&cimg::X11_attr().wait_event_mutex); + } + + void _handle_events(const XEvent *const pevent) { + Display *const dpy = cimg::X11_attr().display; + XEvent event = *pevent; + switch (event.type) { + case ClientMessage : { + if ((int)event.xclient.message_type==(int)_wm_protocol_atom && + (int)event.xclient.data.l[0]==(int)_wm_window_atom) { + XUnmapWindow(cimg::X11_attr().display,_window); + _is_closed = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case ConfigureNotify : { + while (XCheckWindowEvent(dpy,_window,StructureNotifyMask,&event)) {} + const unsigned int nw = event.xconfigure.width, nh = event.xconfigure.height; + const int nx = event.xconfigure.x, ny = event.xconfigure.y; + if (nw && nh && (nw!=_window_width || nh!=_window_height)) { + _window_width = nw; _window_height = nh; _mouse_x = _mouse_y = -1; + XResizeWindow(dpy,_window,_window_width,_window_height); + _is_resized = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + if (nx!=_window_x || ny!=_window_y) { + _window_x = nx; _window_y = ny; _is_moved = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case Expose : { + while (XCheckWindowEvent(dpy,_window,ExposureMask,&event)) {} + _paint(false); + if (_is_fullscreen) { + XWindowAttributes attr; + XGetWindowAttributes(dpy,_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XSetInputFocus(dpy,_window,RevertToParent,CurrentTime); + } + } break; + case ButtonPress : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1); break; + case 3 : set_button(2); break; + case 2 : set_button(3); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonPressMask,&event)); + } break; + case ButtonRelease : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1,false); break; + case 3 : set_button(2,false); break; + case 2 : set_button(3,false); break; + case 4 : set_wheel(1); break; + case 5 : set_wheel(-1); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonReleaseMask,&event)); + } break; + case KeyPress : { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,true); + } break; + case KeyRelease : { + char keys_return[32]; // Check that the key has been physically unpressed + XQueryKeymap(dpy,keys_return); + const unsigned int kc = event.xkey.keycode, kc1 = kc/8, kc2 = kc%8; + const bool is_key_pressed = kc1>=32?false:(keys_return[kc1]>>kc2)&1; + if (!is_key_pressed) { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,false); + } + } break; + case EnterNotify: { + while (XCheckWindowEvent(dpy,_window,EnterWindowMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + } break; + case LeaveNotify : { + while (XCheckWindowEvent(dpy,_window,LeaveWindowMask,&event)) {} + _mouse_x = _mouse_y = -1; _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + case MotionNotify : { + while (XCheckWindowEvent(dpy,_window,PointerMotionMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + } + } + + static void* _events_thread(void *arg) { // Thread to manage events for all opened display windows + Display *const dpy = cimg::X11_attr().display; + XEvent event; + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0); + if (!arg) for ( ; ; ) { + cimg_lock_display(); + bool event_flag = XCheckTypedEvent(dpy,ClientMessage,&event); + if (!event_flag) event_flag = XCheckMaskEvent(dpy, + ExposureMask | StructureNotifyMask | ButtonPressMask | + KeyPressMask | PointerMotionMask | EnterWindowMask | + LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask,&event); + if (event_flag) + for (unsigned int i = 0; i_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window) + cimg::X11_attr().wins[i]->_handle_events(&event); + cimg_unlock_display(); + pthread_testcancel(); + cimg::sleep(8); + } + return 0; + } + + void _set_colormap(Colormap& _colormap, const unsigned int dim) { + XColor *const colormap = new XColor[256]; + switch (dim) { + case 1 : { // colormap for greyscale images + for (unsigned int index = 0; index<256; ++index) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].green = colormap[index].blue = (unsigned short)(index<<8); + colormap[index].flags = DoRed | DoGreen | DoBlue; + } + } break; + case 2 : { // colormap for RG images + for (unsigned int index = 0, r = 8; r<256; r+=16) + for (unsigned int g = 8; g<256; g+=16) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].blue = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } break; + default : { // colormap for RGB images + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap[index].pixel = index; + colormap[index].red = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index].blue = (unsigned short)(b<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } + } + XStoreColors(cimg::X11_attr().display,_colormap,colormap,256); + delete[] colormap; + } + + void _map_window() { + Display *const dpy = cimg::X11_attr().display; + bool is_exposed = false, is_mapped = false; + XWindowAttributes attr; + XEvent event; + XMapRaised(dpy,_window); + do { // Wait for the window to be mapped + XWindowEvent(dpy,_window,StructureNotifyMask | ExposureMask,&event); + switch (event.type) { + case MapNotify : is_mapped = true; break; + case Expose : is_exposed = true; break; + } + } while (!is_exposed || !is_mapped); + do { // Wait for the window to be visible + XGetWindowAttributes(dpy,_window,&attr); + if (attr.map_state!=IsViewable) { XSync(dpy,0); cimg::sleep(10); } + } while (attr.map_state!=IsViewable); + _window_x = attr.x; + _window_y = attr.y; + } + + void _paint(const bool wait_expose=true) { + if (_is_closed || !_image) return; + Display *const dpy = cimg::X11_attr().display; + if (wait_expose) { // Send an expose event sticked to display window to force repaint + XEvent event; + event.xexpose.type = Expose; + event.xexpose.serial = 0; + event.xexpose.send_event = 1; + event.xexpose.display = dpy; + event.xexpose.window = _window; + event.xexpose.x = 0; + event.xexpose.y = 0; + event.xexpose.width = width(); + event.xexpose.height = height(); + event.xexpose.count = 0; + XSendEvent(dpy,_window,0,0,&event); + } else { // Repaint directly (may be called from the expose event) + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height,1); + else XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#else + XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#endif + } + } + + template + void _resize(T pixel_type, const unsigned int ndimx, const unsigned int ndimy, const bool force_redraw) { + Display *const dpy = cimg::X11_attr().display; + cimg::unused(pixel_type); + +#ifdef cimg_use_xshm + if (_shminfo) { + XShmSegmentInfo *const nshminfo = new XShmSegmentInfo; + XImage *const nimage = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy); + if (!nimage) { delete nshminfo; return; } + else { + nshminfo->shmid = shmget(IPC_PRIVATE,ndimx*ndimy*sizeof(T),IPC_CREAT | 0777); + if (nshminfo->shmid==-1) { XDestroyImage(nimage); delete nshminfo; return; } + else { + nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0); + if (nshminfo->shmaddr==(char*)-1) { + shmctl(nshminfo->shmid,IPC_RMID,0); XDestroyImage(nimage); delete nshminfo; return; + } else { + nshminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,nshminfo); + XFlush(dpy); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(nshminfo->shmaddr); + shmctl(nshminfo->shmid,IPC_RMID,0); + XDestroyImage(nimage); + delete nshminfo; + return; + } else { + T *const ndata = (T*)nimage->data; + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + XShmDetach(dpy,_shminfo); + XDestroyImage(_image); + shmdt(_shminfo->shmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = nshminfo; + _image = nimage; + _data = (void*)ndata; + } + } + } + } + } else +#endif + { + T *ndata = (T*)std::malloc(ndimx*ndimy*sizeof(T)); + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + _data = (void*)ndata; + XDestroyImage(_image); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,ndimx,ndimy,8,0); + } + } + + void _init_fullscreen() { + if (!_is_fullscreen || _is_closed) return; + Display *const dpy = cimg::X11_attr().display; + _background_window = 0; + +#ifdef cimg_use_xrandr + int foo; + if (XRRQueryExtension(dpy,&foo,&foo)) { + XRRRotations(dpy,DefaultScreen(dpy),&cimg::X11_attr().curr_rotation); + if (!cimg::X11_attr().resolutions) { + cimg::X11_attr().resolutions = XRRSizes(dpy,DefaultScreen(dpy),&foo); + cimg::X11_attr().nb_resolutions = (unsigned int)foo; + } + if (cimg::X11_attr().resolutions) { + cimg::X11_attr().curr_resolution = 0; + for (unsigned int i = 0; i=_width && nh>=_height && + nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) && + nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height)) + cimg::X11_attr().curr_resolution = i; + } + if (cimg::X11_attr().curr_resolution>0) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy), + cimg::X11_attr().curr_resolution,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + } + } + } + if (!cimg::X11_attr().resolutions) + cimg::warn(_cimgdisplay_instance + "init_fullscreen(): Xrandr extension not supported by the X server.", + cimgdisplay_instance); +#endif + + const unsigned int sx = screen_width(), sy = screen_height(); + if (sx==_width && sy==_height) return; + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _background_window = XCreateWindow(dpy,DefaultRootWindow(dpy),0,0,sx,sy,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + const cimg_ulong buf_size = (cimg_ulong)sx*sy*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + void *background_data = std::malloc(buf_size); + std::memset(background_data,0,buf_size); + XImage *background_image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)background_data,sx,sy,8,0); + XEvent event; + XSelectInput(dpy,_background_window,StructureNotifyMask); + XMapRaised(dpy,_background_window); + do XWindowEvent(dpy,_background_window,StructureNotifyMask,&event); + while (event.type!=MapNotify); + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy,0); + else XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#else + XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#endif + XWindowAttributes attr; + XGetWindowAttributes(dpy,_background_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XDestroyImage(background_image); + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + Display *const dpy = cimg::X11_attr().display; + XUngrabKeyboard(dpy,CurrentTime); +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),0,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + cimg::X11_attr().curr_resolution = 0; + } +#endif + if (_background_window) XDestroyWindow(dpy,_background_window); + _background_window = 0; + _is_fullscreen = false; + } + + static int _assign_xshm(Display *dpy, XErrorEvent *error) { + cimg::unused(dpy,error); + cimg::X11_attr().is_shm_enabled = false; + return 0; + } + + void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + cimg::mutex(14); + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous display window if existing + if (!is_empty()) assign(); + + // Open X11 display and retrieve graphical properties. + Display* &dpy = cimg::X11_attr().display; + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Failed to open X11 display.", + cimgdisplay_instance); + + cimg::X11_attr().nb_bits = DefaultDepth(dpy,DefaultScreen(dpy)); + if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 && + cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Invalid %u bits screen mode detected " + "(only 8, 16, 24 and 32 bits modes are managed).", + cimgdisplay_instance, + cimg::X11_attr().nb_bits); + XVisualInfo vtemplate; + vtemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,DefaultScreen(dpy))); + int nb_visuals; + XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals); + if (vinfo && vinfo->red_maskblue_mask) cimg::X11_attr().is_blue_first = true; + cimg::X11_attr().byte_order = ImageByteOrder(dpy); + XFree(vinfo); + + cimg_lock_display(); + cimg::X11_attr().events_thread = new pthread_t; + pthread_create(cimg::X11_attr().events_thread,0,_events_thread,0); + } else cimg_lock_display(); + + // Set display variables. + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _title = tmp_title; + flush(); + + // Create X11 window (and LUT, if 8bits display) + if (_is_fullscreen) { + if (!_is_closed) _init_fullscreen(); + const unsigned int sx = screen_width(), sy = screen_height(); + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx - _width)/2,(sy - _height)/2,_width,_height,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + } else + _window = XCreateSimpleWindow(dpy,DefaultRootWindow(dpy),0,0,_width,_height,0,0L,0L); + + XSelectInput(dpy,_window, + ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask | + EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask); + + XStoreName(dpy,_window,_title?_title:" "); + if (cimg::X11_attr().nb_bits==8) { + _colormap = XCreateColormap(dpy,_window,DefaultVisual(dpy,DefaultScreen(dpy)),AllocAll); + _set_colormap(_colormap,3); + XSetWindowColormap(dpy,_window,_colormap); + } + + static const char *const _window_class = cimg_appname; + XClassHint *const window_class = XAllocClassHint(); + window_class->res_name = (char*)_window_class; + window_class->res_class = (char*)_window_class; + XSetClassHint(dpy,_window,window_class); + XFree(window_class); + + _window_width = _width; + _window_height = _height; + + // Create XImage +#ifdef cimg_use_xshm + _shminfo = 0; + if (XShmQueryExtension(dpy)) { + _shminfo = new XShmSegmentInfo; + _image = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,_shminfo,_width,_height); + if (!_image) { delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmid = shmget(IPC_PRIVATE,_image->bytes_per_line*_image->height,IPC_CREAT|0777); + if (_shminfo->shmid==-1) { XDestroyImage(_image); delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmaddr = _image->data = (char*)(_data = shmat(_shminfo->shmid,0,0)); + if (_shminfo->shmaddr==(char*)-1) { + shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); delete _shminfo; _shminfo = 0; + } else { + _shminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,_shminfo); + XSync(dpy,0); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(_shminfo->shmaddr); shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); + delete _shminfo; _shminfo = 0; + } + } + } + } + } + if (!_shminfo) +#endif + { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + _data = std::malloc(buf_size); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)_data,_width,_height,8,0); + } + + _wm_window_atom = XInternAtom(dpy,"WM_DELETE_WINDOW",0); + _wm_protocol_atom = XInternAtom(dpy,"WM_PROTOCOLS",0); + XSetWMProtocols(dpy,_window,&_wm_window_atom,1); + + if (_is_fullscreen) XGrabKeyboard(dpy,_window,1,GrabModeAsync,GrabModeAsync,CurrentTime); + cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this; + if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type::min(); } + cimg_unlock_display(); + cimg::mutex(14,0); + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + + // Remove display window from event thread list. + unsigned int i; + for (i = 0; ishmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = 0; + } else +#endif + XDestroyImage(_image); + _data = 0; _image = 0; + if (cimg::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap); + _colormap = 0; + XSync(dpy,0); + + // Reset display variables. + delete[] _title; + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + + cimg_unlock_display(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + (cimg::X11_attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))* + (size_t)_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + cimg::X11_attr().nb_bits==16?sizeof(unsigned short): + sizeof(unsigned int))*(size_t)_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + Display *const dpy = cimg::X11_attr().display; + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*width()/100), + tmpdimy = (nheight>0)?nheight:(-nheight*height()/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + show(); + cimg_lock_display(); + if (_window_width!=dimx || _window_height!=dimy) { + XWindowAttributes attr; + for (unsigned int i = 0; i<10; ++i) { + XResizeWindow(dpy,_window,dimx,dimy); + XGetWindowAttributes(dpy,_window,&attr); + if (attr.width==(int)dimx && attr.height==(int)dimy) break; + cimg::wait(5,&_timer); + } + } + if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) { + case 8 : { unsigned char pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + case 16 : { unsigned short pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + default : { unsigned int pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } + } + _window_width = _width = dimx; _window_height = _height = dimy; + cimg_unlock_display(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - _width)/2,(screen_height() - _height)/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height* + (cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4)); + void *image_data = std::malloc(buf_size); + std::memcpy(image_data,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,image_data,buf_size); + std::free(image_data); + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + cimg_lock_display(); + if (_is_fullscreen) _init_fullscreen(); + _map_window(); + _is_closed = false; + cimg_unlock_display(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (_is_fullscreen) _desinit_fullscreen(); + XUnmapWindow(dpy,_window); + _window_x = _window_y = -1; + _is_closed = true; + cimg_unlock_display(); + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + show(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XMoveWindow(dpy,_window,posx,posy); + _window_x = posx; _window_y = posy; + cimg_unlock_display(); + } + _is_moved = false; + return paint(); + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XUndefineCursor(dpy,_window); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + static const char pix_data[8] = { 0 }; + XColor col; + col.red = col.green = col.blue = 0; + Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8); + Cursor cur = XCreatePixmapCursor(dpy,pix,pix,&col,&col,0,0); + XFreePixmap(dpy,pix); + XDefineCursor(dpy,_window,cur); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XWarpPointer(dpy,0L,_window,0,0,0,0,posx,posy); + _mouse_x = posx; _mouse_y = posy; + _is_moved = false; + XSync(dpy,0); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XStoreName(dpy,_window,tmp); + cimg_unlock_display(); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(false); + } + + CImgDisplay& paint(const bool wait_expose=true) { + if (is_empty()) return *this; + cimg_lock_display(); + _paint(wait_expose); + cimg_unlock_display(); + return *this; + } + + template + CImgDisplay& render(const CImg& img, const bool flag8=false) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + if (cimg::X11_attr().nb_bits==8 && (img._width!=_width || img._height!=_height)) + return render(img.get_resize(_width,_height,1,-100,1)); + if (cimg::X11_attr().nb_bits==8 && !flag8 && img._spectrum==3) { + static const CImg::ucharT> default_colormap = CImg::ucharT>::default_LUT256(); + return render(img.get_index(default_colormap,1,false)); + } + + const T + *data1 = img._data, + *data2 = (img._spectrum>1)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>2)?img.data(0,0,0,2):data1; + + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + cimg_lock_display(); + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, no normalization + _set_colormap(_colormap,img._spectrum); + unsigned char + *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height], + *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + (*ptrd++) = (unsigned char)*(data1++); + break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, no normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (G>>1); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (G<<5) | (G>>1); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, no normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | + (unsigned char)*(data3++); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | + ((unsigned char)*(data1++)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = 0; + ptrd[3] = 0; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = 0; + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = (unsigned char)*(data2++); + ptrd[3] = (unsigned char)*(data3++); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)*(data3++); + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, with normalization + _set_colormap(_colormap,img._spectrum); + unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char R = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = R; + } break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, with normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (val>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (G<<5) | (val>>3); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, with normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<24) | (val<<16) | (val<<8); + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8) | + (unsigned char)((*(data3++) - _min)*mm); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data3++) - _min)*mm)<<24) | + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = 0; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = val; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = val; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[3] = (unsigned char)((*(data3++) - _min)*mm); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)((*(data3++) - _min)*mm); + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } + cimg_unlock_display(); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + Display *dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to open X11 display."); + } + Window root = DefaultRootWindow(dpy); + XWindowAttributes gwa; + XGetWindowAttributes(dpy,root,&gwa); + const int width = gwa.width, height = gwa.height; + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + + XImage *image = 0; + if (_x1>=0 && _x0=0 && _y0red_mask, + green_mask = image->green_mask, + blue_mask = image->blue_mask; + img.assign(image->width,image->height,1,3); + T *pR = img.data(0,0,0,0), *pG = img.data(0,0,0,1), *pB = img.data(0,0,0,2); + cimg_forXY(img,x,y) { + const unsigned long pixel = XGetPixel(image,x,y); + *(pR++) = (T)((pixel & red_mask)>>16); + *(pG++) = (T)((pixel & green_mask)>>8); + *(pB++) = (T)(pixel & blue_mask); + } + XDestroyImage(image); + } + } + if (!cimg::X11_attr().display) XCloseDisplay(dpy); + cimg_unlock_display(); + if (img.is_empty()) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to take screenshot " + "with coordinates (%d,%d)-(%d,%d).", + x0,y0,x1,y1); + } + + template + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned char *ptrs = (unsigned char*)_data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + switch (cimg::X11_attr().nb_bits) { + case 8 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = *(ptrs++); + *(data1++) = (T)(val&0xe0); + *(data2++) = (T)((val&0x1c)<<3); + *(data3++) = (T)(val<<6); + } + } break; + case 16 : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val0&0xf8); + *(data2++) = (T)((val0<<5) | ((val1&0xe0)>>5)); + *(data3++) = (T)(val1<<3); + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned short + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val1&0xf8); + *(data2++) = (T)((val1<<5) | ((val0&0xe0)>>5)); + *(data3++) = (T)(val0<<3); + } + } break; + default : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ++ptrs; + *(data1++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data3++) = (T)ptrs[2]; + ptrs+=3; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + *(data3++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data1++) = (T)ptrs[2]; + ptrs+=3; + ++ptrs; + } + } + } + return *this; + } + + // Windows-based implementation. + //------------------------------- +#elif cimg_display==2 + + bool _is_mouse_tracked, _is_cursor_visible; + HANDLE _thread, _is_created, _mutex; + HWND _window, _background_window; + CLIENTCREATESTRUCT _ccs; + unsigned int *_data; + DEVMODE _curr_mode; + BITMAPINFO _bmi; + HDC _hdc; + + static int screen_width() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsWidth; + } + + static int screen_height() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsHeight; + } + + static void wait_all() { + WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE); + } + + static LRESULT APIENTRY _handle_events(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) { +#ifdef _WIN64 + CImgDisplay *const disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA); +#else + CImgDisplay *const disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA); +#endif + MSG st_msg; + switch (msg) { + case WM_CLOSE : + disp->_mouse_x = disp->_mouse_y = -1; + disp->_window_x = disp->_window_y = 0; + disp->set_button().set_key(0).set_key(0,false)._is_closed = true; + ReleaseMutex(disp->_mutex); + ShowWindow(disp->_window,SW_HIDE); + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + return 0; + case WM_SIZE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam); + if (nw && nh && (nw!=disp->_width || nh!=disp->_height)) { + disp->_window_width = nw; + disp->_window_height = nh; + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_resized = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_MOVE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam)); + if (nx!=disp->_window_x || ny!=disp->_window_y) { + disp->_window_x = nx; + disp->_window_y = ny; + disp->_is_moved = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_PAINT : + disp->paint(); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + break; + case WM_ERASEBKGND : + // return 0; + break; + case WM_KEYDOWN : + disp->set_key((unsigned int)wParam); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_KEYUP : + disp->set_key((unsigned int)wParam,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MOUSEMOVE : { + while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {} + disp->_mouse_x = LOWORD(lParam); + disp->_mouse_y = HIWORD(lParam); +#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT) + if (!disp->_is_mouse_tracked) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = disp->_window; + if (TrackMouseEvent(&tme)) disp->_is_mouse_tracked = true; + } +#endif + if (disp->_mouse_x<0 || disp->_mouse_y<0 || disp->_mouse_x>=disp->width() || disp->_mouse_y>=disp->height()) + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + } break; + case WM_MOUSELEAVE : { + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_mouse_tracked = false; + cimg_lock_display(); + while (ShowCursor(TRUE)<0) {} + cimg_unlock_display(); + } break; + case WM_LBUTTONDOWN : + disp->set_button(1); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONDOWN : + disp->set_button(2); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONDOWN : + disp->set_button(3); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_LBUTTONUP : + disp->set_button(1,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONUP : + disp->set_button(2,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONUP : + disp->set_button(3,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case 0x020A : // WM_MOUSEWHEEL: + disp->set_wheel((int)((short)HIWORD(wParam))/120); + SetEvent(cimg::Win32_attr().wait_event); + } + return DefWindowProc(window,msg,wParam,lParam); + } + + static DWORD WINAPI _events_thread(void* arg) { + CImgDisplay *const disp = (CImgDisplay*)(((void**)arg)[0]); + const char *const title = (const char*)(((void**)arg)[1]); + MSG msg; + delete[] (void**)arg; + disp->_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + disp->_bmi.bmiHeader.biWidth = disp->width(); + disp->_bmi.bmiHeader.biHeight = -disp->height(); + disp->_bmi.bmiHeader.biPlanes = 1; + disp->_bmi.bmiHeader.biBitCount = 32; + disp->_bmi.bmiHeader.biCompression = BI_RGB; + disp->_bmi.bmiHeader.biSizeImage = 0; + disp->_bmi.bmiHeader.biXPelsPerMeter = 1; + disp->_bmi.bmiHeader.biYPelsPerMeter = 1; + disp->_bmi.bmiHeader.biClrUsed = 0; + disp->_bmi.bmiHeader.biClrImportant = 0; + disp->_data = new unsigned int[(size_t)disp->_width*disp->_height]; + if (!disp->_is_fullscreen) { // Normal window + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)disp->_width - 1; rect.bottom = (LONG)disp->_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - disp->_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - disp->_height - border1); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_OVERLAPPEDWINDOW | (disp->_is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT, + disp->_width + 2*border1, disp->_height + border1 + border2, + 0,0,0,&(disp->_ccs)); + if (!disp->_is_closed) { + GetWindowRect(disp->_window,&rect); + disp->_window_x = rect.left + border1; + disp->_window_y = rect.top + border2; + } else disp->_window_x = disp->_window_y = 0; + } else { // Fullscreen window + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_POPUP | (disp->_is_closed?0:WS_VISIBLE), + (sx - disp->_width)/2, + (sy - disp->_height)/2, + disp->_width,disp->_height,0,0,0,&(disp->_ccs)); + disp->_window_x = disp->_window_y = 0; + } + SetForegroundWindow(disp->_window); + disp->_hdc = GetDC(disp->_window); + disp->_window_width = disp->_width; + disp->_window_height = disp->_height; + disp->flush(); +#ifdef _WIN64 + SetWindowLongPtr(disp->_window,GWLP_USERDATA,(LONG_PTR)disp); + SetWindowLongPtr(disp->_window,GWLP_WNDPROC,(LONG_PTR)_handle_events); +#else + SetWindowLong(disp->_window,GWL_USERDATA,(LONG)disp); + SetWindowLong(disp->_window,GWL_WNDPROC,(LONG)_handle_events); +#endif + SetEvent(disp->_is_created); + while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg); + return 0; + } + + CImgDisplay& _update_window_pos() { + if (_is_closed) _window_x = _window_y = -1; + else { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_width - 1; rect.bottom = (LONG)_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - _width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + GetWindowRect(_window,&rect); + _window_x = rect.left + border1; + _window_y = rect.top + border2; + } + return *this; + } + + void _init_fullscreen() { + _background_window = 0; + if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0; + else { + DEVMODE mode; + unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U; + for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) { + const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight; + if (nw>=_width && nh>=_height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) { + bestbpp = mode.dmBitsPerPel; + ibest = imode; + bw = nw; bh = nh; + } + } + if (bestbpp) { + _curr_mode.dmSize = sizeof(DEVMODE); _curr_mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&_curr_mode); + EnumDisplaySettings(0,ibest,&mode); + ChangeDisplaySettings(&mode,0); + } else _curr_mode.dmSize = 0; + + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + if (sx!=_width || sy!=_height) { + CLIENTCREATESTRUCT background_ccs; + _background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs); + SetForegroundWindow(_background_window); + } + } + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + if (_background_window) DestroyWindow(_background_window); + _background_window = 0; + if (_curr_mode.dmSize) ChangeDisplaySettings(&_curr_mode,0); + _is_fullscreen = false; + } + + CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous window if existing + if (!is_empty()) assign(); + + // Set display variables + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _is_cursor_visible = true; + _is_mouse_tracked = false; + _title = tmp_title; + flush(); + if (_is_fullscreen) _init_fullscreen(); + + // Create event thread + void *const arg = (void*)(new void*[2]); + ((void**)arg)[0] = (void*)this; + ((void**)arg)[1] = (void*)_title; + _mutex = CreateMutex(0,FALSE,0); + _is_created = CreateEvent(0,FALSE,FALSE,0); + _thread = CreateThread(0,0,_events_thread,arg,0,0); + WaitForSingleObject(_is_created,INFINITE); + return *this; + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + DestroyWindow(_window); + TerminateThread(_thread,0); + delete[] _data; + delete[] _title; + _data = 0; + _title = 0; + if (_is_fullscreen) _desinit_fullscreen(); + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,sizeof(unsigned int)*_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100), + tmpdimy = (nheight>0)?nheight:(-nheight*_height/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + if (_window_width!=dimx || _window_height!=dimy) { + RECT rect; rect.left = rect.top = 0; rect.right = (LONG)dimx - 1; rect.bottom = (LONG)dimy - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int cwidth = rect.right - rect.left + 1, cheight = rect.bottom - rect.top + 1; + SetWindowPos(_window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS); + } + if (_width!=dimx || _height!=dimy) { + unsigned int *const ndata = new unsigned int[dimx*dimy]; + if (force_redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy); + else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy); + delete[] _data; + _data = ndata; + _bmi.bmiHeader.biWidth = (LONG)dimx; + _bmi.bmiHeader.biHeight = -(int)dimy; + _width = dimx; + _height = dimy; + } + _window_width = dimx; _window_height = dimy; + show(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - width())/2,(screen_height() - height())/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*4; + void *odata = std::malloc(buf_size); + if (odata) { + std::memcpy(odata,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,odata,buf_size); + std::free(odata); + } + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + _is_closed = false; + if (_is_fullscreen) _init_fullscreen(); + ShowWindow(_window,SW_SHOW); + _update_window_pos(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + _is_closed = true; + if (_is_fullscreen) _desinit_fullscreen(); + ShowWindow(_window,SW_HIDE); + _window_x = _window_y = 0; + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + if (!_is_fullscreen) { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_window_width - 1; rect.bottom = (LONG)_window_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 -_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + SetWindowPos(_window,0,posx - border1,posy - border2,0,0,SWP_NOSIZE | SWP_NOZORDER); + } else SetWindowPos(_window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER); + _window_x = posx; + _window_y = posy; + show(); + } + _is_moved = false; + return *this; + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = true; + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = false; + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed || posx<0 || posy<0) return *this; + _update_window_pos(); + const int res = (int)SetCursorPos(_window_x + posx,_window_y + posy); + if (res) { _mouse_x = posx; _mouse_y = posy; } + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + SetWindowTextA(_window, tmp); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(); + } + + CImgDisplay& paint() { + if (_is_closed) return *this; + WaitForSingleObject(_mutex,INFINITE); + SetDIBitsToDevice(_hdc,0,0,_width,_height,0,0,0,_height,_data,&_bmi,DIB_RGB_COLORS); + ReleaseMutex(_mutex); + return *this; + } + + template + CImgDisplay& render(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + + const T + *data1 = img._data, + *data2 = (img._spectrum>=2)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>=3)?img.data(0,0,0,2):data1; + + WaitForSingleObject(_mutex,INFINITE); + unsigned int + *const ndata = (img._width==_width && img._height==_height)?_data: + new unsigned int[(size_t)img._width*img._height], + *ptrd = ndata; + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } + if (ndata!=_data) { _render_resize(ndata,img._width,img._height,_data,_width,_height); delete[] ndata; } + ReleaseMutex(_mutex); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + HDC hScreen = GetDC(GetDesktopWindow()); + if (hScreen) { + const int + width = GetDeviceCaps(hScreen,HORZRES), + height = GetDeviceCaps(hScreen,VERTRES); + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + if (_x1>=0 && _x0=0 && _y0 + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned int *ptrs = _data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned int val = *(ptrs++); + *(data1++) = (T)(unsigned char)(val>>16); + *(data2++) = (T)(unsigned char)((val>>8)&0xFF); + *(data3++) = (T)(unsigned char)(val&0xFF); + } + return *this; + } +#endif + + //@} + }; + + /* + #-------------------------------------- + # + # + # + # Definition of the CImg structure + # + # + # + #-------------------------------------- + */ + + //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T. + /** + This is the main class of the %CImg Library. It declares and constructs + an image, allows access to its pixel values, and is able to perform various image operations. + + \par Image representation + + A %CImg image is defined as an instance of the container \c CImg, which contains a regular grid of pixels, + each pixel value being of type \c T. The image grid can have up to 4 dimensions: width, height, depth + and number of channels. + Usually, the three first dimensions are used to describe spatial coordinates (x,y,z), + while the number of channels is rather used as a vector-valued dimension + (it may describe the R,G,B color channels for instance). + If you need a fifth dimension, you can use image lists \c CImgList 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, ...). + Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions. + + Concerning the pixel value type \c T: + fully supported template types are the basic C++ types: unsigned char, char, short, unsigned int, int, + unsigned long, long, float, double, ... . + Typically, fast image display can be done using CImg images, + while complex image processing algorithms may be rather coded using CImg or CImg + images that have floating-point pixel values. The default value for the template T is \c float. + Using your own template types may be possible. However, you will certainly have to define the complete set + of arithmetic and logical operators for your class. + + \par Image structure + + The \c CImg structure contains \e six fields: + - \c _width defines the number of \a columns of the image (size along the X-axis). + - \c _height defines the number of \a rows of the image (size along the Y-axis). + - \c _depth defines the number of \a slices of the image (size along the Z-axis). + - \c _spectrum defines the number of \a channels of the image (size along the C-axis). + - \c _data defines a \a pointer to the \a pixel \a data (of type \c T). + - \c _is_shared is a boolean that tells if the memory buffer \c data is shared with + another image. + + You can access these fields publicly although it is recommended to use the dedicated functions + width(), height(), depth(), spectrum() and ptr() to do so. + Image dimensions are not limited to a specific range (as long as you got enough available memory). + A value of \e 1 usually means that the corresponding dimension is \a flat. + If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty. + Empty images should not contain any pixel data and thus, will not be processed by CImg member functions + (a CImgInstanceException will be thrown instead). + Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage). + + \par Image declaration and construction + + Declaring an image can be done by using one of the several available constructors. + Here is a list of the most used: + + - Construct images from arbitrary dimensions: + - CImg img; declares an empty image. + - CImg img(128,128); declares a 128x128 greyscale image with + \c unsigned \c char pixel values. + - CImg img(3,3); declares a 3x3 matrix with \c double coefficients. + - CImg img(256,256,1,3); declares a 256x256x1x3 (color) image + (colors are stored as an image with three channels). + - CImg img(128,128,128); declares a 128x128x128 volumetric and greyscale image + (with \c double pixel values). + - CImg<> img(128,128,128,3); declares a 128x128x128 volumetric color image + (with \c float pixels, which is the default value of the template parameter \c T). + - \b Note: images pixels are not automatically initialized to 0. You may use the function \c fill() to + do it, or use the specific constructor taking 5 parameters like this: + CImg<> img(128,128,128,3,0); declares a 128x128x128 volumetric color image with all pixel values to 0. + + - Construct images from filenames: + - CImg img("image.jpg"); reads a JPEG color image from the file "image.jpg". + - CImg img("analyze.hdr"); reads a volumetric image (ANALYZE7.5 format) from the + file "analyze.hdr". + - \b Note: You need to install ImageMagick + to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io). + + - Construct images from C-style arrays: + - CImg img(data_buffer,256,256); constructs a 256x256 greyscale image from a \c int* buffer + \c data_buffer (of size 256x256=65536). + - CImg img(data_buffer,256,256,1,3); constructs a 256x256 color image + from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others). + + The complete list of constructors can be found here. + + \par Most useful functions + + The \c CImg class contains a lot of functions that operates on images. + Some of the most useful are: + + - operator()(): Read or write pixel values. + - display(): displays the image in a new window. + **/ + template + struct CImg { + + unsigned int _width, _height, _depth, _spectrum; + bool _is_shared; + T *_data; + + //! Simple iterator type, to loop through each pixel value of an image instance. + /** + \note + - The \c CImg::iterator type is defined to be a T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + CImg img("reference.jpg"); // Load image from file + // Set all pixels to '0', with a CImg iterator. + for (CImg::iterator it = img.begin(), it::const_iterator type is defined to be a \c const \c T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + 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::value_type type of a \c CImg is defined to be a \c T. + - \c CImg::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimg_plugin +#include cimg_plugin +#endif +#ifdef cimg_plugin1 +#include cimg_plugin1 +#endif +#ifdef cimg_plugin2 +#include cimg_plugin2 +#endif +#ifdef cimg_plugin3 +#include cimg_plugin3 +#endif +#ifdef cimg_plugin4 +#include cimg_plugin4 +#endif +#ifdef cimg_plugin5 +#include cimg_plugin5 +#endif +#ifdef cimg_plugin6 +#include cimg_plugin6 +#endif +#ifdef cimg_plugin7 +#include cimg_plugin7 +#endif +#ifdef cimg_plugin8 +#include cimg_plugin8 +#endif + + //@} + //--------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //--------------------------------------------------------- + + //! Destroy image. + /** + \note + - The pixel buffer data() is deallocated if necessary, e.g. for non-empty and non-shared image instances. + - Destroying an empty or shared image does nothing actually. + \warning + - When destroying a non-shared image, make sure that you will \e not operate on a remaining shared image + that shares its buffer with the destroyed instance, in order to avoid further invalid memory access + (to a deallocated buffer). + **/ + ~CImg() { + if (!_is_shared) delete[] _data; + } + + //! Construct empty image. + /** + \note + - An empty image has no pixel data and all of its dimensions width(), height(), depth(), spectrum() + are set to \c 0, as well as its pixel buffer pointer data(). + - An empty image may be re-assigned afterwards, e.g. with the family of + assign(unsigned int,unsigned int,unsigned int,unsigned int) methods, + or by operator=(const CImg&). In all cases, the type of pixels stays \c T. + - 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 + \endcode + **/ + CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {} + + //! Construct image with specified size. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \note + - It is able to create only \e non-shared images, and allocates thus a pixel buffer data() + for each constructed image instance. + - Setting one dimension \c size_x,\c size_y,\c size_z or \c size_c to \c 0 leads to the construction of + an \e empty image. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values during construction (e.g. with \c 0), use constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead. + \par Example + \code + CImg 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; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value Initialization value. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), + but it also fills the pixel buffer with the specified \c value. + \warning + - It cannot be used to construct a vector-valued image and initialize it with \e vector-valued pixels + (e.g. RGB vector, for color images). + For this task, you may use fillC() after construction. + **/ + CImg(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value): + _is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(value); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified sequence of integers \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be an \e integer). + \param value1 Second value of the initialization sequence (must be an \e integer). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \warning + - You must specify \e exactly \c size_x*\c size_y*\c size_z*\c size_c integers in the initialization sequence. + Otherwise, the constructor may crash or fill your image pixels with garbage. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _CImg_stdarg(img,a0,a1,N,t) { \ + size_t _siz = (size_t)N; \ + if (_siz--) { \ + va_list ap; \ + va_start(ap,a1); \ + T *ptrd = (img)._data; \ + *(ptrd++) = (T)a0; \ + if (_siz--) { \ + *(ptrd++) = (T)a1; \ + for ( ; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \ + } \ + va_end(ap); \ + } \ + } + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + } + +#if cimg_use_cpp11==1 + //! Construct image with specified size and initialize pixel values from an initializer list of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... } + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param { value0, value1, ... } Initialization list + \param repeat_values Tells if the value filling process is repeated over the image. + + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + { 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64 }); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _cimg_constructor_cpp11(repeat_values) \ + auto it = values.begin(); \ + size_t siz = size(); \ + if (repeat_values) for (T *ptrd = _data; siz--; ) { \ + *(ptrd++) = (T)(*(it++)); if (it==values.end()) it = values.begin(); } \ + else { siz = std::min(siz,values.size()); for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); } + assign(size_x,size_y,size_z,size_c); + _cimg_constructor_cpp11(repeat_values); + } + + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, const unsigned int size_y, + std::initializer_list values, + const bool repeat_values=true): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, + std::initializer_list values, + const bool repeat_values=true):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x); + _cimg_constructor_cpp11(repeat_values); + } + + //! Construct single channel 1D image with pixel values and width obtained from an initializer list of integers. + /** + Construct a new image instance of size \c width x \c 1 x \c 1 x \c 1, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... }. Image width is + given by the size of the initializer list. + \param { value0, value1, ... } Initialization list + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int) with height=1, depth=1, and spectrum=1, + but it also fills the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img = {10,20,30,20,10 }; // Construct a 5x1 image with one channel, and set its pixel values + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + CImg(const std::initializer_list values): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(values.size(),1,1,1); + auto it = values.begin(); + unsigned int siz = _width; + for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); + } + + template + CImg & operator=(std::initializer_list values) { + _cimg_constructor_cpp11(siz>values.size()); + return *this; + } +#endif + + //! Construct image with specified size and initialize pixel values from a sequence of doubles. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initialize pixel values from the specified sequence of doubles \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be a \e double). + \param value1 Second value of the initialization sequence (must be a \e double). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...), but + takes a sequence of double values instead of integers. + \warning + - You must specify \e exactly \c dx*\c dy*\c dz*\c dc doubles in the initialization sequence. + Otherwise, the constructor may crash or fill your image with garbage. + For instance, the code below will probably crash on most platforms: + \code + const CImg img(2,2,1,1, 0.5,0.5,255,255); // FAIL: The two last arguments are 'int', not 'double'! + \endcode + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + } + + //! Construct image with specified size and initialize pixel values from a value string. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified string \c values. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param values Value string describing the way pixel values are set. + \param repeat_values Tells if the value filling process is repeated over the image. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with values described in the value string \c values. + - Value string \c values may describe two different filling processes: + - Either \c values is a sequences of values assigned to the image pixels, as in "1,2,3,7,8,2". + In this case, set \c repeat_values to \c true to periodically fill the image with the value sequence. + - Either, \c values is a formula, as in "cos(x/10)*sin(y/20)". + In this case, parameter \c repeat_values is pointless. + - For both cases, specifying \c repeat_values is mandatory. + It disambiguates the possible overloading of constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) with \c T being a const char*. + - 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 + (img1,img2).display(); + \endcode + \image html ref_constructor2.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values):_is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(values,repeat_values); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified \c t* memory buffer. + \param values Pointer to the input memory buffer. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param is_shared Tells if input memory buffer must be shared by the current instance. + \note + - If \c is_shared is \c false, the image instance allocates its own pixel buffer, + and values from the specified input buffer are copied to the instance buffer. + If buffer types \c T and \c t are different, a regular static cast is performed during buffer copy. + - Otherwise, the image instance does \e not allocate a new buffer, and uses the input memory buffer as its + own pixel buffer. This case requires that types \c T and \c t are the same. Later, destroying such a shared + image will not deallocate the pixel buffer, this task being obviously charged to the initial buffer allocator. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - You must take care when operating on a shared image, since it may have an invalid pixel buffer pointer data() + (e.g. already deallocated). + \par Example + \code + unsigned char tab[256*256] = { 0 }; + CImg 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 + CImg(const t *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a (%u,%u,%u,%u) shared instance " + "from a (%s*) buffer (pixel types are different).", + cimg_instance, + size_x,size_y,size_z,size_c,CImg::pixel_type()); + } + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + + } + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = is_shared; + if (_is_shared) _data = const_cast(values); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy(_data,values,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image from reading an image file. + /** + Construct a new image instance with pixels of type \c T, and initialize pixel values with the data read from + an image file. + \param filename Filename, as a C-string. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it reads the image + dimensions and pixel values from the specified image file. + - The recognition of the image file format by %CImg higly depends on the tools installed on your system + and on the external libraries you used to link your code against. + - Considered pixel type \c T should better fit the file format specification, or data loss may occur during + file load (e.g. constructing a \c CImg from a float-valued image file). + - A \c CImgIOException is thrown when the specified \c filename cannot be read, or if the file format is not + recognized. + \par Example + \code + const CImg img("reference.jpg"); + img.display(); + \endcode + \image html ref_image.jpg + **/ + explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(filename); + } + + //! Construct image copy. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance. + \param img Input image to copy. + \note + - Constructed copy has the same size width() x height() x depth() x spectrum() and pixel values as the + input image \c img. + - If input image \c img is \e shared and if types \c T and \c t are the same, the constructed copy is also + \e shared, and shares its pixel buffer with \c img. + Modifying a pixel value in the constructed copy will thus also modifies it in the input image \c img. + This behavior is needful to allow functions to return shared images. + - Otherwise, the constructed copy allocates its own pixel buffer, and copies pixel values from the input + image \c img into its buffer. The copied pixel values may be eventually statically casted if types \c T and + \c t are different. + - Constructing a copy from an image \c img when types \c t and \c T are the same is significantly faster than + with different types. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. not enough available memory). + **/ + template + CImg(const CImg& img):_is_shared(false) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image copy \specialization. + CImg(const CImg& img) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = img._is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Advanced copy constructor. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance, + while forcing the shared state of the constructed copy. + \param img Input image to copy. + \param is_shared Tells about the shared state of the constructed copy. + \note + - Similar to CImg(const CImg&), except that it allows to decide the shared state of + the constructed image, which does not depend anymore on the shared state of the input image \c img: + - If \c is_shared is \c true, the constructed copy will share its pixel buffer with the input image \c img. + For that case, the pixel types \c T and \c t \e must be the same. + - If \c is_shared is \c false, the constructed copy will allocate its own pixel buffer, whether the input + image \c img is shared or not. + - A \c CImgArgumentException is thrown when a shared copy is requested with different pixel types \c T and \c t. + **/ + template + CImg(const CImg& img, const bool is_shared):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a shared instance from a " + "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).", + cimg_instance, + CImg::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data); + } + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Advanced copy constructor \specialization. + CImg(const CImg& img, const bool is_shared) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image with dimensions borrowed from another image. + /** + Construct a new image instance with pixels of type \c T, and size get from some dimensions of an existing + \c CImg instance. + \param img Input image from which dimensions are borrowed. + \param dimensions C-string describing the image size along the X,Y,Z and C-dimensions. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it takes the image dimensions + (\e not its pixel values) from an existing \c CImg instance. + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values (e.g. with \c 0), use constructor CImg(const CImg&,const char*,T) + 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') + \endcode + **/ + template + CImg(const CImg& img, const char *const dimensions): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values. + /** + Construct a new image instance with pixels of type \c T, and size get from the dimensions of an existing + \c CImg instance, and set all pixel values to specified \c value. + \param img Input image from which dimensions are borrowed. + \param dimensions String describing the image size along the X,Y,Z and V-dimensions. + \param value Value used for initialization. + \note + - Similar to CImg(const CImg&,const char*), but it also fills the pixel buffer with the specified \c value. + **/ + template + CImg(const CImg& img, const char *const dimensions, const T& value): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions).fill(value); + } + + //! Construct image from a display window. + /** + Construct a new image instance with pixels of type \c T, as a snapshot of an existing \c CImgDisplay instance. + \param disp Input display window. + \note + - The width() and height() of the constructed image instance are the same as the specified \c CImgDisplay. + - The depth() and spectrum() of the constructed image instance are respectively set to \c 1 and \c 3 + (i.e. a 2D color image). + - The image pixels are read as 8-bits RGB values. + **/ + explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + disp.snapshot(*this); + } + + // Constructor and assignment operator for rvalue references (c++11). + // This avoids an additional image copy for methods returning new images. Can save RAM for big images ! +#if cimg_use_cpp11==1 + CImg(CImg&& 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); + } +#endif + + //! Construct empty image \inplace. + /** + In-place version of the default constructor CImg(). It simply resets the instance to an empty image. + **/ + CImg& assign() { + if (!_is_shared) delete[] _data; + _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; + return *this; + } + + //! Construct image with specified size \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (siz!=curr_siz) { + if (_is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignement request of shared instance from specified " + "image (%u,%u,%u,%u).", + cimg_instance, + size_x,size_y,size_z,size_c); + else { + delete[] _data; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + return *this; + } + + //! Construct image with specified size and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,T). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value) { + return assign(size_x,size_y,size_z,size_c).fill(value); + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a sequence of doubles \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a value string \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values) { + return assign(size_x,size_y,size_z,size_c).fill(values,repeat_values); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \inplace. + /** + In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int). + **/ + template + 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; + if (!values || !siz) return assign(); + assign(size_x,size_y,size_z,size_c); + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c); + if (_is_shared || values + siz<_data || values>=_data + size()) { + assign(size_x,size_y,size_z,size_c); + if (_is_shared) std::memmove((void*)_data,(void*)values,siz*sizeof(T)); + else std::memcpy((void*)_data,(void*)values,siz*sizeof(T)); + } else { + T *new_data = 0; + try { new_data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy((void*)new_data,(void*)values,siz*sizeof(T)); + delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + } + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + template + 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) { + if (is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignment request of shared instance from (%s*) buffer" + "(pixel types are different).", + cimg_instance, + CImg::pixel_type()); + return assign(values,size_x,size_y,size_z,size_c); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const bool is_shared) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + if (!is_shared) { if (_is_shared) assign(); assign(values,size_x,size_y,size_z,size_c); } + else { + if (!_is_shared) { + if (values + siz<_data || values>=_data + size()) assign(); + else cimg::warn(_cimg_instance + "assign(): Shared image instance has overlapping memory.", + cimg_instance); + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true; + _data = const_cast(values); + } + return *this; + } + + //! Construct image from reading an image file \inplace. + /** + In-place version of the constructor CImg(const char*). + **/ + CImg& assign(const char *const filename) { + return load(filename); + } + + //! Construct image copy \inplace. + /** + In-place version of the constructor CImg(const CImg&). + **/ + template + CImg& assign(const CImg& img) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum); + } + + //! In-place version of the advanced copy constructor. + /** + In-place version of the constructor CImg(const CImg&,bool). + **/ + template + CImg& assign(const CImg& img, const bool is_shared) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum,is_shared); + } + + //! Construct image with dimensions borrowed from another image \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions) { + if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum); + unsigned int siz[4] = { 0,1,1,1 }, k = 0; + CImg item(256); + for (const char *s = dimensions; *s && k<4; ++k) { + if (cimg_sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item._data)>0) s+=std::strlen(item); + if (*s) { + unsigned int val = 0; char sep = 0; + if (cimg_sscanf(s,"%u%c",&val,&sep)>0) { + if (sep=='%') siz[k] = val*(k==0?_width:k==1?_height:k==2?_depth:_spectrum)/100; + else siz[k] = val; + while (*s>='0' && *s<='9') ++s; + if (sep=='%') ++s; + } else switch (cimg::lowercase(*s)) { + case 'x' : case 'w' : siz[k] = img._width; ++s; break; + case 'y' : case 'h' : siz[k] = img._height; ++s; break; + case 'z' : case 'd' : siz[k] = img._depth; ++s; break; + case 'c' : case 's' : siz[k] = img._spectrum; ++s; break; + default : + throw CImgArgumentException(_cimg_instance + "assign(): Invalid character '%c' detected in specified dimension string '%s'.", + cimg_instance, + *s,dimensions); + } + } + } + return assign(siz[0],siz[1],siz[2],siz[3]); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*,T). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions, const T& value) { + return assign(img,dimensions).fill(value); + } + + //! Construct image from a display window \inplace. + /** + In-place version of the constructor CImg(const CImgDisplay&). + **/ + CImg& assign(const CImgDisplay &disp) { + disp.snapshot(*this); + return *this; + } + + //! Construct empty image \inplace. + /** + Equivalent to assign(). + \note + - It has been defined for compatibility with STL naming conventions. + **/ + CImg& clear() { + return assign(); + } + + //! Transfer content of an image instance into another one. + /** + Transfer the dimensions and the pixel buffer content of an image instance into another one, + and replace instance by an empty image. It avoids the copy of the pixel buffer + when possible. + \param img Destination image. + \note + - Pixel types \c T and \c t of source and destination images can be different, though the process is + designed to be instantaneous when \c T and \c t are the same. + \par Example + \code + CImg 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 + CImg& move_to(CImg& img) { + img.assign(*this); + assign(); + return img; + } + + //! Transfer content of an image instance into another one \specialization. + CImg& move_to(CImg& img) { + if (_is_shared || img._is_shared) img.assign(*this); + else swap(img); + assign(); + return img; + } + + //! Transfer content of an image instance into a new image in an image list. + /** + Transfer the dimensions and the pixel buffer content of an image instance + into a newly inserted image at position \c pos in specified \c CImgList instance. + \param list Destination list. + \param pos Position of the newly inserted image in the list. + \note + - When optional parameter \c pos is ommited, the image instance is transfered as a new + image at the end of the specified \c list. + - It is convenient to sequentially insert new images into image lists, with no + additional copies of memory buffer. + \par Example + \code + CImgList 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 + CImgList& move_to(CImgList& list, const unsigned int pos=~0U) { + const unsigned int npos = pos>list._width?list._width:pos; + move_to(list.insert(1,npos)[npos]); + return list; + } + + //! Swap fields of two image instances. + /** + \param img Image to swap fields with. + \note + - It can be used to interchange the content of two images in a very fast way. Can be convenient when dealing + with algorithms requiring two swapping buffers. + \par Example + \code + CImg img1("lena.jpg"), + img2("milla.jpg"); + img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena' + \endcode + **/ + CImg& swap(CImg& img) { + cimg::swap(_width,img._width,_height,img._height,_depth,img._depth,_spectrum,img._spectrum); + cimg::swap(_data,img._data); + cimg::swap(_is_shared,img._is_shared); + return img; + } + + //! Return a reference to an empty image. + /** + \note + This function is useful mainly to declare optional parameters having type \c CImg in functions prototypes, + e.g. + \code + void f(const int x=0, const int y=0, const CImg& img=CImg::empty()); + \endcode + **/ + static CImg& empty() { + static CImg _empty; + return _empty.assign(); + } + + //! Return a reference to an empty image \const. + static const CImg& const_empty() { + static const CImg _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Access to a pixel value. + /** + Return a reference to a located pixel value of the image instance, + being possibly \e const, whether the image instance is \e const or not. + This is the standard method to get/set pixel values in \c CImg images. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Range of pixel coordinates start from (0,0,0,0) to + (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 + 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 + 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' + const float + valR = img(10,10,0,0), // Read red value at coordinates (10,10) + valG = img(10,10,0,1), // Read green value at coordinates (10,10) + valB = img(10,10,2), // Read blue value at coordinates (10,10) (Z-coordinate can be omitted) + avg = (valR + valG + valB)/3; // Compute average pixel value + img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the color pixel (10,10) by the average grey value + \endcode + **/ +#if cimg_verbosity>=3 + T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (!_data || off>=size()) { + cimg::warn(_cimg_instance + "operator(): Invalid pixel request, at coordinates (%d,%d,%d,%d) [offset=%u].", + cimg_instance, + (int)x,(int)y,(int)z,(int)c,off); + return *_data; + } + else return _data[off]; + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->operator()(x,y,z,c); + } + + //! Access to a pixel value. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param wh Precomputed offset, must be equal to width()*\ref height(). + \param whd Precomputed offset, must be equal to width()*\ref height()*\ref depth(). + \note + - Similar to (but faster than) operator()(). + It uses precomputed offsets to optimize memory access. You may use it to optimize + the reading/writing of several pixel values in the same image (e.g. in a loop). + **/ + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) const { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } +#else + T& operator()(const unsigned int x) { + return _data[x]; + } + + const T& operator()(const unsigned int x) const { + return _data[x]; + } + + T& operator()(const unsigned int x, const unsigned int y) { + return _data[x + y*_width]; + } + + const T& operator()(const unsigned int x, const unsigned int y) const { + return _data[x + y*_width]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) { + return _data[x + y*_width + z*wh]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) const { + return _data[x + y*_width + z*wh]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) { + return _data[x + y*_width + z*wh + c*whd]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) const { + return _data[x + y*_width + z*wh + c*whd]; + } +#endif + + //! Implicitely cast an image into a \c T*. + /** + Implicitely cast a \c CImg 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 + std::printf("'img1' is not empty, 'img2' is empty."); + } + } + \endcode + - It also allows to use brackets to access pixel values, without need for a \c CImg::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) + \endcode + **/ + operator T*() { + return _data; + } + + //! Implicitely cast an image into a \c T* \const. + operator const T*() const { + return _data; + } + + //! Assign a value to all image pixels. + /** + Assign specified \c value to each pixel value of the image instance. + \param value Value that will be assigned to image pixels. + \note + - The image size is never modified. + - The \c value may be casted to pixel type \c T if necessary. + \par Example + \code + CImg 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) { + return fill(value); + } + + //! Assign pixels values from a specified expression. + /** + Initialize all pixel values from the specified string \c expression. + \param expression Value string describing the way pixel values are set. + \note + - String parameter \c expression may describe different things: + - If \c expression is a list of values (as in \c "1,2,3,8,3,2"), or a formula (as in \c "(x*y)%255"), + the pixel values are set from specified \c expression and the image size is not modified. + - If \c expression is a filename (as in \c "reference.jpg"), the corresponding image file is loaded and + replace the image instance. The image size is modified if necessary. + \par Example + \code + CImg img1(100,100), img2(img1), img3(img1); // Declare 3 scalar images 100x100 with unitialized values + img1 = "0,50,100,150,200,250,200,150,100,50"; // Set pixel values of 'img1' from a value sequence + img2 = "10*((x*y)%25)"; // Set pixel values of 'img2' from a formula + img3 = "reference.jpg"; // Set pixel values of 'img3' from a file (image size is modified) + (img1,img2,img3).display(); + \endcode + \image html ref_operator_eq.jpg + **/ + CImg& operator=(const char *const expression) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { + _fill(expression,true,1,0,0,"operator=",0); + } catch (CImgException&) { + cimg::exception_mode(omode); + load(expression); + } + cimg::exception_mode(omode); + return *this; + } + + //! Copy an image into the current image instance. + /** + Similar to the in-place copy constructor assign(const CImg&). + **/ + template + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy an image into the current image instance \specialization. + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy the content of a display window to the current image instance. + /** + Similar to assign(const CImgDisplay&). + **/ + CImg& operator=(const CImgDisplay& disp) { + disp.snapshot(*this); + return *this; + } + + //! In-place addition operator. + /** + Add specified \c value to all pixels of an image instance. + \param value Value to add. + \note + - Resulting pixel values are casted to fit the pixel type \c T. + For instance, adding \c 0.2 to a \c CImg 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 + // 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 + (img1,img2,img3).display(); + \endcode + \image html ref_operator_plus.jpg + **/ + template + CImg& operator+=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + value,524288); + return *this; + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the specified string \c expression. + \param expression Value string describing the way pixel values are added. + \note + - Similar to operator=(const char*), except that it adds values to the pixels of the current image instance, + instead of assigning them. + **/ + CImg& operator+=(const char *const expression) { + return *this+=(+*this)._fill(expression,true,1,0,0,"operator+=",this); + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the values of the input image \c img. + \param img Input image to add. + \note + - The size of the image instance is never modified. + - It is not mandatory that input image \c img has the same size as the image instance. + If less values are available in \c img, then the values are added periodically. For instance, adding one + WxH scalar image (spectrum() equal to \c 1) to one WxH color image (spectrum() equal to \c 3) + means each color channel will be incremented with the same values at the same locations. + \par Example + \code + CImg 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 + (img2,img1).display(); + \endcode + \image html ref_operator_plus1.jpg + **/ + template + CImg& operator+=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this+=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator++() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + 1,524288); + return *this; + } + + //! In-place increment operator (postfix). + /** + Add \c 1 to all image pixels, and return a new copy of the initial (pre-incremented) image instance. + \note + - Use the prefixed version operator++() if you don't need a copy of the initial + (pre-incremented) image instance, since a useless image copy may be expensive in terms of memory usage. + **/ + CImg operator++(int) { + const CImg copy(*this,false); + ++*this; + return copy; + } + + //! Return a non-shared copy of the image instance. + /** + \note + - Use this operator to ensure you get a non-shared copy of an image instance with same pixel type \c T. + Indeed, the usual copy constructor CImg(const CImg&) returns a shared copy of a shared input image, + and it may be not desirable to work on a regular copy (e.g. for a resize operation) if you have no + information about the shared state of the input image. + - Writing \c (+img) is equivalent to \c CImg(img,false). + **/ + CImg operator+() const { + return CImg(*this,false); + } + + //! Addition operator. + /** + Similar to operator+=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator+(const t value) const { + return CImg<_cimg_Tt>(*this,false)+=value; + } + + //! Addition operator. + /** + Similar to operator+=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator+(const char *const expression) const { + return CImg(*this,false)+=expression; + } + + //! Addition 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. + **/ + template + CImg<_cimg_Tt> operator+(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)+=img; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const t), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - value,524288); + return *this; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const char*), except that it performs a substraction instead of an addition. + **/ + CImg& operator-=(const char *const expression) { + return *this-=(+*this)._fill(expression,true,1,0,0,"operator-=",this); + } + + //! In-place substraction operator. + /** + Similar to operator+=(const CImg&), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this-=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator--() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - 1,524288); + return *this; + } + + //! In-place decrement operator (postfix). + /** + Similar to operator++(int), except that it performs a decrement instead of an increment. + **/ + CImg operator--(int) { + const CImg copy(*this,false); + --*this; + return copy; + } + + //! Replace each pixel by its opposite value. + /** + \note + - If the computed opposite values are out-of-range, they are treated as with standard C++ numeric types. + For instance, the \c unsigned \c char opposite of \c 1 is \c 255. + \par Example + \code + const CImg + img1("reference.jpg"), // Load a RGB color image + img2 = -img1; // Compute its opposite (in 'unsigned char') + (img1,img2).display(); + \endcode + \image html ref_operator_minus.jpg + **/ + CImg operator-() const { + return CImg(_width,_height,_depth,_spectrum,(T)0)-=*this; + } + + //! Substraction operator. + /** + Similar to operator-=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator-(const t value) const { + return CImg<_cimg_Tt>(*this,false)-=value; + } + + //! Substraction operator. + /** + Similar to operator-=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator-(const char *const expression) const { + return CImg(*this,false)-=expression; + } + + //! Substraction 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. + **/ + template + CImg<_cimg_Tt> operator-(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)-=img; + } + + //! In-place multiplication operator. + /** + Similar to operator+=(const t), except that it performs a multiplication instead of an addition. + **/ + template + CImg& operator*=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr * value,262144); + return *this; + } + + //! In-place multiplication operator. + /** + 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,1,0,0,"operator*=",this)); + } + + //! In-place multiplication operator. + /** + Replace the image instance by the matrix multiplication between the image instance and the specified matrix + \c img. + \param img Second operand of the matrix multiplication. + \note + - It does \e not compute a pointwise multiplication between two images. For this purpose, use + mul(const CImg&) instead. + - 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' + // 'A' is now a 1x2 vector whose values are [5;11]. + \endcode + **/ + template + CImg& operator*=(const CImg& img) { + return ((*this)*img).move_to(*this); + } + + //! Multiplication operator. + /** + Similar to operator*=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator*(const t value) const { + return CImg<_cimg_Tt>(*this,false)*=value; + } + + //! Multiplication operator. + /** + Similar to operator*=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator*(const char *const expression) const { + return CImg(*this,false)*=expression; + } + + //! Multiplication 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. + **/ + 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)", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + 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_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 *ps0 = img.data(), *ps1 = img.data(0,1); + Tt *pd0 = res.data(), *pd1 = res.data(0,1); + const Ttdouble + a0 = (Ttdouble)_data[0], a1 = (Ttdouble)_data[1], + a2 = (Ttdouble)_data[2], a3 = (Ttdouble)_data[3]; + cimg_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++); + *(pd0++) = (Tt)(a0*x + a1*y); + *(pd1++) = (Tt)(a2*x + a3*y); + } + return res; + } + case 3 : { // 3x3_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *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_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z); + *(pd1++) = (Tt)(a3*x + a4*y + a5*z); + *(pd2++) = (Tt)(a6*x + a7*y + a8*z); + } + return res; + } + case 4 : { // 4x4_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2), *ps3 = img.data(0,3); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *pd2 = res.data(0,2), *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_forX(img,col) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++), + c = (Ttdouble)*(ps3++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z + a3*c); + *(pd1++) = (Tt)(a4*x + a5*y + a6*z + a7*c); + *(pd2++) = (Tt)(a8*x + a9*y + a10*z + a11*c); + *(pd3++) = (Tt)(a12*x + a13*y + a14*z + a15*c); + } + return res; + } + } + } + + // Fallback to generic version. +#ifdef cimg_use_openmp + 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 + Tt *ptrd = res._data; + cimg_forXY(res,i,j) { + Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); *(ptrd++) = (Tt)value; + } +#endif + return res; + } + + //! In-place division operator. + /** + Similar to operator+=(const t), except that it performs a division instead of an addition. + **/ + template + CImg& operator/=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr / value,32768); + return *this; + } + + //! In-place division operator. + /** + 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,1,0,0,"operator/=",this)); + } + + //! In-place division operator. + /** + Replace the image instance by the (right) matrix division between the image instance and the specified + matrix \c img. + \param img Second operand of the matrix division. + \note + - It does \e not compute a pointwise division between two images. For this purpose, use + div(const CImg&) instead. + - It returns the matrix operation \c A*inverse(img). + - The size of the image instance can be modified by this operator. + **/ + template + CImg& operator/=(const CImg& img) { + return (*this*img.get_invert()).move_to(*this); + } + + //! Division operator. + /** + Similar to operator/=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator/(const t value) const { + return CImg<_cimg_Tt>(*this,false)/=value; + } + + //! Division operator. + /** + Similar to operator/=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator/(const char *const expression) const { + return CImg(*this,false)/=expression; + } + + //! Division 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. + **/ + template + CImg<_cimg_Tt> operator/(const CImg& img) const { + return (*this)*img.get_invert(); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const t), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::mod(*ptr,(T)value),16384); + return *this; + } + + //! In-place modulo operator. + /** + 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,1,0,0,"operator%=",this); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const CImg&), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this%=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> operator%(const t value) const { + return CImg<_cimg_Tt>(*this,false)%=value; + } + + //! Modulo operator. + /** + Similar to operator%=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator%(const char *const expression) const { + return CImg(*this,false)%=expression; + } + + //! Modulo 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. + **/ + template + CImg<_cimg_Tt> operator%(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)%=img; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const t), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr & (ulongT)value,32768); + return *this; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition. + **/ + CImg& operator&=(const char *const expression) { + return *this&=(+*this)._fill(expression,true,1,0,0,"operator&=",this); + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this&=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator&(const t value) const { + return (+*this)&=value; + } + + //! Bitwise AND operator. + /** + Similar to operator&=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator&(const char *const expression) const { + return (+*this)&=expression; + } + + //! Bitwise AND 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 is \c T. + **/ + template + CImg operator&(const CImg& img) const { + return (+*this)&=img; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr | (ulongT)value,32768); + return *this; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition. + **/ + CImg& operator|=(const char *const expression) { + return *this|=(+*this)._fill(expression,true,1,0,0,"operator|=",this); + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this|=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator|(const t value) const { + return (+*this)|=value; + } + + //! Bitwise OR operator. + /** + Similar to operator|=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator|(const char *const expression) const { + return (+*this)|=expression; + } + + //! Bitwise OR 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 is \c T. + **/ + template + CImg operator|(const CImg& img) const { + return (+*this)|=img; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const t) instead. + **/ + template + CImg& operator^=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr ^ (ulongT)value,32768); + return *this; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead. + **/ + CImg& operator^=(const char *const expression) { + return *this^=(+*this)._fill(expression,true,1,0,0,"operator^=",this); + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const CImg&) instead. + **/ + template + CImg& operator^=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator^(const t value) const { + return (+*this)^=value; + } + + //! Bitwise XOR operator. + /** + Similar to operator^=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator^(const char *const expression) const { + return (+*this)^=expression; + } + + //! Bitwise XOR 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 is \c T. + **/ + template + CImg operator^(const CImg& img) const { + return (+*this)^=img; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) << (int)value,65536); + return *this; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition. + **/ + CImg& operator<<=(const char *const expression) { + return *this<<=(+*this)._fill(expression,true,1,0,0,"operator<<=",this); + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator<<(const t value) const { + return (+*this)<<=value; + } + + //! Bitwise left shift operator. + /** + Similar to operator<<=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator<<(const char *const expression) const { + return (+*this)<<=expression; + } + + //! Bitwise left shift 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 is \c T. + **/ + template + CImg operator<<(const CImg& img) const { + return (+*this)<<=img; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) >> (int)value,65536); + return *this; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition. + **/ + CImg& operator>>=(const char *const expression) { + return *this>>=(+*this)._fill(expression,true,1,0,0,"operator>>=",this); + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs> (int)*(ptrs++)); + for (const t *ptrs = img._data; ptrd> (int)*(ptrs++)); + } + return *this; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + template + CImg operator>>(const t value) const { + return (+*this)>>=value; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator>>(const char *const expression) const { + return (+*this)>>=expression; + } + + //! Bitwise right shift 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 is \c T. + **/ + template + CImg operator>>(const CImg& img) const { + return (+*this)>>=img; + } + + //! Bitwise inversion operator. + /** + Similar to operator-(), except that it compute the bitwise inverse instead of the opposite value. + **/ + CImg operator~() const { + CImg res(_width,_height,_depth,_spectrum); + const T *ptrs = _data; + cimg_for(res,ptrd,T) { const ulongT value = (ulongT)*(ptrs++); *ptrd = (T)~value; } + return res; + } + + //! Test if all pixels of an image have the same value. + /** + Return \c true is all pixels of the image instance are equal to the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator==(const t value) const { + if (is_empty()) return false; + typedef _cimg_Tt Tt; + bool is_equal = true; + for (T *ptrd = _data + size(); is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)value)) {} + return is_equal; + } + + //! Test if all pixel values of an image follow a specified expression. + /** + Return \c true is all pixels of the image instance are equal to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator==(const char *const expression) const { + return *this==(+*this)._fill(expression,true,1,0,0,"operator==",this); + } + + //! Test if two images have the same size and values. + /** + Return \c true if the image instance and the input image \c img have the same dimensions and pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - The pixel buffer pointers data() of the two compared images do not have to be the same for operator==() + to return \c true. + Only the dimensions and the pixel values matter. Thus, the comparison can be \c true even for different + pixel types \c T and \c t. + \par Example + \code + const CImg 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 + **/ + template + bool operator==(const CImg& img) const { + typedef _cimg_Tt Tt; + const ulongT siz = size(); + bool is_equal = true; + if (siz!=img.size()) return false; + t *ptrs = img._data + siz; + for (T *ptrd = _data + siz; is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)*(--ptrs))) {} + return is_equal; + } + + //! Test if pixels of an image are all different from a value. + /** + Return \c true is all pixels of the image instance are different than the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator!=(const t value) const { + return !((*this)==value); + } + + //! Test if all pixel values of an image are different from a specified expression. + /** + Return \c true is all pixels of the image instance are different to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator!=(const char *const expression) const { + return !((*this)==expression); + } + + //! Test if two images have different sizes or values. + /** + Return \c true if the image instance and the input image \c img have different dimensions or pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - Writing \c img1!=img2 is equivalent to \c !(img1==img2). + **/ + template + bool operator!=(const CImg& img) const { + return !((*this)==img); + } + + //! Construct an image list from two images. + /** + Return a new list of image (\c CImgList instance) containing exactly two elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image \c img, at position [\c 1]. + + \param img Input image that will be the second image of the resulting list. + \note + - The family of operator,() is convenient to easily create list of images, but it is also \e quite \e slow + in practice (see warning below). + - Constructed lists contain no shared images. If image instance or input image \c img are shared, they are + inserted as new non-shared copies in the resulting list. + - The pixel type of the returned list may be a superset of the initial pixel type \c T, if necessary. + \warning + - Pipelining operator,() \c N times will perform \c N copies of the entire content of a (growing) image list. + This may become very expensive in terms of speed and used memory. You should avoid using this technique to + build a new CImgList instance from several images, if you are seeking for performance. + Fast insertions of images in an image list are possible with + CImgList::insert(const CImg&,unsigned int,bool) or move_to(CImgList&,unsigned int). + \par Example + \code + const CImg + 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' + \endcode + \image html ref_operator_comma.jpg + **/ + template + CImgList<_cimg_Tt> operator,(const CImg& img) const { + return CImgList<_cimg_Tt>(*this,img); + } + + //! Construct an image list from image instance and an input image list. + /** + Return a new list of images (\c CImgList instance) containing exactly \c list.size() \c + \c 1 elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image list \c list, from positions [\c 1] to [\c list.size()]. + + \param list Input image list that will be appended to the image instance. + \note + - Similar to operator,(const CImg&) const, except that it takes an image list as an argument. + **/ + template + CImgList<_cimg_Tt> operator,(const CImgList& list) const { + return CImgList<_cimg_Tt>(list,false).insert(*this,0); + } + + //! Split image along specified axis. + /** + Return a new list of images (\c CImgList instance) containing the splitted components + of the instance image along the specified axis. + \param axis Splitting axis (can be '\c x','\c y','\c z' or '\c c') + \note + - Similar to get_split(char,int) const, with default second argument. + \par Example + \code + const CImg 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 + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the number of image columns. + /** + Return the image width, i.e. the image dimension along the X-axis. + \note + - The width() of an empty image is equal to \c 0. + - width() is typically equal to \c 1 when considering images as \e vectors for matrix calculations. + - width() returns an \c int, although the image width is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._width. + **/ + int width() const { + return (int)_width; + } + + //! Return the number of image rows. + /** + Return the image height, i.e. the image dimension along the Y-axis. + \note + - The height() of an empty image is equal to \c 0. + - height() returns an \c int, although the image height is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._height. + **/ + int height() const { + return (int)_height; + } + + //! Return the number of image slices. + /** + Return the image depth, i.e. the image dimension along the Z-axis. + \note + - The depth() of an empty image is equal to \c 0. + - depth() is typically equal to \c 1 when considering usual 2D images. When depth()\c > \c 1, the image + is said to be \e volumetric. + - depth() returns an \c int, although the image depth is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._depth. + **/ + int depth() const { + return (int)_depth; + } + + //! Return the number of image channels. + /** + Return the number of image channels, i.e. the image dimension along the C-axis. + \note + - The spectrum() of an empty image is equal to \c 0. + - spectrum() is typically equal to \c 1 when considering scalar-valued images, to \c 3 + for RGB-coded color images, and to \c 4 for RGBA-coded color images (with alpha-channel). + The number of channels of an image instance is not limited. The meaning of the pixel values is not linked + up to the number of channels (e.g. a 4-channel image may indifferently stands for a RGBA or CMYK color image). + - spectrum() returns an \c int, although the image spectrum is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._spectrum. + **/ + int spectrum() const { + return (int)_spectrum; + } + + //! Return the total number of pixel values. + /** + Return width()*\ref height()*\ref depth()*\ref spectrum(), + i.e. the total number of values of type \c T in the pixel buffer of the image instance. + \note + - The size() of an empty image is equal to \c 0. + - The allocated memory size for a pixel buffer of a non-shared \c CImg instance is equal to + size()*sizeof(T). + \par Example + \code + 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 + **/ + ulongT size() const { + return (ulongT)_width*_height*_depth*_spectrum; + } + + //! Return a pointer to the first pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the first value in the pixel buffer of the image instance, + whether the instance is \c const or not. + \note + - The data() of an empty image is equal to \c 0 (null pointer). + - The allocated pixel buffer for the image instance starts from \c data() + and goes to data()+\ref size() - 1 (included). + - To get the pointer to one particular location of the pixel buffer, use + data(unsigned int,unsigned int,unsigned int,unsigned int) instead. + **/ + T* data() { + return _data; + } + + //! Return a pointer to the first pixel value \const. + const T* data() const { + return _data; + } + + //! Return a pointer to a located pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the value located at (\c x,\c y,\c z,\c c) in the pixel buffer + of the image instance, + whether the instance is \c const or not. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)). Thus, this method has the same + properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int). + **/ +#if cimg_verbosity>=3 + T *data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (off>=size()) + cimg::warn(_cimg_instance + "data(): Invalid pointer request, at coordinates (%u,%u,%u,%u) [offset=%u].", + cimg_instance, + x,y,z,c,off); + return _data + off; + } + + //! Return a pointer to a located pixel value \const. + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->data(x,y,z,c); + } +#else + T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } + + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } +#endif + + //! Return the offset to a located pixel value, with respect to the beginning of the pixel buffer. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)) - img.data(). + 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 + \endcode + **/ + longT offset(const int x, const int y=0, const int z=0, const int c=0) const { + return x + (longT)y*_width + (longT)z*_width*_height + (longT)c*_width*_height*_depth; + } + + //! Return a CImg::iterator pointing to the first pixel value. + /** + \note + - Equivalent to data(). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + iterator begin() { + return _data; + } + + //! Return a CImg::iterator pointing to the first value of the pixel buffer \const. + const_iterator begin() const { + return _data; + } + + //! Return a CImg::iterator pointing next to the last pixel value. + /** + \note + - Writing \c img.end() is equivalent to img.data() + img.size(). + - It has been mainly defined for compatibility with STL naming conventions. + \warning + - The returned iterator actually points to a value located \e outside the acceptable bounds of the pixel buffer. + Trying to read or write the content of the returned iterator will probably result in a crash. + Use it mainly as a strict upper bound for a CImg::iterator. + \par Example + \code + 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::iterator pointing next to the last pixel value \const. + const_iterator end() const { + return _data + size(); + } + + //! Return a reference to the first pixel value. + /** + \note + - Writing \c img.front() is equivalent to img[0], or img(0,0,0,0). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& front() { + return *_data; + } + + //! Return a reference to the first pixel value \const. + const T& front() const { + return *_data; + } + + //! Return a reference to the last pixel value. + /** + \note + - Writing \c img.back() is equivalent to img[img.size() - 1], or + img(img.width() - 1,img.height() - 1,img.depth() - 1,img.spectrum() - 1). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& back() { + return *(_data + size() - 1); + } + + //! Return a reference to the last pixel value \const. + const T& back() const { + return *(_data + size() - 1); + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to a specified default value in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note + - Writing \c img.at(offset,out_value) is similar to img[offset], except that if \c offset + is outside bounds (e.g. \c offset<0 or \c offset>=img.size()), a reference to a value \c out_value + is safely returned instead. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + **/ + T& at(const int offset, const T& out_value) { + return (offset<0 || offset>=(int)size())?(cimg::temporary(out_value)=out_value):(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions \const. + T at(const int offset, const T& out_value) const { + return (offset<0 || offset>=(int)size())?out_value:(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to the nearest pixel location in the image instance in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \note + - Similar to at(int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified offset, i.e. + - If \c offset<0, then \c img[0] is returned. + - If \c offset>=img.size(), then \c img[img.size() - 1] is returned. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int). + **/ + T& at(const int offset) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + T& _at(const int offset) { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions \const. + const T& at(const int offset) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + const T& _at(const int offset) const { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to a specified default value in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c x,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to operator()(), except that an out-of-bounds access along the X-axis returns the specified value + \c out_value. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || x>=width())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate \const. + T atX(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || x>=width())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to at(int,int,int,int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified X-coordinate. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _at(int,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + T& _atX(const int x, const int y=0, const int z=0, const int c=0) { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate \const. + const T& atX(const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + const T& _atX(const int x, const int y=0, const int z=0, const int c=0) const { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X and Y-coordinates. + **/ + T& atXY(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y coordinates \const. + T atXY(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || x>=width() || y>=height())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXY(int,int,int,int). + **/ + T& atXY(const int x, const int y, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + T& _atXY(const int x, const int y, const int z=0, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates \const. + const T& atXY(const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + const T& _atXY(const int x, const int y, const int z=0, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on + X,Y and Z-coordinates. + **/ + T& atXYZ(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates \const. + T atXYZ(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZ(int,int,int,int). + **/ + T& atXYZ(const int x, const int y, const int z, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + T& _atXYZ(const int x, const int y, const int z, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates \const. + const T& atXYZ(const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + const T& _atXYZ(const int x, const int y, const int z, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed on all + X,Y,Z and C-coordinates. + **/ + T& atXYZC(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions \const. + T atXYZC(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_value: + (*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed on all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZC(int,int,int,int). + **/ + T& atXYZC(const int x, const int y, const int z, const int c) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + T& _atXYZC(const int x, const int y, const int z, const int c) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Access to a pixel value, using Neumann boundary conditions \const. + const T& atXYZC(const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + const T& _atXYZC(const int x, const int y, const int z, const int c) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to atX(int,int,int,int,const T), except that the returned pixel value is approximated by + a linear interpolation along the X-axis, if corresponding coordinates are not integers. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + const Tfloat + Ic = (Tfloat)atX(x,y,z,c,out_value), In = (Tfloat)atXY(nx,y,z,c,out_value); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access along + the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that an out-of-bounds access returns + the value of the nearest pixel in the image instance, regarding the specified X-coordinate. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atX(): Empty instance.", + cimg_instance); + + return _linear_atX(fx,y,z,c); + } + + Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1); + const unsigned int + x = (unsigned int)nfx; + const float + dx = nfx - x; + const unsigned int + nx = dx>0?x + 1:x; + const Tfloat + Ic = (Tfloat)(*this)(x,y,z,c), In = (Tfloat)(*this)(nx,y,z,c); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X and Y-coordinates. + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + const Tfloat + Icc = (Tfloat)atXY(x,y,z,c,out_value), Inc = (Tfloat)atXY(nx,y,z,c,out_value), + Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXY(float,float,int,int). + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXY(): Empty instance.", + cimg_instance); + + return _linear_atXY(fx,fy,z,c); + } + + Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy; + const float + dx = nfx - x, + dy = nfy - y; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y; + const Tfloat + Icc = (Tfloat)(*this)(x,y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X,Y and Z-coordinates. + **/ + Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + const Tfloat + Iccc = (Tfloat)atXYZ(x,y,z,c,out_value), Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), + Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), + Iccn = (Tfloat)atXYZ(x,y,nz,c,out_value), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), + Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZ(float,float,float,int). + **/ + Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZ(): Empty instance.", + cimg_instance); + + return _linear_atXYZ(fx,fy,fz,c); + } + + Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z; + const Tfloat + Iccc = (Tfloat)(*this)(x,y,z,c), Incc = (Tfloat)(*this)(nx,y,z,c), + Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c), + Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c), + Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z,C-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved for all X,Y,Z and C-coordinates. + **/ + Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1, + c = (int)fc - (fc>=0?0:1), nc = c + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z, + dc = fc - c; + const Tfloat + Icccc = (Tfloat)atXYZC(x,y,z,c,out_value), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_value), + Icncc = (Tfloat)atXYZC(x,ny,z,c,out_value), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_value), + Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_value), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_value), + Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_value), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_value), + Icccn = (Tfloat)atXYZC(x,y,z,nc,out_value), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_value), + Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_value), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_value), + Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_value), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_value), + Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_value), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_value); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn -Icccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for all X,Y,Z and C-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved for all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZC(float,float,float,float). + **/ + Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZC(): Empty instance.", + cimg_instance); + + return _linear_atXYZC(fx,fy,fz,fc); + } + + Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1), + nfc = cimg::cut(fc,0,spectrum() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz, + c = (unsigned int)nfc; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z, + dc = nfc - c; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z, + nc = dc>0?c + 1:c; + const Tfloat + Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c), + Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c), + Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c), + Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c), + Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc), + Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc), + Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc), + Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn - Icccc); + } + + //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + The cubic interpolation uses Hermite splines. + \param fx d X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a \e cubic interpolation along the X-axis. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2; + const float + dx = fx - x; + const Tfloat + Ip = (Tfloat)atX(px,y,z,c,out_value), Ic = (Tfloat)atX(x,y,z,c,out_value), + In = (Tfloat)atX(nx,y,z,c,out_value), Ia = (Tfloat)atX(ax,y,z,c,out_value); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Similar to cubic_atX(float,int,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atX(fx,y,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access + along the X-axis. The cubic interpolation uses Hermite splines. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to cubic_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a cubic interpolation along the X-axis. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atX(): Empty instance.", + cimg_instance); + return _cubic_atX(fx,y,z,c); + } + + Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::type::is_nan(fx)?0:cimg::cut(fx,0,width() - 1); + const int + x = (int)nfx; + const float + dx = nfx - x; + const int + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2; + const Tfloat + Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c), + In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atX(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 + are achieved both for X and Y-coordinates. + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2; + const float dx = fx - x, dy = fy - y; + const Tfloat + Ipp = (Tfloat)atXY(px,py,z,c,out_value), Icp = (Tfloat)atXY(x,py,z,c,out_value), + Inp = (Tfloat)atXY(nx,py,z,c,out_value), Iap = (Tfloat)atXY(ax,py,z,c,out_value), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)atXY(px,y,z,c,out_value), Icc = (Tfloat)atXY(x, y,z,c,out_value), + Inc = (Tfloat)atXY(nx,y,z,c,out_value), Iac = (Tfloat)atXY(ax,y,z,c,out_value), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)atXY(px,ny,z,c,out_value), Icn = (Tfloat)atXY(x,ny,z,c,out_value), + Inn = (Tfloat)atXY(nx,ny,z,c,out_value), Ian = (Tfloat)atXY(ax,ny,z,c,out_value), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)atXY(px,ay,z,c,out_value), Ica = (Tfloat)atXY(x,ay,z,c,out_value), + Ina = (Tfloat)atXY(nx,ay,z,c,out_value), Iaa = (Tfloat)atXY(ax,ay,z,c,out_value), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXY(fx,fy,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved for both X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXY(float,float,int,int). + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXY(): Empty instance.", + cimg_instance); + return _cubic_atXY(fx,fy,z,c); + } + + Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::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: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), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)(*this)(px,y,z,c), Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Iac = (Tfloat)(*this)(ax,y,z,c), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c), + Ian = (Tfloat)(*this)(ax,ny,z,c), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c), + Iaa = (Tfloat)(*this)(ax,ay,z,c), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int) const, except that 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 { + 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 { + return cimg::type::cut(_cubic_atXY(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 + are achieved both for X,Y and Z-coordinates. + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2, + z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2; + const float dx = fx - x, dy = fy - y, dz = fz - z; + const Tfloat + Ippp = (Tfloat)atXYZ(px,py,pz,c,out_value), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_value), + Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_value), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_value), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_value), Iccp = (Tfloat)atXYZ(x, y,pz,c,out_value), + Incp = (Tfloat)atXYZ(nx,y,pz,c,out_value), Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_value), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_value), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_value), + Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_value), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_value), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_value), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_value), + Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_value), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_value), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)atXYZ(px,py,z,c,out_value), Icpc = (Tfloat)atXYZ(x,py,z,c,out_value), + Inpc = (Tfloat)atXYZ(nx,py,z,c,out_value), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_value), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)atXYZ(px,y,z,c,out_value), Iccc = (Tfloat)atXYZ(x, y,z,c,out_value), + Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), Iacc = (Tfloat)atXYZ(ax,y,z,c,out_value), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_value), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), + Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_value), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)atXYZ(px,ay,z,c,out_value), Icac = (Tfloat)atXYZ(x,ay,z,c,out_value), + Inac = (Tfloat)atXYZ(nx,ay,z,c,out_value), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_value), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)atXYZ(px,py,nz,c,out_value), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_value), + Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_value), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_value), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_value), Iccn = (Tfloat)atXYZ(x, y,nz,c,out_value), + Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_value), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_value), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), + Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_value), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_value), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_value), + Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_value), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_value), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)atXYZ(px,py,az,c,out_value), Icpa = (Tfloat)atXYZ(x,py,az,c,out_value), + Inpa = (Tfloat)atXYZ(nx,py,az,c,out_value), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_value), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)atXYZ(px,y,az,c,out_value), Icca = (Tfloat)atXYZ(x, y,az,c,out_value), + Inca = (Tfloat)atXYZ(nx,y,az,c,out_value), Iaca = (Tfloat)atXYZ(ax,y,az,c,out_value), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)atXYZ(px,ny,az,c,out_value), Icna = (Tfloat)atXYZ(x,ny,az,c,out_value), + Inna = (Tfloat)atXYZ(nx,ny,az,c,out_value), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_value), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_value), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_value), + Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_value), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_value), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the XYZ-coordinates. + /** + Similar to cubic_atXYZ(float,float,float,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXYZ(float,float,float,int). + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXYZ(): Empty instance.", + cimg_instance); + return _cubic_atXYZ(fx,fy,fz,c); + } + + Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + const float + nfx = cimg::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 + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2, + py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2, + pz = z - 1<0?0:z - 1, nz = dz>0?z + 1:z, az = z + 2>=depth()?depth() - 1:z + 2; + const Tfloat + Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c), + Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)(*this)(px,y,pz,c), Iccp = (Tfloat)(*this)(x, y,pz,c), + Incp = (Tfloat)(*this)(nx,y,pz,c), Iacp = (Tfloat)(*this)(ax,y,pz,c), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c), + Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c), + Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c), + Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)(*this)(px,y,z,c), Iccc = (Tfloat)(*this)(x, y,z,c), + Incc = (Tfloat)(*this)(nx,y,z,c), Iacc = (Tfloat)(*this)(ax,y,z,c), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c), + Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c), + Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c), + Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)(*this)(px,y,nz,c), Iccn = (Tfloat)(*this)(x, y,nz,c), + Incn = (Tfloat)(*this)(nx,y,nz,c), Iacn = (Tfloat)(*this)(ax,y,nz,c), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c), + Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c), + Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c), + Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)(*this)(px,y,az,c), Icca = (Tfloat)(*this)(x, y,az,c), + Inca = (Tfloat)(*this)(nx,y,az,c), Iaca = (Tfloat)(*this)(ax,y,az,c), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c), + Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c), + Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the XYZ-coordinates. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atXYZ(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 + the value is spread amongst several neighbors if the pixel coordinates are float-valued. + \param value Pixel value to set. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param is_added Tells if the pixel value is added to (\c true), or simply replace (\c false) the current image + pixel(s). + \return A reference to the current image instance. + \note + - Calling this method with out-of-bounds coordinates does nothing. + **/ + CImg& set_linear_atX(const T& value, const float fx, const int y=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + if (y>=0 && y=0 && z=0 && c=0 && x=0 && nx& set_linear_atXY(const T& value, const float fx, const float fy=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + if (z>=0 && z=0 && c=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx& set_linear_atXYZ(const T& value, const float fx, const float fy=0, const float fz=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + if (c>=0 && c=0 && z=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx=0 && nz=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx image whose buffer data() is a \c char* string describing the list of all pixel values + of the image instance (written in base 10), separated by specified \c separator character. + \param separator A \c char character which specifies the separator between values in the returned C-string. + \param max_size Maximum size of the returned image (or \c 0 if no limits are set). + \param format For float/double-values, tell the printf format used to generate the Ascii representation + of the numbers (or \c 0 for default representation). + \note + - The returned image is never empty. + - For an empty image instance, the returned string is "". + - If \c max_size is equal to \c 0, there are no limits on the size of the returned string. + - Otherwise, if the maximum number of string characters is exceeded, the value string is cut off + and terminated by character \c '\0'. In that case, the returned image size is max_size + 1. + **/ + CImg value_string(const char separator=',', const unsigned int max_size=0, + const char *const format=0) const { + if (is_empty() || max_size==1) return CImg(1,1,1,1,0); + CImgList items; + CImg s_item(256); *s_item = 0; + const T *ptrs = _data; + unsigned int string_size = 0; + const char *const _format = format?format:cimg::type::format(); + for (ulongT off = 0, siz = size(); off::format(*(ptrs++))); + CImg item(s_item._data,printed_size); + item[printed_size - 1] = separator; + item.move_to(items); + if (max_size) string_size+=printed_size; + } + CImg res; + (items>'x').move_to(res); + if (max_size && res._width>=max_size) res.crop(0,max_size - 1); + res.back() = 0; + return res; + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Test shared state of the pixel buffer. + /** + Return \c true if image instance has a shared memory buffer, and \c false otherwise. + \note + - A shared image do not own his pixel buffer data() and will not deallocate it on destruction. + - Most of the time, a \c CImg image instance will \e not be shared. + - A shared image can only be obtained by a limited set of constructors and methods (see list below). + **/ + bool is_shared() const { + return _is_shared; + } + + //! Test if image instance is empty. + /** + Return \c true, if image instance is empty, i.e. does \e not contain any pixel values, has dimensions + \c 0 x \c 0 x \c 0 x \c 0 and a pixel buffer pointer set to \c 0 (null pointer), and \c false otherwise. + **/ + bool is_empty() const { + return !(_data && _width && _height && _depth && _spectrum); + } + + //! Test if image instance contains a 'inf' value. + /** + Return \c true, if image instance contains a 'inf' value, and \c false otherwise. + **/ + bool is_inf() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_inf((float)*p)) return true; + return false; + } + + //! Test if image instance contains a NaN value. + /** + Return \c true, if image instance contains a NaN value, and \c false otherwise. + **/ + bool is_nan() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_nan((float)*p)) return true; + return false; + } + + //! Test if image width is equal to specified value. + bool is_sameX(const unsigned int size_x) const { + return _width==size_x; + } + + //! Test if image width is equal to specified value. + template + bool is_sameX(const CImg& img) const { + return is_sameX(img._width); + } + + //! Test if image width is equal to specified value. + bool is_sameX(const CImgDisplay& disp) const { + return is_sameX(disp._width); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const unsigned int size_y) const { + return _height==size_y; + } + + //! Test if image height is equal to specified value. + template + bool is_sameY(const CImg& img) const { + return is_sameY(img._height); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const CImgDisplay& disp) const { + return is_sameY(disp._height); + } + + //! Test if image depth is equal to specified value. + bool is_sameZ(const unsigned int size_z) const { + return _depth==size_z; + } + + //! Test if image depth is equal to specified value. + template + bool is_sameZ(const CImg& img) const { + return is_sameZ(img._depth); + } + + //! Test if image spectrum is equal to specified value. + bool is_sameC(const unsigned int size_c) const { + return _spectrum==size_c; + } + + //! Test if image spectrum is equal to specified value. + template + bool is_sameC(const CImg& img) const { + return is_sameC(img._spectrum); + } + + //! Test if image width and height are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameY(unsigned int) const are both verified. + **/ + bool is_sameXY(const unsigned int size_x, const unsigned int size_y) const { + return _width==size_x && _height==size_y; + } + + //! Test if image width and height are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameY(const CImg&) const are both verified. + **/ + template + bool is_sameXY(const CImg& img) const { + return is_sameXY(img._width,img._height); + } + + //! Test if image width and height are the same as that of an existing display window. + /** + Test if is_sameX(const CImgDisplay&) const and is_sameY(const CImgDisplay&) const are both verified. + **/ + bool is_sameXY(const CImgDisplay& disp) const { + return is_sameXY(disp._width,disp._height); + } + + //! Test if image width and depth are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXZ(const unsigned int size_x, const unsigned int size_z) const { + return _width==size_x && _depth==size_z; + } + + //! Test if image width and depth are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXZ(const CImg& img) const { + return is_sameXZ(img._width,img._depth); + } + + //! Test if image width and spectrum are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXC(const unsigned int size_x, const unsigned int size_c) const { + return _width==size_x && _spectrum==size_c; + } + + //! Test if image width and spectrum are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXC(const CImg& img) const { + return is_sameXC(img._width,img._spectrum); + } + + //! Test if image height and depth are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameYZ(const unsigned int size_y, const unsigned int size_z) const { + return _height==size_y && _depth==size_z; + } + + //! Test if image height and depth are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameYZ(const CImg& img) const { + return is_sameYZ(img._height,img._depth); + } + + //! Test if image height and spectrum are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYC(const unsigned int size_y, const unsigned int size_c) const { + return _height==size_y && _spectrum==size_c; + } + + //! Test if image height and spectrum are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYC(const CImg& img) const { + return is_sameYC(img._height,img._spectrum); + } + + //! Test if image depth and spectrum are equal to specified values. + /** + Test if is_sameZ(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameZC(const unsigned int size_z, const unsigned int size_c) const { + return _depth==size_z && _spectrum==size_c; + } + + //! Test if image depth and spectrum are the same as that of another image. + /** + Test if is_sameZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameZC(const CImg& img) const { + return is_sameZC(img._depth,img._spectrum); + } + + //! Test if image width, height and depth are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXYZ(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z) const { + return is_sameXY(size_x,size_y) && _depth==size_z; + } + + //! Test if image width, height and depth are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXYZ(const CImg& img) const { + return is_sameXYZ(img._width,img._height,img._depth); + } + + //! Test if image width, height and spectrum are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXYC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_c) const { + return is_sameXY(size_x,size_y) && _spectrum==size_c; + } + + //! Test if image width, height and spectrum are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYC(const CImg& img) const { + return is_sameXYC(img._width,img._height,img._spectrum); + } + + //! Test if image width, depth and spectrum are equal to specified values. + /** + Test if is_sameXZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXZC(const unsigned int size_x, const unsigned int size_z, const unsigned int size_c) const { + return is_sameXZ(size_x,size_z) && _spectrum==size_c; + } + + //! Test if image width, depth and spectrum are the same as that of another image. + /** + Test if is_sameXZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXZC(const CImg& img) const { + return is_sameXZC(img._width,img._depth,img._spectrum); + } + + //! Test if image height, depth and spectrum are equal to specified values. + /** + Test if is_sameYZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYZC(const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const { + return is_sameYZ(size_y,size_z) && _spectrum==size_c; + } + + //! Test if image height, depth and spectrum are the same as that of another image. + /** + Test if is_sameYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYZC(const CImg& img) const { + return is_sameYZC(img._height,img._depth,img._spectrum); + } + + //! Test if image width, height, depth and spectrum are equal to specified values. + /** + Test if is_sameXYZ(unsigned int,unsigned int,unsigned int) const and is_sameC(unsigned int) const are both + verified. + **/ + bool is_sameXYZC(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c) const { + return is_sameXYZ(size_x,size_y,size_z) && _spectrum==size_c; + } + + //! Test if image width, height, depth and spectrum are the same as that of another image. + /** + Test if is_sameXYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYZC(const CImg& img) const { + return is_sameXYZC(img._width,img._height,img._depth,img._spectrum); + } + + //! Test if specified coordinates are inside image bounds. + /** + Return \c true if pixel located at (\c x,\c y,\c z,\c c) is inside bounds of the image instance, + and \c false otherwise. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Return \c true only if all these conditions are verified: + - The image instance is \e not empty. + - 0<=x<=\ref width() - 1. + - 0<=y<=\ref height() - 1. + - 0<=z<=\ref depth() - 1. + - 0<=c<=\ref spectrum() - 1. + **/ + bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const { + return !is_empty() && x>=0 && x=0 && y=0 && z=0 && c img(100,100,1,3); // Construct a 100x100 RGB color image + const unsigned long offset = 1249; // Offset to the pixel (49,12,0,0) + unsigned int x,y,z,c; + if (img.contains(img[offset],x,y,z,c)) { // Convert offset to (x,y,z,c) coordinates + std::printf("Offset %u refers to pixel located at (%u,%u,%u,%u).\n", + offset,x,y,z,c); + } + \endcode + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z, t& c) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = (ulongT)(ppixel - _data); + const ulongT nc = off/whd; + off%=whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc; + return true; + } + + //! Test if pixel value is inside image bounds and get its X,Y and Z-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X,Y and Z-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((ulongT)(ppixel - _data))%whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; + return true; + } + + //! Test if pixel value is inside image bounds and get its X and Y-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X and Y-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y) const { + const ulongT wh = (ulongT)_width*_height, siz = wh*_depth*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((unsigned int)(ppixel - _data))%wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; + return true; + } + + //! Test if pixel value is inside image bounds and get its X-coordinate. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X-coordinate is set. + **/ + template + bool contains(const T& pixel, t& x) const { + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + size()) return false; + x = (t)(((ulongT)(ppixel - _data))%_width); + return true; + } + + //! Test if pixel value is inside image bounds. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that no pixel coordinates are set. + **/ + bool contains(const T& pixel) const { + const T *const ppixel = &pixel; + return !is_empty() && ppixel>=_data && ppixel<_data + size(); + } + + //! Test if pixel buffers of instance and input images overlap. + /** + Return \c true, if pixel buffers attached to image instance and input image \c img overlap, + and \c false otherwise. + \param img Input image to compare with. + \note + - Buffer overlapping may happen when manipulating \e shared images. + - If two image buffers overlap, operating on one of the image will probably modify the other one. + - Most of the time, \c CImg instances are \e non-shared and do not overlap between each others. + \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 + std::printf("Buffers overlap!\n"); + } + \endcode + **/ + template + bool is_overlapped(const CImg& img) const { + const ulongT csiz = size(), isiz = img.size(); + return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz)); + } + + //! Test if the set {\c *this,\c primitives,\c colors,\c opacities} defines a valid 3D object. + /** + Return \c true is the 3D object represented by the set {\c *this,\c primitives,\c colors,\c opacities} defines a + valid 3D object, and \c false otherwise. The vertex coordinates are defined by the instance image. + \param primitives List of primitives of the 3D object. + \param colors List of colors of the 3D object. + \param opacities List (or image) of opacities of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_checking to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + template + bool is_object3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true, + char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check consistency for the particular case of an empty 3D object. + if (is_empty()) { + if (primitives || colors || opacities) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no vertices but %u primitives, " + "%u colors and %lu opacities", + _width,primitives._width,primitives._width, + colors._width,(unsigned long)opacities.size()); + return false; + } + return true; + } + + // Check consistency of vertices. + if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) has invalid vertex dimensions (%u,%u,%u,%u)", + _width,primitives._width,_width,_height,_depth,_spectrum); + return false; + } + if (colors._width>primitives._width + 1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %u colors", + _width,primitives._width,colors._width); + return false; + } + if (opacities.size()>primitives._width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %lu opacities", + _width,primitives._width,(unsigned long)opacities.size()); + return false; + } + if (!full_check) return true; + + // Check consistency of primitives. + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + const unsigned int psiz = (unsigned int)primitive.size(); + switch (psiz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)primitive(0); + if (i0>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indice %u in " + "point primitive [%u]", + _width,primitives._width,i0,l); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + if (i0>=_width || i1>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + case 2 : 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 " + "segment primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + 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 " + "triangle primitive [%u]", + _width,primitives._width,i0,i1,i2,l); + return false; + } + } break; + 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 " + "quadrangle primitive [%u]", + _width,primitives._width,i0,i1,i2,i3,l); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid primitive [%u] of size %u", + _width,primitives._width,l,(unsigned int)psiz); + return false; + } + } + + // Check consistency of colors. + cimglist_for(colors,c) { + const CImg& color = colors[c]; + if (!color) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no color for primitive [%u]", + _width,primitives._width,c); + return false; + } + } + + // Check consistency of light texture. + if (colors._width>primitives._width) { + const CImg &light = colors.back(); + if (!light || light._depth>1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid light texture (%u,%u,%u,%u)", + _width,primitives._width,light._width, + light._height,light._depth,light._spectrum); + return false; + } + } + + return true; + } + + //! Test if image instance represents a valid serialization of a 3D object. + /** + Return \c true if the image instance represents a valid serialization of a 3D object, and \c false otherwise. + \param full_check Tells if full checking of the instance must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_check to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + bool is_CImg3d(const bool full_check=true, char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check instance dimension and header. + if (_width!=1 || _height<8 || _depth!=1 || _spectrum!=1) { + if (error_message) cimg_sprintf(error_message, + "CImg3d has invalid dimensions (%u,%u,%u,%u)", + _width,_height,_depth,_spectrum); + return false; + } + const T *ptrs = _data, *const ptre = end(); + if (!_is_CImg3d(*(ptrs++),'C') || !_is_CImg3d(*(ptrs++),'I') || !_is_CImg3d(*(ptrs++),'m') || + !_is_CImg3d(*(ptrs++),'g') || !_is_CImg3d(*(ptrs++),'3') || !_is_CImg3d(*(ptrs++),'d')) { + if (error_message) cimg_sprintf(error_message, + "CImg3d header not found"); + return false; + } + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + + // Check consistency of number of vertices / primitives. + if (!full_check) { + const ulongT minimal_size = 8UL + 3*nb_points + 6*nb_primitives; + if (_data + minimal_size>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has only %lu values, while at least %lu values were expected", + nb_points,nb_primitives,(unsigned long)size(),(unsigned long)minimal_size); + return false; + } + } + + // Check consistency of vertex data. + if (!nb_points) { + if (nb_primitives) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no vertices but %u primitives", + nb_points,nb_primitives,nb_primitives); + return false; + } + if (ptrs!=ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) is an empty object but contains %u value%s " + "more than expected", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs),(ptre - ptrs)>1?"s":""); + return false; + } + return true; + } + if (ptrs + 3*nb_points>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines only %u vertices data", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs)/3); + return false; + } + ptrs+=3*nb_points; + + // Check consistency of primitive data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines %u vertices but no primitive", + nb_points,nb_primitives,nb_points); + return false; + } + + if (!full_check) return true; + + for (unsigned int p = 0; p=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive [%u]", + nb_points,nb_primitives,i0,p); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + ptrs+=3; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==6) ptrs+=4; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "segment primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==9) ptrs+=6; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in " + "triangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,p); + return false; + } + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)), + i3 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==12) ptrs+=8; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in " + "quadrangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,i3,p); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines an invalid primitive [%u] of size %u", + nb_points,nb_primitives,p,nb_inds); + return false; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete primitive data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,p,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of color data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no color/texture data", + nb_points,nb_primitives); + return false; + } + for (unsigned int c = 0; c=c) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared sprite/texture indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,c); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete color/texture data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,c,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of opacity data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no opacity data", + nb_points,nb_primitives); + return false; + } + for (unsigned int o = 0; o=o) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared opacity indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,o); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete opacity data for primitive [%u]", + nb_points,nb_primitives,o); + return false; + } + } + + // Check end of data. + if (ptrs1?"s":""); + return false; + } + return true; + } + + static bool _is_CImg3d(const T val, const char c) { + return val>=(T)c && val<(T)(c + 1); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + // Define the math formula parser/compiler and expression evaluator. + struct _cimg_math_parser { + CImg mem; + CImg memtype; + CImgList _code, &code, code_begin, code_end; + 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; + + CImg _img_stats, &img_stats, constcache_vals; + CImgList _list_stats, &list_stats, _list_median, &list_median; + 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; + ulongT 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_vector(arg) (memtype[arg]>1) // Is vector? +#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_op(s) s_op = s; ss_op = ss +#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char) +#define _cimg_mp_check_constant(arg,n_arg,mode) check_constant(arg,n_arg,mode,ss,se,saved_char) +#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char) +#define _cimg_mp_check_list(is_out) check_list(is_out,ss,se,saved_char) +#define _cimg_mp_defunc(mp) (*(mp_func)(*(mp).opcode))(mp) +#define _cimg_mp_return(x) { *se = saved_char; s_op = previous_s_op; ss_op = previous_ss_op; return x; } +#define _cimg_mp_return_nan() _cimg_mp_return(_cimg_mp_slot_nan) +#define _cimg_mp_constant(val) _cimg_mp_return(constant((double)(val))) +#define _cimg_mp_scalar0(op) _cimg_mp_return(scalar0(op)) +#define _cimg_mp_scalar1(op,i1) _cimg_mp_return(scalar1(op,i1)) +#define _cimg_mp_scalar2(op,i1,i2) _cimg_mp_return(scalar2(op,i1,i2)) +#define _cimg_mp_scalar3(op,i1,i2,i3) _cimg_mp_return(scalar3(op,i1,i2,i3)) +#define _cimg_mp_scalar4(op,i1,i2,i3,i4) _cimg_mp_return(scalar4(op,i1,i2,i3,i4)) +#define _cimg_mp_scalar5(op,i1,i2,i3,i4,i5) _cimg_mp_return(scalar5(op,i1,i2,i3,i4,i5)) +#define _cimg_mp_scalar6(op,i1,i2,i3,i4,i5,i6) _cimg_mp_return(scalar6(op,i1,i2,i3,i4,i5,i6)) +#define _cimg_mp_scalar7(op,i1,i2,i3,i4,i5,i6,i7) _cimg_mp_return(scalar7(op,i1,i2,i3,i4,i5,i6,i7)) +#define _cimg_mp_vector1_v(op,i1) _cimg_mp_return(vector1_v(op,i1)) +#define _cimg_mp_vector2_sv(op,i1,i2) _cimg_mp_return(vector2_sv(op,i1,i2)) +#define _cimg_mp_vector2_vs(op,i1,i2) _cimg_mp_return(vector2_vs(op,i1,i2)) +#define _cimg_mp_vector2_vv(op,i1,i2) _cimg_mp_return(vector2_vv(op,i1,i2)) +#define _cimg_mp_vector3_vss(op,i1,i2,i3) _cimg_mp_return(vector3_vss(op,i1,i2,i3)) + + // Constructors / Destructors. + ~_cimg_math_parser() { + cimg::srand(rng); + } + + _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), + rng((cimg::_rand(),cimg::rng())),calling_function(funcname?funcname:"cimg_math_parser") { +#ifdef cimg_use_openmp + 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 && (cimg::is_blank(*_expression) || *_expression==';')) ++_expression; + CImg::string(_expression).move_to(expr); + char *ps = &expr.back() - 1; + 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 (!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_slot_nan 29 +#define _cimg_mp_slot_x 30 +#define _cimg_mp_slot_y 31 +#define _cimg_mp_slot_z 32 +#define _cimg_mp_slot_c 33 + + mem.assign(96); + for (unsigned int i = 0; i<=10; ++i) mem[i] = (double)i; // mem[0-10] = 0...10 + for (unsigned int i = 1; i<=5; ++i) mem[i + 10] = -(double)i; // mem[11-15] = -1...-5 + mem[16] = 0.5; + mem[17] = 0; // thread_id + mem[18] = (double)imgin._width; // w + mem[19] = (double)imgin._height; // h + mem[20] = (double)imgin._depth; // d + mem[21] = (double)imgin._spectrum; // s + mem[22] = (double)imgin._is_shared; // r + mem[23] = (double)imgin._width*imgin._height; // wh + mem[24] = (double)imgin._width*imgin._height*imgin._depth; // whd + mem[25] = (double)imgin._width*imgin._height*imgin._depth*imgin._spectrum; // whds + mem[26] = (double)listin._width; // l + mem[27] = std::exp(1.); // 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 = compile-time constant | N>1 = constant ptr to vector[N-1] }. + memtype.assign(mem._width,1,1,1,0); + for (unsigned int i = 0; i<_cimg_mp_slot_x; ++i) memtype[i] = 1; + memtype[17] = 0; + memtype[_cimg_mp_slot_x] = memtype[_cimg_mp_slot_y] = memtype[_cimg_mp_slot_z] = memtype[_cimg_mp_slot_c] = -2; + mempos = _cimg_mp_slot_c + 1; + variable_pos.assign(8); + + reserved_label.assign(128,1,1,1,~0U); + // reserved_label[4-28] are used to store these two-char variables: + // [0] = wh, [1] = whd, [2] = whds, [3] = pi, [4] = im, [5] = iM, [6] = ia, [7] = iv, + // [8] = is, [9] = ip, [10] = ic, [11] = xm, [12] = ym, [13] = zm, [14] = cm, [15] = xM, + // [16] = yM, [17] = zM, [18]=cM, [19]=i0...[28]=i9, [29] = interpolation, [30] = boundary + + // Compile expression into a serie of opcodes. + s_op = ""; ss_op = expr._data; + const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0,false); + if (!_cimg_mp_is_constant(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(); + } + + // 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(); + level.assign(); + variable_pos.assign(); + reserved_label.assign(); + expr.assign(); + pexpr.assign(); + opcode.assign(); + opcode._is_shared = true; + + // Execute begin() bloc 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_begin.end(); + for (p_code = code_begin; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + p_code_end = code.end(); + } + + _cimg_math_parser(): + code(_code),p_code_end(0),p_break((CImg*)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), + 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)), + rng((cimg::_rand(),cimg::rng())),calling_function(0) { +#ifdef cimg_use_openmp + mem[17] = 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) { + 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'.", + pixel_type(),_cimg_mp_calling_function, + (ss - 4)>expr._data?"...":"", + (ss - 4)>expr._data?ss - 4:expr._data, + se<&expr.back()?"...":""); + } + char c1, c2, c3, c4; + + // Simplify expression when possible. + do { + c2 = 0; + if (ssss && (cimg::is_blank(c1 = *(se - 1)) || c1==';')) --se; + } + while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) { + ++ss; --se; c2 = 1; + } + } while (c2 && ss::%s: %s%s Missing %s, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", + *s_op=='F'?"argument":"item", + (ss_op - 4)>expr._data?"...":"", + (ss_op - 4)>expr._data?ss_op - 4:expr._data, + ss_op + std::strlen(ss_op)<&expr.back()?"...":""); + } + + const char *const previous_s_op = s_op, *const previous_ss_op = ss_op; + const unsigned int depth1 = depth + 1; + unsigned int pos, p1, p2, p3, arg1, arg2, arg3, arg4, arg5, arg6; + char + *const se1 = se - 1, *const se2 = se - 2, *const se3 = se - 3, + *const ss1 = ss + 1, *const ss2 = ss + 2, *const ss3 = ss + 3, *const ss4 = ss + 4, + *const ss5 = ss + 5, *const ss6 = ss + 6, *const ss7 = ss + 7, *const ss8 = ss + 8, + *s, *ps, *ns, *s0, *s1, *s2, *s3, sep = 0, end = 0; + double val = 0, val1, val2; + mp_func op; + + // 'p_ref' is a 'unsigned int[7]' used to return a reference to an image or vector value + // linked to the returned memory slot (reference that cannot be determined at compile time). + // p_ref[0] can be { 0 = scalar (unlinked) | 1 = vector value | 2 = image value (offset) | + // 3 = image value (coordinates) | 4 = image value as a vector (offsets) | + // 5 = image value as a vector (coordinates) }. + // Depending on p_ref[0], the remaining p_ref[k] have the following meaning: + // When p_ref[0]==0, p_ref is actually unlinked. + // When p_ref[0]==1, p_ref = [ 1, vector_ind, offset ]. + // When p_ref[0]==2, p_ref = [ 2, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==3, p_ref = [ 3, image_ind (or ~0U), is_relative, x, y, z, c ]. + // When p_ref[0]==4, p_ref = [ 4, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==5, p_ref = [ 5, image_ind (or ~0U), is_relative, x, y, z ]. + if (p_ref) { *p_ref = 0; p_ref[1] = p_ref[2] = p_ref[3] = p_ref[4] = p_ref[5] = p_ref[6] = ~0U; } + + const char saved_char = *se; *se = 0; + const unsigned int clevel = level[ss - expr._data], clevel1 = clevel + 1; + bool is_sth, is_relative; + CImg ref; + CImg variable_name; + CImgList l_opcode; + + // Look for a single value or a pre-defined variable. + 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=='-'); + 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) 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 (ss1==se) switch (*ss) { // One-char reserved variable + case 'c' : _cimg_mp_return(reserved_label['c']!=~0U?reserved_label['c']:_cimg_mp_slot_c); + case 'd' : _cimg_mp_return(reserved_label['d']!=~0U?reserved_label['d']:20); + case 'e' : _cimg_mp_return(reserved_label['e']!=~0U?reserved_label['e']:27); + case 'h' : _cimg_mp_return(reserved_label['h']!=~0U?reserved_label['h']:19); + case 'l' : _cimg_mp_return(reserved_label['l']!=~0U?reserved_label['l']:26); + case 'r' : _cimg_mp_return(reserved_label['r']!=~0U?reserved_label['r']:22); + case 's' : _cimg_mp_return(reserved_label['s']!=~0U?reserved_label['s']:21); + case 't' : _cimg_mp_return(reserved_label['t']!=~0U?reserved_label['t']:17); + case 'w' : _cimg_mp_return(reserved_label['w']!=~0U?reserved_label['w']:18); + case 'x' : _cimg_mp_return(reserved_label['x']!=~0U?reserved_label['x']:_cimg_mp_slot_x); + case 'y' : _cimg_mp_return(reserved_label['y']!=~0U?reserved_label['y']:_cimg_mp_slot_y); + case 'z' : _cimg_mp_return(reserved_label['z']!=~0U?reserved_label['z']:_cimg_mp_slot_z); + case 'u' : + if (reserved_label['u']!=~0U) _cimg_mp_return(reserved_label['u']); + _cimg_mp_scalar2(mp_u,0,1); + case 'g' : + if (reserved_label['g']!=~0U) _cimg_mp_return(reserved_label['g']); + _cimg_mp_scalar0(mp_g); + case 'i' : + if (reserved_label['i']!=~0U) _cimg_mp_return(reserved_label['i']); + _cimg_mp_scalar0(mp_i); + case 'I' : + _cimg_mp_op("Variable 'I'"); + if (reserved_label['I']!=~0U) _cimg_mp_return(reserved_label['I']); + 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); + _cimg_mp_return(pos); + case 'R' : + if (reserved_label['R']!=~0U) _cimg_mp_return(reserved_label['R']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,0,0); + case 'G' : + if (reserved_label['G']!=~0U) _cimg_mp_return(reserved_label['G']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,0,0); + case 'B' : + if (reserved_label['B']!=~0U) _cimg_mp_return(reserved_label['B']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,0,0); + case 'A' : + if (reserved_label['A']!=~0U) _cimg_mp_return(reserved_label['A']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,0,0); + } + else if (ss2==se) { // Two-chars reserved variable + arg1 = arg2 = ~0U; + if (*ss=='w' && *ss1=='h') // wh + _cimg_mp_return(reserved_label[0]!=~0U?reserved_label[0]:23); + if (*ss=='p' && *ss1=='i') // pi + _cimg_mp_return(reserved_label[3]!=~0U?reserved_label[3]:28); + if (*ss=='i') { + if (*ss1>='0' && *ss1<='9') { // i0...i9 + pos = 19 + *ss1 - '0'; + if (reserved_label[pos]!=~0U) _cimg_mp_return(reserved_label[pos]); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 19,0,0); + } + switch (*ss1) { + case 'm' : arg1 = 4; arg2 = 0; break; // im + case 'M' : arg1 = 5; arg2 = 1; break; // iM + case 'a' : arg1 = 6; arg2 = 2; break; // ia + case 'v' : arg1 = 7; arg2 = 3; break; // iv + case 's' : arg1 = 8; arg2 = 12; break; // is + case 'p' : arg1 = 9; arg2 = 13; break; // 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; + _cimg_mp_return(mem_img_median); + break; + } + } + else if (*ss1=='m') switch (*ss) { + case 'x' : arg1 = 11; arg2 = 4; break; // xm + case 'y' : arg1 = 12; arg2 = 5; break; // ym + case 'z' : arg1 = 13; arg2 = 6; break; // zm + case 'c' : arg1 = 14; arg2 = 7; break; // cm + } + else if (*ss1=='M') switch (*ss) { + case 'x' : arg1 = 15; arg2 = 8; break; // xM + case 'y' : arg1 = 16; arg2 = 9; break; // yM + case 'z' : arg1 = 17; arg2 = 10; break; // zM + case 'c' : arg1 = 18; arg2 = 11; break; // cM + } + if (arg1!=~0U) { + if (reserved_label[arg1]!=~0U) _cimg_mp_return(reserved_label[arg1]); + if (!img_stats) { + img_stats.assign(1,14,1,1,0).fill(imgin.get_stats(),false); + mem_img_stats.assign(1,14,1,1,~0U); + } + if (mem_img_stats[arg2]==~0U) mem_img_stats[arg2] = constant(img_stats[arg2]); + _cimg_mp_return(mem_img_stats[arg2]); + } + } else if (ss3==se) { // Three-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d') // whd + _cimg_mp_return(reserved_label[1]!=~0U?reserved_label[1]:24); + } else if (ss4==se) { // Four-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s') // whds + _cimg_mp_return(reserved_label[2]!=~0U?reserved_label[2]:25); + } + + pos = ~0U; + is_sth = false; + for (s0 = ss, s = ss1; s='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg2)?4:2; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + if (_cimg_mp_is_vector(arg2)) + set_variable_vector(arg2); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + } + + + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), + arg2,p1,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), + arg2,p1,arg1,_cimg_mp_size(arg2)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg1,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ss1=='(' && *ve1==')') { // i/j/I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) is_parallelizable = false; + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + } else if (s1='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg5)?5:3; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + p_ref[4] = arg2; + p_ref[5] = arg3; + p_ref[6] = arg4; + if (_cimg_mp_is_vector(arg5)) + set_variable_vector(arg5); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg5)) memtype[arg5] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2; + if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -2; + } + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg5,p1,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), + arg5,p1,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), + arg5,p1,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg5,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg5,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg5,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } + _cimg_mp_return(arg5); + } + } + + // Assign vector value (direct). + if (l_variable_name>3 && *ve1==']' && *ss!='[') { + s0 = ve1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + is_sth = true; // is_valid_variable_name? + if (*ss>='0' && *ss<='9') is_sth = false; + else for (ns = ss; nsss) { + 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 + _cimg_mp_check_type(arg3,2,1,0); + + if (variable_name[1]) { // Multi-char variable + cimglist_for(variable_def,i) if (!std::strcmp(variable_name,variable_def[i])) { + arg1 = variable_pos[i]; break; + } + } else arg1 = reserved_label[*variable_name]; // Single-char variable + if (arg1==~0U) compile(ss,s0 - 1,depth1,0,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); + } + } + } + + // 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 + 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 && cimg::is_blank(*s)) ++s; + CImg(s,(unsigned int)(se - s + 1)).move_to(macro_body,0); + + p1 = 1; // Indice of current parsed argument + for (s = s0 + 1; s<=s1; ++p1, s = ns + 1) { // Parse function arguments + if (p1>24) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Too much specified arguments (>24) in macro " + "definition '%s()', in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + while (*s && 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; nsexpr._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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + is_sth?"Empty":"Invalid",p1, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (ns==s1 || *ns==',') { // New argument found + *s3 = 0; + p2 = (unsigned int)(s3 - s2); // Argument length + for (ps = std::strstr(macro_body[0],s2); ps; ps = std::strstr(ps,s2)) { // Replace by arg number + if (!((ps>macro_body[0]._data && is_varchar(*(ps - 1))) || + (ps + 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 (cimg::is_blank(*s0)) ++s0; + variable_name.resize(variable_name.end() - s0,1,1,1,0,0,1); + } + + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + // Assign variable (direct). + if (is_sth) { + arg3 = variable_name[1]?~0U:*variable_name; // One-char variable + if (variable_name[1] && !variable_name[2]) { // Two-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + if (c1=='w' && c2=='h') arg3 = 0; // wh + else if (c1=='p' && c2=='i') arg3 = 3; // pi + else if (c1=='i') { + if (c2>='0' && c2<='9') arg3 = 19 + c2 - '0'; // i0...i9 + else if (c2=='m') arg3 = 4; // im + else if (c2=='M') arg3 = 5; // iM + else if (c2=='a') arg3 = 6; // ia + else if (c2=='v') arg3 = 7; // iv + else if (c2=='s') arg3 = 8; // is + else if (c2=='p') arg3 = 9; // ip + else if (c2=='c') arg3 = 10; // ic + } else if (c2=='m') { + if (c1=='x') arg3 = 11; // xm + else if (c1=='y') arg3 = 12; // ym + else if (c1=='z') arg3 = 13; // zm + else if (c1=='c') arg3 = 14; // cm + } else if (c2=='M') { + if (c1=='x') arg3 = 15; // xM + else if (c1=='y') arg3 = 16; // yM + else if (c1=='z') arg3 = 17; // zM + else if (c1=='c') arg3 = 18; // cM + } + } else if (variable_name[1] && variable_name[2] && !variable_name[3]) { // Three-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + if (c1=='w' && c2=='h' && c3=='d') arg3 = 1; // whd + } else if (variable_name[1] && variable_name[2] && variable_name[3] && + !variable_name[4]) { // Four-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + c4 = variable_name[3]; + if (c1=='w' && c2=='h' && c3=='d' && c4=='s') arg3 = 2; // whds + } else if (!std::strcmp(variable_name,"interpolation")) arg3 = 29; // interpolation + else if (!std::strcmp(variable_name,"boundary")) arg3 = 30; // boundary + + arg1 = ~0U; + arg2 = compile(s + 1,se,depth1,0,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; } + + if (arg1==~0U) { // Create new variable + if (_cimg_mp_is_vector(arg2)) { // Vector variable + arg1 = is_comp_vector(arg2)?arg2:vector_copy(arg2); + set_variable_vector(arg1); + } else { // Scalar variable + if (is_const) arg1 = arg2; + else { + arg1 = _cimg_mp_is_comp(arg2)?arg2:scalar1(mp_copy,arg2); + memtype[arg1] = -1; + } + } + + if (arg3!=~0U) reserved_label[arg3] = arg1; + else { + if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); + variable_pos[variable_def._width] = arg1; + variable_name.move_to(variable_def); + } + + } else { // Variable already exists -> assign a new value + if (is_const || _cimg_mp_is_constant(arg1)) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid assignment of %sconst variable '%s'%s, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"already-defined ":"non-", + variable_name._data, + !_cimg_mp_is_constant(arg1) && is_const?" as a new const variable":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + _cimg_mp_check_type(arg2,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_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)). + move_to(code); + else // From scalar + CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg2). + move_to(code); + } else // Scalar + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + } + _cimg_mp_return(arg1); + } + + // Assign lvalue (variable name was not valid for a direct assignment). + arg1 = ~0U; + is_sth = (bool)std::strchr(variable_name,'?'); // Contains_ternary_operator? + if (is_sth) break; // Do nothing and make ternary operator prioritary over assignment + + if (l_variable_name>2 && (std::strchr(variable_name,'(') || std::strchr(variable_name,'['))) { + ref.assign(7); + arg1 = compile(ss,s,depth1,ref,is_single); // Lvalue slot + arg2 = compile(s + 1,se,depth1,0,is_single); // 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). + move_to(code); + _cimg_mp_return(arg2); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg3).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg2,p1,arg3,arg4,arg5,arg6).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg2,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg3,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg2,arg3,arg4,arg5).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg2,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V = value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) // From vector + CImg::vector((ulongT)mp_vector_copy,arg1,arg2,(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). + move_to(code); + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s = scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + } + + // No assignment expressions match -> error + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + arg1!=~0U && _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Apply unary/binary/ternary operators. The operator precedences should be the same as in C++. + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='*' || *ps=='/' || *ps=='^') && *ns==*ps && + level[s - expr._data]==clevel) { // Self-operators for complex numbers only (**=,//=,^^=) + _cimg_mp_op(*ps=='*'?"Operator '**='":*ps=='/'?"Operator '//='":"Operator '^^='"); + + ref.assign(7); + arg1 = compile(ss,ns,depth1,ref,is_single); // Vector slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Right operand + _cimg_mp_check_type(arg1,1,2,2); + _cimg_mp_check_type(arg2,2,3,2); + if (_cimg_mp_is_vector(arg2)) { // Complex **= complex + if (*ps=='*') + CImg::vector((ulongT)mp_complex_mul,arg1,arg1,arg2).move_to(code); + else if (*ps=='/') + CImg::vector((ulongT)mp_complex_div_vv,arg1,arg1,arg2).move_to(code); + else + CImg::vector((ulongT)mp_complex_pow_vv,arg1,arg1,arg2).move_to(code); + } else { // Complex **= scalar + if (*ps=='*') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_mul,arg2); + } else if (*ps=='/') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_div,arg2); + } else { + if (arg2==1) _cimg_mp_return(arg1); + CImg::vector((ulongT)mp_complex_pow_vs,arg1,arg1,arg2).move_to(code); + } + } + + // Write computed value back in image if necessary. + if (*ref==4) { // Image value (vector): I/J[_#ind,off] **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + + } else if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + } + + _cimg_mp_return(arg1); + } + + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || *ps=='%' || + *ps=='&' || *ps=='^' || *ps=='|' || + (*ps=='>' && *ns=='>') || (*ps=='<' && *ns=='<')) && + level[s - expr._data]==clevel) { // Self-operators (+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=) + switch (*ps) { + case '+' : op = mp_self_add; _cimg_mp_op("Operator '+='"); break; + case '-' : op = mp_self_sub; _cimg_mp_op("Operator '-='"); break; + case '*' : op = mp_self_mul; _cimg_mp_op("Operator '*='"); break; + case '/' : op = mp_self_div; _cimg_mp_op("Operator '/='"); break; + case '%' : op = mp_self_modulo; _cimg_mp_op("Operator '%='"); break; + case '<' : op = mp_self_bitwise_left_shift; _cimg_mp_op("Operator '<<='"); break; + case '>' : op = mp_self_bitwise_right_shift; _cimg_mp_op("Operator '>>='"); break; + case '&' : op = mp_self_bitwise_and; _cimg_mp_op("Operator '&='"); break; + case '|' : op = mp_self_bitwise_or; _cimg_mp_op("Operator '|='"); break; + default : op = mp_self_pow; _cimg_mp_op("Operator '^='"); break; + } + s1 = *ps=='>' || *ps=='<'?ns:ps; + + ref.assign(7); + arg1 = compile(ss,s1,depth1,ref,is_single); // Variable slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Value to apply + + // Check for particular case to be simplified. + if ((op==mp_self_add || op==mp_self_sub) && !arg2) _cimg_mp_return(arg1); + if ((op==mp_self_mul || op==mp_self_div) && arg2==1) _cimg_mp_return(arg1); + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k] += scalar + _cimg_mp_check_type(arg2,2,1,0); + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(arg1); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + _cimg_mp_return(arg1); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V += value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) self_vector_v(arg1,op,arg2); // Vector += vector + else self_vector_s(arg1,op,arg2); // Vector += scalar + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s += scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + + variable_name.assign(ss,(unsigned int)(s - ss)).back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + for (s = ss1; s::vector((ulongT)mp_if,pos,arg1,arg2,arg3, + p3 - p2,code._width - p3,arg4).move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='|' && *ns=='|' && level[s - expr._data]==clevel) { // Logical or ('||') + _cimg_mp_op("Operator '||'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] || mem[arg2]); + if (!arg1) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_or,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='&' && *ns=='&' && level[s - expr._data]==clevel) { // Logical and ('&&') + _cimg_mp_op("Operator '&&'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] && mem[arg2]); + if (arg1>0 && arg1<=16) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_and,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se2; s>ss; --s) + if (*s=='|' && level[s - expr._data]==clevel) { // Bitwise or ('|') + _cimg_mp_op("Operator '|'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_vector2_sv(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1] | (longT)mem[arg2]); + if (!arg2) _cimg_mp_return(arg1); + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_scalar2(mp_bitwise_or,arg1,arg2); + } + + for (s = se2; s>ss; --s) + if (*s=='&' && level[s - expr._data]==clevel) { // Bitwise and ('&') + _cimg_mp_op("Operator '&'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_bitwise_and,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='!' && *ns=='=' && level[s - expr._data]==clevel) { // Not equal to ('!=') + _cimg_mp_op("Operator '!='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]!=mem[arg2]); + _cimg_mp_scalar2(mp_neq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='=' && *ns=='=' && level[s - expr._data]==clevel) { // Equal to ('==') + _cimg_mp_op("Operator '=='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]==mem[arg2]); + _cimg_mp_scalar2(mp_eq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='=' && level[s - expr._data]==clevel) { // Less or equal than ('<=') + _cimg_mp_op("Operator '<='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_lte,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='=' && level[s - expr._data]==clevel) { // Greater or equal than ('>=') + _cimg_mp_op("Operator '>='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_gte,arg1,arg2); + } + + for (s = se2, ns = se1, ps = se3; s>ss; --s, --ns, --ps) + if (*s=='<' && *ns!='<' && *ps!='<' && level[s - expr._data]==clevel) { // Less than ('<') + _cimg_mp_op("Operator '<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 ('>') + _cimg_mp_op("Operator '>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_gt,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='<' && level[s - expr._data]==clevel) { // Left bit shift ('<<') + _cimg_mp_op("Operator '<<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_left_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_left_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]<<(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_left_shift,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='>' && level[s - expr._data]==clevel) { // Right bit shift ('>>') + _cimg_mp_op("Operator '>>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_right_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_right_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]>>(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_right_shift,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='+' && (*ns!='+' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Addition ('+') + _cimg_mp_op("Operator '+'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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' + 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]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_linear_add,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_increment,arg1); + if (arg1==1) _cimg_mp_scalar1(mp_increment,arg2); + _cimg_mp_scalar2(mp_add,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='-' && (*ns!='-' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Subtraction ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_sub,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_vector1_v(mp_minus,arg2); + _cimg_mp_vector2_sv(mp_sub,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] - mem[arg2]); + if (!arg1) _cimg_mp_scalar1(mp_minus,arg2); + if (code) { // Try to spot linear cases 'a*b - c' and 'c - a*b' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)(arg3==arg1?mp_linear_sub_left:mp_linear_sub_right), + arg3,arg4,arg5,arg3==arg1?arg2:arg1).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_decrement,arg1); + _cimg_mp_scalar2(mp_sub,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='*' && *ns=='*' && level[s - expr._data]==clevel) { // Complex multiplication ('**') + _cimg_mp_op("Operator '**'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + pos = vector(2); + CImg::vector((ulongT)mp_complex_mul,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='/' && *ns=='/' && level[s - expr._data]==clevel) { // Complex division ('//') + _cimg_mp_op("Operator '//'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + _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); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]); + if (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='*' && level[s - expr._data]==clevel) { // Multiplication ('*') + _cimg_mp_op("Operator '*'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + p2 = _cimg_mp_size(arg2); + if (p2>0 && _cimg_mp_size(arg1)==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); + _cimg_mp_return(pos); + } + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_mul,arg1,arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + + if (code) { // Try to spot double multiplication 'a*b*c' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_mul2,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='/' && level[s - expr._data]==clevel) { // Division ('/') + _cimg_mp_op("Operator '/'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2, ns = se1; s>ss; --s, --ns) + if (*s=='%' && *ns!='^' && level[s - expr._data]==clevel) { // Modulo ('%') + _cimg_mp_op("Operator '%'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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])); + _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)); + } + + if (*ss=='-' && (*ss1!='-' || (ss2='0' && *ss2<='9'))) { // Unary minus ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_minus,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(-mem[arg1]); + _cimg_mp_scalar1(mp_minus,arg1); + } + + if (*ss=='!') { // Logical not ('!') + _cimg_mp_op("Operator '!'"); + if (*ss1=='!') { // '!!expr' optimized as 'bool(expr)' + arg1 = compile(ss2,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_logical_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(!mem[arg1]); + _cimg_mp_scalar1(mp_logical_not,arg1); + } + + if (*ss=='~') { // Bitwise not ('~') + _cimg_mp_op("Operator '~'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bitwise_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(~(unsigned int)mem[arg1]); + _cimg_mp_scalar1(mp_bitwise_not,arg1); + } + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='^' && *ns=='^' && level[s - expr._data]==clevel) { // Complex power ('^^') + _cimg_mp_op("Operator '^^'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + pos = vector(2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vv,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vs,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::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); + _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); + _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])); + switch (arg2) { + case 0 : _cimg_mp_return(1); + case 2 : _cimg_mp_scalar1(mp_sqr,arg1); + case 3 : _cimg_mp_scalar1(mp_pow3,arg1); + case 4 : _cimg_mp_scalar1(mp_pow4,arg1); + default : + if (_cimg_mp_is_constant(arg2)) { + if (mem[arg2]==0.5) { _cimg_mp_scalar1(mp_sqrt,arg1); } + else if (mem[arg2]==0.25) { _cimg_mp_scalar1(mp_pow0_25,arg1); } + } + _cimg_mp_scalar2(mp_pow,arg1,arg2); + } + } + + // Percentage computation. + if (*se1=='%') { + arg1 = compile(ss,se1,depth1,0,is_single); + arg2 = _cimg_mp_is_constant(arg1)?0:constant(100); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(mp_div,arg1,arg2); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(mem[arg1]/100); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + is_sth = ss1ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { // Pre/post-decrement and increment + if ((is_sth && *ss=='+') || (!is_sth && *se1=='+')) { + _cimg_mp_op("Operator '++'"); + op = mp_self_increment; + } else { + _cimg_mp_op("Operator '--'"); + op = mp_self_decrement; + } + ref.assign(7); + arg1 = is_sth?compile(ss2,se,depth1,ref,is_single): + compile(ss,se2,depth1,ref,is_single); // Variable slot + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (is_sth) pos = arg1; // Determine return indice, depending on pre/post action + else { + if (_cimg_mp_is_vector(arg1)) pos = vector_copy(arg1); + else pos = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k]++ + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,1).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(pos); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off]++ + if (!is_single) 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); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off]++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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(pos); + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg1,arg3,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V++ + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s++ + CImg::vector((ulongT)op,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (is_sth) variable_name.assign(ss2,(unsigned int)(se - ss1)); + else variable_name.assign(ss,(unsigned int)(se1 - ss)); + variable_name.back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'. + if (*se1==']' && *ss!='[') { + _cimg_mp_op("Value accessor '[]'"); + is_relative = *ss=='j' || *ss=='J'; + s0 = s1 = std::strchr(ss,'['); if (s0) { do { --s1; } while (cimg::is_blank(*s1)); cimg::swap(*s0,*++s1); } + + if ((*ss=='I' || *ss=='J') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a vector + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff), + pos,p1,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Joff:mp_Ioff), + pos,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + if ((*ss=='i' || *ss=='j') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + if (s0>ss) { // Vector value + arg1 = compile(ss,s0,depth1,0,is_single); + if (_cimg_mp_is_scalar(arg1)) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Array brackets used on non-vector variable '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + + } + s1 = s0 + 1; while (s1 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); + arg3 = (unsigned int)mem[arg3]; + pos = vector(arg3); + CImg::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + // One argument -> vector value reference + arg2 = compile(++s0,se1,depth1,0,is_single); + if (_cimg_mp_is_constant(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::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'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data,nb, + variable_name._data,_cimg_mp_size(arg1), + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (p_ref) { + *p_ref = 1; + p_ref[1] = arg1; + p_ref[2] = arg2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; // Prevent from being used in further optimization + } + pos = scalar3(mp_vector_off,arg1,_cimg_mp_size(arg1),arg2); + memtype[pos] = -2; // Prevent from being used in further optimization + _cimg_mp_return(pos); + } + } + + // Look for a function call, an access to image value, or a parenthesis. + if (*se1==')') { + if (*ss=='(') _cimg_mp_return(compile(ss1,se1,depth1,p_ref,is_single)); // Simple parentheses + _cimg_mp_op("Value accessor '()'"); + is_relative = *ss=='j' || *ss=='J'; + 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 (s01) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + if (s1::vector((ulongT)(is_relative?mp_list_Jxyz:mp_list_Ixyz), + pos,p1,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Jxyz:mp_Ixyz), + pos,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + // i/j(_#ind,_x,_y,_z,_c,_interpolation,_boundary_conditions) + if ((*ss=='i' || *ss=='j') && *ss1=='(') { // Image value as scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + if (s1::vector((ulongT)mp_arg,0,0,p2,arg1,arg2).move_to(l_opcode); + for (s = ++s2; s::vector(arg3).move_to(l_opcode); + ++p3; + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (_cimg_mp_is_constant(arg1)) { + p3-=1; // Number of args + arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]); + if (arg1::vector((ulongT)mp_break,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"breakpoint(",11)) { // Break point (for abort test) + _cimg_mp_op("Function 'breakpoint()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_breakpoint,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"bool(",5)) { // Boolean cast + _cimg_mp_op("Function 'bool()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + + if (!std::strncmp(ss,"begin(",6)) { // Begin + _cimg_mp_op("Function 'begin()'"); + code.swap(code_begin); + arg1 = compile(ss6,se1,depth1,p_ref,true); + code.swap(code_begin); + _cimg_mp_return(arg1); + } + break; + + case 'c' : + if (!std::strncmp(ss,"cabs(",5)) { // Complex absolute value + _cimg_mp_op("Function 'cabs()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_complex_abs,arg1 + 1,arg1 + 2); + } + + if (!std::strncmp(ss,"carg(",5)) { // Complex argument + _cimg_mp_op("Function 'carg()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_atan2,arg1 + 2,arg1 + 1); + } + + if (!std::strncmp(ss,"cats(",5)) { // Concatenate strings + _cimg_mp_op("Function 'cats()'"); + CImg::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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cbrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::cbrt(mem[arg1])); + _cimg_mp_scalar1(mp_cbrt,arg1); + } + + if (!std::strncmp(ss,"cconj(",6)) { // Complex conjugate + _cimg_mp_op("Function 'cconj()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_conj,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ceil(",5)) { // Ceil + _cimg_mp_op("Function 'ceil()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + pos = vector(2); + CImg::vector((ulongT)mp_complex_exp,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"clog(",5)) { // Complex logarithm + _cimg_mp_op("Function 'clog()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_log,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"continue(",9)) { // Complex absolute value + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_continue,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"copy(",5)) { // Memory copy + _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 = constant(_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); + _cimg_mp_check_type(arg6,5,1,0); + 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); + } + + if (!std::strncmp(ss,"cos(",4)) { // Cosine + _cimg_mp_op("Function 'cos()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cos,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cos(mem[arg1])); + _cimg_mp_scalar1(mp_cos,arg1); + } + + if (!std::strncmp(ss,"cosh(",5)) { // Hyperbolic cosine + _cimg_mp_op("Function 'cosh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cosh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cosh(mem[arg1])); + _cimg_mp_scalar1(mp_cosh,arg1); + } + + if (!std::strncmp(ss,"critical(",9)) { // Critical section (single thread at a time) + _cimg_mp_op("Function 'critical()'"); + p1 = code._width; + arg1 = compile(ss + 9,se1,depth1,p_ref,true); + CImg::vector((ulongT)mp_critical,arg1,code._width - p1).move_to(code,p1); + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"crop(",5)) { // Image crop + _cimg_mp_op("Function 'crop()'"); + 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); + } + 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 + 2; + break; + case 3 : + CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode); + arg1 = arg2 + 2; + break; + case 4 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,_cimg_mp_boundary). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + break; + case 5 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,opcode[4]). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + 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 = arg2 + (is_sth?2:4); + break; + case 7 : + CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, + opcode[6]).move_to(opcode); + arg1 = arg2 + (is_sth?2:4); + 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 = arg2 + (is_sth?2:5); + break; + case 9 : + arg1 = arg2 + (is_sth?2:5); + 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); + 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()?"...":""); + } + 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); + _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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"cut(",4)) { // Cut + _cimg_mp_op("Function 'cut()'"); + s1 = ss4; while (s1val2?val2:val); + } + _cimg_mp_scalar3(mp_cut,arg1,arg2,arg3); + } + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_d,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"date(",5)) { // Current date or file date + _cimg_mp_op("Function 'date()'"); + s1 = ss5; while (s1::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_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); + *se1 = 0; + variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + ((CImg::vector((ulongT)mp_debug,arg1,0,code._width - p1), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code,p1); + *se1 = ')'; + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"display(",8)) { // Display memory, vector or image + _cimg_mp_op("Function 'display()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_display_memory,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + if (*ss8!='#') { // Vector + s1 = ss8; while (s1::string(ss8,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + if (_cimg_mp_is_vector(arg1)) + ((CImg::vector((ulongT)mp_vector_print,arg1,0,(ulongT)_cimg_mp_size(arg1),0), + variable_name)>'y').move_to(opcode); + else + ((CImg::vector((ulongT)mp_print,arg1,0,0), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + + ((CImg::vector((ulongT)mp_display,arg1,0,(ulongT)_cimg_mp_size(arg1), + arg2,arg3,arg4,arg5), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + *s1 = c1; + _cimg_mp_return(arg1); + + } else { // Image + p1 = compile(ss8 + 1,se1,depth1,0,is_single); + _cimg_mp_check_list(true); + CImg::vector((ulongT)mp_image_display,_cimg_mp_slot_nan,p1).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"det(",4)) { // Matrix determinant + _cimg_mp_op("Function 'det()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + } + + if (!std::strncmp(ss,"diag(",5)) { // Diagonal matrix + _cimg_mp_op("Function 'diag()'"); + 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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"dot(",4)) { // Dot product + _cimg_mp_op("Function 'dot()'"); + s1 = ss4; while (s1::vector((ulongT)mp_do,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_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; + } else { + if (s0::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; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"eig(",4)) { // Matrix eigenvalues/eigenvector + _cimg_mp_op("Function 'eig()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + _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); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"ellipse(",8)) { // Ellipse/circle drawing + if (!is_single) 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 (!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,"fact(",5)) { // Factorial + _cimg_mp_op("Function 'fact()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + 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])); + _cimg_mp_scalar1(mp_fibonacci,arg1); + } + + if (!std::strncmp(ss,"find(",5)) { // Find + _cimg_mp_op("Function 'find()'"); + + // First argument: data to look at. + s0 = ss5; while (s0::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); + _cimg_mp_return(p3); + } + + if (!std::strncmp(ss,"floor(",6)) { // Floor + _cimg_mp_op("Function 'floor()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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_mp_return(pos); + } + break; + + case 'g' : + if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function + _cimg_mp_op("Function 'gauss()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_h,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'i' : + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_median,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (*ss1=='f' && *ss2=='(') { // If..then[..else.] + _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); + _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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_int,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((longT)mem[arg1]); + _cimg_mp_scalar1(mp_int,arg1); + } + + if (!std::strncmp(ss,"inv(",4)) { // Matrix/scalar inversion + _cimg_mp_op("Function 'inv()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) { + _cimg_mp_check_matrix_square(arg1,1); + p1 = (unsigned int)cimg::round(std::sqrt((float)_cimg_mp_size(arg1))); + pos = vector(p1*p1); + CImg::vector((ulongT)mp_matrix_inv,pos,arg1,p1).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(1/mem[arg1]); + _cimg_mp_scalar2(mp_div,1,arg1); + } + + if (*ss1=='s') { // Family of 'is_?()' functions + + if (!std::strncmp(ss,"isbool(",7)) { // Is boolean? + _cimg_mp_op("Function 'isbool()'"); + if (ss7==se1) _cimg_mp_return(0); + arg1 = compile(ss7,se1,depth1,0,is_single); + 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. || 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); + } + + if (!std::strncmp(ss,"isfile(",7)) { // Is file? + _cimg_mp_op("Function 'isfile()'"); + *se1 = 0; + is_sth = cimg::is_file(ss7); + *se1 = ')'; + _cimg_mp_return(is_sth?1U:0U); + } + + if (!std::strncmp(ss,"isin(",5)) { // Is in sequence/vector? + if (ss5>=se1) _cimg_mp_return(0); + _cimg_mp_op("Function 'isin()'"); + pos = scalar(); + CImg::vector((ulongT)mp_isin,pos,0).move_to(l_opcode); + 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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"isinf(",6)) { // Is infinite? + _cimg_mp_op("Function 'isinf()'"); + if (ss6==se1) _cimg_mp_return(0); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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)); + _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); + 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])); + _cimg_mp_scalar1(mp_isnan,arg1); + } + + if (!std::strncmp(ss,"isval(",6)) { // Is value? + _cimg_mp_op("Function 'isval()'"); + val = 0; + if (cimg_sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1); + _cimg_mp_return(0); + } + + } + break; + + case 'l' : + if (*ss1=='(') { // Size of image list + _cimg_mp_op("Function 'l()'"); + if (ss2!=se1) break; + _cimg_mp_scalar0(mp_list_l); + } + + if (!std::strncmp(ss,"log(",4)) { // Natural logarithm + _cimg_mp_op("Function 'log()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log(mem[arg1])); + _cimg_mp_scalar1(mp_log,arg1); + } + + if (!std::strncmp(ss,"log2(",5)) { // Base-2 logarithm + _cimg_mp_op("Function 'log2()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log2,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::log2(mem[arg1])); + _cimg_mp_scalar1(mp_log2,arg1); + } + + if (!std::strncmp(ss,"log10(",6)) { // Base-10 logarithm + _cimg_mp_op("Function 'log10()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log10,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log10(mem[arg1])); + _cimg_mp_scalar1(mp_log10,arg1); + } + + if (!std::strncmp(ss,"lowercase(",10)) { // Lower case + _cimg_mp_op("Function 'lowercase()'"); + arg1 = compile(ss + 10,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_lowercase,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::lowercase(mem[arg1])); + _cimg_mp_scalar1(mp_lowercase,arg1); + } + break; + + case 'm' : + if (!std::strncmp(ss,"mul(",4)) { // Matrix multiplication + _cimg_mp_op("Function 'mul()'"); + s1 = ss4; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'n' : + if (!std::strncmp(ss,"narg(",5)) { // Number of arguments + _cimg_mp_op("Function 'narg()'"); + if (ss5>=se1) _cimg_mp_return(0); + arg1 = 0; + for (s = ss5; s::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::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); + if (arg1>0 && opcode._height==4) // Special case with one argument and p>=1 + _cimg_mp_scalar1(mp_abs,opcode[3]); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'p' : + if (!std::strncmp(ss,"permut(",7)) { // Number of permutations + _cimg_mp_op("Function 'permut()'"); + s1 = ss7; while (s1::vector((ulongT)mp_polygon,_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 (!std::strncmp(ss,"print(",6) || !std::strncmp(ss,"prints(",7)) { // Print expressions + is_sth = ss[5]=='s'; // is prints() + _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); + 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()?"...":""); + } + 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,"resize(",7)) { // Vector or image resize + _cimg_mp_op("Function 'resize()'"); + if (*ss7!='#') { // Vector + s1 = ss7; while (s1::vector((ulongT)mp_vector_resize,pos,arg2,arg1,(ulongT)_cimg_mp_size(arg1), + arg3,arg4).move_to(code); + _cimg_mp_return(pos); + + } else { // Image + if (!is_single) 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); + pos = 0; + for (s = s0; s10) { + *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 arguments, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + pos<1?"Missing":"Too much", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + l_opcode[0].move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"reverse(",8)) { // Vector reverse + _cimg_mp_op("Function 'reverse()'"); + arg1 = compile(ss8,se1,depth1,0,is_single); + 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); + _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 (s11) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + arg4 = compile(++s1,se1,depth1,0,is_single); + } else { + s2 = s1 + 1; while (s2::vector((ulongT)mp_rot3d,pos,arg1,arg2,arg3,arg4).move_to(code); + } else { // 2D rotation + _cimg_mp_check_type(arg1,1,1,0); + pos = vector(4); + CImg::vector((ulongT)mp_rot2d,pos,arg1).move_to(code); + } + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"round(",6)) { // Value rounding + _cimg_mp_op("Function 'round()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_s,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"same(",5)) { // Test if operands have the same values + _cimg_mp_op("Function 'same()'"); + s1 = ss5; while (s1::vector((ulongT)mp_shift,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sign(",5)) { // Sign + _cimg_mp_op("Function 'sign()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sign,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sign(mem[arg1])); + _cimg_mp_scalar1(mp_sign,arg1); + } + + if (!std::strncmp(ss,"sin(",4)) { // Sine + _cimg_mp_op("Function 'sin()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sin,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sin(mem[arg1])); + _cimg_mp_scalar1(mp_sin,arg1); + } + + if (!std::strncmp(ss,"sinc(",5)) { // Sine cardinal + _cimg_mp_op("Function 'sinc()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinc,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sinc(mem[arg1])); + _cimg_mp_scalar1(mp_sinc,arg1); + } + + if (!std::strncmp(ss,"sinh(",5)) { // Hyperbolic sine + _cimg_mp_op("Function 'sinh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sinh(mem[arg1])); + _cimg_mp_scalar1(mp_sinh,arg1); + } + + if (!std::strncmp(ss,"size(",5)) { // Vector size + _cimg_mp_op("Function 'size()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_constant(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_size(arg1)); + } + + if (!std::strncmp(ss,"solve(",6)) { // Solve linear system + _cimg_mp_op("Function 'solve()'"); + s1 = ss6; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_solve,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sort(",5)) { // Sort vector + _cimg_mp_op("Function 'sort()'"); + 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(); + } + } + + if (!std::strncmp(ss,"sqr(",4)) { // Square + _cimg_mp_op("Function 'sqr()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqr,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sqr(mem[arg1])); + _cimg_mp_scalar1(mp_sqr,arg1); + } + + if (!std::strncmp(ss,"sqrt(",5)) { // Square root + _cimg_mp_op("Function 'sqrt()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sqrt(mem[arg1])); + _cimg_mp_scalar1(mp_sqrt,arg1); + } + + if (!std::strncmp(ss,"srand(",6)) { // Set RNG seed + _cimg_mp_op("Function 'srand()'"); + arg1 = ss6::vector((ulongT)mp_image_stats,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"stov(",5)) { // String to double + _cimg_mp_op("Function 'stov()'"); + s1 = ss5; 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()?"...":""); + } + pos = vector(p1 + p2 + p2*p2); + CImg::vector((ulongT)mp_matrix_svd,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 't' : + if (!std::strncmp(ss,"tan(",4)) { // Tangent + _cimg_mp_op("Function 'tan()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tan,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tan(mem[arg1])); + _cimg_mp_scalar1(mp_tan,arg1); + } + + if (!std::strncmp(ss,"tanh(",5)) { // Hyperbolic tangent + _cimg_mp_op("Function 'tanh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tanh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tanh(mem[arg1])); + _cimg_mp_scalar1(mp_tanh,arg1); + } + + if (!std::strncmp(ss,"trace(",6)) { // Matrix trace + _cimg_mp_op("Function 'trace()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _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); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,p2, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(p3*p2); + CImg::vector((ulongT)mp_transp,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'u' : + if (*ss1=='(') { // Random value with uniform distribution + _cimg_mp_op("Function 'u()'"); + if (*ss2==')') _cimg_mp_scalar2(mp_u,0,1); + s1 = ss2; while (s1ss6 && *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 (arg20) || + !std::strncmp(ss,"vector(",7) || + (!std::strncmp(ss,"vector",6) && ss7::sequence(arg4,arg3 + 1,arg3 + arg4).move_to(l_opcode); + arg2+=arg4; + } else { CImg::vector(arg3).move_to(l_opcode); ++arg2; } + s = ns; + } + if (arg1==~0U) arg1 = arg2; + 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); + _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)mp_vtos,pos,p1,arg1,_cimg_mp_size(arg1),arg2).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'w' : + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_w,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_wh,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss4!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whd,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss5!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whds,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"while(",6)) { // While...do + _cimg_mp_op("Function 'while()'"); + s0 = *ss5=='('?ss6:ss8; + s1 = s0; while (s1::vector((ulongT)mp_while,pos,arg1,p2 - p1,code._width - p2,arg2, + pos>=arg6 && !_cimg_mp_is_constant(pos), + arg1>=arg6 && !_cimg_mp_is_constant(arg1)).move_to(code,p1); + _cimg_mp_return(pos); + } + break; + + case 'x' : + if (!std::strncmp(ss,"xor(",4)) { // Xor + _cimg_mp_op("Function 'xor()'"); + s1 = ss4; while (s1::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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (is_sth) _cimg_mp_constant(op(*this)); + opcode.move_to(code); + _cimg_mp_return(pos); + } + + // No corresponding built-in function -> Look for a user-defined macro call. + s0 = strchr(ss,'('); + if (s0) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + + // Count number of specified arguments. + p1 = 0; + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { + while (*s && 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 + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { // Parse function arguments + while (*s && cimg::is_blank(*s)) ++s; + if (*s==')' && p1==1) break; // Function has no arguments + if (p1>p2) { ++p1; break; } + ns = s; while (ns _pexpr(_expr._width); + ns = _pexpr._data; + for (ps = _expr._data, c1 = ' '; *ps; ++ps) { + if (!cimg::is_blank(*ps)) c1 = *ps; + *(ns++) = c1; + } + *ns = 0; + + CImg _level = get_level(_expr); + expr.swap(_expr); + pexpr.swap(_pexpr); + level.swap(_level); + s0 = user_macro; + user_macro = macro_def[l]; + pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref,is_single); + user_macro = s0; + level.swap(_level); + pexpr.swap(_pexpr); + expr.swap(_expr); + _cimg_mp_return(pos); + } + + if (arg3) { // Macro name matched but number of arguments does not + CImg sig_nargs(arg3); + arg1 = 0; + cimglist_for(macro_def,l) if (!std::strcmp(macro_def[l],variable_name)) + sig_nargs[arg1++] = (unsigned int)macro_def[l].back(); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (sig_nargs._width>1) { + sig_nargs.sort(); + arg1 = sig_nargs.back(); + --sig_nargs._width; + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,sig_nargs.value_string()._data,arg1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } else + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,*sig_nargs,*sig_nargs!=1?"s":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + } + } // if (se1==')') + + // Char / string initializer. + if (*se1=='\'' && + ((se1>ss && *ss=='\'') || + (se1>ss1 && *ss=='_' && *ss1=='\''))) { + if (*ss=='_') { _cimg_mp_op("Char initializer"); s1 = ss2; } + else { _cimg_mp_op("String initializer"); s1 = ss1; } + arg1 = (unsigned int)(se1 - s1); // Original string length + if (arg1) { + CImg(s1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + if (*ss=='_') { + if (arg1==1) _cimg_mp_constant(*variable_name); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Literal %s contains more than one character, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + ss1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg1); + CImg::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); + _cimg_mp_return(pos); + } + + // Vector initializer [ ... ]. + if (*ss=='[' && *se1==']') { + _cimg_mp_op("Vector initializer"); + 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 + if (arg1) { + CImg(s1 + 1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + pos = vector(arg1); + CImg::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); + } else { // Vector values provided as list of items + arg1 = 0; // Number of specified values + if (*ss1!=']') for (s = ss1; s::sequence(arg3,arg2 + 1,arg2 + arg3).move_to(l_opcode); + arg1+=arg3; + } else { CImg::vector(arg2).move_to(l_opcode); ++arg1; } + s = ns; + } + 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); + } + _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); + _cimg_mp_return(pos); + case 'R' : // R#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0, + 0,_cimg_mp_boundary); + case 'G' : // G#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1, + 0,_cimg_mp_boundary); + case 'B' : // B#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2, + 0,_cimg_mp_boundary); + case 'A' : // A#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3, + 0,_cimg_mp_boundary); + } + } + + if (*ss1 && *ss2=='#' && ss3::vector(listin[p1].median()).move_to(list_median[p1]); + _cimg_mp_constant(*list_median[p1]); + } + _cimg_mp_scalar1(mp_list_median,arg1); + } + if (*ss1>='0' && *ss1<='9') { // i0#ind...i9#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,*ss1 - '0', + 0,_cimg_mp_boundary); + } + switch (*ss1) { + case 'm' : arg2 = 0; break; // im#ind + case 'M' : arg2 = 1; break; // iM#ind + case 'a' : arg2 = 2; break; // ia#ind + case 'v' : arg2 = 3; break; // iv#ind + case 's' : arg2 = 12; break; // is#ind + case 'p' : arg2 = 13; break; // ip#ind + } + } else if (*ss1=='m') switch (*ss) { + case 'x' : arg2 = 4; break; // xm#ind + case 'y' : arg2 = 5; break; // ym#ind + case 'z' : arg2 = 6; break; // zm#ind + case 'c' : arg2 = 7; break; // cm#ind + } else if (*ss1=='M') switch (*ss) { + case 'x' : arg2 = 8; break; // xM#ind + case 'y' : arg2 = 9; break; // yM#ind + case 'z' : arg2 = 10; break; // zM#ind + case 'c' : arg2 = 11; break; // cM#ind + } + if (arg2!=~0U) { + if (!listin) _cimg_mp_return(0); + if (_cimg_mp_is_constant(arg1)) { + if (!list_stats) list_stats.assign(listin._width); + if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(listin[p1].get_stats(),false); + _cimg_mp_constant(list_stats(p1,arg2)); + } + _cimg_mp_scalar2(mp_list_stats,arg1,arg2); + } + } + + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4 error. + is_sth = true; // is_valid_variable_name + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + *se = saved_char; + c1 = *se1; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (is_sth) + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Undefined variable '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + s1 = std::strchr(ss,'('); + s_op = s1 && c1==')'?"function call":"item"; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + s_op,variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Evaluation procedure. + double operator()(const double x, const double y, const double z, const double c) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + return *result; + } + + // Evaluation procedure (return output values in vector 'output'). + template + void operator()(const double x, const double y, const double z, const double c, t *const output) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + if (result_dim) { + const double *ptrs = result + 1; + t *ptrd = output; + for (unsigned int k = 0; k_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + + // Return type of a memory element 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); + cimg_sprintf(res._data + 6,"%u",_cimg_mp_size(arg)); + } else CImg::string("scalar").move_to(res); + return res; + } + + // Insert constant value in memory. + unsigned int constant(const double val) { + + // Search for built-in constant. + if (cimg::type::is_nan(val)) return _cimg_mp_slot_nan; + if (val==(double)(int)val) { + if (val>=0 && val<=10) return (unsigned int)val; + if (val<0 && val>=-5) return (unsigned int)(10 - val); + } + if (val==0.5) return 16; + + // Search for constant already requested before (in const cache). + unsigned int ind = ~0U; + if (constcache_size<1024) { + if (!constcache_size) { + constcache_vals.assign(16,1,1,1,0); + constcache_inds.assign(16,1,1,1,0); + *constcache_vals = val; + constcache_size = 1; + ind = 0; + } else { // Dichotomic search + const double val_beg = *constcache_vals, val_end = constcache_vals[constcache_size - 1]; + if (val_beg>=val) ind = 0; + else if (val_end==val) ind = constcache_size - 1; + else if (val_end=constcache_size || constcache_vals[ind]!=val) { + ++constcache_size; + if (constcache_size>constcache_vals._width) { + constcache_vals.resize(-200,1,1,1,0); + constcache_inds.resize(-200,1,1,1,0); + } + const int l = constcache_size - (int)ind - 1; + if (l>0) { + std::memmove(&constcache_vals[ind + 1],&constcache_vals[ind],l*sizeof(double)); + std::memmove(&constcache_inds[ind + 1],&constcache_inds[ind],l*sizeof(unsigned int)); + } + constcache_vals[ind] = val; + constcache_inds[ind] = 0; + } + } + if (constcache_inds[ind]) return constcache_inds[ind]; + } + + // Insert new constant in memory if necessary. + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(-200,1,1,1,0); } + const unsigned int pos = mempos++; + mem[pos] = val; + memtype[pos] = 1; // Set constant property + if (ind!=~0U) constcache_inds[ind] = pos; + return pos; + } + + // Insert code instructions for processing scalars. + unsigned int scalar() { // Insert new scalar in memory + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(mem._width,1,1,1,0); } + return mempos++; + } + + unsigned int scalar0(const mp_func op) { + const unsigned int pos = scalar(); + CImg::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!=~0U && 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!=~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: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: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: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: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: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: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 + if (mempos + siz>=mem._width) { + mem.resize(2*mem._width + siz,1,1,1,0); + memtype.resize(mem._width,1,1,1,0); + } + const unsigned int pos = mempos++; + mem[pos] = cimg::type::nan(); + memtype[pos] = siz + 1; + mempos+=siz; + return pos; + } + + unsigned int vector(const unsigned int siz, const double value) { // Insert new initialized vector + const unsigned int pos = vector(siz); + double *ptr = &mem[pos] + 1; + for (unsigned int i = 0; i::vector((ulongT)mp_vector_copy,pos,arg,siz).move_to(code); + return pos; + } + + void self_vector_s(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_s,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1).move_to(code[code._width - 1 - siz + k]); + } + } + + void self_vector_v(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_v,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + } + + unsigned int vector1_v(const mp_func op, const unsigned int arg1) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vs(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_sv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector3_vss(const mp_func op, const unsigned int arg1, const unsigned int arg2, + const unsigned int arg3) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2,arg3).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + // Check if a memory slot is a positive integer constant scalar value. + // 'mode' can be: + // { 0=constant | 1=integer constant | 2=positive integer constant | 3=strictly-positive integer constant } + void check_constant(const unsigned int arg, const unsigned int n_arg, + const unsigned int mode, + char *const ss, char *const se, const char saved_char) { + _cimg_mp_check_type(arg,n_arg,1,0); + if (!(_cimg_mp_is_constant(arg) && + (!mode || (double)(int)mem[arg]==mem[arg]) && + (mode<2 || mem[arg]>=(mode==3)))) { + const char *s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ": + n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth ": + n_arg==9?"Ninth ":"One of the "; + *se = saved_char; + char *const s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_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); + 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // Evaluation functions, known by the parser. + // Defining these functions 'static' ensures that sizeof(mp_func)==sizeof(ulongT), + // so we can store pointers to them directly in the opcode vectors. +#ifdef _mp_arg +#undef _mp_arg +#endif +#define _mp_arg(x) mp.mem[mp.opcode[x]] + + static double mp_abs(_cimg_math_parser& mp) { + return cimg::abs(_mp_arg(2)); + } + + static double mp_add(_cimg_math_parser& mp) { + return _mp_arg(2) + _mp_arg(3); + } + + static double mp_acos(_cimg_math_parser& mp) { + return std::acos(_mp_arg(2)); + } + + static double mp_acosh(_cimg_math_parser& mp) { + return cimg::acosh(_mp_arg(2)); + } + + static double mp_asinh(_cimg_math_parser& mp) { + return cimg::asinh(_mp_arg(2)); + } + + static double mp_atanh(_cimg_math_parser& mp) { + return cimg::atanh(_mp_arg(2)); + } + + static double mp_arg(_cimg_math_parser& mp) { + const int _ind = (int)_mp_arg(4); + const unsigned int + nb_args = (unsigned int)mp.opcode[2] - 4, + ind = _ind<0?_ind + nb_args:(unsigned int)_ind, + siz = (unsigned int)mp.opcode[3]; + if (siz>0) { + if (ind>=nb_args) std::memset(&_mp_arg(1) + 1,0,siz*sizeof(double)); + else std::memcpy(&_mp_arg(1) + 1,&_mp_arg(ind + 4) + 1,siz*sizeof(double)); + return cimg::type::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; ival) { val = _val; argval = i - 3; } + } + return (double)argval; + } + + static double mp_asin(_cimg_math_parser& mp) { + return std::asin(_mp_arg(2)); + } + + static double mp_atan(_cimg_math_parser& mp) { + return std::atan(_mp_arg(2)); + } + + static double mp_atan2(_cimg_math_parser& mp) { + return std::atan2(_mp_arg(2),_mp_arg(3)); + } + + static double mp_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; i>(unsigned int)_mp_arg(3)); + } + + static double mp_bitwise_xor(_cimg_math_parser& mp) { + return (double)((longT)_mp_arg(2) ^ (longT)_mp_arg(3)); + } + + static double mp_bool(_cimg_math_parser& mp) { + return (double)(bool)_mp_arg(2); + } + + static double mp_break(_cimg_math_parser& mp) { + mp.break_type = 1; + mp.p_code = mp.p_break - 1; + return cimg::type::nan(); + } + + static double mp_breakpoint(_cimg_math_parser& mp) { + cimg_abort_init; + cimg_abort_test; + cimg::unused(mp); + return cimg::type::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_cbrt(_cimg_math_parser& mp) { + return cimg::cbrt(_mp_arg(2)); + } + + static double mp_ceil(_cimg_math_parser& mp) { + return std::ceil(_mp_arg(2)); + } + + static double mp_complex_abs(_cimg_math_parser& mp) { + return cimg::_hypot(_mp_arg(2),_mp_arg(3)); + } + + static double mp_complex_conj(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = *(ptrs++); + *ptrd = -*(ptrs); + return cimg::type::nan(); + } + + static double mp_complex_div_sv(_cimg_math_parser& mp) { + const double + *ptr2 = &_mp_arg(3) + 1, + r1 = _mp_arg(2), + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = r1*r2/denom; + *ptrd = -r1*i2/denom; + return cimg::type::nan(); + } + + static double mp_complex_div_vv(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = (r1*r2 + i1*i2)/denom; + *ptrd = (r2*i1 - r1*i2)/denom; + return cimg::type::nan(); + } + + static double mp_complex_exp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs), er = std::exp(r); + *(ptrd++) = er*std::cos(i); + *(ptrd++) = er*std::sin(i); + return cimg::type::nan(); + } + + static double mp_complex_log(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs); + *(ptrd++) = 0.5*std::log(r*r + i*i); + *(ptrd++) = std::atan2(i,r); + return cimg::type::nan(); + } + + static double mp_complex_mul(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = r1*r2 - i1*i2; + *(ptrd++) = r1*i2 + r2*i1; + return cimg::type::nan(); + } + + static void _mp_complex_pow(const double r1, const double i1, + const double r2, const double i2, + double *ptrd) { + double ro, io; + if (cimg::abs(i2)<1e-15) { // Exponent is real + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) { + if (cimg::abs(r2)<1e-15) { ro = 1; io = 0; } + else ro = io = 0; + } else { + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2), + phio = r2*phi1; + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + } else { // Exponent is complex + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) ro = io = 0; + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2)*std::exp(-i2*phi1), + phio = r2*phi1 + 0.5*i2*std::log(mod1_2); + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + *(ptrd++) = ro; + *ptrd = io; + } + + static double mp_complex_pow_ss(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_sv(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,ptr2[0],ptr2[1],ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vs(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vv(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],ptr2[0],ptr2[1],ptrd); + return cimg::type::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_cos(_cimg_math_parser& mp) { + return std::cos(_mp_arg(2)); + } + + static double mp_cosh(_cimg_math_parser& mp) { + return std::cosh(_mp_arg(2)); + } + + static double mp_critical(_cimg_math_parser& mp) { + const double res = _mp_arg(1); + cimg_pragma_openmp(critical(mp_critical)) + { + for (const CImg *const p_end = ++mp.p_code + mp.opcode[2]; + mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + } + --mp.p_code; + return res; + } + + static double mp_crop(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const unsigned int + dx = (unsigned int)mp.opcode[7], + dy = (unsigned int)mp.opcode[8], + dz = (unsigned int)mp.opcode[9], + dc = (unsigned int)mp.opcode[10]; + const 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 (!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, + z + dz - 1,c + dc - 1, + boundary_conditions); + return cimg::type::nan(); + } + + static double mp_cross(_cimg_math_parser& mp) { + CImg + vout(&_mp_arg(1) + 1,1,3,1,1,true), + v1(&_mp_arg(2) + 1,1,3,1,1,true), + v2(&_mp_arg(3) + 1,1,3,1,1,true); + (vout = v1).cross(v2); + return cimg::type::nan(); + } + + static double mp_cut(_cimg_math_parser& mp) { + double val = _mp_arg(2), cmin = _mp_arg(3), cmax = _mp_arg(4); + return valcmax?cmax:val; + } + + 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; + } + + 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++); + cimg::strellipsize(expr); + const ulongT g_target = mp.opcode[1]; + +#ifndef cimg_use_openmp + const unsigned int n_thread = 0; +#else + const unsigned int n_thread = omp_get_thread_num(); +#endif + cimg_pragma_openmp(critical(mp_debug)) + { + std::fprintf(cimg::output(), + "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" + "Start debugging expression '%s', code length %u -> mem[%u] (memsize: %u)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)mp.opcode[3],(unsigned int)g_target,mp.mem._width); + std::fflush(cimg::output()); + mp.debug_indent+=3; + } + const CImg *const p_end = (++mp.p_code) + mp.opcode[3]; + CImg _op; + for ( ; mp.p_code &op = *mp.p_code; + mp.opcode._data = op._data; + + _op.assign(1,op._height - 1); + const ulongT *ptrs = op._data + 1; + for (ulongT *ptrd = _op._data, *const ptrde = _op._data + _op._height; ptrd mem[%u] = %g", + (void*)&mp,n_thread,mp.debug_indent,' ', + (void*)mp.opcode._data,(void*)*mp.opcode,_op.value_string().data(), + (unsigned int)target,mp.mem[target]); + std::fflush(cimg::output()); + } + } + cimg_pragma_openmp(critical(mp_debug)) + { + 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)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)g_target,mp.mem[g_target],mp.mem._width); + std::fflush(cimg::output()); + } + --mp.p_code; + return mp.mem[g_target]; + } + + static double mp_decrement(_cimg_math_parser& mp) { + return _mp_arg(2) - 1; + } + + static double mp_det(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + return CImg(ptrs,k,k,1,1,true).det(); + } + + 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; + 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"); + return cimg::type::nan(); + } + + static double mp_display(_cimg_math_parser& mp) { + const unsigned int + _siz = (unsigned int)mp.opcode[3], + siz = _siz?_siz:1; + const double *const ptr = &_mp_arg(1) + (_siz?1:0); + const int + w = (int)_mp_arg(4), + h = (int)_mp_arg(5), + d = (int)_mp_arg(6), + s = (int)_mp_arg(7); + CImg img; + if (w>0 && h>0 && d>0 && s>0) { + if ((unsigned int)w*h*d*s<=siz) img.assign(ptr,w,h,d,s,true); + else img.assign(ptr,siz).resize(w,h,d,s,-1); + } else img.assign(ptr,1,siz,1,1,true); + + CImg expr(mp.opcode[2] - 8); + const ulongT *ptrs = mp.opcode._data + 8; + cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); + ((CImg::string("[" cimg_appname "_math_parser] ",false,true),expr)>'x').move_to(expr); + cimg::strellipsize(expr); + std::fputc('\n',cimg::output()); + img.display(expr._data); + return cimg::type::nan(); + } + + static double mp_div(_cimg_math_parser& mp) { + return _mp_arg(2)/_mp_arg(3); + } + + static double mp_dot(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[4]; + return CImg(&_mp_arg(2) + 1,1,siz,1,1,true). + dot(CImg(&_mp_arg(3) + 1,1,siz,1,1,true)); + } + + static double mp_do(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_body = ++mp.p_code, + *const p_cond = p_body + mp.opcode[3], + *const p_end = p_cond + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (mp.mem[mem_cond]); + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_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; + 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); + const CImg str = _str>'x'; + std::fprintf(cimg::output(),"\n%s",str._data); + return cimg::type::nan(); + } + + static double mp_ellipse(_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]; + CImg color(img._spectrum,1,1,1,0); + bool is_invalid_arguments = false; + unsigned int i = 4; + float r1 = 0, r2 = 0, angle = 0, opacity = 1; + int x0 = 0, y0 = 0; + if (i>=i_end) is_invalid_arguments = true; + else { + x0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + y0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + r1 = (float)_mp_arg(i++); + if (i>=i_end) r2 = r1; + 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) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_eq(_cimg_math_parser& mp) { + 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); +#endif + return cimg::type::nan(); + } + + static double mp_exp(_cimg_math_parser& mp) { + return std::exp(_mp_arg(2)); + } + + static double mp_eye(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int k = (unsigned int)mp.opcode[2]; + CImg(ptrd,k,k,1,1,true).identity_matrix(); + return cimg::type::nan(); + } + + static double mp_factorial(_cimg_math_parser& mp) { + return cimg::factorial((int)_mp_arg(2)); + } + + static double mp_fibonacci(_cimg_math_parser& mp) { + return cimg::fibonacci((int)_mp_arg(2)); + } + + static double mp_find(_cimg_math_parser& mp) { + const bool is_forward = (bool)_mp_arg(5); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const double + *const ptrb = &_mp_arg(2) + 1, + *const ptre = ptrb + siz, + val = _mp_arg(4), + *ptr = ptrb + ind; + + // Forward search + if (is_forward) { + while (ptr=ptrb && *ptr!=val) --ptr; + return ptr=(longT)siz1) return -1.; + const double + *const ptr1b = &_mp_arg(2) + 1, + *const ptr1e = ptr1b + siz1, + *const ptr2b = &_mp_arg(4) + 1, + *const ptr2e = ptr2b + siz2, + *ptr1 = ptr1b + ind, + *p1 = 0, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 + *const p_init = ++mp.p_code, + *const p_cond = p_init + mp.opcode[4], + *const p_body = p_cond + mp.opcode[5], + *const p_post = p_body + mp.opcode[6], + *const p_end = p_post + mp.opcode[7]; + const unsigned int vsiz = (unsigned int)mp.opcode[2]; + bool is_cond = false; + if (mp.opcode[8]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[9]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + + for (mp.p_code = p_init; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + + if (!mp.break_type) do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + + for (mp.p_code = p_post; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_fsize(_cimg_math_parser& mp) { + const CImg filename(mp.opcode._data + 3,mp.opcode[2] - 3); + return (double)cimg::fsize(filename); + } + + static double mp_g(_cimg_math_parser& mp) { + cimg::unused(mp); + return cimg::grand(&mp.rng); + } + + 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); + } + + static double mp_gcd(_cimg_math_parser& mp) { + return cimg::gcd((long)_mp_arg(2),(long)_mp_arg(3)); + } + + static double mp_gt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>_mp_arg(3)); + } + + static double mp_gte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>=_mp_arg(3)); + } + + static double mp_i(_cimg_math_parser& mp) { + return (double)mp.imgin.atXYZC((int)mp.mem[_cimg_mp_slot_x],(int)mp.mem[_cimg_mp_slot_y], + (int)mp.mem[_cimg_mp_slot_z],(int)mp.mem[_cimg_mp_slot_c],(T)0); + } + + static double mp_if(_cimg_math_parser& mp) { + const bool is_cond = (bool)_mp_arg(2); + const ulongT + mem_left = mp.opcode[3], + mem_right = mp.opcode[4]; + const CImg + *const p_right = ++mp.p_code + mp.opcode[5], + *const p_end = p_right + mp.opcode[6]; + const unsigned int vtarget = (unsigned int)mp.opcode[1], vsiz = (unsigned int)mp.opcode[7]; + if (is_cond) for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + else for (mp.p_code = p_right; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.p_code==mp.p_break) --mp.p_code; + else mp.p_code = p_end - 1; + if (vsiz) std::memcpy(&mp.mem[vtarget] + 1,&mp.mem[is_cond?mem_left:mem_right] + 1,sizeof(double)*vsiz); + return mp.mem[is_cond?mem_left:mem_right]; + } + + static double mp_image_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]; + 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()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.display(title); + cimg::mutex(6,0); + 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]; + 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]; + return (double)img.median(); + } + + static double mp_image_print(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.print(title); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_resize(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + const double + _w = mp.opcode[3]==~0U?-100:_mp_arg(3), + _h = mp.opcode[4]==~0U?-100:_mp_arg(4), + _d = mp.opcode[5]==~0U?-100:_mp_arg(5), + _s = mp.opcode[6]==~0U?-100:_mp_arg(6); + const unsigned int + w = (unsigned int)(_w>=0?_w:-_w*img.width()/100), + h = (unsigned int)(_h>=0?_h:-_h*img.height()/100), + d = (unsigned int)(_d>=0?_d:-_d*img.depth()/100), + s = (unsigned int)(_s>=0?_s:-_s*img.spectrum()/100), + interp = (int)_mp_arg(7); + if (mp.is_fill && img._data==mp.imgout._data) { + cimg::mutex(6,0); + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'resize()': " + "Cannot both fill and resize image (%u,%u,%u,%u) " + "to new dimensions (%u,%u,%u,%u).", + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,w,h,d,s); + } + const unsigned int + boundary = (int)_mp_arg(8); + const float + cx = (float)_mp_arg(9), + cy = (float)_mp_arg(10), + cz = (float)_mp_arg(11), + cc = (float)_mp_arg(12); + img.resize(w,h,d,s,interp,boundary,cx,cy,cz,cc); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_s(_cimg_math_parser& mp) { + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; + return (double)img.spectrum(); + } + + static double mp_image_sort(_cimg_math_parser& mp) { + const bool is_increasing = (bool)_mp_arg(3); + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()), + axis = (unsigned int)_mp_arg(4); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + img.sort(is_increasing, + axis==0 || axis=='x'?'x': + axis==1 || axis=='y'?'y': + axis==2 || axis=='z'?'z': + axis==3 || axis=='c'?'c':0); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + 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(); + 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(); + } + 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]; + 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]; + 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]; + 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]; + return (double)img.width()*img.height()*img.depth()*img.spectrum(); + } + + static double mp_increment(_cimg_math_parser& mp) { + return _mp_arg(2) + 1; + } + + static double mp_int(_cimg_math_parser& mp) { + return (double)(longT)_mp_arg(2); + } + + static double mp_ioff(_cimg_math_parser& mp) { + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3); + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off::is_inf(_mp_arg(2)); + } + + static double mp_isint(_cimg_math_parser& mp) { + return (double)(cimg::mod(_mp_arg(2),1.)==0); + } + + static double mp_isnan(_cimg_math_parser& mp) { + return (double)cimg::type::is_nan(_mp_arg(2)); + } + + static double mp_ixyzc(_cimg_math_parser& mp) { + const unsigned int + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7); + const CImg &img = mp.imgin; + const double + x = _mp_arg(2), y = _mp_arg(3), + z = _mp_arg(4), c = _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.imgin; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), + z = oz + _mp_arg(4), c = oc + _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx vals(i_end - 4); + double *p = vals.data(); + for (unsigned int i = 4; i &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(4); + const ulongT siz = (ulongT)img.size(); + longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const T + *const ptrb = img.data(), + *const ptre = img.end(), + *ptr = ptrb + ind; + const double val = _mp_arg(3); + + // Forward search + if (is_forward) { + while (ptr=ptrb && (double)*ptr!=val) --ptr; + return ptr &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(5); + const ulongT + siz1 = (ulongT)img.size(), + siz2 = (ulongT)mp.opcode[4]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz1 - 1); + if (ind<0 || ind>=(longT)siz1) return -1.; + const T + *const ptr1b = img.data(), + *const ptr1e = ptr1b + siz1, + *ptr1 = ptr1b + ind, + *p1 = 0; + const double + *const ptr2b = &_mp_arg(3) + 1, + *const ptr2e = ptr2b + siz2, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + x = _mp_arg(3), y = _mp_arg(4), + z = _mp_arg(5), c = _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), + z = oz + _mp_arg(5), c = oc + _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx::vector(mp.listin[ind].median()).move_to(mp.list_median[ind]); + return *mp.list_median[ind]; + } + + static double mp_list_set_ioff(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), y = (int)_mp_arg(4), + z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(3)), y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)), c = (int)(oc + _mp_arg(6)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Ixyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_set_Joff_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Jxyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_spectrum(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._spectrum; + } + + static double mp_list_stats(_cimg_math_parser& mp) { + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + k = (unsigned int)mp.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); + return mp.list_stats(ind,k); + } + + static double mp_list_wh(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height; + } + + static double mp_list_whd(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth; + } + + static double mp_list_whds(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth*mp.listin[ind]._spectrum; + } + + static double mp_list_width(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width; + } + + static double mp_list_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const CImg &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double x = _mp_arg(3), y = _mp_arg(4), z = _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_list_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], oz = (int)mp.mem[_cimg_mp_slot_z]; + const CImg &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), z = oz + _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_log(_cimg_math_parser& mp) { + return std::log(_mp_arg(2)); + } + + static double mp_log10(_cimg_math_parser& mp) { + return std::log10(_mp_arg(2)); + } + + static double mp_log2(_cimg_math_parser& mp) { + return cimg::log2(_mp_arg(2)); + } + + static double mp_logical_and(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (!val_left) { mp.p_code = p_end - 1; return 0; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_logical_not(_cimg_math_parser& mp) { + return (double)!_mp_arg(2); + } + + static double mp_logical_or(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (val_left) { mp.p_code = p_end - 1; return 1; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_lowercase(_cimg_math_parser& mp) { + return cimg::lowercase(_mp_arg(2)); + } + + static double mp_lt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<_mp_arg(3)); + } + + static double mp_lte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<=_mp_arg(3)); + } + + static double mp_matrix_eig(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptr1 = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + CImg val, vec; + CImg(ptr1,k,k,1,1,true).symmetric_eigen(val,vec); + CImg(ptrd,1,k,1,1,true) = val; + CImg(ptrd + k,k,k,1,1,true) = vec.get_transpose(); + 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(); + return cimg::type::nan(); + } + + static double mp_matrix_mul(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr1,l,k,1,1,true)*CImg(ptr2,m,l,1,1,true); + 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; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg U, S, V; + CImg(ptr1,k,l,1,1,true).SVD(U,S,V); + CImg(ptrd,k,l,1,1,true) = U; + CImg(ptrd + k*l,1,k,1,1,true) = S; + CImg(ptrd + k*l + k,k,k,1,1,true) = V; + return cimg::type::nan(); + } + + static double mp_max(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i=mp.mem.width()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds variable pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %u).", + mp.imgin.pixel_type(),siz,inc,off,eoff,mp.mem._width - 1); + return &mp.mem[off]; + } + + static float* _mp_memcopy_float(_cimg_math_parser& mp, const ulongT *const p_ref, + const longT siz, const long inc) { + const unsigned ind = (unsigned int)p_ref[1]; + const CImg &img = ind==~0U?mp.imgin:mp.listin[cimg::mod((int)mp.mem[ind],mp.listin.width())]; + const bool is_relative = (bool)p_ref[2]; + int ox, oy, oz, oc; + longT off = 0; + if (is_relative) { + ox = (int)mp.mem[_cimg_mp_slot_x]; + oy = (int)mp.mem[_cimg_mp_slot_y]; + oz = (int)mp.mem[_cimg_mp_slot_z]; + oc = (int)mp.mem[_cimg_mp_slot_c]; + off = img.offset(ox,oy,oz,oc); + } + if ((*p_ref)%2) { + const int + x = (int)mp.mem[p_ref[3]], + y = (int)mp.mem[p_ref[4]], + z = (int)mp.mem[p_ref[5]], + c = *p_ref==5?0:(int)mp.mem[p_ref[6]]; + off+=img.offset(x,y,z,c); + } else off+=(longT)mp.mem[p_ref[3]]; + const longT eoff = off + (siz - 1)*inc; + if (off<0 || eoff>=(longT)img.size()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds image pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %lu).", + mp.imgin.pixel_type(),siz,inc,off,eoff,img.size() - 1); + return (float*)&img[off]; + } + + static double mp_memcopy(_cimg_math_parser& mp) { + longT siz = (longT)_mp_arg(4); + const longT inc_d = (longT)_mp_arg(5), inc_s = (longT)_mp_arg(6); + const float + _opacity = (float)_mp_arg(7), + opacity = (float)cimg::abs(_opacity), + omopacity = 1 - std::max(_opacity,0.f); + if (siz>0) { + const bool + is_doubled = mp.opcode[8]<=1, + is_doubles = mp.opcode[15]<=1; + if (is_doubled && is_doubles) { // (double*) <- (double*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(double)); + else std::memmove(ptrd,ptrs,siz*sizeof(double)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,double) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } else if (is_doubled && !is_doubles) { // (double*) <- (float*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + _opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else if (!is_doubled && is_doubles) { // (float*) <- (double*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = (float)*ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = (float)(omopacity**ptrd + opacity**ptrs); ptrd+=inc_d; ptrs+=inc_s; } + } else { // (float*) <- (float*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(float)); + else std::memmove(ptrd,ptrs,siz*sizeof(float)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,float) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } + } + return _mp_arg(1); + } + + static double mp_min(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i vals(i_end - 3); + double *p = vals.data(); + 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.; + } + + static double mp_permutations(_cimg_math_parser& mp) { + return cimg::permutations((int)_mp_arg(2),(int)_mp_arg(3),(bool)_mp_arg(4)); + } + + 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 (!is_invalid_arguments) { + const int nbv = (int)_mp_arg(4); + if (nbv<=0) is_invalid_arguments = true; + else { + CImg points(nbv,2,1,1,0); + CImg color(img._spectrum,1,1,1,0); + float opacity = 1; + unsigned int i = 5; + cimg_foroff(points,k) if (i args(i_end - 4); + cimg_forX(args,k) args[k] = _mp_arg(4 + k); + if (ind==~0U) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_pow(_cimg_math_parser& mp) { + const double v = _mp_arg(2), p = _mp_arg(3); + return std::pow(v,p); + } + + static double mp_pow0_25(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return std::sqrt(std::sqrt(val)); + } + + static double mp_pow3(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val; + } + + static double mp_pow4(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val*val; + } + + static double mp_print(_cimg_math_parser& mp) { + const double val = _mp_arg(1); + const bool print_char = (bool)mp.opcode[3]; + cimg_pragma_openmp(critical(mp_print)) + { + 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::mutex(6); + if (print_char) + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g = '%c'",expr._data,val,(int)val); + else + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g",expr._data,val); + std::fflush(cimg::output()); + cimg::mutex(6,0); + } + return val; + } + + static double mp_prod(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i::nan(); + } + + static double mp_rot3d(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const float x = (float)_mp_arg(2), y = (float)_mp_arg(3), z = (float)_mp_arg(4), theta = (float)_mp_arg(5); + CImg(ptrd,3,3,1,1,true) = CImg::rotation_matrix(x,y,z,theta); + return cimg::type::nan(); + } + + static double mp_round(_cimg_math_parser& mp) { + return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4)); + } + + static double mp_self_add(_cimg_math_parser& mp) { + return _mp_arg(1)+=_mp_arg(2); + } + + static double mp_self_bitwise_and(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val & (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_left_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val<<(unsigned int)_mp_arg(2)); + } + + static double mp_self_bitwise_or(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val | (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_right_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val>>(unsigned int)_mp_arg(2)); + } + + static double mp_self_decrement(_cimg_math_parser& mp) { + return --_mp_arg(1); + } + + static double mp_self_increment(_cimg_math_parser& mp) { + return ++_mp_arg(1); + } + + static double mp_self_map_vector_s(_cimg_math_parser& mp) { // Vector += scalar + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2]; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode[2] = mp.opcode[4]; // Scalar argument + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1]; + while (siz-->0) { target = ptrd++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_map_vector_v(_cimg_math_parser& mp) { // Vector += vector + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,4); + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1], &argument = mp.opcode[2]; + while (siz-->0) { target = ptrd++; argument = ptrs++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_mul(_cimg_math_parser& mp) { + return _mp_arg(1)*=_mp_arg(2); + } + + static double mp_self_div(_cimg_math_parser& mp) { + return _mp_arg(1)/=_mp_arg(2); + } + + static double mp_self_modulo(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = cimg::mod(val,_mp_arg(2)); + } + + static double mp_self_pow(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = std::pow(val,_mp_arg(2)); + } + + static double mp_self_sub(_cimg_math_parser& mp) { + return _mp_arg(1)-=_mp_arg(2); + } + + static double mp_set_ioff(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + x = (int)_mp_arg(2), y = (int)_mp_arg(3), + z = (int)_mp_arg(4), c = (int)_mp_arg(5); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(2)), y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)), c = (int)(oc + _mp_arg(5)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Ixyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_set_Joff_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Jxyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_shift(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const unsigned int siz = (unsigned int)mp.opcode[3]; + const int + shift = (int)_mp_arg(4), + boundary_conditions = (int)_mp_arg(5); + CImg(ptrd,siz,1,1,1,true) = CImg(ptrs,siz,1,1,1,true).shift(shift,0,0,0,boundary_conditions); + return cimg::type::nan(); + } + + static double mp_sign(_cimg_math_parser& mp) { + return cimg::sign(_mp_arg(2)); + } + + static double mp_sin(_cimg_math_parser& mp) { + return std::sin(_mp_arg(2)); + } + + static double mp_sinc(_cimg_math_parser& mp) { + return cimg::sinc(_mp_arg(2)); + } + + static double mp_sinh(_cimg_math_parser& mp) { + return std::sinh(_mp_arg(2)); + } + + static double mp_solve(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr2,m,l,1,1,true).get_solve(CImg(ptr1,k,l,1,1,true)); + 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 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); + return cimg::type::nan(); + } + + static double mp_sqr(_cimg_math_parser& mp) { + return cimg::sqr(_mp_arg(2)); + } + + static double mp_sqrt(_cimg_math_parser& mp) { + return std::sqrt(_mp_arg(2)); + } + + static double mp_srand(_cimg_math_parser& mp) { + mp.rng = (ulongT)_mp_arg(2); + return cimg::type::nan(); + } + + static double mp_srand0(_cimg_math_parser& mp) { + cimg::srand(&mp.rng); +#ifdef cimg_use_openmp + 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; i0) mp.mem[ptrd++] = (double)*(ptrs++); + return cimg::type::nan(); + } + + static double mp_stov(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)_mp_arg(4); + const bool is_strict = (bool)_mp_arg(5); + double val = cimg::type::nan(); + if (ind<0 || ind>=(longT)siz) return val; + 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; + + int err = cimg_sscanf(ss,"%lf%c",&val,&sep); +#if cimg_OS==2 + // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able + // to read those particular values. + if (!err && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) { + bool is_positive = true; + const char *s = ss; + if (*s=='+') ++s; else if (*s=='-') { ++s; is_positive = false; } + if (!cimg::strcasecmp(s,"inf")) { val = cimg::type::inf(); err = 1; } + else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); err = 1; } + if (err==1 && !is_positive) val = -val; + } +#endif + if (is_strict && err!=1) return cimg::type::nan(); + return val; + } + + static double mp_sub(_cimg_math_parser& mp) { + return _mp_arg(2) - _mp_arg(3); + } + + static double mp_sum(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i(ptrs,k,k,1,1,true).trace(); + } + + static double mp_transp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg(ptrd,l,k,1,1,true) = CImg(ptrs,k,l,1,1,true).get_transpose(); + return cimg::type::nan(); + } + + static double mp_u(_cimg_math_parser& mp) { + return cimg::rand(_mp_arg(2),_mp_arg(3),&mp.rng); + } + + static double mp_uppercase(_cimg_math_parser& mp) { + return cimg::uppercase(_mp_arg(2)); + } + + 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; i::nan(); + } + + static double mp_vector_crop(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const longT + length = (longT)mp.opcode[3], + start = (longT)_mp_arg(4), + sublength = (longT)mp.opcode[5]; + if (start<0 || start + sublength>length) + throw CImgArgumentException("[" cimg_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)); + return cimg::type::nan(); + } + + static double mp_vector_init(_cimg_math_parser& mp) { + unsigned int + ptrs = 4U, + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[3]; + switch (mp.opcode[2] - 4) { + case 0 : std::memset(mp.mem._data + ptrd,0,siz*sizeof(double)); break; // 0 values given + case 1 : { const double val = _mp_arg(ptrs); while (siz-->0) mp.mem[ptrd++] = val; } break; + default : while (siz-->0) { mp.mem[ptrd++] = _mp_arg(ptrs++); if (ptrs>=mp.opcode[2]) ptrs = 4U; } + } + return cimg::type::nan(); + } + + static double mp_vector_eq(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(4) + 1; + unsigned int p1 = (unsigned int)mp.opcode[3], p2 = (unsigned int)mp.opcode[5], n; + const int N = (int)_mp_arg(6); + const bool case_sensitive = (bool)_mp_arg(7); + bool still_equal = true; + double value; + if (!N) return true; + + // Compare all values. + if (N<0) { + if (p1>0 && p2>0) { // Vector == vector + if (p1!=p2) return false; + if (case_sensitive) + while (still_equal && p1--) still_equal = *(ptr1++)==*(ptr2++); + else + while (still_equal && p1--) + still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p1--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p2--) still_equal = *(ptr2++)==value; + return still_equal; + } else { // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + else return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + } + + // Compare only first N values. + if (p1>0 && p2>0) { // Vector == vector + n = cimg::min((unsigned int)N,p1,p2); + if (case_sensitive) + while (still_equal && n--) still_equal = *(ptr1++)==(*ptr2++); + else + while (still_equal && n--) still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + n = std::min((unsigned int)N,p1); + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + n = std::min((unsigned int)N,p2); + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr2++)==value; + return still_equal; + } // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + + static double mp_vector_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + return off>=0 && off<(int)siz?mp.mem[ptr + off]:cimg::type::nan(); + } + + static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[5] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(4); + l_opcode[2] = mp.opcode[4]; // Scalar argument1 + l_opcode.swap(mp.opcode); + ulongT &argument2 = mp.opcode[3]; + while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_v(_cimg_math_parser& mp) { // Operator(vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode.swap(mp.opcode); + ulongT &argument = mp.opcode[2]; + while (siz-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_vs(_cimg_math_parser& mp) { // Operator(vector,scalar) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg 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); + l_opcode.swap(mp.opcode); + ulongT &argument1 = mp.opcode[2], &argument2 = mp.opcode[3]; + while (siz-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_neq(_cimg_math_parser& mp) { + return !mp_vector_eq(mp); + } + + 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); + const ulongT *ptrs = mp.opcode._data + 5; + 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); + unsigned int count = 0; + while (siz-->0) { + if (count>=64 && siz>=64) { + std::fprintf(cimg::output(),"...,"); + ptr = (unsigned int)mp.opcode[1] + 1 + siz0 - 64; + siz = 64; + } else std::fprintf(cimg::output(),"%g%s",mp.mem[ptr++],siz?",":""); + ++count; + } + if (print_string) { + CImg str(siz0 + 1); + ptr = (unsigned int)mp.opcode[1] + 1; + for (unsigned int k = 0; k::nan(); + } + + static double mp_vector_resize(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const unsigned int p1 = (unsigned int)mp.opcode[2], p2 = (unsigned int)mp.opcode[4]; + const int + interpolation = (int)_mp_arg(5), + boundary_conditions = (int)_mp_arg(6); + if (p2) { // Resize vector + const double *const ptrs = &_mp_arg(3) + 1; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p2,1,1,1,true). + get_resize(p1,1,1,1,interpolation,boundary_conditions); + } else { // Resize scalar + const double value = _mp_arg(3); + CImg(ptrd,p1,1,1,1,true) = CImg(1,1,1,1,value).resize(p1,1,1,1,interpolation, + boundary_conditions); + } + 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; + const unsigned int p1 = (unsigned int)mp.opcode[3]; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p1,1,1,1,true).get_mirror('x'); + return cimg::type::nan(); + } + + static double mp_vector_set_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + if (off>=0 && off<(int)siz) mp.mem[ptr + off] = _mp_arg(5); + return _mp_arg(5); + } + + static double mp_vtos(_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]; + 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); + } + 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); + } else { // Scalar expression + str.assign(sizd + 1); + cimg_snprintf(str,sizd + 1,format,_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_while(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_cond = ++mp.p_code, + *const p_body = p_cond + mp.opcode[3], + *const p_end = p_body + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + bool is_cond = false; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) // Evaluate body + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double x = _mp_arg(2), y = _mp_arg(3), z = _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], + oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), z = oz + _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + +#undef _mp_arg + + }; // struct _cimg_math_parser {} + +#define _cimg_create_pointwise_functions(name,func,min_size) \ + CImg& name() { \ + if (is_empty()) return *this; \ + cimg_openmp_for(*this,func((double)*ptr),min_size); \ + return *this; \ + } \ + CImg get_##name() const { \ + return CImg(*this,false).name(); \ + } + + //! Compute the square value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square value \f$I_{(x,y,z,c)}^2\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqr().normalize(0,255)).display(); + \endcode + \image html ref_sqr.jpg + **/ + _cimg_create_pointwise_functions(sqr,cimg::sqr,524288) + + //! Compute the square root of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square root \f$\sqrt{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqrt().normalize(0,255)).display(); + \endcode + \image html ref_sqrt.jpg + **/ + _cimg_create_pointwise_functions(sqrt,std::sqrt,8192) + + //! Compute the exponential of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its exponential \f$e^{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(exp,std::exp,4096) + + //! Compute the logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its logarithm + \f$\mathrm{log}_{e}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log,std::log,262144) + + //! Compute the base-2 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-2 logarithm + \f$\mathrm{log}_{2}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log2,cimg::log2,4096) + + //! Compute the base-10 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-10 logarithm + \f$\mathrm{log}_{10}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log10,std::log10,4096) + + //! Compute the absolute value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its absolute value \f$|I_{(x,y,z,c)}|\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(abs,cimg::abs,524288) + + //! Compute the sign of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sign + \f$\mathrm{sign}(I_{(x,y,z,c)})\f$. + \note + - The sign is set to: + - \c 1 if pixel value is strictly positive. + - \c -1 if pixel value is strictly negative. + - \c 0 if pixel value is equal to \c 0. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sign,cimg::sign,32768) + + //! Compute the cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its cosine \f$\cos(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cos,std::cos,8192) + + //! Compute the sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sine \f$\sin(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sin,std::sin,8192) + + //! Compute the sinc of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sinc + \f$\mathrm{sinc}(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinc,cimg::sinc,2048) + + //! Compute the tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its tangent \f$\tan(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tan,std::tan,2048) + + //! Compute the hyperbolic cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic cosine + \f$\mathrm{cosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cosh,std::cosh,2048) + + //! Compute the hyperbolic sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic sine + \f$\mathrm{sinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinh,std::sinh,2048) + + //! Compute the hyperbolic tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic tangent + \f$\mathrm{tanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tanh,std::tanh,2048) + + //! Compute the arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosine + \f$\mathrm{acos}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acos,std::acos,8192) + + //! Compute the arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arcsine + \f$\mathrm{asin}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asin,std::asin,8192) + + //! Compute the arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent + \f$\mathrm{atan}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atan,std::atan,8192) + + //! Compute the arctangent2 of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent2 + \f$\mathrm{atan2}(I_{(x,y,z,c)})\f$. + \param img Image whose pixel values specify the second argument of the \c atan2() function. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img_y,img_atan2).display(); + \endcode + **/ + template + CImg& atan2(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return atan2(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_atan2(const CImg& img) const { + return CImg(*this,false).atan2(img); + } + + //! Compute the hyperbolic arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosineh + \f$\mathrm{acosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acosh,cimg::acosh,8192) + + //! Compute the hyperbolic arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arcsine + \f$\mathrm{asinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asinh,cimg::asinh,8192) + + //! Compute the hyperbolic arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arctangent + \f$\mathrm{atanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atanh,cimg::atanh,8192) + + //! In-place pointwise multiplication. + /** + Compute the pointwise multiplication between the image instance and the specified input image \c img. + \param img Input image, as the second operand of the multiplication. + \note + - Similar to operator+=(const CImg&), except that it performs a pointwise multiplication + instead of an addition. + - It does \e not perform a \e matrix multiplication. For this purpose, use operator*=(const CImg&) instead. + \par Example + \code + CImg + img("reference.jpg"), + shade(img.width,img.height(),1,1,"-(x-w/2)^2-(y-h/2)^2",false); + shade.normalize(0,1); + (img,shade,img.get_mul(shade)).display(); + \endcode + **/ + template + CImg& mul(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return mul(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_mul(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).mul(img); + } + + //! In-place pointwise division. + /** + Similar to mul(const CImg&), except that it performs a pointwise division instead of a multiplication. + **/ + template + CImg& div(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return div(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_div(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).div(img); + } + + //! Raise each pixel value to a specified power. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its power \f$I_{(x,y,z,c)}^p\f$. + \param p Exponent value. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img1,img2).display(); + \endcode + **/ + CImg& pow(const double p) { + if (is_empty()) 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.5) return sqrt(); + if (p==1) return *this; + if (p==2) return sqr(); + 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; + } + + //! Raise each pixel value to a specified power \newinstance. + CImg get_pow(const double p) const { + return CImg(*this,false).pow(p); + } + + //! Raise each pixel value to a power, specified from an expression. + /** + Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition. + **/ + CImg& pow(const char *const expression) { + return pow((+*this)._fill(expression,true,1,0,0,"pow",this)); + } + + //! Raise each pixel value to a power, specified from an expression \newinstance. + CImg get_pow(const char *const expression) const { + return CImg(*this,false).pow(expression); + } + + //! Raise each pixel value to a power, pointwisely specified from another image. + /** + Similar to operator+=(const CImg& img), except that it performs an exponentiation instead of an addition. + **/ + template + CImg& pow(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return pow(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_pow(const CImg& img) const { + return CImg(*this,false).pow(img); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(unsigned int), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::rol(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const unsigned int n=1) const { + return (+*this).rol(n); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const char *const expression) { + return rol((+*this)._fill(expression,true,1,0,0,"rol",this)); + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const char *const expression) const { + return (+*this).rol(expression); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const CImg&), except that it performs a left rotation instead of a left shift. + **/ + template + CImg& rol(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return rol(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_rol(const CImg& img) const { + return (+*this).rol(img); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(unsigned int), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::ror(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const unsigned int n=1) const { + return (+*this).ror(n); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const char *const expression) { + return ror((+*this)._fill(expression,true,1,0,0,"ror",this)); + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const char *const expression) const { + return (+*this).ror(expression); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const CImg&), except that it performs a right rotation instead of a right shift. + **/ + template + CImg& ror(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return ror(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_ror(const CImg& img) const { + return (+*this).ror(img); + } + + //! Pointwise min operator between instance image and a value. + /** + \param val Value used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& min(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).min(value); + } + + //! Pointwise min operator between two images. + /** + \param img Image used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& min(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return min(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_min(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).min(img); + } + + //! Pointwise min operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& min(const char *const expression) { + return min((+*this)._fill(expression,true,1,0,0,"min",this)); + } + + //! Pointwise min operator between an image and an expression \newinstance. + CImg get_min(const char *const expression) const { + return CImg(*this,false).min(expression); + } + + //! Pointwise max operator between instance image and a value. + /** + \param val Value used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& max(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).max(value); + } + + //! Pointwise max operator between two images. + /** + \param img Image used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& max(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return max(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_max(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).max(img); + } + + //! Pointwise max operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& max(const char *const expression) { + return max((+*this)._fill(expression,true,1,0,0,"max",this)); + } + + //! Pointwise max operator between an image and an expression \newinstance. + CImg get_max(const char *const expression) const { + return CImg(*this,false).max(expression); + } + + //! Return a reference to the minimum pixel value. + /** + **/ + T& min() { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min; + cimg_for(*this,ptrs,T) if (*ptrsmax_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the maximum pixel value \const. + const T& max() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max; + cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value. + /** + \param[out] max_val Maximum pixel value. + **/ + template + T& min_max(t& max_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value \const. + template + const T& min_max(t& max_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + const T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the maximum pixel value as well as the minimum pixel value. + /** + \param[out] min_val Minimum pixel value. + **/ + template + T& max_min(t& min_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val arr(*this,false); + ulongT l = 0, ir = size() - 1; + for ( ; ; ) { + if (ir<=l + 1) { + if (ir==l + 1 && arr[ir]>1; + cimg::swap(arr[mid],arr[l + 1]); + if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]); + if (arr[l + 1]>arr[ir]) cimg::swap(arr[l + 1],arr[ir]); + if (arr[l]>arr[l + 1]) cimg::swap(arr[l],arr[l + 1]); + ulongT i = l + 1, j = ir; + const T pivot = arr[l + 1]; + for ( ; ; ) { + do ++i; while (arr[i]pivot); + if (j=k) ir = j - 1; + if (j<=k) l = i; + } + } + } + + //! Return the median pixel value. + /** + **/ + T median() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "median(): Empty instance.", + cimg_instance); + const ulongT s = size(); + switch (s) { + case 1 : return _data[0]; + case 2 : return cimg::median(_data[0],_data[1]); + case 3 : return cimg::median(_data[0],_data[1],_data[2]); + case 5 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4]); + case 7 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6]); + case 9 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8]); + case 13 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8], + _data[9],_data[10],_data[11],_data[12]); + } + const T res = kth_smallest(s>>1); + return (s%2)?res:(T)((res + kth_smallest((s>>1) - 1))/2); + } + + //! Return the product of all the pixel values. + /** + **/ + double product() const { + if (is_empty()) return 0; + double res = 1; + cimg_for(*this,ptrs,T) res*=(double)*ptrs; + return res; + } + + //! Return the sum of all the pixel values. + /** + **/ + double sum() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res; + } + + //! Return the average pixel value. + /** + **/ + double mean() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res/size(); + } + + //! Return the variance of the pixel values. + /** + \param variance_method Method used to estimate the variance. Can be: + - \c 0: Second moment, computed as + \f$1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2 = + 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right)\f$ + with \f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$. + - \c 1: Best unbiased estimator, computed as \f$\frac{1}{N - 1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 \f$. + - \c 2: Least median of squares. + - \c 3: Least trimmed of squares. + **/ + double variance(const unsigned int variance_method=1) const { + double foo; + return variance_mean(variance_method,foo); + } + + //! Return the variance as well as the average of the pixel values. + /** + \param variance_method Method used to estimate the variance (see variance(const unsigned int) const). + \param[out] mean Average pixel value. + **/ + template + double variance_mean(const unsigned int variance_method, t& mean) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_mean(): Empty instance.", + cimg_instance); + + double variance = 0, average = 0; + const ulongT siz = size(); + switch (variance_method) { + case 0 : { // Least mean square (standard definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = (S2 - S*S/siz)/siz; + average = S; + } break; + case 1 : { // Least mean square (robust definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + average = S; + } break; + case 2 : { // Least Median of Squares (MAD) + CImg buf(*this,false); + buf.sort(); + const ulongT siz2 = siz>>1; + const double med_i = (double)buf[siz2]; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val; + } + buf.sort(); + const double sig = (double)(1.4828*buf[siz2]); + variance = sig*sig; + } break; + default : { // Least trimmed of Squares + CImg buf(*this,false); + const ulongT siz2 = siz>>1; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val; + } + buf.sort(); + double a = 0; + const Tfloat *ptrs = buf._data; + for (ulongT j = 0; j0?variance:0; + } + + //! Return estimated variance of the noise. + /** + \param variance_method Method used to compute the variance (see variance(const unsigned int) const). + \note Because of structures such as edges in images it is + recommanded to use a robust variance estimation. The variance of the + noise is estimated by computing the variance of the Laplacian \f$(\Delta + I)^2 \f$ scaled by a factor \f$c\f$ insuring \f$ c E[(\Delta I)^2]= + \sigma^2\f$ where \f$\sigma\f$ is the noise variance. + **/ + double variance_noise(const unsigned int variance_method=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_noise(): Empty instance.", + cimg_instance); + + const ulongT siz = size(); + if (!siz || !_data) return 0; + if (variance_method>1) { // Compute a scaled version of the Laplacian + CImg tmp(*this,false); + if (_depth==1) { + 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) { + tmp(x,y,c) = cste*((double)Inc + (double)Ipc + (double)Icn + + (double)Icp - 4*(double)Icc); + } + } + } else { + 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) { + tmp(x,y,z,c) = cste*( + (double)Incc + (double)Ipcc + (double)Icnc + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + } + } + } + return tmp.variance(variance_method); + } + + // Version that doesn't need intermediate images. + double variance = 0, S = 0, S2 = 0; + if (_depth==1) { + const double cste = 1./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 + + (double)Icn + (double)Icp - 4*(double)Icc); + S+=val; S2+=val*val; + } + } else { + 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 * + ((double)Incc + (double)Ipcc + (double)Icnc + + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + S+=val; S2+=val*val; + } + } + if (variance_method) variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + else variance = (S2 - S*S/siz)/siz; + return variance>0?variance:0; + } + + //! Compute the MSE (Mean-Squared Error) between two images. + /** + \param img Image used as the second argument of the MSE operator. + **/ + template + double MSE(const CImg& img) const { + if (img.size()!=size()) + throw CImgArgumentException(_cimg_instance + "MSE(): Instance and specified image (%u,%u,%u,%u,%p) have different dimensions.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + double vMSE = 0; + const t* ptr2 = img._data; + cimg_for(*this,ptr1,T) { + const double diff = (double)*ptr1 - (double)*(ptr2++); + vMSE+=diff*diff; + } + const ulongT siz = img.size(); + if (siz) vMSE/=siz; + return vMSE; + } + + //! Compute the PSNR (Peak Signal-to-Noise Ratio) between two images. + /** + \param img Image used as the second argument of the PSNR operator. + \param max_value Maximum theoretical value of the signal. + **/ + template + double PSNR(const CImg& img, const double max_value=255) const { + const double vMSE = (double)std::sqrt(MSE(img)); + return (vMSE!=0)?(double)(20*std::log10(max_value/vMSE)):(double)(cimg::type::max()); + } + + //! Evaluate math formula. + /** + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. + **/ + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { + return _eval(this,expression,x,y,z,c,list_inputs,list_outputs); + } + + //! Evaluate math formula \const. + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { + return _eval(0,expression,x,y,z,c,list_inputs,list_outputs); + } + + double _eval(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) return 0; + if (!expression[1]) switch (*expression) { // Single-char optimization + case 'w' : return (double)_width; + case 'h' : return (double)_height; + case 'd' : return (double)_depth; + case 's' : return (double)_spectrum; + case 'r' : return (double)_is_shared; + } + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + const double val = mp(x,y,z,c); + mp.end(); + return val; + } + + //! Evaluate math formula. + /** + \param[out] output Contains values of output vector returned by the evaluated expression + (or is empty if the returned type is scalar). + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output 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); + } + + //! 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); + } + + 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=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + output.assign(1,std::max(1U,mp.result_dim)); + mp(x,y,z,c,output._data); + mp.end(); + } + + //! Evaluate math formula on a set of variables. + /** + \param expression Math formula, as a C-string. + \param xyzc Set of values (x,y,z,c) used for the evaluation. + \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. + **/ + 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); + } + + //! 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); + } + + 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 { + 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_pragma_openmp(parallel if (res._height>=512)) + { + _cimg_math_parser + _mp = omp_get_thread_num()?mp:_cimg_math_parser(), + &lmp = omp_get_thread_num()?_mp:mp; + cimg_pragma_openmp(for) + for (unsigned int i = 0; i[min, max, mean, variance, xmin, ymin, zmin, cmin, xmax, ymax, zmax, cmax, sum, product]. + **/ + CImg get_stats(const unsigned int variance_method=1) const { + if (is_empty()) return CImg(); + const ulongT siz = size(); + const longT off_end = (longT)siz; + double S = 0, S2 = 0, P = 1; + longT offm = 0, offM = 0; + T m = *_data, M = m; + + 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) + for (longT off = 0; offlM) { lM = val; loffM = off; } + S+=_val; + S2+=_val*_val; + P*=_val; + } + cimg_pragma_openmp(critical(get_stats)) { + if (lmM || (lM==M && loffM1?(S2 - S*S/siz)/(siz - 1):0): + variance(variance_method)), + variance_value = _variance_value>0?_variance_value:0; + int + xm = 0, ym = 0, zm = 0, cm = 0, + xM = 0, yM = 0, zM = 0, cM = 0; + contains(_data[offm],xm,ym,zm,cm); + contains(_data[offM],xM,yM,zM,cM); + return CImg(1,14).fill((double)m,(double)M,mean_value,variance_value, + (double)xm,(double)ym,(double)zm,(double)cm, + (double)xM,(double)yM,(double)zM,(double)cM, + S,P); + } + + //! Compute statistics vector from the pixel values \inplace. + CImg& stats(const unsigned int variance_method=1) { + return get_stats(variance_method).move_to(*this); + } + + //@} + //------------------------------------- + // + //! \name Vector / Matrix Operations + //@{ + //------------------------------------- + + //! Compute norm of the image, viewed as a matrix. + /** + \param magnitude_type Norm type. Can be: + - \c -1: Linf-norm + - \c 0: L0-norm + - \c 1: L1-norm + - \c 2: L2-norm + **/ + double magnitude(const int magnitude_type=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "magnitude(): Empty instance.", + cimg_instance); + double res = 0; + switch (magnitude_type) { + case -1 : { + cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } + } break; + case 1 : { + cimg_for(*this,ptrs,T) res+=(double)cimg::abs(*ptrs); + } break; + default : { + cimg_for(*this,ptrs,T) res+=(double)cimg::sqr(*ptrs); + res = (double)std::sqrt(res); + } + } + return res; + } + + //! Compute the trace of the image, viewed as a matrix. + /** + **/ + double trace() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "trace(): Empty instance.", + cimg_instance); + double res = 0; + cimg_forX(*this,k) res+=(double)(*this)(k,k); + return res; + } + + //! Compute the determinant of the image, viewed as a matrix. + /** + **/ + double det() const { + if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "det(): Instance is not a square matrix.", + cimg_instance); + + switch (_width) { + case 1 : return (double)((*this)(0,0)); + case 2 : return (double)((*this)(0,0))*(double)((*this)(1,1)) - (double)((*this)(0,1))*(double)((*this)(1,0)); + case 3 : { + const double + a = (double)_data[0], d = (double)_data[1], g = (double)_data[2], + b = (double)_data[3], e = (double)_data[4], h = (double)_data[5], + c = (double)_data[6], f = (double)_data[7], i = (double)_data[8]; + return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e; + } + default : { + CImg lu(*this,false); + CImg indx; + bool d; + lu._LU(indx,d); + double res = d?(double)1:(double)-1; + cimg_forX(lu,i) res*=lu(i,i); + return res; + } + } + } + + //! Compute the dot product between instance and argument, viewed as matrices. + /** + \param img Image used as a second argument of the dot product. + **/ + template + 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 get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + CImg res; + if (res._height!=_spectrum) res.assign(1,_spectrum); + const ulongT whd = (ulongT)_width*_height*_depth; + const T *ptrs = data(x,y,z); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get (square) matrix-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \note - The spectrum() of the image must be a square. + **/ + CImg get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const { + const int n = (int)cimg::round(std::sqrt((double)_spectrum)); + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(n,n); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get tensor-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + CImg get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + if (_spectrum==6) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd),*(ptrs + 3*whd),*(ptrs + 4*whd),*(ptrs + 5*whd)); + if (_spectrum==3) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd)); + return tensor(*ptrs); + } + + //! Set vector-valued pixel at specified position. + /** + \param vec Vector to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_vector_at(const CImg& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) { + if (x<_width && y<_height && z<_depth) { + const t *ptrs = vec._data; + const ulongT whd = (ulongT)_width*_height*_depth; + T *ptrd = data(x,y,z); + for (unsigned int k = std::min((unsigned int)vec.size(),_spectrum); k; --k) { + *ptrd = (T)*(ptrs++); ptrd+=whd; + } + } + return *this; + } + + //! Set (square) matrix-valued pixel at specified position. + /** + \param mat Matrix to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_matrix_at(const CImg& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + return set_vector_at(mat,x,y,z); + } + + //! Set tensor-valued pixel at specified position. + /** + \param ten Tensor to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_tensor_at(const CImg& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + T *ptrd = data(x,y,z,0); + const ulongT siz = (ulongT)_width*_height*_depth; + if (ten._height==2) { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[3]; + } + else { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[2]; ptrd+=siz; + *ptrd = (T)ten[4]; ptrd+=siz; + *ptrd = (T)ten[5]; ptrd+=siz; + *ptrd = (T)ten[8]; + } + return *this; + } + + //! Unroll pixel values along axis \c y. + /** + \note Equivalent to \code unroll('y'); \endcode. + **/ + CImg& 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. + **/ + CImg& diagonal() { + return get_diagonal().move_to(*this); + } + + //! Resize image to become a diagonal matrix \newinstance. + CImg get_diagonal() const { + if (is_empty()) return *this; + const unsigned int siz = (unsigned int)size(); + CImg res(siz,siz,1,1,0); + cimg_foroff(*this,off) res((unsigned int)off,(unsigned int)off) = (*this)[off]; + return res; + } + + //! Replace the image by an identity matrix. + /** + \note If the instance image is not square, it is resized to a square matrix using its maximum + dimension as a reference. + **/ + CImg& identity_matrix() { + return identity_matrix(std::max(_width,_height)).move_to(*this); + } + + //! Replace the image by an identity matrix \newinstance. + CImg get_identity_matrix() const { + return identity_matrix(std::max(_width,_height)); + } + + //! Fill image with a linear sequence of values. + /** + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + CImg& sequence(const T& a0, const T& a1) { + if (is_empty()) return *this; + const ulongT siz = size() - 1; + T* ptr = _data; + if (siz) { + const double delta = (double)a1 - (double)a0; + cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz); + } else *ptr = a0; + return *this; + } + + //! Fill image with a linear sequence of values \newinstance. + CImg get_sequence(const T& a0, const T& a1) const { + return (+*this).sequence(a0,a1); + } + + //! Transpose the image, viewed as a matrix. + /** + \note Equivalent to \code permute_axes("yxzc"); \endcode. + **/ + CImg& transpose() { + if (_width==1) { _width = _height; _height = 1; return *this; } + if (_height==1) { _height = _width; _width = 1; return *this; } + if (_width==_height) { + cimg_forYZC(*this,y,z,c) for (int x = y; x get_transpose() const { + return get_permute_axes("yxzc"); + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors. + /** + \param img Image used as the second argument of the cross product. + \note The first argument of the cross product is \c *this. + **/ + template + 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.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + + const T x = (*this)[0], y = (*this)[1], z = (*this)[2]; + (*this)[0] = (T)(y*img[2] - z*img[1]); + (*this)[1] = (T)(z*img[0] - x*img[2]); + (*this)[2] = (T)(x*img[1] - y*img[0]); + return *this; + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors \newinstance. + template + CImg<_cimg_Tt> get_cross(const CImg& img) const { + return CImg<_cimg_Tt>(*this).cross(img); + } + + //! Invert the instance image, viewed as a matrix. + /** + \param use_LU Choose the inverting algorithm. Can be: + - \c true: LU-based matrix inversion. + - \c false: SVD-based matrix inversion. + **/ + CImg& invert(const bool use_LU=true) { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "invert(): Instance is not a square matrix.", + cimg_instance); +#ifdef cimg_use_lapack + int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N]; + Tfloat + *const lapA = new Tfloat[N*N], + *const WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N + l] = (Tfloat)((*this)(k,l)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + else { + cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetri_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N + l]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] WORK; +#else + const double dete = _width>3?-1.: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. && _width==3) { + const double + a = _data[0], d = _data[1], g = _data[2], + b = _data[3], e = _data[4], h = _data[5], + c = _data[6], f = _data[7], i = _data[8]; + _data[0] = (T)((i*e - f*h)/dete), _data[1] = (T)((g*f - i*d)/dete), _data[2] = (T)((d*h - g*e)/dete); + _data[3] = (T)((h*c - i*b)/dete), _data[4] = (T)((i*a - c*g)/dete), _data[5] = (T)((g*b - a*h)/dete); + _data[6] = (T)((b*f - e*c)/dete), _data[7] = (T)((d*c - a*f)/dete), _data[8] = (T)((a*e - d*b)/dete); + } else { + if (use_LU) { // LU-based inverse computation + CImg A(*this,false), indx, col(1,_width); + bool d; + A._LU(indx,d); + cimg_forX(*this,j) { + col.fill(0); + col(j) = 1; + col._solve(A,indx); + cimg_forX(*this,i) (*this)(j,i) = (T)col(i); + } + } else { // SVD-based inverse computation + CImg U(_width,_width), S(1,_width), V(_width,_width); + SVD(U,S,V,false); + U.transpose(); + cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k]; + S.diagonal(); + *this = V*S*U; + } + } +#endif + return *this; + } + + //! Invert the instance image, viewed as a matrix \newinstance. + CImg get_invert(const bool use_LU=true) const { + return CImg(*this,false).invert(use_LU); + } + + //! 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 { + 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(); + cimg_forX(V,x) { + const Tfloat s = S(x), invs = s>tolerance?1/s:0; + cimg_forY(V,y) V(x,y)*=invs; + } + return V*U.transpose(); + } + + //! Solve a system of linear equations. + /** + \param A Matrix of the linear system. + \note Solve \c AX=B where \c B=*this. + **/ + template + CImg& solve(const CImg& A) { + if (_depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "solve(): Instance and specified matrix (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + if (A._width==A._height) { // Classical linear system + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } +#ifdef cimg_use_lapack + char TRANS = 'N'; + int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N]; + Ttfloat + *const lapA = new Ttfloat[N*N], + *const lapB = new Ttfloat[N], + *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*N + l] = (Ttfloat)(A(k,l)); + cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + + if (!INFO) { + cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrs_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK; +#else + CImg lu(A,false); + CImg indx; + bool d; + lu._LU(indx,d); + _solve(lu,indx); +#endif + } else { // Least-square solution for non-square systems +#ifdef cimg_use_lapack + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } + char TRANS = 'N'; + int INFO, N = A._width, M = A._height, LWORK = -1, LDA = M, LDB = M, NRHS = _width; + Ttfloat WORK_QUERY; + Ttfloat + * const lapA = new Ttfloat[M*N], + * const lapB = new Ttfloat[M*NRHS]; + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, &WORK_QUERY, LWORK, INFO); + LWORK = (int) WORK_QUERY; + Ttfloat *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*M + l] = (Ttfloat)(A(k,l)); + cimg_forXY(*this,k,l) lapB[k*M + l] = (Ttfloat)((*this)(k,l)); + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, WORK, LWORK, INFO); + if (INFO != 0) + cimg::warn(_cimg_instance + "solve(): LAPACK library function sgels() returned error code %d.", + cimg_instance, + INFO); + assign(NRHS, N); + if (!INFO) + cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l]; + else + assign(A.get_pseudoinvert()*(*this)); + delete[] lapA; delete[] lapB; delete[] WORK; +#else + assign(A.get_pseudoinvert()*(*this)); +#endif + } + return *this; + } + + //! Solve a system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve(A); + } + + template + CImg& _solve(const CImg& A, const CImg& indx) { + typedef _cimg_Ttfloat Ttfloat; + const int N = (int)size(); + 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; + (*this)(i) = (T)sum; + } + for (int i = N - 1; i>=0; --i) { + sum = (*this)(i); + for (int j = i + 1; j + CImg& solve_tridiagonal(const CImg& A) { + const unsigned int siz = (unsigned int)size(); + if (A._width!=3 || A._height!=siz) + throw CImgArgumentException(_cimg_instance + "solve_tridiagonal(): Instance and tridiagonal matrix " + "(%u,%u,%u,%u,%p) have incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + const Ttfloat epsilon = 1e-4f; + CImg B = A.get_column(1), V(*this,false); + for (int i = 1; i<(int)siz; ++i) { + const Ttfloat m = A(0,i)/(B[i - 1]?B[i - 1]:epsilon); + B[i] -= m*A(2,i - 1); + V[i] -= m*V[i - 1]; + } + (*this)[siz - 1] = (T)(V[siz - 1]/(B[siz - 1]?B[siz - 1]:epsilon)); + for (int i = (int)siz - 2; i>=0; --i) (*this)[i] = (T)((V[i] - A(2,i)*(*this)[i + 1])/(B[i]?B[i]:epsilon)); + return *this; + } + + //! Solve a tridiagonal system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(A); + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + const CImg& eigen(CImg& val, CImg &vec) const { + if (is_empty()) { val.assign(); vec.assign(); } + else { + if (_width!=_height || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "eigen(): Instance is not a square matrix.", + cimg_instance); + + if (val.size()<(ulongT)_width) val.assign(1,_width); + if (vec.size()<(ulongT)_width*_width) vec.assign(_width,_width); + switch (_width) { + case 1 : { val[0] = (t)(*this)[0]; vec[0] = (t)1; } break; + case 2 : { + const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a + d; + double f = e*e - 4*(a*d - b*c); + if (f<0) + cimg::warn(_cimg_instance + "eigen(): Complex eigenvalues found.", + cimg_instance); + + f = std::sqrt(f); + const double + l1 = 0.5*(e - f), + l2 = 0.5*(e + f), + b2 = b*b, + norm1 = std::sqrt(cimg::sqr(l2 - a) + b2), + norm2 = std::sqrt(cimg::sqr(l1 - a) + b2); + val[0] = (t)l2; + val[1] = (t)l1; + if (norm1>0) { vec(0,0) = (t)(b/norm1); vec(0,1) = (t)((l2 - a)/norm1); } else { vec(0,0) = 1; vec(0,1) = 0; } + if (norm2>0) { vec(1,0) = (t)(b/norm2); vec(1,1) = (t)((l1 - a)/norm2); } else { vec(1,0) = 1; vec(1,1) = 0; } + } break; + default : + throw CImgInstanceException(_cimg_instance + "eigen(): Eigenvalues computation of general matrices is limited " + "to 2x2 matrices.", + cimg_instance); + } + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \return A list of two images [val; vec], whose meaning is similar as in eigen(CImg&,CImg&) const. + **/ + CImgList get_eigen() const { + CImgList res(2); + eigen(res[0],res[1]); + return res; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + 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); + + 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 + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \return A list of two images [val; vec], whose meaning are similar as in + symmetric_eigen(CImg&,CImg&) const. + **/ + CImgList get_symmetric_eigen() const { + CImgList res(2); + symmetric_eigen(res[0],res[1]); + return res; + } + + //! Sort pixel values and get sorting permutations. + /** + \param[out] permutations Permutation map used for the sorting. + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + **/ + template + CImg& sort(CImg& permutations, const bool is_increasing=true) { + permutations.assign(_width,_height,_depth,_spectrum); + if (is_empty()) return *this; + cimg_foroff(permutations,off) permutations[off] = (t)off; + return _quicksort(0,size() - 1,permutations,is_increasing,true); + } + + //! Sort pixel values and get sorting permutations \newinstance. + template + CImg get_sort(CImg& permutations, const bool is_increasing=true) const { + return (+*this).sort(permutations,is_increasing); + } + + //! Sort pixel values. + /** + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + \param axis Tells if the value sorting must be done along a specific axis. Can be: + - \c 0: All pixel values are sorted, independently on their initial position. + - \c 'x': Image columns are sorted, according to the first value in each column. + - \c 'y': Image rows are sorted, according to the first value in each row. + - \c 'z': Image slices are sorted, according to the first value in each slice. + - \c 'c': Image channels are sorted, according to the first value in each channel. + **/ + CImg& sort(const bool is_increasing=true, const char axis=0) { + if (is_empty()) return *this; + CImg perm; + switch (cimg::lowercase(axis)) { + case 0 : + _quicksort(0,size() - 1,perm,is_increasing,false); + break; + case 'x' : { + perm.assign(_width); + get_crop(0,0,0,0,_width - 1,0,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(perm[x],y,z,c); + } break; + case 'y' : { + perm.assign(_height); + get_crop(0,0,0,0,0,_height - 1,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,perm[y],z,c); + } break; + case 'z' : { + perm.assign(_depth); + get_crop(0,0,0,0,0,0,_depth - 1,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,perm[z],c); + } break; + case 'c' : { + perm.assign(_spectrum); + get_crop(0,0,0,0,0,0,0,_spectrum - 1).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,z,perm[c]); + } break; + default : + throw CImgArgumentException(_cimg_instance + "sort(): Invalid specified axis '%c' " + "(should be { x | y | z | c }).", + cimg_instance,axis); + } + return *this; + } + + //! Sort pixel values \newinstance. + CImg get_sort(const bool is_increasing=true, const char axis=0) const { + return (+*this).sort(is_increasing,axis); + } + + template + CImg& _quicksort(const long indm, const long indM, CImg& permutations, + const bool is_increasing, const bool is_permutations) { + if (indm(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]>(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]>(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } else { + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]<(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } + if (indM - indm>=3) { + const T pivot = (*this)[mid]; + long i = indm, j = indM; + if (is_increasing) { + do { + while ((*this)[i]pivot) --j; + if (i<=j) { + if (is_permutations) cimg::swap(permutations[i],permutations[j]); + cimg::swap((*this)[i++],(*this)[j--]); + } + } while (i<=j); + } else { + do { + while ((*this)[i]>pivot) ++i; + while ((*this)[j] A; // Input matrix (assumed to contain some values) + CImg<> U,S,V; + A.SVD(U,S,V) + \endcode + **/ + 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 { + if (is_empty()) { U.assign(); S.assign(); V.assign(); } + else { + U = *this; + if (lambda!=0) { + const unsigned int delta = std::min(U._width,U._height); + for (unsigned int i = 0; i rv1(_width); + t anorm = 0, c, f, g = 0, h, s, scale = 0; + int l = 0, nm = 0; + + cimg_forX(U,i) { + l = i + 1; rv1[i] = scale*g; g = s = scale = 0; + if (i=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; --i) { + if (i=0; --i) { + l = i + 1; g = S[i]; + for (int j = l; j=0; --k) { + for (unsigned int its = 0; its=1; --l) { + nm = l - 1; + if ((cimg::abs(rv1[l]) + anorm)==anorm) { flag = false; break; } + if ((cimg::abs(S[nm]) + anorm)==anorm) break; + } + if (flag) { + c = 0; s = 1; + for (int i = l; i<=k; ++i) { + f = s*rv1[i]; rv1[i] = c*rv1[i]; + if ((cimg::abs(f) + anorm)==anorm) break; + g = S[i]; h = cimg::_hypot(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h; + cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c + z*s; U(i,j) = z*c - y*s; } + } + } + + const t z = S[k]; + if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; } + nm = k - 1; + t x = S[l], y = S[nm]; + g = rv1[nm]; h = rv1[k]; + f = ((y - z)*(y + z)+(g - h)*(g + h))/std::max((t)1e-25,2*h*y); + g = cimg::_hypot(f,(t)1); + f = ((x - z)*(x + z)+h*((y/(f + (f>=0?g:-g))) - h))/std::max((t)1e-25,x); + c = s = 1; + for (int j = l; j<=nm; ++j) { + const int i = j + 1; + g = rv1[i]; h = s*g; g = c*g; + t y = S[i]; + t z = cimg::_hypot(f,h); + rv1[j] = z; c = f/std::max((t)1e-25,z); s = h/std::max((t)1e-25,z); + f = x*c + g*s; g = g*c - x*s; h = y*s; y*=c; + cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c + z*s; V(i,jj) = z*c - x*s; } + z = cimg::_hypot(f,h); S[j] = z; + if (z) { z = 1/std::max((t)1e-25,z); c = f*z; s = h*z; } + f = c*g + s*y; x = c*y - s*g; + cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c + z*s; U(i,jj) = z*c - y*s; } + } + rv1[l] = 0; rv1[k]=f; S[k]=x; + } + } + + if (sorting) { + CImg permutations; + CImg tmp(_width); + S.sort(permutations,false); + cimg_forY(U,k) { + cimg_forY(permutations,y) tmp(y) = U(permutations(y),k); + std::memcpy(U.data(0,k),tmp._data,sizeof(t)*_width); + } + cimg_forY(V,k) { + cimg_forY(permutations,y) tmp(y) = V(permutations(y),k); + std::memcpy(V.data(0,k),tmp._data,sizeof(t)*_width); + } + } + } + return *this; + } + + //! Compute the SVD of the instance image, viewed as a general matrix. + /** + \return A list of three images [U; S; V], whose meaning is similar as in + SVD(CImg&,CImg&,CImg&,bool,unsigned int,float) const. + **/ + CImgList get_SVD(const bool sorting=true, + const unsigned int max_iteration=40, const float lambda=0) const { + CImgList res(3); + SVD(res[0],res[1],res[2],sorting,max_iteration,lambda); + return res; + } + + // [internal] Compute the LU decomposition of a permuted matrix. + template + CImg& _LU(CImg& indx, bool& d) { + const int N = width(); + int imax = 0; + CImg vv(N); + indx.assign(N); + d = true; + cimg_forX(*this,i) { + Tfloat vmax = 0; + cimg_forX(*this,j) { + const Tfloat tmp = cimg::abs((*this)(j,i)); + if (tmp>vmax) vmax = tmp; + } + if (vmax==0) { indx.fill(0); return fill(0); } + vv[i] = 1/vmax; + } + cimg_forX(*this,j) { + for (int i = 0; i=vmax) { vmax=tmp; imax=i; } + } + if (j!=imax) { + cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j)); + d =!d; + vv[imax] = vv[j]; + } + indx[j] = (t)imax; + if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20; + if (j + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + 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 " + "than number of nodes %u.", + pixel_type(),starting_node,nb_nodes); + CImg dist(1,nb_nodes,1,1,cimg::type::max()); + dist(starting_node) = 0; + previous_node.assign(1,nb_nodes,1,1,(t)-1); + previous_node(starting_node) = (t)starting_node; + CImg Q(nb_nodes); + cimg_forX(Q,u) Q(u) = (unsigned int)u; + cimg::swap(Q(starting_node),Q(0)); + unsigned int sizeQ = nb_nodes; + while (sizeQ) { + // Update neighbors from minimal vertex + const unsigned int umin = Q(0); + if (umin==ending_node) sizeQ = 0; + else { + const T dmin = dist(umin); + const T infty = cimg::type::max(); + for (unsigned int q = 1; qdist(Q(left))) || + (rightdist(Q(right)));) { + if (right + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + const unsigned int starting_node, const unsigned int ending_node=~0U) { + CImg foo; + return dijkstra(distance,nb_nodes,starting_node,ending_node,foo); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + /** + \param starting_node Indice of the starting node. + \param ending_node Indice of the ending node. + \param previous_node Array that gives the previous node indice in the path to the starting node + (optional parameter). + \return Array of distances of each node to the starting node. + \note image instance corresponds to the adjacency matrix of the graph. + **/ + template + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) { + return get_dijkstra(starting_node,ending_node,previous_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + template + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) const { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "dijkstra(): Instance is not a graph adjacency matrix.", + cimg_instance); + + return dijkstra(*this,_width,starting_node,ending_node,previous_node); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) { + return get_dijkstra(starting_node,ending_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const { + CImg foo; + return get_dijkstra(starting_node,ending_node,foo); + } + + //! Return an image containing the Ascii codes of the specified string. + /** + \param str input C-string to encode as an image. + \param is_last_zero Tells if the ending \c '0' character appear in the resulting image. + \param is_shared Return result that shares its buffer with \p str. + **/ + static CImg string(const char *const str, const bool is_last_zero=true, const bool is_shared=false) { + if (!str) return CImg(); + 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 vector(const T& a0) { + CImg r(1,1); + r[0] = a0; + return r; + } + + //! Return a \c 1x2 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + **/ + static CImg vector(const T& a0, const T& a1) { + CImg r(1,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + return r; + } + + //! Return a \c 1x3 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + **/ + static CImg vector(const T& a0, const T& a1, const T& a2) { + CImg r(1,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + return r; + } + + //! Return a \c 1x4 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + \param a3 Fourth vector value. + **/ + static CImg 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; + 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; + 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; + 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; + 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; + 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; + return r; + } + + //! Return a \c 1x10 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) { + 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; + return r; + } + + //! Return a \c 1x11 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) { + 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; + return r; + } + + //! Return a \c 1x12 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) { + 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; + return r; + } + + //! Return a \c 1x13 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) { + 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; + return r; + } + + //! Return a \c 1x14 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) { + 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; + return r; + } + + //! Return a \c 1x15 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) { + 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; + return r; + } + + //! Return a \c 1x16 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) { + 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; + return r; + } + + //! Return a 1x1 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg matrix(const T& a0) { + return vector(a0); + } + + //! Return a 2x2 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, + const T& a2, const T& a3) { + CImg r(2,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + *(ptr++) = a2; *(ptr++) = a3; + return r; + } + + //! Return a 3x3 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + \param a4 Fifth matrix value. + \param a5 Sixth matrix value. + \param a6 Seventh matrix value. + \param a7 Eighth matrix value. + \param a8 Nineth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, const T& a2, + const T& a3, const T& a4, const T& a5, + const T& a6, const T& a7, const T& a8) { + CImg r(3,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; + *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; + return r; + } + + //! Return a 4x4 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13, const T& a14, const T& a15) { + CImg r(4,4); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15; + return r; + } + + //! Return a 5x5 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, + const T& a5, const T& a6, const T& a7, const T& a8, const T& a9, + const T& a10, const T& a11, const T& a12, const T& a13, const T& a14, + const T& a15, const T& a16, const T& a17, const T& a18, const T& a19, + const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) { + CImg r(5,5); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; + *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9; + *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; + *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19; + *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24; + return r; + } + + //! Return a 1x1 symmetric matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg tensor(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 symmetric matrix tensor containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2) { + return matrix(a0,a1,a1,a2); + } + + //! Return a 3x3 symmetric matrix containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { + return matrix(a0,a1,a2,a1,a3,a4,a2,a4,a5); + } + + //! Return a 1x1 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1) { + return matrix(a0,0,0,a1); + } + + //! Return a 3x3 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2) { + return matrix(a0,0,0,0,a1,0,0,0,a2); + } + + //! Return a 4x4 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3) { + return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3); + } + + //! Return a 5x5 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { + return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4); + } + + //! Return a NxN identity matrix. + /** + \param N Dimension of the matrix. + **/ + static CImg identity_matrix(const unsigned int N) { + CImg res(N,N,1,1,0); + cimg_forX(res,x) res(x,x) = 1; + return res; + } + + //! Return a N-numbered sequence vector from \p a0 to \p a1. + /** + \param N Size of the resulting vector. + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + static CImg sequence(const unsigned int N, const T& a0, const T& a1) { + if (N) return CImg(1,N).sequence(a0,a1); + return CImg(); + } + + //! Return a 3x3 rotation matrix from an { axis + angle } or a quaternion. + /** + \param x X-coordinate of the rotation axis, or first quaternion coordinate. + \param y Y-coordinate of the rotation axis, or second quaternion coordinate. + \param z Z-coordinate of the rotation axis, or third quaternion coordinate. + \param w Angle of the rotation axis (in degree), or fourth quaternion coordinate. + \param is_quaternion Tell is the four arguments denotes a set { axis + angle } or a quaternion (x,y,z,w). + **/ + static CImg rotation_matrix(const float x, const float y, const float z, const float w, + const bool is_quaternion=false) { + double X, Y, Z, W, N; + if (is_quaternion) { + N = std::sqrt((double)x*x + (double)y*y + (double)z*z + (double)w*w); + if (N>0) { X = x/N; Y = y/N; Z = z/N; W = w/N; } + else { X = Y = Z = 0; W = 1; } + return CImg::matrix((T)(X*X + Y*Y - Z*Z - W*W),(T)(2*Y*Z - 2*X*W),(T)(2*X*Z + 2*Y*W), + (T)(2*X*W + 2*Y*Z),(T)(X*X - Y*Y + Z*Z - W*W),(T)(2*Z*W - 2*X*Y), + (T)(2*Y*W - 2*X*Z),(T)(2*X*Y + 2*Z*W),(T)(X*X - Y*Y - Z*Z + W*W)); + } + N = cimg::hypot((double)x,(double)y,(double)z); + if (N>0) { X = x/N; Y = y/N; Z = z/N; } + else { X = Y = 0; Z = 1; } + const double ang = w*cimg::PI/180, c = std::cos(ang), omc = 1 - c, s = std::sin(ang); + return CImg::matrix((T)(X*X*omc + c),(T)(X*Y*omc - Z*s),(T)(X*Z*omc + Y*s), + (T)(X*Y*omc + Z*s),(T)(Y*Y*omc + c),(T)(Y*Z*omc - X*s), + (T)(X*Z*omc - Y*s),(T)(Y*Z*omc + X*s),(T)(Z*Z*omc + c)); + } + + //@} + //----------------------------------- + // + //! \name Value Manipulation + //@{ + //----------------------------------- + + //! Fill all pixel values with specified value. + /** + \param val Fill value. + **/ + CImg& fill(const T& val) { + if (is_empty()) return *this; + if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val; + else std::memset(_data,(int)(ulongT)val,sizeof(T)*size()); // Double cast to allow val to be (void*) + return *this; + } + + //! Fill all pixel values with specified value \newinstance. + CImg get_fill(const T& val) const { + return CImg(_width,_height,_depth,_spectrum).fill(val); + } + + //! Fill sequentially all pixel values with specified values. + /** + \param val0 First fill value. + \param val1 Second fill value. + **/ + CImg& fill(const T& val0, const T& val1) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 1; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 2; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 3; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 4; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 5; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 6; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 7; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 8; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 9; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 10; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 11; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 12; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 13; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 14; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 15; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14,val15); + } + + //! Fill sequentially pixel values according to a given expression. + /** + \param expression C-string describing a math formula, or a sequence of values. + \param repeat_values In case a list of values is provided, tells if this list must be repeated for the filling. + \param allow_formula Tells that mathematical formulas are authorized for the filling. + \param list_inputs In case of a mathematical expression, attach a list of images to the specified expression. + \param[out] list_outputs In case of a math expression, list of images atatched to the specified expression. + **/ + CImg& 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?1:0,list_inputs,list_outputs,"fill",0); + } + + // 'formula_mode' = { 0 = does not allow formula | 1 = allow formula | + // 2 = allow formula but do not fill image values }. + CImg& _fill(const char *const expression, const bool repeat_values, const unsigned int formula_mode, + const CImgList *const list_inputs, CImgList *const list_outputs, + const char *const calling_function, const CImg *provides_copy) { + if (is_empty() || !expression || !*expression) return *this; + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + CImg is_error; + bool is_value_sequence = false; + cimg_abort_init; + + if (formula_mode) { + + // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser. + double value; + char sep; + const int err = cimg_sscanf(expression,"%lf %c",&value,&sep); + if (err==1 || (err==2 && sep==',')) { + if (err==1) return fill((T)value); + else is_value_sequence = true; + } + + // Try to fill values according to a formula. + _cimg_abort_init_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 + + bool do_in_parallel = false; +#ifdef cimg_use_openmp + cimg_openmp_if(*expression=='*' || *expression==':' || + (mp.is_parallelizable && _width>=(cimg_openmp_sizefactor)*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; + if (formula_mode==2) 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; + if (formula_mode==2) 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 cimg_openmp_collapse(2)) + cimg_forYZ(*this,y,z) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,0); + else { + 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 + } + + } else { // Scalar-valued expression + T *ptrd = *expression=='<'?end() - 1:_data; + if (*expression=='<') { + if (formula_mode==2) 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); } + } else if (*expression=='>' || !do_in_parallel) { + if (formula_mode==2) 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); } + } 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 cimg_openmp_collapse(3)) + cimg_forYZC(*this,y,z,c) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,c); + else { + 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 + } +#endif + } + } + mp.end(); + } catch (CImgException& e) { CImg::string(e._message).move_to(is_error); } + } + + // Try to fill values according to a value sequence. + if (!formula_mode || 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?1:0,list_inputs,list_outputs); + } + + //! Fill sequentially pixel values according to the values found in another image. + /** + \param values Image containing the values used for the filling. + \param repeat_values In case there are less values than necessary in \c values, tells if these values must be + repeated for the filling. + **/ + template + CImg& fill(const CImg& values, const bool repeat_values=true) { + if (is_empty() || !values) return *this; + T *ptrd = _data, *ptre = ptrd + size(); + for (t *ptrs = values._data, *ptrs_end = ptrs + values.size(); ptrs + CImg get_fill(const CImg& values, const bool repeat_values=true) const { + return repeat_values?CImg(_width,_height,_depth,_spectrum).fill(values,repeat_values): + (+*this).fill(values,repeat_values); + } + + //! Fill pixel values along the X-axis at a specified pixel position. + /** + \param y Y-coordinate of the filled column. + \param z Z-coordinate of the filled column. + \param c C-coordinate of the filled column. + \param a0 First fill value. + **/ + CImg& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const int a0, ...) { +#define _cimg_fill1(x,y,z,c,off,siz,t) { \ + va_list ap; va_start(ap,a0); T *ptrd = data(x,y,z,c); *ptrd = (T)a0; \ + for (unsigned int k = 1; k& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const double a0, ...) { + if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,double); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position. + /** + \param x X-coordinate of the filled row. + \param z Z-coordinate of the filled row. + \param c C-coordinate of the filled row. + \param a0 First fill value. + **/ + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const int a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,int); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position \overloading. + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position. + /** + \param x X-coordinate of the filled slice. + \param y Y-coordinate of the filled slice. + \param c C-coordinate of the filled slice. + \param a0 First fill value. + **/ + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position \overloading. + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position. + /** + \param x X-coordinate of the filled channel. + \param y Y-coordinate of the filled channel. + \param z Z-coordinate of the filled channel. + \param a0 First filling value. + **/ + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position \overloading. + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double); + return *this; + } + + //! Discard specified sequence of values in the image buffer, along a specific axis. + /** + \param values Sequence of values to discard. + \param axis Axis along which the values are discarded. If set to \c 0 (default value) + the method does it for all the buffer values and returns a one-column vector. + \note Discarded values will change the image geometry, so the resulting image + is returned as a one-column vector. + **/ + template + CImg& discard(const CImg& values, const char axis=0) { + if (is_empty() || !values) return *this; + return get_discard(values,axis).move_to(*this); + } + + template + CImg get_discard(const CImg& values, const char axis=0) const { + CImg res; + if (!values) return +*this; + if (is_empty()) return res; + const ulongT vsiz = values.size(); + const char _axis = cimg::lowercase(axis); + ulongT j = 0; + unsigned int k = 0; + int i0 = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) { + if ((*this)(i)!=(T)values[j]) { + if (j) --i; + res.draw_image(k,get_columns(i0,i)); + k+=i - i0 + 1; i0 = i + 1; j = 0; + } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = (int)i + 1; }} + } + const ulongT siz = size(); + if ((ulongT)i0& discard(const char axis=0) { + return get_discard(axis).move_to(*this); + } + + //! Discard neighboring duplicates in the image buffer, along the specified axis \newinstance. + CImg get_discard(const char axis=0) const { + CImg res; + if (is_empty()) return res; + const char _axis = cimg::lowercase(axis); + T current = *_data?(T)0:(T)1; + int j = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) + if ((*this)(i)!=current) { res.draw_image(j++,get_column(i)); current = (*this)(i); } + res.resize(j,-100,-100,-100,0); + } break; + case 'y' : { + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { res.draw_image(0,j++,get_row(i)); current = (*this)(0,i); } + res.resize(-100,j,-100,-100,0); + } break; + case 'z' : { + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { res.draw_image(0,0,j++,get_slice(i)); current = (*this)(0,0,i); } + res.resize(-100,-100,j,-100,0); + } break; + case 'c' : { + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { res.draw_image(0,0,0,j++,get_channel(i)); current = (*this)(0,0,0,i); } + res.resize(-100,-100,-100,j,0); + } break; + default : { + res.unroll('y'); + cimg_foroff(*this,i) + if ((*this)[i]!=current) res[j++] = current = (*this)[i]; + res.resize(-100,j,-100,-100,0); + } + } + return res; + } + + //! Invert endianness of all pixel values. + /** + **/ + CImg& invert_endianness() { + cimg::invert_endianness(_data,size()); + return *this; + } + + //! Invert endianness of all pixel values \newinstance. + CImg get_invert_endianness() const { + return (+*this).invert_endianness(); + } + + //! Fill image with random values in specified range. + /** + \param val_min Minimal authorized random value. + \param val_max Maximal authorized random value. + \note Random variables are uniformely distributed in [val_min,val_max]. + **/ + CImg& 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_pragma_openmp(parallel cimg_openmp_if_size(size(),524288)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; + } + + //! Fill image with random values in specified range \newinstance. + CImg get_rand(const T& val_min, const T& val_max) const { + return (+*this).rand(val_min,val_max); + } + + //! Round pixel values. + /** + \param y Rounding precision. + \param rounding_type Rounding type. Can be: + - \c -1: Backward. + - \c 0: Nearest. + - \c 1: Forward. + **/ + CImg& round(const double y=1, const int rounding_type=0) { + if (y>0) cimg_openmp_for(*this,cimg::round(*ptr,y,rounding_type),8192); + return *this; + } + + //! Round pixel values \newinstance. + CImg get_round(const double y=1, const unsigned int rounding_type=0) const { + return (+*this).round(y,rounding_type); + } + + //! Add random noise to pixel values. + /** + \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the + global value range. + \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper, + \p 3=Poisson or \p 4=Rician). + \return A reference to the modified image instance. + \note + - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on + the image value itself. + - Function \p CImg::get_noise() is also defined. It returns a non-shared modified copy of the image instance. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_noise(40); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_noise.jpg + **/ + CImg& noise(const double sigma, const unsigned int noise_type=0) { + if (is_empty()) return *this; + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + Tfloat nsigma = (Tfloat)sigma, m = 0, M = 0; + if (nsigma==0 && noise_type!=3) return *this; + if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M); + if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.); + switch (noise_type) { + case 0 : { // Gaussian noise + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) { + Tfloat val = (Tfloat)(_data[off] + nsigma*cimg::grand(&rng)); + if (val>vmax) val = vmax; + if (valvmax) val = vmax; + if (val::is_float()) { --m; ++M; } + else { m = (Tfloat)cimg::type::min(); M = (Tfloat)cimg::type::max(); } + } + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) if (cimg::rand(100,&rng)vmax) val = vmax; + if (val get_noise(const double sigma, const unsigned int noise_type=0) const { + return (+*this).noise(sigma,noise_type); + } + + //! Linearly normalize pixel values. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(160,220); + (img,res).display(); + \endcode + \image html ref_normalize2.jpg + **/ + CImg& normalize(const T& min_value, const T& max_value) { + 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); + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm. + /** + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_normalize.jpg + **/ + CImg& normalize() { + const ulongT whd = (ulongT)_width*_height*_depth; + 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) { + const T *ptrs = ptrd; + float n = 0; + cimg_forC(*this,c) { n+=cimg::sqr((float)*ptrs); ptrs+=whd; } + n = (float)std::sqrt(n); + T *_ptrd = ptrd++; + if (n>0) cimg_forC(*this,c) { *_ptrd = (T)(*_ptrd/n); _ptrd+=whd; } + else cimg_forC(*this,c) { *_ptrd = (T)0; _ptrd+=whd; } + } + } + return *this; + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm \newinstance. + CImg get_normalize() const { + return CImg(*this,false).normalize(); + } + + //! Compute Lp-norm of each multi-valued pixel of the image instance. + /** + \param norm_type Type of computed vector norm (can be \p -1=Linf, or \p greater or equal than 0). + \par Example + \code + const CImg img("reference.jpg"), res = img.get_norm(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_norm.jpg + **/ + CImg& norm(const int norm_type=2) { + if (_spectrum==1 && norm_type) return abs(); + return get_norm(norm_type).move_to(*this); + } + + //! Compute L2-norm of each multi-valued pixel of the image instance \newinstance. + CImg get_norm(const int norm_type=2) const { + if (is_empty()) return *this; + if (_spectrum==1 && norm_type) return get_abs(); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(_width,_height,_depth); + switch (norm_type) { + 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { const Tfloat val = (Tfloat)cimg::abs(*_ptrs); if (val>n) n = val; _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 0 : { // L0-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + unsigned int n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=*_ptrs==0?0:1; _ptrs+=whd; } + *(ptrd++) = (Tfloat)n; + } + } + } break; + case 1 : { // L1-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 2 : { // L2-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n); + } + } + } break; + default : { // Linf-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=std::pow(cimg::abs((Tfloat)*_ptrs),(Tfloat)norm_type); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::pow((Tfloat)n,1/(Tfloat)norm_type); + } + } + } + } + return res; + } + + //! Cut pixel values in specified range. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_cut(160,220); + (img,res).display(); + \endcode + \image html ref_cut.jpg + **/ + CImg& cut(const T& min_value, const T& max_value) { + if (is_empty()) return *this; + const T a = min_value get_cut(const T& min_value, const T& max_value) const { + return (+*this).cut(min_value,max_value); + } + + //! Uniformly quantize pixel values. + /** + \param nb_levels Number of quantization levels. + \param keep_range Tells if resulting values keep the same range as the original ones. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_quantize(4); + (img,res).display(); + \endcode + \image html ref_quantize.jpg + **/ + CImg& quantize(const unsigned int nb_levels, const bool keep_range=true) { + if (!nb_levels) + throw CImgArgumentException(_cimg_instance + "quantize(): Invalid quantization request with 0 values.", + cimg_instance); + + if (is_empty()) return *this; + Tfloat m, M = (Tfloat)max_min(m), range = M - m; + if (range>0) { + if (keep_range) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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; + } + + //! Uniformly quantize pixel values \newinstance. + CImg get_quantize(const unsigned int n, const bool keep_range=true) const { + return (+*this).quantize(n,keep_range); + } + + //! Threshold pixel values. + /** + \param value Threshold value + \param soft_threshold Tells if soft thresholding must be applied (instead of hard one). + \param strict_threshold Tells if threshold value is strict. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_threshold(128); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_threshold.jpg + **/ + CImg& threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) { + if (is_empty()) return *this; + if (strict_threshold) { + if (soft_threshold) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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(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(size(),65536)) + cimg_rofoff(*this,off) _data[off] = _data[off]>=value?(T)1:(T)0; + } + return *this; + } + + //! Threshold pixel values \newinstance. + CImg get_threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) const { + return (+*this).threshold(value,soft_threshold,strict_threshold); + } + + //! Compute the histogram of pixel values. + /** + \param nb_levels Number of desired histogram levels. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \note + - The histogram H of an image I is the 1D function where H(x) counts the number of 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. + \par Example + \code + const CImg img = CImg("reference.jpg").histogram(256); + img.display_graph(0,3); + \endcode + \image html ref_histogram.jpg + **/ + CImg& histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) { + return get_histogram(nb_levels,min_value,max_value).move_to(*this); + } + + //! Compute the histogram of pixel values \overloading. + CImg& histogram(const unsigned int nb_levels) { + return get_histogram(nb_levels).move_to(*this); + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) const { + if (!nb_levels || is_empty()) return CImg(); + const double + vmin = (double)(min_value res(nb_levels,1,1,1,0); + cimg_rof(*this,ptrs,T) { + const T val = *ptrs; + if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels - 1:(unsigned int)((val - vmin)*nb_levels/(vmax - vmin))]; + } + return res; + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels) const { + if (!nb_levels || is_empty()) return CImg(); + T vmax = 0, vmin = min_max(vmax); + return get_histogram(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values. + /** + \param nb_levels Number of histogram levels used for the equalization. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_equalize(256); + (img,res).display(); + \endcode + \image html ref_equalize.jpg + **/ + CImg& equalize(const unsigned int nb_levels, const T& min_value, const T& max_value) { + if (!nb_levels || is_empty()) return *this; + const T + vmin = min_value hist = get_histogram(nb_levels,vmin,vmax); + ulongT cumul = 0; + cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; } + if (!cumul) cumul = 1; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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; + } + + //! Equalize histogram of pixel values \overloading. + CImg& equalize(const unsigned int nb_levels) { + if (!nb_levels || is_empty()) return *this; + T vmax = 0, vmin = min_max(vmax); + return equalize(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels, const T& val_min, const T& val_max) const { + return (+*this).equalize(nblevels,val_min,val_max); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels) const { + return (+*this).equalize(nblevels); + } + + //! Index multi-valued pixels regarding to a specified colormap. + /** + \param colormap Multi-valued colormap used as the basis for multi-valued pixel indexing. + \param dithering Level of dithering (0=disable, 1=standard level). + \param map_indexes Tell if the values of the resulting image are the colormap indices or the colormap vectors. + \note + - \p img.index(colormap,dithering,1) is equivalent to img.index(colormap,dithering,0).map(colormap). + \par Example + \code + const CImg img("reference.jpg"), colormap(3,1,1,3, 0,128,255, 0,128,255, 0,128,255); + const CImg res = img.get_index(colormap,1,true); + (img,res).display(); + \endcode + \image html ref_index.jpg + **/ + template + CImg& index(const CImg& colormap, const float dithering=1, const bool map_indexes=false) { + return get_index(colormap,dithering,map_indexes).move_to(*this); + } + + //! Index multi-valued pixels regarding to a specified colormap \newinstance. + template + CImg::Tuint> + get_index(const CImg& colormap, const float dithering=1, const bool map_indexes=true) const { + if (colormap._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "index(): Instance and specified colormap (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + typedef typename CImg::Tuint tuint; + if (is_empty()) return CImg(); + const ulongT + 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 + const float ndithering = cimg::cut(dithering,0,1)/16; + Tfloat valm = 0, valM = (Tfloat)max_min(valm); + if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; } + CImg cache = get_crop(-1,0,0,0,_width,1,0,_spectrum - 1); + Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0); + const ulongT cwhd = (ulongT)cache._width*cache._height*cache._depth; + switch (_spectrum) { + case 1 : { // Optimized for scalars + cimg_forYZ(*this,y,z) { + if (yvalM?valM:_val0; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1, + _val2 = (Tfloat)*ptrs2, val2 = _val2valM?valM:_val2; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrpvalM?valM:_val; + dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd; + } + if (dist=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0=(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, + *ptrs_end = ptrs0 + _width; ptrs0::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0=(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::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrp img("reference.jpg"), + colormap1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255), + colormap2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255), + res = img.get_index(colormap1,0).map(colormap2); + (img,res).display(); + \endcode + \image html ref_map.jpg + **/ + template + CImg& map(const CImg& colormap, const unsigned int boundary_conditions=0) { + return get_map(colormap,boundary_conditions).move_to(*this); + } + + //! Map predefined colormap on the scalar (indexed) image instance \newinstance. + template + 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); + + const ulongT + whd = (ulongT)_width*_height*_depth, + cwhd = (ulongT)colormap._width*colormap._height*colormap._depth, + cwhd2 = 2*cwhd; + CImg res(_width,_height,_depth,colormap._spectrum==1?_spectrum:colormap._spectrum); + switch (colormap._spectrum) { + + case 1 : { // Optimized for scalars + const T *ptrs = _data; + switch (boundary_conditions) { + case 3 : // Mirror + cimg_for(res,ptrd,t) { + const ulongT ind = ((ulongT)*(ptrs++))%cwhd2; + *ptrd = colormap[ind& label(const bool is_high_connectivity=false, const Tfloat tolerance=0) { + return get_label(is_high_connectivity,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + CImg get_label(const bool is_high_connectivity=false, + const Tfloat tolerance=0) const { + if (is_empty()) return CImg(); + + // Create neighborhood tables. + int dx[13], dy[13], dz[13], nb = 0; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 0; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 0; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 0; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 0; + } + if (_depth>1) { // 3D version + dx[nb] = 0; dy[nb] = 0; dz[nb++]=1; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = -1; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = -1; + + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 1; + } + } + return _label(nb,dx,dy,dz,tolerance); + } + + //! Label connected components \overloading. + /** + \param connectivity_mask Mask of the neighboring pixels. + \param tolerance Tolerance used to determine if two neighboring pixels belong to the same region. + **/ + template + CImg& label(const CImg& connectivity_mask, const Tfloat tolerance=0) { + return get_label(connectivity_mask,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + template + CImg get_label(const CImg& connectivity_mask, + const Tfloat tolerance=0) const { + 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); + nb = 0; + cimg_forXYZ(connectivity_mask,x,y,z) if ((x || y || z) && + connectivity_mask(x,y,z)) { + dx[nb] = x; dy[nb] = y; dz[nb++] = z; + } + return _label(nb,dx,dy,dz,tolerance); + } + + CImg _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); + + // Init label numbers. + ulongT *ptr = _res.data(); + cimg_foroff(_res,p) *(ptr++) = p; + + // For each neighbour-direction, label. + for (unsigned int n = 0; n& _system_strescape() { +#define cimg_system_strescape(c,s) case c : if (p!=ptrs) CImg(ptrs,(unsigned int)(p-ptrs),1,1,1,false).\ + move_to(list); \ + CImg(s,(unsigned int)std::strlen(s),1,1,1,false).move_to(list); ptrs = p + 1; break + CImgList list; + const T *ptrs = _data; + cimg_for(*this,p,T) switch ((int)*p) { + cimg_system_strescape('\\',"\\\\"); + cimg_system_strescape('\"',"\\\""); + cimg_system_strescape('!',"\"\\!\""); + cimg_system_strescape('`',"\\`"); + cimg_system_strescape('$',"\\$"); + } + if (ptrs(ptrs,(unsigned int)(end()-ptrs),1,1,1,false).move_to(list); + return (list>'x').move_to(*this); + } + + //@} + //--------------------------------- + // + //! \name Color Base Management + //@{ + //--------------------------------- + + //! Return colormap \e "default", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_default.jpg + **/ + static const CImg& default_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,256,1,3); + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap(0,index,0) = (Tuchar)r; + colormap(0,index,1) = (Tuchar)g; + colormap(0,index++,2) = (Tuchar)b; + } + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "HSV", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hsv.jpg + **/ + static const CImg& HSV_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + CImg tmp(1,256,1,3,1); + tmp.get_shared_channel(0).sequence(0,359); + colormap = tmp.HSVtoRGB(); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "lines", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_lines.jpg + **/ + static const CImg& 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; + } + + //! Return colormap \e "hot", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hot.jpg + **/ + static const CImg& hot_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[1] = colormap[2] = colormap[3] = colormap[6] = colormap[7] = colormap[11] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cool", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cool.jpg + **/ + static const CImg& cool_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) colormap.assign(1,2,1,3).fill((T)0,(T)255,(T)255,(T)0,(T)255,(T)255).resize(1,256,1,3,3); + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "jet", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_jet.jpg + **/ + static const CImg& jet_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[2] = colormap[3] = colormap[5] = colormap[6] = colormap[8] = colormap[9] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "flag", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_flag.jpg + **/ + static const CImg& flag_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[0] = colormap[1] = colormap[5] = colormap[9] = colormap[10] = 255; + colormap.resize(1,256,1,3,0,2); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cube", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cube.jpg + **/ + static const CImg& cube_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,8,1,3,(T)0); + colormap[1] = colormap[3] = colormap[5] = colormap[7] = + colormap[10] = colormap[11] = colormap[12] = colormap[13] = + colormap[20] = colormap[21] = colormap[22] = colormap[23] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Convert pixel values from sRGB to RGB color spaces. + CImg& sRGBtoRGB() { + if (is_empty()) return *this; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + sval = (Tfloat)_data[off]/255, + val = (Tfloat)(sval<=0.04045f?sval/12.92f:std::pow((sval + 0.055f)/(1.055f),2.4f)); + _data[off] = (T)cimg::cut(val*255,0,255); + } + return *this; + } + + //! Convert pixel values from sRGB to RGB color spaces \newinstance. + CImg get_sRGBtoRGB() const { + return CImg(*this,false).sRGBtoRGB(); + } + + //! 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(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + val = (Tfloat)_data[off]/255, + sval = (Tfloat)(val<=0.0031308f?val*12.92f:1.055f*std::pow(val,0.416667f) - 0.055f); + _data[off] = (T)cimg::cut(sval*255,0,255); + } + return *this; + } + + //! Convert pixel values from RGB to sRGB color spaces \newinstance. + CImg get_RGBtosRGB() const { + return CImg(*this,false).RGBtosRGB(); + } + + //! Convert pixel values from RGB to HSI color spaces. + CImg& RGBtoHSI() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSI(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSI color spaces \newinstance. + CImg get_RGBtoHSI() const { + return CImg(*this,false).RGBtoHSI(); + } + + //! Convert pixel values from HSI to RGB color spaces. + CImg& HSItoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSItoRGB(): Instance is not a HSI image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSItoRGB() const { + return CImg< Tuchar>(*this,false).HSItoRGB(); + } + + //! Convert pixel values from RGB to HSL color spaces. + CImg& RGBtoHSL() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSL(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSL color spaces \newinstance. + CImg get_RGBtoHSL() const { + return CImg(*this,false).RGBtoHSL(); + } + + //! Convert pixel values from HSL to RGB color spaces. + CImg& HSLtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSLtoRGB(): Instance is not a HSL image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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.f/3 - ntr):p, + G = 6*ntg<1?p + (q - p)*6*ntg:2*ntg<1?q:3*ntg<2?p + (q - p)*6*(2.f/3 - ntg):p, + B = 6*ntb<1?p + (q - p)*6*ntb:2*ntb<1?q:3*ntb<2?p + (q - p)*6*(2.f/3 - ntb):p; + p1[N] = (T)cimg::cut(255*R,0,255); + p2[N] = (T)cimg::cut(255*G,0,255); + p3[N] = (T)cimg::cut(255*B,0,255); + } + return *this; + } + + //! Convert pixel values from HSL to RGB color spaces \newinstance. + CImg get_HSLtoRGB() const { + return CImg(*this,false).HSLtoRGB(); + } + + //! Convert pixel values from RGB to HSV color spaces. + CImg& RGBtoHSV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSV color spaces \newinstance. + CImg get_RGBtoHSV() const { + return CImg(*this,false).RGBtoHSV(); + } + + //! Convert pixel values from HSV to RGB color spaces. + CImg& HSVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSVtoRGB(): Instance is not a HSV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSVtoRGB() const { + return CImg(*this,false).HSVtoRGB(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& RGBtoYCbCr() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYCbCr(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_RGBtoYCbCr() const { + return CImg(*this,false).RGBtoYCbCr(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& YCbCrtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YCbCrtoRGB(): Instance is not a YCbCr image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_YCbCrtoRGB() const { + return CImg(*this,false).YCbCrtoRGB(); + } + + //! Convert pixel values from RGB to YUV color spaces. + CImg& RGBtoYUV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYUV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_RGBtoYUV() const { + return CImg(*this,false).RGBtoYUV(); + } + + //! Convert pixel values from YUV to RGB color spaces. + CImg& YUVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YUVtoRGB(): Instance is not a YUV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_YUVtoRGB() const { + return CImg< Tuchar>(*this,false).YUVtoRGB(); + } + + //! Convert pixel values from RGB to CMY color spaces. + CImg& RGBtoCMY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoCMY(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoCMY() const { + return CImg(*this,false).RGBtoCMY(); + } + + //! Convert pixel values from CMY to RGB color spaces. + CImg& CMYtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoRGB(): Instance is not a CMY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_CMYtoRGB() const { + return CImg(*this,false).CMYtoRGB(); + } + + //! Convert pixel values from CMY to CMYK color spaces. + CImg& CMYtoCMYK() { + return get_CMYtoCMYK().move_to(*this); + } + + //! Convert pixel values from CMY to CMYK color spaces \newinstance. + CImg get_CMYtoCMYK() const { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoCMYK(): Instance is not a CMY image.", + cimg_instance); + + CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT 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; } + pd1[N] = (Tfloat)cimg::cut(C,0,255), + pd2[N] = (Tfloat)cimg::cut(M,0,255), + pd3[N] = (Tfloat)cimg::cut(Y,0,255), + pd4[N] = (Tfloat)cimg::cut(K,0,255); + } + return res; + } + + //! Convert pixel values from CMYK to CMY color spaces. + CImg& CMYKtoCMY() { + return get_CMYKtoCMY().move_to(*this); + } + + //! Convert pixel values from CMYK to CMY color spaces \newinstance. + CImg get_CMYKtoCMY() const { + if (_spectrum!=4) + throw CImgInstanceException(_cimg_instance + "CMYKtoCMY(): Instance is not a CMYK image.", + cimg_instance); + + 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT N = 0; N& RGBtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoXYZ(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).RGBtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to RGB color spaces. + /** + \param use_D65 Tell to use the D65 illuminant (D50 otherwise). + **/ + CImg& XYZtoRGB(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoRGB(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_XYZtoRGB(const bool use_D65=true) const { + return CImg(*this,false).XYZtoRGB(use_D65); + } + + //! Convert pixel values from XYZ to Lab color spaces. + CImg& XYZtoLab(const bool use_D65=true) { +#define _cimg_Labf(x) (24389*(x)>216?cimg::cbrt(x):(24389*(x)/27 + 16)/116) + + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoLab(): Instance is not a XYZ image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N get_XYZtoLab(const bool use_D65=true) const { + return CImg(*this,false).XYZtoLab(use_D65); + } + + //! Convert pixel values from Lab to XYZ color spaces. + CImg& LabtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "LabtoXYZ(): Instance is not a Lab image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N216?cX*cX*cX:(116*cX - 16)*27/24389), + Y = (Tfloat)(27*L>216?cY*cY*cY:27*L/24389), + Z = (Tfloat)(24389*cZ>216?cZ*cZ*cZ:(116*cZ - 16)*27/24389); + p1[N] = (T)(X*white[0]); + p2[N] = (T)(Y*white[1]); + p3[N] = (T)(Z*white[2]); + } + return *this; + } + + //! Convert pixel values from Lab to XYZ color spaces \newinstance. + CImg get_LabtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).LabtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to xyY color spaces. + CImg& XYZtoxyY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoxyY(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?sum:1; + p1[N] = (T)(X/nsum); + p2[N] = (T)(Y/nsum); + p3[N] = (T)Y; + } + return *this; + } + + //! Convert pixel values from XYZ to xyY color spaces \newinstance. + CImg get_XYZtoxyY() const { + return CImg(*this,false).XYZtoxyY(); + } + + //! Convert pixel values from xyY pixels to XYZ color spaces. + CImg& xyYtoXYZ() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "xyYtoXYZ(): Instance is not a xyY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?py:1; + p1[N] = (T)(px*Y/ny); + p2[N] = (T)Y; + p3[N] = (T)((1 - px - py)*Y/ny); + } + return *this; + } + + //! Convert pixel values from xyY pixels to XYZ color spaces \newinstance. + CImg get_xyYtoXYZ() const { + return CImg(*this,false).xyYtoXYZ(); + } + + //! Convert pixel values from RGB to Lab color spaces. + CImg& RGBtoLab(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoLab(use_D65); + } + + //! Convert pixel values from RGB to Lab color spaces \newinstance. + CImg get_RGBtoLab(const bool use_D65=true) const { + return CImg(*this,false).RGBtoLab(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces. + CImg& LabtoRGB(const bool use_D65=true) { + return LabtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces \newinstance. + CImg get_LabtoRGB(const bool use_D65=true) const { + return CImg(*this,false).LabtoRGB(use_D65); + } + + //! Convert pixel values from RGB to xyY color spaces. + CImg& RGBtoxyY(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoxyY(); + } + + //! Convert pixel values from RGB to xyY color spaces \newinstance. + CImg get_RGBtoxyY(const bool use_D65=true) const { + return CImg(*this,false).RGBtoxyY(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces. + CImg& xyYtoRGB(const bool use_D65=true) { + return xyYtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces \newinstance. + CImg get_xyYtoRGB(const bool use_D65=true) const { + return CImg(*this,false).xyYtoRGB(use_D65); + } + + //! Convert pixel values from RGB to CMYK color spaces. + CImg& RGBtoCMYK() { + return RGBtoCMY().CMYtoCMYK(); + } + + //! Convert pixel values from RGB to CMYK color spaces \newinstance. + CImg get_RGBtoCMYK() const { + return CImg(*this,false).RGBtoCMYK(); + } + + //! Convert pixel values from CMYK to RGB color spaces. + CImg& CMYKtoRGB() { + return CMYKtoCMY().CMYtoRGB(); + } + + //! Convert pixel values from CMYK to RGB color spaces \newinstance. + CImg get_CMYKtoRGB() const { + return CImg(*this,false).CMYKtoRGB(); + } + + //@} + //------------------------------------------ + // + //! \name Geometric / Spatial Manipulation + //@{ + //------------------------------------------ + + static float _cimg_lanczos(const float x) { + if (x<=-2 || x>=2) return 0; + const float a = (float)cimg::PI*x, b = 0.5f*a; + return (float)(x?std::sin(a)*std::sin(b)/(a*b):1); + } + + //! Resize image to new dimensions. + /** + \param size_x Number of columns (new size along the X-axis). + \param size_y Number of rows (new size along the Y-axis). + \param size_z Number of slices (new size along the Z-axis). + \param size_c Number of vector-channels (new size along the C-axis). + \param interpolation_type Method of interpolation: + - -1 = no interpolation: raw memory resizing. + - 0 = no interpolation: additional space is filled according to \p boundary_conditions. + - 1 = nearest-neighbor interpolation. + - 2 = moving average interpolation. + - 3 = linear interpolation. + - 4 = grid interpolation. + - 5 = cubic interpolation. + - 6 = lanczos interpolation. + \param boundary_conditions Type of boundary conditions used if necessary. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100). + **/ + CImg& resize(const int size_x, const int size_y=-100, + const int size_z=-100, const int size_c=-100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + if (!size_x || !size_y || !size_z || !size_c) return assign(); + const unsigned int + _sx = (unsigned int)(size_x<0?-size_x*width()/100:size_x), + _sy = (unsigned int)(size_y<0?-size_y*height()/100:size_y), + _sz = (unsigned int)(size_z<0?-size_z*depth()/100:size_z), + _sc = (unsigned int)(size_c<0?-size_c*spectrum()/100:size_c), + sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1; + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return *this; + if (is_empty()) return assign(sx,sy,sz,sc,(T)0); + if (interpolation_type==-1 && sx*sy*sz*sc==size()) { + _width = sx; _height = sy; _depth = sz; _spectrum = sc; + return *this; + } + return get_resize(sx,sy,sz,sc,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c).move_to(*this); + } + + //! Resize image to new dimensions \newinstance. + CImg get_resize(const int size_x, const int size_y = -100, + const int size_z = -100, const int size_c = -100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + if (centering_x<0 || centering_x>1 || centering_y<0 || centering_y>1 || + centering_z<0 || centering_z>1 || centering_c<0 || centering_c>1) + throw CImgArgumentException(_cimg_instance + "resize(): Specified centering arguments (%g,%g,%g,%g) are outside range [0,1].", + cimg_instance, + centering_x,centering_y,centering_z,centering_c); + + if (!size_x || !size_y || !size_z || !size_c) return CImg(); + const unsigned int + sx = std::max(1U,(unsigned int)(size_x>=0?size_x:-size_x*width()/100)), + sy = std::max(1U,(unsigned int)(size_y>=0?size_y:-size_y*height()/100)), + sz = std::max(1U,(unsigned int)(size_z>=0?size_z:-size_z*depth()/100)), + sc = std::max(1U,(unsigned int)(size_c>=0?size_c:-size_c*spectrum()/100)); + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this; + if (is_empty()) return CImg(sx,sy,sz,sc,(T)0); + CImg res; + switch (interpolation_type) { + + // Raw resizing. + // + case -1 : + std::memcpy(res.assign(sx,sy,sz,sc,(T)0)._data,_data,sizeof(T)*std::min(size(),(ulongT)sx*sy*sz*sc)); + break; + + // No interpolation. + // + case 0 : { + const int + xc = (int)(centering_x*((int)sx - width())), + yc = (int)(centering_y*((int)sy - height())), + zc = (int)(centering_z*((int)sz - depth())), + cc = (int)(centering_c*((int)sc - spectrum())); + + switch (boundary_conditions) { + case 3 : { // Mirror + res.assign(sx,sy,sz,sc); + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),65536)) + cimg_forXYZC(res,x,y,z,c) { + const int + mx = cimg::mod(x - xc,w2), my = cimg::mod(y - yc,h2), + mz = cimg::mod(z - zc,d2), mc = cimg::mod(c - cc,s2); + res(x,y,z,c) = (*this)(mx sprite; + if (xc>0) { // X-backward + res.get_crop(xc,yc,zc,cc,xc,yc + height() - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc - 1; x>=0; --x) res.draw_image(x,yc,zc,cc,sprite); + } + if (xc + width()<(int)sx) { // X-forward + res.get_crop(xc + width() - 1,yc,zc,cc,xc + width() - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc + width(); x<(int)sx; ++x) res.draw_image(x,yc,zc,cc,sprite); + } + if (yc>0) { // Y-backward + res.get_crop(0,yc,zc,cc,sx - 1,yc,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc - 1; y>=0; --y) res.draw_image(0,y,zc,cc,sprite); + } + if (yc + height()<(int)sy) { // Y-forward + res.get_crop(0,yc + height() - 1,zc,cc,sx - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc + height(); y<(int)sy; ++y) res.draw_image(0,y,zc,cc,sprite); + } + if (zc>0) { // Z-backward + res.get_crop(0,0,zc,cc,sx - 1,sy - 1,zc,cc + spectrum() - 1).move_to(sprite); + for (int z = zc - 1; z>=0; --z) res.draw_image(0,0,z,cc,sprite); + } + if (zc + depth()<(int)sz) { // Z-forward + res.get_crop(0,0,zc +depth() - 1,cc,sx - 1,sy - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int z = zc + depth(); z<(int)sz; ++z) res.draw_image(0,0,z,cc,sprite); + } + if (cc>0) { // C-backward + res.get_crop(0,0,0,cc,sx - 1,sy - 1,sz - 1,cc).move_to(sprite); + for (int c = cc - 1; c>=0; --c) res.draw_image(0,0,0,c,sprite); + } + if (cc + spectrum()<(int)sc) { // C-forward + res.get_crop(0,0,0,cc + spectrum() - 1,sx - 1,sy - 1,sz - 1,cc + spectrum() - 1).move_to(sprite); + for (int c = cc + spectrum(); c<(int)sc; ++c) res.draw_image(0,0,0,c,sprite); + } + } break; + default : // Dirichlet + res.assign(sx,sy,sz,sc,(T)0).draw_image(xc,yc,zc,cc,*this); + } + break; + } break; + + // Nearest neighbor interpolation. + // + case 1 : { + res.assign(sx,sy,sz,sc); + CImg off_x(sx), off_y(sy + 1), off_z(sz + 1), off_c(sc + 1); + const ulongT + wh = (ulongT)_width*_height, + whd = (ulongT)_width*_height*_depth, + sxy = (ulongT)sx*sy, + sxyz = (ulongT)sx*sy*sz, + 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 = (x + one)*_width/sx; + *(poff_x++) = curr - old; + } + } + if (sy==_height) off_y.fill(_width); + else { + ulongT *poff_y = off_y._data, curr = 0; + cimg_forY(res,y) { + const ulongT old = curr; + curr = (y + one)*_height/sy; + *(poff_y++) = _width*(curr - old); + } + *poff_y = 0; + } + if (sz==_depth) off_z.fill(wh); + else { + ulongT *poff_z = off_z._data, curr = 0; + cimg_forZ(res,z) { + const ulongT old = curr; + curr = (z + one)*_depth/sz; + *(poff_z++) = wh*(curr - old); + } + *poff_z = 0; + } + if (sc==_spectrum) off_c.fill(whd); + else { + ulongT *poff_c = off_c._data, curr = 0; + cimg_forC(res,c) { + const ulongT old = curr; + curr = (c + one)*_spectrum/sc; + *(poff_c++) = whd*(curr - old); + } + *poff_c = 0; + } + + T *ptrd = res._data; + const T* ptrc = _data; + const ulongT *poff_c = off_c._data; + for (unsigned int c = 0; c tmp(sx,_height,_depth,_spectrum,0); + for (unsigned int a = _width*sx, b = _width, c = sx, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d; + if (!b) { + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)/=_width; + ++t; + b = _width; + } + if (!c) { ++s; c = sx; } + } + tmp.move_to(res); + instance_first = false; + } + if (sy!=_height) { + CImg tmp(sx,sy,_depth,_spectrum,0); + for (unsigned int a = _height*sy, b = _height, c = sy, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d; + else + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d; + if (!b) { + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)/=_height; + ++t; + b = _height; + } + if (!c) { ++s; c = sy; } + } + tmp.move_to(res); + instance_first = false; + } + if (sz!=_depth) { + CImg tmp(sx,sy,sz,_spectrum,0); + for (unsigned int a = _depth*sz, b = _depth, c = sz, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d; + else + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d; + if (!b) { + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)/=_depth; + ++t; + b = _depth; + } + if (!c) { ++s; c = sz; } + } + tmp.move_to(res); + instance_first = false; + } + if (sc!=_spectrum) { + CImg tmp(sx,sy,sz,sc,0); + for (unsigned int a = _spectrum*sc, b = _spectrum, c = sc, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d; + else + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d; + if (!b) { + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=_spectrum; + ++t; + b = _spectrum; + } + if (!c) { ++s; c = sc; } + } + tmp.move_to(res); + instance_first = false; + } + } break; + + // Linear interpolation. + // + case 3 : { + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *ptrs = data(0,y,z,c), *const ptrsmax = ptrs + _width - 1; + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + 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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height - 1)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + 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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth - 1)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + 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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum - 1)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrs resx, resy, resz, resc; + if (sx!=_width) { + if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + resx.assign(sx,_height,_depth,_spectrum,(T)0); + const int dx = (int)(2*sx), dy = 2*width(); + int err = (int)(dy + centering_x*(sx*dy/width() - dy)), xs = 0; + cimg_forX(resx,x) if ((err-=dy)<=0) { + cimg_forYZC(resx,y,z,c) resx(x,y,z,c) = (*this)(xs,y,z,c); + ++xs; + err+=dx; + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (sy<_height) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + resy.assign(sx,sy,_depth,_spectrum,(T)0); + const int dx = (int)(2*sy), dy = 2*height(); + int err = (int)(dy + centering_y*(sy*dy/height() - dy)), ys = 0; + cimg_forY(resy,y) if ((err-=dy)<=0) { + cimg_forXZC(resy,x,z,c) resy(x,y,z,c) = resx(x,ys,z,c); + ++ys; + err+=dx; + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (sz<_depth) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + resz.assign(sx,sy,sz,_spectrum,(T)0); + const int dx = (int)(2*sz), dy = 2*depth(); + int err = (int)(dy + centering_z*(sz*dy/depth() - dy)), zs = 0; + cimg_forZ(resz,z) if ((err-=dy)<=0) { + cimg_forXYC(resz,x,y,c) resz(x,y,z,c) = resy(x,y,zs,c); + ++zs; + err+=dx; + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (sc<_spectrum) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + resc.assign(sx,sy,sz,sc,(T)0); + const int dx = (int)(2*sc), dy = 2*spectrum(); + int err = (int)(dy + centering_c*(sc*dy/spectrum() - dy)), cs = 0; + cimg_forC(resc,c) if ((err-=dy)<=0) { + cimg_forXYZ(resc,x,y,z) resc(x,y,z,c) = resz(x,y,z,cs); + ++cs; + err+=dx; + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Cubic interpolation. + // + case 5 : { + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - 1):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + 1):val1, + val3 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sx):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sx):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxy):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxyz):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Lanczos interpolation. + // + case 6 : { + const double vmin = (double)cimg::type::min(), vmax = (double)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1, + *const ptrsmax = ptrs0 + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - 1):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + 1):val2, + val4 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx, + *const ptrsmax = ptrs0 + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sx):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sx):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sx):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy, + *const ptrsmax = ptrs0 + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxy):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxy):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz, + *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxyz):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxyz):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Unknow interpolation. + // + default : + throw CImgArgumentException(_cimg_instance + "resize(): Invalid specified interpolation %d " + "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | " + "5=cubic | 6=lanczos }).", + cimg_instance, + interpolation_type); + } + return res; + } + + //! Resize image to dimensions of another image. + /** + \param src Reference image used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + template + CImg& resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of another image \newinstance. + template + CImg get_resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window. + /** + \param disp Reference display window used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + CImg& resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window \newinstance. + CImg get_resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to half-size along XY axes, using an optimized filter. + CImg& resize_halfXY() { + return get_resize_halfXY().move_to(*this); + } + + //! Resize image to half-size along XY axes, using an optimized filter \newinstance. + CImg get_resize_halfXY() const { + if (is_empty()) return *this; + static const Tfloat kernel[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f, + 0.1231940459f, 0.1935127547f, 0.1231940459f, + 0.07842776544f, 0.1231940459f, 0.07842776544f }; + CImg I(9), res(_width/2,_height/2,_depth,_spectrum); + T *ptrd = res._data; + cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T) + if (x%2 && y%2) *(ptrd++) = (T) + (I[0]*kernel[0] + I[1]*kernel[1] + I[2]*kernel[2] + + I[3]*kernel[3] + I[4]*kernel[4] + I[5]*kernel[5] + + I[6]*kernel[6] + I[7]*kernel[7] + I[8]*kernel[8]); + return res; + } + + //! Resize image to double-size, using the Scale2X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_doubleXY() { + return get_resize_doubleXY().move_to(*this); + } + + //! Resize image to double-size, using the Scale2X algorithm \newinstance. + CImg get_resize_doubleXY() const { +#define _cimg_gs2x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=(res)._width, ptrd2+=(res)._width) + +#define _cimg_gs2x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs2x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(_width<<1,_height<<1,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width; + _cimg_gs2x_for3x3(*this,x,y,z,c,I,T) { + if (Icp!=Icn && Ipc!=Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = Ipc==Icn?Ipc:Icc; + *(ptrd2++) = Icn==Inc?Inc:Icc; + } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; } + } + } + return res; + } + + //! Resize image to triple-size, using the Scale3X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_tripleXY() { + return get_resize_tripleXY().move_to(*this); + } + + //! Resize image to triple-size, using the Scale3X algorithm \newinstance. + CImg get_resize_tripleXY() const { +#define _cimg_gs3x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=2*(res)._width, ptrd2+=2*(res)._width, ptrd3+=2*(res)._width) + +#define _cimg_gs3x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs3x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(3*_width,3*_height,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width, + *ptrd3 = ptrd2 + res._width; + _cimg_gs3x_for3x3(*this,x,y,z,c,I,T) { + if (Icp != Icn && Ipc != Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc; + *(ptrd2++) = Icc; + *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc; + *(ptrd3++) = Ipc==Icn?Ipc:Icc; + *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc; + *(ptrd3++) = Icn==Inc?Inc:Icc; + } else { + *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc; + *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; + *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc; + } + } + } + return res; + } + + //! Mirror image content along specified axis. + /** + \param axis Mirror axis + **/ + CImg& mirror(const char axis) { + if (is_empty()) return *this; + T *pf, *pb, *buf = 0; + switch (cimg::lowercase(axis)) { + case 'x' : { + pf = _data; pb = data(_width - 1); + const unsigned int width2 = _width/2; + for (unsigned int yzv = 0; yzv<_height*_depth*_spectrum; ++yzv) { + for (unsigned int x = 0; x get_mirror(const char axis) const { + return (+*this).mirror(axis); + } + + //! Mirror image content along specified axes. + /** + \param axes Mirror axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& mirror(const char *const axes) { + for (const char *s = axes; *s; ++s) mirror(*s); + return *this; + } + + //! Mirror image content along specified axes \newinstance. + CImg get_mirror(const char *const axes) const { + return (+*this).mirror(axes); + } + + //! Shift image content. + /** + \param delta_x Amount of displacement along the X-axis. + \param delta_y Amount of displacement along the Y-axis. + \param delta_z Amount of displacement along the Z-axis. + \param delta_c Amount of displacement along the C-axis. + \param boundary_conditions Border condition. Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) { + if (is_empty()) return *this; + if (boundary_conditions==3) + return get_crop(-delta_x,-delta_y,-delta_z,-delta_c, + width() - delta_x - 1, + height() - delta_y - 1, + depth() - delta_z - 1, + spectrum() - delta_c - 1,3).move_to(*this); + if (delta_x) // Shift along X-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_x,width()), ndelta_x = (ml<=width()/2)?ml:(ml-width()); + if (!ndelta_x) return *this; + CImg buf(cimg::abs(ndelta_x)); + if (ndelta_x>0) cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(0,y,z,c),ndelta_x*sizeof(T)); + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + std::memcpy(data(_width-ndelta_x,y,z,c),buf,ndelta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(_width + ndelta_x,y,z,c),-ndelta_x*sizeof(T)); + std::memmove(data(-ndelta_x,y,z,c),data(0,y,z,c),(_width + ndelta_x)*sizeof(T)); + std::memcpy(data(0,y,z,c),buf,-ndelta_x*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_x<0) { + const int ndelta_x = (-delta_x>=width())?width() - 1:-delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(_width - 1,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width())?width() - 1:delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(ndelta_x,y,z,c),data(0,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(0,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width()) return fill((T)0); + if (delta_x<0) cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(-delta_x,y,z,c),(_width + delta_x)*sizeof(T)); + std::memset(data(_width + delta_x,y,z,c),0,-delta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memmove(data(delta_x,y,z,c),data(0,y,z,c),(_width-delta_x)*sizeof(T)); + std::memset(data(0,y,z,c),0,delta_x*sizeof(T)); + } + } + + if (delta_y) // Shift along Y-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_y,height()), ndelta_y = (ml<=height()/2)?ml:(ml-height()); + if (!ndelta_y) return *this; + CImg buf(width(),cimg::abs(ndelta_y)); + if (ndelta_y>0) cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,0,z,c),_width*ndelta_y*sizeof(T)); + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + std::memcpy(data(0,_height-ndelta_y,z,c),buf,_width*ndelta_y*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,_height + ndelta_y,z,c),-ndelta_y*_width*sizeof(T)); + std::memmove(data(0,-ndelta_y,z,c),data(0,0,z,c),_width*(_height + ndelta_y)*sizeof(T)); + std::memcpy(data(0,0,z,c),buf,-ndelta_y*_width*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_y<0) { + const int ndelta_y = (-delta_y>=height())?height() - 1:-delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,_height-ndelta_y,z,c), *ptrs = data(0,_height - 1,z,c); + for (int l = 0; l=height())?height() - 1:delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,ndelta_y,z,c),data(0,0,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c); + for (int l = 0; l=height()) return fill((T)0); + if (delta_y<0) cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,-delta_y,z,c),_width*(_height + delta_y)*sizeof(T)); + std::memset(data(0,_height + delta_y,z,c),0,-delta_y*_width*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memmove(data(0,delta_y,z,c),data(0,0,z,c),_width*(_height-delta_y)*sizeof(T)); + std::memset(data(0,0,z,c),0,delta_y*_width*sizeof(T)); + } + } + + if (delta_z) // Shift along Z-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_z,depth()), ndelta_z = (ml<=depth()/2)?ml:(ml-depth()); + if (!ndelta_z) return *this; + CImg buf(width(),height(),cimg::abs(ndelta_z)); + if (ndelta_z>0) cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,0,c),_width*_height*ndelta_z*sizeof(T)); + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,_depth-ndelta_z,c),buf,_width*_height*ndelta_z*sizeof(T)); + } else cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,_depth + ndelta_z,c),-ndelta_z*_width*_height*sizeof(T)); + std::memmove(data(0,0,-ndelta_z,c),data(0,0,0,c),_width*_height*(_depth + ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,0,c),buf,-ndelta_z*_width*_height*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_z<0) { + const int ndelta_z = (-delta_z>=depth())?depth() - 1:-delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,_depth-ndelta_z,c), *ptrs = data(0,0,_depth - 1,c); + for (int l = 0; l=depth())?depth() - 1:delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,ndelta_z,c),data(0,0,0,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c); + for (int l = 0; l=depth()) return fill((T)0); + if (delta_z<0) cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,-delta_z,c),_width*_height*(_depth + delta_z)*sizeof(T)); + std::memset(data(0,0,_depth + delta_z,c),0,_width*_height*(-delta_z)*sizeof(T)); + } else cimg_forC(*this,c) { + std::memmove(data(0,0,delta_z,c),data(0,0,0,c),_width*_height*(_depth-delta_z)*sizeof(T)); + std::memset(data(0,0,0,c),0,delta_z*_width*_height*sizeof(T)); + } + } + + if (delta_c) // Shift along C-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_c,spectrum()), ndelta_c = (ml<=spectrum()/2)?ml:(ml-spectrum()); + if (!ndelta_c) return *this; + CImg buf(width(),height(),depth(),cimg::abs(ndelta_c)); + if (ndelta_c>0) { + std::memcpy(buf,_data,_width*_height*_depth*ndelta_c*sizeof(T)); + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + std::memcpy(data(0,0,0,_spectrum-ndelta_c),buf,_width*_height*_depth*ndelta_c*sizeof(T)); + } else { + std::memcpy(buf,data(0,0,0,_spectrum + ndelta_c),-ndelta_c*_width*_height*_depth*sizeof(T)); + std::memmove(data(0,0,0,-ndelta_c),_data,_width*_height*_depth*(_spectrum + ndelta_c)*sizeof(T)); + std::memcpy(_data,buf,-ndelta_c*_width*_height*_depth*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_c<0) { + const int ndelta_c = (-delta_c>=spectrum())?spectrum() - 1:-delta_c; + if (!ndelta_c) return *this; + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,_spectrum-ndelta_c), *ptrs = data(0,0,0,_spectrum - 1); + for (int l = 0; l=spectrum())?spectrum() - 1:delta_c; + if (!ndelta_c) return *this; + std::memmove(data(0,0,0,ndelta_c),_data,_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,1); + for (int l = 0; l=spectrum()) return fill((T)0); + if (delta_c<0) { + std::memmove(_data,data(0,0,0,-delta_c),_width*_height*_depth*(_spectrum + delta_c)*sizeof(T)); + std::memset(data(0,0,0,_spectrum + delta_c),0,_width*_height*_depth*(-delta_c)*sizeof(T)); + } else { + std::memmove(data(0,0,0,delta_c),_data,_width*_height*_depth*(_spectrum-delta_c)*sizeof(T)); + std::memset(_data,0,delta_c*_width*_height*_depth*sizeof(T)); + } + } + return *this; + } + + //! Shift image content \newinstance. + CImg get_shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) const { + return (+*this).shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions); + } + + //! Permute axes order. + /** + \param order Axes permutations, as a C-string of 4 characters. + This function permutes image content regarding the specified axes permutation. + **/ + CImg& permute_axes(const char *const order) { + return get_permute_axes(order).move_to(*this); + } + + //! Permute axes order \newinstance. + CImg get_permute_axes(const char *const order) const { + const T foo = (T)0; + return _permute_axes(order,foo); + } + + template + CImg _permute_axes(const char *const order, const t&) const { + if (is_empty() || !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; + } + } + if (!res) + throw CImgArgumentException(_cimg_instance + "permute_axes(): Invalid specified permutation '%s'.", + cimg_instance, + order); + return res; + } + + //! Unroll pixel values along specified axis. + /** + \param axis Unroll axis (can be \c 'x', \c 'y', \c 'z' or c 'c'). + **/ + CImg& unroll(const char axis) { + const unsigned int siz = (unsigned int)size(); + if (siz) switch (cimg::lowercase(axis)) { + case 'x' : _width = siz; _height = _depth = _spectrum = 1; break; + case 'y' : _height = siz; _width = _depth = _spectrum = 1; break; + case 'z' : _depth = siz; _width = _height = _spectrum = 1; break; + default : _spectrum = siz; _width = _height = _depth = 1; + } + return *this; + } + + //! Unroll pixel values along specified axis \newinstance. + CImg get_unroll(const char axis) const { + return (+*this).unroll(axis); + } + + //! Rotate image with arbitrary angle. + /** + \param angle Rotation angle, in degrees. + \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle \newinstance. + CImg get_rotate(const float angle, const unsigned int interpolation=1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res; + 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) { + case 1 : { // 90 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(y,hm1 - x,z,c); + } break; + case 2 : { // 180 deg + res.assign(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - x,hm1 - y,z,c); + } break; + case 3 : { // 270 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - y,x,z,c); + } break; + default : // 0 deg + return *this; + } + } else { // Generic angle + const float + rad = (float)(nangle*cimg::PI/180.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad), + ux = cimg::abs((_width - 1)*ca), uy = cimg::abs((_width - 1)*sa), + vx = cimg::abs((_height - 1)*sa), vy = cimg::abs((_height - 1)*ca), + w2 = 0.5f*(_width - 1), h2 = 0.5f*(_height - 1); + res.assign((int)cimg::round(1 + ux + vx),(int)cimg::round(1 + uy + vy),_depth,_spectrum); + const float rw2 = 0.5f*(res._width - 1), rh2 = 0.5f*(res._height - 1); + _rotate(res,nangle,interpolation,boundary_conditions,w2,h2,rw2,rh2); + } + return res; + } + + //! Rotate image with arbitrary angle, around a center point. + /** + \param angle Rotation angle, in degrees. + \param cx X-coordinate of the rotation center. + \param cy Y-coordinate of the rotation center. + \param interpolation Type of interpolation, { 0=nearest | 1=linear | 2=cubic | 3=mirror }. + \param boundary_conditions Boundary conditions, { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) { + return get_rotate(angle,cx,cy,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle, around a center point \newinstance. + CImg get_rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + _rotate(res,angle,interpolation,boundary_conditions,cx,cy,cx,cy); + return res; + } + + // [internal] Perform 2D rotation with arbitrary angle. + void _rotate(CImg& 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.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad); + + switch (boundary_conditions) { + case 3 : { // Mirror + + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const unsigned int interpolation, const unsigned int boundary_conditions) const { + if (is_empty()) return *this; + CImg res; + const float + w1 = _width - 1, h1 = _height - 1, d1 = _depth -1, + w2 = 0.5f*w1, h2 = 0.5f*h1, d2 = 0.5f*d1; + CImg R = CImg::rotation_matrix(u,v,w,angle); + const CImg + X = R*CImg(8,3,1,1, + 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), + zm, zM = X.get_shared_row(2).max_min(zm); + const int + dx = (int)cimg::round(xM - xm), + dy = (int)cimg::round(yM - ym), + dz = (int)cimg::round(zM - zm); + R.transpose(); + res.assign(1 + dx,1 + dy,1 + dz,_spectrum); + const float rw2 = 0.5f*dx, rh2 = 0.5f*dy, rd2 = 0.5f*dz; + _rotate(res,R,interpolation,boundary_conditions,w2,h2,d2,rw2,rh2,rd2); + return res; + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point. + /** + \param u X-coordinate of the 3D rotation axis. + \param v Y-coordinate of the 3D rotation axis. + \param w Z-coordinate of the 3D rotation axis. + \param 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 }. + \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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,cx,cy,cz,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const float cx, const float cy, const float cz, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + CImg R = CImg::rotation_matrix(u,v,w,-angle); + _rotate(res,R,interpolation,boundary_conditions,cx,cy,cz,cx,cy,cz); + return res; + } + + // [internal] Perform 3D rotation with arbitrary axis and angle. + void _rotate(CImg& res, const CImg& R, + const unsigned int interpolation, const unsigned int boundary_conditions, + const float w2, const float h2, const float d2, + const float rw2, const float rh2, const float rd2) const { + switch (boundary_conditions) { + case 3 : // Mirror + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + template + CImg& warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) { + return get_warp(warp,mode,interpolation,boundary_conditions).move_to(*this); + } + + //! Warp image content by a warping field \newinstance + template + CImg get_warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty() || !warp) return *this; + if (mode && !is_sameXYZ(warp)) + throw CImgArgumentException(_cimg_instance + "warp(): Instance and specified relative warping field (%u,%u,%u,%u,%p) " + "have different XYZ dimensions.", + cimg_instance, + warp._width,warp._height,warp._depth,warp._spectrum,warp._data); + + CImg res(warp._width,warp._height,warp._depth,_spectrum); + + if (warp._spectrum==1) { // 1D warping + if (mode>=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + cimg_pragma_openmp(parallel for 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),x + (float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),(float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)), Y = y + (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)), Y = (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++), + z + (float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = x + (int)cimg::round(*(ptrs0++)), + Y = y + (int)cimg::round(*(ptrs1++)), + Z = z + (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = (int)cimg::round(*(ptrs0++)), + Y = (int)cimg::round(*(ptrs1++)), + Z = (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) const { + if (is_empty() || _depth<2) return +*this; + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + const CImg + img_xy = get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1), + img_zy = get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).permute_axes("xzyc"). + resize(_depth,_height,1,-100,-1), + img_xz = get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1); + return CImg(_width + _depth,_height + _depth,1,_spectrum,cimg::min(img_xy.min(),img_zy.min(),img_xz.min())). + draw_image(0,0,img_xy).draw_image(img_xy._width,0,img_zy). + draw_image(0,img_xy._height,img_xz); + } + + //! Construct a 2D representation of a 3D image, with XY,XZ and YZ views \inplace. + CImg& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) { + if (_depth<2) return *this; + return get_projections2d(x0,y0,z0).move_to(*this); + } + + //! Crop image region. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param y0 = Y-coordinate of the upper-left crop rectangle corner. + \param z0 = Z-coordinate of the upper-left crop rectangle corner. + \param c0 = C-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param y1 = Y-coordinate of the lower-right crop rectangle corner. + \param z1 = Z-coordinate of the lower-right crop rectangle corner. + \param c1 = C-coordinate of the lower-right crop rectangle corner. + \param boundary_conditions = Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) { + return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,boundary_conditions).move_to(*this); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "crop(): Empty instance.", + cimg_instance); + const int + nx0 = x0 res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0); + if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum()) + switch (boundary_conditions) { + case 3 : { // Mirror + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for 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), + my = cimg::mod(ny0 + y,h2), + mz = cimg::mod(nz0 + z,d2), + mc = cimg::mod(nc0 + c,s2); + res(x,y,z,c) = (*this)(mx=(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 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 + res.fill((T)0).draw_image(-nx0,-ny0,-nz0,-nc0,*this); + } + else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this); + return res; + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int x1, const unsigned int boundary_conditions=0) { + return crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int x1, const unsigned int boundary_conditions=0) const { + return get_crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Autocrop image region, regarding the specified background value. + CImg& autocrop(const T& value, const char *const axes="czyx") { + if (is_empty()) return *this; + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + const CImg coords = _autocrop(value,axis); + if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels + else switch (axis) { + case 'x' : { + const int x0 = coords[0], x1 = coords[1]; + if (x0>=0 && x1>=0) crop(x0,x1); + } break; + case 'y' : { + const int y0 = coords[0], y1 = coords[1]; + if (y0>=0 && y1>=0) crop(0,y0,_width - 1,y1); + } break; + case 'z' : { + const int z0 = coords[0], z1 = coords[1]; + if (z0>=0 && z1>=0) crop(0,0,z0,_width - 1,_height - 1,z1); + } break; + default : { + const int c0 = coords[0], c1 = coords[1]; + if (c0>=0 && c1>=0) crop(0,0,0,c0,_width - 1,_height - 1,_depth - 1,c1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background value \newinstance. + CImg get_autocrop(const T& value, const char *const axes="czyx") const { + return (+*this).autocrop(value,axes); + } + + //! Autocrop image region, regarding the specified background color. + /** + \param color Color used for the crop. If \c 0, color is guessed. + \param axes Axes used for the crop. + **/ + CImg& autocrop(const T *const color=0, const char *const axes="zyx") { + if (is_empty()) return *this; + 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); + if (_width==w && _height==h && _depth==d && _spectrum==s) { + const CImg col2 = get_vector_at(w - 1,h - 1,d - 1); + autocrop(col2,axes); + } + return *this; + } + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + switch (axis) { + case 'x' : { + int x0 = width(), x1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'x'); + const int nx0 = coords[0], nx1 = coords[1]; + if (nx0>=0 && nx1>=0) { x0 = std::min(x0,nx0); x1 = std::max(x1,nx1); } + } + if (x0==width() && x1==-1) return assign(); else crop(x0,x1); + } break; + case 'y' : { + int y0 = height(), y1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'y'); + const int ny0 = coords[0], ny1 = coords[1]; + if (ny0>=0 && ny1>=0) { y0 = std::min(y0,ny0); y1 = std::max(y1,ny1); } + } + if (y0==height() && y1==-1) return assign(); else crop(0,y0,_width - 1,y1); + } break; + default : { + int z0 = depth(), z1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'z'); + const int nz0 = coords[0], nz1 = coords[1]; + if (nz0>=0 && nz1>=0) { z0 = std::min(z0,nz0); z1 = std::max(z1,nz1); } + } + if (z0==depth() && z1==-1) return assign(); else crop(0,0,z0,_width - 1,_height - 1,z1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background color \newinstance. + CImg get_autocrop(const T *const color=0, const char *const axes="zyx") const { + return (+*this).autocrop(color,axes); + } + + //! Autocrop image region, regarding the specified background color \overloading. + template 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)) { + case 'x' : { + int x0 = -1, x1 = -1; + cimg_forX(*this,x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); } + if (x0>=0) { + for (int x = width() - 1; x>=0; --x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); } + } + res = CImg::vector(x0,x1); + } break; + case 'y' : { + int y0 = -1, y1 = -1; + cimg_forY(*this,y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); } + if (y0>=0) { + for (int y = height() - 1; y>=0; --y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); } + } + res = CImg::vector(y0,y1); + } break; + case 'z' : { + int z0 = -1, z1 = -1; + cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); } + if (z0>=0) { + for (int z = depth() - 1; z>=0; --z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); } + } + res = CImg::vector(z0,z1); + } break; + default : { + int c0 = -1, c1 = -1; + cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); } + if (c0>=0) { + for (int c = spectrum() - 1; c>=0; --c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; } + } + res = CImg::vector(c0,c1); + } + } + return res; + } + + //! Return specified image column. + /** + \param x0 Image column. + **/ + CImg get_column(const int x0) const { + return get_columns(x0,x0); + } + + //! Return specified image column \inplace. + CImg& column(const int x0) { + return columns(x0,x0); + } + + //! Return specified range of image columns. + /** + \param x0 Starting image column. + \param x1 Ending image column. + **/ + CImg& columns(const int x0, const int x1) { + return get_columns(x0,x1).move_to(*this); + } + + //! Return specified range of image columns \inplace. + CImg get_columns(const int x0, const int x1) const { + return get_crop(x0,0,0,0,x1,height() - 1,depth() - 1,spectrum() - 1); + } + + //! Return specified image row. + CImg get_row(const int y0) const { + return get_rows(y0,y0); + } + + //! Return specified image row \inplace. + /** + \param y0 Image row. + **/ + CImg& row(const int y0) { + return rows(y0,y0); + } + + //! Return specified range of image rows. + /** + \param y0 Starting image row. + \param y1 Ending image row. + **/ + CImg get_rows(const int y0, const int y1) const { + return get_crop(0,y0,0,0,width() - 1,y1,depth() - 1,spectrum() - 1); + } + + //! Return specified range of image rows \inplace. + CImg& rows(const int y0, const int y1) { + return get_rows(y0,y1).move_to(*this); + } + + //! Return specified image slice. + /** + \param z0 Image slice. + **/ + CImg get_slice(const int z0) const { + return get_slices(z0,z0); + } + + //! Return specified image slice \inplace. + CImg& slice(const int z0) { + return slices(z0,z0); + } + + //! Return specified range of image slices. + /** + \param z0 Starting image slice. + \param z1 Ending image slice. + **/ + CImg get_slices(const int z0, const int z1) const { + return get_crop(0,0,z0,0,width() - 1,height() - 1,z1,spectrum() - 1); + } + + //! Return specified range of image slices \inplace. + CImg& slices(const int z0, const int z1) { + return get_slices(z0,z1).move_to(*this); + } + + //! Return specified image channel. + /** + \param c0 Image channel. + **/ + CImg get_channel(const int c0) const { + return get_channels(c0,c0); + } + + //! Return specified image channel \inplace. + CImg& channel(const int c0) { + return channels(c0,c0); + } + + //! Return specified range of image channels. + /** + \param c0 Starting image channel. + \param c1 Ending image channel. + **/ + CImg get_channels(const int c0, const int c1) const { + return get_crop(0,0,0,c0,width() - 1,height() - 1,depth() - 1,c1); + } + + //! Return specified range of image channels \inplace. + CImg& channels(const int c0, const int c1) { + return get_channels(c0,c1).move_to(*this); + } + + //! Return stream line of a 2D or 3D vector field. + CImg get_streamline(const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false) const { + if (_spectrum!=2 && _spectrum!=3) + throw CImgInstanceException(_cimg_instance + "streamline(): Instance is not a 2D or 3D vector field.", + cimg_instance); + if (_spectrum==2) { + if (is_oriented_only) { + typename CImg::_functor4d_streamline2d_oriented func(*this); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true, + 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.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.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.f,_height - 1.f,_depth - 1.f); + } + + //! Return stream line of a 3D vector field. + /** + \param func Vector field function. + \param x X-coordinate of the starting point of the streamline. + \param y Y-coordinate of the starting point of the streamline. + \param z Z-coordinate of the starting point of the streamline. + \param L Streamline length. + \param dl Streamline length increment. + \param interpolation_type Type of interpolation. + Can be { 0=nearest int | 1=linear | 2=2nd-order RK | 3=4th-order RK. }. + \param is_backward_tracking Tells if the streamline is estimated forward or backward. + \param is_oriented_only Tells if the direction of the vectors must be ignored. + \param x0 X-coordinate of the first bounding-box vertex. + \param y0 Y-coordinate of the first bounding-box vertex. + \param z0 Z-coordinate of the first bounding-box vertex. + \param x1 X-coordinate of the second bounding-box vertex. + \param y1 Y-coordinate of the second bounding-box vertex. + \param z1 Z-coordinate of the second bounding-box vertex. + **/ + template + static CImg streamline(const tfunc& func, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + if (dl<=0) + throw CImgArgumentException("CImg<%s>::streamline(): Invalid specified integration length %g " + "(should be >0).", + pixel_type(), + dl); + + const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1); + if (L<=0 || (is_bounded && (xx1 || yy1 || zz1))) return CImg(); + const unsigned int size_L = (unsigned int)cimg::round(L/dl + 1); + CImg coordinates(size_L,3); + const float dl2 = dl/2; + float + *ptr_x = coordinates.data(0,0), + *ptr_y = coordinates.data(0,1), + *ptr_z = coordinates.data(0,2), + pu = (float)(dl*func(x,y,z,0)), + pv = (float)(dl*func(x,y,z,1)), + pw = (float)(dl*func(x,y,z,2)), + X = x, Y = y, Z = z; + + switch (interpolation_type) { + case 0 : { // Nearest integer interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + const int + xi = (int)(X>0?X + 0.5f:X - 0.5f), + yi = (int)(Y>0?Y + 0.5f:Y - 0.5f), + zi = (int)(Z>0?Z + 0.5f:Z - 0.5f); + float + u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)), + v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)), + w = (float)(dl*func((float)xi,(float)yi,(float)zi,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 1 : { // First-order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u = (float)(dl*func(X,Y,Z,0)), + v = (float)(dl*func(X,Y,Z,1)), + w = (float)(dl*func(X,Y,Z,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 2 : { // Second order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u = (float)(dl*func(X + u0,Y + v0,Z + w0,0)), + v = (float)(dl*func(X + u0,Y + v0,Z + w0,1)), + w = (float)(dl*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + default : { // Fourth order interpolation + cimg_forX(coordinates,x) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,0)), + v1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,1)), + w1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; } + float + u2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,0)), + v2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,1)), + w2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; } + float + u3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,0)), + v3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,1)), + w3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; } + const float + u = (u0 + u3)/3 + (u1 + u2)/1.5f, + v = (v0 + v3)/3 + (v1 + v2)/1.5f, + w = (w0 + w3)/3 + (w1 + w2)/1.5f; + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } + } + if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0); + return coordinates; + } + + //! Return stream line of a 3D vector field \overloading. + static CImg streamline(const char *const expression, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=true, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + _functor4d_streamline_expr func(expression); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1); + } + + struct _functor4d_streamline2d_directed { + const CImg& ref; + _functor4d_streamline2d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0; + } + }; + + struct _functor4d_streamline3d_directed { + const CImg& ref; + _functor4d_streamline3d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref._linear_atXYZ(x,y,z,c); + } + }; + + struct _functor4d_streamline2d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline2d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,1,2); } + ~_functor4d_streamline2d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign2d(i,j) \ + if (I(i,j,0)*I(0,0,0) + I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z; + const float + dx = x - xi, + dy = y - yi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1); + I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1); + I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1); + I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1); + _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1); + } + return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0; + } + }; + + struct _functor4d_streamline3d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline3d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,2,3); } + ~_functor4d_streamline3d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0) + I(i,j,k,1)*I(0,0,0,1) + I(i,j,k,2)*I(0,0,0,2)<0) { \ + I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z - (z>=0?0:1), nzi = zi + 1; + const float + dx = x - xi, + dy = y - yi, + dz = z - zi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + if (zi<0) zi = 0; + if (nzi<0) nzi = 0; + if (zi>=ref.depth()) zi = ref.depth() - 1; + if (nzi>=ref.depth()) nzi = ref.depth() - 1; + I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1); + I(0,0,0,2) = (float)ref(xi,yi,zi,2); I(1,0,0,0) = (float)ref(nxi,yi,zi,0); + I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2); + I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1); + I(1,1,0,2) = (float)ref(nxi,nyi,zi,2); I(0,1,0,0) = (float)ref(xi,nyi,zi,0); + I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,nyi,zi,2); + I(0,0,1,0) = (float)ref(xi,yi,nzi,0); I(0,0,1,1) = (float)ref(xi,yi,nzi,1); + I(0,0,1,2) = (float)ref(xi,yi,nzi,2); I(1,0,1,0) = (float)ref(nxi,yi,nzi,0); + I(1,0,1,1) = (float)ref(nxi,yi,nzi,1); I(1,0,1,2) = (float)ref(nxi,yi,nzi,2); + I(1,1,1,0) = (float)ref(nxi,nyi,nzi,0); I(1,1,1,1) = (float)ref(nxi,nyi,nzi,1); + I(1,1,1,2) = (float)ref(nxi,nyi,nzi,2); I(0,1,1,0) = (float)ref(xi,nyi,nzi,0); + I(0,1,1,1) = (float)ref(xi,nyi,nzi,1); I(0,1,1,2) = (float)ref(xi,nyi,nzi,2); + _cimg_vecalign3d(1,0,0); _cimg_vecalign3d(1,1,0); _cimg_vecalign3d(0,1,0); + _cimg_vecalign3d(0,0,1); _cimg_vecalign3d(1,0,1); _cimg_vecalign3d(1,1,1); _cimg_vecalign3d(0,1,1); + } + return (float)pI->_linear_atXYZ(dx,dy,dz,c); + } + }; + + struct _functor4d_streamline_expr { + _cimg_math_parser *mp; + ~_functor4d_streamline_expr() { mp->end(); delete mp; } + _functor4d_streamline_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,"streamline",CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)(*mp)(x,y,z,c); + } + }; + + //! Return a shared-memory image referencing a range of pixels of the image instance. + /** + \param x0 X-coordinate of the starting pixel. + \param x1 X-coordinate of the ending pixel. + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_points(const unsigned int x0, const unsigned int x1, + const unsigned int y0=0, const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(x0,y0,z0,c0), + end = (unsigned int)offset(x1,y0,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_points(): Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).", + cimg_instance, + x0,x1,y0,z0,c0); + + return CImg(_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); + 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 rows of the image instance. + /** + \param y0 Y-coordinate of the starting row. + \param y1 Y-coordinate of the ending row. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_rows(const unsigned int y0, const unsigned int y1, + const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,y0,z0,c0), + end = (unsigned int)offset(0,y1,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_rows(): Invalid request of a shared-memory subset " + "(0->%u,%u->%u,%u,%u).", + cimg_instance, + _width - 1,y0,y1,z0,c0); + + return CImg(_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); + 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 one row of the image instance. + /** + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared-memory image referencing one row of the image instance \const. + const CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared memory image referencing a range of slices of the image instance. + /** + \param z0 Z-coordinate of the starting slice. + \param z1 Z-coordinate of the ending slice. + \param c0 C-coordinate. + **/ + CImg get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,0,z0,c0), + end = (unsigned int)offset(0,0,z1,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_slices(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,%u->%u,%u).", + cimg_instance, + _width - 1,_height - 1,z0,z1,c0); + + return CImg(_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); + 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 one slice of the image instance. + /** + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing one slice of the image instance \const. + const CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) const { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing a range of channels of the image instance. + /** + \param c0 C-coordinate of the starting channel. + \param c1 C-coordinate of the ending channel. + **/ + CImg get_shared_channels(const unsigned int c0, const unsigned int c1) { + const unsigned int + beg = (unsigned int)offset(0,0,0,c0), + end = (unsigned int)offset(0,0,0,c1); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_channels(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,0->%u,%u->%u).", + cimg_instance, + _width - 1,_height - 1,_depth - 1,c0,c1); + + return CImg(_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); + 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 one channel of the image instance. + /** + \param c0 C-coordinate. + **/ + CImg get_shared_channel(const unsigned int c0) { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory image referencing one channel of the image instance \const. + const CImg get_shared_channel(const unsigned int c0) const { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory version of the image instance. + CImg get_shared() { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Return a shared-memory version of the image instance \const. + const CImg get_shared() const { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Split image into a list along specified axis. + /** + \param axis Splitting axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param nb Number of splitted parts. + \note + - If \c nb==0, instance image is splitted into blocs of egal values along the specified axis. + - If \c nb<=0, instance image is splitted into blocs of -\c nb pixel wide. + - If \c nb>0, instance image is splitted into \c nb blocs. + **/ + CImgList 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 + 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>=(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); + } break; + case 'y': { + if (_height>dp) { + res.assign(_height/dp + (_height%dp?1:0),1,1); + const unsigned int pe = _height - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'z': { + if (_depth>dp) { + res.assign(_depth/dp + (_depth%dp?1:0),1,1); + const unsigned int pe = _depth - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'c' : { + if (_spectrum>dp) { + res.assign(_spectrum/dp + (_spectrum%dp?1:0),1,1); + const unsigned int pe = _spectrum - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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 + const unsigned int siz = _axis=='x'?_width:_axis=='y'?_height:_axis=='z'?_depth:_axis=='c'?_spectrum:0; + if ((unsigned int)nb>siz) + throw CImgArgumentException(_cimg_instance + "get_split(): Instance cannot be split along %c-axis into %u blocs.", + cimg_instance, + axis,nb); + if (nb==1) res.assign(*this); + else { + int err = (int)siz; + unsigned int _p = 0; + switch (_axis) { + case 'x' : { + cimg_forX(*this,p) if ((err-=nb)<=0) { + get_crop(_p,0,0,0,p,_height - 1,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'y' : { + cimg_forY(*this,p) if ((err-=nb)<=0) { + get_crop(0,_p,0,0,_width - 1,p,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'z' : { + cimg_forZ(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,_p,0,_width - 1,_height - 1,p,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'c' : { + cimg_forC(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,0,_p,_width - 1,_height - 1,_depth - 1,p).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } + } + } + } else { // Split by egal values according to specified axis + T current = *_data; + switch (_axis) { + case 'x' : { + int i0 = 0; + cimg_forX(*this,i) + if ((*this)(i)!=current) { get_columns(i0,i - 1).move_to(res); i0 = i; current = (*this)(i); } + get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + int i0 = 0; + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { get_rows(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,i); } + get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + int i0 = 0; + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { get_slices(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,i); } + get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + int i0 = 0; + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { get_channels(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,0,i); } + get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + longT i0 = 0; + cimg_foroff(*this,i) + if ((*this)[i]!=current) { + CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); + i0 = (longT)i; current = (*this)[i]; + } + CImg(_data + i0,1,(unsigned int)(size() - i0)).move_to(res); + } + } + } + return res; + } + + //! Split image into a list of sub-images, according to a specified splitting value sequence and optionally axis. + /** + \param values Splitting value sequence. + \param axis Axis along which the splitting is performed. Can be '0' to ignore axis. + \param keep_values Tells if the splitting sequence must be kept in the splitted blocs. + **/ + template + CImgList get_split(const CImg& values, const char axis=0, const bool keep_values=true) const { + 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 + const T value = (T)*values; + switch (_axis) { + case 'x' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_width && (*this)(i)==value) ++i; + if (i>i0) { if (keep_values) get_columns(i0,i - 1).move_to(res); i0 = i; } + while (i<_width && (*this)(i)!=value) ++i; + if (i>i0) { get_columns(i0,i - 1).move_to(res); i0 = i; } + } while (i<_width); + } break; + case 'y' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_height && (*this)(0,i)==value) ++i; + if (i>i0) { if (keep_values) get_rows(i0,i - 1).move_to(res); i0 = i; } + while (i<_height && (*this)(0,i)!=value) ++i; + if (i>i0) { get_rows(i0,i - 1).move_to(res); i0 = i; } + } while (i<_height); + } break; + case 'z' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_depth && (*this)(0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_slices(i0,i - 1).move_to(res); i0 = i; } + while (i<_depth && (*this)(0,0,i)!=value) ++i; + if (i>i0) { get_slices(i0,i - 1).move_to(res); i0 = i; } + } while (i<_depth); + } break; + case 'c' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_spectrum && (*this)(0,0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_channels(i0,i - 1).move_to(res); i0 = i; } + while (i<_spectrum && (*this)(0,0,0,i)!=value) ++i; + if (i>i0) { get_channels(i0,i - 1).move_to(res); i0 = i; } + } while (i<_spectrum); + } break; + default : { + const ulongT siz = size(); + ulongT i0 = 0, i = 0; + do { + while (ii0) { 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; + if (i>i1) { + if (i1>i0) get_columns(i0,i1 - 1).move_to(res); + if (keep_values) get_columns(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_width); + if (i0<_width) get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,i)==*values) { + i1 = i; j = 0; + while (i<_height && (*this)(0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_rows(i0,i1 - 1).move_to(res); + if (keep_values) get_rows(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_height); + if (i0<_height) get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,i)==*values) { + i1 = i; j = 0; + while (i<_depth && (*this)(0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_slices(i0,i1 - 1).move_to(res); + if (keep_values) get_slices(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_depth); + if (i0<_depth) get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,0,i)==*values) { + i1 = i; j = 0; + while (i<_spectrum && (*this)(0,0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_channels(i0,i1 - 1).move_to(res); + if (keep_values) get_channels(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_spectrum); + if (i0<_spectrum) get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + ulongT i0 = 0, i1 = 0, i = 0; + const ulongT siz = size(); + do { + if ((*this)[i]==*values) { + i1 = i; j = 0; + while (i=vsiz) j = 0; } + i-=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); + i0 = i; + } else ++i; + } else ++i; + } while (i(_data + i0,1,(unsigned int)(siz - i0)).move_to(res); + } break; + } + } + return res; + } + + //! Append two images along specified axis. + /** + \param img Image to append with instance image. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Append alignment in \c [0,1]. + **/ + template + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,true).insert(img).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \specialization. + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,img,true).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \const. + template + CImg<_cimg_Tt> get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList<_cimg_Tt>(*this,true).insert(img).get_append(axis,align); + } + + //! Append two images along specified axis \specialization. + CImg get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList(*this,img,true).get_append(axis,align); + } + + //@} + //--------------------------------------- + // + //! \name Filtering / Transforms + //@{ + //--------------------------------------- + + //! Correlate image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The correlation of the image instance \p *this by the kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} (*this)(x + i,y + j,z + k)*kernel(i,j,k). + **/ + template + CImg& correlate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_correlate(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + template + 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); + } + + //! 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; + 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>=(cimg_openmp_sizefactor)*32768, + is_outer_parallel = res_size>=(cimg_openmp_sizefactor)*32768; + _cimg_abort_init_omp; + cimg_abort_init; + + if (kernel._width==kernel._height && + ((kernel._depth==1 && kernel._width<=6) || (kernel._depth==kernel._width && kernel._width<=3))) { + + // 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); + + } 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(); + + 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; + } + } + } + } + + 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 cimg_openmp_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); + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0, N = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const Ttfloat _val = (Ttfloat)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); + val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm); + N+=_val*_val; + } + N*=M; + res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0); + } + } _cimg_abort_catch_omp2 + } else { // Classical correlation + cimg_pragma_openmp(parallel for cimg_openmp_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; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img._atXYZ(x + xm,y + ym,z + zm)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img.atXYZ(x + xm,y + ym,z + zm,0,(T)0)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + } + cimg_abort_test; + return res; + } + + //! Convolve image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The result \p res of the convolution of an image \p img by a kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*kernel(i,j,k) + **/ + template + CImg& convolve(const CImg& kernel, const bool boundary_conditions=true, const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_convolve(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + //! Convolve image by a kernel \newinstance. + template + 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); + } + + //! Cumulate image values, optionally along specified axis. + /** + \param axis Cumulation axis. Set it to 0 to cumulate all values globally without taking axes into account. + **/ + CImg& cumulate(const char axis=0) { + switch (cimg::lowercase(axis)) { + case 'x' : + 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; + cimg_forX(*this,x) { cumul+=(Tlong)*ptrd; *(ptrd++) = (T)cumul; } + } + break; + case 'y' : { + const ulongT w = (ulongT)_width; + 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; + cimg_forY(*this,y) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=w; } + } + } break; + case 'z' : { + const ulongT wh = (ulongT)_width*_height; + cimg_pragma_openmp(parallel for 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; + cimg_forZ(*this,z) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=wh; } + } + } break; + case 'c' : { + const ulongT whd = (ulongT)_width*_height*_depth; + cimg_pragma_openmp(parallel for 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 + Tlong cumul = (Tlong)0; + cimg_for(*this,ptrd,T) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; } + } + } + return *this; + } + + //! Cumulate image values, optionally along specified axis \newinstance. + CImg get_cumulate(const char axis=0) const { + return CImg(*this,false).cumulate(axis); + } + + //! Cumulate image values, along specified axes. + /** + \param axes Cumulation axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& cumulate(const char *const axes) { + for (const char *s = axes; *s; ++s) cumulate(*s); + return *this; + } + + //! Cumulate image values, along specified axes \newinstance. + CImg get_cumulate(const char *const axes) const { + return CImg(*this,false).cumulate(axes); + } + + //! Erode image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the erosion in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_erode(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Erode image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel) return *this; + if (!is_real && kernel==0) return CImg(width(),height(),depth(),spectrum(),0); + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2, + mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real erosion + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx1 + xm,my1 + ym,mz1 + zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) - mval); + if (cval=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); + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx1 + xm,my1 + ym,mz1 + zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval=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)) { + 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 + 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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val get_erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).erode(sx,sy,sz); + } + + //! Erode the image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& erode(const unsigned int s) { + return erode(s,s,s); + } + + //! Erode the image by a square structuring element of specified size \newinstance. + CImg get_erode(const unsigned int s) const { + return (+*this).erode(s); + } + + //! Dilate image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the dilation in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_dilate(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Dilate image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel || (!is_real && kernel==0)) return *this; + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx1 = kernel.width()/2, my1 = kernel.height()/2, mz1 = kernel.depth()/2, + mx2 = kernel.width() - mx1 - 1, my2 = kernel.height() - my1 - 1, mz2 = kernel.depth() - mz1 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx2 - xm,my2 - ym,mz2 - zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forYZ(*this,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,0,(T)0) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } else { // Binary dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx2 - xm,my2 - ym,mz2 - zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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,0,(T)0); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + cimg_abort_test; + return res; + } + + //! Dilate image by a rectangular structuring element of specified size. + /** + \param sx Width of the structuring element. + \param sy Height of the structuring element. + \param sz Depth of the structuring element. + **/ + CImg& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { + if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this; + if (sx>1 && _width>1) { // Along X-axis + const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(0,y,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sy>1 && _height>1) { // Along Y-axis + const int L = height(), off = width(), s = (int)sy, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,0,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sz>1 && _depth>1) { // Along Z-axis + const int L = depth(), off = width()*height(), s = (int)sz, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,y,0,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + return *this; + } + + //! Dilate image by a rectangular structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).dilate(sx,sy,sz); + } + + //! Dilate image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& dilate(const unsigned int s) { + return dilate(s,s,s); + } + + //! Dilate image by a square structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int s) const { + return (+*this).dilate(s); + } + + //! Compute watershed transform. + /** + \param priority Priority map. + \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity + in 2D case, and between 6(false)- or 26(true)-connectivity in 3D case. + \note Non-zero values of the instance instance are propagated to zero-valued ones according to + specified the priority map. + **/ + template + CImg& watershed(const CImg& priority, const bool is_high_connectivity=false) { +#define _cimg_watershed_init(cond,X,Y,Z) \ + if (cond && !(*this)(X,Y,Z)) Q._priority_queue_insert(labels,sizeQ,priority(X,Y,Z),X,Y,Z,nb_seeds) + +#define _cimg_watershed_propagate(cond,X,Y,Z) \ + if (cond) { \ + if ((*this)(X,Y,Z)) { \ + ns = labels(X,Y,Z) - 1; xs = seeds(ns,0); ys = seeds(ns,1); zs = seeds(ns,2); \ + d = cimg::sqr((float)x - xs) + cimg::sqr((float)y - ys) + cimg::sqr((float)z - zs); \ + if (d labels(_width,_height,_depth,1,0), seeds(64,3); + CImg::type> Q; + unsigned int sizeQ = 0; + int px, nx, py, ny, pz, nz; + bool is_px, is_nx, is_py, is_ny, is_pz, is_nz; + const bool is_3d = _depth>1; + + // Find seed points and insert them in priority queue. + unsigned int nb_seeds = 0; + const T *ptrs = _data; + cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) { // 3D version + if (nb_seeds>=seeds._width) seeds.resize(2*seeds._width,3,1,1,0); + seeds(nb_seeds,0) = x; seeds(nb_seeds,1) = y; seeds(nb_seeds++,2) = z; + px = x - 1; nx = x + 1; + py = y - 1; ny = y + 1; + pz = z - 1; nz = z + 1; + is_px = px>=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz::inf(); + T label = (T)0; + _cimg_watershed_propagate(is_px,px,y,z); + _cimg_watershed_propagate(is_nx,nx,y,z); + _cimg_watershed_propagate(is_py,x,py,z); + _cimg_watershed_propagate(is_ny,x,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_pz,x,y,pz); + _cimg_watershed_propagate(is_nz,x,y,nz); + } + if (is_high_connectivity) { + _cimg_watershed_propagate(is_px && is_py,px,py,z); + _cimg_watershed_propagate(is_nx && is_py,nx,py,z); + _cimg_watershed_propagate(is_px && is_ny,px,ny,z); + _cimg_watershed_propagate(is_nx && is_ny,nx,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_px && is_pz,px,y,pz); + _cimg_watershed_propagate(is_nx && is_pz,nx,y,pz); + _cimg_watershed_propagate(is_px && is_nz,px,y,nz); + _cimg_watershed_propagate(is_nx && is_nz,nx,y,nz); + _cimg_watershed_propagate(is_py && is_pz,x,py,pz); + _cimg_watershed_propagate(is_ny && is_pz,x,ny,pz); + _cimg_watershed_propagate(is_py && is_nz,x,py,nz); + _cimg_watershed_propagate(is_ny && is_nz,x,ny,nz); + _cimg_watershed_propagate(is_px && is_py && is_pz,px,py,pz); + _cimg_watershed_propagate(is_nx && is_py && is_pz,nx,py,pz); + _cimg_watershed_propagate(is_px && is_ny && is_pz,px,ny,pz); + _cimg_watershed_propagate(is_nx && is_ny && is_pz,nx,ny,pz); + _cimg_watershed_propagate(is_px && is_py && is_nz,px,py,nz); + _cimg_watershed_propagate(is_nx && is_py && is_nz,nx,py,nz); + _cimg_watershed_propagate(is_px && is_ny && is_nz,px,ny,nz); + _cimg_watershed_propagate(is_nx && is_ny && is_nz,nx,ny,nz); + } + } + (*this)(x,y,z) = label; + labels(x,y,z) = ++nmin; + } + return *this; + } + + //! Compute watershed transform \newinstance. + template + CImg get_watershed(const CImg& priority, const bool is_high_connectivity=false) const { + return (+*this).watershed(priority,is_high_connectivity); + } + + // [internal] Insert/Remove items in priority queue, for watershed/distance transforms. + template + bool _priority_queue_insert(CImg& is_queued, unsigned int& siz, const tv value, + const unsigned int x, const unsigned int y, const unsigned int z, + const unsigned int n=1) { + if (is_queued(x,y,z)) return false; + is_queued(x,y,z) = (tq)n; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; + (*this)(siz - 1,1) = (T)x; + (*this)(siz - 1,2) = (T)y; + (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); + cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); + cimg::swap((*this)(pos,3),(*this)(par,3)); + } + return true; + } + + CImg& _priority_queue_remove(unsigned int& siz) { + (*this)(0,0) = (*this)(--siz,0); + (*this)(0,1) = (*this)(siz,1); + (*this)(0,2) = (*this)(siz,2); + (*this)(0,3) = (*this)(siz,3); + const float value = (*this)(0,0); + for (unsigned int pos = 0, left = 0, right = 0; + ((right=2*(pos + 1),(left=right - 1))(*this)(right,0)) { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } else { + cimg::swap((*this)(pos,0),(*this)(right,0)); + cimg::swap((*this)(pos,1),(*this)(right,1)); + cimg::swap((*this)(pos,2),(*this)(right,2)); + cimg::swap((*this)(pos,3),(*this)(right,3)); + pos = right; + } + } else { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } + } + return *this; + } + + //! Apply recursive Deriche filter. + /** + \param sigma Standard deviation of the filter. + \param order Order of the filter. Can be { 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 }. + **/ + CImg& deriche(const float sigma, const unsigned int order=0, const char axis='x', + const bool boundary_conditions=true) { +#define _cimg_deriche_apply \ + CImg Y(N); \ + Tfloat *ptrY = Y._data, yb = 0, yp = 0; \ + T xp = (T)0; \ + if (boundary_conditions) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \ + for (int m = 0; m=0; --n) { \ + const T xc = *(ptrX-=off); \ + const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \ + xa = xn; xn = xc; ya = yn; yn = yc; \ + *ptrX = (T)(*(--ptrY)+yc); \ + } + const char naxis = cimg::lowercase(axis); + const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + if (is_empty() || (nsigma<0.1f && !order)) return *this; + const float + nnsigma = nsigma<0.1f?0.1f:nsigma, + alpha = 1.695f/nnsigma, + ema = (float)std::exp(-alpha), + ema2 = (float)std::exp(-2*alpha), + b1 = -2*ema, + b2 = ema2; + float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0; + switch (order) { + case 0 : { + const float k = (1-ema)*(1-ema)/(1 + 2*alpha*ema-ema2); + a0 = k; + a1 = k*(alpha - 1)*ema; + a2 = k*(alpha + 1)*ema; + a3 = -k*ema2; + } break; + case 1 : { + const float k = -(1-ema)*(1-ema)*(1-ema)/(2*(ema + 1)*ema); + a0 = a3 = 0; + a1 = k*ema; + a2 = -a1; + } break; + case 2 : { + const float + ea = (float)std::exp(-alpha), + k = -(ema2 - 1)/(2*alpha*ema), + kn = (-2*(-1 + 3*ea - 3*ea*ea + ea*ea*ea)/(3*ea + 1 + 3*ea*ea + ea*ea*ea)); + a0 = kn; + a1 = -kn*(1 + k*alpha)*ema; + a2 = kn*(1 - k*alpha)*ema; + a3 = -kn*ema2; + } break; + default : + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified filter order %u " + "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).", + cimg_instance, + order); + } + coefp = (a0 + a1)/(1 + b1 + b2); + coefn = (a2 + a3)/(1 + b1 + b2); + switch (naxis) { + case 'x' : { + const int N = width(); + const ulongT off = 1U; + cimg_pragma_openmp(parallel for 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 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 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 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; } + } + } + return *this; + } + + //! 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 { + return CImg(*this,false).deriche(sigma,order,axis,boundary_conditions); + } + + // [internal] Apply a recursive filter (used by CImg::vanvliet()). + /* + \param ptr the pointer of the data + \param filter the coefficient of the filter in the following order [n,n - 1,n - 2,n - 3]. + \param N size of the data + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive, 2nd derivative, 3rd derivative + \param boundary_conditions Boundary conditions. Can be { 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 double + 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. - 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.) * (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. - 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; + } + } 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; + } + } 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; + } + } break; + } + } + + //! Van Vliet recursive Gaussian filter. + /** + \param sigma standard deviation of the Gaussian filter + \param order the order of the filter 0,1,2,3 + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \note dirichlet boundary condition has a strange behavior + + I.T. Young, L.J. van Vliet, M. van Ginkel, Recursive Gabor filtering. + IEEE Trans. Sig. Proc., vol. 50, pp. 2799-2805, 2002. + + (this is an improvement over Young-Van Vliet, Sig. Proc. 44, 1995) + + Boundary conditions (only for order 0) using Triggs matrix, from + B. Triggs and M. Sdika. Boundary conditions for Young-van Vliet + recursive filtering. IEEE Trans. Signal Processing, + vol. 54, pp. 2365-2367, 2006. + **/ + CImg& vanvliet(const float sigma, const unsigned int order, const char axis='x', + const bool boundary_conditions=true) { + if (is_empty()) return *this; + 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; + const double + nnsigma = nsigma<0.5f?0.5f:nsigma, + m0 = 1.16680, m1 = 1.10783, m2 = 1.40586, + m1sq = m1 * m1, m2sq = m2 * m2, + q = (nnsigma<3.556?-0.2568 + 0.5784*nnsigma + 0.0561*nnsigma*nnsigma:2.5091 + 0.9804*(nnsigma - 3.556)), + qsq = q * q, + scale = (m0 + q) * (m1sq + m2sq + 2 * m1 * q + qsq), + b1 = -q * (2 * m0 * m1 + m1sq + m2sq + (2 * m0 + 4 * m1) * q + 3 * qsq) / scale, + b2 = qsq * (m0 + 2 * m1 + 3 * q) / scale, + b3 = -qsq * q / scale, + B = ( m0 * (m1sq + m2sq) ) / scale; + double filter[4]; + filter[0] = B; filter[1] = -b1; filter[2] = -b2; filter[3] = -b3; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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; + } + + //! 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 { + return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions); + } + + //! Blur image. + /** + \param sigma_x Standard deviation of the blur, along the X-axis. + \param sigma_y Standard deviation of the blur, along the Y-axis. + \param sigma_z Standard deviation of the blur, along the Z-axis. + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \param is_gaussian Tells if the blur uses a gaussian (\c true) or quasi-gaussian (\c false) kernel. + \note + - The blur is computed as a 0-order Deriche filter. This is not a gaussian blur. + - This is a recursive algorithm, not depending on the values of the standard deviations. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) { + if (is_empty()) return *this; + if (is_gaussian) { + if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions); + if (_height>1) vanvliet(sigma_y,0,'y',boundary_conditions); + if (_depth>1) vanvliet(sigma_z,0,'z',boundary_conditions); + } else { + if (_width>1) deriche(sigma_x,0,'x',boundary_conditions); + if (_height>1) deriche(sigma_y,0,'y',boundary_conditions); + if (_depth>1) deriche(sigma_z,0,'z',boundary_conditions); + } + return *this; + } + + //! Blur image \newinstance. + CImg get_blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*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. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) { + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur(nsigma,nsigma,nsigma,boundary_conditions,is_gaussian); + } + + //! Blur image isotropically \newinstance. + CImg get_blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*this,false).blur(sigma,boundary_conditions,is_gaussian); + } + + //! Blur image anisotropically, directed by a field of diffusion tensors. + /** + \param G Field of square roots of diffusion tensors/vectors used to drive the smoothing. + \param amplitude Amplitude of the smoothing. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + template + CImg& blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=1) { + + // Check arguments and init variables + if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6)) + throw CImgArgumentException(_cimg_instance + "blur_anisotropic(): Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).", + cimg_instance, + G._width,G._height,G._depth,G._spectrum,G._data); + if (is_empty() || dl<0) return *this; + const float namplitude = amplitude>=0?amplitude:-amplitude*cimg::max(_width,_height,_depth)/100; + 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; + + 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 + cimg_forZC(*this,z,c) { + cimg_abort_test; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + veloc = (Tfloat)(G(x,y,0,0)*ixx + 2*G(x,y,0,1)*ixy + G(x,y,0,2)*iyy); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + } + if (veloc_max>0) *this+=(velocity*=dl/veloc_max); + } + } else { // LIC-based smoothing + const ulongT whd = (ulongT)_width*_height*_depth; + const float sqrt2amplitude = (float)std::sqrt(2*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.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.f:datmp; + for (float theta = 0; theta<360; (theta+=da2),++N) { + const float + thetar = (float)(theta*cimg::PI/180), + vx = (float)(std::cos(thetar)*std::cos(phir)), + vy = (float)(std::sin(thetar)*std::cos(phir)), + vz = (float)std::sin(phir); + const t + *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2), + *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(0,0,0,3); + cimg_forXYZ(G,xg,yg,zg) { + const t a = *(pa++), b = *(pb++), c = *(pc++), d = *(pd++), e = *(pe++), f = *(pf++); + const float + u = (float)(a*vx + b*vy + c*vz), + v = (float)(b*vx + d*vy + e*vz), + w = (float)(c*vx + e*vy + f*vz), + n = 1e-5f + cimg::hypot(u,v,w), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)(w*dln); + *(pd3++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for 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_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,z,3), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y, + Z = (float)z; + switch (interpolation_type) { + case 0 : { // Nearest neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f), + cz = (int)(Z + 0.5f); + const float + u = (float)W(cx,cy,cz,0), + v = (float)W(cx,cy,cz,1), + w = (float)W(cx,cy,cz,2); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,cz,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,cz,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u = (float)(W._linear_atXYZ(X,Y,Z,0)), + v = (float)(W._linear_atXYZ(X,Y,Z,1)), + w = (float)(W._linear_atXYZ(X,Y,Z,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + default : { // 2nd order Runge Kutta + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)), + v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)), + w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2)), + u = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,0)), + v = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,1)), + w = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + } + Tfloat *ptrd = res.data(x,y,z); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,z,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + } 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); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2); + cimg_forXY(G,xg,yg) { + const t a = *(pa++), b = *(pb++), c = *(pc++); + const float + u = (float)(a*vx + b*vy), + v = (float)(b*vx + c*vy), + n = std::max(1e-5f,cimg::hypot(u,v)), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && _height>=2) + firstprivate(val)) + cimg_forY(*this,y) _cimg_abort_try_omp2 { + cimg_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,0,2), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y; + switch (interpolation_type) { + case 0 : { // Nearest-neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f); + const float + u = (float)W(cx,cy,0,0), + v = (float)W(cx,cy,0,1); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u = (float)(W._linear_atXY(X,Y,0,0)), + v = (float)(W._linear_atXY(X,Y,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + default : { // 2nd-order Runge-kutta interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)), + v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)), + u = (float)(W._linear_atXY(X + u0,Y + v0,0,0)), + v = (float)(W._linear_atXY(X + u0,Y + v0,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } + } + Tfloat *ptrd = res.data(x,y); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,0,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + const Tfloat *ptrs = res._data; + cimg_for(*this,ptrd,T) { + const Tfloat val = *(ptrs++)/N; + *ptrd = valval_max?val_max:(T)val); + } + } + cimg_abort_test; + return *this; + } + + //! Blur image anisotropically, directed by a field of diffusion tensors \newinstance. + template + CImg get_blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way. + /** + \param amplitude Amplitude of the smoothing. + \param sharpness Sharpness. + \param anisotropy Anisotropy. + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + CImg& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) { + const float nalpha = alpha>=0?alpha:-alpha*cimg::max(_width,_height,_depth)/100; + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur_anisotropic(get_diffusion_tensors(sharpness,anisotropy,nalpha,nsigma,interpolation_type!=3), + amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way \newinstance. + CImg get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, + const float da=30, const float gauss_prec=2, + const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec, + interpolation_type,is_fast_approx); + } + + //! Blur image, with the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_x Amount of blur along the X-axis. + \param sigma_y Amount of blur along the Y-axis. + \param sigma_z Amount of blur along the Z-axis. + \param sigma_r Amount of blur along the value axis. + \param sampling_x Amount of downsampling along the X-axis used for the approximation. + Defaults (0) to sigma_x. + \param sampling_y Amount of downsampling along the Y-axis used for the approximation. + Defaults (0) to sigma_y. + \param sampling_z Amount of downsampling along the Z-axis used for the approximation. + Defaults (0) to sigma_z. + \param sampling_r Amount of downsampling along the value axis used for the approximation. + Defaults (0) to sigma_r. + \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006 + (extended for 3D volumetric images). + It is based on the reference implementation http://people.csail.mit.edu/jiawen/software/bilateralFilter.m + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_bilateral(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || (!sigma_x && !sigma_y && !sigma_z)) return *this; + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return blur(sigma_x,sigma_y,sigma_z); + const float + edge_delta = (float)(edge_max - edge_min), + _sigma_x = sigma_x>=0?sigma_x:-sigma_x*_width/100, + _sigma_y = sigma_y>=0?sigma_y:-sigma_y*_height/100, + _sigma_z = sigma_z>=0?sigma_z:-sigma_z*_depth/100, + _sigma_r = sigma_r>=0?sigma_r:-sigma_r*(edge_max - edge_min)/100, + _sampling_x = sampling_x?sampling_x:std::max(_sigma_x,1.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, + derived_sigma_z = _sigma_z / _sampling_z, + derived_sigma_r = _sigma_r / _sampling_r; + const int + padding_x = (int)(2*derived_sigma_x) + 1, + padding_y = (int)(2*derived_sigma_y) + 1, + padding_z = (int)(2*derived_sigma_z) + 1, + padding_r = (int)(2*derived_sigma_r) + 1; + const unsigned int + bx = (unsigned int)((_width - 1)/_sampling_x + 1 + 2*padding_x), + by = (unsigned int)((_height - 1)/_sampling_y + 1 + 2*padding_y), + bz = (unsigned int)((_depth - 1)/_sampling_z + 1 + 2*padding_z), + br = (unsigned int)(edge_delta/_sampling_r + 1 + 2*padding_r); + if (bx>0 || by>0 || bz>0 || br>0) { + const bool is_3d = (_depth>1); + if (is_3d) { // 3D version of the algorithm + CImg bgrid(bx,by,bz,br), bgridw(bx,by,bz,br); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); bgridw.fill(0); + cimg_forXYZ(*this,x,y,z) { + const T val = (*this)(x,y,z,c); + const float edge = (float)_guide(x,y,z); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + Z = (int)cimg::round(z/_sampling_z) + padding_z, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,Z,R)+=(float)val; + bgridw(X,Y,Z,R)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + bgridw.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + + cimg_pragma_openmp(parallel for 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 + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + Z = z/_sampling_z + padding_z, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZC(X,Y,Z,R), bval1 = bgridw._linear_atXYZC(X,Y,Z,R); + (*this)(x,y,z,c) = (T)(bval0/bval1); + } + } + } else { // 2D version of the algorithm + CImg bgrid(bx,by,br,2); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); + cimg_forXY(*this,x,y) { + const T val = (*this)(x,y,c); + const float edge = (float)_guide(x,y); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,R,0)+=(float)val; + bgrid(X,Y,R,1)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,0,true).blur(0,0,derived_sigma_r,false); + + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(size(),4096)) + cimg_forXY(*this,x,y) { + const float edge = (float)_guide(x,y); + const float + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1); + (*this)(x,y,c) = (T)(bval0/bval1); + } + } + } + } + return *this; + } + + //! Blur image, with the joint bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) const { + return CImg(*this,false).blur_bilateral(guide,sigma_x,sigma_y,sigma_z,sigma_r, + sampling_x,sampling_y,sampling_z,sampling_r); + } + + //! Blur image using the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_r Amount of blur along the value axis. + \param sampling_s Amount of downsampling along the XYZ-axes used for the approximation. Defaults to sigma_s. + \param sampling_r Amount of downsampling along the value axis used for the approximation. Defaults to sigma_r. + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) { + const float _sigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100; + return blur_bilateral(guide,_sigma_s,_sigma_s,_sigma_s,sigma_r,sampling_s,sampling_s,sampling_s,sampling_r); + } + + //! Blur image using the bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) const { + return CImg(*this,false).blur_bilateral(guide,sigma_s,sigma_r,sampling_s,sampling_r); + } + + // [internal] Apply a box filter (used by CImg::boxfilter() and CImg::blur_box()). + /* + \param ptr the pointer of the data + \param N size of the data + \param boxsize Size of the box filter (can be subpixel). + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive and 2nd derivative. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + */ + static void _cimg_blur_box_apply(T *ptr, const float boxsize, const int N, const ulongT off, + const int order, const bool boundary_conditions, + const unsigned int nb_iter) { + // Smooth. + if (boxsize>1 && nb_iter) { + const int w2 = (int)(boxsize - 1)/2; + const unsigned int winsize = 2*w2 + 1U; + const double frac = (boxsize - winsize)/2.; + CImg win(winsize); + for (unsigned int iter = 0; iter=N) return boundary_conditions?ptr[(N - 1)*off]:T(); + return ptr[x*off]; + } + + // Apply box filter of order 0,1,2. + /** + \param boxsize Size of the box window (can be subpixel) + \param order the order of the filter 0,1 or 2. + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \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 nb_iter=1) { + if (is_empty() || !boxsize || (boxsize<=1 && !order)) return *this; + const char naxis = cimg::lowercase(axis); + const float nboxsize = boxsize>=0?boxsize:-boxsize* + (naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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); + } + } + return *this; + } + + // 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 nb_iter=1) const { + return CImg(*this,false).boxfilter(boxsize,order,axis,boundary_conditions,nb_iter); + } + + //! Blur image with a box filter. + /** + \param boxsize_x Size of the box window, along the X-axis (can be subpixel). + \param boxsize_y Size of the box window, along the Y-axis (can be subpixel). + \param boxsize_z Size of the box window, along the Z-axis (can be subpixel). + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \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 nb_iter=1) { + if (is_empty()) return *this; + if (_width>1) boxfilter(boxsize_x,0,'x',boundary_conditions,nb_iter); + if (_height>1) boxfilter(boxsize_y,0,'y',boundary_conditions,nb_iter); + if (_depth>1) boxfilter(boxsize_z,0,'z',boundary_conditions,nb_iter); + return *this; + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, + const bool boundary_conditions=true) 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 + \see deriche(), vanvliet(). + **/ + CImg& blur_box(const float boxsize, const bool boundary_conditions=true) { + const float nboxsize = boxsize>=0?boxsize:-boxsize*cimg::max(_width,_height,_depth)/100; + return blur_box(nboxsize,nboxsize,nboxsize,boundary_conditions); + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize, const bool boundary_conditions=true) const { + return CImg(*this,false).blur_box(boxsize,boundary_conditions); + } + + //! Blur image, with the image guided filter. + /** + \param guide Image used to guide the smoothing process. + \param radius Spatial radius. If negative, it is expressed as a percentage of the largest image size. + \param regularization Regularization parameter. + If negative, it is expressed as a percentage of the guide value range. + \note This method implements the filtering algorithm described in: + He, Kaiming; Sun, Jian; Tang, Xiaoou, "Guided Image Filtering," Pattern Analysis and Machine Intelligence, + IEEE Transactions on , vol.35, no.6, pp.1397,1409, June 2013 + **/ + template + CImg& blur_guided(const CImg& guide, const float radius, const float regularization) { + return get_blur_guided(guide,radius,regularization).move_to(*this); + } + + //! Blur image, with the image guided filter \newinstance. + template + CImg get_blur_guided(const CImg& guide, const float radius, const float regularization) const { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_guided(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || !radius) return *this; + const int _radius = radius>=0?(int)radius:(int)(-radius*cimg::max(_width,_height,_depth)/100); + float _regularization = regularization; + if (regularization<0) { + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return *this; + _regularization = -regularization*(edge_max - edge_min)/100; + } + _regularization = std::max(_regularization,0.01f); + const unsigned int psize = (unsigned int)(1 + 2*_radius); + CImg + mean_p = get_blur_box(psize,true), + mean_I = guide.get_blur_box(psize,true).resize(mean_p), + cov_Ip = get_mul(guide).blur_box(psize,true)-=mean_p.get_mul(mean_I), + var_I = guide.get_sqr().blur_box(psize,true)-=mean_I.get_sqr(), + &a = cov_Ip.div(var_I+=_regularization), + &b = mean_p-=a.get_mul(mean_I); + a.blur_box(psize,true); + b.blur_box(psize,true); + return a.mul(guide)+=b; + } + + //! Blur image using patch-based space. + /** + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_p Amount of blur along the value axis. + \param patch_size Size of the 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, + const unsigned int lookup_size=4, const float smoothness=0, const bool is_fast_approx=true) { + if (is_empty() || !patch_size || !lookup_size) return *this; + return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,is_fast_approx).move_to(*this); + } + + //! Blur image using patch-based space \newinstance. + CImg get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, + const unsigned int lookup_size=4, const float smoothness=0, + const bool is_fast_approx=true) const { + +#define _cimg_blur_patch3d_fast(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) \ + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch3d(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \ + alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch2d_fast(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) \ + if (cimg::abs((Tfloat)img(x,y,0,0) - (Tfloat)img(p,q,0,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + +#define _cimg_blur_patch2d(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, \ + alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + + if (is_empty() || !patch_size || !lookup_size) return +*this; + CImg 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 float + nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100, + sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p, + Pnorm = P.size()*sigma_p2; + const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1; + const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size; + cimg::unused(N2,N3); + if (_depth>1) switch (patch_size) { // 3D + case 2 : if (is_fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch3d_fast(3) else _cimg_blur_patch3d(3) break; + default : { + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) + private(P,Q)) + cimg_forXYZ(res,x,y,z) { // Fast + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + if (res._width>=32 && res._height*res._depth>=4) firstprivate(P,Q)) + cimg_forXYZ(res,x,y,z) { // Exact + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { + (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, dz = (float)z - r, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } + } + } else switch (patch_size) { // 2D + case 2 : if (is_fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break; + case 4 : if (is_fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break; + case 5 : if (is_fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break; + case 6 : if (is_fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break; + case 7 : if (is_fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break; + case 8 : if (is_fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break; + case 9 : if (is_fast_approx) _cimg_blur_patch2d_fast(9) else _cimg_blur_patch2d(9) break; + default : { // Fast + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Fast + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) + if ((Tfloat)cimg::abs(img(x,y,0) - (Tfloat)img(p,q,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Exact + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { + (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,0,c) = (Tfloat)((*this)(x,y,c)); + } + } + } + return res; + } + + //! Blur image with the median filter. + /** + \param n Size of the median filter. + \param threshold Threshold used to discard pixels too far from the current pixel value in the median computation. + **/ + CImg& blur_median(const unsigned int n, const float threshold=0) { + if (!n) return *this; + return get_blur_median(n,threshold).move_to(*this); + } + + //! Blur image with the median filter \newinstance. + CImg get_blur_median(const unsigned int n, const float threshold=0) const { + if (is_empty() || n<=1) return +*this; + CImg res(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg::unused(ptrd); + const int hr = (int)n/2, hl = n - hr - 1; + if (res._depth!=1) { // 3D + if (threshold>0) + cimg_pragma_openmp(parallel for 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + const Tfloat val0 = (Tfloat)(*this)(x,y,z,c); + CImg values(n*n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXYZ(*this,nx0,ny0,nz0,nx1,ny1,nz1,p,q,r) + if (cimg::abs((*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 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + res(x,y,z,c) = get_crop(nx0,ny0,nz0,c,nx1,ny1,nz1,c).median(); + } + } else { + if (threshold>0) + 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + const Tfloat val0 = (Tfloat)(*this)(x,y,c); + CImg values(n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXY(*this,nx0,ny0,nx1,ny1,p,q) + if (cimg::abs((*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 { + const int + w1 = width() - 1, h1 = height() - 1, + w2 = width() - 2, h2 = height() - 2, + w3 = width() - 3, h3 = height() - 3, + w4 = width() - 4, h4 = height() - 4; + switch (n) { // Without threshold + case 3 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(9); + cimg_for_in3x3(*this,1,1,w2,h2,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6],I[7],I[8]); + cimg_for_borderXY(*this,x,y,1) + res(x,y,c) = get_crop(std::max(0,x - 1),std::max(0,y - 1),0,c, + std::min(w1,x + 1),std::min(h1,y + 1),0,c).median(); + } + } break; + case 5 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(25); + cimg_for_in5x5(*this,2,2,w3,h3,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4], + I[5],I[6],I[7],I[8],I[9], + I[10],I[11],I[12],I[13],I[14], + I[15],I[16],I[17],I[18],I[19], + I[20],I[21],I[22],I[23],I[24]); + cimg_for_borderXY(*this,x,y,2) + res(x,y,c) = get_crop(std::max(0,x - 2),std::max(0,y - 2),0,c, + std::min(w1,x + 2),std::min(h1,y + 2),0,c).median(); + } + } break; + case 7 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(49); + cimg_for_in7x7(*this,3,3,w4,h4,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6], + I[7],I[8],I[9],I[10],I[11],I[12],I[13], + I[14],I[15],I[16],I[17],I[18],I[19],I[20], + I[21],I[22],I[23],I[24],I[25],I[26],I[27], + I[28],I[29],I[30],I[31],I[32],I[33],I[34], + I[35],I[36],I[37],I[38],I[39],I[40],I[41], + I[42],I[43],I[44],I[45],I[46],I[47],I[48]); + cimg_for_borderXY(*this,x,y,3) + res(x,y,c) = get_crop(std::max(0,x - 3),std::max(0,y - 3),0,c, + std::min(w1,x + 3),std::min(h1,y + 3),0,c).median(); + } + } break; + default : { + 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, + nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + res(x,y,c) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median(); + } + } + } + } + } + return res; + } + + //! Sharpen image. + /** + \param amplitude Sharpening amplitude + \param sharpen_type Select sharpening method. Can be { false=inverse diffusion | true=shock filters }. + \param edge Edge threshold (shock filters only). + \param alpha Gradient smoothness (shock filters only). + \param sigma Tensor smoothness (shock filters only). + **/ + CImg& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) { + if (is_empty()) return *this; + T val_min, val_max = max_min(val_min); + const float nedge = edge/2; + CImg velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum); + + 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 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); + CImg val, vec; + cimg_forX(G,x) { + G.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + if (val[2]<0) val[2] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = vec(0,2); + *(ptrG3++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1] + val[2],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + u = G(x,y,z,0), + v = G(x,y,z,1), + w = G(x,y,z,2), + amp = G(x,y,z,3), + ixx = Incc + Ipcc - 2*Iccc, + ixy = (Innc + Ippc - Inpc - Ipnc)/4, + ixz = (Incn + Ipcp - Incp - Ipcn)/4, + iyy = Icnc + Icpc - 2*Iccc, + iyz = (Icnn + Icpp - Icnp - Icpn)/4, + izz = Iccn + Iccp - 2*Iccc, + ixf = Incc - Iccc, + ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, + iyb = Iccc - Icpc, + izf = Iccn - Iccc, + izb = Iccc - Iccp, + itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else { // 2D + if (sharpen_type) { // Shock filters + CImg 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>=(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); + cimg_forX(G,x) { + G.get_tensor_at(x,y).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat + u = G(x,y,0), + v = G(x,y,1), + amp = G(x,y,2), + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + ixf = Inc - Icc, + ixb = Icc - Ipc, + iyf = Icn - Icc, + iyb = Icc - Icp, + itt = u*u*ixx + v*v*iyy + 2*u*v*ixy, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } + const Tfloat veloc_max = _veloc_max.max(); + if (veloc_max<=0) return *this; + return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this); + } + + //! Sharpen image \newinstance. + CImg get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) const { + return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma); + } + + //! Return image gradient. + /** + \param axes Axes considered for the gradient computation, as a C-string (e.g "xy"). + \param scheme = Numerical scheme used for the gradient computation: + - -1 = Backward finite differences + - 0 = Centered finite differences + - 1 = Forward finite differences + - 2 = Using Sobel kernels + - 3 = Using rotation invariant kernels + - 4 = Using Deriche recusrsive filter. + - 5 = Using Van Vliet recusrsive filter. + **/ + CImgList 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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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.f))), b = (Tfloat)(0.5f*(std::sqrt(2.f) - 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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 res; + for (unsigned int l = 0; axes[l]; ++l) { + const char axis = cimg::lowercase(axes[l]); + switch (axis) { + case 'x' : res.insert(grad[0]); break; + case 'y' : res.insert(grad[1]); break; + case 'z' : res.insert(grad[2]); break; + } + } + grad.assign(); + return res; + } + + //! Return image hessian. + /** + \param axes Axes considered for the hessian computation, as a C-string (e.g "xy"). + **/ + CImgList 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) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + + res.assign(lmax/2,_width,_height,_depth,_spectrum); + if (!cimg::strcasecmp(naxes,def_axes3d)) { // 3D + + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + const ulongT off = (ulongT)c*_width*_height*_depth; + Tfloat + *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off, + *ptrd3 = res[3]._data + off, *ptrd4 = res[4]._data + off, *ptrd5 = res[5]._data + off; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipcc + Incc - 2*Iccc; // Ixx + *(ptrd1++) = (Ippc + Innc - Ipnc - Inpc)/4; // Ixy + *(ptrd2++) = (Ipcp + Incn - Ipcn - Incp)/4; // Ixz + *(ptrd3++) = Icpc + Icnc - 2*Iccc; // Iyy + *(ptrd4++) = (Icpp + Icnn - Icpn - Icnp)/4; // Iyz + *(ptrd5++) = Iccn + Iccp - 2*Iccc; // Izz + } + } + } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // 2D + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; + Tfloat *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipc + Inc - 2*Icc; // Ixx + *(ptrd1++) = (Ipp + Inn - Ipn - Inp)/4; // Ixy + *(ptrd2++) = Icp + Icn - 2*Icc; // Iyy + } + } + } else for (unsigned int l = 0; laxis2) cimg::swap(axis1,axis2); + bool valid_axis = false; + if (axis1=='x' && axis2=='x') { // Ixx + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Ipc + Inc - 2*Icc; + } + } + else if (axis1=='x' && axis2=='y') { // Ixy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipp + Inn - Ipn - Inp)/4; + } + } + else if (axis1=='x' && axis2=='z') { // Ixz + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipcp + Incn - Ipcn - Incp)/4; + } + } + else if (axis1=='y' && axis2=='y') { // Iyy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Iccn + Iccp - 2*Iccc; + } + } + else if (!valid_axis) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + } + return res; + } + + //! Compute image Laplacian. + CImg& laplacian() { + return get_laplacian().move_to(*this); + } + + //! 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>=(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>=(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>=(cimg_openmp_sizefactor)*1048576 && + _height*_depth*_spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res.data(0,0,0,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc - 2*Icc; + } + } + return res; + } + + //! Compute the structure tensor field of an image. + /** + \param is_fwbw_scheme scheme. Can be { false=centered | true=forward-backward } + **/ + CImg& structure_tensors(const bool is_fwbw_scheme=false) { + return get_structure_tensors(is_fwbw_scheme).move_to(*this); + } + + //! Compute the structure tensor field of an image \newinstance. + CImg get_structure_tensors(const bool is_fwbw_scheme=false) const { + if (is_empty()) return *this; + CImg res; + if (_depth>1) { // 3D + res.assign(_width,_height,_depth,6,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ix = (Incc - Ipcc)/2, + iy = (Icnc - Icpc)/2, + iz = (Iccn - Iccp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=ix*iz; + *(ptrd3++)+=iy*iy; + *(ptrd4++)+=iy*iz; + *(ptrd5++)+=iz*iz; + } + } + } else { // Forward/backward finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixf = Incc - Iccc, ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, iyb = Iccc - Icpc, + izf = Iccn - Iccc, izb = Iccc - Iccp; + *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; + *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4; + *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4; + *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2; + *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4; + *(ptrd5++)+=(izf*izf + izb*izb)/2; + } + } + } + } else { // 2D + res.assign(_width,_height,_depth,3,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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 + ix = (Inc - Ipc)/2, + iy = (Icn - Icp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=iy*iy; + } + } + } else { // Forward/backward finite differences (version 2) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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; + } + } + } + } + return res; + } + + //! Compute field of diffusion tensors for edge-preserving smoothing. + /** + \param sharpness Sharpness + \param anisotropy Anisotropy + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param is_sqrt Tells if the square root of the tensor field is computed instead. + **/ + CImg& diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) { + CImg res; + const float + nsharpness = std::max(sharpness,1e-5f), + power1 = (is_sqrt?0.5f:1)*nsharpness, + power2 = power1/(1e-7f + 1 - anisotropy); + blur(alpha).normalize(0,(T)255); + + if (_depth>1) { // 3D + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for 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), + *ptrd3 = res.data(0,y,z,3), *ptrd4 = res.data(0,y,z,4), *ptrd5 = res.data(0,y,z,5); + CImg val(3), vec(3,3); + cimg_forX(*this,x) { + res.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + const float + _l1 = val[2], _l2 = val[1], _l3 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, l3 = _l3>0?_l3:0, + ux = vec(0,0), uy = vec(0,1), uz = vec(0,2), + vx = vec(1,0), vy = vec(1,1), vz = vec(1,2), + wx = vec(2,0), wy = vec(2,1), wz = vec(2,2), + n1 = (float)std::pow(1 + l1 + l2 + l3,-power1), + n2 = (float)std::pow(1 + l1 + l2 + l3,-power2); + *(ptrd0++) = n1*(ux*ux + vx*vx) + n2*wx*wx; + *(ptrd1++) = n1*(ux*uy + vx*vy) + n2*wx*wy; + *(ptrd2++) = n1*(ux*uz + vx*vz) + n2*wx*wz; + *(ptrd3++) = n1*(uy*uy + vy*vy) + n2*wy*wy; + *(ptrd4++) = n1*(uy*uz + vy*vz) + n2*wy*wz; + *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz; + } + } + } else { // for 2D images + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(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); + cimg_forX(*this,x) { + res.get_tensor_at(x,y).symmetric_eigen(val,vec); + const float + _l1 = val[1], _l2 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, + ux = vec(1,0), uy = vec(1,1), + vx = vec(0,0), vy = vec(0,1), + n1 = (float)std::pow(1 + l1 + l2,-power1), + n2 = (float)std::pow(1 + l1 + l2,-power2); + *(ptrd0++) = n1*ux*ux + n2*vx*vx; + *(ptrd1++) = n1*ux*uy + n2*vx*vy; + *(ptrd2++) = n1*uy*uy + n2*vy*vy; + } + } + } + return res.move_to(*this); + } + + //! Compute field of diffusion tensors for edge-preserving smoothing \newinstance. + CImg get_diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) const { + return CImg(*this,false).diffusion_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt); + } + + //! Estimate displacement field between two images. + /** + \param source Reference image. + \param smoothness Smoothness of estimated displacement field. + \param precision Precision required for algorithm convergence. + \param nb_scales Number of scales used to estimate the displacement field. + \param iteration_max Maximum number of iterations allowed for one scale. + \param is_backward If false, match I2(X + U(X)) = I1(X), else match I2(X) = I1(X - U(X)). + \param guide Image used as the initial correspondence estimate for the algorithm. + 'guide' may have a last channel with boolean values (0=false | other=true) that + tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask). + **/ + CImg& 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()) { + return get_displacement(source,smoothness,precision,nb_scales,iteration_max,is_backward,guide). + move_to(*this); + } + + //! Estimate displacement field between two images \newinstance. + CImg get_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()) const { + if (is_empty() || !source) return +*this; + if (!is_sameXYZC(source)) + throw CImgArgumentException(_cimg_instance + "displacement(): Instance and source image (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + source._width,source._height,source._depth,source._spectrum,source._data); + if (precision<0) + throw CImgArgumentException(_cimg_instance + "displacement(): Invalid specified precision %g " + "(should be >=0)", + cimg_instance, + precision); + + const bool is_3d = source._depth>1; + const unsigned int constraint = is_3d?3:2; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum0?nb_scales: + (unsigned int)cimg::round(std::log(mins/8.)/std::log(1.5),1,1); + + 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); + + CImg U, V; + floatT bound = 0; + for (int scale = (int)_nb_scales - 1; scale>=0; --scale) { + const float factor = (float)std::pow(1.5,(double)scale); + const unsigned int + _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1, + _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1, + _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1; + if (sw<5 && sh<5 && (!is_3d || sd<5)) continue; // Skip too small scales + const CImg + I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta, + I2 = (get_resize(I1,2)-=tm)/=tdelta; + if (guide._spectrum>constraint) guide.get_resize(I2._width,I2._height,I2._depth,-100,1).move_to(V); + if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3); + else { + if (guide) + guide.get_shared_channels(0,is_3d?2:1).get_resize(I2._width,I2._height,I2._depth,-100,2).move_to(U); + else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0); + } + + float dt = 2, energy = cimg::type::max(); + const CImgList dI = is_backward?I1.get_gradient():I2.get_gradient(); + cimg_abort_init; + + for (unsigned int iteration = 0; iteration=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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for 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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } + } + } else { // 2D version + if (smoothness>=0) // Isotropic regularization + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } + } + } + const float d_energy = (_energy - energy)/(sw*sh*sd); + if (d_energy<=0 && -d_energy<_precision) break; + if (d_energy>0) dt*=0.5f; + energy = _energy; + } + } + return U; + } + + //! Compute correspondence map between two images, using 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. + \param patch_height Height of the patch used for matching. + \param patch_depth Depth of the patch used for matching. + \param nb_iterations Number of patch-match iterations. + \param nb_randoms Number of randomization attempts (per pixel). + \param occ_penalization Penalization factor in score related patch occurrences. + \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. + **/ + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide,matching_score).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \newinstance. + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization, + guide,true,matching_score); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,guide,occ_penalization,false,CImg::empty()); + } + + template + 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 occ_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 + "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 + "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 + "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, + patch_image._data); + const unsigned int + _constraint = patch_image._depth>1?3:2, + constraint = guide._spectrum>_constraint?_constraint:0; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<_constraint)) + throw CImgArgumentException(_cimg_instance + "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 is_updated(_width,_height,_depth,1,3); + CImg score(_width,_height,_depth); + CImg occ, loop_order; + ulongT rng = (cimg::_rand(),cimg::rng()); + if (occ_penalization!=0) { + occ.assign(patch_image._width,patch_image._height,patch_image._depth,1,0); + loop_order.assign(_width,_height,_depth,_depth>1?3:2); + cimg_forXYZ(loop_order,x,y,z) { + loop_order(x,y,z,0) = x; + loop_order(x,y,z,1) = y; + if (loop_order._spectrum>2) loop_order(x,y,z,2) = z; + } + cimg_forXYZ(loop_order,x,y,z) { // Randomize loop order in case of constraints on patch occurrence + const unsigned int + X = (unsigned int)cimg::round(cimg::rand(loop_order._width - 1.,&rng)), + Y = (unsigned int)cimg::round(cimg::rand(loop_order._height - 1.,&rng)), + Z = loop_order._depth>1?(unsigned int)cimg::round(cimg::rand(loop_order._depth - 1.,&rng)):0U; + cimg::swap(loop_order(x,y,z,0),loop_order(X,Y,Z,0)); + cimg::swap(loop_order(x,y,z,1),loop_order(X,Y,Z,1)); + if (loop_order._spectrum>2) cimg::swap(loop_order(x,y,z,2),loop_order(X,Y,Z,2)); + } + } + 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 (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_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_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter2) z = loop_order(_x,_y,_z,2); else z = _z; + } else { x = _x; y = _y; 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 = map(x - 1,y,z,0); + v = map(x - 1,y,z,1); + w = map(x - 1,y,z,2); + if (u>=cx1 - 1 && u=cy1 && v=cz1 && w0 && (is_updated(x,y - 1,z)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,z,0); + v = map(x,y - 1,z,1); + w = map(x,y - 1,z,2); + if (u>=cx1 && u=cy1 - 1 && v=cz1 && w0 && (is_updated(x,y,z - 1)&cmask)) { // Compare with backward neighbor + u = map(x,y,z - 1,0); + v = map(x,y,z - 1,1); + w = map(x,y,z - 1,2); + if (u>=cx1 && u=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::inf()); + } else cimg_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter0 && (is_updated(x - 1,y)&cmask)) { // Compare with left neighbor + u = map(x - 1,y,0); + v = map(x - 1,y,1); + if (u>=cx1 - 1 && u=cy1 && v0 && (is_updated(x,y - 1)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,0); + v = map(x,y - 1,1); + if (u>=cx1 && u=cy1 - 1 && v=cx1 + 1 && u=cy1 && v=cx1 && u=cy1 + 1 && v& img1, const CImg& img2, const CImg& occ, + const unsigned int psizew, const unsigned int psizeh, + 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 occ_penalization, + const float max_score) { // 3D version + 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 - 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; + for (unsigned int k = 0; kmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + p1+=offy1; p2+=offy2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc,zc)); + } + + static float _matchpatch(const CImg& img1, const CImg& img2, const CImg& occ, + 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 occ_penalization, + const float max_score) { // 2D version + 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 - psizewc, + offx2 = (ulongT)img2._width - psizewc; + float ssd = 0; + for (unsigned int j = 0; jmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc)); + } + + //! Compute Euclidean distance function to a specified value. + /** + \param value Reference value. + \param metric Type of metric. Can be { 0=Chebyshev | 1=Manhattan | 2=Euclidean | 3=Squared-euclidean }. + \note + The distance transform implementation has been submitted by A. Meijster, and implements + the article 'W.H. Hesselink, A. Meijster, J.B.T.M. Roerdink, + "A general algorithm for computing distance transforms in linear time.", + In: Mathematical Morphology and its Applications to Image and Signal Processing, + J. Goutsias, L. Vincent, and D.S. Bloomberg (eds.), Kluwer, 2000, pp. 331-340.' + The submitted code has then been modified to fit CImg coding style and constraints. + **/ + CImg& distance(const T& value, const unsigned int metric=2) { + if (is_empty()) return *this; + if (cimg::type::string()!=cimg::type::string()) // 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 + } + return *this; + } + + //! Compute distance to a specified value \newinstance. + CImg get_distance(const T& value, const unsigned int metric=2) const { + return CImg(*this,false).distance((Tfloat)value,metric); + } + + static longT _distance_sep_edt(const longT i, const longT u, const longT *const g) { + return (u*u - i*i + g[u] - g[i])/(2*(u - i)); + } + + static longT _distance_dist_edt(const longT x, const longT i, const longT *const g) { + return (x - i)*(x - i) + g[i]; + } + + static longT _distance_sep_mdt(const longT i, const longT u, const longT *const g) { + return (u - i<=g[u] - g[i]?999999999:(g[u] - g[i] + u + i)/2); + } + + static longT _distance_dist_mdt(const longT x, const longT i, const longT *const g) { + return (x=0) && f(t[q],s[q],g)>f(t[q],u,g)) { --q; } + if (q<0) { q = 0; s[0] = u; } + else { const longT w = 1 + sep(s[q], u, g); if (w<(longT)len) { ++q; s[q] = u; t[q] = w; }} + } + for (int u = (int)len - 1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan + } + + CImg& _distance_core(longT (*const sep)(const longT, const longT, const longT *const), + longT (*const f)(const longT, const longT, const longT *const)) { + // Check for g++ 4.9.X, as OpenMP seems to crash for this particular function. I have no clues why. +#define cimg_is_gcc49x (__GNUC__==4 && __GNUC_MINOR__==9) + + const ulongT wh = (ulongT)_width*_height; +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) +#endif + cimg_forC(*this,c) { + CImg 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 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_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 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_forY(*this,y) g[y] = (longT)img(x,y,z,0,wh); + _distance_scan(_height,g,sep,f,s,t,dt); + cimg_forY(*this,y) img(x,y,z,0,wh) = (T)dt[y]; + } + } + if (_depth>1) { + g.assign(_depth); dt.assign(_depth); s.assign(_depth); t.assign(_depth); +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for 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_forZ(*this,z) g[z] = (longT)img(x,y,z,0,wh); + _distance_scan(_depth,g,sep,f,s,t,dt); + cimg_forZ(*this,z) img(x,y,z,0,wh) = (T)dt[z]; + } + } + } + return *this; + } + + //! Compute chamfer distance to a specified value, with a custom metric. + /** + \param value Reference value. + \param metric_mask Metric mask. + \note The algorithm code has been initially proposed by A. Meijster, and modified by D. Tschumperlé. + **/ + template + CImg& distance(const T& value, const CImg& metric_mask) { + if (is_empty()) return *this; + bool is_value = false; + cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,0:(T)999999999; + if (!is_value) return fill(cimg::type::max()); + const ulongT wh = (ulongT)_width*_height; + 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 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 y = height() - 1 - dy, ny = height() - 1; y>=0; --y,--ny) { + for (int x = width() - 1 - dx, nx = width() - 1; x>=0; --x,--nx) { + const T dd = img(nx,ny,nz,0,wh) + weight; + if (dd + CImg get_distance(const T& value, const CImg& metric_mask) const { + return CImg(*this,false).distance(value,metric_mask); + } + + //! Compute distance to a specified value, according to a custom metric (use dijkstra algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + \param is_high_connectivity Tells if the algorithm uses low or high connectivity. + \param[out] return_path An image containing the nodes of the minimal path. + **/ + template + CImg& distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) { + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm) \newinstance. + template + CImg::type> + get_distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) const { + if (is_empty()) return return_path.assign(); + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_dijkstra(): image instance and metric map (%u,%u,%u,%u) " + "have incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + typedef typename cimg::superset::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); + + cimg_forC(*this,c) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + CImg path = return_path?return_path.get_shared_channel(c):CImg(); + unsigned int sizeQ = 0; + + // Detect initial seeds. + is_queued.fill(0); + cimg_forXYZ(img,x,y,z) if (img(x,y,z)==value) { + Q._priority_queue_insert(is_queued,sizeQ,0,x,y,z); + res(x,y,z) = 0; + if (path) path(x,y,z) = (to)0; + } + + // Start distance propagation. + while (sizeQ) { + + // Get and remove point with minimal potential from the queue. + const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3); + const td P = (td)-Q(0,0); + Q._priority_queue_remove(sizeQ); + + // Update neighbors. + td npot = 0; + if (x - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x - 1,y,z) + P),x - 1,y,z)) { + res(x - 1,y,z) = npot; if (path) path(x - 1,y,z) = (to)2; + } + if (x + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y - 1,z) + P),x,y - 1,z)) { + res(x,y - 1,z) = npot; if (path) path(x,y - 1,z) = (to)8; + } + if (y + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y,z - 1) + P),x,y,z - 1)) { + res(x,y,z - 1) = npot; if (path) path(x,y,z - 1) = (to)32; + } + if (z + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y - 1,z) + P)),x - 1,y - 1,z)) { + res(x - 1,y - 1,z) = npot; if (path) path(x - 1,y - 1,z) = (to)10; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y - 1,z) + P)),x + 1,y - 1,z)) { + res(x + 1,y - 1,z) = npot; if (path) path(x + 1,y - 1,z) = (to)9; + } + if (x - 1>=0 && y + 1=0) { // Diagonal neighbors on slice z - 1 + if (x - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z - 1) + P)),x - 1,y,z - 1)) { + res(x - 1,y,z - 1) = npot; if (path) path(x - 1,y,z - 1) = (to)34; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z - 1) + P)),x,y - 1,z - 1)) { + res(x,y - 1,z - 1) = npot; if (path) path(x,y - 1,z - 1) = (to)40; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z - 1) + P)), + x - 1,y - 1,z - 1)) { + res(x - 1,y - 1,z - 1) = npot; if (path) path(x - 1,y - 1,z - 1) = (to)42; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z - 1) + P)), + x + 1,y - 1,z - 1)) { + res(x + 1,y - 1,z - 1) = npot; if (path) path(x + 1,y - 1,z - 1) = (to)41; + } + if (x - 1>=0 && y + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z + 1) + P)),x - 1,y,z + 1)) { + res(x - 1,y,z + 1) = npot; if (path) path(x - 1,y,z + 1) = (to)18; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z + 1) + P)),x,y - 1,z + 1)) { + res(x,y - 1,z + 1) = npot; if (path) path(x,y - 1,z + 1) = (to)24; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z + 1) + P)), + x - 1,y - 1,z + 1)) { + res(x - 1,y - 1,z + 1) = npot; if (path) path(x - 1,y - 1,z + 1) = (to)26; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z + 1) + P)), + x + 1,y - 1,z + 1)) { + res(x + 1,y - 1,z + 1) = npot; if (path) path(x + 1,y - 1,z + 1) = (to)25; + } + if (x - 1>=0 && y + 1 + CImg& distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) { + return get_distance_dijkstra(value,metric,is_high_connectivity).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm). \newinstance. + template + CImg get_distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) const { + CImg return_path; + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + **/ + template + CImg& distance_eikonal(const T& value, const CImg& metric) { + return get_distance_eikonal(value,metric).move_to(*this); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + template + CImg get_distance_eikonal(const T& value, const CImg& metric) const { + if (is_empty()) return *this; + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_eikonal(): image instance and metric map (%u,%u,%u,%u) have " + "incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + CImg result(_width,_height,_depth,_spectrum,cimg::type::max()), Q; + 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) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + unsigned int sizeQ = 0; + state.fill(-1); + + // Detect initial seeds. + Tfloat *ptr1 = res._data; char *ptr2 = state._data; + cimg_for(img,ptr0,T) { if (*ptr0==value) { *ptr1 = 0; *ptr2 = 1; } ++ptr1; ++ptr2; } + + // Initialize seeds neighbors. + ptr2 = state._data; + cimg_forXYZ(img,x,y,z) if (*(ptr2++)==1) { + if (x - 1>=0 && state(x - 1,y,z)==-1) { + const Tfloat dist = res(x - 1,y,z) = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x - 1,y,z); + } + if (x + 1=0 && state(x,y - 1,z)==-1) { + const Tfloat dist = res(x,y - 1,z) = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y - 1,z); + } + if (y + 1=0 && state(x,y,z - 1)==-1) { + const Tfloat dist = res(x,y,z - 1) = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z - 1); + } + if (z + 1=0) { + if (x - 1>=0 && state(x - 1,y,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + if (dist=0 && state(x,y - 1,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + if (dist=0 && state(x,y,z - 1)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + if (dist& res, const Tfloat P, + const int x=0, const int y=0, const int z=0) const { + const Tfloat M = (Tfloat)cimg::type::max(); + T T1 = (T)std::min(x - 1>=0?res(x - 1,y,z):M,x + 11) { // 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 + 1T2) cimg::swap(T1,T2); + if (T2>T3) cimg::swap(T2,T3); + if (T1>T2) cimg::swap(T1,T2); + if (P<=0) return (Tfloat)T1; + if (T31) { // 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 + void _eik_priority_queue_insert(CImg& state, unsigned int& siz, const t value, + const unsigned int x, const unsigned int y, const unsigned int z) { + if (state(x,y,z)>0) return; + state(x,y,z) = 0; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; (*this)(siz - 1,1) = (T)x; (*this)(siz - 1,2) = (T)y; (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); cimg::swap((*this)(pos,3),(*this)(par,3)); + } + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE. + /** + \param nb_iterations Number of PDE iterations. + \param band_size Size of the narrow band. + \param time_step Time step of the PDE iterations. + **/ + CImg& distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) { + if (is_empty()) return *this; + CImg velocity(*this,false); + for (unsigned int iteration = 0; iteration1) { // 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)0?(Incc - Iccc):(Iccc - Ipcc), + iy = gy*sgn>0?(Icnc - Iccc):(Iccc - Icpc), + iz = gz*sgn>0?(Iccn - Iccc):(Iccc - Iccp), + ng = 1e-5f + cimg::hypot(gx,gy,gz), + ngx = gx/ng, + ngy = gy/ng, + ngz = gz/ng, + veloc = sgn*(ngx*ix + ngy*iy + ngz*iz - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } else { // 2D version + CImg_3x3(I,Tfloat); + cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) if (band_size<=0 || cimg::abs(Icc)0?(Inc - Icc):(Icc - Ipc), + iy = gy*sgn>0?(Icn - Icc):(Icc - Icp), + ng = std::max((Tfloat)1e-5,cimg::hypot(gx,gy)), + ngx = gx/ng, + ngy = gy/ng, + veloc = sgn*(ngx*ix + ngy*iy - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } + if (veloc_max>0) *this+=(velocity*=time_step/veloc_max); + } + return *this; + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE \newinstance. + CImg get_distance_eikonal(const unsigned int nb_iterations, const float band_size=0, + const float time_step=0.5f) const { + return CImg(*this,false).distance_eikonal(nb_iterations,band_size,time_step); + } + + //! Compute Haar multiscale wavelet transform. + /** + \param axis Axis considered for the transform. + \param invert Set inverse of direct transform. + \param nb_scales Number of scales used for the transform. + **/ + CImg& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(axis,invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg 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.f); + if (nb_scales==1) { + switch (cimg::lowercase(axis)) { // Single scale transform + case 'x' : { + const unsigned int w = _width/2; + if (w) { + if ((w%2) && w!=1) + throw CImgInstanceException(_cimg_instance + "haar(): Sub-image width %u is not even.", + cimg_instance, + w); + + res.assign(_width,_height,_depth,_spectrum); + if (invert) cimg_forYZC(*this,y,z,c) { // Inverse transform along X + for (unsigned int x = 0, xw = w, x2 = 0; x& haar(const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg get_haar(const bool invert=false, const unsigned int nb_scales=1) const { + CImg res; + if (nb_scales==1) { // Single scale transform + if (_width>1) get_haar('x',invert,1).move_to(res); + if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); } + if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_to(res); } + if (res) return res; + } else { // Multi-scale transform + if (invert) { // Inverse transform + res.assign(*this,false); + if (_width>1) { + if (_height>1) { + if (_depth>1) { + unsigned int w = _width, h = _height, d = _depth; + for (unsigned int s = 1; w && h && d && s1) { + unsigned int w = _width, d = _depth; + for (unsigned int s = 1; w && d && s1) { + if (_depth>1) { + unsigned int h = _height, d = _depth; + for (unsigned int s = 1; h && d && s1) { + unsigned int d = _depth; + for (unsigned int s = 1; d && s1) { + if (_height>1) { + if (_depth>1) + for (unsigned int s = 1, w = _width/2, h = _height/2, d = _depth/2; w && h && d && s1) for (unsigned int s = 1, w = _width/2, d = _depth/2; w && d && s1) { + if (_depth>1) + for (unsigned int s = 1, h = _height/2, d = _depth/2; h && d && s1) for (unsigned int s = 1, d = _depth/2; d && s get_FFT(const char axis, const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],axis,is_invert); + return res; + } + + //! Compute n-d Fast Fourier Transform. + /* + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + CImgList get_FFT(const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],is_invert); + return res; + } + + //! Compute 1D Fast Fourier Transform, along a specified axis. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param axis Axis along which the FFT is computed. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + static void FFT(CImg& real, CImg& imag, const char axis, const bool is_invert=false) { + if (!real) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part is empty.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); +#ifdef cimg_use_fftw3 + cimg::mutex(12); + fftw_complex *data_in; + fftw_plan data_plan; + + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the X-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forYZC(real,y,z,c) { + T *ptrr = real.data(0,y,z,c), *ptri = imag.data(0,y,z,c); + double *ptrd = (double*)data_in; + cimg_forX(real,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); } + fftw_execute(data_plan); + const unsigned int fact = real._width; + if (is_invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); } + else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); } + } + } break; + case 'y' : { // Fourier along Y, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Y-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._height), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const unsigned int off = real._width; + cimg_forXZC(real,x,z,c) { + T *ptrr = real.data(x,0,z,c), *ptri = imag.data(x,0,z,c); + double *ptrd = (double*)data_in; + cimg_forY(real,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._height; + if (is_invert) + cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + case 'z' : { // Fourier along Z, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Z-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._depth), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const ulongT off = (ulongT)real._width*real._height; + cimg_forXYC(real,x,y,c) { + T *ptrr = real.data(x,y,0,c), *ptri = imag.data(x,y,0,c); + double *ptrd = (double*)data_in; + cimg_forZ(real,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._depth; + if (is_invert) + cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + default : + throw CImgArgumentException("CImgList<%s>::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } + fftw_destroy_plan(data_plan); + fftw_free(data_in); + cimg::mutex(12,0); +#else + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using built-in functions + const unsigned int N = real._width, N2 = N>>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the X-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forYZC(real,y,z,c) { + cimg::swap(real(i,y,z,c),real(j,y,z,c)); + cimg::swap(imag(i,y,z,c),imag(j,y,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = delta>>1; + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Y-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXZC(real,x,z,c) { + cimg::swap(real(x,i,z,c),real(x,j,z,c)); + cimg::swap(imag(x,i,z,c),imag(x,j,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Z-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXYC(real,x,y,c) { + cimg::swap(real(x,y,i,c),real(x,y,j,c)); + cimg::swap(imag(x,y,i,c),imag(x,y,j,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } +#endif + } + + //! Compute n-d Fast Fourier Transform. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + \param nb_threads Number of parallel threads used for the computation. + Use \c 0 to set this to the number of available cpus. + **/ + static void FFT(CImg& real, CImg& imag, const bool is_invert=false, const unsigned int nb_threads=0) { + if (!real) + throw CImgInstanceException("CImgList<%s>::FFT(): Empty specified real part.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); + +#ifdef cimg_use_fftw3 + cimg::mutex(12); +#ifndef cimg_use_fftw3_singlethread + const unsigned int _nb_threads = nb_threads?nb_threads:cimg::nb_cpus(); + static int fftw_st = fftw_init_threads(); + cimg::unused(fftw_st); + fftw_plan_with_nthreads(_nb_threads); +#else + cimg::unused(nb_threads); +#endif + fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u).", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width* + real._height*real._depth*real._spectrum), + real._width,real._height,real._depth,real._spectrum); + + fftw_plan data_plan; + const ulongT w = (ulongT)real._width, wh = w*real._height, whd = wh*real._depth; + data_plan = fftw_plan_dft_3d(real._width,real._height,real._depth,data_in,data_in, + is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forC(real,c) { + T *ptrr = real.data(0,0,0,c), *ptri = imag.data(0,0,0,c); + double *ptrd = (double*)data_in; + for (unsigned int x = 0; 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); +#endif + } + + //@} + //------------------------------------- + // + //! \name 3D Objects Management + //@{ + //------------------------------------- + + //! Shift 3D object's vertices. + /** + \param tx X-coordinate of the 3D displacement vector. + \param ty Y-coordinate of the 3D displacement vector. + \param tz Z-coordinate of the 3D displacement vector. + **/ + CImg& shift_object3d(const float tx, const float ty=0, const float tz=0) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "shift_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + get_shared_row(0)+=tx; get_shared_row(1)+=ty; get_shared_row(2)+=tz; + return *this; + } + + //! Shift 3D object's vertices \newinstance. + CImg 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. + /** + \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.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2; + return *this; + } + + //! Shift 3D object's vertices, so that it becomes centered \newinstance. + CImg get_shift_object3d() const { + return CImg(*this,false).shift_object3d(); + } + + //! Resize 3D object. + /** + \param sx Width of the 3D object's bounding box. + \param sy Height of the 3D object's bounding box. + \param sz Depth of the 3D object's bounding box. + **/ + CImg& resize_object3d(const float sx, const float sy=-100, const float sz=-100) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + if (xm0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; } + if (ym0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; } + if (zm0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; } + return *this; + } + + //! 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. + CImg resize_object3d() { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz); + if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; } + return *this; + } + + //! Resize 3D object to unit size \newinstance. + CImg get_resize_object3d() const { + return CImg(*this,false).resize_object3d(); + } + + //! Merge two 3D objects together. + /** + \param[in,out] primitives Primitives data of the current 3D object. + \param obj_vertices Vertices data of the additional 3D object. + \param obj_primitives Primitives data of the additional 3D object. + **/ + template + CImg& append_object3d(CImgList& primitives, const CImg& obj_vertices, + const CImgList& obj_primitives) { + if (!obj_vertices || !obj_primitives) return *this; + if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Specified vertice image (%u,%u,%u,%u,%p) is not a " + "set of 3D vertices.", + cimg_instance, + obj_vertices._width,obj_vertices._height, + obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data); + + if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); } + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + const unsigned int P = _width; + append(obj_vertices,'x'); + const unsigned int N = primitives._width; + primitives.insert(obj_primitives); + for (unsigned int i = N; i &p = primitives[i]; + switch (p.size()) { + case 1 : p[0]+=P; break; // Point + case 5 : p[0]+=P; p[1]+=P; break; // Sphere + case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment + case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle + case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle + } + } + return *this; + } + + //! Texturize primitives of a 3D object. + /** + \param[in,out] primitives Primitives data of the 3D object. + \param[in,out] colors Colors data of the 3D object. + \param texture Texture image to map to 3D object. + \param coords Texture-mapping coordinates. + **/ + template + const CImg& texturize_object3d(CImgList& primitives, CImgList& colors, + const CImg& texture, const CImg& coords=CImg::const_empty()) const { + if (is_empty()) return *this; + if (_height!=3) + throw CImgInstanceException(_cimg_instance + "texturize_object3d(): image instance is not a set of 3D points.", + cimg_instance); + if (coords && (coords._width!=_width || coords._height!=2)) + throw CImgArgumentException(_cimg_instance + "texturize_object3d(): Invalid specified texture coordinates (%u,%u,%u,%u,%p).", + cimg_instance, + coords._width,coords._height,coords._depth,coords._spectrum,coords._data); + CImg _coords; + if (!coords) { // If no texture coordinates specified, do a default XY-projection + _coords.assign(_width,2); + float + xmin, xmax = (float)get_shared_row(0).max_min(xmin), + ymin, ymax = (float)get_shared_row(1).max_min(ymin), + dx = xmax>xmin?xmax-xmin:1, + dy = ymax>ymin?ymax-ymin:1; + cimg_forX(*this,p) { + _coords(p,0) = (int)(((*this)(p,0) - xmin)*texture._width/dx); + _coords(p,1) = (int)(((*this)(p,1) - ymin)*texture._height/dy); + } + } else _coords = coords; + + int texture_ind = -1; + cimglist_for(primitives,l) { + CImg &p = primitives[l]; + const unsigned int siz = p.size(); + switch (siz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)p[0]; + const int x0 = _coords(i0,0), y0 = _coords(i0,1); + texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0).move_to(colors[l]); + } break; + case 2 : case 6 : { // Line + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,x0,y0,x1,y1).move_to(p); + } break; + case 3 : case 9 : { // Triangle + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p); + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2], i3 = (unsigned int)p[3]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1), + x3 = _coords(i3,0), y3 = _coords(i3,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,i3,x0,y0,x1,y1,x2,y2,x3,y3).move_to(p); + } break; + } + } + return *this; + } + + //! Generate a 3D elevation of the image instance. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param[out] colors The returned list of the 3D object colors. + \param elevation The input elevation map. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + CImgList colors3d; + const CImg points3d = img.get_elevation3d(faces3d,colors3d,img.get_norm()*0.2); + CImg().display_object3d("Elevation3d",points3d,faces3d,colors3d); + \endcode + \image html ref_elevation3d.jpg + **/ + template + CImg get_elevation3d(CImgList& primitives, CImgList& colors, const CImg& elevation) const { + if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1) + throw CImgArgumentException(_cimg_instance + "get_elevation3d(): Instance and specified elevation (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + elevation._width,elevation._height,elevation._depth, + elevation._spectrum,elevation._data); + if (is_empty()) return *this; + float m, M = (float)max_min(m); + if (M==m) ++M; + colors.assign(); + const unsigned int size_x1 = _width - 1, size_y1 = _height - 1; + for (unsigned int y = 0; y1?((*this)(x,y,1) - m)*255/(M-m):r), + b = (unsigned char)(_spectrum>2?((*this)(x,y,2) - m)*255/(M-m):_spectrum>1?0:r); + CImg::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.f,_height - 1.f,_width,_height); + } + + //! Generate the 3D projection planes of the image instance. + /** + \param[out] primitives Primitives data of the returned 3D object. + \param[out] colors Colors data of the returned 3D object. + \param x0 X-coordinate of the projection point. + \param y0 Y-coordinate of the projection point. + \param z0 Z-coordinate of the projection point. + \param normalize_colors Tells if the created textures have normalized colors. + **/ + template + CImg get_projections3d(CImgList& primitives, CImgList& colors, + const unsigned int x0, const unsigned int y0, const unsigned int z0, + const bool normalize_colors=false) const { + float m = 0, M = 0, delta = 1; + if (normalize_colors) { m = (float)min_max(M); delta = 255/(m==M?1:M-m); } + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + CImg img_xy, img_xz, img_yz; + if (normalize_colors) { + ((get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1)-=m)*=delta).move_to(img_xy); + ((get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_width,_depth,1,-100,-1). + move_to(img_xz); + ((get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_height,_depth,1,-100,-1). + move_to(img_yz); + } else { + get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1).move_to(img_xy); + get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1).move_to(img_xz); + get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).resize(_height,_depth,1,-100,-1).move_to(img_yz); + } + CImg points(12,3,1,1, + 0,_width - 1,_width - 1,0, 0,_width - 1,_width - 1,0, _x0,_x0,_x0,_x0, + 0,0,_height - 1,_height - 1, _y0,_y0,_y0,_y0, 0,_height - 1,_height - 1,0, + _z0,_z0,_z0,_z0, 0,0,_depth - 1,_depth - 1, 0,0,_depth - 1,_depth - 1); + primitives.assign(); + CImg::vector(0,1,2,3,0,0,img_xy._width - 1,0,img_xy._width - 1,img_xy._height - 1,0,img_xy._height - 1). + move_to(primitives); + CImg::vector(4,5,6,7,0,0,img_xz._width - 1,0,img_xz._width - 1,img_xz._height - 1,0,img_xz._height - 1). + move_to(primitives); + CImg::vector(8,9,10,11,0,0,img_yz._width - 1,0,img_yz._width - 1,img_yz._height - 1,0,img_yz._height - 1). + move_to(primitives); + colors.assign(); + img_xy.move_to(colors); + img_xz.move_to(colors); + img_yz.move_to(colors); + return points; + } + + //! Generate a isoline of the image instance as a 3D object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x The number of subdivisions along the X-axis. + \param size_y The number of subdisivions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + const CImg points3d = img.get_isoline3d(faces3d,100); + CImg().display_object3d("Isoline3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isoline3d.jpg + **/ + template + CImg get_isoline3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a scalar image.", + cimg_instance); + if (_depth>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a 2D image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.f,height() - 1.f,width(),height()); + } else { + const _functor2d_float func(*this); + 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. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x Number of subdivisions along the X-axis. + \param size_y Number of subdisivions along the Y-axis. + \param size_z Number of subdisivions along the Z-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img = CImg("reference.jpg").resize(-100,-100,20); + CImgList faces3d; + const CImg points3d = img.get_isosurface3d(faces3d,100); + CImg().display_object3d("Isosurface3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isosurface3d.jpg + **/ + template + CImg get_isosurface3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100, const int size_z=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isosurface3d(): Instance is not a scalar image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.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.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. + /** + \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 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 CImg isosurface3d(CImgList& primitives, const tfunc& func, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int size_x=32, const int size_y=32, const int size_z=32) { + static const unsigned int edges[256] = { + 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 + }; + + static const int triangles[256][16] = { + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 }, + { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 }, + { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 }, + { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 }, + { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 }, + { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 }, + { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, + { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, + { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, + { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 }, + { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, + { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 }, + { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, + { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 }, + { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, + { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 }, + { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, + { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 }, + { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 }, + { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, + { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 }, + { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 }, + { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 }, + { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 }, + { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, + { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 }, + { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 }, + { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, + { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 }, + { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 }, + { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, + { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 }, + { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, + { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, + { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 }, + { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 }, + { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 }, + { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, + { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 }, + { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 }, + { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, + { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 }, + { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, + { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, + { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, + { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 }, + { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 }, + { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, + { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 }, + { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 }, + { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, + { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 }, + { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, + { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 }, + { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, + { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 }, + { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 }, + { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, + { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, + { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 }, + { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, + { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 }, + { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, + { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, + { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, + { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 }, + { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 }, + { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 }, + { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, + { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 }, + { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, + { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 }, + { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, + { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 }, + { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, + { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 }, + { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, + { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, + { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, + { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, + { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 }, + { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, + { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 }, + { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, + { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 }, + { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 }, + { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, + { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 }, + { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, + { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 }, + { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 }, + { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, + { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } + }; + + const unsigned int + _nx = (unsigned int)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)), + _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)), + _nz = (unsigned int)(size_z>=0?size_z:cimg::round((z1-z0)*-size_z/100 + 1)), + nx = _nx?_nx:1, + ny = _ny?_ny:1, + nz = _nz?_nz:1, + nxm1 = nx - 1, + nym1 = ny - 1, + nzm1 = nz - 1; + primitives.assign(); + if (!nxm1 || !nym1 || !nzm1) return CImg(); + 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; + + // Fill the first plane with function values + Y = y0; + cimg_forY(values1,y) { + X = x0; + cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=dx; } + Y+=dy; + } + + // Run Marching Cubes algorithm + Z = z0; nZ = Z + dz; + for (unsigned int zi = 0; zi::vector(Xi,Y,Z).move_to(vertices); + } + if ((edge&2) && indices1(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val1)*dy/(val2-val1); + indices1(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,Z).move_to(vertices); + } + if ((edge&4) && indices1(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val3)*dx/(val2-val3); + indices1(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,Z).move_to(vertices); + } + if ((edge&8) && indices1(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val0)*dy/(val3-val0); + indices1(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,Z).move_to(vertices); + } + if ((edge&16) && indices2(xi,yi,0)<0) { + const float Xi = X + (isovalue-val4)*dx/(val5-val4); + indices2(xi,yi,0) = vertices.width(); + CImg::vector(Xi,Y,nZ).move_to(vertices); + } + if ((edge&32) && indices2(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val5)*dy/(val6-val5); + indices2(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,nZ).move_to(vertices); + } + if ((edge&64) && indices2(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val7)*dx/(val6-val7); + indices2(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,nZ).move_to(vertices); + } + if ((edge&128) && indices2(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val4)*dy/(val7-val4); + indices2(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,nZ).move_to(vertices); + } + if ((edge&256) && indices1(xi,yi,2)<0) { + const float Zi = Z+ (isovalue-val0)*dz/(val4-val0); + indices1(xi,yi,2) = vertices.width(); + CImg::vector(X,Y,Zi).move_to(vertices); + } + if ((edge&512) && indices1(nxi,yi,2)<0) { + const float Zi = Z + (isovalue-val1)*dz/(val5-val1); + indices1(nxi,yi,2) = vertices.width(); + CImg::vector(nX,Y,Zi).move_to(vertices); + } + if ((edge&1024) && indices1(nxi,nyi,2)<0) { + const float Zi = Z + (isovalue-val2)*dz/(val6-val2); + indices1(nxi,nyi,2) = vertices.width(); + CImg::vector(nX,nY,Zi).move_to(vertices); + } + if ((edge&2048) && indices1(xi,nyi,2)<0) { + const float Zi = Z + (isovalue-val3)*dz/(val7-val3); + indices1(xi,nyi,2) = vertices.width(); + CImg::vector(X,nY,Zi).move_to(vertices); + } + + // Create triangles + for (const int *triangle = triangles[configuration]; *triangle!=-1; ) { + const unsigned int + p0 = (unsigned int)*(triangle++), + p1 = (unsigned int)*(triangle++), + p2 = (unsigned int)*(triangle++); + const tf + i0 = (tf)(_isosurface3d_indice(p0,indices1,indices2,xi,yi,nxi,nyi)), + i1 = (tf)(_isosurface3d_indice(p1,indices1,indices2,xi,yi,nxi,nyi)), + i2 = (tf)(_isosurface3d_indice(p2,indices1,indices2,xi,yi,nxi,nyi)); + CImg::vector(i0,i2,i1).move_to(primitives); + } + } + } + } + cimg::swap(values1,values2); + cimg::swap(indices1,indices2); + } + return vertices>'x'; + } + + //! Compute isosurface of a function, as a 3D object \overloading. + template + static CImg isosurface3d(CImgList& primitives, const char *const expression, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int dx=32, const int dy=32, const int dz=32) { + const _functor3d_expr func(expression); + return isosurface3d(primitives,func,isovalue,x0,y0,z0,x1,y1,z1,dx,dy,dz); + } + + template + static int _isosurface3d_indice(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) { + case 0 : return indices1(x,y,0); + case 1 : return indices1(nx,y,1); + case 2 : return indices1(x,ny,0); + case 3 : return indices1(x,y,1); + case 4 : return indices2(x,y,0); + case 5 : return indices2(nx,y,1); + case 6 : return indices2(x,ny,0); + case 7 : return indices2(x,y,1); + case 8 : return indices1(x,y,2); + case 9 : return indices1(nx,y,2); + case 10 : return indices1(nx,ny,2); + case 11 : return indices1(x,ny,2); + } + return 0; + } + + // Define functors for accessing image values (used in previous functions). + struct _functor2d_int { + const CImg& ref; + _functor2d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref((int)x,(int)y); + } + }; + + struct _functor2d_float { + const CImg& ref; + _functor2d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref._linear_atXY(x,y); + } + }; + + struct _functor2d_expr { + _cimg_math_parser *mp; + ~_functor2d_expr() { mp->end(); delete mp; } + _functor2d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y) const { + return (float)(*mp)(x,y,0,0); + } + }; + + struct _functor3d_int { + const CImg& ref; + _functor3d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref((int)x,(int)y,(int)z); + } + }; + + struct _functor3d_float { + const CImg& ref; + _functor3d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref._linear_atXYZ(x,y,z); + } + }; + + struct _functor3d_expr { + _cimg_math_parser *mp; + ~_functor3d_expr() { mp->end(); delete mp; } + _functor3d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z) const { + return (float)(*mp)(x,y,z,0); + } + }; + + struct _functor4d_int { + const CImg& ref; + _functor4d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref((int)x,(int)y,(int)z,c); + } + }; + + //! Generate a 3D box object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the box (dimension along the X-axis). + \param size_y The height of the box (dimension along the Y-axis). + \param size_z The depth of the box (dimension along the Z-axis). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::box3d(faces3d,10,20,30); + CImg().display_object3d("Box3d",points3d,faces3d); + \endcode + \image html ref_box3d.jpg + **/ + template + static CImg box3d(CImgList& primitives, + const float size_x=200, const float size_y=100, const float size_z=100) { + primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5); + return CImg(8,3,1,1, + 0.,size_x,size_x, 0., 0.,size_x,size_x, 0., + 0., 0.,size_y,size_y, 0., 0.,size_y,size_y, + 0., 0., 0., 0.,size_z,size_z,size_z,size_z); + } + + //! Generate a 3D cone. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the cone basis. + \param size_z The cone's height. + \param subdivisions The number of basis angular subdivisions. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cone3d(faces3d,50); + CImg().display_object3d("Cone3d",points3d,faces3d); + \endcode + \image html ref_cone3d.jpg + **/ + template + static CImg cone3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,size_z, + 0.,0.,0.); + 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. + /** + \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). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cylinder3d(faces3d,50); + CImg().display_object3d("Cylinder3d",points3d,faces3d); + \endcode + \image html ref_cylinder3d.jpg + **/ + template + static CImg cylinder3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,0., + 0.,0.,size_z); + 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); + } + return vertices>'x'; + } + + //! Generate a 3D torus. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius1 The large radius. + \param radius2 The small radius. + \param subdivisions1 The number of angular subdivisions for the large radius. + \param subdivisions2 The number of angular subdivisions for the small radius. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::torus3d(faces3d,20,4); + CImg().display_object3d("Torus3d",points3d,faces3d); + \endcode + \image html ref_torus3d.jpg + **/ + template + static CImg torus3d(CImgList& primitives, + const float radius1=100, const float radius2=30, + const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) { + primitives.assign(); + if (!subdivisions1 || !subdivisions2) return CImg(); + 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); + } + } + return vertices>'x'; + } + + //! Generate a 3D XY-plane. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the plane (dimension along the X-axis). + \param size_y The height of the plane (dimensions along the Y-axis). + \param subdivisions_x The number of planar subdivisions along the X-axis. + \param subdivisions_y The number of planar subdivisions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::plane3d(faces3d,100,50); + CImg().display_object3d("Plane3d",points3d,faces3d); + \endcode + \image html ref_plane3d.jpg + **/ + template + static CImg plane3d(CImgList& primitives, + const float size_x=100, const float size_y=100, + const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) { + primitives.assign(); + if (!subdivisions_x || !subdivisions_y) return CImg(); + CImgList vertices; + const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1; + const float fx = (float)size_x/w, fy = (float)size_y/h; + for (unsigned int y = 0; y::vector(fx*x,fy*y,0).move_to(vertices); + for (unsigned int y = 0; y::vector(off1,off4,off3,off2).move_to(primitives); + } + return vertices>'x'; + } + + //! Generate a 3D sphere. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the sphere (dimension along the X-axis). + \param subdivisions The number of recursive subdivisions from an initial icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::sphere3d(faces3d,100,4); + CImg().display_object3d("Sphere3d",points3d,faces3d); + \endcode + \image html ref_sphere3d.jpg + **/ + template + static CImg sphere3d(CImgList& primitives, + const float radius=50, const unsigned int subdivisions=3) { + + // Create initial icosahedron + primitives.assign(); + 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); + // edge - length/2 + float he = (float)a; + + // Recurse subdivisions + for (unsigned int i = 0; i::vector(nx0,ny0,nz0).move_to(vertices); i0 = vertices.width() - 1; } + if (i1<0) { CImg::vector(nx1,ny1,nz1).move_to(vertices); i1 = vertices.width() - 1; } + if (i2<0) { CImg::vector(nx2,ny2,nz2).move_to(vertices); i2 = vertices.width() - 1; } + primitives.remove(0); + CImg::vector(p0,i0,i1).move_to(primitives); + CImg::vector((tf)i0,(tf)p1,(tf)i2).move_to(primitives); + CImg::vector((tf)i1,(tf)i2,(tf)p2).move_to(primitives); + CImg::vector((tf)i1,(tf)i0,(tf)i2).move_to(primitives); + } + } + return (vertices>'x')*=radius; + } + + //! Generate a 3D ellipsoid. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param tensor The tensor which gives the shape and size of the ellipsoid. + \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg tensor = CImg::diagonal(10,7,3), + points3d = CImg::ellipsoid3d(faces3d,tensor,4); + CImg().display_object3d("Ellipsoid3d",points3d,faces3d); + \endcode + \image html ref_ellipsoid3d.jpg + **/ + template + static CImg ellipsoid3d(CImgList& primitives, + const CImg& tensor, const unsigned int subdivisions=3) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImg S, V; + tensor.symmetric_eigen(S,V); + const float orient = + (V(0,1)*V(1,2) - V(0,2)*V(1,1))*V(2,0) + + (V(0,2)*V(1,0) - V(0,0)*V(1,2))*V(2,1) + + (V(0,0)*V(1,1) - V(0,1)*V(1,0))*V(2,2); + if (orient<0) { V(2,0) = -V(2,0); V(2,1) = -V(2,1); V(2,2) = -V(2,2); } + const float l0 = S[0], l1 = S[1], l2 = S[2]; + CImg 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. + /** + \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, + const CImgList& colors, + const to& opacities, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg& object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + 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. + CImg& object3dtoCImg3d(const bool full_check=true) { + return get_object3dtoCImg3d(full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \newinstance. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true) const { + 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).", + cimg_instance,_width,primitives._width,error_message.data()); + CImg res(1,_size_object3dtoCImg3d(primitives,colors,opacities)); + float *ptrd = res._data; + + // Put magick number. + *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f; + *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f; + + // Put number of vertices and primitives. + *(ptrd++) = cimg::uint2float(_width); + *(ptrd++) = cimg::uint2float(primitives._width); + + // Put vertex data. + if (is_empty() || !primitives) return res; + const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2); + cimg_forX(*this,p) { + *(ptrd++) = (float)*(ptrx++); + *(ptrd++) = (float)*(ptry++); + *(ptrd++) = (float)*(ptrz++); + } + + // Put primitive data. + cimglist_for(primitives,p) { + *(ptrd++) = (float)primitives[p].size(); + const tp *ptrp = primitives[p]._data; + cimg_foroff(primitives[p],i) *(ptrd++) = cimg::uint2float((unsigned int)*(ptrp++)); + } + + // Put color/texture data. + const unsigned int csiz = std::min(colors._width,primitives._width); + for (int c = 0; c<(int)csiz; ++c) { + const CImg& color = colors[c]; + const tc *ptrc = color._data; + if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; } + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (color.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImgList& opacities, float *ptrd) const { + cimglist_for(opacities,o) { + const CImg& opacity = opacities[o]; + const to *ptro = opacity._data; + if (opacity.size()==1) *(ptrd++) = (float)*ptro; + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (opacity.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImg& opacities, float *ptrd) const { + const to *ptro = opacities._data; + cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++); + return ptrd; + } + + template + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImgList& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + if (colors[c].is_shared()) siz+=4; + else { const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; } + } + if (colors._width + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImg& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; + } + if (colors._width + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) const { + CImgList opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const bool full_check=true) const { + CImgList colors, opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! 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); + cimglist_for(primitives,p) primitives(p,0) = p; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert CImg3d representation into a 3D object. + /** + \param[out] primitives Primitives data of the 3D object. + \param[out] colors Colors data of the 3D object. + \param[out] opacities Opacities data of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + **/ + template + CImg& CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) { + return get_CImg3dtoobject3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert CImg3d representation into a 3D object \newinstance. + template + CImg get_CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) const { + CImg error_message(1024); + if (!is_CImg3d(full_check,error_message)) + throw CImgInstanceException(_cimg_instance + "CImg3dtoobject3d(): image instance is not a CImg3d (%s).", + cimg_instance,error_message.data()); + const T *ptrs = _data + 6; + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + const CImg points = CImg(ptrs,3,nb_points,1,1,true).get_transpose(); + ptrs+=3*nb_points; + primitives.assign(nb_primitives); + cimglist_for(primitives,p) { + const unsigned int nb_inds = (unsigned int)*(ptrs++); + primitives[p].assign(1,nb_inds); + tp *ptrp = primitives[p]._data; + for (unsigned int i = 0; i + 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 int nx0 = x0>0?x0:0, nx1 = x1=0) { + const tc *col = color; + const ulongT off = whd - dx - 1; + T *ptrd = data(nx0,y); + if (opacity>=1) { // ** Opaque drawing ** + if (brightness==1) { // Brightness==1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)*(col++); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)*(col++); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else if (brightness<1) { // Brightness<1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else { // Brightness>1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } + } else { // ** Transparent drawing ** + if (brightness==1) { // Brightness==1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else if (brightness<=1) { // Brightness<1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*brightness*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else { // Brightness>1 + cimg_forC(*this,c) { + const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*maxval)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } + } + } + return *this; + } + + //! Draw a 3D point. + /** + \param x0 X-coordinate of the point. + \param y0 Y-coordinate of the point. + \param z0 Z-coordinate of the point. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \note + - To set pixel values without clipping needs, you should use the faster CImg::operator()() function. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_point(50,50,color); + \endcode + **/ + template + CImg& draw_point(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_point(): Specified color is (null).", + cimg_instance); + if (x0>=0 && y0>=0 && z0>=0 && x0=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + return *this; + } + + //! Draw a 2D point \simplification. + template + CImg& draw_point(const int x0, const int y0, + const tc *const color, const float opacity=1) { + return draw_point(x0,y0,0,color,opacity); + } + + // Draw a points cloud. + /** + \param points Image of vertices coordinates. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_point(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_point(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),color,opacity); + } break; + default : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),(int)points(i,2),color,opacity); + } + } + return *this; + } + + //! Draw a 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + \note + - Line routine uses Bresenham's algorithm. + - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,color); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; } + } + 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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 2D line, with z-buffering. + /** + \param zbuffer Zbuffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz && pattern&hatch) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 3D line. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + 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.f + nx1 - nx0; + ny0-=(int)((float)nx0*(1.f + ny1 - ny0)/D); + nz0-=(int)((float)nx0*(1.f + nz1 - nz0)/D); + nx0 = 0; + } + if (nx1>=width()) { + const float d = (float)nx1 - width(), D = 1.f + nx1 - nx0; + ny1+=(int)(d*(1.f + ny0 - ny1)/D); + nz1+=(int)(d*(1.f + 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.f + ny1 - ny0; + nx0-=(int)((float)ny0*(1.f + nx1 - nx0)/D); + nz0-=(int)((float)ny0*(1.f + nz1 - nz0)/D); + ny0 = 0; + } + if (ny1>=height()) { + const float d = (float)ny1 - height(), D = 1.f + ny1 - ny0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + nz1+=(int)(d*(1.f + 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.f + nz1 - nz0; + nx0-=(int)((float)nz0*(1.f + nx1 - nx0)/D); + ny0-=(int)((float)nz0*(1.f + ny1 - ny0)/D); + nz0 = 0; + } + if (nz1>=depth()) { + const float d = (float)nz1 - depth(), D = 1.f + nz1 - nz0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + ny1+=(int)(d*(1.f + 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.f); + for (unsigned int t = 0; t<=dmax; ++t) { + if (!(~pattern) || (~pattern && pattern&hatch)) { + T* ptrd = data((unsigned int)x,(unsigned int)y,(unsigned int)z); + const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } + } + } + return *this; + } + + //! Draw a textured 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + \note + - Line routine uses the well known Bresenham's algorithm. + \par Example: + \code + CImg img(100,100,1,3,0), texture("texture256x256.ppm"); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,texture,0,0,255,255); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + 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()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + if (pattern&hatch) { + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(const int x0, const int y0, const float z0, + const int x1, const 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 (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction and z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const 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_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a set of consecutive lines. + /** + \param points Coordinates of vertices, stored as a list of vectors. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If set to true, init hatch motif. + \note + - This function uses several call to the single CImg::draw_line() procedure, + depending on the vectors size in \p points. + **/ + template + 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 : + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i + CImg& draw_arrow(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const float angle=30, const float length=-10, + const unsigned int pattern=~0U) { + if (is_empty()) return *this; + const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v, + deg = (float)(angle*cimg::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.f, + l = (length>=0)?length:-length*(float)std::sqrt(sq)/100; + if (sq>0) { + const float + cl = (float)std::cos(ang - deg), sl = (float)std::sin(ang - deg), + cr = (float)std::cos(ang + deg), sr = (float)std::sin(ang + deg); + const int + xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl), + xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr), + xc = x1 + (int)((l + 1)*(cl + cr))/2, yc = y1 + (int)((l + 1)*(sl + sr))/2; + draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity); + } else draw_point(x0,y0,color,opacity); + return *this; + } + + //! Draw a 2D spline. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + \note + - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points + and corresponding velocity vectors. + - The spline is drawn as a serie of connected segments. The \p precision parameter sets the + average number of pixels in each drawn segment. + - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), + (\p xb,\p yb), (\p x1,\p y1) } where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point + and (\p xa,\p ya), (\p xb,\p yb) are two + \e control points. + The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from + the control points as + \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb). + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,255,255 }; + img.draw_spline(30,30,0,100,90,40,0,-100,color); + \endcode + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const tc *const color, const float opacity=1, + const float precision=0.25, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Specified color is (null).", + cimg_instance); + if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0; + for (float t = 0; t<1; t+=_precision) { + const float t2 = t*t, t3 = t2*t; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t + x0), + ny = (int)(ay*t3 + by*t2 + v0*t + y0); + draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; + } + return draw_line(ox,oy,x1,y1,color,opacity,pattern,false); + } + + //! Draw a 3D spline \overloading. + /** + \note + - Similar to CImg::draw_spline() for a 3D spline in a volumetric image. + **/ + template + 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. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param texture Texture image defining line pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch if \c true, reinit hatch motif. + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const CImg& texture, + const int tx0, const int ty0, const int tx1, const int ty1, + const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_empty()) return *this; + if (is_overlapped(texture)) + return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch); + if (x0==x1 && y0==y1) + return draw_point(x0,y0,texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0),opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0, otx = tx0, oty = ty0; + for (float t1 = 0; t1<1; t1+=_precision) { + const float t2 = t1*t1, t3 = t2*t1; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0), + ny = (int)(ay*t3 + by*t2 + v0*t1 + y0), + ntx = tx0 + (int)((tx1 - tx0)*t1), + nty = ty0 + (int)((ty1 - ty0)*t1); + draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; otx = ntx; oty = nty; + } + return draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false); + } + + //! Draw a set of consecutive splines. + /** + \param points Vertices data. + \param tangents Tangents data. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param is_closed_set Tells if the drawn spline set is closed. + \param precision Precision of the drawing. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + **/ + template + CImg& draw_spline(const CImg& points, const CImg& tangents, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || !tangents || points._width<2 || tangents._width<2) return *this; + bool ninit_hatch = init_hatch; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1); + int ox = x0, oy = y0; + float ou = u0, ov = v0; + for (unsigned int i = 1; i + CImg& draw_spline(const CImg& points, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || points._width<2) return *this; + CImg tangents; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + tangents.assign(points._width,points._height); + cimg_forX(points,p) { + const unsigned int + p0 = is_closed_set?(p + points._width - 1)%points._width:(p?p - 1:0), + p1 = is_closed_set?(p + 1)%points._width:(p + 1=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, + 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); + } + return *this; + } + + //! Draw a filled 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1); + return *this; + } + + //! Draw a outlined 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + draw_line(x0,y0,x1,y1,color,opacity,pattern,true). + draw_line(x1,y1,x2,y2,color,opacity,pattern,false). + draw_line(x2,y2,x0,y0,color,opacity,pattern,false); + return *this; + } + + //! Draw a filled 2D triangle, with z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param z0 Z-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param z1 Z-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param z2 Z-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param brightness Brightness factor. + **/ + template + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + } + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param brightness0 Brightness factor of the first vertex (in [0,2]). + \param brightness1 brightness factor of the second vertex (in [0,2]). + \param brightness2 brightness factor of the third vertex (in [0,2]). + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle, with z-buffering \overloading. + template + 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, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a color-interpolated 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color1 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the first vertex. + \param color2 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the seconf vertex. + \param color3 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the third vertex. + \param opacity Drawing opacity. + **/ + template + 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, + const float opacity=1) { + const unsigned char one = 1; + cimg_forC(*this,c) + get_shared_channel(c).draw_triangle(x0,y0,x1,y1,x2,y2,&one,color1[c],color2[c],color3[c],opacity); + return *this; + } + + //! Draw a textured 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param opacity Drawing opacity. + \param brightness Brightness factor of the drawing (in [0,2]). + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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 opacity=1, + const float brightness=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y, + nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + } + return *this; + } + + //! Draw a 2D textured triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int xleft = xleft0, xright = xright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xright=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval)); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle, with z-buffering. + template + 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, + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; pzl = pzn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + *ptrd = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + const T val = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param brightness0 Brightness factor of the first vertex. + \param brightness1 Brightness factor of the second vertex. + \param brightness2 Brightness factor of the third vertex. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y, + nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction \overloading. + template + 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, + 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, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright - cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc - (dx?dx*(dc/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errc = dx>>1; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + zleft-=xleft*(zright - zleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction and z-buffering \overloading. + template + 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, + 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, + 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 + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y, + nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (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 float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + zleft-=xleft*(zright - zleft)/dx; + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a filled 4D rectangle. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param z0 Z-coordinate of the upper-left rectangle corner. + \param c0 C-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param z1 Z-coordinate of the lower-right rectangle corner. + \param c1 C-coordinate of the lower-right rectangle corner. + \param val Scalar value used to fill the rectangle area. + \param opacity Drawing opacity. + **/ + CImg& draw_rectangle(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const T val, const float opacity=1) { + if (is_empty()) return *this; + const int + nx0 = x0=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.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; 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=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_rectangle(): Specified color is (null).", + cimg_instance); + cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,(T)color[c],opacity); + return *this; + } + + //! Draw an outlined 3D rectangle \overloading. + template + 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. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1) { + return draw_rectangle(x0,y0,0,x1,y1,_depth - 1,color,opacity); + } + + //! Draw a outlined 2D rectangle \overloading. + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true); + if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true); + const int + nx0 = x0 + CImg& draw_polygon(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Specified color is (null).", + cimg_instance); + if (points._width==1) return draw_point((int)points(0,0),(int)points(0,1),color,opacity); + if (points._width==2) return draw_line((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1),color,opacity); + if (points._width==3) return draw_triangle((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1), + (int)points(2,0),(int)points(2,1),color,opacity); + cimg_init_scanline(color,opacity); + int + xmin = 0, ymin = 0, + xmax = points.get_shared_row(0).max_min(xmin), + ymax = points.get_shared_row(1).max_min(ymin); + if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this; + if (ymin==ymax) return draw_line(xmin,ymin,xmax,ymax,color,opacity); + + ymin = std::max(0,ymin); + ymax = std::min(height() - 1,ymax); + CImg Xs(points._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 tn = an; + while (points(tn,1)==y1) (tn+=1)%=points._width; + + if (y0!=y1) { + const int + y2 = (int)points(tn,1), + x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1, + dy = cimg::sign(y01), + tmax = std::max(1,cimg::abs(y01)), + tend = tmax - (dy==cimg::sign(y12)); + unsigned int y = (unsigned int)y0 - ymin; + for (int t = 0; t<=tend; ++t, y+=dy) + if (yn; + n = nn; + nn = an; + } + + cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>=(cimg_openmp_sizefactor)*32)) + 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, + const tc *const color, const float opacity, const unsigned int pattern) { + if (is_empty() || !points || points._width<3) return *this; + bool ninit_hatch = true; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Invalid specified point set.", + cimg_instance); + case 2 : { // 2D version + CImg 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 + 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); + } + + //! Draw a filled 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity=1) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity); + } + + //! Draw an outlined 2D ellipse. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param r1 First radius of the ellipse. + \param r2 Second radius of the ellipse. + \param angle Angle of the first radius. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + 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); + return *this; + } + + //! Draw an outlined 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity, + const unsigned int pattern) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity,pattern); + } + + template + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_ellipse(): Specified color is (null).", + cimg_instance); + if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity); + if (r1==r2 && (float)(int)r1==r1) { + if (pattern) return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity,pattern); + else return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity); + } + cimg_init_scanline(color,opacity); + const float + nr1 = cimg::abs(r1) - 0.5, nr2 = cimg::abs(r2) - 0.5, + nangle = (float)(angle*cimg::PI/180), + u = (float)std::cos(nangle), + v = (float)std::sin(nangle), + rmax = std::max(nr1,nr2), + l1 = (float)std::pow(rmax/(nr1>0?nr1:1e-6),2), + l2 = (float)std::pow(rmax/(nr2>0?nr2:1e-6),2), + a = l1*u*u + l2*v*v, + b = u*v*(l1 - l2), + c = l1*v*v + l2*u*u; + const int + yb = (int)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 + Y = y - y0 + (y0?(float)std::sqrt(delta)/a:0.f, + 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 + 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 (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + cimg_init_scanline(color,opacity); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (y0>=0 && y0=0) { + const int x1 = x0 - x, x2 = x0 + x, y1 = y0 - y, y2 = y0 + y; + if (y1>=0 && y1=0 && y2=0 && y1=0 && y2 + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (!radius) return draw_point(x0,y0,color,opacity); + draw_point(x0 - radius,y0,color,opacity).draw_point(x0 + radius,y0,color,opacity). + draw_point(x0,y0 - radius,color,opacity).draw_point(x0,y0 + radius,color,opacity); + if (radius==1) return *this; + for (int f = 1 - radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x=0) { f+=(ddFy+=2); --y; } + ++x; ++(f+=(ddFx+=2)); + if (x!=y + 1) { + const int x1 = x0 - y, x2 = x0 + y, y1 = y0 - x, y2 = y0 + x, + x3 = x0 - x, x4 = x0 + x, y3 = y0 - y, y4 = y0 + y; + draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity). + draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity); + if (x!=y) + draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity). + draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity); + } + } + return *this; + } + + //! Draw an image. + /** + \param sprite Sprite image. + \param x0 X-coordinate of the sprite position. + \param y0 Y-coordinate of the sprite position. + \param z0 Z-coordinate of the sprite position. + \param c0 C-coordinate of the sprite position. + \param opacity Drawing opacity. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const t + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; x& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const T + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ), + slX = lX*sizeof(T); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; y + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,z0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const CImg& sprite, const float opacity=1) { + return draw_image(0,sprite,opacity); + } + + //! Draw a masked image. + /** + \param sprite Sprite image. + \param mask Mask image. + \param x0 X-coordinate of the sprite position in the image instance. + \param y0 Y-coordinate of the sprite position in the image instance. + \param z0 Z-coordinate of the sprite position in the image instance. + \param c0 C-coordinate of the sprite position in the image instance. + \param mask_max_value Maximum pixel value of the mask image \c mask. + \param opacity Drawing opacity. + \note + - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite. + - Dimensions along x,y and z of \p sprite and \p mask must be the same. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + if (is_empty() || !sprite || !mask) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_max_value); + if (is_overlapped(mask)) return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_max_value); + if (mask._width!=sprite._width || mask._height!=sprite._height || mask._depth!=sprite._depth) + throw CImgArgumentException(_cimg_instance + "draw_image(): Sprite (%u,%u,%u,%u,%p) and mask (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + sprite._width,sprite._height,sprite._depth,sprite._spectrum,sprite._data, + mask._width,mask._height,mask._depth,mask._spectrum,mask._data); + + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const ulongT + coff = (bx?-x0:0) + + (by?-y0*(ulongT)mask.width():0) + + (bz?-z0*(ulongT)mask.width()*mask.height():0) + + (bc?-c0*(ulongT)mask.width()*mask.height()*mask.depth():0), + ssize = (ulongT)mask.width()*mask.height()*mask.depth()*mask.spectrum(); + const ti *ptrs = sprite._data + coff; + const tm *ptrm = mask._data + coff; + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + if (lX>0 && lY>0 && lZ>0 && lC>0) { + T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0); + for (int c = 0; c + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw an image. + template + CImg& draw_image(const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a text string. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Pointer to \c spectrum() consecutive values, defining the foreground drawing color. + \param background_color Pointer to \c spectrum() consecutive values, defining the background drawing color. + \param opacity Drawing opacity. + \param font Font used for drawing text. + **/ + template + CImg& 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent background is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent foreground is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Array of spectrum() values of type \c T, + defining the foreground color (0 means 'transparent'). + \param background_color Array of spectrum() values of type \c T, + defining the background color (0 means 'transparent'). + \param opacity Drawing opacity. + \param font_height Height of the text font (exact match for 13,23,53,103, interpolated otherwise). + **/ + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc1 *const foreground_color, const tc2 *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + 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; + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc *const foreground_color, const int background_color=0, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + cimg::unused(background_color); + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",foreground_color,(const tc*)0,opacity,font_height,tmp._data); + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const int, const tc *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",(tc*)0,background_color,opacity,font_height,tmp._data); + } + + template + CImg& _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, + const bool is_native_font) { + if (!text) return *this; + if (!font) + throw CImgArgumentException(_cimg_instance + "draw_text(): Empty specified font.", + cimg_instance); + + const unsigned int text_length = (unsigned int)std::strlen(text); + if (is_empty()) { + // If needed, pre-compute necessary size of the image + int x = 0, y = 0, w = 0; + unsigned char c = 0; + for (unsigned int i = 0; iw) w = x; x = 0; break; + case '\t' : x+=4*font[' ']._width; break; + default : if (cw) w=x; + y+=font[0]._height; + } + assign(x0 + w,y0 + y,1,is_native_font?1:font[0]._spectrum,(T)0); + } + + int x = x0, y = y0; + for (unsigned int i = 0; i letter = font[c]; + if (letter) { + if (is_native_font && _spectrum>letter._spectrum) letter.resize(-100,-100,1,_spectrum,0,2); + const unsigned int cmin = std::min(_spectrum,letter._spectrum); + if (foreground_color) + for (unsigned int c = 0; c& __draw_text(const char *const text, const bool 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 label, labelmask; + const unsigned char labelcolor = 127; + const unsigned int fsize = 13; + label.draw_text(0,0,"%s",&labelcolor,0,1,fsize,tmp._data); + if (label) { + label.crop(2,0,label.width() - 1,label.height()); + ((labelmask = label)+=label.get_dilate(5)).max(80); + (label*=2).resize(-100,-100,1,3,1); + return draw_image(0,is_down?height() - fsize:0,label,labelmask,1,254); + } + return *this; + } + + //! Draw a 2D vector field. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Drawing opacity. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const t2 *const color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + return draw_quiver(flow,CImg(color,_spectrum,1,1,1,true),opacity,sampling,factor,is_arrow,pattern); + } + + //! Draw a 2D vector field, using a field of colors. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Opacity of the drawing. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const CImg& color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + if (is_empty()) return *this; + if (!flow || flow._spectrum!=2) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid dimensions of specified flow (%u,%u,%u,%u,%p).", + cimg_instance, + flow._width,flow._height,flow._depth,flow._spectrum,flow._data); + if (sampling<=0) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid sampling value %g " + "(should be >0)", + cimg_instance, + sampling); + const bool colorfield = (color._width==flow._width && color._height==flow._height && + color._depth==1 && color._spectrum==_spectrum); + if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,is_arrow,pattern); + float vmax,fact; + if (factor<=0) { + float m, M = (float)flow.get_norm(2).max_min(m); + vmax = (float)std::max(cimg::abs(m),cimg::abs(M)); + if (!vmax) vmax = 1; + fact = -factor; + } else { fact = factor; vmax = 1; } + + for (unsigned int y = sampling/2; y<_height; y+=sampling) + for (unsigned int x = sampling/2; x<_width; x+=sampling) { + const unsigned int X = x*flow._width/_width, Y = y*flow._height/_height; + float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax; + if (is_arrow) { + const int xx = (int)(x + u), yy = (int)(y + v); + if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y)._data,opacity,45,sampling/5.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), + color.get_vector_at(X,Y)._data,opacity,pattern); + else draw_line((int)(x - 0.5*u),(int)(y - 0.5*v),(int)(x + 0.5*u),(int)(y + 0.5*v), + color._data,opacity,pattern); + } + } + return *this; + } + + //! Draw a labeled horizontal axis. + /** + \param values_x Values along the horizontal axis. + \param y Y-coordinate of the horizontal axis in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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 + draw_line(0,y,_width - 1,y,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_x); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _xt = (width() - label.width())/2, + xt = _xt<3?3:_xt + label.width()>=width() - 2?width() - 3 - label.width():_xt; + draw_point(width()/2,y - 1,color,opacity).draw_point(width()/2,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_x[0]=width() - 2?width() - 3 - label.width():_xt; + draw_point(xi,y - 1,color,opacity).draw_point(xi,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw a labeled vertical axis. + /** + \param x X-coordinate of the vertical axis in the image instance. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + if (is_empty()) return *this; + int siz = (int)values_y.size() - 1; + CImg txt(32); + CImg label; + if (siz<=0) { // Degenerated case + draw_line(x,0,x,_height - 1,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_y); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _yt = (height() - label.height())/2, + yt = _yt<0?0:_yt + label.height()>=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,height()/2,color,opacity).draw_point(x + 1,height()/2,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_y[0]=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,yi,color,opacity).draw_point(x + 1,yi,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes. + /** + \param values_x Values along the X-axis. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for the X-axis. + \param pattern_y Drawing pattern for the Y-axis. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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; + if (sizx>=0) { + float ox = (float)*nvalues_x; + for (unsigned int x = sizx?1U:0U; x<_width; ++x) { + const float nx = (float)nvalues_x._linear_atX((float)x*sizx/wm1); + if (nx*ox<=0) { draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero); break; } + ox = nx; + } + } + const CImg nvalues_y(values_y._data,values_y.size(),1,1,1,true); + const int sizy = (int)values_y.size() - 1, hm1 = height() - 1; + if (sizy>0) { + float oy = (float)nvalues_y[0]; + for (unsigned int y = sizy?1U:0U; y<_height; ++y) { + const float ny = (float)nvalues_y._linear_atX((float)y*sizy/hm1); + if (ny*oy<=0) { draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero); break; } + oy = ny; + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes \overloading. + template + CImg& draw_axes(const float x0, const float x1, const float y0, const float y1, + const tc *const color, const float opacity=1, + const int subdivisionx=-60, const int subdivisiony=-60, + const float precisionx=0, const float precisiony=0, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U, + const unsigned int font_height=13) { + if (is_empty()) return *this; + const bool allow_zero = (x0*x1>0) || (y0*y1>0); + const float + dx = cimg::abs(x1 - x0), dy = cimg::abs(y1 - y0), + px = dx<=0?1:precisionx==0?(float)std::pow(10.,(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); + 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); + 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); + return *this; + } + + //! Draw 2D grid. + /** + \param values_x X-coordinates of the vertical lines. + \param values_y Y-coordinates of the horizontal lines. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for vertical lines. + \param pattern_y Drawing pattern for horizontal lines. + **/ + template + CImg& draw_grid(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) { + if (is_empty()) return *this; + if (values_x) cimg_foroff(values_x,x) { + const int xi = (int)values_x[x]; + if (xi>=0 && xi=0 && yi + CImg& draw_grid(const float delta_x, const float delta_y, + const float offsetx, const float offsety, + const bool invertx, const bool inverty, + const tc *const color, const float opacity=1, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) { + if (is_empty()) return *this; + CImg seqx, seqy; + if (delta_x!=0) { + const float dx = delta_x>0?delta_x:_width*-delta_x/100; + const unsigned int nx = (unsigned int)(_width/dx); + seqx = CImg::sequence(1 + nx,0,(unsigned int)(dx*nx)); + if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x) + offsetx,(float)_width); + if (invertx) cimg_foroff(seqx,x) seqx(x) = _width - 1 - seqx(x); + } + if (delta_y!=0) { + const float dy = delta_y>0?delta_y:_height*-delta_y/100; + const unsigned int ny = (unsigned int)(_height/dy); + seqy = CImg::sequence(1 + ny,0,(unsigned int)(dy*ny)); + if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y) + offsety,(float)_height); + if (inverty) cimg_foroff(seqy,y) seqy(y) = _height - 1 - seqy(y); + } + return draw_grid(seqx,seqy,color,opacity,pattern_x,pattern_y); + } + + //! Draw 1D graph. + /** + \param data Image containing the graph values I = f(x). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + + \param plot_type Define the type of the plot: + - 0 = No plot. + - 1 = Plot using segments. + - 2 = Plot using cubic splines. + - 3 = Plot with bars. + \param vertex_type Define the type of points: + - 0 = No points. + - 1 = Point. + - 2 = Straight cross. + - 3 = Diagonal cross. + - 4 = Filled circle. + - 5 = Outlined circle. + - 6 = Square. + - 7 = Diamond. + \param ymin Lower bound of the y-range. + \param ymax Upper bound of the y-range. + \param pattern Drawing pattern. + \note + - if \c ymin==ymax==0, the y-range is computed automatically from the input samples. + **/ + template + CImg& draw_graph(const CImg& data, + const tc *const color, const float opacity=1, + const unsigned int plot_type=1, const int vertex_type=1, + const double ymin=0, const double ymax=0, const unsigned int pattern=~0U) { + if (is_empty() || _height<=1) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_graph(): Specified color is (null).", + cimg_instance); + + // Create shaded colors for displaying bar plots. + CImg color1, color2; + if (plot_type==3) { + color1.assign(_spectrum); color2.assign(_spectrum); + cimg_forC(*this,c) { + color1[c] = (tc)std::min((float)cimg::type::max(),(float)color[c]*1.2f); + color2[c] = (tc)(color[c]*0.4f); + } + } + + // Compute min/max and normalization factors. + const ulongT + siz = data.size(), + _siz1 = siz - (plot_type!=3), + siz1 = _siz1?_siz1:1; + const unsigned int + _width1 = _width - (plot_type!=3), + width1 = _width1?_width1:1; + double m = ymin, M = ymax; + if (ymin==ymax) m = (double)data.max_min(M); + if (m==M) { --m; ++M; } + const float ca = (float)(M-m)/(_height - 1); + bool init_hatch = true; + + // Draw graph edges + switch (plot_type%4) { + case 1 : { // Segments + int oX = 0, oY = (int)((data[0] - m)/ca); + if (siz==1) { + const int Y = (int)((*data - m)/ca); + draw_line(0,Y,width() - 1,Y,color,opacity,pattern); + } else { + const float fx = (float)_width/siz1; + for (ulongT off = 1; off ndata(data._data,siz,1,1,1,true); + int oY = (int)((data[0] - m)/ca); + cimg_forX(*this,x) { + const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-m)/ca); + if (x>0) draw_line(x,oY,x + 1,Y,color,opacity,pattern,init_hatch); + init_hatch = false; + oY = Y; + } + } break; + case 3 : { // Bars + const int Y0 = (int)(-m/ca); + const float fx = (float)_width/siz1; + int oX = 0; + cimg_foroff(data,off) { + const int + X = (int)((off + 1)*fx) - 1, + Y = (int)((data[off] - m)/ca); + draw_rectangle(oX,Y0,X,Y,color,opacity). + draw_line(oX,Y,oX,Y0,color2.data(),opacity). + draw_line(oX,Y0,X,Y0,Y<=Y0?color2.data():color1.data(),opacity). + draw_line(X,Y,X,Y0,color1.data(),opacity). + draw_line(oX,Y,X,Y,Y<=Y0?color1.data():color2.data(),opacity); + oX = X + 1; + } + } break; + default : break; // No edges + } + + // Draw graph points + const unsigned int wb2 = plot_type==3?_width1/(2*siz):0; + const float fx = (float)_width1/siz1; + switch (vertex_type%8) { + case 1 : { // Point + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_point(X,Y,color,opacity); + } + } break; + case 2 : { // Straight Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y,X + 3,Y,color,opacity).draw_line(X,Y - 3,X,Y + 3,color,opacity); + } + } break; + case 3 : { // Diagonal Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y - 3,X + 3,Y + 3,color,opacity).draw_line(X - 3,Y + 3,X + 3,Y - 3,color,opacity); + } + } break; + case 4 : { // Filled Circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity); + } + } break; + case 5 : { // Outlined circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity,0U); + } + } break; + case 6 : { // Square + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_rectangle(X - 3,Y - 3,X + 3,Y + 3,color,opacity,~0U); + } + } break; + case 7 : { // Diamond + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X,Y - 4,X + 4,Y,color,opacity). + draw_line(X + 4,Y,X,Y + 4,color,opacity). + draw_line(X,Y + 4,X - 4,Y,color,opacity). + draw_line(X - 4,Y,X,Y - 4,color,opacity); + } + } break; + default : break; // No points + } + return *this; + } + + bool _draw_fill(const int x, const int y, const int z, + const CImg& ref, const float tolerance2) const { + const T *ptr1 = data(x,y,z), *ptr2 = ref._data; + const unsigned long off = _width*_height*_depth; + float diff = 0; + cimg_forC(*this,c) { diff += cimg::sqr(*ptr1 - *(ptr2++)); ptr1+=off; } + return diff<=tolerance2; + } + + //! Draw filled 3D region with the flood fill algorithm. + /** + \param x0 X-coordinate of the starting point of the region to fill. + \param y0 Y-coordinate of the starting point of the region to fill. + \param z0 Z-coordinate of the starting point of the region to fill. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param[out] region Image that will contain the mask of the filled region mask, as an output. + \param tolerance Tolerance concerning neighborhood values. + \param opacity Opacity of the drawing. + \param is_high_connectivity Tells if 8-connexity must be used. + \return \c region is initialized with the binary mask of the filled region. + **/ + template + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity, + CImg ®ion, + const float tolerance = 0, + const bool is_high_connectivity = false) { +#define _draw_fill_push(x,y,z) if (N>=stack._width) stack.resize(2*N + 1,1,1,3,0); \ + stack[N] = x; stack(N,1) = y; stack(N++,2) = z +#define _draw_fill_pop(x,y,z) x = stack[--N]; y = stack(N,1); z = stack(N,2) +#define _draw_fill_is_inside(x,y,z) !_region(x,y,z) && _draw_fill(x,y,z,ref,tolerance2) + + if (!containsXYZC(x0,y0,z0,0)) return *this; + const float nopacity = cimg::abs((float)opacity), copacity = 1 - std::max((float)opacity,0.f); + const float tolerance2 = cimg::sqr(tolerance); + const CImg ref = get_vector_at(x0,y0,z0); + CImg stack(256,1,1,3); + CImg _region(_width,_height,_depth,1,0); + unsigned int N = 0; + int x, y, z; + + _draw_fill_push(x0,y0,z0); + while (N>0) { + _draw_fill_pop(x,y,z); + if (!_region(x,y,z)) { + const int yp = y - 1, yn = y + 1, zp = z - 1, zn = z + 1; + int xl = x, xr = x; + + // Using these booleans reduces the number of pushes drastically. + bool is_yp = false, is_yn = false, is_zp = false, is_zn = false; + for (int step = -1; step<2; step+=2) { + while (x>=0 && x=0 && _draw_fill_is_inside(x,yp,z)) { + if (!is_yp) { _draw_fill_push(x,yp,z); is_yp = true; } + } else is_yp = false; + if (yn1) { + if (zp>=0 && _draw_fill_is_inside(x,y,zp)) { + if (!is_zp) { _draw_fill_push(x,y,zp); is_zp = true; } + } else is_zp = false; + if (zn=0 && !is_yp) { + if (xp>=0 && _draw_fill_is_inside(xp,yp,z)) { + _draw_fill_push(xp,yp,z); if (step<0) is_yp = true; + } + if (xn0) is_yp = true; + } + } + if (yn=0 && _draw_fill_is_inside(xp,yn,z)) { + _draw_fill_push(xp,yn,z); if (step<0) is_yn = true; + } + if (xn0) is_yn = true; + } + } + if (depth()>1) { + if (zp>=0 && !is_zp) { + if (xp>=0 && _draw_fill_is_inside(xp,y,zp)) { + _draw_fill_push(xp,y,zp); if (step<0) is_zp = true; + } + if (xn0) is_zp = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zp)) { _draw_fill_push(x,yp,zp); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zp)) { _draw_fill_push(xp,yp,zp); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zp)) { _draw_fill_push(xp,yn,zp); } + if (xn=0 && _draw_fill_is_inside(xp,y,zn)) { + _draw_fill_push(xp,y,zn); if (step<0) is_zn = true; + } + if (xn0) is_zn = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zn)) { _draw_fill_push(x,yp,zn); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zn)) { _draw_fill_push(xp,yp,zn); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zn)) { _draw_fill_push(xp,yn,zn); } + if (xn + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,z0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw filled 2D region with the flood fill algorithm \simplification. + template + CImg& draw_fill(const int x0, const int y0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw a random plasma texture. + /** + \param alpha Alpha-parameter. + \param beta Beta-parameter. + \param scale Scale-parameter. + \note Use the mid-point algorithm to render. + **/ + CImg& draw_plasma(const float alpha=1, const float beta=0, const unsigned int scale=8) { + if (is_empty()) return *this; + const int w = width(), h = height(); + const Tfloat m = (Tfloat)cimg::type::min(), M = (Tfloat)cimg::type::max(); + ulongT rng = (cimg::_rand(),cimg::rng()); + cimg_forZC(*this,z,c) { + CImg ref = get_shared_slice(z,c); + for (int delta = 1<1; delta>>=1) { + const int delta2 = delta>>1; + const float r = alpha*delta + beta; + + // Square step. + for (int y0 = 0; y0M?M:val); + } + + // Diamond steps. + for (int y = -delta2; yM?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. + /** + \param x0 X-coordinate of the upper-left pixel. + \param y0 Y-coordinate of the upper-left pixel. + \param x1 X-coordinate of the lower-right pixel. + \param y1 Y-coordinate of the lower-right pixel. + \param colormap Colormap. + \param opacity Drawing opacity. + \param z0r Real part of the upper-left fractal vertex. + \param z0i Imaginary part of the upper-left fractal vertex. + \param z1r Real part of the lower-right fractal vertex. + \param z1i Imaginary part of the lower-right fractal vertex. + \param iteration_max Maximum number of iterations for each estimated point. + \param is_normalized_iteration Tells if iterations are normalized. + \param is_julia_set Tells if the Mandelbrot or Julia set is rendered. + \param param_r Real part of the Julia set parameter. + \param param_i Imaginary part of the Julia set parameter. + \note Fractal rendering is done by the Escape Time Algorithm. + **/ + template + CImg& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1, + const CImg& colormap, const float opacity=1, + const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + if (is_empty()) return *this; + CImg palette; + if (colormap) palette.assign(colormap._data,colormap.size()/colormap._spectrum,1,1,colormap._spectrum,true); + if (palette && palette._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_mandelbrot(): Instance and specified colormap (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.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 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; + const double x = z0r + p*(z1r-z0r)/_width, y = z0i + q*(z1i-z0i)/_height; + double zr, zi, cr, ci; + if (is_julia_set) { zr = x; zi = y; cr = param_r; ci = param_i; } + else { zr = param_r; zi = param_i; cr = x; ci = y; } + for (iteration=1; zr*zr + zi*zi<=4 && iteration<=iteration_max; ++iteration) { + const double temp = zr*zr - zi*zi + cr; + zi = 2*zr*zi + ci; + zr = temp; + } + if (iteration>iteration_max) { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette(0,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(0,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)0; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)((*this)(p,q,0,c)*copacity); + } + } else if (is_normalized_iteration) { + const float + normz = (float)cimg::abs(zr*zr + zi*zi), + niteration = (float)(iteration + 1 - std::log(std::log(normz))/ln2); + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._linear_atX(niteration,c); + else cimg_forC(*this,c) + (*this)(p,q,0,c) = (T)(palette._linear_atX(niteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)niteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(niteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } else { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._atX(iteration,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(iteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)iteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(iteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } + } + return *this; + } + + //! Draw a quadratic Mandelbrot or Julia 2D fractal \overloading. + template + 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, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + return draw_mandelbrot(0,0,_width - 1,_height - 1,colormap,opacity, + z0r,z0i,z1r,z1i,iteration_max,is_normalized_iteration,is_julia_set,param_r,param_i); + } + + //! Draw a 1D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param sigma Standard variation of the gaussian distribution. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float sigma, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forX(*this,x) { + const float dx = (x - xc), val = (float)std::exp(-dx*dx/sigma2); + T *ptrd = data(x,0,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 2D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param yc Y-coordinate of the gaussian center. + \param tensor Covariance matrix (must be 2x2). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + typedef typename CImg::Tfloat tfloat; + 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + float dy = -yc; + cimg_forY(*this,y) { + float dx = -xc; + cimg_forX(*this,x) { + const float val = (float)std::exp(a*dx*dx + b*dx*dy + c*dy*dy); + T *ptrd = data(x,y,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + ++dx; + } + ++dy; + } + return *this; + } + + //! Draw a 2D gaussian function \overloading. + template + 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) { + const double + a = r1*ru*ru + r2*rv*rv, + b = (r1-r2)*ru*rv, + c = r1*rv*rv + r2*ru*ru; + const CImg tensor(2,2,1,1, a,b,b,c); + return draw_gaussian(xc,yc,tensor,color,opacity); + } + + //! 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. + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + typedef typename CImg::Tfloat tfloat; + if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + + const CImg 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forXYZ(*this,x,y,z) { + const float + dx = (x - xc), dy = (y - yc), dz = (z - zc), + val = (float)std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz); + T *ptrd = data(x,y,z,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 3D gaussian function \overloading. + template + 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. + /** + \param x0 X-coordinate of the 3D object position + \param y0 Y-coordinate of the 3D object position + \param z0 Z-coordinate of the 3D object position + \param vertices Image Nx3 describing 3D point coordinates + \param primitives List of P primitives + \param colors List of P color (or textures) + \param opacities Image or list of P opacities + \param render_type d Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud) + \param is_double_sided Tells if object faces have two sides or are oriented. + \param focale length of the focale (0 for parallel projection) + \param lightx X-coordinate of the light + \param lighty Y-coordinate of the light + \param lightz Z-coordinate of the light + \param specular_lightness Amount of specular light. + \param specular_shininess Shininess of the object + \param g_opacity Global opacity of the object. + **/ + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } +#endif + + template + static float __draw_object3d(const CImgList& opacities, const unsigned int n_primitive, CImg& opacity) { + if (n_primitive>=opacities._width || opacities[n_primitive].is_empty()) { opacity.assign(); return 1; } + if (opacities[n_primitive].size()==1) { opacity.assign(); return opacities(n_primitive,0); } + opacity.assign(opacities[n_primitive],true); + return 1.f; + } + + template + static float __draw_object3d(const CImg& opacities, const unsigned int n_primitive, CImg& opacity) { + opacity.assign(); + 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 + CImg& _draw_object3d(void *const pboard, CImg& zbuffer, + const float X, const float Y, const float Z, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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).", + cimg_instance,vertices._width,primitives._width,error_message.data()); +#ifndef cimg_use_board + if (pboard) return *this; +#endif + if (render_type==5) cimg::mutex(10); // Static variable used in this case, breaks thread-safety + + const float + nspec = 1 - (specular_lightness<0.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; + + // Create light texture for phong-like rendering. + CImg light_texture; + if (render_type==5) { + if (colors._width>primitives._width) { + static CImg default_light_texture; + static const tc *lptr = 0; + static tc ref_values[64] = { 0 }; + const CImg& img = colors.back(); + bool is_same_texture = (lptr==img._data); + if (is_same_texture) + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + if (ref_values[r++]!=img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum)) { + is_same_texture = false; break; + } + if (!is_same_texture || default_light_texture._spectrum<_spectrum) { + (default_light_texture.assign(img,false)/=255).resize(-100,-100,1,_spectrum); + lptr = colors.back().data(); + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + ref_values[r++] = img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum); + } + light_texture.assign(default_light_texture,true); + } else { + static CImg default_light_texture; + static float olightx = 0, olighty = 0, olightz = 0, ospecular_shininess = 0; + if (!default_light_texture || + lightx!=olightx || lighty!=olighty || lightz!=olightz || + specular_shininess!=ospecular_shininess || default_light_texture._spectrum<_spectrum) { + default_light_texture.assign(512,512); + const float + dlx = lightx - X, + dly = lighty - Y, + dlz = lightz - Z, + nl = cimg::hypot(dlx,dly,dlz), + nlx = (default_light_texture._width - 1)/2*(1 + dlx/nl), + nly = (default_light_texture._height - 1)/2*(1 + dly/nl), + white[] = { 1 }; + default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.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.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; + } + light_texture.assign(default_light_texture,true); + } + } + + // 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_size(projections.size(),4096)) + cimg_forX(projections,l) { // Perspective projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + const tpfloat projectedz = z + Z + absfocale; + projections(l,1) = Y + absfocale*y/projectedz; + projections(l,0) = X + absfocale*x/projectedz; + } + } else { + cimg_pragma_openmp(parallel for cimg_openmp_if_size(projections.size(),4096)) + cimg_forX(projections,l) { // Parallel projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + if (z visibles(primitives._width,1,1,1,~0U); + CImg zrange(primitives._width); + 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_size(primitives.size(),4096)) + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + switch (primitive.size()) { + case 1 : { // Point + CImg<_to> _opacity; + __draw_object3d(opacities,l,_opacity); + if (l<=colors.width() && (colors[l].size()!=_spectrum || _opacity)) is_forward = false; + const unsigned int i0 = (unsigned int)primitive(0); + const tpfloat z0 = Z + vertices(i0,2); + if (z0>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = z0; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + Xc = 0.5f*((float)vertices(i0,0) + (float)vertices(i1,0)), + Yc = 0.5f*((float)vertices(i0,1) + (float)vertices(i1,1)), + Zc = 0.5f*((float)vertices(i0,2) + (float)vertices(i1,2)), + _zc = Z + Zc, + zc = _zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(i1,0) - vertices(i0,0), + vertices(i1,1) - vertices(i0,1), + vertices(i1,2) - vertices(i0,2))*(absfocale?absfocale/zc:1), + xm = xc - radius, + ym = yc - radius, + xM = xc + radius, + yM = yc + radius; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && _zc>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = _zc; + } + is_forward = false; + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2); + tpfloat xm, xM, ym, yM; + if (x0=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1)/2; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (y0yM) yM = y2; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) { + const tpfloat d = (x1-x0)*(y2-y0) - (x2-x0)*(y1-y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2)/3; + } + } + } break; + case 4 : 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); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2), + x3 = projections(i3,0), y3 = projections(i3,1), z3 = Z + vertices(i3,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (x3xM) xM = x3; + if (y0yM) yM = y2; + if (y3yM) yM = y3; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin && z3>zmin) { + const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2 + z3)/4; + } + } + } break; + default : + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Invalid primitive[%u] with size %u " + "(should have size 1,2,3,4,5,6,9 or 12).", + cimg_instance, + l,primitive.size()); + } + } + + // Force transparent primitives to be drawn last when zbuffer is activated + // (and if object contains no spheres or sprites). + if (is_forward) + cimglist_for(primitives,l) + if (___draw_object3d(opacities,l)!=1) zrange(l) = 2*zmax - zrange(l); + + // Sort only visibles primitives. + unsigned int *p_visibles = visibles._data; + tpfloat *p_zrange = zrange._data; + const tpfloat *ptrz = p_zrange; + cimg_for(visibles,ptr,unsigned int) { + if (*ptr!=~0U) { *(p_visibles++) = *ptr; *(p_zrange++) = *ptrz; } + ++ptrz; + } + const unsigned int nb_visibles = (unsigned int)(p_zrange - zrange._data); + if (!nb_visibles) { + if (render_type==5) cimg::mutex(10,0); + return *this; + } + CImg permutations; + CImg(zrange._data,nb_visibles,1,1,1,true).sort(permutations,is_forward); + + // Compute light properties + CImg lightprops; + switch (render_type) { + case 3 : { // Flat Shading + lightprops.assign(nb_visibles); + 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(); + if (psize==3 || psize==4 || psize==9 || psize==12) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nx = dy1*dz2 - dz1*dy2, + ny = dz1*dx2 - dx1*dz2, + nz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + (x0 + x1 + x2)/3 - lightx, + ly = Y + (y0 + y1 + y2)/3 - lighty, + lz = Z + (z0 + z1 + z2)/3 - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max(cimg::abs(-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } else lightprops[l] = 1; + } + } break; + + case 4 : // Gouraud Shading + case 5 : { // Phong-Shading + CImg vertices_normals(vertices._width,6,1,1,0); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + for (int l = 0; l<(int)nb_visibles; ++l) { + const CImg& primitive = primitives[visibles(l)]; + const unsigned int psize = (unsigned int)primitive.size(); + const bool + triangle_flag = (psize==3) || (psize==9), + quadrangle_flag = (psize==4) || (psize==12); + if (triangle_flag || quadrangle_flag) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2), + i3 = quadrangle_flag?(unsigned int)primitive(3):0; + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nnx = dy1*dz2 - dz1*dy2, + nny = dz1*dx2 - dx1*dz2, + nnz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nnx,nny,nnz), + nx = nnx/norm, + ny = nny/norm, + nz = nnz/norm; + unsigned int ix = 0, iy = 1, iz = 2; + if (is_double_sided && nz>0) { ix = 3; iy = 4; iz = 5; } + vertices_normals(i0,ix)+=nx; vertices_normals(i0,iy)+=ny; vertices_normals(i0,iz)+=nz; + vertices_normals(i1,ix)+=nx; vertices_normals(i1,iy)+=ny; vertices_normals(i1,iz)+=nz; + vertices_normals(i2,ix)+=nx; vertices_normals(i2,iy)+=ny; vertices_normals(i2,iz)+=nz; + if (quadrangle_flag) { + vertices_normals(i3,ix)+=nx; vertices_normals(i3,iy)+=ny; vertices_normals(i3,iz)+=nz; + } + } + } + + if (is_double_sided) cimg_forX(vertices_normals,p) { + const float + nx0 = vertices_normals(p,0), ny0 = vertices_normals(p,1), nz0 = vertices_normals(p,2), + nx1 = vertices_normals(p,3), ny1 = vertices_normals(p,4), nz1 = vertices_normals(p,5), + n0 = nx0*nx0 + ny0*ny0 + nz0*nz0, n1 = nx1*nx1 + ny1*ny1 + nz1*nz1; + if (n1>n0) { + vertices_normals(p,0) = -nx1; + vertices_normals(p,1) = -ny1; + vertices_normals(p,2) = -nz1; + } + } + + if (render_type==4) { + lightprops.assign(vertices._width); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + vertices(l,0) - lightx, + ly = Y + vertices(l,1) - lighty, + lz = Z + vertices(l,2) - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max((-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } + } else { + const unsigned int + lw2 = light_texture._width/2 - 1, + lh2 = light_texture._height/2 - 1; + lightprops.assign(vertices._width,2); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + nnx = nx/norm, + nny = ny/norm; + lightprops(l,0) = lw2*(1 + nnx); + lightprops(l,1) = lh2*(1 + nny); + } + } + } break; + } + + // Draw visible primitives + const CImg default_color(1,_spectrum,1,1,(tc)200); + CImg<_to> _opacity; + + for (unsigned int l = 0; l& primitive = primitives[n_primitive]; + const CImg + &__color = n_primitive(), + _color = (__color && __color.size()!=_spectrum && __color._spectrum<_spectrum)? + __color.get_resize(-100,-100,-100,_spectrum,0):CImg(), + &color = _color?_color:(__color?__color:default_color); + const tc *const pcolor = color._data; + float opacity = __draw_object3d(opacities,n_primitive,_opacity); + if (_opacity.is_empty()) opacity*=g_opacity; + else if (!_opacity.is_shared()) _opacity*=g_opacity; + +#ifdef cimg_use_board + LibBoard::Board &board = *(LibBoard::Board*)pboard; +#endif + + switch (primitive.size()) { + case 1 : { // Colored point or sprite + const unsigned int n0 = (unsigned int)primitive[0]; + const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1); + + if (_opacity.is_empty()) { // Scalar opacity + + if (color.size()==_spectrum) { // Colored point + draw_point(x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height()-(float)y0); + } +#endif + } else { // Sprite + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(color._width*factor), + _sh = (unsigned int)(color._height*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + 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); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } else { // Opacity mask + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(std::max(color._width,_opacity._width)*factor), + _sh = (unsigned int)(std::max(color._height,_opacity._height)*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg(), + &sprite = _sprite?_sprite:color; + const CImg<_to> + _nopacity = (sw!=_opacity._width || sh!=_opacity._height)? + _opacity.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<_to>(), + &nopacity = _nopacity?_nopacity:_opacity; + draw_image(nx0,ny0,sprite,nopacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } break; + case 2 : { // Colored line + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity); + else draw_line(x0,y0,x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 5 : { // Colored sphere + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + is_wireframe = (unsigned int)primitive[2]; + const float + Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)), + Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)), + Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)), + zc = Z + Zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(n1,0) - vertices(n0,0), + vertices(n1,1) - vertices(n0,1), + vertices(n1,2) - vertices(n0,2))*(absfocale?absfocale/zc:1); + switch (render_type) { + case 0 : + draw_point((int)xc,(int)yc,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot(xc,height() - yc); + } +#endif + break; + case 1 : + draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } +#endif + break; + default : + if (is_wireframe) draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); + else draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + if (!is_wireframe) board.fillCircle(xc,height() - yc,radius); + else { + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } + } +#endif + break; + } + } break; + case 6 : { // Textured line + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for line primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + tx0 = (int)primitive[2], ty0 = (int)primitive[3], + tx1 = (int)primitive[4], ty1 = (int)primitive[5], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity); + else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 3 : { // Colored triangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity).draw_point(x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x0,y0,x2,y2,pcolor,opacity). + draw_line(x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)); + else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + } break; + } + } break; + case 4 : { // Colored quadrangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1), + x3 = (int)projections(n3,0), y3 = (int)projections(n3,1), + xc = (x0 + x1 + x2 + x3)/4, yc = (y0 + y1 + y2 + y3)/4; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity). + draw_point(x2,y2,pcolor,opacity).draw_point(x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opacity).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x1,y1,x2,y2,pcolor,opacity). + draw_line(x2,y2,x3,y3,pcolor,opacity).draw_line(x3,y3,x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity); + else + draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity,lightprops(l)); + else + _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)). + _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp),(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop2)/4; + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,xc,yc,zc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,light_texture,lx0,ly0,lx1,ly1,lxc,lyc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + case 9 : { // Textured triangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for triangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + tx0 = (int)primitive[3], ty0 = (int)primitive[4], + tx1 = (int)primitive[5], ty1 = (int)primitive[6], + tx2 = (int)primitive[7], ty2 = (int)primitive[8], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + break; + } + } break; + case 12 : { // Textured quadrangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for quadrangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + tx0 = (int)primitive[4], ty0 = (int)primitive[5], + tx1 = (int)primitive[6], ty1 = (int)primitive[7], + tx2 = (int)primitive[8], ty2 = (int)primitive[9], + tx3 = (int)primitive[10], ty3 = (int)primitive[11], + 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; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity). + draw_point(x3,y3,color.get_vector_at(tx3<=0?0:tx3>=color.width()?color.width() - 1:tx3, + ty3<=0?0:ty3>=color.height()?color.height() - 1:ty3)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop3)/4; + 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); + 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); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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,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); + 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); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + } + } + if (render_type==5) cimg::mutex(10,0); + return *this; + } + + //@} + //--------------------------- + // + //! \name Data Input + //@{ + //--------------------------- + + //! Launch simple interface to select a shape from an image. + /** + \param disp Display window to use. + \param feature_type Type of feature to select. Can be { 0=point | 1=line | 2=rectangle | 3=ellipse }. + \param XYZ Pointer to 3 values X,Y,Z which tells about the projection point coordinates, for volumetric images. + \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 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); + } + + //! 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 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); + } + + //! 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 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); + } + + //! 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 bool exit_on_anykey=false, + const bool is_deep_selection_default=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,XYZ,0,0,0,exit_on_anykey,true,false,is_deep_selection_default); + } + + CImg _select(CImgDisplay &disp, const char *const title, + const unsigned int feature_type, unsigned int *const XYZ, + const int origX, const int origY, const int origZ, + const bool exit_on_anykey, + const bool reset_view3d, + const bool force_display_z_coord, + const bool is_deep_selection_default) const { + if (is_empty()) return CImg(1,feature_type==0?3:6,1,1,-1); + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + } else { + if (title) disp.set_title("%s",title); + disp.move_inside_screen(); + } + + CImg thumb; + if (width()>disp.screen_width() || height()>disp.screen_height()) + get_resize(cimg_fitscreen(width(),height(),depth()),depth(),-100).move_to(thumb); + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0).set_wheel().show_mouse(); + + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + int area = 0, 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), + 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; + + bool is_deep_selection = is_deep_selection_default, + shape_selected = false, text_down = false, visible_cursor = true; + static CImg pose3d; + static bool is_view3d = false, is_axes = true; + if (reset_view3d) { pose3d.assign(); is_view3d = false; } + CImg points3d, opacities3d, sel_opacities3d; + CImgList primitives3d, sel_primitives3d; + CImgList colors3d, sel_colors3d; + CImg visu, visu0, view3d; + CImg text(1024); *text = 0; + + while (!key && !disp.is_closed() && !shape_selected) { + + // Handle mouse motion and selection + int + mx = disp.mouse_x(), + my = disp.mouse_y(); + + const float + mX = mx<0?-1.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 && mX=0 && mX=height()) { area = 2; X = mX; Z = mY - _height; Y = (float)(phase?Y1:Y0); } + if (mY>=0 && mX>=width() && mY=width() && mY>=height()) area = 4; + if (disp.button()) { if (!area_clicked) area_clicked = area; } else area_clicked = 0; + + CImg filename(32); + + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(1); key = 0; } break; + case cimg::keyPAGEDOWN : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(-1); key = 0; } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_axes = !is_axes; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_view3d = !is_view3d; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot...",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + } + + switch (area) { + + case 0 : // When mouse is out of image range + mx = my = -1; X = Y = Z = -1; + break; + + case 1 : case 2 : case 3 : { // When mouse is over the XY,XZ or YZ projections + const unsigned int but = disp.button(); + const bool b1 = (bool)(but&1), b2 = (bool)(but&2), b3 = (bool)(but&4); + + if (b1 && phase==1 && area_clicked==area) { // When selection has been started (1st step) + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } + if (!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 (phase) { + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } else { + if (_depth>1 && (X0!=(int)X || Y0!=(int)Y || Z0!=(int)Z)) visu0.assign(); + X0 = (int)X; Y0 = (int)Y; Z0 = (int)Z; + } + } + if (b3) { // Reset selection + 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 (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() && + !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT()) { + switch (area) { + case 1 : + if (phase) Z = (float)(Z1+=disp.wheel()); else Z = (float)(Z0+=disp.wheel()); + visu0.assign(); break; + case 2 : + if (phase) Y = (float)(Y1+=disp.wheel()); else Y = (float)(Y0+=disp.wheel()); + visu0.assign(); break; + case 3 : + if (phase) X = (float)(X1+=disp.wheel()); else X = (float)(X0+=disp.wheel()); + visu0.assign(); break; + } + disp.set_wheel(); + } else key = ~0U; + } + + if ((phase==0 && b1) || + (phase==1 && !b1) || + (phase==2 && b1)) switch (phase) { // Detect change of phase + case 0 : + if (area==area_clicked) { + X0 = X1 = (int)X; Y0 = Y1 = (int)Y; Z0 = Z1 = (int)Z; area_started = area; ++phase; + } break; + case 1 : + if (area==area_started) { + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++phase; + if (_depth>1) { + if (disp.is_keyCTRLLEFT()) is_deep_selection = !is_deep_selection_default; + if (is_deep_selection) ++phase; + } + } else if (!b1) { X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = 0; visu0.assign(); } + break; + case 2 : ++phase; break; + } + } break; + + case 4 : // When mouse is over the 3D view + if (is_view3d && points3d) { + X3d = mx - width()*disp.width()/(width() + (depth()>1?depth():0)); + Y3d = my - height()*disp.height()/(height() + (depth()>1?depth():0)); + if (oX3d<0) { oX3d = X3d; oY3d = Y3d; } + // Left + right buttons: reset. + if ((disp.button()&3)==3) { pose3d.assign(); view3d.assign(); oX3d = oY3d = X3d = Y3d = -1; } + else if (disp.button()&1 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Left button: rotate + const float + R = 0.45f*std::min(view3d._width,view3d._height), + R2 = R*R, + u0 = (float)(oX3d - view3d.width()/2), + v0 = (float)(oY3d - view3d.height()/2), + u1 = (float)(X3d - view3d.width()/2), + v1 = (float)(Y3d - view3d.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + pose3d.draw_image(CImg::rotation_matrix(u,v,w,-alpha)*pose3d.get_crop(0,0,2,2)); + view3d.assign(); + } else if (disp.button()&2 && pose3d && oY3d!=Y3d) { // Right button: zoom + pose3d(3,2)+=(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 + pose3d(3,0)-=oX3d - X3d; pose3d(3,1)-=oY3d - Y3d; view3d.assign(); + } + oX3d = X3d; oY3d = Y3d; + } + mx = my = -1; X = Y = Z = -1; + break; + } + + if (phase) { + if (!feature_type) shape_selected = phase?true:false; + else { + if (_depth>1) shape_selected = (phase==3)?true:false; + else shape_selected = (phase==2)?true:false; + } + } + + if (X0<0) X0 = 0; + if (X0>=width()) X0 = width() - 1; + if (Y0<0) Y0 = 0; + if (Y0>=height()) Y0 = height() - 1; + if (Z0<0) Z0 = 0; + if (Z0>=depth()) Z0 = depth() - 1; + if (X1<1) X1 = 0; + if (X1>=width()) X1 = width() - 1; + if (Y1<0) Y1 = 0; + if (Y1>=height()) Y1 = height() - 1; + if (Z1<0) Z1 = 0; + if (Z1>=depth()) Z1 = depth() - 1; + + // Draw visualization image on the display + if (mx!=omx || my!=omy || !visu0 || (_depth>1 && !view3d)) { + + if (!visu0) { // Create image of projected planes + if (thumb) thumb._get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + else _get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + visu0.resize(disp); + view3d.assign(); + points3d.assign(); + } + + if (is_view3d && _depth>1 && !view3d) { // Create 3D view for volumetric images + const unsigned int + _x3d = (unsigned int)cimg::round((float)_width*visu0._width/(_width + _depth),1,1), + _y3d = (unsigned int)cimg::round((float)_height*visu0._height/(_height + _depth),1,1), + x3d = _x3d>=visu0._width?visu0._width - 1:_x3d, + y3d = _y3d>=visu0._height?visu0._height - 1:_y3d; + CImg(1,2,1,1,64,128).resize(visu0._width - x3d,visu0._height - y3d,1,visu0._spectrum,3). + move_to(view3d); + if (!points3d) { + get_projections3d(primitives3d,colors3d,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0,true).move_to(points3d); + points3d.append(CImg(8,3,1,1, + 0,_width - 1,_width - 1,0,0,_width - 1,_width - 1,0, + 0,0,_height - 1,_height - 1,0,0,_height - 1,_height - 1, + 0,0,0,0,_depth - 1,_depth - 1,_depth - 1,_depth - 1),'x'); + CImg::vector(12,13).move_to(primitives3d); CImg::vector(13,14).move_to(primitives3d); + CImg::vector(14,15).move_to(primitives3d); CImg::vector(15,12).move_to(primitives3d); + CImg::vector(16,17).move_to(primitives3d); CImg::vector(17,18).move_to(primitives3d); + CImg::vector(18,19).move_to(primitives3d); CImg::vector(19,16).move_to(primitives3d); + CImg::vector(12,16).move_to(primitives3d); CImg::vector(13,17).move_to(primitives3d); + CImg::vector(14,18).move_to(primitives3d); CImg::vector(15,19).move_to(primitives3d); + colors3d.insert(12,CImg::vector(255,255,255)); + opacities3d.assign(primitives3d.width(),1,1,1,0.5f); + if (!phase) { + opacities3d[0] = opacities3d[1] = opacities3d[2] = 0.8f; + sel_primitives3d.assign(); + sel_colors3d.assign(); + sel_opacities3d.assign(); + } else { + if (feature_type==2) { + points3d.append(CImg(8,3,1,1, + X0,X1,X1,X0,X0,X1,X1,X0, + Y0,Y0,Y1,Y1,Y0,Y0,Y1,Y1, + Z0,Z0,Z0,Z0,Z1,Z1,Z1,Z1),'x'); + sel_primitives3d.assign(); + CImg::vector(20,21).move_to(sel_primitives3d); + CImg::vector(21,22).move_to(sel_primitives3d); + CImg::vector(22,23).move_to(sel_primitives3d); + CImg::vector(23,20).move_to(sel_primitives3d); + CImg::vector(24,25).move_to(sel_primitives3d); + CImg::vector(25,26).move_to(sel_primitives3d); + CImg::vector(26,27).move_to(sel_primitives3d); + CImg::vector(27,24).move_to(sel_primitives3d); + CImg::vector(20,24).move_to(sel_primitives3d); + CImg::vector(21,25).move_to(sel_primitives3d); + CImg::vector(22,26).move_to(sel_primitives3d); + CImg::vector(23,27).move_to(sel_primitives3d); + } else { + points3d.append(CImg(2,3,1,1, + X0,X1, + Y0,Y1, + Z0,Z1),'x'); + sel_primitives3d.assign(CImg::vector(20,21)); + } + sel_colors3d.assign(sel_primitives3d._width,CImg::vector(255,255,255)); + sel_opacities3d.assign(sel_primitives3d._width,1,1,1,0.8f); + } + points3d.shift_object3d(-0.5f*(_width - 1),-0.5f*(_height - 1),-0.5f*(_depth - 1)).resize_object3d(); + points3d*=0.75f*std::min(view3d._width,view3d._height); + } + + if (!pose3d) CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose3d); + CImg zbuffer3d(view3d._width,view3d._height,1,1,0); + const CImg rotated_points3d = pose3d.get_crop(0,0,2,2)*points3d; + if (sel_primitives3d) + view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width, + pose3d(3,1) + 0.5f*view3d._height, + pose3d(3,2), + rotated_points3d,sel_primitives3d,sel_colors3d,sel_opacities3d, + 2,true,500,0,0,0,0,0,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,1,zbuffer3d); + visu0.draw_image(x3d,y3d,view3d); + } + visu = visu0; + + if (X<0 || Y<0 || Z<0) { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + else { + if (is_axes) { if (visible_cursor) { disp.hide_mouse(); visible_cursor = false; }} + else { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + const int d = (depth()>1)?depth():0; + int _vX = (int)X, _vY = (int)Y, _vZ = (int)Z; + if (phase>=2) { _vX = X1; _vY = Y1; _vZ = Z1; } + int + w = disp.width(), W = width() + d, + h = disp.height(), H = height() + d, + _xp = (int)(_vX*(float)w/W), xp = _xp + ((int)(_xp*(float)W/w)!=_vX), + _yp = (int)(_vY*(float)h/H), yp = _yp + ((int)(_yp*(float)H/h)!=_vY), + _xn = (int)((_vX + 1.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.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, + zyc = (zyp + zyn)/2, + xf = (int)(X*w/W), + yf = (int)(Y*h/H), + zxf = (int)((Z + width())*w/W), + zyf = (int)((Z + height())*h/H); + + if (is_axes) { // Draw axes + visu.draw_line(0,yf,visu.width() - 1,yf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,yf,visu.width() - 1,yf,background_color,0.7f,0x00FF00FF). + draw_line(xf,0,xf,visu.height() - 1,foreground_color,0.7f,0xFF00FF00). + draw_line(xf,0,xf,visu.height() - 1,background_color,0.7f,0x00FF00FF); + if (_depth>1) + visu.draw_line(zxf,0,zxf,yM,foreground_color,0.7f,0xFF00FF00). + draw_line(zxf,0,zxf,yM,background_color,0.7f,0x00FF00FF). + draw_line(0,zyf,xM,zyf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,zyf,xM,zyf,background_color,0.7f,0x00FF00FF); + } + + // Draw box cursor. + if (xn - xp>=4 && yn - yp>=4) + visu.draw_rectangle(xp,yp,xn,yn,foreground_color,0.2f). + draw_rectangle(xp,yp,xn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,yp,xn,yn,background_color,1,0x55555555); + if (_depth>1) { + if (yn - yp>=4 && zxn - zxp>=4) + visu.draw_rectangle(zxp,yp,zxn,yn,background_color,0.2f). + draw_rectangle(zxp,yp,zxn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(zxp,yp,zxn,yn,background_color,1,0x55555555); + if (xn - xp>=4 && zyn - zyp>=4) + visu.draw_rectangle(xp,zyp,xn,zyn,background_color,0.2f). + draw_rectangle(xp,zyp,xn,zyn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,zyp,xn,zyn,background_color,1,0x55555555); + } + + // Draw selection. + if (phase && (phase!=1 || area_started==area)) { + 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.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.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); + if (d) { + visu.draw_arrow(zxc0,yc0,zxc,yc,background_color,0.9f,30,5,0x55555555). + draw_arrow(zxc0,yc0,zxc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA). + draw_arrow(xc0,zyc0,xc,zyc,background_color,0.9f,30,5,0x55555555). + draw_arrow(xc0,zyc0,xc,zyc,foreground_color,0.9f,30,5,0xAAAAAAAA); + } + } break; + case 2 : { + visu.draw_rectangle(X0=0 && my<13) text_down = true; else if (my>=visu.height() - 13) text_down = false; + if (!feature_type || !phase) { + if (X>=0 && Y>=0 && Z>=0 && X1 || force_display_z_coord) + cimg_snprintf(text,text._width," Point (%d,%d,%d) = [ ",origX + (int)X,origY + (int)Y,origZ + (int)Z); + else cimg_snprintf(text,text._width," Point (%d,%d) = [ ",origX + (int)X,origY + (int)Y); + CImg values = get_vector_at((int)X,(int)Y,(int)Z); + const bool is_large_spectrum = values._height>16; + if (is_large_spectrum) + values.draw_image(0,8,values.get_rows(values._height - 8,values._height - 1)).resize(1,16,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," (...)"); + ctext += std::strlen(ctext); + } + *(ctext++) = ' '; *ctext = 0; + } + std::strcpy(text._data + std::strlen(text),"] "); + } + } else switch (feature_type) { + case 1 : { + const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), + length = cimg::round(cimg::hypot(dX,dY,dZ),0.1); + 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 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 ", + origX + (X01 || force_display_z_coord) + cimg_snprintf(text,text._width," Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d) ", + origX + X0,origY + Y0,origZ + Z0,origX + X1,origY + Y1,origZ + Z1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1),1 + cimg::abs(Z0 - Z1)); + else cimg_snprintf(text,text._width," Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d) ", + origX + X0,origY + Y0,origX + X1,origY + Y1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1)); + } + if (phase || (mx>=0 && my>=0)) visu.__draw_text("%s",text_down,text._data); + } + + disp.display(visu); + } + if (!shape_selected) disp.wait(); + if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); } + omx = mx; omy = my; + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + + // Return result. + CImg res(1,feature_type==0?3:6,1,1,-1); + if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; } + if (shape_selected) { + if (feature_type==2) { + if (is_deep_selection) switch (area_started) { + case 1 : Z0 = 0; Z1 = _depth - 1; break; + case 2 : Y0 = 0; Y1 = _height - 1; break; + case 3 : X0 = 0; X1 = _width - 1; break; + } + if (X0>X1) cimg::swap(X0,X1); + if (Y0>Y1) cimg::swap(Y0,Y1); + if (Z0>Z1) cimg::swap(Z0,Z1); + } + if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1; + switch (feature_type) { + case 1 : case 2 : res[0] = X0; res[1] = Y0; res[2] = Z0; res[3] = X1; res[4] = Y1; res[5] = Z1; break; + case 3 : + res[3] = cimg::abs(X1 - X0); res[4] = cimg::abs(Y1 - Y0); res[5] = cimg::abs(Z1 - Z0); + res[0] = X0; res[1] = Y0; res[2] = Z0; + break; + default : res[0] = X0; res[1] = Y0; res[2] = Z0; + } + } + if (!exit_on_anykey || !(disp.button()&4)) disp.set_button(); + if (!visible_cursor) disp.show_mouse(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + if (key!=~0U) disp.set_key(key); + return res; + } + + // Return a visualizable uchar8 image for display routines. + CImg _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; + if (_depth>1) { + const int mdisp = std::min(disp.screen_width(),disp.screen_height()); + if (depth()>mdisp) { + crop.get_resize(-100,-100,mdisp,-100,0).move_to(img2d); + img2d.projections2d(x,y,z*img2d._depth/_depth); + } else crop.get_projections2d(x,y,z).move_to(img2d); + } else CImg(crop,false).move_to(img2d); + + // Check for inf and NaN values. + if (cimg::type::is_float() && normalization) { + bool is_inf = false, is_nan = false; + cimg_for(img2d,ptr,Tuchar) + if (cimg::type::is_inf(*ptr)) { is_inf = true; break; } + else if (cimg::type::is_nan(*ptr)) { is_nan = true; break; } + if (is_inf || is_nan) { + 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 + 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); + if (is_nan) + cimg_for(img2d,ptr,Tuchar) + 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 + } + } + + switch (normalization) { + case 1 : img2d.normalize((ucharT)0,(ucharT)255); break; + case 2 : { + const float m = disp._min, M = disp._max; + (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.f/(M - m>0?M - m:1); + } break; + } + if (img2d.spectrum()==2) img2d.channels(0,2); + return img2d; + } + + //! Select sub-graph in a graph. + CImg get_select_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "select_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title("CImg<%s>",pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth; + const unsigned int old_normalization = disp.normalization(); + disp.show().set_button().set_wheel()._normalization = 0; + + double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax; + if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; } + if (nymin==nymax) { --nymin; ++nymax; } + if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.; } + + 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 }; + + 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) { + ulongT 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; + CImg message(1024); + CImg_3x3(I,unsigned char); + + for (bool selected = false; !selected && !disp.is_closed() && !okey && !disp.wheel(); ) { + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + const unsigned int key = disp.key(), button = disp.button(); + + // Generate graph representation. + if (!visu0) { + visu0.assign(disp.width(),disp.height(),1,3,220); + const int gdimx = disp.width() - 32, gdimy = disp.height() - 32; + if (gdimx>0 && gdimy>0) { + graph.assign(gdimx,gdimy,1,3,255); + if (siz<32) { + if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0, + false,true,black,0.2f,0x33333333,0x33333333); + } else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333); + cimg_forC(*this,c) + graph.draw_graph(get_shared_channel(c),&colormap(0,c),(plot_type!=3 || _spectrum==1)?1:0.6f, + plot_type,vertex_type,nymax,nymin); + + axes.assign(gdimx,gdimy,1,1,0); + const float + dx = (float)cimg::abs(nxmax - nxmin), dy = (float)cimg::abs(nymax - nymin), + px = (float)std::pow(10.,(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); + + const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0); + axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero); + if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero); + if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero); + if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero); + if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero); + + cimg_for3x3(axes,x,y,0,0,I,unsigned char) + if (Icc) { + if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0; + else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3); + } + else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) + cimg_forC(graph,c) graph(x,y,c) = (unsigned char)((graph(x,y,c) + 511)/3); + + visu0.draw_image(16,16,graph); + visu0.draw_line(15,15,16 + gdimx,15,gray2).draw_line(16 + gdimx,15,16 + gdimx,16 + gdimy,gray2). + draw_line(16 + gdimx,16 + gdimy,15,16 + gdimy,white).draw_line(15,16 + gdimy,15,15,white); + } else graph.assign(); + text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1,13).resize(-100,-100,1,3); + visu0.draw_image((visu0.width() - text.width())/2,visu0.height() - 14,~text); + text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3); + visu0.draw_image(1,(visu0.height() - text.height())/2,~text); + visu.assign(); + } + + // Generate and display current view. + if (!visu) { + visu.assign(visu0); + if (graph && x0>=0 && x1>=0) { + const int + nx0 = x0<=x1?x0:x1, + nx1 = x0<=x1?x1:x0, + ny0 = y0<=y1?y0:y1, + ny1 = y0<=y1?y1:y0, + sx0 = (int)(16 + nx0*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sx1 = (int)(15 + (nx1 + 1)*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sy0 = 16 + ny0, + sy1 = 16 + ny1; + if (y0>=0 && y1>=0) + visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU); + else visu.draw_rectangle(sx0,0,sx1,visu.height() - 17,gray,0.5f). + draw_line(sx0,16,sx0,visu.height() - 17,black,0.5f,0xCCCCCCCCU). + draw_line(sx1,16,sx1,visu.height() - 17,black,0.5f,0xCCCCCCCCU); + } + if (mouse_x>=16 && mouse_y>=16 && mouse_x=7) + cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx, + (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2), + (double)(*this)(x,0,0,_spectrum - 4),(double)(*this)(x,0,0,_spectrum - 3), + (double)(*this)(x,0,0,_spectrum - 1)); + else { + cimg_snprintf(message,message._width,"Value[%u:%g] = ( ",x,cx); + cimg_forC(*this,c) cimg_sprintf(message._data + std::strlen(message),"%g ",(double)(*this)(x,0,0,c)); + cimg_sprintf(message._data + std::strlen(message),")"); + } + if (x0>=0 && x1>=0) { + const unsigned int + nx0 = (unsigned int)(x0<=x1?x0:x1), + nx1 = (unsigned int)(x0<=x1?x1:x0), + ny0 = (unsigned int)(y0<=y1?y0:y1), + ny1 = (unsigned int)(y0<=y1?y1:y0); + const double + cx0 = nxmin + nx0*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cx1 = nxmin + (nx1 + one)*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cy0 = nymax - ny0*(nymax - nymin)/(visu._height - 32), + cy1 = nymax - ny1*(nymax - nymin)/(visu._height - 32); + if (y0>=0 && y1>=0) + cimg_sprintf(message._data + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )", + x0,cx0,cy0,x1 + one,cx1,cy1); + else + cimg_sprintf(message._data + std::strlen(message)," - Range [ %u:%g - %u:%g ]", + x0,cx0,x1 + one,cx1); + } + text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3); + visu.draw_image((visu.width() - text.width())/2,1,~text); + } + visu.display(disp); + } + + // Test keys. + CImg filename(32); + switch (okey = key) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(CImgDisplay::screen_width()/2, + CImgDisplay::screen_height()/2,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving snapshot... ",false).display(disp); + screen.save(filename); + (+screen).__draw_text(" Snapshot '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving instance... ",false).display(disp); + save(filename); + (+screen).__draw_text(" Instance '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + } + + // Handle mouse motion and mouse buttons. + if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) { + visu.assign(); + if (disp.mouse_x()>=0 && disp.mouse_y()>=0) { + const int + mx = (mouse_x - 16)*(int)(siz - one)/(disp.width() - 32), + cx = cimg::cut(mx,0,(int)(siz - 1 - one)), + my = mouse_y - 16, + cy = cimg::cut(my,0,disp.height() - 32); + if (button&1) { + if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; } + } + else if (button&2) { + if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; } + } + else if (obutton) { x1 = x1>=0?cx:-1; y1 = y1>=0?cy:-1; selected = true; } + } else if (!button && obutton) selected = true; + obutton = button; omouse_x = mouse_x; omouse_y = mouse_y; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (visu && visu0) disp.wait(); + if (!exit_on_anykey && okey && okey!=cimg::keyESC && + (okey!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + okey = 0; + } + } + + disp._normalization = old_normalization; + if (x1>=0 && x1(4,1,1,1,x0,y0,x1>=0?x1 + (int)one:-1,y1); + } + + //! Load image from a file. + /** + \param filename Filename, as a C-string. + \note The extension of \c filename defines the file format. If no filename + extension is provided, CImg::get_load() will try to load the file as a .cimg or .cimgz file. + **/ + CImg& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load(): Specified filename is (null).", + cimg_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimg_load_plugin + cimg_load_plugin(filename); +#endif +#ifdef cimg_load_plugin1 + cimg_load_plugin1(filename); +#endif +#ifdef cimg_load_plugin2 + cimg_load_plugin2(filename); +#endif +#ifdef cimg_load_plugin3 + cimg_load_plugin3(filename); +#endif +#ifdef cimg_load_plugin4 + cimg_load_plugin4(filename); +#endif +#ifdef cimg_load_plugin5 + cimg_load_plugin5(filename); +#endif +#ifdef cimg_load_plugin6 + cimg_load_plugin6(filename); +#endif +#ifdef cimg_load_plugin7 + cimg_load_plugin7(filename); +#endif +#ifdef cimg_load_plugin8 + cimg_load_plugin8(filename); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) load_dlm(filename); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) load_jpeg(filename); + else if (!cimg::strcasecmp(ext,"png")) load_png(filename); + else if (!cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"pnm") || + !cimg::strcasecmp(ext,"pbm") || + !cimg::strcasecmp(ext,"pnk")) load_pnm(filename); + else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename); + else if (!cimg::strcasecmp(ext,"cr2") || + !cimg::strcasecmp(ext,"crw") || + !cimg::strcasecmp(ext,"dcr") || + !cimg::strcasecmp(ext,"mrw") || + !cimg::strcasecmp(ext,"nef") || + !cimg::strcasecmp(ext,"orf") || + !cimg::strcasecmp(ext,"pix") || + !cimg::strcasecmp(ext,"ptx") || + !cimg::strcasecmp(ext,"raf") || + !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"dcm") || + !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) load_analyze(filename); + else if (!cimg::strcasecmp(ext,"par") || + !cimg::strcasecmp(ext,"rec")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"mnc")) load_minc2(filename); + else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) return load_cimg(filename); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + + // Image sequences + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded) { + std::FILE *file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to open file '%s'.", + cimg_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + cimg::fclose(file); + is_loaded = true; + try { + if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename); + else if (!cimg::strcasecmp(f_type,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(f_type,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(f_type,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(f_type,"jpg")) load_jpeg(filename); + else if (!cimg::strcasecmp(f_type,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(f_type,"png")) load_png(filename); + else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename); + else if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file with other means. + if (!is_loaded) { + try { + load_other(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image from a file \newinstance. + static CImg get_load(const char *const filename) { + return CImg().load(filename); + } + + //! Load image from an Ascii file. + /** + \param filename Filename, as a C -string. + **/ + CImg& load_ascii(const char *const filename) { + return _load_ascii(0,filename); + } + + //! Load image from an Ascii file \inplace. + static CImg get_load_ascii(const char *const filename) { + return CImg().load_ascii(filename); + } + + //! Load image from an Ascii file \overloading. + CImg& load_ascii(std::FILE *const file) { + return _load_ascii(file,0); + } + + //! Loadimage from an Ascii file \newinstance. + static CImg get_load_ascii(std::FILE *const file) { + return CImg().load_ascii(file); + } + + CImg& _load_ascii(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_ascii(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg line(256); *line = 0; + int err = std::fscanf(nfile,"%255[^\n]",line._data); + unsigned int dx = 0, dy = 1, dz = 1, dc = 1; + cimg_sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc); + err = std::fscanf(nfile,"%*[^0-9.eEinfa+-]"); + if (!dx || !dy || !dz || !dc) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_ascii(): Invalid Ascii header in file '%s', image dimensions are set " + "to (%u,%u,%u,%u).", + cimg_instance, + filename?filename:"(FILE*)",dx,dy,dz,dc); + } + assign(dx,dy,dz,dc); + const ulongT siz = size(); + ulongT off = 0; + double val; + T *ptr = _data; + for (err = 1, off = 0; off& load_dlm(const char *const filename) { + return _load_dlm(0,filename); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(const char *const filename) { + return CImg().load_dlm(filename); + } + + //! Load image from a DLM file \overloading. + CImg& load_dlm(std::FILE *const file) { + return _load_dlm(file,0); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(std::FILE *const file) { + return CImg().load_dlm(file); + } + + CImg& _load_dlm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_dlm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + CImg delimiter(256), tmp(256); *delimiter = *tmp = 0; + unsigned int cdx = 0, dx = 0, dy = 0; + int err = 0; + double val; + assign(256,256,1,1,(T)0); + while ((err = std::fscanf(nfile,"%lf%255[^0-9eEinfa.+-]",&val,delimiter._data))>0) { + if (err>0) (*this)(cdx++,dy) = (T)val; + if (cdx>=_width) resize(3*_width/2,_height,1,1,0); + char c = 0; + if (!cimg_sscanf(delimiter,"%255[^\n]%c",tmp._data,&c) || c=='\n') { + dx = std::max(cdx,dx); + if (++dy>=_height) resize(_width,3*_height/2,1,1,0); + cdx = 0; + } + } + if (cdx && err==1) { dx = cdx; ++dy; } + if (!dx || !dy) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_dlm(): Invalid DLM file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + resize(dx,dy,1,1,0); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a BMP file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_bmp(const char *const filename) { + return _load_bmp(0,filename); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(const char *const filename) { + return CImg().load_bmp(filename); + } + + //! Load image from a BMP file \overloading. + CImg& load_bmp(std::FILE *const file) { + return _load_bmp(file,0); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(std::FILE *const file) { + return CImg().load_bmp(file); + } + + CImg& _load_bmp(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_bmp(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(54); + cimg::fread(header._data,54,nfile); + if (*header!='B' || header[1]!='M') { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_bmp(): Invalid BMP file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read header and pixel buffer + int + file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24), + offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24), + header_size = header[0x0E] + (header[0x0F]<<8) + (header[0x10]<<16) + (header[0x11]<<24), + dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24), + dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24), + compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24), + nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24), + bpp = header[0x1C] + (header[0x1D]<<8); + + if (!file_size || file_size==offset) { + cimg::fseek(nfile,0,SEEK_END); + file_size = (int)cimg::ftell(nfile); + cimg::fseek(nfile,54,SEEK_SET); + } + if (header_size>40) cimg::fseek(nfile,header_size - 40,SEEK_CUR); + + const int + dx_bytes = (bpp==1)?(dx/8 + (dx%8?1:0)):((bpp==4)?(dx/2 + (dx%2)):(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); + + CImg colormap; + if (bpp<16) { if (!nb_colors) nb_colors = 1<0) cimg::fseek(nfile,xoffset,SEEK_CUR); + + CImg buffer; + if (buf_size=2) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0x80, val = 0; + cimg_forX(*this,x) { + if (mask==0x80) val = *(ptrs++); + const unsigned char *col = (unsigned char*)(colormap._data + (val&mask?1:0)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask); + } + ptrs+=align_bytes; + } + } break; + case 4 : { // 16 colors + if (colormap._width>=16) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0xF0, val = 0; + cimg_forX(*this,x) { + if (mask==0xF0) val = *(ptrs++); + const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4)); + const unsigned char *col = (unsigned char*)(colormap._data + color); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask,4); + } + ptrs+=align_bytes; + } + } break; + case 8 : { // 256 colors + if (colormap._width>=256) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char *col = (unsigned char*)(colormap._data + *(ptrs++)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + } + ptrs+=align_bytes; + } + } break; + case 16 : { // 16 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char c1 = *(ptrs++), c2 = *(ptrs++); + const unsigned short col = (unsigned short)(c1|(c2<<8)); + (*this)(x,y,2) = (T)(col&0x1F); + (*this)(x,y,1) = (T)((col>>5)&0x1F); + (*this)(x,y,0) = (T)((col>>10)&0x1F); + } + ptrs+=align_bytes; + } + } break; + case 24 : { // 24 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + } + ptrs+=align_bytes; + } + } break; + case 32 : { // 32 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + ++ptrs; + } + ptrs+=align_bytes; + } + } break; + } + if (dy<0) mirror('y'); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a JPEG file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_jpeg(const char *const filename) { + return _load_jpeg(0,filename); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(const char *const filename) { + return CImg().load_jpeg(filename); + } + + //! Load image from a JPEG file \overloading. + CImg& load_jpeg(std::FILE *const file) { + return _load_jpeg(file,0); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(std::FILE *const file) { + return CImg().load_jpeg(file); + } + + // Custom error handler for libjpeg. +#ifdef cimg_use_jpeg + struct _cimg_error_mgr { + struct jpeg_error_mgr original; + jmp_buf setjmp_buffer; + char message[JMSG_LENGTH_MAX]; + }; + + typedef struct _cimg_error_mgr *_cimg_error_ptr; + + METHODDEF(void) _cimg_jpeg_error_exit(j_common_ptr cinfo) { + _cimg_error_ptr c_err = (_cimg_error_ptr) cinfo->err; // Return control to the setjmp point + (*cinfo->err->format_message)(cinfo,c_err->message); + jpeg_destroy(cinfo); // Clean memory and temp files + longjmp(c_err->setjmp_buffer,1); + } +#endif + + CImg& _load_jpeg(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_jpeg(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_jpeg + if (file) + throw CImgIOException(_cimg_instance + "load_jpeg(): Unable to load data from '(FILE*)' unless libjpeg is enabled.", + cimg_instance); + else return load_other(filename); +#else + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + struct jpeg_decompress_struct cinfo; + struct _cimg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr.original); + jerr.original.error_exit = _cimg_jpeg_error_exit; + if (setjmp(jerr.setjmp_buffer)) { // JPEG error + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_jpeg(): Error message returned by libjpeg: %s.", + cimg_instance,jerr.message); + } + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo,nfile); + jpeg_read_header(&cinfo,TRUE); + jpeg_start_decompress(&cinfo); + + if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) { + if (!file) { + cimg::fclose(nfile); + return load_other(filename); + } else + throw CImgIOException(_cimg_instance + "load_jpeg(): Failed to load JPEG data from file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + } + CImg buffer(cinfo.output_width*cinfo.output_components); + JSAMPROW row_pointer[1]; + try { assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); } + catch (...) { if (!file) cimg::fclose(nfile); throw; } + T *ptr_r = _data, *ptr_g = _data + 1UL*_width*_height, *ptr_b = _data + 2UL*_width*_height, + *ptr_a = _data + 3UL*_width*_height; + while (cinfo.output_scanline + // This is experimental code, not much tested, use with care. + CImg& load_magick(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_magick(): Specified filename is (null).", + cimg_instance); +#ifdef cimg_use_magick + Magick::Image image(filename); + const unsigned int W = image.size().width(), H = image.size().height(); + switch (image.type()) { + case Magick::PaletteMatteType : + case Magick::TrueColorMatteType : + case Magick::ColorSeparationType : { + assign(W,H,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + case Magick::PaletteType : + case Magick::TrueColorType : { + assign(W,H,1,3); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + ++pixels; + } + } break; + case Magick::GrayscaleMatteType : { + assign(W,H,1,2); + T *ptr_r = data(0,0,0,0), *ptr_a = data(0,0,0,1); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + default : { + assign(W,H,1,1); + T *ptr_r = data(0,0,0,0); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + ++pixels; + } + } + } + return *this; +#else + throw CImgIOException(_cimg_instance + "load_magick(): Unable to load file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Load image from a file, using Magick++ library \newinstance. + static CImg get_load_magick(const char *const filename) { + return CImg().load_magick(filename); + } + + //! 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. + **/ + CImg& load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return _load_png(0,filename,bits_per_pixel); + } + + //! Load image from a PNG file \newinstance. + static CImg get_load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return CImg().load_png(filename,bits_per_pixel); + } + + //! 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); + } + + //! 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); + } + + // (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) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_png(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_png + cimg::unused(bits_per_pixel); + if (file) + throw CImgIOException(_cimg_instance + "load_png(): Unable to load data from '(FILE*)' unless libpng is enabled.", + cimg_instance); + + else return load_other(filename); +#else + // Open file and check for PNG validity +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb"); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"rb"); +#endif + unsigned char pngCheck[8] = { 0 }; + cimg::fread(pngCheck,8,(std::FILE*)nfile); + if (png_sig_cmp(pngCheck,0,8)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Invalid PNG file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Setup PNG structures for read + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn); + if (!png_ptr) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'png_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'info_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'end_info' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Error handling callback for png file reading + if (setjmp(png_jmpbuf(png_ptr))) { + if (!file) cimg::fclose((std::FILE*)nfile); + png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Encountered unknown fatal error in libpng for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + png_set_sig_bytes(png_ptr, 8); + + // Get PNG Header Info up to data block + png_read_info(png_ptr,info_ptr); + png_uint_32 W, H; + int bit_depth, color_type, interlace_type; + bool is_gray = false; + png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0); + if (bits_per_pixel) *bits_per_pixel = (unsigned int)bit_depth; + + // Transforms to unify image data + if (color_type==PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + color_type = PNG_COLOR_TYPE_RGB; + bit_depth = 8; + } + if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + is_gray = true; + bit_depth = 8; + } + if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + color_type |= PNG_COLOR_MASK_ALPHA; + } + if (color_type==PNG_COLOR_TYPE_GRAY || color_type==PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + color_type |= PNG_COLOR_MASK_COLOR; + is_gray = true; + } + if (color_type==PNG_COLOR_TYPE_RGB) + png_set_filler(png_ptr,0xffffU,PNG_FILLER_AFTER); + + png_read_update_info(png_ptr,info_ptr); + if (bit_depth!=8 && bit_depth!=16) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Invalid bit depth %u in file '%s'.", + cimg_instance, + bit_depth,nfilename?nfilename:"(FILE*)"); + } + const int byte_depth = bit_depth>>3; + + // Allocate memory for image reading + png_bytep *const imgData = new png_bytep[H]; + for (unsigned int row = 0; row& load_pnm(const char *const filename) { + return _load_pnm(0,filename); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(const char *const filename) { + return CImg().load_pnm(filename); + } + + //! Load image from a PNM file \overloading. + CImg& load_pnm(std::FILE *const file) { + return _load_pnm(file,0); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(std::FILE *const file) { + return CImg().load_pnm(file); + } + + CImg& _load_pnm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pnm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + unsigned int ppm_type, W, H, D = 1, colormax = 255; + CImg item(16384,1,1,1,0); + int err, rval, gval, bval; + const longT cimg_iobuffer = (longT)24*1024*1024; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%u",&ppm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %u %u %u %u",&W,&H,&D,&colormax))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): WIDTH and HEIGHT fields undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (ppm_type!=1 && ppm_type!=4) { + if (err==2 || (err==3 && (ppm_type==5 || ppm_type==7 || ppm_type==8 || ppm_type==9))) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%u",&colormax)!=1) + cimg::warn(_cimg_instance + "load_pnm(): COLORMAX field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else { colormax = D; D = 1; } + } + std::fgetc(nfile); + + switch (ppm_type) { + case 1 : { // 2D b&w Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)(rval?0:255); else break; } + } break; + case 2 : { // 2D grey Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)rval; else break; } + } break; + case 3 : { // 2D color Ascii + assign(W,H,1,3); + T *ptrd = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forXY(*this,x,y) { + if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { + *(ptrd++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval; + } else break; + } + } break; + case 4 : { // 2D b&w binary (support 3D PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + unsigned int w = 0, h = 0, d = 0; + for (longT to_read = (longT)((W/8 + (W%8?1:0))*H*D); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + unsigned char mask = 0, val = 0; + for (ulongT off = (ulongT)raw._width; off || mask; mask>>=1) { + if (!mask) { if (off--) val = *(ptrs++); mask = 128; } + *(ptrd++) = (T)((val&mask)?0:255); + if (++w==W) { w = 0; mask = 0; if (++h==H) { h = 0; if (++d==D) break; }} + } + } + } break; + case 5 : case 7 : { // 2D/3D grey binary (support 3D PINK extension) + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } else { // 16 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } + } break; + case 6 : { // 2D color binary + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { // 16 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } + } break; + case 8 : { // 2D/3D grey binary with int32 integers (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const int *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + case 9 : { // 2D/3D grey binary with float values (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const float *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + default : + assign(); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM type 'P%d' found, but type is not supported.", + cimg_instance, + filename?filename:"(FILE*)",ppm_type); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PFM file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pfm(const char *const filename) { + return _load_pfm(0,filename); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(const char *const filename) { + return CImg().load_pfm(filename); + } + + //! Load image from a PFM file \overloading. + CImg& load_pfm(std::FILE *const file) { + return _load_pfm(file,0); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(std::FILE *const file) { + return CImg().load_pfm(file); + } + + CImg& _load_pfm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pfm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + char pfm_type; + CImg item(16384,1,1,1,0); + int W = 0, H = 0, err = 0; + double scale = 0; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%c",&pfm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): PFM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %d %d",&W,&H))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH and HEIGHT fields are undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else if (W<=0 || H<=0) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH (%d) and HEIGHT (%d) fields are invalid in file '%s'.", + cimg_instance,W,H, + filename?filename:"(FILE*)"); + } + if (err==2) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%lf",&scale)!=1) + cimg::warn(_cimg_instance + "load_pfm(): SCALE field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + std::fgetc(nfile); + const bool is_color = (pfm_type=='F'), is_inverted = (scale>0)!=cimg::endianness(); + if (is_color) { + assign(W,H,1,3,(T)0); + CImg buf(3*W); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forY(*this,y) { + cimg::fread(buf._data,3*W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,3*W); + const float *ptrs = buf._data; + cimg_forX(*this,x) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { + assign(W,H,1,1,(T)0); + CImg buf(W); + T *ptrd = data(0,0,0,0); + cimg_forY(*this,y) { + cimg::fread(buf._data,W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,W); + const float *ptrs = buf._data; + cimg_forX(*this,x) *(ptrd++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return mirror('y'); // Most of the .pfm files are flipped along the y-axis + } + + //! Load image from a RGB file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(0,filename,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(filename,dimw,dimh); + } + + //! Load image from a RGB file \overloading. + CImg& load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(file,0,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(file,dimw,dimh); + } + + CImg& _load_rgb(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgb(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/3UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a RGBA file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(0,filename,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(filename,dimw,dimh); + } + + //! Load image from a RGBA file \overloading. + CImg& load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(file,0,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(file,dimw,dimh); + } + + CImg& _load_rgba(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgba(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,4); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2), + *ptr_a = data(0,0,0,3); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/4UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + *(ptr_a++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a TIFF file. + /** + \param filename Filename, as a C-string. + \param first_frame First frame to read (for multi-pages tiff). + \param last_frame Last frame to read (for multi-pages tiff). + \param step_frame Step value of frame reading. + \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. + - 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 + 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) { + 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.", + cimg_instance, + filename); + return load_other(filename); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimg_instance + "load_tiff(): File '%s' contains %u image(s) while specified frame range is [%u,%u] (step %u).", + cimg_instance, + filename,nb_images,nfirst_frame,nlast_frame,nstep_frame); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + TIFFSetDirectory(tif,0); + CImg frame; + for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) { + frame._load_tiff(tif,l,voxel_size,description); + if (l==nfirst_frame) + assign(frame._width,frame._height,1 + (nlast_frame - nfirst_frame)/nstep_frame,frame._spectrum); + if (frame._width>_width || frame._height>_height || frame._spectrum>_spectrum) + resize(std::max(frame._width,_width), + std::max(frame._height,_height),-100, + std::max(frame._spectrum,_spectrum),0); + draw_image(0,0,(l - nfirst_frame)/nstep_frame,frame); + } + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "load_tiff(): Failed to open file '%s'.", + cimg_instance, + filename); + return *this; +#endif + } + + //! Load image from a TIFF file \newinstance. + static CImg 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); + } + + // (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) { + 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) { + 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) { + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (row = 0; rowny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, 0); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0; rr + void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { + t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (unsigned int vv = 0; vvny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, vv); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0;rr& _load_tiff(TIFF *const tif, const unsigned int directory, + 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; + const char *const filename = TIFFFileName(tif); + const bool is_spp = (bool)TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel); + TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx); + TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny); + TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat); + TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample); + TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo); + if (voxel_size) { + const char *s_description = 0; + float vx = 0, vy = 0, vz = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) { + const char *s_desc = std::strstr(s_description,"VX="); + if (s_desc && cimg_sscanf(s_desc,"VX=%f VY=%f VZ=%f",&vx,&vy,&vz)==3) { // CImg format + voxel_size[0] = vx; voxel_size[1] = vy; voxel_size[2] = vz; + } + s_desc = std::strstr(s_description,"spacing="); + if (s_desc && cimg_sscanf(s_desc,"spacing=%f",&vz)==1) { // Fiji format + voxel_size[2] = vz; + } + } + TIFFGetField(tif,TIFFTAG_XRESOLUTION,voxel_size); + TIFFGetField(tif,TIFFTAG_YRESOLUTION,voxel_size + 1); + voxel_size[0] = 1.f/voxel_size[0]; + voxel_size[1] = 1.f/voxel_size[1]; + } + if (description) { + const char *s_description = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) + CImg::string(s_description).move_to(*description); + } + const unsigned int spectrum = !is_spp || photo>=3?(photo>1?3:1):samplesperpixel; + assign(nx,ny,1,spectrum); + + if ((photo>=3 && sampleformat==1 && + (bitspersample==4 || bitspersample==8) && + (samplesperpixel==1 || samplesperpixel==3 || samplesperpixel==4)) || + (bitspersample==1 && samplesperpixel==1)) { + // Special case for unsigned color images. + uint32 *const raster = (uint32*)_TIFFmalloc(nx*ny*sizeof(uint32)); + if (!raster) { + _TIFFfree(raster); TIFFClose(tif); + throw CImgException(_cimg_instance + "load_tiff(): Failed to allocate memory (%s) for file '%s'.", + cimg_instance, + cimg::strbuffersize(nx*ny*sizeof(uint32)),filename); + } + TIFFReadRGBAImage(tif,nx,ny,raster,0); + switch (spectrum) { + case 1 : + cimg_forXY(*this,x,y) + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + break; + case 3 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 -y) + x]); + } + break; + case 4 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny - 1 - y) + x]); + } + break; + } + _TIFFfree(raster); + } else { // Other cases + uint16 config; + TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config); + if (TIFFIsTiled(tif)) { + uint32 tw = 1, th = 1; + TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw); + TIFFGetField(tif,TIFFTAG_TILELENGTH,&th); + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + } + } else { + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + } + } + } + return *this; + } +#endif + + //! Load image from a MINC2 file. + /** + \param filename Filename, as a C-string. + **/ + // (Original code by Haz-Edine Assemlal). + CImg& load_minc2(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_minc2(): Specified filename is (null).", + cimg_instance); +#ifndef cimg_use_minc2 + return load_other(filename); +#else + minc::minc_1_reader rdr; + rdr.open(filename); + assign(rdr.ndim(1)?rdr.ndim(1):1, + rdr.ndim(2)?rdr.ndim(2):1, + rdr.ndim(3)?rdr.ndim(3):1, + rdr.ndim(4)?rdr.ndim(4):1); + if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_byte(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_int(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_double(); + else + rdr.setup_read_float(); + minc::load_standard_volume(rdr,this->_data); + return *this; +#endif + } + + //! Load image from a MINC2 file \newinstance. + static CImg get_load_minc2(const char *const filename) { + return CImg().load_analyze(filename); + } + + //! Load image from an ANALYZE7.5/NIFTI file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_analyze(const char *const filename, float *const voxel_size=0) { + return _load_analyze(0,filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(const char *const filename, float *const voxel_size=0) { + return CImg().load_analyze(filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \overloading. + CImg& load_analyze(std::FILE *const file, float *const voxel_size=0) { + return _load_analyze(file,0,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(std::FILE *const file, float *const voxel_size=0) { + return CImg().load_analyze(file,voxel_size); + } + + CImg& _load_analyze(std::FILE *const file, const char *const filename, float *const voxel_size=0) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_analyze(): Specified filename is (null).", + cimg_instance); + + std::FILE *nfile_header = 0, *nfile = 0; + if (!file) { + CImg body(1024); + const char *const ext = cimg::split_filename(filename,body); + if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file + nfile_header = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".img"); + nfile = cimg::fopen(body,"rb"); + } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file + nfile = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".hdr"); + nfile_header = cimg::fopen(body,"rb"); + } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file + } else nfile_header = nfile = file; // File is a Niftii file + if (!nfile || !nfile_header) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid Analyze7.5 or NIFTI header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + // Read header. + bool endian = false; + unsigned int header_size; + cimg::fread(&header_size,1,nfile_header); + if (!header_size) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid zero-size header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); } + + unsigned char *const header = new unsigned char[header_size]; + cimg::fread(header + 4,header_size - 4,nfile_header); + if (!file && nfile_header!=nfile) cimg::fclose(nfile_header); + if (endian) { + cimg::invert_endianness((short*)(header + 40),5); + cimg::invert_endianness((short*)(header + 70),1); + cimg::invert_endianness((short*)(header + 72),1); + cimg::invert_endianness((float*)(header + 76),4); + cimg::invert_endianness((float*)(header + 108),1); + cimg::invert_endianness((float*)(header + 112),1); + } + + if (nfile_header==nfile) { + const unsigned int vox_offset = (unsigned int)*(float*)(header + 108); + std::fseek(nfile,vox_offset,SEEK_SET); + } + + unsigned short *dim = (unsigned short*)(header + 40), dimx = 1, dimy = 1, dimz = 1, dimv = 1; + if (!dim[0]) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with zero dimensions.", + cimg_instance, + filename?filename:"(FILE*)"); + + if (dim[0]>4) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with %u dimensions, reading only the 4 first.", + cimg_instance, + filename?filename:"(FILE*)",dim[0]); + + if (dim[0]>=1) dimx = dim[1]; + if (dim[0]>=2) dimy = dim[2]; + if (dim[0]>=3) dimz = dim[3]; + if (dim[0]>=4) dimv = dim[4]; + float scalefactor = *(float*)(header + 112); if (scalefactor==0) scalefactor = 1; + const unsigned short datatype = *(unsigned short*)(header + 70); + if (voxel_size) { + const float *vsize = (float*)(header + 76); + voxel_size[0] = vsize[1]; voxel_size[1] = vsize[2]; voxel_size[2] = vsize[3]; + } + delete[] header; + + // Read pixel data. + assign(dimx,dimy,dimz,dimv); + const size_t pdim = (size_t)dimx*dimy*dimz*dimv; + switch (datatype) { + case 2 : { + unsigned char *const buffer = new unsigned char[pdim]; + cimg::fread(buffer,pdim,nfile); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 4 : { + short *const buffer = new short[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 8 : { + int *const buffer = new int[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 16 : { + float *const buffer = new float[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 64 : { + double *const buffer = new double[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_analyze(): Unable to load datatype %d in file '%s'", + cimg_instance, + datatype,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a .cimg[z] file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(const char *const filename, const char axis='z', const float align=0) { + return CImg().load_cimg(filename,axis,align); + } + + //! Load image from a .cimg[z] file \overloading. + CImg& load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + return CImg().load_cimg(file,axis,align); + } + + //! Load sub-images of a .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Starting frame. + \param n1 Ending frame (~0U for max). + \param x0 X-coordinate of the starting sub-image vertex. + \param y0 Y-coordinate of the starting sub-image vertex. + \param z0 Z-coordinate of the starting sub-image vertex. + \param c0 C-coordinate of the starting sub-image vertex. + \param x1 X-coordinate of the ending sub-image vertex (~0U for max). + \param y1 Y-coordinate of the ending sub-image vertex (~0U for max). + \param z1 Z-coordinate of the ending sub-image vertex (~0U for max). + \param c1 C-coordinate of the ending sub-image vertex (~0U for max). + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load sub-images of a .cimg file \overloading. + CImg& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load image from an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_inr(const char *const filename, float *const voxel_size=0) { + return _load_inr(0,filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(const char *const filename, float *const voxel_size=0) { + return CImg().load_inr(filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \overloading. + CImg& load_inr(std::FILE *const file, float *const voxel_size=0) { + return _load_inr(file,0,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(std::FILE *const file, float *voxel_size=0) { + return CImg().load_inr(file,voxel_size); + } + + static void _load_inr_header(std::FILE *file, int out[8], float *const voxel_size) { + CImg item(1024), tmp1(64), tmp2(64); + *item = *tmp1 = *tmp2 = 0; + out[0] = std::fscanf(file,"%63s",item._data); + out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1; + if (cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0) + throw CImgIOException("CImg<%s>::load_inr(): INRIMAGE-4 header not found.", + pixel_type()); + + while (std::fscanf(file," %63[^\n]%*c",item._data)!=EOF && std::strncmp(item,"##}",3)) { + cimg_sscanf(item," XDIM%*[^0-9]%d",out); + cimg_sscanf(item," YDIM%*[^0-9]%d",out + 1); + cimg_sscanf(item," ZDIM%*[^0-9]%d",out + 2); + cimg_sscanf(item," VDIM%*[^0-9]%d",out + 3); + cimg_sscanf(item," PIXSIZE%*[^0-9]%d",out + 6); + if (voxel_size) { + cimg_sscanf(item," VX%*[^0-9.+-]%f",voxel_size); + cimg_sscanf(item," VY%*[^0-9.+-]%f",voxel_size + 1); + cimg_sscanf(item," VZ%*[^0-9.+-]%f",voxel_size + 2); + } + if (cimg_sscanf(item," CPU%*[ =]%s",tmp1._data)) out[7] = cimg::strncasecmp(tmp1,"sun",3)?0:1; + switch (cimg_sscanf(item," TYPE%*[ =]%s %s",tmp1._data,tmp2._data)) { + case 0 : break; + case 2 : + out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; + std::strncpy(tmp1,tmp2,tmp1._width - 1); // 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 + default : + throw CImgIOException("CImg<%s>::load_inr(): Invalid pixel type '%s' defined in header.", + pixel_type(), + tmp2._data); + } + } + if (out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0) + throw CImgIOException("CImg<%s>::load_inr(): Invalid dimensions (%d,%d,%d,%d) defined in header.", + pixel_type(), + out[0],out[1],out[2],out[3]); + if (out[4]<0 || out[5]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete pixel type defined in header.", + pixel_type()); + if (out[6]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete PIXSIZE field defined in header.", + pixel_type()); + if (out[7]<0) + throw CImgIOException("CImg<%s>::load_inr(): Big/Little Endian coding type undefined in header.", + pixel_type()); + } + + CImg& _load_inr(std::FILE *const file, const char *const filename, float *const voxel_size) { +#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \ + if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \ + Ts *xval, *const val = new Ts[(size_t)fopt[0]*fopt[3]]; \ + cimg_forYZ(*this,y,z) { \ + cimg::fread(val,fopt[0]*fopt[3],nfile); \ + if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \ + xval = val; cimg_forX(*this,x) cimg_forC(*this,c) (*this)(x,y,z,c) = (T)*(xval++); \ + } \ + delete[] val; \ + loaded = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_inr(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + int fopt[8], endian = cimg::endianness()?1:0; + bool loaded = false; + if (voxel_size) voxel_size[0] = voxel_size[1] = voxel_size[2] = 1; + _load_inr_header(nfile,fopt,voxel_size); + assign(fopt[0],fopt[1],fopt[2],fopt[3]); + _cimg_load_inr_case(0,0,8,unsigned char); + _cimg_load_inr_case(0,1,8,char); + _cimg_load_inr_case(0,0,16,unsigned short); + _cimg_load_inr_case(0,1,16,short); + _cimg_load_inr_case(0,0,32,unsigned int); + _cimg_load_inr_case(0,1,32,int); + _cimg_load_inr_case(1,0,32,float); + _cimg_load_inr_case(1,1,32,float); + _cimg_load_inr_case(1,0,64,double); + _cimg_load_inr_case(1,1,64,double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_inr(): Unknown pixel type defined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a EXR file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_exr(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_exr(): Specified filename is (null).", + cimg_instance); +#if defined(cimg_use_openexr) + Imf::RgbaInputFile file(filename); + Imath::Box2i dw = file.dataWindow(); + const int + inwidth = dw.max.x - dw.min.x + 1, + inheight = dw.max.y - dw.min.y + 1; + Imf::Array2D pixels; + pixels.resizeErase(inheight,inwidth); + file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y*inwidth, 1, inwidth); + file.readPixels(dw.min.y, dw.max.y); + assign(inwidth,inheight,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + cimg_forXY(*this,x,y) { + *(ptr_r++) = (T)pixels[y][x].r; + *(ptr_g++) = (T)pixels[y][x].g; + *(ptr_b++) = (T)pixels[y][x].b; + *(ptr_a++) = (T)pixels[y][x].a; + } +#elif defined(cimg_use_tinyexr) + float *res; + const char *err = 0; + int width = 0, height = 0; + const int ret = LoadEXR(&res,&width,&height,filename,&err); + if (ret) throw CImgIOException(_cimg_instance + "load_exr(): Unable to load EXR file '%s'.", + cimg_instance,filename); + CImg(out,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this); + std::free(res); +#else + return load_other(filename); +#endif + return *this; + } + + //! Load image from a EXR file \newinstance. + static CImg get_load_exr(const char *const filename) { + return CImg().load_exr(filename); + } + + //! Load image from a PANDORE-5 file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pandore(const char *const filename) { + return _load_pandore(0,filename); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(const char *const filename) { + return CImg().load_pandore(filename); + } + + //! Load image from a PANDORE-5 file \overloading. + CImg& load_pandore(std::FILE *const file) { + return _load_pandore(file,0); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(std::FILE *const file) { + return CImg().load_pandore(file); + } + + CImg& _load_pandore(std::FILE *const file, const char *const filename) { +#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \ + cimg::fread(dims,nbdim,nfile); \ + if (endian) cimg::invert_endianness(dims,nbdim); \ + assign(nwidth,nheight,ndepth,ndim); \ + const size_t siz = size(); \ + stype *buffer = new stype[siz]; \ + cimg::fread(buffer,siz,nfile); \ + if (endian) cimg::invert_endianness(buffer,siz); \ + T *ptrd = _data; \ + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \ + buffer-=siz; \ + delete[] buffer + +#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \ + if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \ + else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \ + else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \ + else throw CImgIOException(_cimg_instance \ + "load_pandore(): Unknown pixel datatype in file '%s'.", \ + cimg_instance, \ + filename?filename:"(FILE*)"); } + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pandore(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(32); + cimg::fread(header._data,12,nfile); + if (cimg::strncasecmp("PANDORE",header,7)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): PANDORE header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + unsigned int imageid, dims[8] = { 0 }; + int ptbuf[4] = { 0 }; + cimg::fread(&imageid,1,nfile); + const bool endian = imageid>255; + if (endian) cimg::invert_endianness(imageid); + cimg::fread(header._data,20,nfile); + + switch (imageid) { + case 2 : _cimg_load_pandore_case(2,dims[1],1,1,1,unsigned char,unsigned char,unsigned char,1); break; + case 3 : _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break; + case 4 : _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break; + case 5 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,unsigned char,unsigned char,unsigned char,1); break; + case 6 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break; + case 7 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break; + case 8 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,unsigned char,unsigned char,unsigned char,1); break; + case 9 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break; + case 10 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break; + case 11 : { // Region 1D + cimg::fread(dims,3,nfile); + if (endian) cimg::invert_endianness(dims,3); + assign(dims[1],1,1,1); + const unsigned siz = size(); + if (dims[2]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[2]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 12 : { // Region 2D + cimg::fread(dims,4,nfile); + if (endian) cimg::invert_endianness(dims,4); + assign(dims[2],dims[1],1,1); + const size_t siz = size(); + if (dims[3]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[3]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 13 : { // Region 3D + cimg::fread(dims,5,nfile); + if (endian) cimg::invert_endianness(dims,5); + assign(dims[3],dims[2],dims[1],1); + const size_t siz = size(); + if (dims[4]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[4]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 16 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,unsigned char,unsigned char,unsigned char,1); break; + case 17 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break; + case 18 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break; + case 19 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,unsigned char,unsigned char,unsigned char,1); break; + case 20 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break; + case 21 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break; + case 22 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 23 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4); break; + case 24 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 25 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break; + case 26 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 27 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break; + case 28 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 29 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break; + case 30 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned char,unsigned char,unsigned char,1); + break; + case 31 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break; + case 32 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned long,unsigned int,unsigned short,4); + break; + case 33 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break; + case 34 : { // Points 1D + cimg::fread(ptbuf,1,nfile); + if (endian) cimg::invert_endianness(ptbuf,1); + assign(1); (*this)(0) = (T)ptbuf[0]; + } break; + case 35 : { // Points 2D + cimg::fread(ptbuf,2,nfile); + if (endian) cimg::invert_endianness(ptbuf,2); + assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0]; + } break; + case 36 : { // Points 3D + cimg::fread(ptbuf,3,nfile); + if (endian) cimg::invert_endianness(ptbuf,3); + assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0]; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): Unable to load data with ID_type %u in file '%s'.", + cimg_instance, + imageid,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PAR-REC (Philips) file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_parrec(const char *const filename, const char axis='c', const float align=0) { + CImgList list; + list.load_parrec(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a PAR-REC (Philips) file \newinstance. + static CImg get_load_parrec(const char *const filename, const char axis='c', const float align=0) { + return CImg().load_parrec(filename,axis,align); + } + + //! Load image from a raw binary file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the image buffer. + \param size_y Height of the image buffer. + \param size_z Depth of the image buffer. + \param size_c Spectrum of the image buffer. + \param is_multiplexed Tells if the image values are multiplexed along the C-axis. + \param invert_endianness Tells if the endianness of the image buffer must be inverted. + \param offset Starting offset of the read in the specified file. + **/ + CImg& load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(0,filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \overloading. + CImg& load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(file,0,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(file,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + 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 ulongT offset) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename is (null).", + cimg_instance); + if (cimg::is_directory(filename)) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename '%s' is a directory.", + cimg_instance,filename); + + ulongT siz = (ulongT)size_x*size_y*size_z*size_c; + unsigned int + _size_x = size_x, + _size_y = size_y, + _size_z = size_z, + _size_c = size_c; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + if (!siz) { // Retrieve file size + const longT fpos = cimg::ftell(nfile); + if (fpos<0) throw CImgArgumentException(_cimg_instance + "load_raw(): Cannot determine size of input file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + cimg::fseek(nfile,0,SEEK_END); + siz = cimg::ftell(nfile)/sizeof(T); + _size_y = (unsigned int)siz; + _size_x = _size_z = _size_c = 1; + cimg::fseek(nfile,fpos,SEEK_SET); + } + cimg::fseek(nfile,offset,SEEK_SET); + assign(_size_x,_size_y,_size_z,_size_c,0); + if (siz && (!is_multiplexed || size_c==1)) { + cimg::fread(_data,siz,nfile); + if (invert_endianness) cimg::invert_endianness(_data,siz); + } else if (siz) { + CImg buf(1,1,1,_size_c); + cimg_forXYZ(*this,x,y,z) { + cimg::fread(buf._data,_size_c,nfile); + if (invert_endianness) cimg::invert_endianness(buf._data,_size_c); + set_vector_at(buf,x,y,z); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image sequence from a YUV file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the frames. + \param size_y Height of the frames. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param yuv2rgb Tells if the YUV to RGB transform must be applied. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + **/ + CImg& load_yuv(const char *const filename, + 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') { + return get_load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(const char *const filename, + 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') { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb).get_append(axis); + } + + //! Load image sequence from a YUV file \overloading. + CImg& load_yuv(std::FILE *const file, + 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') { + return get_load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(std::FILE *const file, + 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') { + 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. + /** + \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 + CImg& load_off(CImgList& primitives, CImgList& colors, const char *const filename) { + return _load_off(primitives,colors,0,filename); + } + + //! 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. + 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. + template + static CImg get_load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { + return CImg().load_off(primitives,colors,file); + } + + template + CImg& _load_off(CImgList& primitives, CImgList& colors, + std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_off(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0; + CImg line(256); *line = 0; + int err; + + // Skip comments, and read magic string OFF + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): OFF header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Invalid number of vertices or primitives specified in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read points data + assign(nb_points,3); + float X = 0, Y = 0, Z = 0; + cimg_forX(*this,l) { + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Failed to read vertex %u/%u in file '%s'.", + cimg_instance, + l + 1,nb_points,filename?filename:"(FILE*)"); + } + (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z; + } + + // Read primitive data + primitives.assign(); + colors.assign(); + bool stop_flag = false; + while (!stop_flag) { + float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f; + unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0; + *line = 0; + if ((err = std::fscanf(nfile,"%u",&prim))!=1) stop_flag = true; + else { + ++nb_read; + switch (prim) { + case 1 : { + if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 2 : { + if ((err = std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 3 : { + if ((err = std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line._data))<3) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 4 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line._data))<4) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 5 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line._data))<5) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 6 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line._data))<6) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 7 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i4,i3,i1).move_to(primitives); + CImg::vector(i0,i6,i5,i4).move_to(primitives); + CImg::vector(i3,i2,i1).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + case 8 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + CImg::vector(i0,i7,i6,i5).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + default : + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u (%u vertices) from file '%s'.", + cimg_instance, + nb_read,nb_primitives,prim,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } + } + } + if (!file) cimg::fclose(nfile); + if (primitives._width!=nb_primitives) + cimg::warn(_cimg_instance + "load_off(): Only %u/%u primitives read from file '%s'.", + cimg_instance, + primitives._width,nb_primitives,filename?filename:"(FILE*)"); + return *this; + } + + //! Load image sequence from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param axis Alignment axis. + \param align Apending alignment. + **/ + CImg& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return get_load_video(filename,first_frame,last_frame,step_frame,axis,align).move_to(*this); + } + + //! Load image sequence from a video file, using OpenCV library \newinstance. + static CImg get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame).get_append(axis,align); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg'. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return get_load_ffmpeg_external(filename,axis,align).move_to(*this); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg' \newinstance. + static CImg get_load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return CImgList().load_ffmpeg_external(filename).get_append(axis,align); + } + + //! Load gif file, using Imagemagick or GraphicsMagicks's external tools. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return get_load_gif_external(filename,axis,align).move_to(*this); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tool 'convert' \newinstance. + static CImg get_load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return CImgList().load_gif_external(filename).get_append(axis,align); + } + + //! Load image using GraphicsMagick's external tool 'gm'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_graphicsmagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + 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()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_graphicsmagick_external(): Failed to load file '%s' " + "with external command 'gm'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=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::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::graphicsmagick_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using GraphicsMagick's external tool 'gm' \newinstance. + static CImg get_load_graphicsmagick_external(const char *const filename) { + return CImg().load_graphicsmagick_external(filename); + } + + //! Load gzipped image file, using external tool 'gunzip'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimg_instance + "load_gzip_external(): Specified filename is (null).", + cimg_instance); + 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), + *const ext2 = cimg::split_filename(body,0); + + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load gzipped image file, using external tool 'gunzip' \newinstance. + static CImg get_load_gzip_external(const char *const filename) { + return CImg().load_gzip_external(filename); + } + + //! Load image using ImageMagick's external tool 'convert'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_imagemagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_imagemagick_external(): Specified filename is (null).", + cimg_instance); + 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::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_imagemagick_external(): Failed to load file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s%s \"%s\" \"%s\"", + cimg::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::imagemagick_path()); + 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 " + "external command 'magick/convert'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using ImageMagick's external tool 'convert' \newinstance. + static CImg get_load_imagemagick_external(const char *const filename) { + return CImg().load_imagemagick_external(filename); + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_medcon_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_medcon_external(): Specified filename is (null).", + cimg_instance); + 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=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::medcon_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + cimg::split_filename(filename_tmp,body); + + cimg_snprintf(command,command._width,"%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + throw CImgIOException(_cimg_instance + "load_medcon_external(): Failed to load file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + load_analyze(command); + std::remove(command); + cimg::split_filename(command,body); + cimg_snprintf(command,command._width,"%s.img",body._data); + std::remove(command); + return *this; + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon' \newinstance. + static CImg get_load_medcon_external(const char *const filename) { + return CImg().load_medcon_external(filename); + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_dcraw_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_dcraw_external(): Specified filename is (null).", + cimg_instance); + 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 + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\"", + cimg::dcraw_path(),s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_dcraw_external(): Failed to load file '%s' with external command 'dcraw'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.ppm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\" > \"%s\"", + cimg::dcraw_path(),s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::dcraw_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw' \newinstance. + static CImg get_load_dcraw_external(const char *const filename) { + return CImg().load_dcraw_external(filename); + } + + //! Load image from a camera stream, using OpenCV. + /** + \param camera_index Index of the camera to capture images from. + \param skip_frames Number of frames to skip before the capture. + \param release_camera Tells if the camera ressource must be released at the end of the method. + \param capture_width Width of the desired image. + \param capture_height Height of the desired image. + **/ + 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) { +#ifdef cimg_use_opencv + if (camera_index>99) + throw CImgArgumentException(_cimg_instance + "load_camera(): Invalid request for camera #%u " + "(no more than 100 cameras can be managed simultaneously).", + cimg_instance, + camera_index); + static CvCapture *capture[100] = { 0 }; + static unsigned int capture_w[100], capture_h[100]; + if (release_camera) { + cimg::mutex(9); + if (capture[camera_index]) cvReleaseCapture(&(capture[camera_index])); + capture[camera_index] = 0; + capture_w[camera_index] = capture_h[camera_index] = 0; + cimg::mutex(9,0); + return *this; + } + if (!capture[camera_index]) { + cimg::mutex(9); + capture[camera_index] = cvCreateCameraCapture(camera_index); + capture_w[camera_index] = 0; + capture_h[camera_index] = 0; + cimg::mutex(9,0); + if (!capture[camera_index]) { + throw CImgIOException(_cimg_instance + "load_camera(): Failed to initialize camera #%u.", + cimg_instance, + camera_index); + } + } + cimg::mutex(9); + if (capture_width!=capture_w[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_WIDTH,capture_width); + capture_w[camera_index] = capture_width; + } + if (capture_height!=capture_h[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_HEIGHT,capture_height); + capture_h[camera_index] = capture_height; + } + const IplImage *img = 0; + for (unsigned int i = 0; 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++); + } + } + cimg::mutex(9,0); + return *this; +#else + cimg::unused(camera_index,skip_frames,release_camera,capture_width,capture_height); + throw CImgIOException(_cimg_instance + "load_camera(): This function requires the OpenCV library to run " + "(macro 'cimg_use_opencv' must be defined).", + cimg_instance); +#endif + } + + //! Load image from a camera stream, using OpenCV \newinstance. + static CImg 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); + } + + //! Load image using various non-native ways. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_other(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_other(): Specified filename is (null).", + cimg_instance); + + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_magick(filename); } + catch (CImgException&) { + try { load_imagemagick_external(filename); } + catch (CImgException&) { + try { load_graphicsmagick_external(filename); } + catch (CImgException&) { + try { load_cimg(filename); } + catch (CImgException&) { + try { + cimg::fclose(cimg::fopen(filename,"rb")); + } catch (CImgException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to open file '%s'.", + cimg_instance, + filename); + } + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image using various non-native ways \newinstance. + static CImg get_load_other(const char *const filename) { + return CImg().load_other(filename); + } + + //@} + //--------------------------- + // + //! \name Data Output + //@{ + //--------------------------- + + //! Display information about the image data. + /** + \param title Name for the considered image. + \param display_stats Tells to compute and display image statistics. + **/ + const CImg& print(const char *const title=0, const bool display_stats=true) const { + + int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0; + CImg st; + if (!is_empty() && display_stats) { + st = get_stats(); + xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7]; + xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11]; + } + + const ulongT siz = size(), msiz = siz*sizeof(T), siz1 = siz - 1, + mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U, width1 = _width - 1; + + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImg<%s>",pixel_type()); + + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = (%u,%u,%u,%u) [%lu %s], %sdata%s = (%s*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum, + (unsigned long)(mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20))), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) + std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end() - 1),_is_shared?"shared":"non-shared"); + else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared"); + + if (!is_empty()) cimg_foroff(*this,off) { + std::fprintf(cimg::output(),"%g",(double)_data[off]); + if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" "); + if (off==7 && siz>16) { off = siz1 - 8; std::fprintf(cimg::output(),"... "); } + } + if (!is_empty() && display_stats) + std::fprintf(cimg::output(), + " ], %smin%s = %g, %smax%s = %g, %smean%s = %g, %sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), " + "%scoords_max%s = (%u,%u,%u,%u).\n", + cimg::t_bold,cimg::t_normal,st[0], + cimg::t_bold,cimg::t_normal,st[1], + cimg::t_bold,cimg::t_normal,st[2], + cimg::t_bold,cimg::t_normal,std::sqrt(st[3]), + cimg::t_bold,cimg::t_normal,xm,ym,zm,vm, + cimg::t_bold,cimg::t_normal,xM,yM,zM,vM); + else std::fprintf(cimg::output(),"%s].\n",is_empty()?"":" "); + std::fflush(cimg::output()); + return *this; + } + + //! Display image into a CImgDisplay window. + /** + \param disp Display window. + **/ + const CImg& display(CImgDisplay& disp) const { + disp.display(*this); + return *this; + } + + //! Display image into a CImgDisplay window, in an interactive way. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(CImgDisplay &disp, const bool display_info, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + return _display(disp,0,display_info,XYZ,exit_on_anykey,false); + } + + //! Display image into an interactive window. + /** + \param title Window title + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(const char *const title=0, const bool display_info=true, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display(disp,title,display_info,XYZ,exit_on_anykey,false); + } + + const CImg& _display(CImgDisplay &disp, const char *const title, const bool display_info, + unsigned int *const XYZ, const bool exit_on_anykey, + const bool exit_on_simpleclick) const { + unsigned int oldw = 0, oldh = 0, _XYZ[3] = { 0 }, key = 0; + int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1, + old_mouse_x = -1, old_mouse_y = -1; + + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + else disp.set_title("%s",title); + } else if (title) disp.set_title("%s",title); + disp.show().flush(); + + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(dtitle); + + CImg zoom; + for (bool reset_view = true, resize_disp = false, is_first_select = true; !key && !disp.is_closed(); ) { + if (reset_view) { + if (XYZ) { _XYZ[0] = XYZ[0]; _XYZ[1] = XYZ[1]; _XYZ[2] = XYZ[2]; } + else { + _XYZ[0] = (unsigned int)(x0 + x1)/2; + _XYZ[1] = (unsigned int)(y0 + y1)/2; + _XYZ[2] = (unsigned int)(z0 + z1)/2; + } + x0 = 0; y0 = 0; z0 = 0; x1 = width() - 1; y1 = height() - 1; z1 = depth() - 1; + 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) { + if (is_empty()) zoom.assign(1,1,1,1,(T)0); else zoom.assign(); + } else zoom = get_crop(x0,y0,z0,x1,y1,z1); + + const CImg& visu = zoom?zoom:*this; + const unsigned int + dx = 1U + x1 - x0, dy = 1U + y1 - y0, dz = 1U + z1 - z0, + tw = dx + (dz>1?dz:0U), th = dy + (dz>1?dz:0U); + if (!is_empty() && !disp.is_fullscreen() && resize_disp) { + const 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 + 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; + } + oldw = tw; oldh = th; + + bool + go_up = false, go_down = false, go_left = false, go_right = false, + go_inc = false, go_dec = false, go_in = false, go_out = false, + go_in_center = false; + + disp.set_title("%s",dtitle._data); + if (_width>1 && visu._width==1) disp.set_title("%s | x=%u",disp._title,x0); + if (_height>1 && visu._height==1) disp.set_title("%s | y=%u",disp._title,y0); + if (_depth>1 && visu._depth==1) disp.set_title("%s | z=%u",disp._title,z0); + + disp._mouse_x = old_mouse_x; disp._mouse_y = old_mouse_y; + CImg selection = visu._select(disp,0,2,_XYZ,x0,y0,z0,true,is_first_select,_depth>1,true); + old_mouse_x = disp._mouse_x; old_mouse_y = disp._mouse_y; + is_first_select = false; + + if (disp.wheel()) { + if ((disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) && + (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT())) { + go_left = !(go_right = disp.wheel()>0); + } else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) { + go_down = !(go_up = disp.wheel()>0); + } else if (depth()==1 || disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + go_out = !(go_in = disp.wheel()>0); go_in_center = false; + } + disp.set_wheel(); + } + + const int + sx0 = selection(0), sy0 = selection(1), sz0 = selection(2), + sx1 = selection(3), sy1 = selection(4), sz1 = selection(5); + if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) { + x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; + x0+=sx0; y0+=sy0; z0+=sz0; + if ((sx0==sx1 && sy0==sy1) || (_depth>1 && sx0==sx1 && sz0==sz1) || (_depth>1 && sy0==sy1 && sz0==sz1)) { + if (exit_on_simpleclick && (!zoom || is_empty())) break; else reset_view = true; + } + resize_disp = true; + } else switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : key = 0; break; + case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) { + // Special mode: play stack of frames + const unsigned int + w1 = visu._width*disp.width()/(visu._width + (visu._depth>1?visu._depth:0)), + h1 = visu._height*disp.height()/(visu._height + (visu._depth>1?visu._depth:0)); + float frame_timing = 5; + bool is_stopped = false; + disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0; + for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) { + if (disp.is_resized()) disp.resize(false); + if (!timer) { + visu.get_slice((int)_XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),_XYZ[2])); + (++_XYZ[2])%=visu._depth; + } + if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U; + if (disp.wheel()) { frame_timing-=disp.wheel()/3.f; disp.set_wheel(); } + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break; + case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break; + case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true; + (_XYZ[2]+=visu._depth - 2)%=visu._depth; timer = 0; key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false). + toggle_fullscreen().set_key(key,false); key = 0; + } break; + } + frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing); + disp.wait(20); + } + const unsigned int + w2 = (visu._width + (visu._depth>1?visu._depth:0))*disp.width()/visu._width, + h2 = (visu._height + (visu._depth>1?visu._depth:0))*disp.height()/visu._height; + disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(dtitle.data()).set_key().set_button().set_wheel(); + key = 0; + } break; + case cimg::keyHOME : reset_view = resize_disp = true; key = 0; break; + case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break; + case cimg::keyPADSUB : go_out = true; key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break; + case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break; + case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break; + case cimg::keyPAD7 : go_up = go_left = true; key = 0; break; + case cimg::keyPAD9 : go_up = go_right = true; key = 0; break; + case cimg::keyPAD1 : go_down = go_left = true; key = 0; break; + case cimg::keyPAD3 : go_down = go_right = true; key = 0; break; + case cimg::keyPAGEUP : go_inc = true; key = 0; break; + case cimg::keyPAGEDOWN : go_dec = true; key = 0; break; + } + if (go_in) { + const int + mx = go_in_center?disp.width()/2:disp.mouse_x(), + my = go_in_center?disp.height()/2:disp.mouse_y(), + mX = mx*(width() + (depth()>1?depth():0))/disp.width(), + mY = my*(height() + (depth()>1?depth():0))/disp.height(); + int X = (int)_XYZ[0], Y = (int)_XYZ[1], Z = (int)_XYZ[2]; + if (mX=height()) { + X = x0 + mX*(1 + x1 - x0)/width(); Z = z0 + (mY - height())*(1 + z1 - z0)/depth(); + } + if (mX>=width() && mY4) { x0 = X - 3*(X - x0)/4; x1 = X + 3*(x1 - X)/4; } + if (y1 - y0>4) { y0 = Y - 3*(Y - y0)/4; y1 = Y + 3*(y1 - Y)/4; } + if (z1 - z0>4) { z0 = Z - 3*(Z - z0)/4; z1 = Z + 3*(z1 - Z)/4; } + } + if (go_out) { + const int + delta_x = (x1 - x0)/8, delta_y = (y1 - y0)/8, delta_z = (z1 - z0)/8, + ndelta_x = delta_x?delta_x:(_width>1), + ndelta_y = delta_y?delta_y:(_height>1), + ndelta_z = delta_z?delta_z:(_depth>1); + x0-=ndelta_x; y0-=ndelta_y; z0-=ndelta_z; + x1+=ndelta_x; y1+=ndelta_y; z1+=ndelta_z; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=width()) x1 = width() - 1; } + if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; } + if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; } + if (x1>=width()) { x0-=(x1 - width() + 1); x1 = width() - 1; if (x0<0) x0 = 0; } + if (y1>=height()) { y0-=(y1 - height() + 1); y1 = height() - 1; if (y0<0) y0 = 0; } + if (z1>=depth()) { z0-=(z1 - depth() + 1); z1 = depth() - 1; if (z0<0) z0 = 0; } + const float + ratio = (float)(x1-x0)/(y1-y0), + ratiow = (float)disp._width/disp._height, + sub = std::min(cimg::abs(ratio - ratiow),cimg::abs(1/ratio-1/ratiow)); + if (sub>0.01) resize_disp = true; + } + if (go_left) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + } + if (go_right) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x1+ndelta1); + if (y0 - ndelta>=0) { y0-=ndelta; y1-=ndelta; } + else { y1-=y0; y0 = 0; } + } + if (go_down) { + const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1); + if (y1+ndelta1); + if (z0 - ndelta>=0) { z0-=ndelta; z1-=ndelta; } + else { z1-=z0; z0 = 0; } + } + if (go_dec) { + const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1); + if (z1+ndelta + const CImg& display_object3d(CImgDisplay& disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return _display_object3d(disp,0,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + template + const CImg& _display_object3d(CImgDisplay& disp, const char *const title, + 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 float light_x, const float light_y, const float light_z, + const float specular_lightness, const float specular_shininess, + const bool display_axes, float *const pose_matrix, + const bool exit_on_anykey) const { + typedef typename cimg::superset::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); + } 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).", + 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, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3); + if (!title) disp.set_title("CImg<%s> (%u vertices, %u primitives)", + pixel_type(),vertices._width,primitives._width); + } else if (title) disp.set_title("%s",title); + + // Init 3D objects and compute object statistics + CImg + pose, + rotated_vertices(vertices._width,3), + bbox_vertices, rotated_bbox_vertices, + axes_vertices, rotated_axes_vertices, + bbox_opacities, axes_opacities; + CImgList bbox_primitives, axes_primitives; + CImgList reverse_primitives; + CImgList bbox_colors, bbox_colors2, axes_colors; + unsigned int ns_width = 0, ns_height = 0; + int _is_double_sided = (int)is_double_sided; + bool ndisplay_axes = display_axes; + const CImg + background_color(1,1,1,_spectrum,0), + foreground_color(1,1,1,_spectrum,255); + float + Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1, + xm = 0, xM = vertices?vertices.get_shared_row(0).max_min(xm):0, + ym = 0, yM = vertices?vertices.get_shared_row(1).max_min(ym):0, + zm = 0, zM = vertices?vertices.get_shared_row(2).max_min(zm):0; + const float delta = cimg::max(xM - xm,yM - ym,zM - zm); + + rotated_bbox_vertices = bbox_vertices.assign(8,3,1,1, + xm,xM,xM,xm,xm,xM,xM,xm, + ym,ym,yM,yM,ym,ym,yM,yM, + zm,zm,zm,zm,zM,zM,zM,zM); + bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6); + bbox_colors.assign(6,_spectrum,1,1,1,background_color[0]); + bbox_colors2.assign(6,_spectrum,1,1,1,foreground_color[0]); + bbox_opacities.assign(bbox_colors._width,1,1,1,0.3f); + + rotated_axes_vertices = axes_vertices.assign(7,3,1,1, + 0,20,0,0,22,-6,-6, + 0,0,20,0,-6,22,-6, + 0,0,0,20,0,0,22); + axes_opacities.assign(3,1,1,1,1); + axes_colors.assign(3,_spectrum,1,1,1,foreground_color[0]); + axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3); + + // Begin user interaction loop + CImg 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; + int + x0 = 0, y0 = 0, x1 = 0, y1 = 0, + nrender_static = render_static, + nrender_motion = render_motion; + disp.show().flush(); + + while (!disp.is_closed() && !key) { + + // Init object pose + if (init_pose) { + const float + ratio = delta>0?(2.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); + else CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose); + if (pose_matrix) { + CImg pose0(pose_matrix,4,3,1,1,false); + pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0); + pose0(3,3) = pose(3,3) = 1; + (pose0*pose).get_crop(0,0,3,2).move_to(pose); + Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15]; + } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; } + init_pose = false; + redraw = true; + } + + // Rotate and draw 3D object + if (redraw) { + const float + r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0), + r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1), + r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2); + if ((clicked && nrender_motion>=0) || (!clicked && nrender_static>=0)) + cimg_forX(vertices,l) { + const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2); + rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + else cimg_forX(bbox_vertices,l) { + const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2); + rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + + // Draw objects + const bool render_with_zbuffer = !clicked && nrender_static>0; + visu = visu0; + if ((clicked && nrender_motion<0) || (!clicked && nrender_static<0)) + visu.draw_object3d(Xoff + visu._width/2.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.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.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.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 + n = 1e-8f + cimg::hypot(r00,r01,r02), + _r00 = r00/n, _r10 = r10/n, _r20 = r20/n, + _r01 = r01/n, _r11 = r11/n, _r21 = r21/n, + _r02 = r01/n, _r12 = r12/n, _r22 = r22/n, + Xaxes = 25, Yaxes = visu._height - 38.f; + cimg_forX(axes_vertices,l) { + const float + x = axes_vertices(l,0), + y = axes_vertices(l,1), + z = axes_vertices(l,2); + rotated_axes_vertices(l,0) = _r00*x + _r10*y + _r20*z; + rotated_axes_vertices(l,1) = _r01*x + _r11*y + _r21*z; + rotated_axes_vertices(l,2) = _r02*x + _r12*y + _r22*z; + } + axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.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)), + (int)(Yaxes + rotated_axes_vertices(4,1)), + "X",axes_colors[0]._data,0,axes_opacities(0,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(5,0)), + (int)(Yaxes + rotated_axes_vertices(5,1)), + "Y",axes_colors[1]._data,0,axes_opacities(1,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(6,0)), + (int)(Yaxes + rotated_axes_vertices(6,1)), + "Z",axes_colors[2]._data,0,axes_opacities(2,0),13); + } + visu.display(disp); + if (!clicked || nrender_motion==nrender_static) redraw = false; + } + + // Handle user interaction + disp.wait(); + if ((disp.button() || disp.wheel()) && disp.mouse_x()>=0 && disp.mouse_y()>=0) { + redraw = true; + if (!clicked) { x0 = x1 = disp.mouse_x(); y0 = y1 = disp.mouse_y(); if (!disp.wheel()) clicked = true; } + else { x1 = disp.mouse_x(); y1 = disp.mouse_y(); } + if (disp.button()&1) { + const float + R = 0.45f*std::min(disp.width(),disp.height()), + R2 = R*R, + u0 = (float)(x0 - disp.width()/2), + v0 = (float)(y0 - disp.height()/2), + u1 = (float)(x1 - disp.width()/2), + v1 = (float)(y1 - disp.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + (CImg::rotation_matrix(u,v,w,-alpha)*pose).move_to(pose); + x0 = x1; y0 = y1; + } + if (disp.button()&2) { + if (focale>0) Zoff-=(y0 - y1)*focale/400; + else { const float s = std::exp((y0 - y1)/400.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.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)) { + 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); + } + } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; } + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD: if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + if (!ns_width || !ns_height || + ns_width>(unsigned int)disp.screen_width() || ns_height>(unsigned int)disp.screen_height()) { + ns_width = disp.screen_width()*3U/4; + ns_height = disp.screen_height()*3U/4; + } + if (disp.is_fullscreen()) disp.resize(ns_width,ns_height,false); + else { + ns_width = disp._width; ns_height = disp._height; + disp.resize(disp.screen_width(),disp.screen_height(),false); + } + disp.toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyT : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Switch single/double-sided primitives. + if (--_is_double_sided==-2) _is_double_sided = 1; + if (_is_double_sided>=0) reverse_primitives.assign(); + else primitives.get_reverse_object3d().move_to(reverse_primitives); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyZ : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Enable/disable Z-buffer + if (zbuffer) zbuffer.assign(); + else zbuffer.assign(visu0.width(),visu0.height(),1,1,0); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Show/hide 3D axes + ndisplay_axes = !ndisplay_axes; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF1 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to points + nrender_motion = (nrender_static==0 && nrender_motion!=0)?0:-1; nrender_static = 0; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF2 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to lines + nrender_motion = (nrender_static==1 && nrender_motion!=1)?1:-1; nrender_static = 1; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF3 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat + nrender_motion = (nrender_static==2 && nrender_motion!=2)?2:-1; nrender_static = 2; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF4 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat-shaded + nrender_motion = (nrender_static==3 && nrender_motion!=3)?3:-1; nrender_static = 3; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF5 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Set rendering mode to gouraud-shaded. + nrender_motion = (nrender_static==4 && nrender_motion!=4)?4:-1; nrender_static = 4; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF6 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to phong-shaded + nrender_motion = (nrender_static==5 && nrender_motion!=5)?5:-1; nrender_static = 5; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save snapshot + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving snapshot... ",false).display(disp); + visu.save(filename); + (+visu).__draw_text(" Snapshot '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.save_off(reverse_primitives?reverse_primitives:primitives,colors,filename); + (+visu).__draw_text(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities). + save(filename); + (+visu).__draw_text(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving EPS snapshot... ",false).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving SVG snapshot... ",false,13).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; +#endif + } + if (disp.is_resized()) { + disp.resize(false); visu0 = get_resize(disp,1); + if (zbuffer) zbuffer.assign(disp.width(),disp.height()); + redraw = true; + } + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + if (pose_matrix) { + std::memcpy(pose_matrix,pose._data,12*sizeof(float)); + pose_matrix[12] = Xoff; pose_matrix[13] = Yoff; pose_matrix[14] = Zoff; pose_matrix[15] = sprite_scale; + } + disp.set_button().set_key(key); + return *this; + } + + //! Display 1D graph in an interactive window. + /** + \param disp Display window. + \param plot_type Plot type. Can be { 0=points | 1=segments | 2=splines | 3=bars }. + \param vertex_type Vertex type. + \param labelx Title for the horizontal axis, as a C-string. + \param xmin Minimum value along the X-axis. + \param xmax Maximum value along the X-axis. + \param labely Title for the vertical axis, as a C-string. + \param ymin Minimum value along the X-axis. + \param ymax Maximum value along the X-axis. + \param exit_on_anykey Exit function when any key is pressed. + **/ + const CImg& display_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + return _display_graph(disp,0,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + //! Display 1D graph in an interactive window \overloading. + const CImg& display_graph(const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_graph(disp,title,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + const CImg& _display_graph(CImgDisplay &disp, const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "display_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title(title?"%s":"CImg<%s>",title?title:pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth, siz1 = std::max((ulongT)1,siz - 1); + const unsigned int old_normalization = disp.normalization(); + disp.show().flush()._normalization = 0; + + double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax; + if (nxmin==nxmax) { nxmin = 0; nxmax = siz1; } + int x0 = 0, x1 = width()*height()*depth() - 1, key = 0; + + for (bool reset_view = true; !key && !disp.is_closed(); ) { + if (reset_view) { x0 = 0; x1 = width()*height()*depth() - 1; y0 = ymin; y1 = ymax; reset_view = false; } + CImg zoom(x1 - x0 + 1,1,1,spectrum()); + cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg(data(x0,0,0,c),x1 - x0 + 1,1,1,1,true); + if (y0==y1) { y0 = zoom.min_max(y1); const double dy = y1 - y0; y0-=dy/20; y1+=dy/20; } + if (y0==y1) { --y0; ++y1; } + + const CImg selection = zoom.get_select_graph(disp,plot_type,vertex_type, + labelx, + nxmin + x0*(nxmax - nxmin)/siz1, + nxmin + x1*(nxmax - nxmin)/siz1, + labely,y0,y1,true); + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + if (selection[0]>=0) { + if (selection[2]<0) reset_view = true; + else { + x1 = x0 + selection[2]; x0+=selection[0]; + if (selection[1]>=0 && selection[3]>=0) { + y0 = y1 - selection[3]*(y1 - y0)/(disp.height() - 32); + y1-=selection[1]*(y1 - y0)/(disp.height() - 32); + } + } + } else { + bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false; + switch (key = (int)disp.key()) { + case cimg::keyHOME : reset_view = true; key = 0; disp.set_key(); break; + case cimg::keyPADADD : go_in = true; go_out = false; key = 0; disp.set_key(); break; + case cimg::keyPADSUB : go_out = true; go_in = false; key = 0; disp.set_key(); break; + case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; go_right = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; go_left = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; go_down = false; key = 0; disp.set_key(); break; + case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; go_up = false; key = 0; disp.set_key(); break; + case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; disp.set_key(); break; + case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; disp.set_key(); break; + } + if (disp.wheel()) { + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) go_up = !(go_down = disp.wheel()<0); + else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0); + else go_out = !(go_in = disp.wheel()>0); + key = 0; + } + + if (go_in) { + const int + xsiz = x1 - x0, + mx = (mouse_x - 16)*xsiz/(disp.width() - 32), + cx = x0 + cimg::cut(mx,0,xsiz); + if (x1 - x0>4) { + x0 = cx - 7*(cx - x0)/8; x1 = cx + 7*(x1 - cx)/8; + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + const double + ysiz = y1 - y0, + my = (mouse_y - 16)*ysiz/(disp.height() - 32), + cy = y1 - cimg::cut(my,0.,ysiz); + y0 = cy - 7*(cy - y0)/8; y1 = cy + 7*(y1 - cy)/8; + } else y0 = y1 = 0; + } + } + if (go_out) { + if (x0>0 || x1<(int)siz1) { + const int delta_x = (x1 - x0)/8, ndelta_x = delta_x?delta_x:(siz>1); + const double ndelta_y = (y1 - y0)/8; + x0-=ndelta_x; x1+=ndelta_x; + y0-=ndelta_y; y1+=ndelta_y; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz1; } + if (x1>=(int)siz) { x0-=(x1 - siz1); x1 = (int)siz1; if (x0<0) x0 = 0; } + } + } + if (go_left) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + go_left = false; + } + if (go_right) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x1 + ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; } + else { x0+=(siz1 - x1); x1 = (int)siz1; } + go_right = false; + } + if (go_up) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0+=ndelta; y1+=ndelta; + go_up = false; + } + if (go_down) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0-=ndelta; y1-=ndelta; + go_down = false; + } + } + if (!exit_on_anykey && key && key!=(int)cimg::keyESC && + (key!=(int)cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + key = 0; + } + } + disp._normalization = old_normalization; + return *this; + } + + //! Save image as a file. + /** + \param filename Filename, as a C-string. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + \note + - The used file format is defined by the file extension in the filename \p filename. + - Parameter \p number can be used to add a 6-digit number to the filename before saving. + + **/ + const CImg& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save(): Specified filename is (null).", + cimg_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:(number>=0)?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimg_save_plugin + cimg_save_plugin(fn); +#endif +#ifdef cimg_save_plugin1 + cimg_save_plugin1(fn); +#endif +#ifdef cimg_save_plugin2 + cimg_save_plugin2(fn); +#endif +#ifdef cimg_save_plugin3 + cimg_save_plugin3(fn); +#endif +#ifdef cimg_save_plugin4 + cimg_save_plugin4(fn); +#endif +#ifdef cimg_save_plugin5 + cimg_save_plugin5(fn); +#endif +#ifdef cimg_save_plugin6 + cimg_save_plugin6(fn); +#endif +#ifdef cimg_save_plugin7 + cimg_save_plugin7(fn); +#endif +#ifdef cimg_save_plugin8 + cimg_save_plugin8(fn); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) return save_dlm(fn); + else if (!cimg::strcasecmp(ext,"cpp") || + !cimg::strcasecmp(ext,"hpp") || + !cimg::strcasecmp(ext,"h") || + !cimg::strcasecmp(ext,"c")) return save_cpp(fn); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn); + else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn); + else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn); + else if (!cimg::strcasecmp(ext,"png")) return save_png(fn); + else if (!cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn); + else if (!cimg::strcasecmp(ext,"pnk")) return save_pnk(fn); + else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn); + else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) return save_analyze(fn); + else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn); + else if (!cimg::strcasecmp(ext,"mnc")) return save_minc2(fn); + else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn); + else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + + // Image sequences + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); + return save_other(fn); + } + + //! Save image as an Ascii file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_ascii(const char *const filename) const { + return _save_ascii(0,filename); + } + + //! Save image as an Ascii file \overloading. + const CImg& save_ascii(std::FILE *const file) const { + return _save_ascii(file,0); + } + + const CImg& _save_ascii(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_ascii(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + std::fprintf(nfile,"%u %u %u %u\n",_width,_height,_depth,_spectrum); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g ",(double)*(ptrs++)); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .cpp source file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_cpp(const char *const filename) const { + return _save_cpp(0,filename); + } + + //! Save image as a .cpp source file \overloading. + const CImg& save_cpp(std::FILE *const file) const { + return _save_cpp(file,0); + } + + const CImg& _save_cpp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_cpp(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + CImg varname(1024); *varname = 0; + if (filename) cimg_sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname._data); + if (!*varname) cimg_snprintf(varname,varname._width,"unnamed"); + std::fprintf(nfile, + "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n" + "%s data_%s[] = { %s\n ", + varname._data,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname._data, + is_empty()?"};":""); + if (!is_empty()) for (ulongT off = 0, siz = size() - 1; off<=siz; ++off) { + std::fprintf(nfile,cimg::type::format(),cimg::type::format((*this)[off])); + if (off==siz) std::fprintf(nfile," };\n"); + else if (!((off + 1)%16)) std::fprintf(nfile,",\n "); + else std::fprintf(nfile,", "); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a DLM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_dlm(const char *const filename) const { + return _save_dlm(0,filename); + } + + //! Save image as a DLM file \overloading. + const CImg& save_dlm(std::FILE *const file) const { + return _save_dlm(file,0); + } + + const CImg& _save_dlm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_dlm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is volumetric, values along Z will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is multispectral, values along C will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g%s",(double)*(ptrs++),(x==width() - 1)?"":","); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a BMP file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_bmp(const char *const filename) const { + return _save_bmp(0,filename); + } + + //! Save image as a BMP file \overloading. + const CImg& save_bmp(std::FILE *const file) const { + return _save_bmp(file,0); + } + + const CImg& _save_bmp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_bmp(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_bmp(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_bmp(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(54,1,1,1,0); + unsigned char align_buf[4] = { 0 }; + const unsigned int + align = (4 - (3*_width)%4)%4, + buf_size = (3*_width + align)*height(), + file_size = 54 + buf_size; + header[0] = 'B'; header[1] = 'M'; + header[0x02] = file_size&0xFF; + header[0x03] = (file_size>>8)&0xFF; + header[0x04] = (file_size>>16)&0xFF; + header[0x05] = (file_size>>24)&0xFF; + header[0x0A] = 0x36; + header[0x0E] = 0x28; + header[0x12] = _width&0xFF; + header[0x13] = (_width>>8)&0xFF; + header[0x14] = (_width>>16)&0xFF; + header[0x15] = (_width>>24)&0xFF; + header[0x16] = _height&0xFF; + header[0x17] = (_height>>8)&0xFF; + header[0x18] = (_height>>16)&0xFF; + header[0x19] = (_height>>24)&0xFF; + header[0x1A] = 1; + header[0x1B] = 0; + header[0x1C] = 24; + header[0x1D] = 0; + header[0x22] = buf_size&0xFF; + header[0x23] = (buf_size>>8)&0xFF; + header[0x24] = (buf_size>>16)&0xFF; + header[0x25] = (buf_size>>24)&0xFF; + header[0x27] = 0x1; + header[0x2B] = 0x1; + cimg::fwrite(header._data,54,nfile); + + const T + *ptr_r = data(0,_height - 1,0,0), + *ptr_g = (_spectrum>=2)?data(0,_height - 1,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,_height - 1,0,2):0; + + switch (_spectrum) { + case 1 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + const unsigned char val = (unsigned char)*(ptr_r++); + std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; + } + } break; + case 2 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc(0,nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; + } + } break; + default : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc((unsigned char)(*(ptr_b++)),nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; ptr_b-=2*_width; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a JPEG file. + /** + \param filename Filename, as a C-string. + \param quality Image quality (in %) + **/ + const CImg& save_jpeg(const char *const filename, const unsigned int quality=100) const { + return _save_jpeg(0,filename,quality); + } + + //! Save image as a JPEG file \overloading. + const CImg& save_jpeg(std::FILE *const file, const unsigned int quality=100) const { + return _save_jpeg(file,0,quality); + } + + const CImg& _save_jpeg(std::FILE *const file, const char *const filename, const unsigned int quality) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_jpeg(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_jpeg(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + +#ifndef cimg_use_jpeg + if (!file) return save_other(filename,quality); + else throw CImgIOException(_cimg_instance + "save_jpeg(): Unable to save data in '(*FILE)' unless libjpeg is enabled.", + cimg_instance); +#else + unsigned int dimbuf = 0; + J_COLOR_SPACE colortype = JCS_RGB; + + switch (_spectrum) { + case 1 : dimbuf = 1; colortype = JCS_GRAYSCALE; break; + case 2 : dimbuf = 3; colortype = JCS_RGB; break; + case 3 : dimbuf = 3; colortype = JCS_RGB; break; + default : dimbuf = 4; colortype = JCS_CMYK; break; + } + + // Call libjpeg functions + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + jpeg_stdio_dest(&cinfo,nfile); + cinfo.image_width = _width; + cinfo.image_height = _height; + cinfo.input_components = dimbuf; + cinfo.in_color_space = colortype; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE); + jpeg_start_compress(&cinfo,TRUE); + + JSAMPROW row_pointer[1]; + CImg buffer(_width*dimbuf); + + while (cinfo.next_scanline& save_magick(const char *const filename, const unsigned int bytes_per_pixel=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_magick(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_magick + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_magick(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_magick(): Instance is multispectral, only the three first channels will be " + "saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_magick(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + Magick::Image image(Magick::Geometry(_width,_height),"black"); + image.type(Magick::TrueColorType); + image.depth(bytes_per_pixel?(8*bytes_per_pixel):(stmax>=256?16:8)); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = _spectrum>1?data(0,0,0,1):0, + *ptr_b = _spectrum>2?data(0,0,0,2):0; + Magick::PixelPacket *pixels = image.getPixels(0,0,_width,_height); + switch (_spectrum) { + case 1 : // Scalar images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++); + ++pixels; + } + break; + case 2 : // RG images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = 0; ++pixels; + } + break; + default : // RGB images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = (Magick::Quantum)*(ptr_b++); + ++pixels; + } + } + image.syncPixels(); + image.write(filename); + return *this; +#else + cimg::unused(bytes_per_pixel); + throw CImgIOException(_cimg_instance + "save_magick(): Unable to save file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Save image as a PNG file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving, when possible. + **/ + const CImg& save_png(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_png(0,filename,bytes_per_pixel); + } + + //! Save image as a PNG file \overloading. + const CImg& save_png(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_png(file,0,bytes_per_pixel); + } + + const CImg& _save_png(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_png(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + +#ifndef cimg_use_png + cimg::unused(bytes_per_pixel); + if (!file) return save_other(filename); + else throw CImgIOException(_cimg_instance + "save_png(): Unable to save data in '(*FILE)' unless libpng is enabled.", + cimg_instance); +#else + +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb"); + volatile double stmin, stmax = (double)max_min(stmin); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"wb"); + double stmin, stmax = (double)max_min(stmin); +#endif + + if (_depth>1) + cimg::warn(_cimg_instance + "save_png(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>4) + cimg::warn(_cimg_instance + "save_png(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + // Setup PNG structures for write + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, + user_warning_fn); + if (!png_ptr){ + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'png_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr,(png_infopp)0); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'info_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + + const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8); + + int color_type; + switch (spectrum()) { + case 1 : color_type = PNG_COLOR_TYPE_GRAY; break; + case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; + case 3 : color_type = PNG_COLOR_TYPE_RGB; break; + default : color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + const int interlace_type = PNG_INTERLACE_NONE; + const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT; + const int filter_method = PNG_FILTER_TYPE_DEFAULT; + png_set_IHDR(png_ptr,info_ptr,_width,_height,bit_depth,color_type,interlace_type,compression_type,filter_method); + png_write_info(png_ptr,info_ptr); + const int byte_depth = bit_depth>>3; + const int numChan = spectrum()>4?4:spectrum(); + const int pixel_bit_depth_flag = numChan * (bit_depth - 1); + + // Allocate Memory for Image Save and Fill pixel data + png_bytep *const imgData = new png_byte*[_height]; + for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width]; + const T *pC0 = data(0,0,0,0); + switch (pixel_bit_depth_flag) { + case 7 : { // Gray 8-bit + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++); + } + } break; + case 14 : { // Gray w/ Alpha 8-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + } + } + } break; + case 21 : { // RGB 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + } + } + } break; + case 28 : { // RGB x/ Alpha 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y){ + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x){ + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + *(ptrd++) = (unsigned char)*(pC3++); + } + } + } break; + case 15 : { // Gray 16-bit + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++); + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width); + } + } break; + case 30 : { // Gray w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width); + } + } break; + case 45 : { // RGB 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width); + } + } break; + case 60 : { // RGB w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + *(ptrd++) = (unsigned short)*(pC3++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width); + } + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_write_image(png_ptr,imgData); + png_write_end(png_ptr,info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + // Deallocate Image Write Memory + cimg_forY(*this,n) delete[] imgData[n]; + delete[] imgData; + + if (!file) cimg::fclose(nfile); + return *this; +#endif + } + + //! Save image as a PNM file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving. + **/ + const CImg& save_pnm(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(0,filename,bytes_per_pixel); + } + + //! Save image as a PNM file \overloading. + const CImg& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(file,0,bytes_per_pixel); + } + + const CImg& _save_pnm(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_pnm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pnm(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_pnm(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + stmin,stmax,filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const ulongT buf_size = std::min((ulongT)(1024*1024),(ulongT)(_width*_height*(_spectrum==1?1UL:3UL))); + + std::fprintf(nfile,"P%c\n%u %u\n%u\n", + (_spectrum==1?'5':'6'),_width,_height,stmax<256?255:(stmax<4096?4095:65535)); + + switch (_spectrum) { + case 1 : { // Scalar image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PGM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr_r++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Binary PGM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned short)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + } break; + case 2 : { // RG image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = 0; + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } break; + default : { // RGB image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = (unsigned char)*(ptr_b++); + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = (unsigned short)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PNK file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pnk(const char *const filename) const { + return _save_pnk(0,filename); + } + + //! Save image as a PNK file \overloading. + const CImg& save_pnk(std::FILE *const file) const { + return _save_pnk(file,0); + } + + const CImg& _save_pnk(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnk(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_pnk(): Instance is multispectral, only the first channel will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + const ulongT buf_size = std::min((ulongT)1024*1024,(ulongT)_width*_height*_depth); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T *ptr = data(0,0,0,0); + + if (!cimg::type::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 + 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; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else if (!cimg::type::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); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + int *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (int)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Save as P9: Binary float-valued 3D + if (_depth>1) std::fprintf(nfile,"P9\n%u %u %u\n%g\n",_width,_height,_depth,(double)max()); + else std::fprintf(nfile,"P9\n%u %u\n%g\n",_width,_height,(double)max()); + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PFM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pfm(const char *const filename) const { + get_mirror('y')._save_pfm(0,filename); + return *this; + } + + //! Save image as a PFM file \overloading. + const CImg& save_pfm(std::FILE *const file) const { + get_mirror('y')._save_pfm(file,0); + return *this; + } + + const CImg& _save_pfm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pfm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_pfm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pfm(): image instance is multispectral, only the three first channels will be saved " + "in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const unsigned int buf_size = std::min(1024*1024U,_width*_height*(_spectrum==1?1:3)); + + std::fprintf(nfile,"P%c\n%u %u\n1.0\n", + (_spectrum==1?'f':'F'),_width,_height); + + switch (_spectrum) { + case 1 : { // Scalar image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,(ulongT)buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } break; + case 2 : { // RG image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } break; + default : { // RGB image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = (float)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a RGB file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_rgb(const char *const filename) const { + return _save_rgb(0,filename); + } + + //! Save image as a RGB file \overloading. + const CImg& save_rgb(std::FILE *const file) const { + return _save_rgb(file,0); + } + + const CImg& _save_rgb(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgb(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=3) + cimg::warn(_cimg_instance + "save_rgb(): image instance has not exactly 3 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0; + switch (_spectrum) { + case 1 : { // Scalar image + for (ulongT k = 0; k& save_rgba(const char *const filename) const { + return _save_rgba(0,filename); + } + + //! Save image as a RGBA file \overloading. + const CImg& save_rgba(std::FILE *const file) const { + return _save_rgba(file,0); + } + + const CImg& _save_rgba(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgba(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=4) + cimg::warn(_cimg_instance + "save_rgba(): image instance has not exactly 4 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[4*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0, + *ptr4 = _spectrum>3?data(0,0,0,3):0; + switch (_spectrum) { + case 1 : { // Scalar images + for (ulongT k = 0; k{ 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 use_bigtiff Allow to save big tiff files (>4Gb). + \note + - libtiff support is enabled by defining the precompilation + directive \c cimg_use_tif. + - When libtiff is enabled, 2D and 3D (multipage) several + channel per pixel are supported for + char,uchar,short,ushort,float and \c double pixel types. + - If \c cimg_use_tif 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) + throw CImgArgumentException(_cimg_instance + "save_tiff(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_tiff + const bool + _use_bigtiff = use_bigtiff && sizeof(ulongT)>=8 && size()*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + cimg_forZ(*this,z) _save_tiff(tif,z,z,compression_type,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "save_tiff(): Failed to open file '%s' for writing.", + cimg_instance, + filename); + return *this; +#else + cimg::unused(compression_type,voxel_size,description,use_bigtiff); + return save_other(filename); +#endif + } + +#ifdef cimg_use_tiff + +#define _cimg_save_tiff(types,typed,compression_type) if (!std::strcmp(types,pixel_type())) { \ + const typed foo = (typed)0; return _save_tiff(tif,directory,z,foo,compression_type,voxel_size,description); } + + // [internal] Save a plane into a tiff file + template + const CImg& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, const t& pixel_t, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + if (is_empty() || !tif || pixel_t) return *this; + const char *const filename = TIFFFileName(tif); + uint32 rowsperstrip = (uint32)-1; + uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric; + if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB; + else photometric = PHOTOMETRIC_MINISBLACK; + TIFFSetDirectory(tif,directory); + TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,_width); + TIFFSetField(tif,TIFFTAG_IMAGELENGTH,_height); + if (voxel_size) { + const float vx = voxel_size[0], vy = voxel_size[1], vz = voxel_size[2]; + TIFFSetField(tif,TIFFTAG_RESOLUTIONUNIT,RESUNIT_NONE); + TIFFSetField(tif,TIFFTAG_XRESOLUTION,1.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()); + } + if (description) TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,description); + TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT); + TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp); + if (cimg::type::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3); + else if (cimg::type::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1); + else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2); + double valm, valM = max_min(valm); + TIFFSetField(tif,TIFFTAG_SMINSAMPLEVALUE,valm); + TIFFSetField(tif,TIFFTAG_SMAXSAMPLEVALUE,valM); + TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp); + TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG); + TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric); + TIFFSetField(tif,TIFFTAG_COMPRESSION,compression_type==2?COMPRESSION_JPEG: + compression_type==1?COMPRESSION_LZW:COMPRESSION_NONE); + rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip); + TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip); + TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB); + TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg"); + + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + for (unsigned int row = 0; row<_height; row+=rowsperstrip) { + uint32 nrow = (row + rowsperstrip>_height?_height - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif,row,0); + tsize_t i = 0; + for (unsigned int rr = 0; rr& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + _cimg_save_tiff("bool",unsigned char,compression_type); + _cimg_save_tiff("unsigned char",unsigned char,compression_type); + _cimg_save_tiff("char",char,compression_type); + _cimg_save_tiff("unsigned short",unsigned short,compression_type); + _cimg_save_tiff("short",short,compression_type); + _cimg_save_tiff("unsigned int",unsigned int,compression_type); + _cimg_save_tiff("int",int,compression_type); + _cimg_save_tiff("unsigned int64",unsigned int,compression_type); + _cimg_save_tiff("int64",int,compression_type); + _cimg_save_tiff("float",float,compression_type); + _cimg_save_tiff("double",float,compression_type); + const char *const filename = TIFFFileName(tif); + throw CImgInstanceException(_cimg_instance + "save_tiff(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + return *this; + } +#endif + + //! Save image as a MINC2 file. + /** + \param filename Filename, as a C-string. + \param imitate_file If non-zero, reference filename, as a C-string, to borrow header from. + **/ + const CImg& save_minc2(const char *const filename, + const char *const imitate_file=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_minc2(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_minc2 + cimg::unused(imitate_file); + return save_other(filename); +#else + minc::minc_1_writer wtr; + if (imitate_file) + wtr.open(filename, imitate_file); + else { + minc::minc_info di; + if (width()) di.push_back(minc::dim_info(width(),width()*0.5,-1,minc::dim_info::DIM_X)); + if (height()) di.push_back(minc::dim_info(height(),height()*0.5,-1,minc::dim_info::DIM_Y)); + if (depth()) di.push_back(minc::dim_info(depth(),depth()*0.5,-1,minc::dim_info::DIM_Z)); + if (spectrum()) di.push_back(minc::dim_info(spectrum(),spectrum()*0.5,-1,minc::dim_info::DIM_TIME)); + wtr.open(filename,di,1,NC_FLOAT,0); + } + if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_byte(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_int(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_double(); + else + wtr.setup_write_float(); + minc::save_standard_volume(wtr, this->_data); + return *this; +#endif + } + + //! Save image as an ANALYZE7.5 or NIFTI file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 consecutive values that tell about the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_analyze(const char *const filename, const float *const voxel_size=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_analyze(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + std::FILE *file; + CImg hname(1024), iname(1024); + const char *const ext = cimg::split_filename(filename); + short datatype = -1; + if (!*ext) { + cimg_snprintf(hname,hname._width,"%s.hdr",filename); + cimg_snprintf(iname,iname._width,"%s.img",filename); + } + if (!cimg::strncasecmp(ext,"hdr",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(iname._data + std::strlen(iname) - 3,"img"); + } + if (!cimg::strncasecmp(ext,"img",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(hname._data + std::strlen(iname) - 3,"hdr"); + } + if (!cimg::strncasecmp(ext,"nii",3)) { + std::strncpy(hname,filename,hname._width - 1); *iname = 0; + } + + CImg header(*iname?348:352,1,1,1,0); + int *const iheader = (int*)header._data; + *iheader = 348; + std::strcpy(header._data + 4,"CImg"); + std::strcpy(header._data + 14," "); + ((short*)&(header[36]))[0] = 4096; + ((char*)&(header[38]))[0] = 114; + ((short*)&(header[40]))[0] = 4; + ((short*)&(header[40]))[1] = (short)_width; + ((short*)&(header[40]))[2] = (short)_height; + ((short*)&(header[40]))[3] = (short)_depth; + ((short*)&(header[40]))[4] = (short)_spectrum; + if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"unsigned int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16; + if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64; + if (datatype<0) + throw CImgIOException(_cimg_instance + "save_analyze(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename); + + ((short*)&(header[70]))[0] = datatype; + ((short*)&(header[72]))[0] = sizeof(T); + ((float*)&(header[108]))[0] = (float)(*iname?0:header.width()); + ((float*)&(header[112]))[0] = 1; + ((float*)&(header[76]))[0] = 0; + if (voxel_size) { + ((float*)&(header[76]))[1] = voxel_size[0]; + ((float*)&(header[76]))[2] = voxel_size[1]; + ((float*)&(header[76]))[3] = voxel_size[2]; + } else ((float*)&(header[76]))[1] = ((float*)&(header[76]))[2] = ((float*)&(header[76]))[3] = 1; + file = cimg::fopen(hname,"wb"); + cimg::fwrite(header._data,header.width(),file); + if (*iname) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); } + cimg::fwrite(_data,size(),file); + cimg::fclose(file); + return *this; + } + + //! Save image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param is_compressed Tells if the file contains compressed image data. + **/ + const CImg& save_cimg(const char *const filename, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(filename,is_compressed); + return *this; + } + + //! Save image as a .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(file,is_compressed); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Index of the image inside the file. + \param x0 X-coordinate of the sub-image location. + \param y0 Y-coordinate of the sub-image location. + \param z0 Z-coordinate of the sub-image location. + \param c0 C-coordinate of the sub-image location. + **/ + const CImg& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(filename,n0,x0,y0,z0,c0); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(file,n0,x0,y0,z0,c0); + return *this; + } + + //! Save blank image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param dx Width of the image. + \param dy Height of the image. + \param dz Depth of the image. + \param dc Number of channels of the image. + \note + - All pixel values of the saved image are set to \c 0. + - Use this method to save large images without having to instanciate and allocate them. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(filename,1,dx,dy,dz,dc); + } + + //! Save blank image as a .cimg file \overloading. + /** + Same as save_empty_cimg(const char *,unsigned int,unsigned int,unsigned int,unsigned int) + with a file stream argument instead of a filename string. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(file,1,dx,dy,dz,dc); + } + + //! Save image as an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 values specifying the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_inr(const char *const filename, const float *const voxel_size=0) const { + return _save_inr(0,filename,voxel_size); + } + + //! Save image as an INRIMAGE-4 file \overloading. + const CImg& save_inr(std::FILE *const file, const float *const voxel_size=0) const { + return _save_inr(file,0,voxel_size); + } + + const CImg& _save_inr(std::FILE *const file, const char *const filename, const float *const voxel_size) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_inr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + int inrpixsize = -1; + const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { + inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"char")) { + inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { + inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"short")) { + inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { + inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"int")) { + inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"float")) { + inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"double")) { + inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; + } + if (inrpixsize<=0) + throw CImgIOException(_cimg_instance + "save_inr(): Unsupported pixel type '%s' for file '%s'", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(257); + int err = cimg_snprintf(header,header._width,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n", + _width,_height,_depth,_spectrum); + if (voxel_size) err+=cimg_sprintf(header._data + err,"VX=%g\nVY=%g\nVZ=%g\n", + voxel_size[0],voxel_size[1],voxel_size[2]); + err+=cimg_sprintf(header._data + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm"); + std::memset(header._data + err,'\n',252 - err); + std::memcpy(header._data + 252,"##}\n",4); + cimg::fwrite(header._data,256,nfile); + cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) cimg::fwrite(&((*this)(x,y,z,c)),1,nfile); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as an OpenEXR file. + /** + \param filename Filename, as a C-string. + \note The OpenEXR file format is described here. + **/ + const CImg& save_exr(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_exr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_exr(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + +#ifndef cimg_use_openexr + return save_other(filename); +#else + Imf::Rgba *const ptrd0 = new Imf::Rgba[(size_t)_width*_height], *ptrd = ptrd0, rgba; + switch (_spectrum) { + case 1 : { // Grayscale image + for (const T *ptr_r = data(), *const ptr_e = ptr_r + (ulongT)_width*_height; ptr_rPandore file specifications + for more information). + **/ + const CImg& save_pandore(const char *const filename, const unsigned int colorspace=0) const { + return _save_pandore(0,filename,colorspace); + } + + //! Save image as a Pandore-5 file \overloading. + /** + Same as save_pandore(const char *,unsigned int) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_pandore(std::FILE *const file, const unsigned int colorspace=0) const { + return _save_pandore(file,0,colorspace); + } + + unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const { + unsigned int nbdims = 0; + if (id==2 || id==3 || id==4) { + dims[0] = 1; dims[1] = _width; nbdims = 2; + } + if (id==5 || id==6 || id==7) { + dims[0] = 1; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==8 || id==9 || id==10) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + if (id==16 || id==17 || id==18) { + dims[0] = 3; dims[1] = _height; dims[2] = _width; dims[3] = colorspace; nbdims = 4; + } + if (id==19 || id==20 || id==21) { + dims[0] = 3; dims[1] = _depth; dims[2] = _height; dims[3] = _width; dims[4] = colorspace; nbdims = 5; + } + if (id==22 || id==23 || id==25) { + dims[0] = _spectrum; dims[1] = _width; nbdims = 2; + } + if (id==26 || id==27 || id==29) { + dims[0] = _spectrum; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==30 || id==31 || id==33) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + return nbdims; + } + + const CImg& _save_pandore(std::FILE *const file, const char *const filename, + const unsigned int colorspace) const { + +#define __cimg_save_pandore_case(dtype) \ + dtype *buffer = new dtype[size()]; \ + const T *ptrs = _data; \ + cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \ + buffer-=size(); \ + cimg::fwrite(buffer,size(),nfile); \ + delete[] buffer + +#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \ + if (!saved && (sy?(sy==_height):true) && (sz?(sz==_depth):true) && \ + (sv?(sv==_spectrum):true) && !std::strcmp(stype,pixel_type())) { \ + unsigned int *iheader = (unsigned int*)(header + 12); \ + nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \ + cimg::fwrite(header,36,nfile); \ + if (sizeof(unsigned long)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned int)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned short)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \ + __cimg_save_pandore_case(unsigned char); \ + } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \ + if (sizeof(unsigned long)==4) { __cimg_save_pandore_case(unsigned long); } \ + else if (sizeof(unsigned int)==4) { __cimg_save_pandore_case(unsigned int); } \ + else if (sizeof(unsigned short)==4) { __cimg_save_pandore_case(unsigned short); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \ + if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \ + else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pandore(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0, + 0,0,0,0,'C','I','m','g',0,0,0,0,0, + 'N','o',' ','d','a','t','e',0,0,0,0 }; + unsigned int nbdims, dims[5] = { 0 }; + bool saved = false; + _cimg_save_pandore_case(1,1,1,"unsigned char",2); + _cimg_save_pandore_case(1,1,1,"char",3); + _cimg_save_pandore_case(1,1,1,"unsigned short",3); + _cimg_save_pandore_case(1,1,1,"short",3); + _cimg_save_pandore_case(1,1,1,"unsigned int",3); + _cimg_save_pandore_case(1,1,1,"int",3); + _cimg_save_pandore_case(1,1,1,"unsigned int64",3); + _cimg_save_pandore_case(1,1,1,"int64",3); + _cimg_save_pandore_case(1,1,1,"float",4); + _cimg_save_pandore_case(1,1,1,"double",4); + + _cimg_save_pandore_case(0,1,1,"unsigned char",5); + _cimg_save_pandore_case(0,1,1,"char",6); + _cimg_save_pandore_case(0,1,1,"unsigned short",6); + _cimg_save_pandore_case(0,1,1,"short",6); + _cimg_save_pandore_case(0,1,1,"unsigned int",6); + _cimg_save_pandore_case(0,1,1,"int",6); + _cimg_save_pandore_case(0,1,1,"unsigned int64",6); + _cimg_save_pandore_case(0,1,1,"int64",6); + _cimg_save_pandore_case(0,1,1,"float",7); + _cimg_save_pandore_case(0,1,1,"double",7); + + _cimg_save_pandore_case(0,0,1,"unsigned char",8); + _cimg_save_pandore_case(0,0,1,"char",9); + _cimg_save_pandore_case(0,0,1,"unsigned short",9); + _cimg_save_pandore_case(0,0,1,"short",9); + _cimg_save_pandore_case(0,0,1,"unsigned int",9); + _cimg_save_pandore_case(0,0,1,"int",9); + _cimg_save_pandore_case(0,0,1,"unsigned int64",9); + _cimg_save_pandore_case(0,0,1,"int64",9); + _cimg_save_pandore_case(0,0,1,"float",10); + _cimg_save_pandore_case(0,0,1,"double",10); + + _cimg_save_pandore_case(0,1,3,"unsigned char",16); + _cimg_save_pandore_case(0,1,3,"char",17); + _cimg_save_pandore_case(0,1,3,"unsigned short",17); + _cimg_save_pandore_case(0,1,3,"short",17); + _cimg_save_pandore_case(0,1,3,"unsigned int",17); + _cimg_save_pandore_case(0,1,3,"int",17); + _cimg_save_pandore_case(0,1,3,"unsigned int64",17); + _cimg_save_pandore_case(0,1,3,"int64",17); + _cimg_save_pandore_case(0,1,3,"float",18); + _cimg_save_pandore_case(0,1,3,"double",18); + + _cimg_save_pandore_case(0,0,3,"unsigned char",19); + _cimg_save_pandore_case(0,0,3,"char",20); + _cimg_save_pandore_case(0,0,3,"unsigned short",20); + _cimg_save_pandore_case(0,0,3,"short",20); + _cimg_save_pandore_case(0,0,3,"unsigned int",20); + _cimg_save_pandore_case(0,0,3,"int",20); + _cimg_save_pandore_case(0,0,3,"unsigned int64",20); + _cimg_save_pandore_case(0,0,3,"int64",20); + _cimg_save_pandore_case(0,0,3,"float",21); + _cimg_save_pandore_case(0,0,3,"double",21); + + _cimg_save_pandore_case(1,1,0,"unsigned char",22); + _cimg_save_pandore_case(1,1,0,"char",23); + _cimg_save_pandore_case(1,1,0,"unsigned short",23); + _cimg_save_pandore_case(1,1,0,"short",23); + _cimg_save_pandore_case(1,1,0,"unsigned int",23); + _cimg_save_pandore_case(1,1,0,"int",23); + _cimg_save_pandore_case(1,1,0,"unsigned int64",23); + _cimg_save_pandore_case(1,1,0,"int64",23); + _cimg_save_pandore_case(1,1,0,"float",25); + _cimg_save_pandore_case(1,1,0,"double",25); + + _cimg_save_pandore_case(0,1,0,"unsigned char",26); + _cimg_save_pandore_case(0,1,0,"char",27); + _cimg_save_pandore_case(0,1,0,"unsigned short",27); + _cimg_save_pandore_case(0,1,0,"short",27); + _cimg_save_pandore_case(0,1,0,"unsigned int",27); + _cimg_save_pandore_case(0,1,0,"int",27); + _cimg_save_pandore_case(0,1,0,"unsigned int64",27); + _cimg_save_pandore_case(0,1,0,"int64",27); + _cimg_save_pandore_case(0,1,0,"float",29); + _cimg_save_pandore_case(0,1,0,"double",29); + + _cimg_save_pandore_case(0,0,0,"unsigned char",30); + _cimg_save_pandore_case(0,0,0,"char",31); + _cimg_save_pandore_case(0,0,0,"unsigned short",31); + _cimg_save_pandore_case(0,0,0,"short",31); + _cimg_save_pandore_case(0,0,0,"unsigned int",31); + _cimg_save_pandore_case(0,0,0,"int",31); + _cimg_save_pandore_case(0,0,0,"unsigned int64",31); + _cimg_save_pandore_case(0,0,0,"int64",31); + _cimg_save_pandore_case(0,0,0,"float",33); + _cimg_save_pandore_case(0,0,0,"double",33); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a raw data file. + /** + \param filename Filename, as a C-string. + \param is_multiplexed Tells if the image channels are stored in a multiplexed way (\c true) or not (\c false). + \note The .raw format does not store the image dimensions in the output file, + so you have to keep track of them somewhere to be able to read the file correctly afterwards. + **/ + const CImg& save_raw(const char *const filename, const bool is_multiplexed=false) const { + return _save_raw(0,filename,is_multiplexed); + } + + //! Save image as a raw data file \overloading. + /** + Same as save_raw(const char *,bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_raw(std::FILE *const file, const bool is_multiplexed=false) const { + return _save_raw(file,0,is_multiplexed); + } + + const CImg& _save_raw(std::FILE *const file, const char *const filename, const bool is_multiplexed) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_raw(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!is_multiplexed) cimg::fwrite(_data,size(),nfile); + else { + CImg buf(_spectrum); + cimg_forXYZ(*this,x,y,z) { + cimg_forC(*this,c) buf[c] = (*this)(x,y,z,c); + cimg::fwrite(buf._data,_spectrum,nfile); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .yuv video file. + /** + \param filename Filename, as a C-string. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if pixel values of the instance image are RGB-coded (\c true) or YUV-coded (\c false). + \note Each slice of the instance image is considered to be a single frame of the output video file. + **/ + const CImg& save_yuv(const char *const filename, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(filename,chroma_subsampling,is_rgb); + return *this; + } + + //! Save image as a .yuv video file \overloading. + /** + Same as save_yuv(const char*,const unsigned int,const bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(file,chroma_subsampling,is_rgb); + return *this; + } + + //! Save 3D object as an Object File Format (.off) file. + /** + \param filename Filename, as a C-string. + \param primitives List of 3D object primitives. + \param colors List of 3D object colors. + \note + - Instance image contains the vertices data of the 3D object. + - Textured, transparent or sphere-shaped primitives cannot be managed by the .off file format. + Such primitives will be lost or simplified during file saving. + - The .off file format is described here. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + const char *const filename) const { + return _save_off(primitives,colors,0,filename); + } + + //! Save 3D object as an Object File Format (.off) file \overloading. + /** + Same as save_off(const CImgList&,const CImgList&,const char*) const + with a file stream argument instead of a filename string. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file) const { + return _save_off(primitives,colors,file,0); + } + + template + const CImg& _save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_off(): Specified filename is (null).", + cimg_instance); + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "save_off(): Empty instance, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + CImgList opacities; + 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).", + cimg_instance, + filename?filename:"(FILE*)",error_message.data()); + + const CImg default_color(1,3,1,1,200); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + unsigned int supported_primitives = 0; + cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives; + std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width); + cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n", + (float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2))); + cimglist_for(primitives,l) { + const CImg& color = l1?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; + case 2 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 3 : std::fprintf(nfile,"3 %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,2), + (unsigned int)primitives(l,1),r,g,b); break; + case 4 : std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,3), + (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); break; + case 5 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 6 : { + const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3); + const float + rt = color.atXY(xt,yt,0)/255.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.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); + } break; + case 12 : { + const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5); + const float + rt = color.atXY(xt,yt,0)/255.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); + } break; + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save volumetric image as a video, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImg& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { + if (is_empty()) { CImgList().save_video(filename,fps,codec,keep_open); return *this; } + CImgList list; + get_split('z').move_to(list); + list.save_video(filename,fps,codec,keep_open); + return *this; + } + + //! Save volumetric image as a video, using ffmpeg external binary. + /** + \param filename Filename, as a C-string. + \param fps Video framerate. + \param codec Video codec, as a C-string. + \param bitrate Video bitrate. + \note + - Each slice of the instance image is considered to be a single frame of the output video file. + - This method uses \c ffmpeg, an external executable binary provided by + FFmpeg. + It must be installed for the method to succeed. + **/ + const CImg& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImgList list; + get_split('z').move_to(list); + list.save_ffmpeg_external(filename,fps,codec,bitrate); + return *this; + } + + //! Save image using gzip external binary. + /** + \param filename Filename, as a C-string. + \note This method uses \c gzip, an external executable binary provided by + gzip. + It must be installed for the method to succeed. + **/ + const CImg& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_gzip_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimg_instance, + filename); + + else cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using GraphicsMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c gm, an external executable binary provided by + GraphicsMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + +#ifdef cimg_use_png +#define _cimg_sge_ext1 "png" +#define _cimg_sge_ext2 "png" +#else +#define _cimg_sge_ext1 "pgm" +#define _cimg_sge_ext2 "ppm" +#endif + CImg 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=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::graphicsmagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_graphicsmagick_external(): Failed to save file '%s' with external command 'gm'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using ImageMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c convert, an external executable binary provided by + ImageMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_imagemagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick only writes the first image slice.", + cimg_instance,filename); +#ifdef cimg_use_png +#define _cimg_sie_ext1 "png" +#define _cimg_sie_ext2 "png" +#else +#define _cimg_sie_ext1 "pgm" +#define _cimg_sie_ext2 "ppm" +#endif + CImg 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=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::imagemagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_imagemagick_external(): Failed to save file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image as a Dicom file. + /** + \param filename Filename, as a C-string. + \note This method uses \c medcon, an external executable binary provided by + (X)Medcon. + It must be installed for the method to succeed. + **/ + const CImg& save_medcon_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_medcon_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + std::FILE *file; + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand()); + 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::medcon_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command); + std::remove(filename_tmp); + cimg::split_filename(filename_tmp,body); + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.img",body._data); + std::remove(filename_tmp); + + file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s",filename); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimg_instance + "save_medcon_external(): Failed to save file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + std::rename(command,filename); + return *this; + } + + // Save image for non natively supported formats. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note + - The filename extension tells about the desired file format. + - This method tries to save the instance image as a file, using external tools from + ImageMagick or + GraphicsMagick. + At least one of these tool must be installed for the method to succeed. + - It is recommended to use the generic method save(const char*, int) const instead, + as it can handle some file formats natively. + **/ + const CImg& save_other(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_other(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick or GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + + const unsigned int omode = cimg::exception_mode(); + bool is_saved = true; + cimg::exception_mode(0); + try { save_magick(filename); } + catch (CImgException&) { + try { save_imagemagick_external(filename,quality); } + catch (CImgException&) { + try { save_graphicsmagick_external(filename,quality); } + catch (CImgException&) { + is_saved = false; + } + } + } + cimg::exception_mode(omode); + if (!is_saved) + throw CImgIOException(_cimg_instance + "save_other(): Failed to save file '%s'. Format is not natively supported, " + "and no external commands succeeded.", + cimg_instance, + filename); + return *this; + } + + //! Serialize a CImg instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { + return CImgList(*this,true).get_serialize(is_compressed); + } + + // [internal] Return a 40x38 color logo of a 'danger' item. + static CImg _logo40x38() { + CImg res(40,38,1,3); + const unsigned char *ptrs = cimg::logo40x38; + T *ptr1 = res.data(0,0,0,0), *ptr2 = res.data(0,0,0,1), *ptr3 = res.data(0,0,0,2); + for (ulongT off = 0; off<(ulongT)res._width*res._height;) { + const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++); + for (unsigned int l = 0; l structure + # + # + # + #------------------------------------------ + */ + //! Represent a list of images CImg. + template + struct CImgList { + unsigned int _width, _allocated_width; + CImg *_data; + + //! Simple iterator type, to loop through each image of a list. + /** + \note + - 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 + for (CImgList<>::iterator it = list.begin(); it* iterator; + + //! Simple const iterator type, to loop through each image of a \c const list instance. + /** + \note + - The \c CImgList::const_iterator type is defined to be a const CImg*. + - Similar to CImgList::iterator, but for constant list instances. + **/ + typedef const CImg* const_iterator; + + //! Pixel value type. + /** + Refer to the pixels value type of the images in the list. + \note + - The \c CImgList::value_type type of a \c CImgList is defined to be a \c T. + It is then similar to CImg::value_type. + - \c CImgList::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimglist_plugin +#include cimglist_plugin +#endif +#ifdef cimglist_plugin1 +#include cimglist_plugin1 +#endif +#ifdef cimglist_plugin2 +#include cimglist_plugin2 +#endif +#ifdef cimglist_plugin3 +#include cimglist_plugin3 +#endif +#ifdef cimglist_plugin4 +#include cimglist_plugin4 +#endif +#ifdef cimglist_plugin5 +#include cimglist_plugin5 +#endif +#ifdef cimglist_plugin6 +#include cimglist_plugin6 +#endif +#ifdef cimglist_plugin7 +#include cimglist_plugin7 +#endif +#ifdef cimglist_plugin8 +#include cimglist_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + Destroy current list instance. + \note + - Any allocated buffer is deallocated. + - Destroying an empty list does nothing actually. + **/ + ~CImgList() { + delete[] _data; + } + + //! Default constructor. + /** + Construct a new empty list instance. + \note + - An empty list has no pixel data and its dimension width() is set to \c 0, as well as its + image buffer pointer data(). + - An empty list may be reassigned afterwards, with the family of the assign() methods. + In all cases, the type of pixels stays \c T. + **/ + CImgList(): + _width(0),_allocated_width(0),_data(0) {} + + //! Construct list containing empty images. + /** + \param n Number of empty images. + \note Useful when you know by advance the number of images you want to manage, as + it will allocate the right amount of memory for the list, without needs for reallocation + (that may occur when starting from an empty list and inserting several images in it). + **/ + explicit CImgList(const unsigned int n):_width(n) { + if (n) _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + else { _allocated_width = 0; _data = 0; } + } + + //! Construct list containing images of specified size. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \note Pixel values are not initialized and may probably contain garbage. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + } + + //! Construct list containing images of specified size, and initialize pixel values. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val Initialization value for images pixels. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + } + + //! Construct list containing images of specified size, and initialize pixel values from a sequence of integers. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val0 First value of the initializing integers sequence. + \param val1 Second value of the initializing integers sequence. + \warning You must specify at least width*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...): + _width(0),_allocated_width(0),_data(0) { +#define _CImgList_stdarg(t) { \ + assign(n,width,height,depth,spectrum); \ + const ulongT siz = (ulongT)width*height*depth*spectrum, nsiz = siz*n; \ + T *ptrd = _data->_data; \ + va_list ap; \ + va_start(ap,val1); \ + for (ulongT l = 0, s = 0, i = 0; iwidth*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const double val0, const double val1, ...): + _width(0),_allocated_width(0),_data(0) { + _CImgList_stdarg(double); + } + + //! Construct list containing copies of an input image. + /** + \param n Number of images. + \param img Input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of \c img. + **/ + template + CImgList(const unsigned int n, const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + } + + //! Construct list from one image. + /** + \param img Input image to copy in the constructed list. + \param is_shared Tells if the element of the list is a shared or non-shared copy of \c img. + **/ + template + explicit CImgList(const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(1); + _data[0].assign(img,is_shared); + } + + //! Construct list from two images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + } + + //! Construct list from three images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + } + + //! Construct list from four images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + } + + //! Construct list from five images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + } + + //! Construct list from six images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + } + + //! Construct list from seven images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + } + + //! Construct list from eight images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param img8 Eighth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + } + + //! Construct list copy. + /** + \param list Input list to copy. + \note The shared state of each element of the constructed list is kept the same as in \c list. + **/ + template + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + } + + //! Construct list copy \specialization. + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared); + } + + //! Construct list copy, and force the shared state of the list elements. + /** + \param list Input list to copy. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImgList& list, const bool is_shared):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],is_shared); + } + + //! Construct list by reading the content of a file. + /** + \param filename Filename, as a C-string. + **/ + explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) { + assign(filename); + } + + //! Construct list from the content of a display window. + /** + \param disp Display window to get content from. + \note Constructed list contains a single image only. + **/ + explicit CImgList(const CImgDisplay& disp):_width(0),_allocated_width(0),_data(0) { + assign(disp); + } + + //! Return a list with elements being shared copies of images in the list instance. + /** + \note list2 = list1.get_shared() is equivalent to list2.assign(list1,true). + **/ + CImgList get_shared() { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Return a list with elements being shared copies of images in the list instance \const. + const CImgList get_shared() const { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Destructor \inplace. + /** + \see CImgList(). + **/ + CImgList& assign() { + delete[] _data; + _width = _allocated_width = 0; + _data = 0; + return *this; + } + + //! Destructor \inplace. + /** + Equivalent to assign(). + \note Only here for compatibility with STL naming conventions. + **/ + CImgList& clear() { + return assign(); + } + + //! Construct list containing empty images \inplace. + /** + \see CImgList(unsigned int). + **/ + CImgList& assign(const unsigned int n) { + if (!n) return assign(); + if (_allocated_width(n<<2)) { + delete[] _data; + _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + } + _width = n; + return *this; + } + + //! Construct list containing images of specified size \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + return *this; + } + + //! Construct list containing images of specified size, and initialize pixel values \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const T). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of integers \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const int, const int, ...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) { + _CImgList_stdarg(int); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of doubles \inplace. + /** + \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const double,const double,...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, + const double val0, const double val1, ...) { + _CImgList_stdarg(double); + return *this; + } + + //! Construct list containing copies of an input image \inplace. + /** + \see CImgList(unsigned int, const CImg&, bool). + **/ + template + CImgList& assign(const unsigned int n, const CImg& img, const bool is_shared=false) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + return *this; + } + + //! Construct list from one image \inplace. + /** + \see CImgList(const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img, const bool is_shared=false) { + assign(1); + _data[0].assign(img,is_shared); + return *this; + } + + //! Construct list from two images \inplace. + /** + \see CImgList(const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const bool is_shared=false) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + return *this; + } + + //! Construct list from three images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + return *this; + } + + //! Construct list from four images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + return *this; + } + + //! Construct list from five images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + return *this; + } + + //! Construct list from six images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + return *this; + } + + //! Construct list from seven images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + return *this; + } + + //! Construct list from eight images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + return *this; + } + + //! Construct list as a copy of an existing list and force the shared state of the list elements \inplace. + /** + \see CImgList(const CImgList&, bool is_shared). + **/ + template + CImgList& assign(const CImgList& list, const bool is_shared=false) { + cimg::unused(is_shared); + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + return *this; + } + + //! Construct list as a copy of an existing list and force shared state of elements \inplace \specialization. + CImgList& assign(const CImgList& list, const bool is_shared=false) { + if (this==&list) return *this; + CImgList res(list._width); + cimglist_for(res,l) res[l].assign(list[l],is_shared); + return res.move_to(*this); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& assign(const char *const filename) { + return load(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& assign(const CImgDisplay &disp) { + return assign(CImg(disp)); + } + + //! Transfer the content of the list instance to another list. + /** + \param list Destination list. + \note When returning, the current list instance is empty and the initial content of \c list is destroyed. + **/ + template + CImgList& move_to(CImgList& list) { + list.assign(_width); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[l]); + assign(); + return list; + } + + //! Transfer the content of the list instance at a specified position in another list. + /** + \param list Destination list. + \param pos Index of the insertion in the list. + \note When returning, the list instance is empty and the initial content of \c list is preserved + (only images indexes may be modified). + **/ + template + CImgList& move_to(CImgList& list, const unsigned int pos) { + if (is_empty()) return list; + const unsigned int npos = pos>list._width?list._width:pos; + list.insert(_width,npos); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[npos + l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[npos + l]); + assign(); + return list; + } + + //! Swap all fields between two list instances. + /** + \param list List to swap fields with. + \note Can be used to exchange the content of two lists in a fast way. + **/ + CImgList& swap(CImgList& list) { + cimg::swap(_width,list._width,_allocated_width,list._allocated_width); + cimg::swap(_data,list._data); + return list; + } + + //! Return a reference to an empty list. + /** + \note Can be used to define default values in a function taking a CImgList as an argument. + \code + void f(const CImgList& list=CImgList::empty()); + \endcode + **/ + static CImgList& empty() { + static CImgList _empty; + return _empty.assign(); + } + + //! Return a reference to an empty list \const. + static const CImgList& const_empty() { + static const CImgList _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Return a reference to one image element of the list. + /** + \param pos Indice of the image element. + **/ + CImg& operator()(const unsigned int pos) { +#if cimg_verbosity>=3 + if (pos>=_width) { + cimg::warn(_cimglist_instance + "operator(): Invalid image request, at position [%u].", + cimglist_instance, + pos); + return *_data; + } +#endif + return _data[pos]; + } + + //! Return a reference to one image of the list. + /** + \param pos Indice of the image element. + **/ + const CImg& operator()(const unsigned int pos) const { + return const_cast*>(this)->operator()(pos); + } + + //! Return a reference to one pixel value of one image of the list. + /** + \param pos Indice of the image element. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note list(n,x,y,z,c) is equivalent to list[n](x,y,z,c). + **/ + T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + return (*this)[pos](x,y,z,c); + } + + //! Return a reference to one pixel value of one image of the list \const. + const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return (*this)[pos](x,y,z,c); + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + operator CImg*() { + return _data; + } + + //! Return pointer to the first image of the list \const. + operator const CImg*() const { + return _data; + } + + //! Construct list from one image \inplace. + /** + \param img Input image to copy in the constructed list. + \note list = img; is equivalent to list.assign(img);. + **/ + template + CImgList& operator=(const CImg& img) { + return assign(img); + } + + //! Construct list from another list. + /** + \param list Input list to copy. + \note list1 = list2 is equivalent to list1.assign(list2);. + **/ + template + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list from another list \specialization. + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& operator=(const char *const filename) { + return assign(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return a non-shared copy of a list. + /** + \note +list is equivalent to CImgList(list,false). + It forces the copy to have non-shared elements. + **/ + CImgList operator+() const { + return CImgList(*this,false); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end. + /** + \param img Image inserted at the end of the instance copy. + \note Define a convenient way to create temporary lists of images, as in the following code: + \code + (img1,img2,img3,img4).display("My four images"); + \endcode + **/ + template + CImgList& operator,(const CImg& img) { + return insert(img); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end \const. + template + CImgList operator,(const CImg& img) const { + return (+*this).insert(img); + } + + //! Return a copy of the list instance, where all elements of input list \c list have been inserted at the end. + /** + \param list List inserted at the end of the instance copy. + **/ + template + CImgList& operator,(const CImgList& list) { + return insert(list); + } + + //! Return a copy of the list instance, where all elements of input \c list have been inserted at the end \const. + template + CImgList& operator,(const CImgList& list) const { + return (+*this).insert(list); + } + + //! Return image corresponding to the appending of all images of the instance list along specified axis. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \note list>'x' is equivalent to list.get_append('x'). + **/ + CImg operator>(const char axis) const { + return get_append(axis,0); + } + + //! Return list corresponding to the splitting of all images of the instance list along specified axis. + /** + \param axis Axis used for image splitting. + \note list<'x' is equivalent to list.get_split('x'). + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to size() but returns result as a (signed) integer. + **/ + int width() const { + return (int)_width; + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to width() but returns result as an unsigned integer. + **/ + unsigned int size() const { + return _width; + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + CImg *data() { + return _data; + } + + //! Return pointer to the first image of the list \const. + const CImg *data() const { + return _data; + } + + //! Return pointer to the pos-th image of the list. + /** + \param pos Indice of the image element to access. + \note list.data(n); is equivalent to list.data + n;. + **/ +#if cimg_verbosity>=3 + CImg *data(const unsigned int pos) { + if (pos>=size()) + cimg::warn(_cimglist_instance + "data(): Invalid pointer request, at position [%u].", + cimglist_instance, + pos); + return _data + pos; + } + + const CImg *data(const unsigned int l) const { + return const_cast*>(this)->data(l); + } +#else + CImg *data(const unsigned int l) { + return _data + l; + } + + //! Return pointer to the pos-th image of the list \const. + const CImg *data(const unsigned int l) const { + return _data + l; + } +#endif + + //! Return iterator to the first image of the list. + /** + **/ + iterator begin() { + return _data; + } + + //! Return iterator to the first image of the list \const. + const_iterator begin() const { + return _data; + } + + //! Return iterator to one position after the last image of the list. + /** + **/ + iterator end() { + return _data + _width; + } + + //! Return iterator to one position after the last image of the list \const. + const_iterator end() const { + return _data + _width; + } + + //! Return reference to the first image of the list. + /** + **/ + CImg& front() { + return *_data; + } + + //! Return reference to the first image of the list \const. + const CImg& front() const { + return *_data; + } + + //! Return a reference to the last image of the list. + /** + **/ + const CImg& back() const { + return *(_data + _width - 1); + } + + //! Return a reference to the last image of the list \const. + CImg& back() { + return *(_data + _width - 1); + } + + //! Return pos-th image of the list. + /** + \param pos Indice of the image element to access. + **/ + CImg& at(const int pos) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "at(): Empty instance.", + cimglist_instance); + + return _data[cimg::cut(pos,0,width() - 1)]; + } + + //! Access to pixel value with Dirichlet boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZC(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + T _atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZ(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + T _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXY(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + T _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x) \const. + T atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atX(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x) \const. + T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + T _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:(*this)(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Return \c true if list is empty. + /** + **/ + bool is_empty() const { + return (!_data || !_width); + } + + //! Test if number of image elements is equal to specified value. + /** + \param size_n Number of image elements to test. + **/ + bool is_sameN(const unsigned int size_n) const { + return _width==size_n; + } + + //! Test if number of image elements is equal between two images lists. + /** + \param list Input list to compare with. + **/ + template + bool is_sameN(const CImgList& list) const { + return is_sameN(list._width); + } + + // Define useful functions to check list dimensions. + // (cannot be documented because macro-generated). +#define _cimglist_def_is_same1(axis) \ + bool is_same##axis(const unsigned int val) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(val); return res; \ + } \ + bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \ + return is_sameN(n) && is_same##axis(val); \ + } \ + +#define _cimglist_def_is_same2(axis1,axis2) \ + bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2(val1,val2); return res; \ + } \ + bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \ + return is_sameN(n) && is_same##axis1##axis2(val1,val2); \ + } \ + +#define _cimglist_def_is_same3(axis1,axis2,axis3) \ + bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, \ + const unsigned int val3) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2##axis3(val1,val2,val3); \ + return res; \ + } \ + bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, \ + const unsigned int val2, const unsigned int val3) const { \ + return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \ + } \ + +#define _cimglist_def_is_same(axis) \ + template 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; \ + } \ + 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)); \ + } \ + template bool is_sameN##axis(const CImgList& list) const { \ + return (is_sameN(list) && is_same##axis(list)); \ + } + + _cimglist_def_is_same(XY) + _cimglist_def_is_same(XZ) + _cimglist_def_is_same(XC) + _cimglist_def_is_same(YZ) + _cimglist_def_is_same(YC) + _cimglist_def_is_same(XYZ) + _cimglist_def_is_same(XYC) + _cimglist_def_is_same(YZC) + _cimglist_def_is_same(XYZC) + _cimglist_def_is_same1(X) + _cimglist_def_is_same1(Y) + _cimglist_def_is_same1(Z) + _cimglist_def_is_same1(C) + _cimglist_def_is_same2(X,Y) + _cimglist_def_is_same2(X,Z) + _cimglist_def_is_same2(X,C) + _cimglist_def_is_same2(Y,Z) + _cimglist_def_is_same2(Y,C) + _cimglist_def_is_same2(Z,C) + _cimglist_def_is_same3(X,Y,Z) + _cimglist_def_is_same3(X,Y,C) + _cimglist_def_is_same3(X,Z,C) + _cimglist_def_is_same3(Y,Z,C) + + //! Test if dimensions of each image of the list match specified arguments. + /** + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameXYZC(const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + bool res = true; + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_sameXYZC(dx,dy,dz,dc); + return res; + } + + //! Test if list dimensions match specified arguments. + /** + \param n Number of images in the list. + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameNXYZC(const unsigned int n, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + return is_sameN(n) && is_sameXYZC(dx,dy,dz,dc); + } + + //! Test if list contains one particular pixel location. + /** + \param n Index of the image whom checked pixel value belong to. + \param x X-coordinate of the checked pixel value. + \param y Y-coordinate of the checked pixel value. + \param z Z-coordinate of the checked pixel value. + \param c C-coordinate of the checked pixel value. + **/ + bool containsNXYZC(const int n, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width && x>=0 && x<_data[n].width() && y>=0 && y<_data[n].height() && + z>=0 && z<_data[n].depth() && c>=0 && c<_data[n].spectrum(); + } + + //! Test if list contains image with specified indice. + /** + \param n Index of the checked image. + **/ + bool containsN(const int n) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width; + } + + //! Test if one image of the list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \param[out] c C-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z,c). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const { + if (is_empty()) return false; + cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; } + return false; + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z) const { + t c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y) const { + t z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x). + **/ + template + bool contains(const T& pixel, t& n, t& x) const { + t y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \note If true, set coordinates (n). + **/ + template + bool contains(const T& pixel, t& n) const { + t x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + **/ + bool contains(const T& pixel) const { + unsigned int n, x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if the list contains the image 'img'. + /** + \param img Reference to image to test. + \param[out] n Index of image in the list, if test succeeds. + \note If true, returns the position (n) of the image in the list. + **/ + template + bool contains(const CImg& img, t& n) const { + if (is_empty()) return false; + const CImg *const ptr = &img; + cimglist_for(*this,i) if (_data + i==ptr) { n = (t)i; return true; } + return false; + } + + //! Test if the list contains the image img. + /** + \param img Reference to image to test. + **/ + bool contains(const CImg& img) const { + unsigned int n; + return contains(img,n); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + //! Return a reference to the minimum pixel value of the instance list. + /** + **/ + T& min() { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the maximum pixel value of the instance list \const. + const T& max() const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T max_value = *ptr_max; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + T& min_max(t& max_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well \const. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + const T& min_max(t& max_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well. + /** + \param[out] min_val Value of the minimum value found. + **/ + template + T& max_min(t& min_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + if (is_shared) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified shared image " + "CImg<%s>(%u,%u,%u,%u,%p) at position %u (pixel types are different).", + cimglist_instance, + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos); + + CImg *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 + _data = new_data; + *_data = img; + } else { + 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 + 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; + _data[npos] = img; + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \specialization. + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + CImg *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 + _data = new_data; + if (is_shared && img) { + _data->_width = img._width; + _data->_height = img._height; + _data->_depth = img._depth; + _data->_spectrum = img._spectrum; + _data->_is_shared = true; + _data->_data = img._data; + } else *_data = img; + } + else { + if (new_data) { // Insert with re-allocation + if (npos) std::memcpy((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)); + if (is_shared && img) { + new_data[npos]._width = img._width; + new_data[npos]._height = img._height; + new_data[npos]._depth = img._depth; + new_data[npos]._spectrum = img._spectrum; + new_data[npos]._is_shared = true; + new_data[npos]._data = img._data; + } else { + new_data[npos]._width = new_data[npos]._height = new_data[npos]._depth = new_data[npos]._spectrum = 0; + new_data[npos]._data = 0; + new_data[npos] = img; + } + std::memset((void*)_data,0,sizeof(CImg)*(_width - 1)); + delete[] _data; + _data = new_data; + } 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) { + _data[npos]._width = img._width; + _data[npos]._height = img._height; + _data[npos]._depth = img._depth; + _data[npos]._spectrum = img._spectrum; + _data[npos]._is_shared = true; + _data[npos]._data = img._data; + } else { + _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; + _data[npos]._data = 0; + _data[npos] = img; + } + } + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \newinstance. + template + CImgList get_insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(img,pos,is_shared); + } + + //! Insert n empty images img into the current image list, at position \p pos. + /** + \param n Number of empty images to insert. + \param pos Index of the insertion. + **/ + CImgList& insert(const unsigned int n, const unsigned int pos=~0U) { + CImg empty; + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i get_insert(const unsigned int n, const unsigned int pos=~0U) const { + return (+*this).insert(n,pos); + } + + //! Insert \c n copies of the image \c img into the current image list, at position \c pos. + /** + \param n Number of image copies to insert. + \param img Image to insert by copy. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of \c img or not. + **/ + template + CImgList& insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + insert(img,npos,is_shared); + for (unsigned int i = 1; i + CImgList get_insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,img,pos,is_shared); + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos. + /** + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos + l,is_shared); + else insert(CImgList(list),npos,is_shared); + return *this; + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos \newinstance. + template + CImgList get_insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(list,pos,is_shared); + } + + //! Insert n copies of the list \c list at position \c pos of the current list. + /** + \param n Number of list copies to insert. + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i + CImgList get_insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,list,pos,is_shared); + } + + //! Remove all images between from indexes. + /** + \param pos1 Starting index of the removal. + \param pos2 Ending index of the removal. + **/ + CImgList& remove(const unsigned int pos1, const unsigned int pos2) { + const unsigned int + npos1 = pos1=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + else { + if (tpos2>=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + + for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign(); + const unsigned int nb = 1 + npos2 - npos1; + if (!(_width-=nb)) return assign(); + if (_width>(_allocated_width>>2) || _allocated_width<=16) { // Removing items without reallocation + if (npos1!=_width) + std::memmove((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; + 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); + if (npos1!=_width) + std::memcpy((void*)(new_data + npos1),(void*)(_data + npos2 + 1),sizeof(CImg)*(_width - npos1)); + if (_width!=_allocated_width) + std::memset((void*)(new_data + _width),0,sizeof(CImg)*(_allocated_width - _width)); + std::memset((void*)_data,0,sizeof(CImg)*(_width + nb)); + delete[] _data; + _data = new_data; + } + } + return *this; + } + + //! Remove all images between from indexes \newinstance. + CImgList get_remove(const unsigned int pos1, const unsigned int pos2) const { + return (+*this).remove(pos1,pos2); + } + + //! Remove image at index \c pos from the image list. + /** + \param pos Index of the image to remove. + **/ + CImgList& remove(const unsigned int pos) { + return remove(pos,pos); + } + + //! Remove image at index \c pos from the image list \newinstance. + CImgList get_remove(const unsigned int pos) const { + return (+*this).remove(pos); + } + + //! Remove last image. + /** + **/ + CImgList& remove() { + return remove(_width - 1); + } + + //! Remove last image \newinstance. + CImgList get_remove() const { + return (+*this).remove(); + } + + //! Reverse list order. + CImgList& reverse() { + for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width - 1 - l]); + return *this; + } + + //! Reverse list order \newinstance. + CImgList get_reverse() const { + return (+*this).reverse(); + } + + //! Return a sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList& images(const unsigned int pos0, const unsigned int pos1) { + return get_images(pos0,pos1).move_to(*this); + } + + //! Return a sublist \newinstance. + CImgList get_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l]); + return res; + } + + //! Return a shared sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a shared sublist \newinstance. + const CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a single image which is the appending of all images of the current CImgList instance. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg get_append(const char axis, const float align=0) const { + if (is_empty()) return CImg(); + if (_width==1) return +((*this)[0]); + unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0; + CImg res; + switch (cimg::lowercase(axis)) { + case 'x' : { // Along the X-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx+=img._width; + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image(pos, + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._width; + } + } break; + case 'y' : { // Along the Y-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy+=img._height; + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + pos, + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._height; + } + } break; + case 'z' : { // Along the Z-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz+=img._depth; + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + pos, + (int)(align*(dc - img._spectrum)), + img); + pos+=img._depth; + } + } break; + default : { // Along the C-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc+=img._spectrum; + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + pos, + img); + pos+=img._spectrum; + } + } + } + return res; + } + + //! Return a list where each image has been split along the specified axis. + /** + \param axis Axis to split images along. + \param nb Number of spliting parts for each image. + **/ + CImgList& split(const char axis, const int nb=-1) { + return get_split(axis,nb).move_to(*this); + } + + //! Return a list where each image has been split along the specified axis \newinstance. + CImgList get_split(const char axis, const int nb=-1) const { + CImgList res; + cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U); + return res; + } + + //! Insert image at the end of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_back(const CImg& img) { + return insert(img); + } + + //! Insert image at the front of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_front(const CImg& img) { + return insert(img,0); + } + + //! Insert list at the end of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_back(const CImgList& list) { + return insert(list); + } + + //! Insert list at the front of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_front(const CImgList& list) { + return insert(list,0); + } + + //! Remove last image. + /** + **/ + CImgList& pop_back() { + return remove(_width - 1); + } + + //! Remove first image. + /** + **/ + CImgList& pop_front() { + return remove(0); + } + + //! Remove image pointed by iterator. + /** + \param iter Iterator pointing to the image to remove. + **/ + CImgList& erase(const iterator iter) { + return remove(iter - _data); + } + + //@} + //---------------------------------- + // + //! \name Data Input + //@{ + //---------------------------------- + + //! Display a simple interactive interface to select images or sublists. + /** + \param disp Window instance to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(CImgDisplay &disp, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + return _select(disp,0,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + //! Display a simple interactive interface to select images or sublists. + /** + \param title Title of a new window used to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(const char *const title, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + CImg _select(CImgDisplay &disp, const char *const title, const bool feature_type, + const char axis, const float align, const bool exit_on_anykey, + const unsigned int orig, const bool resize_disp, + const bool exit_on_rightbutton, const bool exit_on_wheel) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "select(): Empty instance.", + cimglist_instance); + + // Create image correspondence table and get list dimensions for visualization. + CImgList _indices; + unsigned int max_width = 0, max_height = 0, sum_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + if (h>max_height) max_height = h; + sum_width+=w; sum_height+=h; + if (axis=='x') CImg(w,1,1,1,(unsigned int)l).move_to(_indices); + else CImg(h,1,1,1,(unsigned int)l).move_to(_indices); + } + const CImg indices0 = _indices>'x'; + + // Create display window. + if (!disp) { + if (axis=='x') disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1); + else disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1); + if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else { + if (title) disp.set_title("%s",title); + 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); + } + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0); + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + // Enter event loop. + CImg visu0, visu; + CImg indices; + CImg positions(_width,4,1,1,-1); + int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1; + bool is_clicked = false, is_selected = false, text_down = false, update_display = true; + unsigned int key = 0; + + while (!is_selected && !disp.is_closed() && !key) { + + // Create background image. + if (!visu0) { + visu0.assign(disp._width,disp._height,1,3,0); visu.assign(); + (indices0.get_resize(axis=='x'?visu0._width:visu0._height,1)).move_to(indices); + unsigned int ind = 0; + const CImg onexone(1,1,1,1,(T)0); + if (axis=='x') + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)x0; + positions(ind,1) = positions(ind,3) = (int)(align*(visu0.height() - res.height())); + positions(ind,2)+=res._width; + positions(ind,3)+=res._height - 1; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + else + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)(align*(visu0.width() - res.width())); + positions(ind,1) = positions(ind,3) = (int)y0; + positions(ind,2)+=res._width - 1; + positions(ind,3)+=res._height; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + if (axis=='x') --positions(ind,2); else --positions(ind,3); + update_display = true; + } + + if (!visu || oindice0!=indice0 || oindice1!=indice1) { + if (indice0>=0 && indice1>=0) { + visu.assign(visu0,false); + const int indm = std::min(indice0,indice1), indM = std::max(indice0,indice1); + for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) { + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + background_color,0.2f); + if ((axis=='x' && positions(ind,2) - positions(ind,0)>=8) || + (axis!='x' && positions(ind,3) - positions(ind,1)>=8)) + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + foreground_color,0.9f,0xAAAAAAAA); + } + if (is_clicked) visu.__draw_text(" Images #%u - #%u, Size = %u",text_down, + orig + indm,orig + indM,indM - indm + 1); + else visu.__draw_text(" Image #%u (%u,%u,%u,%u)",text_down, + orig + indice0, + _data[indice0]._width, + _data[indice0]._height, + _data[indice0]._depth, + _data[indice0]._spectrum); + update_display = true; + } else visu.assign(); + } + if (!visu) { visu.assign(visu0,true); update_display = true; } + if (update_display) { visu.display(disp); update_display = false; } + disp.wait(); + + // Manage user events. + const int xm = disp.mouse_x(), ym = disp.mouse_y(); + int indice = -1; + + if (xm>=0) { + indice = (int)indices(axis=='x'?xm:ym); + if (disp.button()&1) { + if (!is_clicked) { is_clicked = true; oindice0 = indice0; indice0 = indice; } + oindice1 = indice1; indice1 = indice; + if (!feature_type) is_selected = true; + } else { + if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; } + else is_selected = true; + } + } else { + if (is_clicked) { + if (!(disp.button()&1)) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + else indice1 = -1; + } else indice0 = indice1 = -1; + } + + if (disp.button()&4) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; indice1 = indice0 = -1; } + if (disp.wheel() && exit_on_wheel) is_selected = true; + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(axis=='x'?sum_width:max_width,axis=='x'?max_height:sum_height,1),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot... ",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false).wait(); key = 0; + } break; + case cimg::keyO : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false).wait(); key = 0; + } break; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (ym>=0 && ym<13) { if (!text_down) { visu.assign(); text_down = true; }} + else if (ym>=visu.height() - 13) { if (text_down) { visu.assign(); text_down = false; }} + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + CImg res(1,2,1,1,-1); + if (is_selected) { + if (feature_type) res.fill(std::min(indice0,indice1),std::max(indice0,indice1)); + else res.fill(indice0); + } + if (!(disp.button()&2)) disp.set_button(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + disp.set_key(key); + return res; + } + + //! Load a list from a file. + /** + \param filename Filename to read data from. + **/ + CImgList& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load(): Specified filename is (null).", + cimglist_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimglist_load_plugin + cimglist_load_plugin(filename); +#endif +#ifdef cimglist_load_plugin1 + cimglist_load_plugin1(filename); +#endif +#ifdef cimglist_load_plugin2 + cimglist_load_plugin2(filename); +#endif +#ifdef cimglist_load_plugin3 + cimglist_load_plugin3(filename); +#endif +#ifdef cimglist_load_plugin4 + cimglist_load_plugin4(filename); +#endif +#ifdef cimglist_load_plugin5 + cimglist_load_plugin5(filename); +#endif +#ifdef cimglist_load_plugin6 + cimglist_load_plugin6(filename); +#endif +#ifdef cimglist_load_plugin7 + cimglist_load_plugin7(filename); +#endif +#ifdef cimglist_load_plugin8 + cimglist_load_plugin8(filename); +#endif + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) load_cimg(filename); + else if (!cimg::strcasecmp(ext,"rec") || + !cimg::strcasecmp(ext,"par")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded && !is_stdin) { + std::FILE *const file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to open file '%s'.", + cimglist_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + 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 is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file as a single image. + if (!is_loaded) { + assign(1); + try { + _data->load(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to recognize format of file '%s'.", + cimglist_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load a list from a file \newinstance. + static CImgList get_load(const char *const filename) { + return CImgList().load(filename); + } + + //! Load a list from a .cimg file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_cimg(const char *const filename) { + return _load_cimg(0,filename); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename) { + return CImgList().load_cimg(filename); + } + + //! Load a list from a .cimg file. + /** + \param file File to read data from. + **/ + CImgList& load_cimg(std::FILE *const file) { + return _load_cimg(file,0); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file) { + return CImgList().load_cimg(file); + } + + CImgList& _load_cimg(std::FILE *const file, const char *const filename) { +#ifdef cimg_use_zlib +#define _cimgz_load_cimg_case(Tss) { \ + Bytef *const cbuf = new Bytef[csiz]; \ + cimg::fread(cbuf,csiz,nfile); \ + raw.assign(W,H,D,C); \ + uLongf destlen = (ulongT)raw.size()*sizeof(Tss); \ + uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \ + delete[] cbuf; \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ +} +#else +#define _cimgz_load_cimg_case(Tss) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Unable to load compressed data from file '%s' unless zlib is enabled.", \ + cimglist_instance, \ + filename?filename:"(FILE*)"); +#endif + +#define _cimg_load_cimg_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; csiz = 0; \ + if ((err = cimg_sscanf(tmp,"%u %u %u %u #%lu",&W,&H,&D,&C,&csiz))<4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'.", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:("(FILE*)")); \ + if (W*H*D*C>0) { \ + CImg 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; \ + } \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + + const ulongT cimg_iobuffer = (ulongT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + unsigned long csiz; + int i, err; + do { + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; + } while (*tmp=='#' && i>=0); + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + assign(N); + _cimg_load_cimg_case("bool",bool); + _cimg_load_cimg_case("unsigned_char",unsigned char); + _cimg_load_cimg_case("uchar",unsigned char); + _cimg_load_cimg_case("char",char); + _cimg_load_cimg_case("unsigned_short",unsigned short); + _cimg_load_cimg_case("ushort",unsigned short); + _cimg_load_cimg_case("short",short); + _cimg_load_cimg_case("unsigned_int",unsigned int); + _cimg_load_cimg_case("uint",unsigned int); + _cimg_load_cimg_case("int",int); + _cimg_load_cimg_case("unsigned_long",ulongT); + _cimg_load_cimg_case("ulong",ulongT); + _cimg_load_cimg_case("long",longT); + _cimg_load_cimg_case("unsigned_int64",uint64T); + _cimg_load_cimg_case("uint64",uint64T); + _cimg_load_cimg_case("int64",int64T); + _cimg_load_cimg_case("float",float); + _cimg_load_cimg_case("double",double); + + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a sublist list from a (non compressed) .cimg file. + /** + \param filename Filename to read data from. + \param n0 Starting index of images to read (~0U for max). + \param n1 Ending index of images to read (~0U for max). + \param x0 Starting X-coordinates of image regions to read. + \param y0 Starting Y-coordinates of image regions to read. + \param z0 Starting Z-coordinates of image regions to read. + \param c0 Starting C-coordinates of image regions to read. + \param x1 Ending X-coordinates of image regions to read (~0U for max). + \param y1 Ending Y-coordinates of image regions to read (~0U for max). + \param z1 Ending Z-coordinates of image regions to read (~0U for max). + \param c1 Ending C-coordinates of image regions to read (~0U for max). + **/ + CImgList& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sublist list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \overloading. + CImgList& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + 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 z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { +#define _cimg_load_cimg_case2(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l<=nn1; ++l) { \ + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; \ + if (cimg_sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:"(FILE*)"); \ + if (W*H*D*C>0) { \ + if (l=W || ny0>=H || nz0>=D || nc0>=C) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const unsigned int \ + _nx1 = nx1==~0U?W - 1:nx1, \ + _ny1 = ny1==~0U?H - 1:ny1, \ + _nz1 = nz1==~0U?D - 1:nz1, \ + _nc1 = nc1==~0U?C - 1:nc1; \ + if (_nx1>=W || _ny1>=H || _nz1>=D || _nc1>=C) \ + throw CImgArgumentException(_cimglist_instance \ + "load_cimg(): Invalid specified coordinates " \ + "[%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " \ + "because image [%u] in file '%s' has size (%u,%u,%u,%u).", \ + cimglist_instance, \ + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,l,filename?filename:"(FILE*)",W,H,D,C); \ + CImg raw(1 + _nx1 - nx0); \ + CImg &img = _data[l - nn0]; \ + img.assign(1 + _nx1 - nx0,1 + _ny1 - ny0,1 + _nz1 - nz0,1 + _nc1 - nc0); \ + T *ptrd = img._data; \ + ulongT skipvb = nc0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int c = 1 + _nc1 - nc0; c; --c) { \ + const ulongT skipzb = nz0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + _nz1 - nz0; z; --z) { \ + const ulongT skipyb = ny0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + _ny1 - ny0; y; --y) { \ + const ulongT skipxb = nx0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + cimg::fread(raw._data,raw._width,nfile); \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); \ + const Tss *ptrs = raw._data; \ + for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \ + const ulongT skipxe = (W - 1 - _nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const ulongT skipye = (H - 1 - _ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const ulongT skipze = (D - 1 - _nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const ulongT skipve = (C - 1 - _nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + unsigned int + nn0 = std::min(n0,n1), nn1 = std::max(n0,n1), + nx0 = std::min(x0,x1), nx1 = std::max(x0,x1), + ny0 = std::min(y0,y1), ny1 = std::max(y0,y1), + nz0 = std::min(z0,z1), nz1 = std::max(z0,z1), + nc0 = std::min(c0,c1), nc1 = std::max(c0,c1); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + nn1 = n1==~0U?N - 1:n1; + if (nn1>=N) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Invalid specified coordinates [%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " + "because file '%s' contains only %u images.", + cimglist_instance, + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,filename?filename:"(FILE*)",N); + assign(1 + nn1 - n0); + _cimg_load_cimg_case2("bool",bool); + _cimg_load_cimg_case2("unsigned_char",unsigned char); + _cimg_load_cimg_case2("uchar",unsigned char); + _cimg_load_cimg_case2("char",char); + _cimg_load_cimg_case2("unsigned_short",unsigned short); + _cimg_load_cimg_case2("ushort",unsigned short); + _cimg_load_cimg_case2("short",short); + _cimg_load_cimg_case2("unsigned_int",unsigned int); + _cimg_load_cimg_case2("uint",unsigned int); + _cimg_load_cimg_case2("int",int); + _cimg_load_cimg_case2("unsigned_long",ulongT); + _cimg_load_cimg_case2("ulong",ulongT); + _cimg_load_cimg_case2("long",longT); + _cimg_load_cimg_case2("unsigned_int64",uint64T); + _cimg_load_cimg_case2("uint64",uint64T); + _cimg_load_cimg_case2("int64",int64T); + _cimg_load_cimg_case2("float",float); + _cimg_load_cimg_case2("double",double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_parrec(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_parrec(): Specified filename is (null).", + cimglist_instance); + + CImg body(1024), filenamepar(1024), filenamerec(1024); + *body = *filenamepar = *filenamerec = 0; + const char *const ext = cimg::split_filename(filename,body); + if (!std::strcmp(ext,"par")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.rec",body._data); + } + if (!std::strcmp(ext,"PAR")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.REC",body._data); + } + if (!std::strcmp(ext,"rec")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.par",body._data); + } + if (!std::strcmp(ext,"REC")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.PAR",body._data); + } + std::FILE *file = cimg::fopen(filenamepar,"r"); + + // Parse header file + CImgList st_slices; + CImgList st_global; + CImg line(256); *line = 0; + int err; + do { err = std::fscanf(file,"%255[^\n]%*c",line._data); } while (err!=EOF && (*line=='#' || *line=='.')); + do { + unsigned int sn,size_x,size_y,pixsize; + float rs,ri,ss; + err = std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&size_x,&size_y,&ri,&rs,&ss); + if (err==7) { + CImg::vector((float)sn,(float)pixsize,(float)size_x,(float)size_y,ri,rs,ss,0).move_to(st_slices); + unsigned int i; for (i = 0; i::vector(size_x,size_y,sn).move_to(st_global); + else { + CImg &vec = st_global[i]; + if (size_x>vec[0]) vec[0] = size_x; + if (size_y>vec[1]) vec[1] = size_y; + vec[2] = sn; + } + st_slices[st_slices._width - 1][7] = (float)i; + } + } while (err==7); + + // Read data + std::FILE *file2 = cimg::fopen(filenamerec,"rb"); + cimglist_for(st_global,l) { + const CImg& vec = st_global[l]; + CImg(vec[0],vec[1],vec[2]).move_to(*this); + } + + cimglist_for(st_slices,l) { + const CImg& vec = st_slices[l]; + const unsigned int + sn = (unsigned int)vec[0] - 1, + pixsize = (unsigned int)vec[1], + size_x = (unsigned int)vec[2], + size_y = (unsigned int)vec[3], + imn = (unsigned int)vec[7]; + const float ri = vec[4], rs = vec[5], ss = vec[6]; + switch (pixsize) { + case 8 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 16 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 32 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + default : + cimg::fclose(file); + cimg::fclose(file2); + throw CImgIOException(_cimglist_instance + "load_parrec(): Unsupported %d-bits pixel type for file '%s'.", + cimglist_instance, + pixsize,filename); + } + } + cimg::fclose(file); + cimg::fclose(file2); + if (!_width) + throw CImgIOException(_cimglist_instance + "load_parrec(): Failed to recognize valid PAR-REC data in file '%s'.", + cimglist_instance, + filename); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file \newinstance. + static CImgList get_load_parrec(const char *const filename) { + return CImgList().load_parrec(filename); + } + + //! Load a list from a YUV image sequence file. + /** + \param filename Filename to read data from. + \param size_x Width of the images. + \param size_y Height of the images. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of first image frame to read. + \param last_frame Index of last image frame to read. + \param step_frame Step applied between each frame. + \param yuv2rgb Apply YUV to RGB transformation during reading. + **/ + CImgList& load_yuv(const char *const filename, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(0,filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from a YUV image sequence file \newinstance. + static CImgList get_load_yuv(const char *const filename, + 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) { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \overloading. + CImgList& load_yuv(std::FILE *const file, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(file,0,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \newinstance. + static CImgList get_load_yuv(std::FILE *const file, + 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) { + return CImgList().load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + CImgList& _load_yuv(std::FILE *const file, const char *const filename, + 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) { + 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'.", + 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_frame YUV(size_x,size_y,1,3), UV(size_x/cfx,size_y/cfy,1,2); + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool stop_flag = false; + int err; + if (nfirst_frame) { + err = cimg::fseek(nfile,(uint64T)nfirst_frame*(YUV._width*YUV._height + 2*UV._width*UV._height),SEEK_CUR); + if (err) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_yuv(): File '%s' doesn't contain frame number %u.", + cimglist_instance, + filename?filename:"(FILE*)",nfirst_frame); + } + } + unsigned int frame; + for (frame = nfirst_frame; !stop_flag && frame<=nlast_frame; frame+=nstep_frame) { + YUV.get_shared_channel(0).fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(YUV._data),1,(size_t)YUV._width*YUV._height,nfile); + if (err!=(int)(YUV._width*YUV._height)) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + UV.fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(UV._data),1,(size_t)UV.size(),nfile); + if (err!=(int)(UV.size())) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + const ucharT *ptrs1 = UV._data, *ptrs2 = UV.data(0,0,0,1); + ucharT *ptrd1 = YUV.data(0,0,0,1), *ptrd2 = YUV.data(0,0,0,2); + const unsigned int wd = YUV._width; + switch (chroma_subsampling) { + case 420 : + cimg_forY(UV,y) { + cimg_forX(UV,x) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd2[wd] = V; *(ptrd2)++ = V; + ptrd2[wd] = V; *(ptrd2)++ = V; + } + ptrd1+=wd; ptrd2+=wd; + } + break; + case 422 : + cimg_forXY(UV,x,y) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + *(ptrd1++) = U; *(ptrd1++) = U; + *(ptrd2++) = V; *(ptrd2++) = V; + } + break; + default : + YUV.draw_image(0,0,0,1,UV); + } + if (yuv2rgb) YUV.YCbCrtoRGB(); + insert(YUV); + if (nstep_frame>1) cimg::fseek(nfile,(uint64T)(nstep_frame - 1)*(size_x*size_y + size_x*size_y/2),SEEK_CUR); + } + } + } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_yuv() : Missing data in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + if (stop_flag && nlast_frame!=~0U && frame!=nlast_frame) + cimg::warn(_cimglist_instance + "load_yuv(): Frame %d not reached since only %u frames were found in file '%s'.", + cimglist_instance, + nlast_frame,frame - 1,filename?filename:"(FILE*)"); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \note If step_frame==0, the current video stream is forced to be released (without any frames read). + **/ + CImgList& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { +#ifndef cimg_use_opencv + if (first_frame || last_frame!=~0U || step_frame>1) + throw CImgArgumentException(_cimglist_instance + "load_video() : File '%s', arguments 'first_frame', 'last_frame' " + "and 'step_frame' can be only set when using OpenCV " + "(-Dcimg_use_opencv must be enabled).", + cimglist_instance,filename); + return load_ffmpeg_external(filename); +#else + static CvCapture *captures[32] = { 0 }; + static CImgList filenames(32); + static CImg positions(32,1,1,1,0); + static int last_used_index = -1; + + // Detect if a video capture already exists for the specified filename. + cimg::mutex(9); + int index = -1; + if (filename) { + if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) { + index = last_used_index; + } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) { + index = l; break; + } + } else index = last_used_index; + cimg::mutex(9,0); + + // Release stream if needed. + if (!step_frame || (index>=0 && positions[index]>first_frame)) { + if (index>=0) { + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; filenames[index].assign(); positions[index] = 0; + if (last_used_index==index) last_used_index = -1; + index = -1; + cimg::mutex(9,0); + } else + if (filename) + cimg::warn(_cimglist_instance + "load_video() : File '%s', no opened video stream associated with filename found.", + cimglist_instance,filename); + else + cimg::warn(_cimglist_instance + "load_video() : No opened video stream found.", + cimglist_instance,filename); + if (!step_frame) return *this; + } + + // Find empty slot for capturing video stream. + if (index<0) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_video(): No already open video reader found. You must specify a " + "non-(null) filename argument for the first call.", + cimglist_instance); + else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); } + if (index<0) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', no video reader slots available. " + "You have to release some of your previously opened videos.", + cimglist_instance,filename); + cimg::mutex(9); + captures[index] = cvCaptureFromFile(filename); + CImg::string(filename).move_to(filenames[index]); + positions[index] = 0; + cimg::mutex(9,0); + if (!captures[index]) { + filenames[index].assign(); + 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::mutex(9); + const unsigned int nb_frames = (unsigned int)std::max(0.,cvGetCaptureProperty(captures[index], + CV_CAP_PROP_FRAME_COUNT)); + cimg::mutex(9,0); + assign(); + + // Skip frames if necessary. + bool go_on = true; + unsigned int &pos = positions[index]; + while (pos frame(src->width,src->height,1,3); + const int step = (int)(src->widthStep - 3*src->width); + const unsigned char* ptrs = (unsigned char*)src->imageData; + T *ptr_r = frame.data(0,0,0,0), *ptr_g = frame.data(0,0,0,1), *ptr_b = frame.data(0,0,0,2); + if (step>0) cimg_forY(frame,y) { + cimg_forX(frame,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); } + ptrs+=step; + } else for (ulongT siz = (ulongT)src->width*src->height; siz; --siz) { + *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); + } + frame.move_to(*this); + ++pos; + + bool skip_failed = false; + for (unsigned int i = 1; i=nb_frames)) { // Close video stream when necessary + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; + filenames[index].assign(); + positions[index] = 0; + index = -1; + cimg::mutex(9,0); + } + + cimg::mutex(9); + last_used_index = index; + cimg::mutex(9,0); + + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', unable to locate frame %u.", + cimglist_instance,filename,first_frame); + return *this; +#endif + } + + //! Load an image from a video file, using OpenCV library \newinstance. + static CImgList get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame); + } + + //! Load an image from a video file using the external tool 'ffmpeg'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_ffmpeg_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + 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=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::ffmpeg_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp2)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + unsigned int i = 1; + for (bool stop_flag = false; !stop_flag; ++i) { + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,i); + CImg img; + try { img.load_pnm(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + cimg::exception_mode(omode); + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_ffmpeg_external(): Failed to open file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + return *this; + } + + //! Load an image from a video file using the external tool 'ffmpeg' \newinstance. + static CImgList get_load_ffmpeg_external(const char *const filename) { + return CImgList().load_ffmpeg_external(filename); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gif_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_gif_external(): Specified filename is (null).", + cimglist_instance); + 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(); } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_gif_external(): Failed to open file '%s'.", + cimglist_instance,filename); + return *this; + } + + CImgList& _load_gif_external(const char *const filename, const bool use_graphicsmagick=false) { + 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()); + 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=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\"", + cimg::imagemagick_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + + // Try to read a single frame gif. + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png",filename_tmp._data); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + else { // Try to read animated gif + unsigned int i = 0; + for (bool stop_flag = false; !stop_flag; ++i) { + if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.%u",filename_tmp._data,i); + else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-%u.png",filename_tmp._data,i); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools \newinstance. + static CImgList get_load_gif_external(const char *const filename) { + return CImgList().load_gif_external(filename); + } + + //! Load a gzipped list, using external tool 'gunzip'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Specified filename is (null).", + cimglist_instance); + 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), + *ext2 = cimg::split_filename(body,0); + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=cimg::std_fopen(filename_tmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Failed to open file '%s'.", + cimglist_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load a gzipped list, using external tool 'gunzip' \newinstance. + static CImgList get_load_gzip_external(const char *const filename) { + 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] 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 + nfirst_frame = first_frame::get_load_tiff(filename)); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimglist_instance + "load_tiff(): Invalid specified frame range is [%u,%u] (step %u) since " + "file '%s' contains %u image(s).", + cimglist_instance, + nfirst_frame,nlast_frame,nstep_frame,filename,nb_images); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + assign(1 + (nlast_frame - nfirst_frame)/nstep_frame); + TIFFSetDirectory(tif,0); + cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimglist_instance + "load_tiff(): Failed to open file '%s'.", + cimglist_instance, + filename); + return *this; +#endif + } + + //! Load a multi-page TIFF file \newinstance. + static CImgList 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); + } + + //@} + //---------------------------------- + // + //! \name Data Output + //@{ + //---------------------------------- + + //! Print information about the list on the standard output. + /** + \param title Label set to the information displayed. + \param display_stats Tells if image statistics must be computed and displayed. + **/ + const CImgList& print(const char *const title=0, const bool display_stats=true) const { + unsigned int msiz = 0; + cimglist_for(*this,l) msiz+=_data[l].size(); + msiz*=sizeof(T); + const unsigned int mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U; + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImgList<%s>",pixel_type()); + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = %u/%u [%u %s], %sdata%s = (CImg<%s>*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_allocated_width, + mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end() - 1)); + else std::fprintf(cimg::output(),".\n"); + + char tmp[16] = { 0 }; + cimglist_for(*this,ll) { + cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll); + std::fprintf(cimg::output()," "); + _data[ll].print(tmp,display_stats); + if (ll==3 && width()>8) { ll = width() - 5; std::fprintf(cimg::output()," ...\n"); } + } + std::fflush(cimg::output()); + return *this; + } + + //! Display the current CImgList instance in an existing CImgDisplay window (by reference). + /** + \param disp Reference to an existing CImgDisplay instance, where the current image list will be displayed. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignmenet. + \note This function displays the list images of the current CImgList instance into an existing + CImgDisplay window. + Images of the list are appended in a single temporarly image for visualization purposes. + The function returns immediately. + **/ + const CImgList& display(CImgDisplay &disp, const char axis='x', const float align=0) const { + disp.display(*this,axis,align); + return *this; + } + + //! Display the current CImgList instance in a new display window. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \param axis Alignment axis for images viewing. + \param align Apending alignment. + \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. + 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, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + bool is_exit = false; + return _display(disp,0,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + //! Display the current CImgList instance in a new display window. + /** + \param title Title of the opening display window. + \param display_info Tells if list information must be written on standard output. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \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. + **/ + const CImgList& display(const char *const title=0, const bool display_info=true, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + CImgDisplay disp; + bool is_exit = false; + return _display(disp,title,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + const CImgList& _display(CImgDisplay &disp, const char *const title, const CImgList *const titles, + const bool display_info, const char axis, const float align, unsigned int *const XYZ, + const bool exit_on_anykey, const unsigned int orig, const bool is_first_call, + bool &is_exit) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "display(): Empty instance.", + cimglist_instance); + if (!disp) { + if (axis=='x') { + unsigned int sum_width = 0, max_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + sum_width+=w; + if (h>max_height) max_height = h; + } + disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:titles?titles->__display()._data:0,1); + } else { + unsigned int max_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + sum_height+=h; + } + disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:titles?titles->__display()._data:0,1); + } + if (!title && !titles) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else if (title) disp.set_title("%s",title); + else if (titles) disp.set_title("%s",titles->__display()._data); + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(disp.title()); + disp.show().flush(); + + if (_width==1) { + const unsigned int dw = disp._width, dh = disp._height; + if (!is_first_call) + disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false); + disp.set_title("%s (%ux%ux%ux%u)", + dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum); + _data[0]._display(disp,0,false,XYZ,exit_on_anykey,!is_first_call); + if (disp.key()) is_exit = true; + disp.resize(cimg_fitscreen(dw,dh,1),false).set_title("%s",dtitle.data()); + } else { + bool disp_resize = !is_first_call; + while (!disp.is_closed() && !is_exit) { + const CImg s = _select(disp,0,true,axis,align,exit_on_anykey,orig,disp_resize,!is_first_call,true); + disp_resize = true; + if (s[0]<0 && !disp.wheel()) { // No selections done + if (disp.button()&2) { disp.flush(); break; } + is_exit = true; + } else if (disp.wheel()) { // Zoom in/out + const int wheel = disp.wheel(); + disp.set_wheel(); + if (!is_first_call && wheel<0) break; + if (wheel>0 && _width>=4) { + const unsigned int + delta = std::max(1U,(unsigned int)cimg::round(0.3*_width)), + ind0 = (unsigned int)std::max(0,s[0] - (int)delta), + ind1 = (unsigned int)std::min(width() - 1,s[0] + (int)delta); + if ((ind0!=0 || ind1!=_width - 1) && ind1 - ind0>=3) { + const CImgList sublist = get_shared_images(ind0,ind1); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(ind0,ind1); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + ind0,false,is_exit); + } + } + } else if (s[0]!=0 || s[1]!=width() - 1) { + const CImgList sublist = get_shared_images(s[0],s[1]); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(s[0],s[1]); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + s[0],false,is_exit); + } + disp.set_title("%s",dtitle.data()); + } + } + return *this; + } + + // [internal] Return string to describe display title. + CImg __display() const { + CImg res, str; + cimglist_for(*this,l) { + CImg::string(_data[l]).move_to(str); + if (l!=width() - 1) { + str.resize(str._width + 1,1,1,1,0); + str[str._width - 2] = ','; + str[str._width - 1] = ' '; + } + res.append(str,'x'); + } + if (!res) return CImg(1,1,1,1,0).move_to(res); + cimg::strellipsize(res,128,false); + if (_width>1) { + const unsigned int l = (unsigned int)std::strlen(res); + if (res._width<=l + 16) res.resize(l + 16,1,1,1,0); + cimg_snprintf(res._data + l,16," (#%u)",_width); + } + return res; + } + + //! Save list into a file. + /** + \param filename Filename to write data to. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + **/ + const CImgList& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save(): Specified filename is (null).", + cimglist_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:number>=0?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimglist_save_plugin + cimglist_save_plugin(fn); +#endif +#ifdef cimglist_save_plugin1 + cimglist_save_plugin1(fn); +#endif +#ifdef cimglist_save_plugin2 + cimglist_save_plugin2(fn); +#endif +#ifdef cimglist_save_plugin3 + cimglist_save_plugin3(fn); +#endif +#ifdef cimglist_save_plugin4 + cimglist_save_plugin4(fn); +#endif +#ifdef cimglist_save_plugin5 + cimglist_save_plugin5(fn); +#endif +#ifdef cimglist_save_plugin6 + cimglist_save_plugin6(fn); +#endif +#ifdef cimglist_save_plugin7 + cimglist_save_plugin7(fn); +#endif +#ifdef cimglist_save_plugin8 + cimglist_save_plugin8(fn); +#endif + if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); +#ifdef cimg_use_tiff + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); +#endif + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + else { + if (_width==1) _data[0].save(fn,-1); + else cimglist_for(*this,l) { _data[l].save(fn,is_stdout?-1:l); if (is_stdout) std::fputc(EOF,cimg::_stdout()); } + } + return *this; + } + + //! Tell if an image list can be saved as one single file. + /** + \param filename Filename, as a C-string. + \return \c true if the file format supports multiple images, \c false otherwise. + **/ + static bool is_saveable(const char *const filename) { + const char *const ext = cimg::split_filename(filename); + if (!cimg::strcasecmp(ext,"cimgz") || +#ifdef cimg_use_tiff + !cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff") || +#endif + !cimg::strcasecmp(ext,"yuv") || + !cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return true; + return false; + } + + //! Save image sequence as a GIF animated file. + /** + \param filename Filename to write data to. + \param fps Number of desired frames per second. + \param nb_loops Number of loops (\c 0 for infinite looping). + **/ + const CImgList& save_gif_external(const char *const filename, const float fps=25, + const unsigned int nb_loops=0) { + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + +#ifdef cimg_use_png +#define _cimg_save_gif_ext "png" +#else +#define _cimg_save_gif_ext "ppm" +#endif + + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_ext,filename_tmp._data); + if ((file=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::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_snprintf(command,command._width,"%s -delay %u -loop %u", + 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()); + CImg::string(command).move_to(filenames); + CImg _command = filenames>'x'; + cimg_for(_command,p,char) if (!*p) *p = ' '; + _command.back() = 0; + + cimg::system(_command); + 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'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for_in(*this,1,filenames._width - 1,l) std::remove(filenames[l]); + return *this; + } + + //! Save list as a YUV image sequence file. + /** + \param filename Filename to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(const char *const filename=0, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(0,filename,chroma_subsampling,is_rgb); + } + + //! Save image sequence into a YUV file. + /** + \param file File to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(file,0,chroma_subsampling,is_rgb); + } + + const CImgList& _save_yuv(std::FILE *const file, const char *const filename, + const unsigned int chroma_subsampling, + const bool is_rgb) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified filename is (null).", + cimglist_instance); + if (chroma_subsampling!=420 && chroma_subsampling!=422 && chroma_subsampling!=444) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified chroma subsampling %u is invalid, for file '%s'.", + cimglist_instance, + chroma_subsampling,filename?filename:"(FILE*)"); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + const unsigned int + cfx = chroma_subsampling==420 || chroma_subsampling==422?2:1, + cfy = chroma_subsampling==420?2:1, + w0 = (*this)[0]._width, h0 = (*this)[0]._height, + width0 = w0 + (w0%cfx), height0 = h0 + (h0%cfy); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + cimglist_for(*this,l) { + const CImg &frame = (*this)[l]; + cimg_forZ(frame,z) { + CImg YUV; + if (sizeof(T)==1 && !is_rgb && + frame._width==width0 && frame._height==height0 && frame._depth==1 && frame._spectrum==3) + YUV.assign((unsigned char*)frame._data,width0,height0,1,3,true); + else { + YUV = frame.get_slice(z); + if (YUV._width!=width0 || YUV._height!=height0) YUV.resize(width0,height0,1,-100,0); + if (YUV._spectrum!=3) YUV.resize(-100,-100,1,3,YUV._spectrum==1?1:0); + if (is_rgb) YUV.RGBtoYCbCr(); + } + if (chroma_subsampling==444) + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height*3,nfile); + else { + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height,nfile); + CImg UV = YUV.get_channels(1,2); + UV.resize(UV._width/cfx,UV._height/cfy,1,2,2); + cimg::fwrite(UV._data,(size_t)UV._width*UV._height*2,nfile); + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param filename Filename to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(const char *const filename, const bool is_compressed=false) const { + return _save_cimg(0,filename,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, const bool is_compressed) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "save_cimg(): Unable to save compressed data in file '%s' unless zlib is enabled, " + "saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); +#endif + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little"; + if (std::strstr(ptype,"unsigned")==ptype) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",_width,ptype + 9,etype); + else std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = siz + siz/100 + 16; + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "save_cimg(): Failed to save compressed data for file '%s', saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); + else { + std::fprintf(nfile," #%lu\n",csiz); + cimg::fwrite(cbuf,csiz,nfile); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + if (failed_to_compress) { // Write in a non-compressed way + std::fputc('\n',nfile); + cimg::fwrite(ref._data,ref.size(),nfile); + } + } else std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param file File to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(std::FILE *file, const bool is_compressed=false) const { + return _save_cimg(file,0,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { +#define _cimg_save_cimg_case(Ts,Tss) \ + if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l0) { \ + if (l=W || y0>=H || z0>=D || c0>=D) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const CImg& img = (*this)[l - n0]; \ + const T *ptrs = img._data; \ + const unsigned int \ + x1 = x0 + img._width - 1, \ + y1 = y0 + img._height - 1, \ + z1 = z0 + img._depth - 1, \ + c1 = c0 + img._spectrum - 1, \ + nx1 = x1>=W?W - 1:x1, \ + ny1 = y1>=H?H - 1:y1, \ + nz1 = z1>=D?D - 1:z1, \ + nc1 = c1>=C?C - 1:c1; \ + CImg raw(1 + nx1 - x0); \ + const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int v = 1 + nc1 - c0; v; --v) { \ + const unsigned int skipzb = z0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + nz1 - z0; z; --z) { \ + const unsigned int skipyb = y0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + ny1 - y0; y; --y) { \ + const unsigned int skipxb = x0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + raw.assign(ptrs, raw._width); \ + ptrs+=img._width; \ + if (endian) cimg::invert_endianness(raw._data,raw._width); \ + cimg::fwrite(raw._data,raw._width,nfile); \ + const unsigned int skipxe = (W - 1 - nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "save_cimg(): Empty instance, for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+"); + bool saved = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + const unsigned int lmax = std::min(N,n0 + _width); + _cimg_save_cimg_case("bool",bool); + _cimg_save_cimg_case("unsigned_char",unsigned char); + _cimg_save_cimg_case("uchar",unsigned char); + _cimg_save_cimg_case("char",char); + _cimg_save_cimg_case("unsigned_short",unsigned short); + _cimg_save_cimg_case("ushort",unsigned short); + _cimg_save_cimg_case("short",short); + _cimg_save_cimg_case("unsigned_int",unsigned int); + _cimg_save_cimg_case("uint",unsigned int); + _cimg_save_cimg_case("int",int); + _cimg_save_cimg_case("unsigned_int64",uint64T); + _cimg_save_cimg_case("uint64",uint64T); + _cimg_save_cimg_case("int64",int64T); + _cimg_save_cimg_case("float",float); + _cimg_save_cimg_case("double",double); + if (!saved) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): Unsupported data type '%s' for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)",str_pixeltype._data); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param filename Filename to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(0,filename,n0,x0,y0,z0,c0); + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param file File to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(file,0,n0,x0,y0,z0,c0); + } + + static void _save_empty_cimg(std::FILE *const file, const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) { + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT siz = (ulongT)dx*dy*dz*dc*sizeof(T); + std::fprintf(nfile,"%u %s\n",nb,pixel_type()); + for (unsigned int i=nb; i; --i) { + std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dc); + for (ulongT off = siz; off; --off) std::fputc(0,nfile); + } + if (!file) cimg::fclose(nfile); + } + + //! Save empty (non-compressed) .cimg file with specified dimensions. + /** + \param filename Filename to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc); + } + + //! Save empty .cimg file with specified dimensions. + /** + \param file File to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(file,0,nb,dx,dy,dz,dc); + } + + //! Save list as a TIFF file. + /** + \param filename Filename to write data to. + \param compression_type Compression mode used to write data. + \param voxel_size Voxel size, to be stored in the filename. + \param description Description, to be stored in the filename. + \param use_bigtiff Allow to save big tiff files (>4Gb). + **/ + const CImgList& save_tiff(const char *const filename, const unsigned int compression_type=0, + const float *const voxel_size=0, const char *const description=0, + const bool use_bigtiff=true) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_tiff(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_tiff + if (_width==1) _data[0].save_tiff(filename,compression_type,voxel_size,description,use_bigtiff); + else cimglist_for(*this,l) { + CImg nfilename(1024); + cimg::number_filename(filename,l,6,nfilename); + _data[l].save_tiff(nfilename,compression_type,voxel_size,description,use_bigtiff); + } +#else + ulongT siz = 0; + cimglist_for(*this,l) siz+=_data[l].size(); + const bool _use_bigtiff = use_bigtiff && sizeof(siz)>=8 && siz*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + for (unsigned int dir = 0, l = 0; l<_width; ++l) { + const CImg& img = (*this)[l]; + cimg_forZ(img,z) img._save_tiff(tif,dir++,z,compression_type,voxel_size,description); + } + TIFFClose(tif); + } else + throw CImgIOException(_cimglist_instance + "save_tiff(): Failed to open stream for file '%s'.", + cimglist_instance, + filename); +#endif + return *this; + } + + //! Save list as a gzipped file, using external tool 'gzip'. + /** + \param filename Filename to write data to. + **/ + const CImgList& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Specified filename is (null).", + cimglist_instance); + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimglist_instance, + filename); + else cimg::fclose(file); + std::remove(filename_tmp); + } else { + CImg nfilename(1024); + cimglist_for(*this,l) { + cimg::number_filename(body,l,6,nfilename); + if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext); + _data[l].save_gzip_external(nfilename); + } + } + return *this; + } + + //! Save image sequence, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImgList& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { +#ifndef cimg_use_opencv + cimg::unused(codec,keep_open); + return save_ffmpeg_external(filename,fps); +#else + static CvVideoWriter *writers[32] = { 0 }; + static CImgList 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; + 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; + 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++); + } + } + cvWriteFrame(writers[index],ipl); + } + cvReleaseImage(&ipl); + cimg::mutex(9,0); + } + + cimg::mutex(9); + if (!keep_open) { + cvReleaseVideoWriter(&writers[index]); + writers[index] = 0; + filenames[index].assign(); + sizes(index,0) = sizes(index,1) = 0; + last_used_index = -1; + } else last_used_index = index; + cimg::mutex(9,0); + + return *this; +#endif + } + + //! Save image sequence, using the external tool 'ffmpeg'. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression. + \param bitrate Output bitrate + **/ + const CImgList& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + const char + *const ext = cimg::split_filename(filename), + *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video"; + + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + cimglist_for(*this,l) if (!_data[l].is_sameXYZ(_data[0])) + throw CImgInstanceException(_cimglist_instance + "save_ffmpeg_external(): Invalid instance dimensions for file '%s'.", + cimglist_instance, + filename); + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data); + if ((file=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.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_snprintf(command,command._width,"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\"", + cimg::ffmpeg_path(), + CImg::string(filename_tmp)._system_strescape().data(), + _codec,bitrate,fps, + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_ffmpeg_external(): Failed to save file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for(*this,l) std::remove(filenames[l]); + return *this; + } + + //! Serialize a CImgList instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "get_serialize(): Unable to compress data unless zlib is enabled, " + "storing them uncompressed.", + cimglist_instance); +#endif + CImgList 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::string(tmpstr,false).move_to(stream); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_snprintf(tmpstr,tmpstr._width,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + CImg::string(tmpstr,false).move_to(stream); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = (ulongT)compressBound(siz); + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "get_serialize(): Failed to save compressed data, saving them uncompressed.", + cimglist_instance); + else { + cimg_snprintf(tmpstr,tmpstr._width," #%lu\n",csiz); + CImg::string(tmpstr,false).move_to(stream); + CImg(cbuf,csiz).move_to(stream); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + 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); + } + } else CImg::string("\n",false).move_to(stream); + } + cimglist_apply(stream,unroll)('y'); + return stream>'y'; + } + + //! Unserialize a CImg serialized buffer into a CImgList list. + template + static CImgList get_unserialize(const CImg& buffer) { +#ifdef cimg_use_zlib +#define _cimgz_unserialize_case(Tss) { \ + Bytef *cbuf = 0; \ + if (sizeof(t)!=1 || cimg::type::string()==cimg::type::string()) { \ + cbuf = new Bytef[csiz]; Bytef *_cbuf = cbuf; \ + for (ulongT i = 0; i::get_unserialize(): Unable to unserialize compressed data " \ + "unless zlib is enabled.", \ + pixel_type()); +#endif + +#define _cimg_unserialize_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l::unserialize(): Invalid specified size (%u,%u,%u,%u) for " \ + "image #%u in serialized buffer.", \ + pixel_type(),W,H,D,C,l); \ + if (W*H*D*C>0) { \ + CImg 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 { \ + 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 (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ + } \ + } \ + loaded = true; \ + } + + if (buffer.is_empty()) + 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(); + bool loaded = false, endian = cimg::endianness(), is_bytef = false; + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + uint64T csiz; + int i, err; + cimg::unused(is_bytef); + do { + j = 0; while ((i=(int)*stream)!='\n' && stream::get_unserialize(): CImg header not found in serialized buffer.", + pixel_type()); + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + res.assign(N); + _cimg_unserialize_case("bool",bool); + _cimg_unserialize_case("unsigned_char",unsigned char); + _cimg_unserialize_case("uchar",unsigned char); + _cimg_unserialize_case("char",char); + _cimg_unserialize_case("unsigned_short",unsigned short); + _cimg_unserialize_case("ushort",unsigned short); + _cimg_unserialize_case("short",short); + _cimg_unserialize_case("unsigned_int",unsigned int); + _cimg_unserialize_case("uint",unsigned int); + _cimg_unserialize_case("int",int); + _cimg_unserialize_case("unsigned_int64",uint64T); + _cimg_unserialize_case("uint64",uint64T); + _cimg_unserialize_case("int64",int64T); + _cimg_unserialize_case("float",float); + _cimg_unserialize_case("double",double); + if (!loaded) + throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unsupported pixel type '%s' defined " + "in serialized buffer.", + pixel_type(),str_pixeltype._data); + return res; + } + + //@} + //---------------------------------- + // + //! \name Others + //@{ + //---------------------------------- + + //! Return a CImg pre-defined font with requested height. + /** + \param font_height Height of the desired font (exact match for 13,23,53,103). + \param is_variable_width Decide if the font has a variable (\c true) or fixed (\c false) width. + **/ + static const CImgList& 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. + CImg &basef = font_base[ind]; + if (!basef) { + basef.assign(256*font_width[ind],font_height[ind]); + + 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). + 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 }; + ind = ~0U; + for (int i = 0; i<16; ++i) + 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 + fonts->assign(); + std::memmove(fonts,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 + } + CImgList &font = fonts[ind]; + + // Render requested font. + if (!font) { + const unsigned int padding_x = requested_height<=64?1U:requested_height<=128?2U:3U; + is_variable_widths[ind] = is_variable_width; + font = basef.get_split('x',256); + 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, + font[0]._height>requested_height?2: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_forXY(letter,x,y) if (letter(x,y)) { if (xxmax) xmax = x; } + if (xmin<=xmax) letter.crop(xmin,0,xmax,letter._height - 1); + } + font[' '].resize(font['f']._width,-100,-100,-100,0); + if (' ' + 256& FFT(const char axis, const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],axis,invert); + return *this; + } + + //! Compute a 1-D Fast Fourier Transform, along specified axis \newinstance. + CImgList get_FFT(const char axis, const bool invert=false) const { + return CImgList(*this,false).FFT(axis,invert); + } + + //! Compute a n-d Fast Fourier Transform. + /** + \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed. + **/ + CImgList& FFT(const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],invert); + return *this; + } + + //! Compute a 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. + /** + **/ + CImgList& reverse_object3d() { + cimglist_for(*this,l) { + CImg& p = _data[l]; + switch (p.size()) { + case 2 : case 3: cimg::swap(p[0],p[1]); break; + case 6 : cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break; + case 9 : cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break; + case 4 : cimg::swap(p[0],p[1],p[2],p[3]); break; + case 12 : cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break; + } + } + return *this; + } + + //! Reverse primitives orientations of a 3D object \newinstance. + CImgList get_reverse_object3d() const { + return (+*this).reverse_object3d(); + } + + //@} + }; // struct CImgList { ... + + /* + #--------------------------------------------- + # + # Completion of previously declared functions + # + #---------------------------------------------- + */ + +namespace cimg { + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + // (throw a CImgIOException when macro 'cimg_use_r' is defined). + inline FILE* _stdin(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdin; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdin(): Reference to 'stdin' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stdout(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdout; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdout(): Reference to 'stdout' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stderr(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stderr; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stderr(): Reference to 'stderr' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + // Open a file (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 + // Try alternative method, with wide-character string. + int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0); + if (err) { + CImg wpath(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(err); + if (MultiByteToWideChar(CP_UTF8,0,mode,-1,wmode,err)) + return _wfopen(wpath,wmode); + } + } + } +#endif + return 0; + } + + //! 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 + _cimg_test_temporary_path("/tmp"); + _cimg_test_temporary_path("/var/tmp"); +#endif + if (!path_found) { + *s_path = 0; + std::strncpy(tmp,filename_tmp,tmp._width - 1); + if ((file=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"); + } + } + 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). +#if !defined(__INTEL_COMPILER) + if (!SHGetSpecialFolderPathA(0,s_path,0x0026,false)) { + const char *const pfPath = std::getenv("PROGRAMFILES"); + if (pfPath) std::strncpy(s_path,pfPath,MAX_PATH - 1); + else std::strcpy(s_path,"C:\\PROGRA~1"); + } +#else + std::strcpy(s_path,"C:\\PROGRA~1"); +#endif + } + cimg::mutex(7,0); + return s_path; + } +#endif + + //! Get/set path to the ImageMagick's \c convert binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c convert binary. + **/ + inline const char* imagemagick_path(const char *const user_path, const bool reinit_path) { + static CImg 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=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 + 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 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 + const char *const pf_path = 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\\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\\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\\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,"%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,"%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,"%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:\\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:\\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:\\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,"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,"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,"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:\\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:\\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:\\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:\\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 + 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); + } + 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; +#if cimg_OS==2 + const char *const pf_path = 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,"./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 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=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 \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=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,"./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 \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=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,"./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); + } + 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=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 \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=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,"./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); + } + cimg::mutex(7,0); + return s_path; + } + + //! Get/set path to the \c curl binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c curl binary. + **/ + inline const char *curl_path(const char *const user_path, const bool reinit_path) { + static CImg s_path; + cimg::mutex(7); + if (reinit_path) s_path.assign(); + if (user_path) { + if (!s_path) s_path.assign(1024); + std::strncpy(s_path,user_path,1023); + } else if (!s_path) { + s_path.assign(1024); + bool path_found = false; + std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + std::strcpy(s_path,".\\curl.exe"); + if ((file=cimg::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; } + } + if (!path_found) std::strcpy(s_path,"curl"); +#endif + winformat_string(s_path); + } + cimg::mutex(7,0); + return s_path; + } + + // [internal] Sorting function, used by cimg::files(). + inline int _sort_files(const void* a, const void* b) { + const CImg &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 +#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); + } + } + } 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); +#endif + + // Sort resulting list by lexicographic order. + if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg),_sort_files); + + return res; + } + + //! Try to guess format from an image file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + \return C-string containing the guessed file format, or \c 0 if nothing has been guessed. + **/ + inline const char *ftype(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::ftype(): Specified filename is (null)."); + static const char + *const _pnm = "pnm", + *const _pfm = "pfm", + *const _bmp = "bmp", + *const _gif = "gif", + *const _jpg = "jpg", + *const _off = "off", + *const _pan = "pan", + *const _png = "png", + *const _tif = "tif", + *const _inr = "inr", + *const _dcm = "dcm"; + const char *f_type = 0; + CImg 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; } + } + f_type = 0; break; + } + } + } 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)."); + + 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); +#endif + + CImg command((unsigned int)std::strlen(url) + 64); + cimg::unused(try_fallback); + + // Try with 'curl' first. + if (timeout) { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,timeout,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } else { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } 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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::std_fopen(filename_local,"rb"))) + throw CImgIOException("cimg::load_network(): Failed to load file '%s' with external commands " + "'wget' or 'curl'.",url); + cimg::fclose(file); + + // Try gunzip it. + cimg_snprintf(command,command._width,"%s.gz",filename_local); + std::rename(filename_local,command); + cimg_snprintf(command,command._width,"%s --quiet \"%s.gz\"", + gunzip_path(),filename_local); + cimg::system(command); + file = 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"); + } + } + 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.), + 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; + } + + // 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 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."); +#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; +#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); + } + + // End of cimg:: namespace +} + + // End of cimg_library:: namespace +} + +//! Short alias name. +namespace cil = cimg_library_suffixed; + +#ifdef _cimg_redefine_False +#define False 0 +#endif +#ifdef _cimg_redefine_True +#define True 1 +#endif +#ifdef _cimg_redefine_min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_PI +#define PI 3.141592653589793238462643383 +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif +// Local Variables: +// mode: c++ +// End: diff --git a/tutorial_2/CMakeLists.txt b/tutorial_2/CMakeLists.txt new file mode 100644 index 0000000..3a954d7 --- /dev/null +++ b/tutorial_2/CMakeLists.txt @@ -0,0 +1,42 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# sets versions and names of project +cmake_minimum_required(VERSION 3.7) +project(opencl_tutorial_1) + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) +# specify our compiled binaries name +set(BINARY_NAME tut2) +# specify all our cpp files names +set(CPP_FILES tut2.cpp utils.h CImage.h) + +# build our project executable, and link opencl lib +find_package(OpenCL 2 REQUIRED) +add_executable(${BINARY_NAME} ${CPP_FILES}) +target_link_libraries(tut2 OpenCL::OpenCL) + +# copy kernel files over to binary directory so it can access them +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/kernels/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/kernels/) diff --git a/tutorial_2/Dockerfile b/tutorial_2/Dockerfile new file mode 100644 index 0000000..75b2ad6 --- /dev/null +++ b/tutorial_2/Dockerfile @@ -0,0 +1,36 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +FROM nvidia/opencl + +# setting up the place, making sure everything is installed, and getting the +# pesky CL/cl2.hpp +RUN mkdir -p /opencl_tutorials && \ + apt update -y && \ + apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev && \ + git clone https://github.com/KhronosGroup/opencl-clhpp && \ + cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ + rm -r /opencl-clhpp && \ + echo "cd /opencl_tutorials" >> /root/.bashrc + +COPY . /opencl_tutorials/ diff --git a/tutorial_2/Tutorial 2.vcxproj b/tutorial_2/Tutorial 2.vcxproj deleted file mode 100644 index db3f98a..0000000 --- a/tutorial_2/Tutorial 2.vcxproj +++ /dev/null @@ -1,197 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - Debug - x64 - - - Release - x64 - - - - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F} - Tutorial 1 - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - Unicode - true - - - Application - true - v142 - Unicode - - - Application - false - v142 - Unicode - true - - - - - - - - - - - - - - - - - false - - - true - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - - 0 - - - $(INTELOCLSDKROOT)include;%(AdditionalIncludeDirectories) - Win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x86;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - If exist "*.cl" copy "*.cl" "$(OutDir)\" - - - - - 0 - - - $(INTELOCLSDKROOT)include;.\Graphics\include\win32;.\Graphics\lodepng;%(AdditionalIncludeDirectories) - Win32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x86;.\Graphics\lib\win32\glut;%(AdditionalLibraryDirectories) - OpenCL.lib;glut32.lib;%(AdditionalDependencies) - true - - - If exist "*.cl" copy "*.cl" "$(OutDir)\" - - - - - 0 - - - $(INTELOCLSDKROOT)include;..\include;%(AdditionalIncludeDirectories) - __x86_64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MaxSpeed - false - Default - MultiThreadedDLL - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - true - true - - - xcopy /y "..\images\*" "$(ProjectDir)" - - - - - 0 - - - $(INTELOCLSDKROOT)include;..\include;%(AdditionalIncludeDirectories) - __x86_64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - Disabled - false - EnableFastChecks - MultiThreadedDebugDLL - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - xcopy /y "..\images\*" "$(ProjectDir)" -xcopy /s /i /y "kernels" "$(OutDir)kernels" -xcopy /y "..\images\*" "$(OutDir)" - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tutorial_2/Tutorial 2.vcxproj.filters b/tutorial_2/Tutorial 2.vcxproj.filters deleted file mode 100644 index 6d37d7c..0000000 --- a/tutorial_2/Tutorial 2.vcxproj.filters +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - {bddc8ef0-f6c2-4509-accd-01b89c7b41b1} - - - {533d906d-5db8-4839-8b76-d22542cb0f52} - - - - - kernels - - - - - include - - - include - - - \ No newline at end of file diff --git a/tutorial_2/Tutorial 2.cpp b/tutorial_2/tut2.cpp similarity index 100% rename from tutorial_2/Tutorial 2.cpp rename to tutorial_2/tut2.cpp diff --git a/tutorial_2/utils.h b/tutorial_2/utils.h new file mode 100644 index 0000000..1539094 --- /dev/null +++ b/tutorial_2/utils.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define CL_HPP_MINIMUM_OPENCL_VERSION 120 +#define CL_HPP_TARGET_OPENCL_VERSION 120 +#define CL_HPP_ENABLE_EXCEPTIONS + +#include + +using namespace std; + +template +ostream& operator<< (ostream& out, const vector& v) { + if (!v.empty()) { + out << '['; + copy(v.begin(), v.end(), ostream_iterator(out, ", ")); + out << "\b\b]"; + } + return out; +} + +string GetPlatformName(int platform_id) { + vector platforms; + cl::Platform::get(&platforms); + return platforms[platform_id].getInfo(); +} + +string GetDeviceName(int platform_id, int device_id) { + vector platforms; + cl::Platform::get(&platforms); + vector devices; + platforms[platform_id].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + return devices[device_id].getInfo(); +} + +const char *getErrorString(cl_int error) { + switch (error){ + // run-time and JIT compiler errors + case 0: return "CL_SUCCESS"; + case -1: return "CL_DEVICE_NOT_FOUND"; + case -2: return "CL_DEVICE_NOT_AVAILABLE"; + case -3: return "CL_COMPILER_NOT_AVAILABLE"; + case -4: return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; + case -5: return "CL_OUT_OF_RESOURCES"; + case -6: return "CL_OUT_OF_HOST_MEMORY"; + case -7: return "CL_PROFILING_INFO_NOT_AVAILABLE"; + case -8: return "CL_MEM_COPY_OVERLAP"; + case -9: return "CL_IMAGE_FORMAT_MISMATCH"; + case -10: return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; + case -11: return "CL_BUILD_PROGRAM_FAILURE"; + case -12: return "CL_MAP_FAILURE"; + case -13: return "CL_MISALIGNED_SUB_BUFFER_OFFSET"; + case -14: return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST"; + case -15: return "CL_COMPILE_PROGRAM_FAILURE"; + case -16: return "CL_LINKER_NOT_AVAILABLE"; + case -17: return "CL_LINK_PROGRAM_FAILURE"; + case -18: return "CL_DEVICE_PARTITION_FAILED"; + case -19: return "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; + + // compile-time errors + case -30: return "CL_INVALID_VALUE"; + case -31: return "CL_INVALID_DEVICE_TYPE"; + case -32: return "CL_INVALID_PLATFORM"; + case -33: return "CL_INVALID_DEVICE"; + case -34: return "CL_INVALID_CONTEXT"; + case -35: return "CL_INVALID_QUEUE_PROPERTIES"; + case -36: return "CL_INVALID_COMMAND_QUEUE"; + case -37: return "CL_INVALID_HOST_PTR"; + case -38: return "CL_INVALID_MEM_OBJECT"; + case -39: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; + case -40: return "CL_INVALID_IMAGE_SIZE"; + case -41: return "CL_INVALID_SAMPLER"; + case -42: return "CL_INVALID_BINARY"; + case -43: return "CL_INVALID_BUILD_OPTIONS"; + case -44: return "CL_INVALID_PROGRAM"; + case -45: return "CL_INVALID_PROGRAM_EXECUTABLE"; + case -46: return "CL_INVALID_KERNEL_NAME"; + case -47: return "CL_INVALID_KERNEL_DEFINITION"; + case -48: return "CL_INVALID_KERNEL"; + case -49: return "CL_INVALID_ARG_INDEX"; + case -50: return "CL_INVALID_ARG_VALUE"; + case -51: return "CL_INVALID_ARG_SIZE"; + case -52: return "CL_INVALID_KERNEL_ARGS"; + case -53: return "CL_INVALID_WORK_DIMENSION"; + case -54: return "CL_INVALID_WORK_GROUP_SIZE"; + case -55: return "CL_INVALID_WORK_ITEM_SIZE"; + case -56: return "CL_INVALID_GLOBAL_OFFSET"; + case -57: return "CL_INVALID_EVENT_WAIT_LIST"; + case -58: return "CL_INVALID_EVENT"; + case -59: return "CL_INVALID_OPERATION"; + case -60: return "CL_INVALID_GL_OBJECT"; + case -61: return "CL_INVALID_BUFFER_SIZE"; + case -62: return "CL_INVALID_MIP_LEVEL"; + case -63: return "CL_INVALID_GLOBAL_WORK_SIZE"; + case -64: return "CL_INVALID_PROPERTY"; + case -65: return "CL_INVALID_IMAGE_DESCRIPTOR"; + case -66: return "CL_INVALID_COMPILER_OPTIONS"; + case -67: return "CL_INVALID_LINKER_OPTIONS"; + case -68: return "CL_INVALID_DEVICE_PARTITION_COUNT"; + + // extension errors + case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; + case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; + case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; + case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; + case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; + case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; + default: return "Unknown OpenCL error"; + } +} + +void CheckError(cl_int error) { + if (error != CL_SUCCESS) { + cerr << "OpenCL call failed with error " << getErrorString(error) << endl; + exit(1); + } +} + +void AddSources(cl::Program::Sources& sources, const string& file_name) { + //TODO: add file existence check + ifstream file(file_name); + string* source_code = new string(istreambuf_iterator(file), (istreambuf_iterator())); + sources.push_back((*source_code).c_str()); +} + +string ListPlatformsDevices() { + + stringstream sstream; + vector platforms; + + cl::Platform::get(&platforms); + + sstream << "Found " << platforms.size() << " platform(s):" << endl; + + for (unsigned int i = 0; i < platforms.size(); i++) + { + sstream << "\nPlatform " << i << ", " << platforms[i].getInfo() << ", version: " << platforms[i].getInfo(); + + sstream << ", vendor: " << platforms[i].getInfo() << endl; + // sstream << ", extensions: " << platforms[i].getInfo() << endl; + + vector devices; + + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + sstream << "\n Found " << devices.size() << " device(s):" << endl; + + for (unsigned int j = 0; j < devices.size(); j++) + { + sstream << "\n Device " << j << ", " << devices[j].getInfo() << ", version: " << devices[j].getInfo(); + + sstream << ", vendor: " << devices[j].getInfo(); + cl_device_type device_type = devices[j].getInfo(); + sstream << ", type: "; + if (device_type & CL_DEVICE_TYPE_DEFAULT) + sstream << "DEFAULT "; + if (device_type & CL_DEVICE_TYPE_CPU) + sstream << "CPU "; + if (device_type & CL_DEVICE_TYPE_GPU) + sstream << "GPU "; + if (device_type & CL_DEVICE_TYPE_ACCELERATOR) + sstream << "ACCELERATOR "; + sstream << ", compute units: " << devices[j].getInfo(); + sstream << ", clock freq [MHz]: " << devices[j].getInfo(); + sstream << ", max memory size [B]: " << devices[j].getInfo(); + sstream << ", max allocatable memory [B]: " << devices[j].getInfo(); + + sstream << endl; + } + } + sstream << "----------------------------------------------------------------" << endl; + + return sstream.str(); +} + +cl::Context GetContext(int platform_id, int device_id) { + vector platforms; + + cl::Platform::get(&platforms); + + for (unsigned int i = 0; i < platforms.size(); i++) + { + vector devices; + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + for (unsigned int j = 0; j < devices.size(); j++) + { + if ((i == platform_id) && (j == device_id)) + return cl::Context({ devices[j] }); + } + } + + return cl::Context(); +} + +enum ProfilingResolution { + PROF_NS = 1, + PROF_US = 1000, + PROF_MS = 1000000, + PROF_S = 1000000000 +}; + +string GetFullProfilingInfo(const cl::Event& evnt, ProfilingResolution resolution) { + stringstream sstream; + + sstream << "Queued " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Submitted " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Executed " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Total " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + + switch (resolution) { + case PROF_NS: sstream << " [ns]"; break; + case PROF_US: sstream << " [us]"; break; + case PROF_MS: sstream << " [ms]"; break; + case PROF_S: sstream << " [s]"; break; + default: break; + } + + return sstream.str(); +} From a8e187d9a7983d1c3242a810ac703c313e9859b0 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 11 Feb 2020 12:29:15 +0000 Subject: [PATCH 41/69] Working cmake for tut2 linux --- tutorial_2/CMakeLists.txt | 9 +++++++-- tutorial_2/tut2.cpp | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tutorial_2/CMakeLists.txt b/tutorial_2/CMakeLists.txt index 3a954d7..bfb9663 100644 --- a/tutorial_2/CMakeLists.txt +++ b/tutorial_2/CMakeLists.txt @@ -31,12 +31,17 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # specify our compiled binaries name set(BINARY_NAME tut2) # specify all our cpp files names -set(CPP_FILES tut2.cpp utils.h CImage.h) +set(CPP_FILES tut2.cpp utils.h CImg.h) # build our project executable, and link opencl lib find_package(OpenCL 2 REQUIRED) +# find x11 for linux desktop +find_package(X11 REQUIRED) +# get thread library used in the tutorials +find_package(Threads REQUIRED) + add_executable(${BINARY_NAME} ${CPP_FILES}) -target_link_libraries(tut2 OpenCL::OpenCL) +target_link_libraries(tut2 OpenCL::OpenCL ${X11_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) # copy kernel files over to binary directory so it can access them file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/kernels/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/kernels/) diff --git a/tutorial_2/tut2.cpp b/tutorial_2/tut2.cpp index 3475b7a..61a6c3e 100644 --- a/tutorial_2/tut2.cpp +++ b/tutorial_2/tut2.cpp @@ -1,7 +1,7 @@ #include #include -#include "Utils.h" +#include "utils.h" #include "CImg.h" using namespace cimg_library; @@ -60,7 +60,7 @@ int main(int argc, char **argv) { cl::Program program(context, sources); //build and debug the kernel code - try { + try { program.build(); } catch (const cl::Error& err) { @@ -100,7 +100,7 @@ int main(int argc, char **argv) { && !disp_input.is_keyESC() && !disp_output.is_keyESC()) { disp_input.wait(1); disp_output.wait(1); - } + } } catch (const cl::Error& err) { From bb4466147b65c4bdfaa65af061af8596c821c612 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 11 Feb 2020 12:38:15 +0000 Subject: [PATCH 42/69] Changed kernel copy to configure_file This ensures it is always up to date --- tutorial_1/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial_1/CMakeLists.txt b/tutorial_1/CMakeLists.txt index d03f26b..6773cf8 100644 --- a/tutorial_1/CMakeLists.txt +++ b/tutorial_1/CMakeLists.txt @@ -39,4 +39,5 @@ add_executable(${BINARY_NAME} ${CPP_FILES}) target_link_libraries(tut1 OpenCL::OpenCL) # copy kernel files over to binary directory so it can access them -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/kernels/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/kernels/) +# file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/kernels/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/kernels/) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kernels/my_kernels.cl ${CMAKE_CURRENT_BINARY_DIR}/kernels/my_kernels.cl COPYONLY) From fe967ea8a4520f4c8f837e41b07307bdb8777abe Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 11 Feb 2020 12:41:47 +0000 Subject: [PATCH 43/69] Working minimal cmake for linux --- tutorial_2/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial_2/CMakeLists.txt b/tutorial_2/CMakeLists.txt index bfb9663..2f4073d 100644 --- a/tutorial_2/CMakeLists.txt +++ b/tutorial_2/CMakeLists.txt @@ -44,4 +44,5 @@ add_executable(${BINARY_NAME} ${CPP_FILES}) target_link_libraries(tut2 OpenCL::OpenCL ${X11_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) # copy kernel files over to binary directory so it can access them -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/kernels/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/kernels/) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kernels/my_kernels.cl ${CMAKE_CURRENT_BINARY_DIR}/kernels/my_kernels.cl COPYONLY) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/test.ppm ${CMAKE_CURRENT_BINARY_DIR}/test.ppm COPYONLY) From 4ae49330edf80eba39deabb97a5e05ac20a6fa31 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 11 Feb 2020 13:12:26 +0000 Subject: [PATCH 44/69] Saving current state during tutorial --- tutorial_2/kernels/my_kernels.cl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tutorial_2/kernels/my_kernels.cl b/tutorial_2/kernels/my_kernels.cl index 2705acc..993625f 100644 --- a/tutorial_2/kernels/my_kernels.cl +++ b/tutorial_2/kernels/my_kernels.cl @@ -1,6 +1,12 @@ //a simple OpenCL kernel which copies all pixels from A to B kernel void identity(global const uchar* A, global uchar* B) { int id = get_global_id(0); + // printf("work item id = %d\n", id); + // if (id == 0) { + // printf("work group size %d\n", get_local_size(0)); + // } + // int loc_id = get_local_id(0); + // printf("global id = %d, local id = %d\n", id, loc_id); B[id] = A[id]; } @@ -45,7 +51,7 @@ kernel void avg_filterND(global const uchar* A, global uchar* B) { uint result = 0; for (int i = (x-1); i <= (x+1); i++) - for (int j = (y-1); j <= (y+1); j++) + for (int j = (y-1); j <= (y+1); j++) result += A[i + j*width + c*image_size]; result /= 9; @@ -69,8 +75,8 @@ kernel void convolutionND(global const uchar* A, global uchar* B, constant float float result = 0; for (int i = (x-1); i <= (x+1); i++) - for (int j = (y-1); j <= (y+1); j++) + for (int j = (y-1); j <= (y+1); j++) result += A[i + j*width + c*image_size]*mask[i-(x-1) + j-(y-1)]; B[id] = (uchar)result; -} \ No newline at end of file +} From 6e77c9e197a150048cb9ec2b503db5e7d8c9d4d6 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 17:59:40 +0000 Subject: [PATCH 45/69] Added working CMake for tutorial 3 The docker file has yet to be checked, but to make it work we will probably need to remove the X11/ display usage in the tutorials as we prefer to save to file rather than instant popup. --- tutorial_3/.dockerignore | 5 + tutorial_3/CImg.h | 62163 ++++++++++++++++++++++ tutorial_3/CMakeLists.txt | 48 + tutorial_3/Dockerfile | 36 + tutorial_3/Tutorial 3.vcxproj | 194 - tutorial_3/Tutorial 3.vcxproj.filters | 24 - tutorial_3/build.sh | 38 + tutorial_3/{Tutorial 3.cpp => tut3.cpp} | 14 +- tutorial_3/utils.h | 225 + 9 files changed, 62527 insertions(+), 220 deletions(-) create mode 100644 tutorial_3/.dockerignore create mode 100644 tutorial_3/CImg.h create mode 100644 tutorial_3/CMakeLists.txt create mode 100644 tutorial_3/Dockerfile delete mode 100644 tutorial_3/Tutorial 3.vcxproj delete mode 100644 tutorial_3/Tutorial 3.vcxproj.filters create mode 100755 tutorial_3/build.sh rename tutorial_3/{Tutorial 3.cpp => tut3.cpp} (94%) create mode 100644 tutorial_3/utils.h diff --git a/tutorial_3/.dockerignore b/tutorial_3/.dockerignore new file mode 100644 index 0000000..9eca48d --- /dev/null +++ b/tutorial_3/.dockerignore @@ -0,0 +1,5 @@ +# ignore everything using whitelist strategy +# * +build/ +# you can selectiveley allow files using the ! at the beginning of the line +#!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/tutorial_3/CImg.h b/tutorial_3/CImg.h new file mode 100644 index 0000000..d9d2a21 --- /dev/null +++ b/tutorial_3/CImg.h @@ -0,0 +1,62163 @@ +/* + # + # File : CImg.h + # ( C++ header file ) + # + # Description : The C++ Template Image Processing Toolkit. + # This file is the main component of the CImg Library project. + # ( http://cimg.eu ) + # + # Project manager : David Tschumperle. + # ( http://tschumperle.users.greyc.fr/ ) + # + # A complete list of contributors is available in file 'README.txt' + # distributed within the CImg package. + # + # Licenses : This file is 'dual-licensed', you have to choose one + # of the two licenses below to apply. + # + # CeCILL-C + # The CeCILL-C license is close to the GNU LGPL. + # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html ) + # + # or CeCILL v2.1 + # The CeCILL license is compatible with the GNU GPL. + # ( http://www.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". + # + # As a counterpart to the access to the source code and rights to copy, + # modify and redistribute granted by the license, users are provided only + # with a limited warranty and the software's author, the holder of the + # economic rights, and the successive licensors have only limited + # liability. + # + # In this respect, the user's attention is drawn to the risks associated + # with loading, using, modifying and/or developing or reproducing the + # software by the user in light of its specific status of free software, + # that may mean that it is complicated to manipulate, and that also + # therefore means that it is reserved for developers and experienced + # professionals having in-depth computer knowledge. Users are therefore + # encouraged to load and test the software's suitability as regards their + # requirements in conditions enabling the security of their systems and/or + # data to be ensured and, more generally, to use and operate it in the + # same conditions as regards security. + # + # The fact that you are presently reading this means that you have had + # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms. + # +*/ + +// Set version number of the library. +#ifndef cimg_version +#define cimg_version 250 + +/*----------------------------------------------------------- + # + # Test and possibly auto-set CImg configuration variables + # and include required headers. + # + # If you find that the default configuration variables are + # not adapted to your system, you can override their values + # before including the header file "CImg.h" + # (use the #define directive). + # + ------------------------------------------------------------*/ + +// Include standard C++ headers. +// This is the minimal set of required headers to make CImg-based codes compile. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Detect/configure OS variables. +// +// Define 'cimg_OS' to: '0' for an unknown OS (will try to minize library dependencies). +// '1' for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...). +// '2' for Microsoft Windows. +// (auto-detection is performed if 'cimg_OS' is not set by the user). +#ifndef cimg_OS +#if defined(unix) || defined(__unix) || defined(__unix__) \ + || defined(linux) || defined(__linux) || defined(__linux__) \ + || defined(sun) || defined(__sun) \ + || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__FreeBSD__) || defined (__DragonFly__) \ + || defined(sgi) || defined(__sgi) \ + || defined(__MACOSX__) || defined(__APPLE__) \ + || defined(__CYGWIN__) +#define cimg_OS 1 +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \ + || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) +#define cimg_OS 2 +#else +#define cimg_OS 0 +#endif +#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2) +#error CImg Library: Invalid configuration variable 'cimg_OS'. +#error (correct values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows'). +#endif +#ifndef cimg_date +#define cimg_date __DATE__ +#endif +#ifndef cimg_time +#define cimg_time __TIME__ +#endif + +// Disable silly warnings on some Microsoft VC++ compilers. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) +#pragma warning(disable:4244) +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4319) +#pragma warning(disable:4512) +#pragma warning(disable:4571) +#pragma warning(disable:4640) +#pragma warning(disable:4706) +#pragma warning(disable:4710) +#pragma warning(disable:4800) +#pragma warning(disable:4804) +#pragma warning(disable:4820) +#pragma warning(disable:4996) + +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#endif +#endif + +// Define correct string functions for each compiler and OS. +#if cimg_OS==2 && defined(_MSC_VER) +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#include +#if defined(__MACOSX__) || defined(__APPLE__) +#define cimg_sscanf cimg::_sscanf +#define cimg_sprintf cimg::_sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf snprintf +#define cimg_vsnprintf vsnprintf +#endif +#endif + +// Include OS-specific headers. +#if cimg_OS==1 +#include +#include +#include +#include +#include +#include +#elif cimg_OS==2 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#ifndef _WIN32_IE +#define _WIN32_IE 0x0400 +#endif +#include +#include +#include +#endif + +// Look for C++11 features. +#ifndef cimg_use_cpp11 +#if __cplusplus>201100 +#define cimg_use_cpp11 1 +#else +#define cimg_use_cpp11 0 +#endif +#endif +#if cimg_use_cpp11==1 +#include +#include +#endif + +// Convenient macro to define pragma +#ifdef _MSC_VER +#define cimg_pragma(x) __pragma(x) +#else +#define cimg_pragma(x) _Pragma(#x) +#endif + +// Define own types 'cimg_long/ulong' and 'cimg_int64/uint64' to ensure portability. +// ( constrained to 'sizeof(cimg_ulong/cimg_long) = sizeof(void*)' and 'sizeof(cimg_int64/cimg_uint64)=8' ). +#if cimg_OS==2 + +#define cimg_uint64 unsigned __int64 +#define cimg_int64 __int64 +#define cimg_ulong UINT_PTR +#define cimg_long INT_PTR +#ifdef _MSC_VER +#define cimg_fuint64 "%I64u" +#define cimg_fint64 "%I64d" +#else +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#endif + +#else + +#if UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX)) +#define cimg_uint64 unsigned long long +#define cimg_int64 long long +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#else +#define cimg_uint64 unsigned long +#define cimg_int64 long +#define cimg_fuint64 "%lu" +#define cimg_fint64 "%ld" +#endif + +#if defined(__arm__) || defined(_M_ARM) +#define cimg_ulong unsigned long long +#define cimg_long long long +#else +#define cimg_ulong unsigned long +#define cimg_long long +#endif + +#endif + +// Configure filename separator. +// +// Filename separator is set by default to '/', except for Windows where it is '\'. +#ifndef cimg_file_separator +#if cimg_OS==2 +#define cimg_file_separator '\\' +#else +#define cimg_file_separator '/' +#endif +#endif + +// Configure verbosity of output messages. +// +// Define 'cimg_verbosity' to: '0' to hide library messages (quiet mode). +// '1' to output library messages on the console. +// '2' to output library messages on a basic dialog window (default behavior). +// '3' to do as '1' + add extra warnings (may slow down the code!). +// '4' to do as '2' + add extra warnings (may slow down the code!). +// +// Define 'cimg_strict_warnings' to replace warning messages by exception throwns. +// +// Define 'cimg_use_vt100' to allow output of color messages on VT100-compatible terminals. +#ifndef cimg_verbosity +#if cimg_OS==2 +#define cimg_verbosity 2 +#else +#define cimg_verbosity 1 +#endif +#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4) +#error CImg Library: Configuration variable 'cimg_verbosity' is badly defined. +#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }). +#endif + +// Configure display framework. +// +// Define 'cimg_display' to: '0' to disable display capabilities. +// '1' to use the X-Window framework (X11). +// '2' to use the Microsoft GDI32 framework. +#ifndef cimg_display +#if cimg_OS==0 +#define cimg_display 0 +#elif cimg_OS==1 +#define cimg_display 1 +#elif cimg_OS==2 +#define cimg_display 2 +#endif +#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2) +#error CImg Library: Configuration variable 'cimg_display' is badly defined. +#error (should be { 0=none | 1=X-Window (X11) | 2=Microsoft GDI32 }). +#endif + +// Configure the 'abort' signal handler (does nothing by default). +// A typical signal handler can be defined in your own source like this: +// #define cimg_abort_test if (is_abort) throw CImgAbortException("") +// +// where 'is_abort' is a boolean variable defined somewhere in your code and reachable in the method. +// 'cimg_abort_test2' does the same but is called more often (in inner loops). +#if defined(cimg_abort_test) && defined(cimg_use_openmp) + +// Define abort macros to be used with OpenMP. +#ifndef _cimg_abort_init_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 + +// Include display-specific headers. +#if cimg_display==1 +#include +#include +#include +#include +#ifdef cimg_use_xshm +#include +#include +#include +#endif +#ifdef cimg_use_xrandr +#include +#endif +#endif +#ifndef cimg_appname +#define cimg_appname "CImg" +#endif + +// Configure OpenMP support. +// (http://www.openmp.org) +// +// Define 'cimg_use_openmp' to enable OpenMP support (requires OpenMP 3.0+). +// +// OpenMP directives are used in many 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/) +// +// Define 'cimg_use_opencv' to enable OpenCV support. +// +// OpenCV library may be used to access images from cameras +// (see method 'CImg::load_camera()'). +#ifdef cimg_use_opencv +#ifdef True +#undef True +#define _cimg_redefine_True +#endif +#ifdef False +#undef False +#define _cimg_redefine_False +#endif +#include +#include "cv.h" +#include "highgui.h" +#endif + +// Configure LibPNG support. +// (http://www.libpng.org) +// +// Define 'cimg_use_png' to enable LibPNG support. +// +// PNG library may be used to get a native support of '.png' files. +// (see methods 'CImg::{load,save}_png()'. +#ifdef cimg_use_png +extern "C" { +#include "png.h" +} +#endif + +// Configure LibJPEG support. +// (http://en.wikipedia.org/wiki/Libjpeg) +// +// Define 'cimg_use_jpeg' to enable LibJPEG support. +// +// JPEG library may be used to get a native support of '.jpg' files. +// (see methods 'CImg::{load,save}_jpeg()'). +#ifdef cimg_use_jpeg +extern "C" { +#include "jpeglib.h" +#include "setjmp.h" +} +#endif + +// Configure LibTIFF support. +// (http://www.libtiff.org) +// +// Define 'cimg_use_tiff' to enable LibTIFF support. +// +// TIFF library may be used to get a native support of '.tif' files. +// (see methods 'CImg[List]::{load,save}_tiff()'). +#ifdef cimg_use_tiff +extern "C" { +#define uint64 uint64_hack_ +#define int64 int64_hack_ +#include "tiffio.h" +#undef uint64 +#undef int64 +} +#endif + +// Configure LibMINC2 support. +// (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference) +// +// Define 'cimg_use_minc2' to enable LibMINC2 support. +// +// MINC2 library may be used to get a native support of '.mnc' files. +// (see methods 'CImg::{load,save}_minc2()'). +#ifdef cimg_use_minc2 +#include "minc_io_simple_volume.h" +#include "minc_1_simple.h" +#include "minc_1_simple_rw.h" +#endif + +// Configure Zlib support. +// (http://www.zlib.net) +// +// Define 'cimg_use_zlib' to enable Zlib support. +// +// Zlib library may be used to allow compressed data in '.cimgz' files +// (see methods 'CImg[List]::{load,save}_cimg()'). +#ifdef cimg_use_zlib +extern "C" { +#include "zlib.h" +} +#endif + +// Configure libcurl support. +// (http://curl.haxx.se/libcurl/) +// +// Define 'cimg_use_curl' to enable libcurl support. +// +// Libcurl may be used to get a native support of file downloading from the network. +// (see method 'cimg::load_network()'.) +#ifdef cimg_use_curl +#include "curl/curl.h" +#endif + +// Configure Magick++ support. +// (http://www.imagemagick.org/Magick++) +// +// Define 'cimg_use_magick' to enable Magick++ support. +// +// Magick++ library may be used to get a native support of various image file formats. +// (see methods 'CImg::{load,save}()'). +#ifdef cimg_use_magick +#include "Magick++.h" +#endif + +// Configure FFTW3 support. +// (http://www.fftw.org) +// +// Define 'cimg_use_fftw3' to enable libFFTW3 support. +// +// FFTW3 library may be used to efficiently compute the Fast Fourier Transform +// of image data, without restriction on the image size. +// (see method 'CImg[List]::FFT()'). +#ifdef cimg_use_fftw3 +extern "C" { +#include "fftw3.h" +} +#endif + +// Configure LibBoard support. +// (http://libboard.sourceforge.net/) +// +// Define 'cimg_use_board' to enable Board support. +// +// Board library may be used to draw 3D objects in vector-graphics canvas +// that can be saved as '.ps' or '.svg' files afterwards. +// (see method 'CImg::draw_object3d()'). +#ifdef cimg_use_board +#include "Board.h" +#endif + +// Configure OpenEXR support. +// (http://www.openexr.com/) +// +// Define 'cimg_use_openexr' to enable OpenEXR support. +// +// OpenEXR library may be used to get a native support of '.exr' files. +// (see methods 'CImg::{load,save}_exr()'). +#ifdef cimg_use_openexr +#include "ImfRgbaFile.h" +#include "ImfInputFile.h" +#include "ImfChannelList.h" +#include "ImfMatrixAttribute.h" +#include "ImfArray.h" +#endif + +// Configure TinyEXR support. +// (https://github.com/syoyo/tinyexr) +// +// Define 'cimg_use_tinyexr' to enable TinyEXR support. +// +// TinyEXR is a small, single header-only library to load and save OpenEXR(.exr) images. +#ifdef cimg_use_tinyexr +#ifndef TINYEXR_IMPLEMENTATION +#define TINYEXR_IMPLEMENTATION +#endif +#include "tinyexr.h" +#endif + +// Lapack configuration. +// (http://www.netlib.org/lapack) +// +// Define 'cimg_use_lapack' to enable LAPACK support. +// +// Lapack library may be used in several CImg methods to speed up +// matrix computations (eigenvalues, inverse, ...). +#ifdef cimg_use_lapack +extern "C" { + extern void sgetrf_(int*, int*, float*, int*, int*, int*); + extern void sgetri_(int*, float*, int*, int*, float*, int*, int*); + extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*); + extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*); + extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*); + extern void dgetrf_(int*, int*, double*, int*, int*, int*); + extern void dgetri_(int*, double*, int*, int*, double*, int*, int*); + extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*); + extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, + int*, double*, int*, double*, int*, int*); + extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*); + extern void dgels_(char*, int*,int*,int*,double*,int*,double*,int*,double*,int*,int*); + extern void sgels_(char*, int*,int*,int*,float*,int*,float*,int*,float*,int*,int*); +} +#endif + +// Check if min/max/PI macros are defined. +// +// CImg does not compile if macros 'min', 'max' or 'PI' are defined, +// because it redefines functions min(), max() and const variable PI in the cimg:: namespace. +// so it '#undef' these macros if necessary, and restore them to reasonable +// values at the end of this file. +#ifdef min +#undef min +#define _cimg_redefine_min +#endif +#ifdef max +#undef max +#define _cimg_redefine_max +#endif +#ifdef PI +#undef PI +#define _cimg_redefine_PI +#endif + +// Define 'cimg_library' namespace suffix. +// +// You may want to add a suffix to the 'cimg_library' namespace, for instance if you need to work +// with several versions of the library at the same time. +#ifdef cimg_namespace_suffix +#define __cimg_library_suffixed(s) cimg_library_##s +#define _cimg_library_suffixed(s) __cimg_library_suffixed(s) +#define cimg_library_suffixed _cimg_library_suffixed(cimg_namespace_suffix) +#else +#define cimg_library_suffixed cimg_library +#endif + +/*------------------------------------------------------------------------------ + # + # Define user-friendly macros. + # + # These CImg macros are prefixed by 'cimg_' and can be used safely in your own + # code. They are useful to parse command line options, or to write image loops. + # + ------------------------------------------------------------------------------*/ + +// Macros to define program usage, and retrieve command line arguments. +#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false) +#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0) +#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage) + +// Macros to define and manipulate local neighborhoods. +#define CImg_2x2(I,T) T I[4]; \ + T& I##cc = I[0]; T& I##nc = I[1]; \ + T& I##cn = I[2]; T& I##nn = I[3]; \ + I##cc = I##nc = \ + I##cn = I##nn = 0 + +#define CImg_3x3(I,T) T I[9]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \ + T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \ + T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \ + I##pp = I##cp = I##np = \ + I##pc = I##cc = I##nc = \ + I##pn = I##cn = I##nn = 0 + +#define CImg_4x4(I,T) T I[16]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \ + T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \ + T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \ + T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \ + I##pp = I##cp = I##np = I##ap = \ + I##pc = I##cc = I##nc = I##ac = \ + I##pn = I##cn = I##nn = I##an = \ + I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_5x5(I,T) T I[25]; \ + T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \ + T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \ + T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \ + T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \ + T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \ + I##bb = I##pb = I##cb = I##nb = I##ab = \ + I##bp = I##pp = I##cp = I##np = I##ap = \ + I##bc = I##pc = I##cc = I##nc = I##ac = \ + I##bn = I##pn = I##cn = I##nn = I##an = \ + I##ba = I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_2x2x2(I,T) T I[8]; \ + T& I##ccc = I[0]; T& I##ncc = I[1]; \ + T& I##cnc = I[2]; T& I##nnc = I[3]; \ + T& I##ccn = I[4]; T& I##ncn = I[5]; \ + T& I##cnn = I[6]; T& I##nnn = I[7]; \ + I##ccc = I##ncc = \ + I##cnc = I##nnc = \ + I##ccn = I##ncn = \ + I##cnn = I##nnn = 0 + +#define CImg_3x3x3(I,T) T I[27]; \ + T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \ + T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \ + T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \ + T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \ + T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \ + T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \ + T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \ + T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \ + T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \ + I##ppp = I##cpp = I##npp = \ + I##pcp = I##ccp = I##ncp = \ + I##pnp = I##cnp = I##nnp = \ + I##ppc = I##cpc = I##npc = \ + I##pcc = I##ccc = I##ncc = \ + I##pnc = I##cnc = I##nnc = \ + I##ppn = I##cpn = I##npn = \ + I##pcn = I##ccn = I##ncn = \ + I##pnn = I##cnn = I##nnn = 0 + +#define cimg_get2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_p1##x,y,z,c), I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), \ + I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), I[8] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get4x4(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_n2##x,_p1##y,z,c), I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), \ + I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), I[8] = (T)(img)(_p1##x,_n1##y,z,c), \ + I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \ + I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[15] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get5x5(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), I[8] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \ + I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), \ + I[15] = (T)(img)(_p2##x,_n1##y,z,c), I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), I[20] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get6x6(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), I[8] = (T)(img)(x,_p1##y,z,c), \ + I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), \ + I[15] = (T)(img)(_n1##x,y,z,c), I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), \ + I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), I[20] = (T)(img)(x,_n1##y,z,c), \ + I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), \ + I[27] = (T)(img)(_n1##x,_n2##y,z,c), I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), I[32] = (T)(img)(x,_n3##y,z,c), \ + I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get7x7(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), I[8] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), \ + I[15] = (T)(img)(_p2##x,_p1##y,z,c), I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), I[20] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \ + I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), \ + I[27] = (T)(img)(_n3##x,y,z,c), I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), I[32] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \ + I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), \ + I[39] = (T)(img)(_n1##x,_n2##y,z,c), I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), I[44] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[48] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get8x8(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), I[8] = (T)(img)(_p3##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \ + I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[15] = (T)(img)(_n4##x,_p2##y,z,c), I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), I[20] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), \ + I[27] = (T)(img)(x,y,z,c), I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), \ + I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), I[32] = (T)(img)(_p3##x,_n1##y,z,c), \ + I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \ + I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[39] = (T)(img)(_n4##x,_n1##y,z,c), I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), I[44] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[51] = (T)(img)(x,_n3##y,z,c), I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), I[56] = (T)(img)(_p3##x,_n4##y,z,c), \ + I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \ + I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), \ + I[63] = (T)(img)(_n4##x,_n4##y,z,c); + +#define cimg_get9x9(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), \ + I[3] = (T)(img)(_p1##x,_p4##y,z,c), I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), \ + I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), I[8] = (T)(img)(_n4##x,_p4##y,z,c), \ + I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \ + I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), \ + I[15] = (T)(img)(_n2##x,_p3##y,z,c), I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), \ + I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), I[20] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), \ + I[27] = (T)(img)(_p4##x,_p1##y,z,c), I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), I[32] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), \ + I[39] = (T)(img)(_p1##x,y,z,c), I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), \ + I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), I[44] = (T)(img)(_n4##x,y,z,c), \ + I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[51] = (T)(img)(_n2##x,_n1##y,z,c), I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), \ + I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), I[56] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[63] = (T)(img)(_p4##x,_n3##y,z,c), I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), \ + I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), I[68] = (T)(img)(_n1##x,_n3##y,z,c), \ + I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \ + I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), \ + I[75] = (T)(img)(_p1##x,_n4##y,z,c), I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), \ + I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), I[80] = (T)(img)(_n4##x,_n4##y,z,c) + +#define cimg_get2x2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c), I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), \ + I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +#define cimg_get3x3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), \ + I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), \ + I[5] = (T)(img)(_n1##x,y,_p1##z,c), I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), \ + I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), \ + I[11] = (T)(img)(_n1##x,_p1##y,z,c), I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), \ + I[14] = (T)(img)(_n1##x,y,z,c), I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), \ + I[17] = (T)(img)(_n1##x,_n1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), \ + I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), \ + I[23] = (T)(img)(_n1##x,y,_n1##z,c), I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), \ + I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +// Macros to perform various image loops. +// +// These macros are simpler to use than loops with C++ iterators. +#define cimg_for(img,ptrs,T_ptrs) \ + for (T_ptrs *ptrs = (img)._data, *_max##ptrs = (img)._data + (img).size(); ptrs<_max##ptrs; ++ptrs) +#define cimg_rof(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size() - 1; ptrs>=(img)._data; --ptrs) +#define cimg_foroff(img,off) for (cimg_ulong off = 0, _max##off = (img).size(); off<_max##off; ++off) +#define cimg_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) +#define cimg_forY(img,y) cimg_for1((img)._height,y) +#define cimg_forZ(img,z) cimg_for1((img)._depth,z) +#define cimg_forC(img,c) cimg_for1((img)._spectrum,c) +#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x) +#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x) +#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y) +#define cimg_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x) +#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y) +#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z) +#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y) +#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y) +#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z) +#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z) +#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) cimg_forXYZ(img,x,y,z) + +#define cimg_rof1(bound,i) for (int i = (int)(bound) - 1; i>=0; --i) +#define cimg_rofX(img,x) cimg_rof1((img)._width,x) +#define cimg_rofY(img,y) cimg_rof1((img)._height,y) +#define cimg_rofZ(img,z) cimg_rof1((img)._depth,z) +#define cimg_rofC(img,c) cimg_rof1((img)._spectrum,c) +#define cimg_rofXY(img,x,y) cimg_rofY(img,y) cimg_rofX(img,x) +#define cimg_rofXZ(img,x,z) cimg_rofZ(img,z) cimg_rofX(img,x) +#define cimg_rofYZ(img,y,z) cimg_rofZ(img,z) cimg_rofY(img,y) +#define cimg_rofXC(img,x,c) cimg_rofC(img,c) cimg_rofX(img,x) +#define cimg_rofYC(img,y,c) cimg_rofC(img,c) cimg_rofY(img,y) +#define cimg_rofZC(img,z,c) cimg_rofC(img,c) cimg_rofZ(img,z) +#define cimg_rofXYZ(img,x,y,z) cimg_rofZ(img,z) cimg_rofXY(img,x,y) +#define cimg_rofXYC(img,x,y,c) cimg_rofC(img,c) cimg_rofXY(img,x,y) +#define cimg_rofXZC(img,x,z,c) cimg_rofC(img,c) cimg_rofXZ(img,x,z) +#define cimg_rofYZC(img,y,z,c) cimg_rofC(img,c) cimg_rofYZ(img,y,z) +#define cimg_rofXYZC(img,x,y,z,c) cimg_rofC(img,c) cimg_rofXYZ(img,x,y,z) + +#define cimg_for_in1(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound) - 1; i<=_max##i; ++i) +#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img)._width,x0,x1,x) +#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img)._height,y0,y1,y) +#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img)._depth,z0,z1,z) +#define cimg_for_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c) +#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inZ(img,z0,z1,z) +#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z) +#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_inC(img,c0,c1,c) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_insideXYZ(img,x,y,z,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_insideXYZC(img,x,y,z,c,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) + +#define cimg_for_out1(boundi,i0,i1,i) \ + for (int i = (int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1) + 1:i) +#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \ + for (int j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \ + for (int k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \ + for (int l = 0; l<(int)(boundl); ++l) \ + for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1) + 1; \ + i<(int)(boundi); ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img)._width,x0,x1,x) +#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img)._height,y0,y1,y) +#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img)._depth,z0,z1,z) +#define cimg_for_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c) +#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img)._width,(img)._height,x0,y0,x1,y1,x,y) +#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img)._width,(img)._depth,x0,z0,x1,z1,x,z) +#define cimg_for_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c) +#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img)._height,(img)._depth,y0,z0,y1,z1,y,z) +#define cimg_for_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c) +#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c) +#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) \ + cimg_for_out3((img)._width,(img)._height,(img)._depth,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) \ + cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c) +#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) \ + cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c) +#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) \ + cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c) +#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) +#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_borderXYZ(img,x,y,z,n) \ + cimg_for_outXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_borderXYZC(img,x,y,z,c,n) \ + cimg_for_outXYZC(img,n,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n), \ + (img)._depth - 1 - (n),(img)._spectrum - 1 - (n),x,y,z,c) + +#define cimg_for_spiralXY(img,x,y) \ + for (int x = 0, y = 0, _n1##x = 1, _n1##y = (img).width()*(img).height(); _n1##y; \ + --_n1##y, _n1##x+=(_n1##x>>2) - ((!(_n1##x&3)?--y:((_n1##x&3)==1?(img)._width - 1 - ++x:\ + ((_n1##x&3)==2?(img)._height - 1 - ++y:--x))))?0:1) + +#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \ + for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \ + _dx=(x1)>(x0)?(int)(x1) - (int)(x0):(_sx=-1,(int)(x0) - (int)(x1)), \ + _dy=(y1)>(y0)?(int)(y1) - (int)(y0):(_sy=-1,(int)(y0) - (int)(y1)), \ + _counter = _dx, \ + _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \ + _counter>=0; \ + --_counter, x+=_steep? \ + (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \ + (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx)) + +#define cimg_for2(bound,i) \ + for (int i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + ++i, ++_n1##i) +#define cimg_for2X(img,x) cimg_for2((img)._width,x) +#define cimg_for2Y(img,y) cimg_for2((img)._height,y) +#define cimg_for2Z(img,z) cimg_for2((img)._depth,z) +#define cimg_for2C(img,c) cimg_for2((img)._spectrum,c) +#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x) +#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x) +#define cimg_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x) +#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y) +#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y) +#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z) +#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y) +#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z) +#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z) +#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) cimg_for2XYZ(img,x,y,z) + +#define cimg_for_in2(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + ++i, ++_n1##i) +#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img)._width,x0,x1,x) +#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img)._height,y0,y1,y) +#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img)._depth,z0,z1,z) +#define cimg_for_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c) +#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Z(img,z0,z1,z) +#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in2C(img,c0,c1,c) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i) +#define cimg_for3X(img,x) cimg_for3((img)._width,x) +#define cimg_for3Y(img,y) cimg_for3((img)._height,y) +#define cimg_for3Z(img,z) cimg_for3((img)._depth,z) +#define cimg_for3C(img,c) cimg_for3((img)._spectrum,c) +#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x) +#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x) +#define cimg_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x) +#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y) +#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y) +#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z) +#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y) +#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z) +#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z) +#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) cimg_for3XYZ(img,x,y,z) + +#define cimg_for_in3(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + _p1##i = i++, ++_n1##i) +#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img)._width,x0,x1,x) +#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img)._height,y0,y1,y) +#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img)._depth,z0,z1,z) +#define cimg_for_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c) +#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Z(img,z0,z1,z) +#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in3C(img,c0,c1,c) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for4(bound,i) \ + for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for4X(img,x) cimg_for4((img)._width,x) +#define cimg_for4Y(img,y) cimg_for4((img)._height,y) +#define cimg_for4Z(img,z) cimg_for4((img)._depth,z) +#define cimg_for4C(img,c) cimg_for4((img)._spectrum,c) +#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x) +#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x) +#define cimg_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x) +#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y) +#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y) +#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z) +#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y) +#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z) +#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z) +#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) cimg_for4XYZ(img,x,y,z) + +#define cimg_for_in4(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img)._width,x0,x1,x) +#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img)._height,y0,y1,y) +#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img)._depth,z0,z1,z) +#define cimg_for_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c) +#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Z(img,z0,z1,z) +#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in4C(img,c0,c1,c) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for5(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for5X(img,x) cimg_for5((img)._width,x) +#define cimg_for5Y(img,y) cimg_for5((img)._height,y) +#define cimg_for5Z(img,z) cimg_for5((img)._depth,z) +#define cimg_for5C(img,c) cimg_for5((img)._spectrum,c) +#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x) +#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x) +#define cimg_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x) +#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y) +#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y) +#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z) +#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y) +#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z) +#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z) +#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) cimg_for5XYZ(img,x,y,z) + +#define cimg_for_in5(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img)._width,x0,x1,x) +#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img)._height,y0,y1,y) +#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img)._depth,z0,z1,z) +#define cimg_for_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c) +#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Z(img,z0,z1,z) +#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in5C(img,c0,c1,c) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for6(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for6X(img,x) cimg_for6((img)._width,x) +#define cimg_for6Y(img,y) cimg_for6((img)._height,y) +#define cimg_for6Z(img,z) cimg_for6((img)._depth,z) +#define cimg_for6C(img,c) cimg_for6((img)._spectrum,c) +#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x) +#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x) +#define cimg_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x) +#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y) +#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y) +#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z) +#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y) +#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z) +#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z) +#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) cimg_for6XYZ(img,x,y,z) + +#define cimg_for_in6(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img)._width,x0,x1,x) +#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img)._height,y0,y1,y) +#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img)._depth,z0,z1,z) +#define cimg_for_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c) +#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Z(img,z0,z1,z) +#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in6C(img,c0,c1,c) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for7(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for7X(img,x) cimg_for7((img)._width,x) +#define cimg_for7Y(img,y) cimg_for7((img)._height,y) +#define cimg_for7Z(img,z) cimg_for7((img)._depth,z) +#define cimg_for7C(img,c) cimg_for7((img)._spectrum,c) +#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x) +#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x) +#define cimg_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x) +#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y) +#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y) +#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z) +#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y) +#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z) +#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z) +#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) cimg_for7XYZ(img,x,y,z) + +#define cimg_for_in7(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img)._width,x0,x1,x) +#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img)._height,y0,y1,y) +#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img)._depth,z0,z1,z) +#define cimg_for_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c) +#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Z(img,z0,z1,z) +#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in7C(img,c0,c1,c) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for8(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for8X(img,x) cimg_for8((img)._width,x) +#define cimg_for8Y(img,y) cimg_for8((img)._height,y) +#define cimg_for8Z(img,z) cimg_for8((img)._depth,z) +#define cimg_for8C(img,c) cimg_for8((img)._spectrum,c) +#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x) +#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x) +#define cimg_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x) +#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y) +#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y) +#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z) +#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y) +#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z) +#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z) +#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) cimg_for8XYZ(img,x,y,z) + +#define cimg_for_in8(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img)._width,x0,x1,x) +#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img)._height,y0,y1,y) +#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img)._depth,z0,z1,z) +#define cimg_for_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c) +#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Z(img,z0,z1,z) +#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in8C(img,c0,c1,c) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for9(bound,i) \ + for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(int)(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(int)(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(int)(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(int)(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for9X(img,x) cimg_for9((img)._width,x) +#define cimg_for9Y(img,y) cimg_for9((img)._height,y) +#define cimg_for9Z(img,z) cimg_for9((img)._depth,z) +#define cimg_for9C(img,c) cimg_for9((img)._spectrum,c) +#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x) +#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x) +#define cimg_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x) +#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y) +#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y) +#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z) +#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y) +#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z) +#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z) +#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) cimg_for9XYZ(img,x,y,z) + +#define cimg_for_in9(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p4##i = i - 4<0?0:i - 4, \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img)._width,x0,x1,x) +#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img)._height,y0,y1,y) +#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img)._depth,z0,z1,z) +#define cimg_for_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c) +#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Z(img,z0,z1,z) +#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[4] = (T)(img)(x,y,z,c)), \ + (I[7] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for4x4(img,x,y,z,c,I,T) \ + cimg_for4((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = I[5] = (T)(img)(0,y,z,c)), \ + (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in4((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = (T)(img)(_p1##x,y,z,c)), \ + (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[5] = (T)(img)(x,y,z,c)), \ + (I[9] = (T)(img)(x,_n1##y,z,c)), \ + (I[13] = (T)(img)(x,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for5x5(img,x,y,z,c,I,T) \ + cimg_for5((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \ + (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \ + (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in5((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[10] = (T)(img)(_p2##x,y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[11] = (T)(img)(_p1##x,y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[7] = (T)(img)(x,_p1##y,z,c)), \ + (I[12] = (T)(img)(x,y,z,c)), \ + (I[17] = (T)(img)(x,_n1##y,z,c)), \ + (I[22] = (T)(img)(x,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for6x6(img,x,y,z,c,I,T) \ + cimg_for6((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \ + (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \ + (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \ + (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in6((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p2##x,y,z,c)), \ + (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_p1##x,y,z,c)), \ + (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[8] = (T)(img)(x,_p1##y,z,c)), \ + (I[14] = (T)(img)(x,y,z,c)), \ + (I[20] = (T)(img)(x,_n1##y,z,c)), \ + (I[26] = (T)(img)(x,_n2##y,z,c)), \ + (I[32] = (T)(img)(x,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for7x7(img,x,y,z,c,I,T) \ + cimg_for7((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \ + (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \ + (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \ + (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \ + (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \ + (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in7((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[21] = (T)(img)(_p3##x,y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[22] = (T)(img)(_p2##x,y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[23] = (T)(img)(_p1##x,y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[10] = (T)(img)(x,_p2##y,z,c)), \ + (I[17] = (T)(img)(x,_p1##y,z,c)), \ + (I[24] = (T)(img)(x,y,z,c)), \ + (I[31] = (T)(img)(x,_n1##y,z,c)), \ + (I[38] = (T)(img)(x,_n2##y,z,c)), \ + (I[45] = (T)(img)(x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for8x8(img,x,y,z,c,I,T) \ + cimg_for8((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \ + (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \ + (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \ + (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \ + (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \ + (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \ + (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in8((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[24] = (T)(img)(_p3##x,y,z,c)), \ + (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_p2##x,y,z,c)), \ + (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_p1##x,y,z,c)), \ + (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[11] = (T)(img)(x,_p2##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,z,c)), \ + (I[27] = (T)(img)(x,y,z,c)), \ + (I[35] = (T)(img)(x,_n1##y,z,c)), \ + (I[43] = (T)(img)(x,_n2##y,z,c)), \ + (I[51] = (T)(img)(x,_n3##y,z,c)), \ + (I[59] = (T)(img)(x,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for9x9(img,x,y,z,c,I,T) \ + cimg_for9((img)._height,y) for (int x = 0, \ + _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \ + (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \ + (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \ + (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \ + (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \ + (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \ + (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \ + (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in9((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p4##x = x - 4<0?0:x - 4, \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \ + (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \ + (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \ + (I[36] = (T)(img)(_p4##x,y,z,c)), \ + (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \ + (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \ + (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \ + (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \ + (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[37] = (T)(img)(_p3##x,y,z,c)), \ + (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \ + (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[38] = (T)(img)(_p2##x,y,z,c)), \ + (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[39] = (T)(img)(_p1##x,y,z,c)), \ + (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[4] = (T)(img)(x,_p4##y,z,c)), \ + (I[13] = (T)(img)(x,_p3##y,z,c)), \ + (I[22] = (T)(img)(x,_p2##y,z,c)), \ + (I[31] = (T)(img)(x,_p1##y,z,c)), \ + (I[40] = (T)(img)(x,y,z,c)), \ + (I[49] = (T)(img)(x,_n1##y,z,c)), \ + (I[58] = (T)(img)(x,_n2##y,z,c)), \ + (I[67] = (T)(img)(x,_n3##y,z,c)), \ + (I[76] = (T)(img)(x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for2x2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + (I[4] = (T)(img)(0,y,_n1##z,c)), \ + (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in2((img)._depth,z0,z1,z) cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + (I[4] = (T)(img)(x,y,_n1##z,c)), \ + (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for3x3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \ + (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \ + (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \ + (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \ + (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in3((img)._depth,z0,z1,z) cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = (T)(img)(_p1##x,y,_p1##z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \ + (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,y,z,c)), \ + (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \ + (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \ + (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \ + (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \ + (I[4] = (T)(img)(x,y,_p1##z,c)), \ + (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \ + (I[10] = (T)(img)(x,_p1##y,z,c)), \ + (I[13] = (T)(img)(x,y,z,c)), \ + (I[16] = (T)(img)(x,_n1##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \ + (I[22] = (T)(img)(x,y,_n1##z,c)), \ + (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l) +#define cimglist_for_in(list,l0,l1,l) \ + for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width - 1; \ + l<=_max##l; ++l) + +#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn + +// Macros used to display error messages when exceptions are thrown. +// You should not use these macros is your own code. +#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::" +#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']' +#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::" +#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type() +#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::" +#define cimglist_instance _width,_allocated_width,_data,pixel_type() + +/*------------------------------------------------ + # + # + # Define cimg_library:: namespace + # + # + -------------------------------------------------*/ +//! Contains all classes and functions of the \CImg library. +/** + This namespace is defined to avoid functions and class names collisions + that could happen with the inclusion of other C++ header files. + Anyway, it should not happen often and you should reasonnably start most of your + \CImg-based programs with + \code + #include "CImg.h" + using namespace cimg_library; + \endcode + to simplify the declaration of \CImg Library objects afterwards. +**/ +namespace cimg_library_suffixed { + + // Declare the four classes of the CImg Library. + template struct CImg; + template struct CImgList; + struct CImgDisplay; + struct CImgException; + + // Declare cimg:: namespace. + // This is an uncomplete namespace definition here. It only contains some + // necessary stuff to ensure a correct declaration order of the classes and functions + // defined afterwards. + namespace cimg { + + // Define Ascii sequences for colored terminal output. +#ifdef cimg_use_vt100 + static const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 }; + static const char t_black[] = { 0x1b, '[', '0', ';', '3', '0', ';', '5', '9', 'm', 0 }; + static const char t_red[] = { 0x1b, '[', '0', ';', '3', '1', ';', '5', '9', 'm', 0 }; + static const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 }; + static const char t_yellow[] = { 0x1b, '[', '0', ';', '3', '3', ';', '5', '9', 'm', 0 }; + static const char t_blue[] = { 0x1b, '[', '0', ';', '3', '4', ';', '5', '9', 'm', 0 }; + static const char t_magenta[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 }; + static const char t_cyan[] = { 0x1b, '[', '0', ';', '3', '6', ';', '5', '9', 'm', 0 }; + static const char t_white[] = { 0x1b, '[', '0', ';', '3', '7', ';', '5', '9', 'm', 0 }; + static const char t_bold[] = { 0x1b, '[', '1', 'm', 0 }; + static const char t_underscore[] = { 0x1b, '[', '4', 'm', 0 }; +#else + static const char t_normal[] = { 0 }; + static const char *const t_black = cimg::t_normal, + *const t_red = cimg::t_normal, + *const t_green = cimg::t_normal, + *const t_yellow = cimg::t_normal, + *const t_blue = cimg::t_normal, + *const t_magenta = cimg::t_normal, + *const t_cyan = cimg::t_normal, + *const t_white = cimg::t_normal, + *const t_bold = cimg::t_normal, + *const t_underscore = cimg::t_normal; +#endif + + inline std::FILE* output(std::FILE *file=0); + inline void info(); + + //! Avoid warning messages due to unused parameters. Do nothing actually. + template + inline void unused(const T&, ...) {} + + // [internal] Lock/unlock a mutex for managing concurrent threads. + // 'lock_mode' can be { 0=unlock | 1=lock | 2=trylock }. + // 'n' can be in [0,31] but mutex range [0,15] is reserved by CImg. + inline int mutex(const unsigned int n, const int lock_mode=1); + + inline unsigned int& exception_mode(const unsigned int value, const bool is_set) { + static unsigned int mode = cimg_verbosity; + if (is_set) { cimg::mutex(0); mode = value<4?value:4; cimg::mutex(0,0); } + return mode; + } + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + inline FILE* _stdin(const bool throw_exception=true); + inline FILE* _stdout(const bool throw_exception=true); + inline FILE* _stderr(const bool throw_exception=true); + + // Mandatory because Microsoft's _snprintf() and _vsnprintf() do not add the '\0' character + // at the end of the string. +#if cimg_OS==2 && defined(_MSC_VER) + inline int _snprintf(char *const s, const size_t size, const char *const format, ...) { + va_list ap; + va_start(ap,format); + const int result = _vsnprintf(s,size,format,ap); + va_end(ap); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char *const format, va_list ap) { + int result = -1; + cimg::mutex(6); + if (size) result = _vsnprintf_s(s,size,_TRUNCATE,format,ap); + if (result==-1) result = _vscprintf(format,ap); + cimg::mutex(6,0); + return result; + } + + // Mutex-protected version of sscanf, sprintf and snprintf. + // Used only MacOSX, as it seems those functions are not re-entrant on MacOSX. +#elif defined(__MACOSX__) || defined(__APPLE__) + inline int _sscanf(const char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsscanf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _sprintf(char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsprintf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _snprintf(char *const s, const size_t n, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsnprintf(s,n,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char* format, va_list ap) { + cimg::mutex(6); + const int result = std::vsnprintf(s,size,format,ap); + cimg::mutex(6,0); + return result; + } +#endif + + //! Set current \CImg exception mode. + /** + The way error messages are handled by \CImg can be changed dynamically, using this function. + \param mode Desired exception mode. Possible values are: + - \c 0: Hide library messages (quiet mode). + - \c 1: Print library messages on the console. + - \c 2: Display library messages on a dialog window. + - \c 3: Do as \c 1 + add extra debug warnings (slow down the code!). + - \c 4: Do as \c 2 + add extra debug warnings (slow down the code!). + **/ + inline unsigned int& exception_mode(const unsigned int mode) { + return exception_mode(mode,true); + } + + //! Return current \CImg exception mode. + /** + \note By default, return the value of configuration macro \c cimg_verbosity + **/ + inline unsigned int& exception_mode() { + return exception_mode(0,false); + } + + 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; + } + + //! Set current \CImg openmp mode. + /** + The way openmp-based methods are handled by \CImg can be changed dynamically, using this function. + \param mode Desired openmp mode. Possible values are: + - \c 0: Never parallelize. + - \c 1: Always parallelize. + - \c 2: Adaptive parallelization mode (default behavior). + **/ + inline unsigned int openmp_mode(const unsigned int mode) { + return openmp_mode(mode,true); + } + + //! Return current \CImg openmp mode. + inline unsigned int openmp_mode() { + return openmp_mode(0,false); + } + +#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); + + // Evaluate math expression. + inline double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0); + + } + + /*--------------------------------------- + # + # Define the CImgException structures + # + --------------------------------------*/ + //! Instances of \c CImgException are thrown when errors are encountered in a \CImg function call. + /** + \par Overview + + CImgException is the base class of all exceptions thrown by \CImg (except \b CImgAbortException). + CImgException is never thrown itself. Derived classes that specify the type of errord are thrown instead. + These classes can be: + + - \b CImgAbortException: Thrown when a computationally-intensive function is aborted by an external signal. + This is the only \c non-derived exception class. + + - \b CImgArgumentException: Thrown when one argument of a called \CImg function is invalid. + This is probably one of the most thrown exception by \CImg. + For instance, the following example throws a \c CImgArgumentException: + \code + CImg img(100,100,1,3); // Define a 100x100 color image with float-valued pixels + img.mirror('e'); // Try to mirror image along the (non-existing) 'e'-axis + \endcode + + - \b CImgDisplayException: Thrown when something went wrong during the display of images in CImgDisplay instances. + + - \b CImgInstanceException: Thrown when an instance associated to a called \CImg method does not fit + the function requirements. For instance, the following example throws a \c CImgInstanceException: + \code + const CImg img; // Define an empty image + const float value = img.at(0); // Try to read first pixel value (does not exist) + \endcode + + - \b CImgIOException: Thrown when an error occured when trying to load or save image files. + This happens when trying to read files that do not exist or with invalid formats. + For instance, the following example throws a \c CImgIOException: + \code + const CImg img("missing_file.jpg"); // Try to load a file that does not exist + \endcode + + - \b CImgWarningException: Thrown only if configuration macro \c cimg_strict_warnings is set, and + when a \CImg function has to display a warning message (see cimg::warn()). + + It is not recommended to throw CImgException instances by yourself, + since they are expected to be thrown only by \CImg. + When an error occurs in a library function call, \CImg may display error messages on the screen or on the + standard output, depending on the current \CImg exception mode. + The \CImg exception mode can be get and set by functions cimg::exception_mode() and + cimg::exception_mode(unsigned int). + + \par Exceptions handling + + In all cases, when an error occurs in \CImg, an instance of the corresponding exception class is thrown. + This may lead the program to break (this is the default behavior), but you can bypass this behavior by + handling the exceptions by yourself, + using a usual try { ... } catch () { ... } bloc, as in the following example: + \code + #define "CImg.h" + using namespace cimg_library; + int main() { + cimg::exception_mode(0); // Enable quiet exception mode + try { + ... // Here, do what you want to stress CImg + } catch (CImgException& e) { // You succeeded: something went wrong! + std::fprintf(stderr,"CImg Library Error: %s",e.what()); // Display your custom error message + ... // Do what you want now to save the ship! + } + } + \endcode + **/ + struct CImgException : public std::exception { +#define _cimg_exception_err(etype,disp_flag) \ + std::va_list ap, ap2; \ + va_start(ap,format); va_start(ap2,format); \ + int size = cimg_vsnprintf(0,0,format,ap2); \ + if (size++>=0) { \ + delete[] _message; \ + _message = new char[size]; \ + cimg_vsnprintf(_message,size,format,ap); \ + if (cimg::exception_mode()) { \ + std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \ + if (cimg_display && disp_flag && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } \ + catch (CImgException&) {} \ + if (cimg::exception_mode()>=3) cimg_library_suffixed::cimg::info(); \ + } \ + } \ + va_end(ap); va_end(ap2); \ + + char *_message; + CImgException() { _message = new char[1]; *_message = 0; } + CImgException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgException",true); } + CImgException(const CImgException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgException() throw() { delete[] _message; } + CImgException& operator=(const CImgException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgAbortException class is used to throw an exception when + // a computationally-intensive function has been aborted by an external signal. + struct CImgAbortException : public std::exception { + char *_message; + CImgAbortException() { _message = new char[1]; *_message = 0; } + CImgAbortException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgAbortException",true); } + CImgAbortException(const CImgAbortException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgAbortException() throw() { delete[] _message; } + CImgAbortException& operator=(const CImgAbortException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgArgumentException class is used to throw an exception related + // to invalid arguments encountered in a library function call. + struct CImgArgumentException : public CImgException { + CImgArgumentException(const char *const format, ...) { _cimg_exception_err("CImgArgumentException",true); } + }; + + // The CImgDisplayException class is used to throw an exception related + // to display problems encountered in a library function call. + struct CImgDisplayException : public CImgException { + CImgDisplayException(const char *const format, ...) { _cimg_exception_err("CImgDisplayException",false); } + }; + + // The CImgInstanceException class is used to throw an exception related + // to an invalid instance encountered in a library function call. + struct CImgInstanceException : public CImgException { + CImgInstanceException(const char *const format, ...) { _cimg_exception_err("CImgInstanceException",true); } + }; + + // The CImgIOException class is used to throw an exception related + // to input/output file problems encountered in a library function call. + struct CImgIOException : public CImgException { + CImgIOException(const char *const format, ...) { _cimg_exception_err("CImgIOException",true); } + }; + + // The CImgWarningException class is used to throw an exception for warnings + // encountered in a library function call. + struct CImgWarningException : public CImgException { + CImgWarningException(const char *const format, ...) { _cimg_exception_err("CImgWarningException",false); } + }; + + /*------------------------------------- + # + # Define cimg:: namespace + # + -----------------------------------*/ + //! Contains \a low-level functions and variables of the \CImg Library. + /** + Most of the functions and variables within this namespace are used by the \CImg library for low-level operations. + You may use them to access specific const values or environment variables internally used by \CImg. + \warning Never write using namespace cimg_library::cimg; in your source code. Lot of functions in the + cimg:: namespace have the same names as standard C functions that may be defined in the global + namespace ::. + **/ + namespace cimg { + + // Define traits that will be used to determine the best data type to work in CImg functions. + // + template struct type { + static const char* string() { + static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24", + "unknown32", "unknown40", "unknown48", "unknown56", + "unknown64", "unknown72", "unknown80", "unknown88", + "unknown96", "unknown104", "unknown112", "unknown120", + "unknown128" }; + return s[(sizeof(T)<17)?sizeof(T):0]; + } + static bool is_float() { return false; } + static bool is_inf(const T) { return false; } + static bool is_nan(const T) { return false; } + static T min() { return ~max(); } + static T max() { return (T)1<<(8*sizeof(T) - 1); } + static T inf() { return max(); } + static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const T& val) { static const char *const s = "unknown"; cimg::unused(val); return s; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "bool"; return s; } + static bool is_float() { return false; } + static bool is_inf(const bool) { return false; } + static bool is_nan(const bool) { return false; } + static bool min() { return false; } + static bool max() { return true; } + static bool inf() { return max(); } + static bool is_inf() { return false; } + static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned char) { return false; } + static bool is_nan(const unsigned char) { return false; } + static unsigned char min() { return 0; } + static unsigned char max() { return (unsigned char)-1; } + static unsigned char inf() { return max(); } + static unsigned char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned char val) { return (unsigned int)val; } + }; + +#if defined(CHAR_MAX) && CHAR_MAX==255 + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return 0; } + static char max() { return (char)-1; } + static char inf() { return max(); } + static char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const char val) { return (unsigned int)val; } + }; +#else + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return ~max(); } + static char max() { return (char)((unsigned char)-1>>1); } + static char inf() { return max(); } + static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const char val) { return (int)val; } + }; +#endif + + template<> struct type { + static const char* string() { static const char *const s = "signed char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const signed char) { return false; } + static bool is_nan(const signed char) { return false; } + static signed char min() { return ~max(); } + static signed char max() { return (signed char)((unsigned char)-1>>1); } + static signed char inf() { return max(); } + static signed char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(signed char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const signed char val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned short) { return false; } + static bool is_nan(const unsigned short) { return false; } + static unsigned short min() { return 0; } + static unsigned short max() { return (unsigned short)-1; } + static unsigned short inf() { return max(); } + static unsigned short cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned short val) { return (unsigned int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const short) { return false; } + static bool is_nan(const short) { return false; } + static short min() { return ~max(); } + static short max() { return (short)((unsigned short)-1>>1); } + static short inf() { return max(); } + static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const short val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned int) { return false; } + static bool is_nan(const unsigned int) { return false; } + static unsigned int min() { return 0; } + static unsigned int max() { return (unsigned int)-1; } + static unsigned int inf() { return max(); } + static unsigned int cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const int) { return false; } + static bool is_nan(const int) { return false; } + static int min() { return ~max(); } + static int max() { return (int)((unsigned int)-1>>1); } + static int inf() { return max(); } + static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_uint64) { return false; } + static bool is_nan(const cimg_uint64) { return false; } + static cimg_uint64 min() { return 0; } + static cimg_uint64 max() { return (cimg_uint64)-1; } + static cimg_uint64 inf() { return max(); } + static cimg_uint64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; } + static const char* format() { return cimg_fuint64; } + static const char* format_s() { return cimg_fuint64; } + static unsigned long format(const cimg_uint64 val) { return (unsigned long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_int64) { return false; } + static bool is_nan(const cimg_int64) { return false; } + static cimg_int64 min() { return ~max(); } + static cimg_int64 max() { return (cimg_int64)((cimg_uint64)-1>>1); } + static cimg_int64 inf() { return max(); } + static cimg_int64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; + } + static const char* format() { return cimg_fint64; } + static const char* format_s() { return cimg_fint64; } + static long format(const long val) { return (long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const double val) { // Custom version that works with '-ffast-math' + if (sizeof(double)==8) { + cimg_uint64 u; + std::memcpy(&u,&val,sizeof(double)); + return ((unsigned int)(u>>32)&0x7fffffff) + ((unsigned int)u!=0)>0x7ff00000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static double min() { return -DBL_MAX; } + static double max() { return DBL_MAX; } + static double inf() { +#ifdef INFINITY + return (double)INFINITY; +#else + return max()*max(); +#endif + } + static double nan() { +#ifdef NAN + return (double)NAN; +#else + const double val_nan = -std::sqrt(-1.); return val_nan; +#endif + } + static double cut(const double val) { return val; } + static const char* format() { return "%.17g"; } + static const char* format_s() { return "%g"; } + static double format(const double val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "float"; return s; } + static bool is_float() { return true; } + static bool is_inf(const float val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const float val) { // Custom version that works with '-ffast-math' + if (sizeof(float)==4) { + unsigned int u; + std::memcpy(&u,&val,sizeof(float)); + return (u&0x7fffffff)>0x7f800000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static float min() { return -FLT_MAX; } + static float max() { return FLT_MAX; } + static float inf() { return (float)cimg::type::inf(); } + static float nan() { return (float)cimg::type::nan(); } + static float cut(const double val) { return (float)val; } + static float cut(const float val) { return (float)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const float val) { return (double)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "long double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const long double val) { +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static long double min() { return -LDBL_MAX; } + static long double max() { return LDBL_MAX; } + static long double inf() { return max()*max(); } + static long double nan() { const long double val_nan = -std::sqrt(-1.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"; } + static double format(const long double val) { return (double)val; } + }; + +#ifdef cimg_use_half + template<> struct type { + static const char* string() { static const char *const s = "half"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const half val) { // Custom version that works with '-ffast-math' + if (sizeof(half)==2) { + short u; + std::memcpy(&u,&val,sizeof(short)); + return (bool)((u&0x7fff)>0x7c00); + } + return cimg::type::is_nan((float)val); + } + 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.); return val_nan; } + static half cut(const double val) { return (half)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const half val) { return (double)val; } + }; +#endif + + template struct superset { typedef T type; }; + template<> struct superset { typedef unsigned char type; }; + template<> struct superset { typedef char type; }; + template<> struct superset { typedef signed char type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 float type; }; + template<> struct superset { typedef double 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 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; }; + template<> struct superset { typedef double type; }; +#ifdef cimg_use_half + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; +#endif + + template struct superset2 { + typedef typename superset::type>::type type; + }; + + template struct superset3 { + typedef typename superset::type>::type type; + }; + + template struct last { typedef t2 type; }; + +#define _cimg_Tt 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 { + unsigned int nb_wins; + pthread_t *events_thread; + pthread_cond_t wait_event; + pthread_mutex_t wait_event_mutex; + CImgDisplay **wins; + Display *display; + unsigned int nb_bits; + bool is_blue_first; + bool is_shm_enabled; + bool byte_order; +#ifdef cimg_use_xrandr + XRRScreenSize *resolutions; + Rotation curr_rotation; + unsigned int curr_resolution; + unsigned int nb_resolutions; +#endif + X11_info():nb_wins(0),events_thread(0),display(0), + nb_bits(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) { +#ifdef __FreeBSD__ + XInitThreads(); +#endif + wins = new CImgDisplay*[1024]; + pthread_mutex_init(&wait_event_mutex,0); + pthread_cond_init(&wait_event,0); +#ifdef cimg_use_xrandr + resolutions = 0; + curr_rotation = 0; + curr_resolution = nb_resolutions = 0; +#endif + } + + ~X11_info() { + delete[] wins; + /* + if (events_thread) { + pthread_cancel(*events_thread); + delete events_thread; + } + if (display) { } // XCloseDisplay(display); } + pthread_cond_destroy(&wait_event); + pthread_mutex_unlock(&wait_event_mutex); + pthread_mutex_destroy(&wait_event_mutex); + */ + } + }; +#if defined(cimg_module) + X11_info& X11_attr(); +#elif defined(cimg_main) + X11_info& X11_attr() { static X11_info val; return val; } +#else + inline X11_info& X11_attr() { static X11_info val; return val; } +#endif + +#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 +#define cimg_lock_display() cimg::mutex(15) +#define cimg_unlock_display() cimg::mutex(15,0) + + struct Mutex_info { +#ifdef _PTHREAD_H + pthread_mutex_t mutex[32]; + Mutex_info() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); } + void lock(const unsigned int n) { pthread_mutex_lock(&mutex[n]); } + void unlock(const unsigned int n) { pthread_mutex_unlock(&mutex[n]); } + int trylock(const unsigned int n) { return pthread_mutex_trylock(&mutex[n]); } +#elif 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; } +#else + Mutex_info() {} + void lock(const unsigned int) {} + void unlock(const unsigned int) {} + int trylock(const unsigned int) { return 0; } +#endif + }; +#if defined(cimg_module) + Mutex_info& Mutex_attr(); +#elif defined(cimg_main) + Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#else + inline Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#endif + +#if defined(cimg_use_magick) + static struct Magick_info { + Magick_info() { + Magick::InitializeMagick(""); + } + } _Magick_info; +#endif + +#if cimg_display==1 + // Define keycodes for X11-based graphical systems. + const unsigned int keyESC = XK_Escape; + const unsigned int keyF1 = XK_F1; + const unsigned int keyF2 = XK_F2; + const unsigned int keyF3 = XK_F3; + const unsigned int keyF4 = XK_F4; + const unsigned int keyF5 = XK_F5; + const unsigned int keyF6 = XK_F6; + const unsigned int keyF7 = XK_F7; + const unsigned int keyF8 = XK_F8; + const unsigned int keyF9 = XK_F9; + const unsigned int keyF10 = XK_F10; + const unsigned int keyF11 = XK_F11; + const unsigned int keyF12 = XK_F12; + const unsigned int keyPAUSE = XK_Pause; + const unsigned int key1 = XK_1; + const unsigned int key2 = XK_2; + const unsigned int key3 = XK_3; + const unsigned int key4 = XK_4; + const unsigned int key5 = XK_5; + const unsigned int key6 = XK_6; + const unsigned int key7 = XK_7; + const unsigned int key8 = XK_8; + const unsigned int key9 = XK_9; + const unsigned int key0 = XK_0; + const unsigned int keyBACKSPACE = XK_BackSpace; + const unsigned int keyINSERT = XK_Insert; + const unsigned int keyHOME = XK_Home; + const unsigned int keyPAGEUP = XK_Page_Up; + const unsigned int keyTAB = XK_Tab; + const unsigned int keyQ = XK_q; + const unsigned int keyW = XK_w; + const unsigned int keyE = XK_e; + const unsigned int keyR = XK_r; + const unsigned int keyT = XK_t; + const unsigned int keyY = XK_y; + const unsigned int keyU = XK_u; + const unsigned int keyI = XK_i; + const unsigned int keyO = XK_o; + const unsigned int keyP = XK_p; + const unsigned int keyDELETE = XK_Delete; + const unsigned int keyEND = XK_End; + const unsigned int keyPAGEDOWN = XK_Page_Down; + const unsigned int keyCAPSLOCK = XK_Caps_Lock; + const unsigned int keyA = XK_a; + const unsigned int keyS = XK_s; + const unsigned int keyD = XK_d; + const unsigned int keyF = XK_f; + const unsigned int keyG = XK_g; + const unsigned int keyH = XK_h; + const unsigned int keyJ = XK_j; + const unsigned int keyK = XK_k; + const unsigned int keyL = XK_l; + const unsigned int keyENTER = XK_Return; + const unsigned int keySHIFTLEFT = XK_Shift_L; + const unsigned int keyZ = XK_z; + const unsigned int keyX = XK_x; + const unsigned int keyC = XK_c; + const unsigned int keyV = XK_v; + const unsigned int keyB = XK_b; + const unsigned int keyN = XK_n; + const unsigned int keyM = XK_m; + const unsigned int keySHIFTRIGHT = XK_Shift_R; + const unsigned int keyARROWUP = XK_Up; + const unsigned int keyCTRLLEFT = XK_Control_L; + const unsigned int keyAPPLEFT = XK_Super_L; + const unsigned int keyALT = XK_Alt_L; + const unsigned int keySPACE = XK_space; + const unsigned int keyALTGR = XK_Alt_R; + const unsigned int keyAPPRIGHT = XK_Super_R; + const unsigned int keyMENU = XK_Menu; + const unsigned int keyCTRLRIGHT = XK_Control_R; + const unsigned int keyARROWLEFT = XK_Left; + const unsigned int keyARROWDOWN = XK_Down; + const unsigned int keyARROWRIGHT = XK_Right; + const unsigned int keyPAD0 = XK_KP_0; + const unsigned int keyPAD1 = XK_KP_1; + const unsigned int keyPAD2 = XK_KP_2; + const unsigned int keyPAD3 = XK_KP_3; + const unsigned int keyPAD4 = XK_KP_4; + const unsigned int keyPAD5 = XK_KP_5; + const unsigned int keyPAD6 = XK_KP_6; + const unsigned int keyPAD7 = XK_KP_7; + const unsigned int keyPAD8 = XK_KP_8; + const unsigned int keyPAD9 = XK_KP_9; + const unsigned int keyPADADD = XK_KP_Add; + const unsigned int keyPADSUB = XK_KP_Subtract; + const unsigned int keyPADMUL = XK_KP_Multiply; + const unsigned int keyPADDIV = XK_KP_Divide; + +#elif cimg_display==2 + // Define keycodes for Windows. + const unsigned int keyESC = VK_ESCAPE; + const unsigned int keyF1 = VK_F1; + const unsigned int keyF2 = VK_F2; + const unsigned int keyF3 = VK_F3; + const unsigned int keyF4 = VK_F4; + const unsigned int keyF5 = VK_F5; + const unsigned int keyF6 = VK_F6; + const unsigned int keyF7 = VK_F7; + const unsigned int keyF8 = VK_F8; + const unsigned int keyF9 = VK_F9; + const unsigned int keyF10 = VK_F10; + const unsigned int keyF11 = VK_F11; + const unsigned int keyF12 = VK_F12; + const unsigned int keyPAUSE = VK_PAUSE; + const unsigned int key1 = '1'; + const unsigned int key2 = '2'; + const unsigned int key3 = '3'; + const unsigned int key4 = '4'; + const unsigned int key5 = '5'; + const unsigned int key6 = '6'; + const unsigned int key7 = '7'; + const unsigned int key8 = '8'; + const unsigned int key9 = '9'; + const unsigned int key0 = '0'; + const unsigned int keyBACKSPACE = VK_BACK; + const unsigned int keyINSERT = VK_INSERT; + const unsigned int keyHOME = VK_HOME; + const unsigned int keyPAGEUP = VK_PRIOR; + const unsigned int keyTAB = VK_TAB; + const unsigned int keyQ = 'Q'; + const unsigned int keyW = 'W'; + const unsigned int keyE = 'E'; + const unsigned int keyR = 'R'; + const unsigned int keyT = 'T'; + const unsigned int keyY = 'Y'; + const unsigned int keyU = 'U'; + const unsigned int keyI = 'I'; + const unsigned int keyO = 'O'; + const unsigned int keyP = 'P'; + const unsigned int keyDELETE = VK_DELETE; + const unsigned int keyEND = VK_END; + const unsigned int keyPAGEDOWN = VK_NEXT; + const unsigned int keyCAPSLOCK = VK_CAPITAL; + const unsigned int keyA = 'A'; + const unsigned int keyS = 'S'; + const unsigned int keyD = 'D'; + const unsigned int keyF = 'F'; + const unsigned int keyG = 'G'; + const unsigned int keyH = 'H'; + const unsigned int keyJ = 'J'; + const unsigned int keyK = 'K'; + const unsigned int keyL = 'L'; + const unsigned int keyENTER = VK_RETURN; + const unsigned int keySHIFTLEFT = VK_SHIFT; + const unsigned int keyZ = 'Z'; + const unsigned int keyX = 'X'; + const unsigned int keyC = 'C'; + const unsigned int keyV = 'V'; + const unsigned int keyB = 'B'; + const unsigned int keyN = 'N'; + const unsigned int keyM = 'M'; + const unsigned int keySHIFTRIGHT = VK_SHIFT; + const unsigned int keyARROWUP = VK_UP; + const unsigned int keyCTRLLEFT = VK_CONTROL; + const unsigned int keyAPPLEFT = VK_LWIN; + const unsigned int keyALT = VK_LMENU; + const unsigned int keySPACE = VK_SPACE; + const unsigned int keyALTGR = VK_CONTROL; + const unsigned int keyAPPRIGHT = VK_RWIN; + const unsigned int keyMENU = VK_APPS; + const unsigned int keyCTRLRIGHT = VK_CONTROL; + const unsigned int keyARROWLEFT = VK_LEFT; + const unsigned int keyARROWDOWN = VK_DOWN; + const unsigned int keyARROWRIGHT = VK_RIGHT; + const unsigned int keyPAD0 = 0x60; + const unsigned int keyPAD1 = 0x61; + const unsigned int keyPAD2 = 0x62; + const unsigned int keyPAD3 = 0x63; + const unsigned int keyPAD4 = 0x64; + const unsigned int keyPAD5 = 0x65; + const unsigned int keyPAD6 = 0x66; + const unsigned int keyPAD7 = 0x67; + const unsigned int keyPAD8 = 0x68; + const unsigned int keyPAD9 = 0x69; + const unsigned int keyPADADD = VK_ADD; + const unsigned int keyPADSUB = VK_SUBTRACT; + const unsigned int keyPADMUL = VK_MULTIPLY; + const unsigned int keyPADDIV = VK_DIVIDE; + +#else + // Define random keycodes when no display is available. + // (should rarely be used then!). + const unsigned int keyESC = 1U; //!< Keycode for the \c ESC key (architecture-dependent) + const unsigned int keyF1 = 2U; //!< Keycode for the \c F1 key (architecture-dependent) + const unsigned int keyF2 = 3U; //!< Keycode for the \c F2 key (architecture-dependent) + const unsigned int keyF3 = 4U; //!< Keycode for the \c F3 key (architecture-dependent) + const unsigned int keyF4 = 5U; //!< Keycode for the \c F4 key (architecture-dependent) + const unsigned int keyF5 = 6U; //!< Keycode for the \c F5 key (architecture-dependent) + const unsigned int keyF6 = 7U; //!< Keycode for the \c F6 key (architecture-dependent) + const unsigned int keyF7 = 8U; //!< Keycode for the \c F7 key (architecture-dependent) + const unsigned int keyF8 = 9U; //!< Keycode for the \c F8 key (architecture-dependent) + const unsigned int keyF9 = 10U; //!< Keycode for the \c F9 key (architecture-dependent) + const unsigned int keyF10 = 11U; //!< Keycode for the \c F10 key (architecture-dependent) + const unsigned int keyF11 = 12U; //!< Keycode for the \c F11 key (architecture-dependent) + const unsigned int keyF12 = 13U; //!< Keycode for the \c F12 key (architecture-dependent) + const unsigned int keyPAUSE = 14U; //!< Keycode for the \c PAUSE key (architecture-dependent) + const unsigned int key1 = 15U; //!< Keycode for the \c 1 key (architecture-dependent) + const unsigned int key2 = 16U; //!< Keycode for the \c 2 key (architecture-dependent) + const unsigned int key3 = 17U; //!< Keycode for the \c 3 key (architecture-dependent) + const unsigned int key4 = 18U; //!< Keycode for the \c 4 key (architecture-dependent) + const unsigned int key5 = 19U; //!< Keycode for the \c 5 key (architecture-dependent) + const unsigned int key6 = 20U; //!< Keycode for the \c 6 key (architecture-dependent) + const unsigned int key7 = 21U; //!< Keycode for the \c 7 key (architecture-dependent) + const unsigned int key8 = 22U; //!< Keycode for the \c 8 key (architecture-dependent) + const unsigned int key9 = 23U; //!< Keycode for the \c 9 key (architecture-dependent) + const unsigned int key0 = 24U; //!< Keycode for the \c 0 key (architecture-dependent) + const unsigned int keyBACKSPACE = 25U; //!< Keycode for the \c BACKSPACE key (architecture-dependent) + const unsigned int keyINSERT = 26U; //!< Keycode for the \c INSERT key (architecture-dependent) + const unsigned int keyHOME = 27U; //!< Keycode for the \c HOME key (architecture-dependent) + const unsigned int keyPAGEUP = 28U; //!< Keycode for the \c PAGEUP key (architecture-dependent) + const unsigned int keyTAB = 29U; //!< Keycode for the \c TAB key (architecture-dependent) + const unsigned int keyQ = 30U; //!< Keycode for the \c Q key (architecture-dependent) + const unsigned int keyW = 31U; //!< Keycode for the \c W key (architecture-dependent) + const unsigned int keyE = 32U; //!< Keycode for the \c E key (architecture-dependent) + const unsigned int keyR = 33U; //!< Keycode for the \c R key (architecture-dependent) + const unsigned int keyT = 34U; //!< Keycode for the \c T key (architecture-dependent) + const unsigned int keyY = 35U; //!< Keycode for the \c Y key (architecture-dependent) + const unsigned int keyU = 36U; //!< Keycode for the \c U key (architecture-dependent) + const unsigned int keyI = 37U; //!< Keycode for the \c I key (architecture-dependent) + const unsigned int keyO = 38U; //!< Keycode for the \c O key (architecture-dependent) + const unsigned int keyP = 39U; //!< Keycode for the \c P key (architecture-dependent) + const unsigned int keyDELETE = 40U; //!< Keycode for the \c DELETE key (architecture-dependent) + const unsigned int keyEND = 41U; //!< Keycode for the \c END key (architecture-dependent) + const unsigned int keyPAGEDOWN = 42U; //!< Keycode for the \c PAGEDOWN key (architecture-dependent) + const unsigned int keyCAPSLOCK = 43U; //!< Keycode for the \c CAPSLOCK key (architecture-dependent) + const unsigned int keyA = 44U; //!< Keycode for the \c A key (architecture-dependent) + const unsigned int keyS = 45U; //!< Keycode for the \c S key (architecture-dependent) + const unsigned int keyD = 46U; //!< Keycode for the \c D key (architecture-dependent) + const unsigned int keyF = 47U; //!< Keycode for the \c F key (architecture-dependent) + const unsigned int keyG = 48U; //!< Keycode for the \c G key (architecture-dependent) + const unsigned int keyH = 49U; //!< Keycode for the \c H key (architecture-dependent) + const unsigned int keyJ = 50U; //!< Keycode for the \c J key (architecture-dependent) + const unsigned int keyK = 51U; //!< Keycode for the \c K key (architecture-dependent) + const unsigned int keyL = 52U; //!< Keycode for the \c L key (architecture-dependent) + const unsigned int keyENTER = 53U; //!< Keycode for the \c ENTER key (architecture-dependent) + const unsigned int keySHIFTLEFT = 54U; //!< Keycode for the \c SHIFTLEFT key (architecture-dependent) + const unsigned int keyZ = 55U; //!< Keycode for the \c Z key (architecture-dependent) + const unsigned int keyX = 56U; //!< Keycode for the \c X key (architecture-dependent) + const unsigned int keyC = 57U; //!< Keycode for the \c C key (architecture-dependent) + const unsigned int keyV = 58U; //!< Keycode for the \c V key (architecture-dependent) + const unsigned int keyB = 59U; //!< Keycode for the \c B key (architecture-dependent) + const unsigned int keyN = 60U; //!< Keycode for the \c N key (architecture-dependent) + const unsigned int keyM = 61U; //!< Keycode for the \c M key (architecture-dependent) + const unsigned int keySHIFTRIGHT = 62U; //!< Keycode for the \c SHIFTRIGHT key (architecture-dependent) + const unsigned int keyARROWUP = 63U; //!< Keycode for the \c ARROWUP key (architecture-dependent) + const unsigned int keyCTRLLEFT = 64U; //!< Keycode for the \c CTRLLEFT key (architecture-dependent) + const unsigned int keyAPPLEFT = 65U; //!< Keycode for the \c APPLEFT key (architecture-dependent) + const unsigned int keyALT = 66U; //!< Keycode for the \c ALT key (architecture-dependent) + const unsigned int keySPACE = 67U; //!< Keycode for the \c SPACE key (architecture-dependent) + const unsigned int keyALTGR = 68U; //!< Keycode for the \c ALTGR key (architecture-dependent) + const unsigned int keyAPPRIGHT = 69U; //!< Keycode for the \c APPRIGHT key (architecture-dependent) + const unsigned int keyMENU = 70U; //!< Keycode for the \c MENU key (architecture-dependent) + const unsigned int keyCTRLRIGHT = 71U; //!< Keycode for the \c CTRLRIGHT key (architecture-dependent) + const unsigned int keyARROWLEFT = 72U; //!< Keycode for the \c ARROWLEFT key (architecture-dependent) + const unsigned int keyARROWDOWN = 73U; //!< Keycode for the \c ARROWDOWN key (architecture-dependent) + const unsigned int keyARROWRIGHT = 74U; //!< Keycode for the \c ARROWRIGHT key (architecture-dependent) + const unsigned int keyPAD0 = 75U; //!< Keycode for the \c PAD0 key (architecture-dependent) + const unsigned int keyPAD1 = 76U; //!< Keycode for the \c PAD1 key (architecture-dependent) + const unsigned int keyPAD2 = 77U; //!< Keycode for the \c PAD2 key (architecture-dependent) + const unsigned int keyPAD3 = 78U; //!< Keycode for the \c PAD3 key (architecture-dependent) + const unsigned int keyPAD4 = 79U; //!< Keycode for the \c PAD4 key (architecture-dependent) + const unsigned int keyPAD5 = 80U; //!< Keycode for the \c PAD5 key (architecture-dependent) + const unsigned int keyPAD6 = 81U; //!< Keycode for the \c PAD6 key (architecture-dependent) + const unsigned int keyPAD7 = 82U; //!< Keycode for the \c PAD7 key (architecture-dependent) + const unsigned int keyPAD8 = 83U; //!< Keycode for the \c PAD8 key (architecture-dependent) + const unsigned int keyPAD9 = 84U; //!< Keycode for the \c PAD9 key (architecture-dependent) + const unsigned int keyPADADD = 85U; //!< Keycode for the \c PADADD key (architecture-dependent) + const unsigned int keyPADSUB = 86U; //!< Keycode for the \c PADSUB key (architecture-dependent) + const unsigned int keyPADMUL = 87U; //!< Keycode for the \c PADMUL key (architecture-dependent) + const unsigned int keyPADDIV = 88U; //!< Keycode for the \c PADDDIV key (architecture-dependent) +#endif + + const double PI = 3.14159265358979323846; //!< Value of the mathematical constant PI + + // Define a 10x13 binary font (small sans). + static const char *const data_font_small[] = { + " UwlwnwoyuwHwlwmwcwlwnw[xuwowlwmwoyuwRwlwnxcw Mw (wnwnwuwpwuypwuwoy" + "ZwnwmwuwowuwmwnwnwuwowuwfwuxnwnwmwuwpwuypwuwZwnwnwtwpwtwow'y Hw cwnw >{ 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 " + "rwswewnwuwowuwozswtwuwqwtwmwnwlwowuwuwowOxpxuxqwuwowswqwswoxpwlwjwqwswqwswy~}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 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

    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

    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 std::printf(). + \note If configuration macro \c cimg_strict_warnings is set, this function throws a + \c CImgWarningException instead. + \warning As the first argument is a format string, it is highly recommended to write + \code + cimg::warn("%s",warning_message); + \endcode + instead of + \code + cimg::warn(warning_message); + \endcode + if \c warning_message can be arbitrary, to prevent nasty memory access. + **/ + inline void warn(const char *const format, ...) { + if (cimg::exception_mode()>=1) { + char *const message = new char[16384]; + std::va_list ap; + va_start(ap,format); + cimg_vsnprintf(message,16384,format,ap); + va_end(ap); +#ifdef cimg_strict_warnings + throw CImgWarningException(message); +#else + std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s\n",cimg::t_red,cimg::t_normal,message); +#endif + delete[] message; + } + } + + // Execute an external system command. + /** + \param command C-string containing the command line to execute. + \param module_name Module name. + \return Status value of the executed command, whose meaning is OS-dependent. + \note This function is similar to std::system() + but it does not open an extra console windows + on Windows-based systems. + **/ + inline int system(const char *const command, const char *const module_name=0, const bool is_verbose=false) { + cimg::unused(module_name); +#ifdef cimg_no_system_calls + return -1; +#else + if (is_verbose) return std::system(command); +#if cimg_OS==1 + const unsigned int l = (unsigned int)std::strlen(command); + if (l) { + char *const ncommand = new char[l + 24]; + std::memcpy(ncommand,command,l); + 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; + std::memset(&pi,0,sizeof(PROCESS_INFORMATION)); + std::memset(&si,0,sizeof(STARTUPINFO)); + GetStartupInfo(&si); + si.cb = sizeof(si); + si.wShowWindow = SW_HIDE; + si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW; + const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi); + if (res) { + WaitForSingleObject(pi.hProcess,INFINITE); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; + } else return std::system(command); +#else + return std::system(command); +#endif +#endif + } + + //! Return a reference to a temporary variable of type T. + template + inline T& temporary(const T&) { + static T temp; + return temp; + } + + //! Exchange values of variables \c a and \c b. + template + inline void swap(T& a, T& b) { T t = a; a = b; b = t; } + + //! Exchange values of variables (\c a1,\c a2) and (\c b1,\c b2). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) { + cimg::swap(a1,b1); cimg::swap(a2,b2); + } + + //! Exchange values of variables (\c a1,\c a2,\c a3) and (\c b1,\c b2,\c b3). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) { + cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a4) and (\c b1,\c b2,...,\c b4). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) { + cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a5) and (\c b1,\c b2,...,\c b5). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a6) and (\c b1,\c b2,...,\c b6). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a7) and (\c b1,\c b2,...,\c b7). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a8) and (\c b1,\c b2,...,\c b8). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7, T8& a8, T8& b8) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8); + } + + //! Return the endianness of the current architecture. + /** + \return \c false for Little Endian or \c true for Big Endian. + **/ + inline bool endianness() { + const int x = 1; + return ((unsigned char*)&x)[0]?false:true; + } + + //! Reverse endianness of all elements in a memory buffer. + /** + \param[in,out] buffer Memory buffer whose endianness must be reversed. + \param size Number of buffer elements to reverse. + **/ + template + inline void invert_endianness(T* const buffer, const cimg_ulong size) { + if (size) switch (sizeof(T)) { + case 1 : break; + case 2 : { + for (unsigned short *ptr = (unsigned short*)buffer + size; ptr>(unsigned short*)buffer; ) { + const unsigned short val = *(--ptr); + *ptr = (unsigned short)((val>>8) | ((val<<8))); + } + } break; + case 4 : { + for (unsigned int *ptr = (unsigned int*)buffer + size; ptr>(unsigned int*)buffer; ) { + const unsigned int val = *(--ptr); + *ptr = (val>>24) | ((val>>8)&0xff00) | ((val<<8)&0xff0000) | (val<<24); + } + } break; + case 8 : { + const cimg_uint64 + m0 = (cimg_uint64)0xff, m1 = m0<<8, m2 = m0<<16, m3 = m0<<24, + m4 = m0<<32, m5 = m0<<40, m6 = m0<<48, m7 = m0<<56; + for (cimg_uint64 *ptr = (cimg_uint64*)buffer + size; ptr>(cimg_uint64*)buffer; ) { + const cimg_uint64 val = *(--ptr); + *ptr = (((val&m7)>>56) | ((val&m6)>>40) | ((val&m5)>>24) | ((val&m4)>>8) | + ((val&m3)<<8) |((val&m2)<<24) | ((val&m1)<<40) | ((val&m0)<<56)); + } + } break; + default : { + for (T* ptr = buffer + size; ptr>buffer; ) { + unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T); + for (int i = 0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe)); + } + } + } + } + + //! Reverse endianness of a single variable. + /** + \param[in,out] a Variable to reverse. + \return Reference to reversed variable. + **/ + template + inline T& invert_endianness(T& a) { + invert_endianness(&a,1); + return a; + } + + // Conversion functions to get more precision when trying to store unsigned ints values as floats. + inline unsigned int float2uint(const float f) { + int tmp = 0; + std::memcpy(&tmp,&f,sizeof(float)); + if (tmp>=0) return (unsigned int)f; + unsigned int u; + // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&u,&f,sizeof(float)); + return ((u)<<1)>>1; // set sign bit to 0 + } + + inline float uint2float(const unsigned int u) { + if (u<(1U<<19)) return (float)u; // Consider safe storage of unsigned int as floats until 19bits (i.e 524287) + float f; + const unsigned int v = u|(1U<<(8*sizeof(unsigned int)-1)); // set sign bit to 1 + // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&f,&v,sizeof(float)); + return f; + } + + //! Return the value of a system timer, with a millisecond precision. + /** + \note The timer does not necessarily starts from \c 0. + **/ + inline cimg_ulong time() { +#if cimg_OS==1 + struct timeval st_time; + gettimeofday(&st_time,0); + return (cimg_ulong)(st_time.tv_usec/1000 + st_time.tv_sec*1000); +#elif cimg_OS==2 + SYSTEMTIME st_time; + GetLocalTime(&st_time); + return (cimg_ulong)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour))); +#else + return 0; +#endif + } + + // Implement a tic/toc mechanism to display elapsed time of algorithms. + inline cimg_ulong tictoc(const bool is_tic); + + //! Start tic/toc timer for time measurement between code instructions. + /** + \return Current value of the timer (same value as time()). + **/ + inline cimg_ulong tic() { + return cimg::tictoc(true); + } + + //! End tic/toc timer and displays elapsed time from last call to tic(). + /** + \return Time elapsed (in ms) since last call to tic(). + **/ + inline cimg_ulong toc() { + return cimg::tictoc(false); + } + + //! Sleep for a given numbers of milliseconds. + /** + \param milliseconds Number of milliseconds to wait for. + \note This function frees the CPU ressources during the sleeping time. + It can be used to temporize your program properly, without wasting CPU time. + **/ + inline void sleep(const unsigned int milliseconds) { +#if cimg_OS==1 + struct timespec tv; + tv.tv_sec = milliseconds/1000; + tv.tv_nsec = (milliseconds%1000)*1000000; + nanosleep(&tv,0); +#elif cimg_OS==2 + Sleep(milliseconds); +#else + cimg::unused(milliseconds); +#endif + } + + inline unsigned int wait(const unsigned int milliseconds, cimg_ulong *const p_timer) { + if (!*p_timer) *p_timer = cimg::time(); + const cimg_ulong current_time = cimg::time(); + if (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; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \return Number of milliseconds elapsed since the last call to wait(). + \note Same as sleep() with a waiting time computed with regard to the last call + of wait(). It may be used to temporize your program properly, without wasting CPU time. + **/ + inline cimg_long wait(const unsigned int milliseconds) { + cimg::mutex(3); + static cimg_ulong timer = cimg::time(); + cimg::mutex(3,0); + return cimg::wait(milliseconds,&timer); + } + + // Custom random number generator (allow re-entrance). + inline cimg_ulong& rng() { // Used as a shared global number for rng + static cimg_ulong rng = 0xB16B00B5U; + return rng; + } + + inline unsigned int _rand(cimg_ulong *const p_rng) { + *p_rng = *p_rng*1103515245 + 12345U; + return (unsigned int)*p_rng; + } + + inline unsigned int _rand() { + cimg::mutex(4); + const unsigned int res = cimg::_rand(&cimg::rng()); + cimg::mutex(4,0); + return res; + } + + inline void srand(cimg_ulong *const p_rng) { +#if cimg_OS==1 + *p_rng = cimg::time() + (cimg_ulong)getpid(); +#elif cimg_OS==2 + *p_rng = cimg::time() + (cimg_ulong)_getpid(); +#endif + } + + inline void srand() { + cimg::mutex(4); + cimg::srand(&cimg::rng()); + cimg::mutex(4,0); + } + + inline void srand(const cimg_ulong seed) { + cimg::mutex(4); + cimg::rng() = seed; + cimg::mutex(4,0); + } + + inline double rand(const double val_min, const double val_max, cimg_ulong *const p_rng) { + const double val = cimg::_rand(p_rng)/(double)~0U; + return val_min + (val_max - val_min)*val; + } + + 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; + } + + inline double rand(const double val_max, cimg_ulong *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_ulong *const p_rng) { + double x1, w; + do { + 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.); + return x1*std::sqrt((-2*std::log(w))/w); + } + + 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_ulong *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.; 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; + } + + //! Bitwise-rotate value on the left. + template + inline T rol(const T& a, const unsigned int n=1) { + return n?(T)((a<>((sizeof(T)<<3) - n))):a; + } + + inline float rol(const float a, const unsigned int n=1) { + return (float)rol((int)a,n); + } + + inline double rol(const double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + + inline double rol(const long double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half rol(const half a, const unsigned int n=1) { + return (half)rol((int)a,n); + } +#endif + + //! Bitwise-rotate value on the right. + template + inline T ror(const T& a, const unsigned int n=1) { + return n?(T)((a>>n)|(a<<((sizeof(T)<<3) - n))):a; + } + + inline float ror(const float a, const unsigned int n=1) { + return (float)ror((int)a,n); + } + + inline double ror(const double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + + inline double ror(const long double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half ror(const half a, const unsigned int n=1) { + return (half)ror((int)a,n); + } +#endif + + //! Return absolute value of a value. + template + inline T abs(const T& a) { + return a>=0?a:-a; + } + inline bool abs(const bool a) { + return a; + } + inline int abs(const unsigned char a) { + return (int)a; + } + inline int abs(const unsigned short a) { + return (int)a; + } + inline int abs(const unsigned int a) { + return (int)a; + } + inline int abs(const int a) { + return std::abs(a); + } + inline cimg_int64 abs(const cimg_uint64 a) { + return (cimg_int64)a; + } + inline double abs(const double a) { + return std::fabs(a); + } + inline float abs(const float a) { + return (float)std::fabs((double)a); + } + + //! Return hyperbolic arcosine of a value. + inline double acosh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::acosh(x); +#else + return std::log(x + std::sqrt(x*x - 1)); +#endif + } + + //! Return hyperbolic arcsine of a value. + inline double asinh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::asinh(x); +#else + return std::log(x + std::sqrt(x*x + 1)); +#endif + } + + //! Return hyperbolic arctangent of a value. + inline double atanh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::atanh(x); +#else + return 0.5*std::log((1. + x)/(1. - x)); +#endif + } + + //! Return the sinc of a given value. + inline double sinc(const double x) { + return x?std::sin(x)/x:1; + } + + //! Return base-2 logarithm of a value. + inline double log2(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::log2(x); +#else + const double base2 = std::log(2.); + return std::log(x)/base2; +#endif + } + + //! Return square of a value. + template + inline T sqr(const T& val) { + return val*val; + } + + //! 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./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) { + return std::min(std::min(a,b),c); + } + + //! Return the minimum between four values. + template + inline t min(const t& a, const t& b, const t& c, const t& d) { + return std::min(std::min(a,b),std::min(c,d)); + } + + //! Return the maximum between three values. + template + inline t max(const t& a, const t& b, const t& c) { + return std::max(std::max(a,b),c); + } + + //! Return the maximum between four values. + template + inline t max(const t& a, const t& b, const t& c, const t& d) { + return std::max(std::max(a,b),std::max(c,d)); + } + + //! Return the sign of a value. + template + inline T sign(const T& x) { + return (T)(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; + while (x>i) i<<=1; + return i; + } + + //! Return the modulo of a value. + /** + \param x Input value. + \param m Modulo value. + \note This modulo function accepts negative and floating-points modulo numbers, as well as variables of any type. + **/ + template + inline T mod(const T& x, const T& m) { + const double dx = (double)x, dm = (double)m; + return (T)(dx - dm * std::floor(dx / dm)); + } + inline int mod(const bool x, const bool m) { + return m?(x?1:0):0; + } + inline int mod(const unsigned char x, const unsigned char m) { + return x%m; + } + inline int mod(const char x, const char m) { +#if defined(CHAR_MAX) && CHAR_MAX==255 + return x%m; +#else + return x>=0?x%m:(x%m?m + x%m:0); +#endif + } + inline int mod(const unsigned short x, const unsigned short m) { + return x%m; + } + inline int mod(const short x, const short m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline int mod(const unsigned int x, const unsigned int m) { + return (int)(x%m); + } + inline int mod(const int x, const int m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline cimg_int64 mod(const cimg_uint64 x, const cimg_uint64 m) { + return x%m; + } + inline cimg_int64 mod(const cimg_int64 x, const cimg_int64 m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + + //! Return the min-mod of two values. + /** + \note minmod(\p a,\p b) is defined to be: + - minmod(\p a,\p b) = min(\p a,\p b), if \p a and \p b have the same sign. + - minmod(\p a,\p b) = 0, if \p a and \p b have different signs. + **/ + template + inline T minmod(const T& a, const T& b) { + return a*b<=0?0:(a>0?(a + inline T round(const T& x) { + return (T)std::floor((_cimg_Tfloat)x + 0.5f); + } + + //! Return rounded value. + /** + \param x Value to be rounded. + \param y Rounding precision. + \param rounding_type Type of rounding operation (\c 0 = nearest, \c -1 = backward, \c 1 = forward). + \return Rounded value, having the same type as input value \c x. + **/ + template + inline T round(const T& x, const double y, const int rounding_type=0) { + if (y<=0) return x; + if (y==1) switch (rounding_type) { + case 0 : return cimg::round(x); + case 1 : return (T)std::ceil((_cimg_Tfloat)x); + default : return (T)std::floor((_cimg_Tfloat)x); + } + const double sx = (double)x/y, floor = std::floor(sx), delta = sx - floor; + return (T)(y*(rounding_type<0?floor:rounding_type>0?std::ceil(sx):delta<0.5?floor:std::ceil(sx))); + } + + // Code to compute fast median from 2,3,5,7,9,13,25 and 49 values. + // (contribution by RawTherapee: http://rawtherapee.com/). + template + inline T median(T val0, T val1) { + return (val0 + val1)/2; + } + + template + inline T median(T val0, T val1, T val2) { + return std::max(std::min(val0,val1),std::min(val2,std::max(val0,val1))); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = std::max(val0,tmp); val1 = std::min(val1,val4); tmp = std::min(val1,val2); val2 = std::max(val1,val2); + val1 = tmp; tmp = std::min(val2,val3); + return std::max(val1,tmp); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6) { + T tmp = std::min(val0,val5); + val5 = std::max(val0,val5); val0 = tmp; tmp = std::min(val0,val3); val3 = std::max(val0,val3); val0 = tmp; + tmp = std::min(val1,val6); val6 = std::max(val1,val6); val1 = tmp; tmp = std::min(val2,val4); + val4 = std::max(val2,val4); val2 = tmp; val1 = std::max(val0,val1); tmp = std::min(val3,val5); + val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); + val3 = std::max(tmp,val3); val3 = std::min(val3,val6); tmp = std::min(val4,val5); val4 = std::max(val1,tmp); + tmp = std::min(val1,tmp); val3 = std::max(tmp,val3); + return std::min(val3,val4); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8) { + T tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val7 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); + val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val6,val7); + val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val3 = std::max(val0,val3); val5 = std::min(val5,val8); + val7 = std::max(val4,tmp); tmp = std::min(val4,tmp); val6 = std::max(val3,val6); + val4 = std::max(val1,tmp); val2 = std::min(val2,val5); val4 = std::min(val4,val7); + tmp = std::min(val4,val2); val2 = std::max(val4,val2); val4 = std::max(val6,tmp); + return std::min(val4,val2); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8, T val9, T val10, T val11, + T val12) { + T tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = tmp; tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; + tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val5,val8); + val8 = std::max(val5,val8); val5 = tmp; tmp = std::min(val0,val12); val12 = std::max(val0,val12); + val0 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val2,val3); val3 = std::max(val2,val3); val2 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val8,val11); + val11 = std::max(val8,val11); val8 = tmp; tmp = std::min(val7,val12); val12 = std::max(val7,val12); val7 = tmp; + tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val10,val11); val11 = std::max(val10,val11); val10 = tmp; tmp = std::min(val1,val4); + val4 = std::max(val1,val4); val1 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val8); val8 = std::max(val7,val8); val7 = tmp; val11 = std::min(val11,val12); + tmp = std::min(val4,val9); val9 = std::max(val4,val9); val4 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; + tmp = std::min(val5,val6); val6 = std::max(val5,val6); val5 = tmp; val8 = std::min(val8,val9); + val10 = std::min(val10,val11); tmp = std::min(val1,val7); val7 = std::max(val1,val7); val1 = tmp; + tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; val3 = std::max(val1,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; val8 = std::min(val8,val10); + val5 = std::max(val0,val5); val5 = std::max(val2,val5); tmp = std::min(val6,val8); val8 = std::max(val6,val8); + val5 = std::max(val3,val5); val7 = std::min(val7,val8); val6 = std::max(val4,tmp); tmp = std::min(val4,tmp); + val5 = std::max(tmp,val5); val6 = std::min(val6,val7); + return std::max(val5,val6); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, + T val5, T val6, T val7, T val8, T val9, + T val10, T val11, T val12, T val13, T val14, + T val15, T val16, T val17, T val18, T val19, + T val20, T val21, T val22, T val23, T val24) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); val2 = std::min(tmp,val3); + val3 = std::max(tmp,val3); tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; + tmp = std::min(val5,val7); val7 = std::max(val5,val7); val5 = std::min(tmp,val6); val6 = std::max(tmp,val6); + tmp = std::min(val9,val10); val10 = std::max(val9,val10); val9 = tmp; tmp = std::min(val8,val10); + val10 = std::max(val8,val10); val8 = std::min(tmp,val9); val9 = std::max(tmp,val9); + tmp = std::min(val12,val13); val13 = std::max(val12,val13); val12 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val15,val16); val16 = std::max(val15,val16); val15 = tmp; tmp = std::min(val14,val16); + val16 = std::max(val14,val16); val14 = std::min(tmp,val15); val15 = std::max(tmp,val15); + tmp = std::min(val18,val19); val19 = std::max(val18,val19); val18 = tmp; tmp = std::min(val17,val19); + val19 = std::max(val17,val19); val17 = std::min(tmp,val18); val18 = std::max(tmp,val18); + tmp = std::min(val21,val22); val22 = std::max(val21,val22); val21 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = std::min(tmp,val21); val21 = std::max(tmp,val21); + tmp = std::min(val23,val24); val24 = std::max(val23,val24); val23 = tmp; tmp = std::min(val2,val5); + val5 = std::max(val2,val5); val2 = tmp; tmp = std::min(val3,val6); val6 = std::max(val3,val6); val3 = tmp; + tmp = std::min(val0,val6); val6 = std::max(val0,val6); val0 = std::min(tmp,val3); val3 = std::max(tmp,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = std::min(tmp,val4); val4 = std::max(tmp,val4); tmp = std::min(val11,val14); + val14 = std::max(val11,val14); val11 = tmp; tmp = std::min(val8,val14); val14 = std::max(val8,val14); + val8 = std::min(tmp,val11); val11 = std::max(tmp,val11); tmp = std::min(val12,val15); + val15 = std::max(val12,val15); val12 = tmp; tmp = std::min(val9,val15); val15 = std::max(val9,val15); + val9 = std::min(tmp,val12); val12 = std::max(tmp,val12); tmp = std::min(val13,val16); + val16 = std::max(val13,val16); val13 = tmp; tmp = std::min(val10,val16); val16 = std::max(val10,val16); + val10 = std::min(tmp,val13); val13 = std::max(tmp,val13); tmp = std::min(val20,val23); + val23 = std::max(val20,val23); val20 = tmp; tmp = std::min(val17,val23); val23 = std::max(val17,val23); + val17 = std::min(tmp,val20); val20 = std::max(tmp,val20); tmp = std::min(val21,val24); + val24 = std::max(val21,val24); val21 = tmp; tmp = std::min(val18,val24); val24 = std::max(val18,val24); + val18 = std::min(tmp,val21); val21 = std::max(tmp,val21); tmp = std::min(val19,val22); + val22 = std::max(val19,val22); val19 = tmp; val17 = std::max(val8,val17); tmp = std::min(val9,val18); + val18 = std::max(val9,val18); val9 = tmp; tmp = std::min(val0,val18); val18 = std::max(val0,val18); + val9 = std::max(tmp,val9); tmp = std::min(val10,val19); val19 = std::max(val10,val19); val10 = tmp; + tmp = std::min(val1,val19); val19 = std::max(val1,val19); val1 = std::min(tmp,val10); + val10 = std::max(tmp,val10); tmp = std::min(val11,val20); val20 = std::max(val11,val20); val11 = tmp; + tmp = std::min(val2,val20); val20 = std::max(val2,val20); val11 = std::max(tmp,val11); + tmp = std::min(val12,val21); val21 = std::max(val12,val21); val12 = tmp; tmp = std::min(val3,val21); + val21 = std::max(val3,val21); val3 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val13,val22); val22 = std::max(val13,val22); val4 = std::min(val4,val22); + val13 = std::max(val4,tmp); tmp = std::min(val4,tmp); val4 = tmp; tmp = std::min(val14,val23); + val23 = std::max(val14,val23); val14 = tmp; tmp = std::min(val5,val23); val23 = std::max(val5,val23); + val5 = std::min(tmp,val14); val14 = std::max(tmp,val14); tmp = std::min(val15,val24); + val24 = std::max(val15,val24); val15 = tmp; val6 = std::min(val6,val24); tmp = std::min(val6,val15); + val15 = std::max(val6,val15); val6 = tmp; tmp = std::min(val7,val16); val7 = std::min(tmp,val19); + tmp = std::min(val13,val21); val15 = std::min(val15,val23); tmp = std::min(val7,tmp); + val7 = std::min(tmp,val15); val9 = std::max(val1,val9); val11 = std::max(val3,val11); + val17 = std::max(val5,val17); val17 = std::max(val11,val17); val17 = std::max(val9,val17); + tmp = std::min(val4,val10); val10 = std::max(val4,val10); val4 = tmp; tmp = std::min(val6,val12); + val12 = std::max(val6,val12); val6 = tmp; tmp = std::min(val7,val14); val14 = std::max(val7,val14); + val7 = tmp; tmp = std::min(val4,val6); val6 = std::max(val4,val6); val7 = std::max(tmp,val7); + tmp = std::min(val12,val14); val14 = std::max(val12,val14); val12 = tmp; val10 = std::min(val10,val14); + tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val10,val12); + val12 = std::max(val10,val12); val10 = std::max(val6,tmp); tmp = std::min(val6,tmp); + val17 = std::max(tmp,val17); tmp = std::min(val12,val17); val17 = std::max(val12,val17); val12 = tmp; + val7 = std::min(val7,val17); tmp = std::min(val7,val10); val10 = std::max(val7,val10); val7 = tmp; + tmp = std::min(val12,val18); val18 = std::max(val12,val18); val12 = std::max(val7,tmp); + val10 = std::min(val10,val18); tmp = std::min(val12,val20); val20 = std::max(val12,val20); val12 = tmp; + tmp = std::min(val10,val20); + return std::max(tmp,val12); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, + T val7, T val8, T val9, T val10, T val11, T val12, T val13, + T val14, T val15, T val16, T val17, T val18, T val19, T val20, + T val21, T val22, T val23, T val24, T val25, T val26, T val27, + T val28, T val29, T val30, T val31, T val32, T val33, T val34, + T val35, T val36, T val37, T val38, T val39, T val40, T val41, + T val42, T val43, T val44, T val45, T val46, T val47, T val48) { + T tmp = std::min(val0,val32); + val32 = std::max(val0,val32); val0 = tmp; tmp = std::min(val1,val33); val33 = std::max(val1,val33); val1 = tmp; + tmp = std::min(val2,val34); val34 = std::max(val2,val34); val2 = tmp; tmp = std::min(val3,val35); + val35 = std::max(val3,val35); val3 = tmp; tmp = std::min(val4,val36); val36 = std::max(val4,val36); val4 = tmp; + tmp = std::min(val5,val37); val37 = std::max(val5,val37); val5 = tmp; tmp = std::min(val6,val38); + val38 = std::max(val6,val38); val6 = tmp; tmp = std::min(val7,val39); val39 = std::max(val7,val39); val7 = tmp; + tmp = std::min(val8,val40); val40 = std::max(val8,val40); val8 = tmp; tmp = std::min(val9,val41); + val41 = std::max(val9,val41); val9 = tmp; tmp = std::min(val10,val42); val42 = std::max(val10,val42); + val10 = tmp; tmp = std::min(val11,val43); val43 = std::max(val11,val43); val11 = tmp; + tmp = std::min(val12,val44); val44 = std::max(val12,val44); val12 = tmp; tmp = std::min(val13,val45); + val45 = std::max(val13,val45); val13 = tmp; tmp = std::min(val14,val46); val46 = std::max(val14,val46); + val14 = tmp; tmp = std::min(val15,val47); val47 = std::max(val15,val47); val15 = tmp; + tmp = std::min(val16,val48); val48 = std::max(val16,val48); val16 = tmp; tmp = std::min(val0,val16); + val16 = std::max(val0,val16); val0 = tmp; tmp = std::min(val1,val17); val17 = std::max(val1,val17); + val1 = tmp; tmp = std::min(val2,val18); val18 = std::max(val2,val18); val2 = tmp; tmp = std::min(val3,val19); + val19 = std::max(val3,val19); val3 = tmp; tmp = std::min(val4,val20); val20 = std::max(val4,val20); val4 = tmp; + tmp = std::min(val5,val21); val21 = std::max(val5,val21); val5 = tmp; tmp = std::min(val6,val22); + val22 = std::max(val6,val22); val6 = tmp; tmp = std::min(val7,val23); val23 = std::max(val7,val23); val7 = tmp; + tmp = std::min(val8,val24); val24 = std::max(val8,val24); val8 = tmp; tmp = std::min(val9,val25); + val25 = std::max(val9,val25); val9 = tmp; tmp = std::min(val10,val26); val26 = std::max(val10,val26); + val10 = tmp; tmp = std::min(val11,val27); val27 = std::max(val11,val27); val11 = tmp; + tmp = std::min(val12,val28); val28 = std::max(val12,val28); val12 = tmp; tmp = std::min(val13,val29); + val29 = std::max(val13,val29); val13 = tmp; tmp = std::min(val14,val30); val30 = std::max(val14,val30); + val14 = tmp; tmp = std::min(val15,val31); val31 = std::max(val15,val31); val15 = tmp; + tmp = std::min(val32,val48); val48 = std::max(val32,val48); val32 = tmp; tmp = std::min(val16,val32); + val32 = std::max(val16,val32); val16 = tmp; tmp = std::min(val17,val33); val33 = std::max(val17,val33); + val17 = tmp; tmp = std::min(val18,val34); val34 = std::max(val18,val34); val18 = tmp; + tmp = std::min(val19,val35); val35 = std::max(val19,val35); val19 = tmp; tmp = std::min(val20,val36); + val36 = std::max(val20,val36); val20 = tmp; tmp = std::min(val21,val37); val37 = std::max(val21,val37); + val21 = tmp; tmp = std::min(val22,val38); val38 = std::max(val22,val38); val22 = tmp; + tmp = std::min(val23,val39); val39 = std::max(val23,val39); val23 = tmp; tmp = std::min(val24,val40); + val40 = std::max(val24,val40); val24 = tmp; tmp = std::min(val25,val41); val41 = std::max(val25,val41); + val25 = tmp; tmp = std::min(val26,val42); val42 = std::max(val26,val42); val26 = tmp; + tmp = std::min(val27,val43); val43 = std::max(val27,val43); val27 = tmp; tmp = std::min(val28,val44); + val44 = std::max(val28,val44); val28 = tmp; tmp = std::min(val29,val45); val45 = std::max(val29,val45); + val29 = tmp; tmp = std::min(val30,val46); val46 = std::max(val30,val46); val30 = tmp; + tmp = std::min(val31,val47); val47 = std::max(val31,val47); val31 = tmp; tmp = std::min(val0,val8); + val8 = std::max(val0,val8); val0 = tmp; tmp = std::min(val1,val9); val9 = std::max(val1,val9); val1 = tmp; + tmp = std::min(val2,val10); val10 = std::max(val2,val10); val2 = tmp; tmp = std::min(val3,val11); + val11 = std::max(val3,val11); val3 = tmp; tmp = std::min(val4,val12); val12 = std::max(val4,val12); val4 = tmp; + tmp = std::min(val5,val13); val13 = std::max(val5,val13); val5 = tmp; tmp = std::min(val6,val14); + val14 = std::max(val6,val14); val6 = tmp; tmp = std::min(val7,val15); val15 = std::max(val7,val15); val7 = tmp; + tmp = std::min(val16,val24); val24 = std::max(val16,val24); val16 = tmp; tmp = std::min(val17,val25); + val25 = std::max(val17,val25); val17 = tmp; tmp = std::min(val18,val26); val26 = std::max(val18,val26); + val18 = tmp; tmp = std::min(val19,val27); val27 = std::max(val19,val27); val19 = tmp; + tmp = std::min(val20,val28); val28 = std::max(val20,val28); val20 = tmp; tmp = std::min(val21,val29); + val29 = std::max(val21,val29); val21 = tmp; tmp = std::min(val22,val30); val30 = std::max(val22,val30); + val22 = tmp; tmp = std::min(val23,val31); val31 = std::max(val23,val31); val23 = tmp; + tmp = std::min(val32,val40); val40 = std::max(val32,val40); val32 = tmp; tmp = std::min(val33,val41); + val41 = std::max(val33,val41); val33 = tmp; tmp = std::min(val34,val42); val42 = std::max(val34,val42); + val34 = tmp; tmp = std::min(val35,val43); val43 = std::max(val35,val43); val35 = tmp; + tmp = std::min(val36,val44); val44 = std::max(val36,val44); val36 = tmp; tmp = std::min(val37,val45); + val45 = std::max(val37,val45); val37 = tmp; tmp = std::min(val38,val46); val46 = std::max(val38,val46); + val38 = tmp; tmp = std::min(val39,val47); val47 = std::max(val39,val47); val39 = tmp; + tmp = std::min(val8,val32); val32 = std::max(val8,val32); val8 = tmp; tmp = std::min(val9,val33); + val33 = std::max(val9,val33); val9 = tmp; tmp = std::min(val10,val34); val34 = std::max(val10,val34); + val10 = tmp; tmp = std::min(val11,val35); val35 = std::max(val11,val35); val11 = tmp; + tmp = std::min(val12,val36); val36 = std::max(val12,val36); val12 = tmp; tmp = std::min(val13,val37); + val37 = std::max(val13,val37); val13 = tmp; tmp = std::min(val14,val38); val38 = std::max(val14,val38); + val14 = tmp; tmp = std::min(val15,val39); val39 = std::max(val15,val39); val15 = tmp; + tmp = std::min(val24,val48); val48 = std::max(val24,val48); val24 = tmp; tmp = std::min(val8,val16); + val16 = std::max(val8,val16); val8 = tmp; tmp = std::min(val9,val17); val17 = std::max(val9,val17); + val9 = tmp; tmp = std::min(val10,val18); val18 = std::max(val10,val18); val10 = tmp; + tmp = std::min(val11,val19); val19 = std::max(val11,val19); val11 = tmp; tmp = std::min(val12,val20); + val20 = std::max(val12,val20); val12 = tmp; tmp = std::min(val13,val21); val21 = std::max(val13,val21); + val13 = tmp; tmp = std::min(val14,val22); val22 = std::max(val14,val22); val14 = tmp; + tmp = std::min(val15,val23); val23 = std::max(val15,val23); val15 = tmp; tmp = std::min(val24,val32); + val32 = std::max(val24,val32); val24 = tmp; tmp = std::min(val25,val33); val33 = std::max(val25,val33); + val25 = tmp; tmp = std::min(val26,val34); val34 = std::max(val26,val34); val26 = tmp; + tmp = std::min(val27,val35); val35 = std::max(val27,val35); val27 = tmp; tmp = std::min(val28,val36); + val36 = std::max(val28,val36); val28 = tmp; tmp = std::min(val29,val37); val37 = std::max(val29,val37); + val29 = tmp; tmp = std::min(val30,val38); val38 = std::max(val30,val38); val30 = tmp; + tmp = std::min(val31,val39); val39 = std::max(val31,val39); val31 = tmp; tmp = std::min(val40,val48); + val48 = std::max(val40,val48); val40 = tmp; tmp = std::min(val0,val4); val4 = std::max(val0,val4); + val0 = tmp; tmp = std::min(val1,val5); val5 = std::max(val1,val5); val1 = tmp; tmp = std::min(val2,val6); + val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val8,val12); val12 = std::max(val8,val12); val8 = tmp; tmp = std::min(val9,val13); + val13 = std::max(val9,val13); val9 = tmp; tmp = std::min(val10,val14); val14 = std::max(val10,val14); + val10 = tmp; tmp = std::min(val11,val15); val15 = std::max(val11,val15); val11 = tmp; + tmp = std::min(val16,val20); val20 = std::max(val16,val20); val16 = tmp; tmp = std::min(val17,val21); + val21 = std::max(val17,val21); val17 = tmp; tmp = std::min(val18,val22); val22 = std::max(val18,val22); + val18 = tmp; tmp = std::min(val19,val23); val23 = std::max(val19,val23); val19 = tmp; + tmp = std::min(val24,val28); val28 = std::max(val24,val28); val24 = tmp; tmp = std::min(val25,val29); + val29 = std::max(val25,val29); val25 = tmp; tmp = std::min(val26,val30); val30 = std::max(val26,val30); + val26 = tmp; tmp = std::min(val27,val31); val31 = std::max(val27,val31); val27 = tmp; + tmp = std::min(val32,val36); val36 = std::max(val32,val36); val32 = tmp; tmp = std::min(val33,val37); + val37 = std::max(val33,val37); val33 = tmp; tmp = std::min(val34,val38); val38 = std::max(val34,val38); + val34 = tmp; tmp = std::min(val35,val39); val39 = std::max(val35,val39); val35 = tmp; + tmp = std::min(val40,val44); val44 = std::max(val40,val44); val40 = tmp; tmp = std::min(val41,val45); + val45 = std::max(val41,val45); val41 = tmp; tmp = std::min(val42,val46); val46 = std::max(val42,val46); + val42 = tmp; tmp = std::min(val43,val47); val47 = std::max(val43,val47); val43 = tmp; + tmp = std::min(val4,val32); val32 = std::max(val4,val32); val4 = tmp; tmp = std::min(val5,val33); + val33 = std::max(val5,val33); val5 = tmp; tmp = std::min(val6,val34); val34 = std::max(val6,val34); + val6 = tmp; tmp = std::min(val7,val35); val35 = std::max(val7,val35); val7 = tmp; + tmp = std::min(val12,val40); val40 = std::max(val12,val40); val12 = tmp; tmp = std::min(val13,val41); + val41 = std::max(val13,val41); val13 = tmp; tmp = std::min(val14,val42); val42 = std::max(val14,val42); + val14 = tmp; tmp = std::min(val15,val43); val43 = std::max(val15,val43); val15 = tmp; + tmp = std::min(val20,val48); val48 = std::max(val20,val48); val20 = tmp; tmp = std::min(val4,val16); + val16 = std::max(val4,val16); val4 = tmp; tmp = std::min(val5,val17); val17 = std::max(val5,val17); + val5 = tmp; tmp = std::min(val6,val18); val18 = std::max(val6,val18); val6 = tmp; + tmp = std::min(val7,val19); val19 = std::max(val7,val19); val7 = tmp; tmp = std::min(val12,val24); + val24 = std::max(val12,val24); val12 = tmp; tmp = std::min(val13,val25); val25 = std::max(val13,val25); + val13 = tmp; tmp = std::min(val14,val26); val26 = std::max(val14,val26); val14 = tmp; + tmp = std::min(val15,val27); val27 = std::max(val15,val27); val15 = tmp; tmp = std::min(val20,val32); + val32 = std::max(val20,val32); val20 = tmp; tmp = std::min(val21,val33); val33 = std::max(val21,val33); + val21 = tmp; tmp = std::min(val22,val34); val34 = std::max(val22,val34); val22 = tmp; + tmp = std::min(val23,val35); val35 = std::max(val23,val35); val23 = tmp; tmp = std::min(val28,val40); + val40 = std::max(val28,val40); val28 = tmp; tmp = std::min(val29,val41); val41 = std::max(val29,val41); + val29 = tmp; tmp = std::min(val30,val42); val42 = std::max(val30,val42); val30 = tmp; + tmp = std::min(val31,val43); val43 = std::max(val31,val43); val31 = tmp; tmp = std::min(val36,val48); + val48 = std::max(val36,val48); val36 = tmp; tmp = std::min(val4,val8); val8 = std::max(val4,val8); + val4 = tmp; tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val7,val11); val11 = std::max(val7,val11); val7 = tmp; + tmp = std::min(val12,val16); val16 = std::max(val12,val16); val12 = tmp; tmp = std::min(val13,val17); + val17 = std::max(val13,val17); val13 = tmp; tmp = std::min(val14,val18); val18 = std::max(val14,val18); + val14 = tmp; tmp = std::min(val15,val19); val19 = std::max(val15,val19); val15 = tmp; + tmp = std::min(val20,val24); val24 = std::max(val20,val24); val20 = tmp; tmp = std::min(val21,val25); + val25 = std::max(val21,val25); val21 = tmp; tmp = std::min(val22,val26); val26 = std::max(val22,val26); + val22 = tmp; tmp = std::min(val23,val27); val27 = std::max(val23,val27); val23 = tmp; + tmp = std::min(val28,val32); val32 = std::max(val28,val32); val28 = tmp; tmp = std::min(val29,val33); + val33 = std::max(val29,val33); val29 = tmp; tmp = std::min(val30,val34); val34 = std::max(val30,val34); + val30 = tmp; tmp = std::min(val31,val35); val35 = std::max(val31,val35); val31 = tmp; + tmp = std::min(val36,val40); val40 = std::max(val36,val40); val36 = tmp; tmp = std::min(val37,val41); + val41 = std::max(val37,val41); val37 = tmp; tmp = std::min(val38,val42); val42 = std::max(val38,val42); + val38 = tmp; tmp = std::min(val39,val43); val43 = std::max(val39,val43); val39 = tmp; + tmp = std::min(val44,val48); val48 = std::max(val44,val48); val44 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val1,val3); val3 = std::max(val1,val3); val1 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val5,val7); + val7 = std::max(val5,val7); val5 = tmp; tmp = std::min(val8,val10); val10 = std::max(val8,val10); val8 = tmp; + tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; tmp = std::min(val12,val14); + val14 = std::max(val12,val14); val12 = tmp; tmp = std::min(val13,val15); val15 = std::max(val13,val15); + val13 = tmp; tmp = std::min(val16,val18); val18 = std::max(val16,val18); val16 = tmp; + tmp = std::min(val17,val19); val19 = std::max(val17,val19); val17 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = tmp; tmp = std::min(val21,val23); val23 = std::max(val21,val23); + val21 = tmp; tmp = std::min(val24,val26); val26 = std::max(val24,val26); val24 = tmp; + tmp = std::min(val25,val27); val27 = std::max(val25,val27); val25 = tmp; tmp = std::min(val28,val30); + val30 = std::max(val28,val30); val28 = tmp; tmp = std::min(val29,val31); val31 = std::max(val29,val31); + val29 = tmp; tmp = std::min(val32,val34); val34 = std::max(val32,val34); val32 = tmp; + tmp = std::min(val33,val35); val35 = std::max(val33,val35); val33 = tmp; tmp = std::min(val36,val38); + val38 = std::max(val36,val38); val36 = tmp; tmp = std::min(val37,val39); val39 = std::max(val37,val39); + val37 = tmp; tmp = std::min(val40,val42); val42 = std::max(val40,val42); val40 = tmp; + tmp = std::min(val41,val43); val43 = std::max(val41,val43); val41 = tmp; tmp = std::min(val44,val46); + val46 = std::max(val44,val46); val44 = tmp; tmp = std::min(val45,val47); val47 = std::max(val45,val47); + val45 = tmp; tmp = std::min(val2,val32); val32 = std::max(val2,val32); val2 = tmp; tmp = std::min(val3,val33); + val33 = std::max(val3,val33); val3 = tmp; tmp = std::min(val6,val36); val36 = std::max(val6,val36); val6 = tmp; + tmp = std::min(val7,val37); val37 = std::max(val7,val37); val7 = tmp; tmp = std::min(val10,val40); + val40 = std::max(val10,val40); val10 = tmp; tmp = std::min(val11,val41); val41 = std::max(val11,val41); + val11 = tmp; tmp = std::min(val14,val44); val44 = std::max(val14,val44); val14 = tmp; + tmp = std::min(val15,val45); val45 = std::max(val15,val45); val15 = tmp; tmp = std::min(val18,val48); + val48 = std::max(val18,val48); val18 = tmp; tmp = std::min(val2,val16); val16 = std::max(val2,val16); + val2 = tmp; tmp = std::min(val3,val17); val17 = std::max(val3,val17); val3 = tmp; + tmp = std::min(val6,val20); val20 = std::max(val6,val20); val6 = tmp; tmp = std::min(val7,val21); + val21 = std::max(val7,val21); val7 = tmp; tmp = std::min(val10,val24); val24 = std::max(val10,val24); + val10 = tmp; tmp = std::min(val11,val25); val25 = std::max(val11,val25); val11 = tmp; + tmp = std::min(val14,val28); val28 = std::max(val14,val28); val14 = tmp; tmp = std::min(val15,val29); + val29 = std::max(val15,val29); val15 = tmp; tmp = std::min(val18,val32); val32 = std::max(val18,val32); + val18 = tmp; tmp = std::min(val19,val33); val33 = std::max(val19,val33); val19 = tmp; + tmp = std::min(val22,val36); val36 = std::max(val22,val36); val22 = tmp; tmp = std::min(val23,val37); + val37 = std::max(val23,val37); val23 = tmp; tmp = std::min(val26,val40); val40 = std::max(val26,val40); + val26 = tmp; tmp = std::min(val27,val41); val41 = std::max(val27,val41); val27 = tmp; + tmp = std::min(val30,val44); val44 = std::max(val30,val44); val30 = tmp; tmp = std::min(val31,val45); + val45 = std::max(val31,val45); val31 = tmp; tmp = std::min(val34,val48); val48 = std::max(val34,val48); + val34 = tmp; tmp = std::min(val2,val8); val8 = std::max(val2,val8); val2 = tmp; tmp = std::min(val3,val9); + val9 = std::max(val3,val9); val3 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val13); val13 = std::max(val7,val13); val7 = tmp; tmp = std::min(val10,val16); + val16 = std::max(val10,val16); val10 = tmp; tmp = std::min(val11,val17); val17 = std::max(val11,val17); + val11 = tmp; tmp = std::min(val14,val20); val20 = std::max(val14,val20); val14 = tmp; + tmp = std::min(val15,val21); val21 = std::max(val15,val21); val15 = tmp; tmp = std::min(val18,val24); + val24 = std::max(val18,val24); val18 = tmp; tmp = std::min(val19,val25); val25 = std::max(val19,val25); + val19 = tmp; tmp = std::min(val22,val28); val28 = std::max(val22,val28); val22 = tmp; + tmp = std::min(val23,val29); val29 = std::max(val23,val29); val23 = tmp; tmp = std::min(val26,val32); + val32 = std::max(val26,val32); val26 = tmp; tmp = std::min(val27,val33); val33 = std::max(val27,val33); + val27 = tmp; tmp = std::min(val30,val36); val36 = std::max(val30,val36); val30 = tmp; + tmp = std::min(val31,val37); val37 = std::max(val31,val37); val31 = tmp; tmp = std::min(val34,val40); + val40 = std::max(val34,val40); val34 = tmp; tmp = std::min(val35,val41); val41 = std::max(val35,val41); + val35 = tmp; tmp = std::min(val38,val44); val44 = std::max(val38,val44); val38 = tmp; + tmp = std::min(val39,val45); val45 = std::max(val39,val45); val39 = tmp; tmp = std::min(val42,val48); + val48 = std::max(val42,val48); val42 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); + val2 = tmp; tmp = std::min(val3,val5); val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val6,val8); + val8 = std::max(val6,val8); val6 = tmp; tmp = std::min(val7,val9); val9 = std::max(val7,val9); val7 = tmp; + tmp = std::min(val10,val12); val12 = std::max(val10,val12); val10 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = tmp; tmp = std::min(val14,val16); val16 = std::max(val14,val16); + val14 = tmp; tmp = std::min(val15,val17); val17 = std::max(val15,val17); val15 = tmp; + tmp = std::min(val18,val20); val20 = std::max(val18,val20); val18 = tmp; tmp = std::min(val19,val21); + val21 = std::max(val19,val21); val19 = tmp; tmp = std::min(val22,val24); val24 = std::max(val22,val24); + val22 = tmp; tmp = std::min(val23,val25); val25 = std::max(val23,val25); val23 = tmp; + tmp = std::min(val26,val28); val28 = std::max(val26,val28); val26 = tmp; tmp = std::min(val27,val29); + val29 = std::max(val27,val29); val27 = tmp; tmp = std::min(val30,val32); val32 = std::max(val30,val32); + val30 = tmp; tmp = std::min(val31,val33); val33 = std::max(val31,val33); val31 = tmp; + tmp = std::min(val34,val36); val36 = std::max(val34,val36); val34 = tmp; tmp = std::min(val35,val37); + val37 = std::max(val35,val37); val35 = tmp; tmp = std::min(val38,val40); val40 = std::max(val38,val40); + val38 = tmp; tmp = std::min(val39,val41); val41 = std::max(val39,val41); val39 = tmp; + tmp = std::min(val42,val44); val44 = std::max(val42,val44); val42 = tmp; tmp = std::min(val43,val45); + val45 = std::max(val43,val45); val43 = tmp; tmp = std::min(val46,val48); val48 = std::max(val46,val48); + val46 = tmp; val1 = std::max(val0,val1); val3 = std::max(val2,val3); val5 = std::max(val4,val5); + val7 = std::max(val6,val7); val9 = std::max(val8,val9); val11 = std::max(val10,val11); + val13 = std::max(val12,val13); val15 = std::max(val14,val15); val17 = std::max(val16,val17); + val19 = std::max(val18,val19); val21 = std::max(val20,val21); val23 = std::max(val22,val23); + val24 = std::min(val24,val25); val26 = std::min(val26,val27); val28 = std::min(val28,val29); + val30 = std::min(val30,val31); val32 = std::min(val32,val33); val34 = std::min(val34,val35); + val36 = std::min(val36,val37); val38 = std::min(val38,val39); val40 = std::min(val40,val41); + val42 = std::min(val42,val43); val44 = std::min(val44,val45); val46 = std::min(val46,val47); + val32 = std::max(val1,val32); val34 = std::max(val3,val34); val36 = std::max(val5,val36); + val38 = std::max(val7,val38); val9 = std::min(val9,val40); val11 = std::min(val11,val42); + val13 = std::min(val13,val44); val15 = std::min(val15,val46); val17 = std::min(val17,val48); + val24 = std::max(val9,val24); val26 = std::max(val11,val26); val28 = std::max(val13,val28); + val30 = std::max(val15,val30); val17 = std::min(val17,val32); val19 = std::min(val19,val34); + val21 = std::min(val21,val36); val23 = std::min(val23,val38); val24 = std::max(val17,val24); + val26 = std::max(val19,val26); val21 = std::min(val21,val28); val23 = std::min(val23,val30); + val24 = std::max(val21,val24); val23 = std::min(val23,val26); + return std::max(val23,val24); + } + + //! Return sqrt(x^2 + y^2). + template + inline T hypot(const T x, const T y) { + return std::sqrt(x*x + y*y); + } + + template + inline T hypot(const T x, const T y, const T z) { + return std::sqrt(x*x + y*y + z*z); + } + + template + inline T _hypot(const T x, const T y) { // Slower but more precise version + T nx = cimg::abs(x), ny = cimg::abs(y), t; + if (nx0) { t/=nx; return nx*std::sqrt(1 + t*t); } + return 0; + } + + //! Return the factorial of n + inline double factorial(const int n) { + if (n<0) return cimg::type::nan(); + if (n<2) return 1; + double res = 2; + for (int i = 3; i<=n; ++i) res*=i; + return res; + } + + //! Return the number of permutations of k objects in a set of n objects. + inline double permutations(const int k, const int n, const bool with_order) { + if (n<0 || k<0) return cimg::type::nan(); + if (k>n) return 0; + double res = 1; + for (int i = n; i>=n - k + 1; --i) res*=i; + return with_order?res:res/cimg::factorial(k); + } + + inline double _fibonacci(int exp) { + double + base = (1 + std::sqrt(5.))/2, + result = 1/std::sqrt(5.); + while (exp) { + if (exp&1) result*=base; + exp>>=1; + base*=base; + } + return result; + } + + //! Calculate fibonacci number. + // (Precise up to n = 78, less precise for n>78). + inline double fibonacci(const int n) { + if (n<0) return cimg::type::nan(); + if (n<3) return 1; + if (n<11) { + cimg_uint64 fn1 = 1, fn2 = 1, fn = 0; + for (int i = 3; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + if (n<75) // precise up to n = 74, faster than the integer calculation above for n>10 + return (double)((cimg_uint64)(_fibonacci(n) + 0.5)); + + if (n<94) { // precise up to n = 78, less precise for n>78 up to n = 93, overflows for n>93 + cimg_uint64 + fn1 = (cimg_uint64)1304969544928657ULL, + fn2 = (cimg_uint64)806515533049393ULL, + fn = 0; + for (int i = 75; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + return _fibonacci(n); // Not precise, but better than the wrong overflowing calculation + } + + //! Calculate greatest common divisor. + inline long gcd(long a, long b) { + while (a) { const long c = a; a = b%a; b = c; } + return b; + } + + //! Convert Ascii character to lower case. + inline char lowercase(const char x) { + return (char)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + inline double lowercase(const double x) { + return (double)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + + //! Convert C-string to lower case. + inline void lowercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = lowercase(*ptr); + } + + //! Convert Ascii character to upper case. + inline char uppercase(const char x) { + return (char)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + inline double uppercase(const double x) { + return (double)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + //! Convert C-string to upper case. + inline void uppercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uppercase(*ptr); + } + + //! Return \c true if input character is blank (space, tab, or non-printable character). + inline bool is_blank(const char c) { + return c>=0 && c<=' '; + } + + //! Read value in a C-string. + /** + \param str C-string containing the float value to read. + \return Read value. + \note Same as std::atof() extended to manage the retrieval of fractions from C-strings, + as in "1/2". + **/ + inline double atof(const char *const str) { + double x = 0, y = 1; + return str && cimg_sscanf(str,"%lf/%lf",&x,&y)>0?x/y:0; + } + + //! Compare the first \p l characters of two C-strings, ignoring the case. + /** + \param str1 C-string. + \param str2 C-string. + \param l Number of characters to compare. + \return \c 0 if the two strings are equal, something else otherwise. + \note This function has to be defined since it is not provided by all C++-compilers (not ANSI). + **/ + inline int strncasecmp(const char *const str1, const char *const str2, const int l) { + if (!l) return 0; + if (!str1) return str2?-1:0; + const char *nstr1 = str1, *nstr2 = str2; + int k, diff = 0; for (k = 0; kp && str[q]==delimiter; ) { --q; if (!is_iterative) break; } + } + const int n = q - p + 1; + if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; } + return false; + } + + //! Remove white spaces on the start and/or end of a C-string. + inline bool strpare(char *const str, const bool is_symmetric, const bool is_iterative) { + if (!str) return false; + const int l = (int)std::strlen(str); + int p, q; + if (is_symmetric) for (p = 0, q = l - 1; 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; } + return false; + } + + //! Replace reserved characters (for Windows filename) by another character. + /** + \param[in,out] str C-string to work with (modified at output). + \param[in] c Replacement character. + **/ + inline void strwindows_reserved(char *const str, const char c='_') { + for (char *s = str; *s; ++s) { + const char i = *s; + if (i=='<' || i=='>' || i==':' || i=='\"' || i=='/' || i=='\\' || i=='|' || i=='?' || i=='*') *s = c; + } + } + + //! Replace escape sequences in C-strings by their binary Ascii values. + /** + \param[in,out] str C-string to work with (modified at output). + **/ + inline void strunescape(char *const str) { +#define cimg_strunescape(ci,co) case ci : *nd = co; ++ns; break; + unsigned int val = 0; + for (char *ns = str, *nd = str; *ns || (bool)(*nd=0); ++nd) if (*ns=='\\') switch (*(++ns)) { + cimg_strunescape('a','\a'); + cimg_strunescape('b','\b'); + cimg_strunescape('e',0x1B); + cimg_strunescape('f','\f'); + cimg_strunescape('n','\n'); + cimg_strunescape('r','\r'); + cimg_strunescape('t','\t'); + cimg_strunescape('v','\v'); + cimg_strunescape('\\','\\'); + cimg_strunescape('\'','\''); + cimg_strunescape('\"','\"'); + cimg_strunescape('\?','\?'); + case 0 : *nd = 0; break; + case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : + cimg_sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns; + *nd = (char)val; break; + case 'x' : + cimg_sscanf(++ns,"%x",&val); + while ((*ns>='0' && *ns<='9') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns; + *nd = (char)val; break; + default : *nd = *(ns++); + } else *nd = *(ns++); + } + + // Return a temporary string describing the size of a memory buffer. + inline const char *strbuffersize(const cimg_ulong size); + + // Return string that identifies the running OS. + inline const char *stros() { +#if defined(linux) || defined(__linux) || defined(__linux__) + static const char *const str = "Linux"; +#elif defined(sun) || defined(__sun) + static const char *const str = "Sun OS"; +#elif defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined (__DragonFly__) + static const char *const str = "BSD"; +#elif defined(sgi) || defined(__sgi) + static const char *const str = "Irix"; +#elif defined(__MACOSX__) || defined(__APPLE__) + static const char *const str = "Mac OS"; +#elif defined(unix) || defined(__unix) || defined(__unix__) + static const char *const str = "Generic Unix"; +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \ + defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + static const char *const str = "Windows"; +#else + const char + *const _str1 = std::getenv("OSTYPE"), + *const _str2 = _str1?_str1:std::getenv("OS"), + *const str = _str2?_str2:"Unknown OS"; +#endif + return str; + } + + //! Return the basename of a filename. + inline const char* basename(const char *const s, const char separator=cimg_file_separator) { + const char *p = 0, *np = s; + while (np>=s && (p=np)) np = std::strchr(np,separator) + 1; + return p; + } + + // Return a random filename. + inline const char* filenamerand() { + cimg::mutex(6); + static char randomid[9]; + for (unsigned int k = 0; k<8; ++k) { + const int v = (int)cimg::rand(65535)%3; + randomid[k] = (char)(v==0?('0' + ((int)cimg::rand(65535)%10)): + (v==1?('a' + ((int)cimg::rand(65535)%26)): + ('A' + ((int)cimg::rand(65535)%26)))); + } + cimg::mutex(6,0); + return randomid; + } + + // Convert filename as a Windows-style filename (short path name). + inline void winformat_string(char *const str) { + if (str && *str) { +#if cimg_OS==2 + char *const nstr = new char[MAX_PATH]; + if (GetShortPathNameA(str,nstr,MAX_PATH)) std::strcpy(str,nstr); + delete[] nstr; +#endif + } + } + + // Open a file (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. + /** + \param path Path of the filename to open. + \param mode C-string describing the opening mode. + \return Opened file. + \note Same as std::fopen() but throw a \c CImgIOException when + the specified file cannot be opened, instead of returning \c 0. + **/ + inline std::FILE *fopen(const char *const path, const char *const mode) { + if (!path) + throw CImgArgumentException("cimg::fopen(): Specified file path is (null)."); + if (!mode) + throw CImgArgumentException("cimg::fopen(): File '%s', specified mode is (null).", + path); + std::FILE *res = 0; + if (*path=='-' && (!path[1] || path[1]=='.')) { + res = (*mode=='r')?cimg::_stdin():cimg::_stdout(); +#if cimg_OS==2 + if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode +#ifdef __BORLANDC__ + if (setmode(_fileno(res),0x8000)==-1) res = 0; +#else + if (_setmode(_fileno(res),0x8000)==-1) res = 0; +#endif + } +#endif + } else res = cimg::std_fopen(path,mode); + if (!res) throw CImgIOException("cimg::fopen(): Failed to open file '%s' with mode '%s'.", + path,mode); + return res; + } + + //! Close a file. + /** + \param file File to close. + \return \c 0 if file has been closed properly, something else otherwise. + \note Same as std::fclose() but display a warning message if + the file has not been closed properly. + **/ + inline int fclose(std::FILE *file) { + if (!file) { warn("cimg::fclose(): Specified file is (null)."); return 0; } + if (file==cimg::_stdin(false) || file==cimg::_stdout(false)) return 0; + const int errn = std::fclose(file); + if (errn!=0) warn("cimg::fclose(): Error code %d returned during file closing.", + errn); + return errn; + } + + //! Version of 'fseek()' that supports >=64bits offsets everywhere (for Windows). + inline int fseek(FILE *stream, cimg_long offset, int origin) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return _fseeki64(stream,(__int64)offset,origin); +#else + return std::fseek(stream,offset,origin); +#endif + } + + //! Version of 'ftell()' that supports >=64bits offsets everywhere (for Windows). + inline cimg_long ftell(FILE *stream) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return (cimg_long)_ftelli64(stream); +#else + return (cimg_long)std::ftell(stream); +#endif + } + + //! Check if a path is a directory. + /** + \param path Specified path to test. + **/ + inline bool is_directory(const char *const path) { + if (!path || !*path) return false; +#if cimg_OS==1 + struct stat st_buf; + return (!stat(path,&st_buf) && S_ISDIR(st_buf.st_mode)); +#elif cimg_OS==2 + const unsigned int res = (unsigned int)GetFileAttributesA(path); + return res==INVALID_FILE_ATTRIBUTES?false:(res&16); +#else + return false; +#endif + } + + //! Check if a path is a file. + /** + \param path Specified path to test. + **/ + inline bool is_file(const char *const path) { + if (!path || !*path) return false; + std::FILE *const file = cimg::std_fopen(path,"rb"); + if (!file) return false; + cimg::fclose(file); + return !is_directory(path); + } + + //! Get file size. + /** + \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 = cimg::std_fopen(filename,"rb"); + if (!file) return (cimg_int64)-1; + std::fseek(file,0,SEEK_END); + const cimg_int64 siz = (cimg_int64)std::ftell(file); + cimg::fclose(file); + return siz; + } + + //! Get last write time of a given file or directory (multiple-attributes version). + /** + \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). + \param nb_attr Number of attributes to read/write. + \return Latest read attribute. + **/ + template + inline int fdate(const char *const path, T *attr, const unsigned int nb_attr) { +#define _cimg_fdate_err() for (unsigned int i = 0; i + inline int date(T *attr, const unsigned int nb_attr) { + int res = -1; + cimg::mutex(6); +#if cimg_OS==2 + 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); + attr[i] = (T)res; + } +#endif + cimg::mutex(6,0); + return res; + } + + //! 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. + **/ + 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 dcraw binary. + inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c wget binary. + inline const char *wget_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c curl binary. + inline const char *curl_path(const char *const user_path=0, const bool reinit_path=false); + + //! Split filename into two C-strings \c body and \c extension. + /** + filename and body must not overlap! + **/ + inline const char *split_filename(const char *const filename, char *const body=0) { + if (!filename) { if (body) *body = 0; return 0; } + const char *p = 0; for (const char *np = filename; np>=filename && (p=np); np = std::strchr(np,'.') + 1) {} + if (p==filename) { + if (body) std::strcpy(body,filename); + return filename + std::strlen(filename); + } + const unsigned int l = (unsigned int)(p - filename - 1); + if (body) { if (l) std::memcpy(body,filename,l); body[l] = 0; } + return p; + } + + //! Generate a numbered version of a filename. + inline char* number_filename(const char *const filename, const int number, + const unsigned int digits, char *const str) { + if (!filename) { if (str) *str = 0; return 0; } + char *const format = new char[1024], *const body = new char[1024]; + const char *const ext = cimg::split_filename(filename,body); + if (*ext) cimg_snprintf(format,1024,"%%s_%%.%ud.%%s",digits); + else cimg_snprintf(format,1024,"%%s_%%.%ud",digits); + cimg_sprintf(str,format,body,number,ext); + delete[] format; delete[] body; + return str; + } + + //! Read data from file. + /** + \param[out] ptr Pointer to memory buffer that will contain the binary data read from file. + \param nmemb Number of elements to read. + \param stream File to read data from. + \return Number of read elements. + \note Same as std::fread() but may display warning message if all elements could not be read. + **/ + template + inline size_t fread(T *const ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fread(): Invalid reading request of %u %s%s from file %p to buffer %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",stream,ptr); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_read = nmemb, al_read = 0, l_to_read = 0, l_al_read = 0; + do { + l_to_read = (to_read*sizeof(T))0); + if (to_read>0) + warn("cimg::fread(): Only %lu/%lu elements could be read from file.", + (unsigned long)al_read,(unsigned long)nmemb); + return al_read; + } + + //! Write data to file. + /** + \param ptr Pointer to memory buffer containing the binary data to write on file. + \param nmemb Number of elements to write. + \param[out] stream File to write data on. + \return Number of written elements. + \note Similar to std::fwrite but may display warning messages if all elements could not be written. + **/ + template + inline size_t fwrite(const T *ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fwrite(): Invalid writing request of %u %s%s from buffer %p to file %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",ptr,stream); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_write = nmemb, al_write = 0, l_to_write = 0, l_al_write = 0; + do { + l_to_write = (to_write*sizeof(T))0); + if (to_write>0) + warn("cimg::fwrite(): Only %lu/%lu elements could be written in file.", + (unsigned long)al_write,(unsigned long)nmemb); + return al_write; + } + + //! Create an empty file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + **/ + inline void fempty(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::fempty(): Specified filename is (null)."); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!file) cimg::fclose(nfile); + } + + // Try to guess format from an image file. + inline const char *ftype(std::FILE *const file, const char *const filename); + + // Load file from network as a local temporary file. + inline char *load_network(const char *const url, char *const filename_local, + const unsigned int timeout=0, const bool try_fallback=false, + const char *const referer=0); + + //! Return options specified on the command line. + inline const char* option(const char *const name, const int argc, const char *const *const argv, + const char *const defaut, const char *const usage, const bool reset_static) { + static bool first = true, visu = false; + if (reset_static) { first = true; return 0; } + const char *res = 0; + if (first) { + first = false; + visu = cimg::option("-h",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("-help",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("--help",argc,argv,(char*)0,(char*)0,false)!=0; + } + if (!name && visu) { + if (usage) { + std::fprintf(cimg::output(),"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal); + std::fprintf(cimg::output(),": %s",usage); + std::fprintf(cimg::output()," (%s, %s)\n\n",cimg_date,cimg_time); + } + if (defaut) std::fprintf(cimg::output(),"%s\n",defaut); + } + if (name) { + if (argc>0) { + int k = 0; + while (k Operating System: %s%-13s%s %s('cimg_OS'=%d)%s\n", + cimg::t_bold, + cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"), + cimg::t_normal,cimg::t_green, + cimg_OS, + cimg::t_normal); + + std::fprintf(cimg::output()," > CPU endianness: %s%s Endian%s\n", + cimg::t_bold, + cimg::endianness()?"Big":"Little", + cimg::t_normal); + + std::fprintf(cimg::output()," > Verbosity mode: %s%-13s%s %s('cimg_verbosity'=%d)%s\n", + cimg::t_bold, + cimg_verbosity==0?"Quiet": + cimg_verbosity==1?"Console": + cimg_verbosity==2?"Dialog": + cimg_verbosity==3?"Console+Warnings":"Dialog+Warnings", + cimg::t_normal,cimg::t_green, + cimg_verbosity, + cimg::t_normal); + + std::fprintf(cimg::output()," > Stricts warnings: %s%-13s%s %s('cimg_strict_warnings' %s)%s\n", + cimg::t_bold, +#ifdef cimg_strict_warnings + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Support for C++11: %s%-13s%s %s('cimg_use_cpp11'=%d)%s\n", + cimg::t_bold, + cimg_use_cpp11?"Yes":"No", + cimg::t_normal,cimg::t_green, + (int)cimg_use_cpp11, + cimg::t_normal); + + std::fprintf(cimg::output()," > Using VT100 messages: %s%-13s%s %s('cimg_use_vt100' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_vt100 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Display type: %s%-13s%s %s('cimg_display'=%d)%s\n", + cimg::t_bold, + cimg_display==0?"No display":cimg_display==1?"X11":cimg_display==2?"Windows GDI":"Unknown", + cimg::t_normal,cimg::t_green, + (int)cimg_display, + cimg::t_normal); + +#if cimg_display==1 + std::fprintf(cimg::output()," > Using XShm for X11: %s%-13s%s %s('cimg_use_xshm' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xshm + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using XRand for X11: %s%-13s%s %s('cimg_use_xrandr' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xrandr + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); +#endif + std::fprintf(cimg::output()," > Using OpenMP: %s%-13s%s %s('cimg_use_openmp' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_openmp + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using PNG library: %s%-13s%s %s('cimg_use_png' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_png + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using JPEG library: %s%-13s%s %s('cimg_use_jpeg' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_jpeg + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using TIFF library: %s%-13s%s %s('cimg_use_tiff' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_tiff + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using Magick++ library: %s%-13s%s %s('cimg_use_magick' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_magick + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using FFTW3 library: %s%-13s%s %s('cimg_use_fftw3' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_fftw3 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using LAPACK library: %s%-13s%s %s('cimg_use_lapack' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_lapack + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + char *const tmp = new char[1024]; + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::imagemagick_path()); + std::fprintf(cimg::output()," > Path of ImageMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::graphicsmagick_path()); + std::fprintf(cimg::output()," > Path of GraphicsMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::medcon_path()); + std::fprintf(cimg::output()," > Path of 'medcon': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::temporary_path()); + std::fprintf(cimg::output()," > Temporary path: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + std::fprintf(cimg::output(),"\n"); + delete[] tmp; + } + + // Declare LAPACK function signatures if LAPACK support is enabled. +#ifdef cimg_use_lapack + template + inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) { + dgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) { + sgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + template + inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) { + dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) { + sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + template + inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN, + T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) { + dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN, + float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) { + sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + template + inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) { + int one = 1; + dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) { + int one = 1; + sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + template + inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) { + dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) { + ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + template + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, T* lapA, int &LDA, + T* lapB, int &LDB, T* WORK, int &LWORK, int &INFO){ + dgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, float* lapA, int &LDA, + float* lapB, int &LDB, float* WORK, int &LWORK, int &INFO){ + sgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + +#endif + + // End of the 'cimg' namespace + } + + /*------------------------------------------------ + # + # + # Definition of mathematical operators and + # external functions. + # + # + -------------------------------------------------*/ + +#define _cimg_create_ext_operators(typ) \ + template \ + inline CImg::type> operator+(const typ val, const CImg& img) { \ + return img + val; \ + } \ + template \ + inline CImg::type> operator-(const typ val, const CImg& img) { \ + typedef typename cimg::superset::type Tt; \ + return CImg(img._width,img._height,img._depth,img._spectrum,val)-=img; \ + } \ + template \ + inline CImg::type> operator*(const typ val, const CImg& img) { \ + return img*val; \ + } \ + template \ + inline CImg::type> operator/(const typ val, const CImg& img) { \ + return val*img.get_invert(); \ + } \ + template \ + inline CImg::type> operator&(const typ val, const CImg& img) { \ + return img & val; \ + } \ + template \ + inline CImg::type> operator|(const typ val, const CImg& img) { \ + return img | val; \ + } \ + template \ + inline CImg::type> operator^(const typ val, const CImg& img) { \ + return img ^ val; \ + } \ + template \ + inline bool operator==(const typ val, const CImg& img) { \ + return img == val; \ + } \ + template \ + inline bool operator!=(const typ val, const CImg& img) { \ + return img != val; \ + } + + _cimg_create_ext_operators(bool) + _cimg_create_ext_operators(unsigned char) + _cimg_create_ext_operators(char) + _cimg_create_ext_operators(signed char) + _cimg_create_ext_operators(unsigned short) + _cimg_create_ext_operators(short) + _cimg_create_ext_operators(unsigned int) + _cimg_create_ext_operators(int) + _cimg_create_ext_operators(cimg_uint64) + _cimg_create_ext_operators(cimg_int64) + _cimg_create_ext_operators(float) + _cimg_create_ext_operators(double) + _cimg_create_ext_operators(long double) + + template + inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg& img) { + return img + expression; + } + + template + inline CImg<_cimg_Tfloat> operator-(const char *const expression, const CImg& img) { + return CImg<_cimg_Tfloat>(img,false).fill(expression,true)-=img; + } + + template + inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg& img) { + return img*expression; + } + + template + inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg& img) { + return expression*img.get_invert(); + } + + template + inline CImg operator&(const char *const expression, const CImg& img) { + return img & expression; + } + + template + inline CImg operator|(const char *const expression, const CImg& img) { + return img | expression; + } + + template + inline CImg operator^(const char *const expression, const CImg& img) { + return img ^ expression; + } + + template + inline bool operator==(const char *const expression, const CImg& img) { + return img==expression; + } + + template + inline bool operator!=(const char *const expression, const CImg& img) { + return img!=expression; + } + + template + inline CImg transpose(const CImg& instance) { + return instance.get_transpose(); + } + + template + inline CImg<_cimg_Tfloat> invert(const CImg& instance) { + return instance.get_invert(); + } + + template + inline CImg<_cimg_Tfloat> pseudoinvert(const CImg& instance) { + return instance.get_pseudoinvert(); + } + +#define _cimg_create_ext_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) + + /*----------------------------------- + # + # Define the CImgDisplay structure + # + ----------------------------------*/ + //! Allow the creation of windows, display images on them and manage user events (keyboard, mouse and windows events). + /** + CImgDisplay methods rely on a low-level graphic library to perform: it can be either \b X-Window + (X11, for Unix-based systems) or \b GDI32 (for Windows-based systems). + If both libraries are missing, CImgDisplay will not be able to display images on screen, and will enter + a minimal mode where warning messages will be outputed each time the program is trying to call one of the + CImgDisplay method. + + The configuration variable \c cimg_display tells about the graphic library used. + It is set automatically by \CImg when one of these graphic libraries has been detected. + But, you can override its value if necessary. Valid choices are: + - 0: Disable display capabilities. + - 1: Use \b X-Window (X11) library. + - 2: Use \b GDI32 library. + + Remember to link your program against \b X11 or \b GDI32 libraries if you use CImgDisplay. + **/ + struct CImgDisplay { + cimg_ulong _timer, _fps_frames, _fps_timer; + unsigned int _width, _height, _normalization; + float _fps_fps, _min, _max; + bool _is_fullscreen; + char *_title; + unsigned int _window_width, _window_height, _button, *_keys, *_released_keys; + int _window_x, _window_y, _mouse_x, _mouse_y, _wheel; + bool _is_closed, _is_resized, _is_moved, _is_event, + _is_keyESC, _is_keyF1, _is_keyF2, _is_keyF3, _is_keyF4, _is_keyF5, _is_keyF6, _is_keyF7, + _is_keyF8, _is_keyF9, _is_keyF10, _is_keyF11, _is_keyF12, _is_keyPAUSE, _is_key1, _is_key2, + _is_key3, _is_key4, _is_key5, _is_key6, _is_key7, _is_key8, _is_key9, _is_key0, + _is_keyBACKSPACE, _is_keyINSERT, _is_keyHOME, _is_keyPAGEUP, _is_keyTAB, _is_keyQ, _is_keyW, _is_keyE, + _is_keyR, _is_keyT, _is_keyY, _is_keyU, _is_keyI, _is_keyO, _is_keyP, _is_keyDELETE, + _is_keyEND, _is_keyPAGEDOWN, _is_keyCAPSLOCK, _is_keyA, _is_keyS, _is_keyD, _is_keyF, _is_keyG, + _is_keyH, _is_keyJ, _is_keyK, _is_keyL, _is_keyENTER, _is_keySHIFTLEFT, _is_keyZ, _is_keyX, + _is_keyC, _is_keyV, _is_keyB, _is_keyN, _is_keyM, _is_keySHIFTRIGHT, _is_keyARROWUP, _is_keyCTRLLEFT, + _is_keyAPPLEFT, _is_keyALT, _is_keySPACE, _is_keyALTGR, _is_keyAPPRIGHT, _is_keyMENU, _is_keyCTRLRIGHT, + _is_keyARROWLEFT, _is_keyARROWDOWN, _is_keyARROWRIGHT, _is_keyPAD0, _is_keyPAD1, _is_keyPAD2, _is_keyPAD3, + _is_keyPAD4, _is_keyPAD5, _is_keyPAD6, _is_keyPAD7, _is_keyPAD8, _is_keyPAD9, _is_keyPADADD, _is_keyPADSUB, + _is_keyPADMUL, _is_keyPADDIV; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- + +#ifdef cimgdisplay_plugin +#include cimgdisplay_plugin +#endif +#ifdef cimgdisplay_plugin1 +#include cimgdisplay_plugin1 +#endif +#ifdef cimgdisplay_plugin2 +#include cimgdisplay_plugin2 +#endif +#ifdef cimgdisplay_plugin3 +#include cimgdisplay_plugin3 +#endif +#ifdef cimgdisplay_plugin4 +#include cimgdisplay_plugin4 +#endif +#ifdef cimgdisplay_plugin5 +#include cimgdisplay_plugin5 +#endif +#ifdef cimgdisplay_plugin6 +#include cimgdisplay_plugin6 +#endif +#ifdef cimgdisplay_plugin7 +#include cimgdisplay_plugin7 +#endif +#ifdef cimgdisplay_plugin8 +#include cimgdisplay_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + \note If the associated window is visible on the screen, it is closed by the call to the destructor. + **/ + ~CImgDisplay() { + assign(); + delete[] _keys; + delete[] _released_keys; + } + + //! Construct an empty display. + /** + \note Constructing an empty CImgDisplay instance does not make a window appearing on the screen, until + display of valid data is performed. + \par Example + \code + CImgDisplay disp; // Does actually nothing + ... + disp.display(img); // Construct new window and display image in it + \endcode + **/ + CImgDisplay(): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(); + } + + //! Construct a display with specified dimensions. + /** \param width Window width. + \param height Window height. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note A black background is initially displayed on the associated window. + **/ + CImgDisplay(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(width,height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image. + /** \param img Image used as a model to create the window. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note The pixels of the input image are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(img,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list. + /** \param list The images list to display. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note All images of the list, appended along the X-axis, are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(list,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of an existing one. + /** + \param disp Display instance to copy. + \note The pixel buffer of the input window is initially displayed on the associated window. + **/ + CImgDisplay(const CImgDisplay& disp): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(disp); + } + + //! Take a screenshot. + /** + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(CImg& img) { + return screenshot(0,0,cimg::type::max(),cimg::type::max(),img); + } + +#if cimg_display==0 + + static void _no_display_exception() { + throw CImgDisplayException("CImgDisplay(): No display available."); + } + + //! Destructor - Empty constructor \inplace. + /** + \note Replace the current instance by an empty display. + **/ + CImgDisplay& assign() { + return flush(); + } + + //! Construct a display with specified dimensions \inplace. + /** + **/ + CImgDisplay& assign(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + cimg::unused(width,height,title,normalization,is_fullscreen,is_closed); + _no_display_exception(); + return assign(); + } + + //! Construct a display from an image \inplace. + /** + **/ + template + CImgDisplay& assign(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list \inplace. + /** + **/ + template + CImgDisplay& assign(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of another one \inplace. + /** + **/ + CImgDisplay& assign(const CImgDisplay &disp) { + _no_display_exception(); + return assign(disp._width,disp._height); + } + +#endif + + //! Return a reference to an empty display. + /** + \note Can be useful for writing function prototypes where one of the argument (of type CImgDisplay&) + must have a default value. + \par Example + \code + void foo(CImgDisplay& disp=CImgDisplay::empty()); + \endcode + **/ + static CImgDisplay& empty() { + static CImgDisplay _empty; + return _empty.assign(); + } + + //! Return a reference to an empty display \const. + static const CImgDisplay& const_empty() { + static const CImgDisplay _empty; + return _empty; + } + +#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,256,-85,false), \ + CImgDisplay::_fitscreen(dx,dy,dz,256,-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 int + u = CImgDisplay::screen_width(), + v = CImgDisplay::screen_height(); + const float + mw = dmin<0?cimg::round(u*-dmin/100.f):(float)dmin, + mh = dmin<0?cimg::round(v*-dmin/100.f):(float)dmin, + Mw = dmax<0?cimg::round(u*-dmax/100.f):(float)dmax, + Mh = dmax<0?cimg::round(v*-dmax/100.f):(float)dmax; + float + w = (float)std::max(1U,dx), + h = (float)std::max(1U,dy); + if (dz>1) { w+=dz; h+=dz; } + if (wMw) { h = h*Mw/w; w = Mw; } + if (h>Mh) { w = w*Mh/h; h = Mh; } + if (wdisp = img is equivalent to disp.display(img). + **/ + template + CImgDisplay& operator=(const CImg& img) { + return display(img); + } + + //! Display list of images on associated window. + /** + \note disp = list is equivalent to disp.display(list). + **/ + template + CImgDisplay& operator=(const CImgList& list) { + return display(list); + } + + //! Construct a display as a copy of another one \inplace. + /** + \note Equivalent to assign(const CImgDisplay&). + **/ + CImgDisplay& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return \c false if display is empty, \c true otherwise. + /** + \note if (disp) { ... } is equivalent to if (!disp.is_empty()) { ... }. + **/ + operator bool() const { + return !is_empty(); + } + + //@} + //------------------------------------------ + // + //! \name Instance Checking + //@{ + //------------------------------------------ + + //! Return \c true if display is empty, \c false otherwise. + /** + **/ + bool is_empty() const { + return !(_width && _height); + } + + //! Return \c true if display is closed (i.e. not visible on the screen), \c false otherwise. + /** + \note + - When a user physically closes the associated window, the display is set to closed. + - A closed display is not destroyed. Its associated window can be show again on the screen using show(). + **/ + bool is_closed() const { + return _is_closed; + } + + //! Return \c true if associated window has been resized on the screen, \c false otherwise. + /** + **/ + bool is_resized() const { + return _is_resized; + } + + //! Return \c true if associated window has been moved on the screen, \c false otherwise. + /** + **/ + bool is_moved() const { + return _is_moved; + } + + //! Return \c true if any event has occured on the associated window, \c false otherwise. + /** + **/ + bool is_event() const { + return _is_event; + } + + //! Return \c true if current display is in fullscreen mode, \c false otherwise. + /** + **/ + bool is_fullscreen() const { + return _is_fullscreen; + } + + //! Return \c true if any key is being pressed on the associated window, \c false otherwise. + /** + \note The methods below do the same only for specific keys. + **/ + bool is_key() const { + return _is_keyESC || _is_keyF1 || _is_keyF2 || _is_keyF3 || + _is_keyF4 || _is_keyF5 || _is_keyF6 || _is_keyF7 || + _is_keyF8 || _is_keyF9 || _is_keyF10 || _is_keyF11 || + _is_keyF12 || _is_keyPAUSE || _is_key1 || _is_key2 || + _is_key3 || _is_key4 || _is_key5 || _is_key6 || + _is_key7 || _is_key8 || _is_key9 || _is_key0 || + _is_keyBACKSPACE || _is_keyINSERT || _is_keyHOME || + _is_keyPAGEUP || _is_keyTAB || _is_keyQ || _is_keyW || + _is_keyE || _is_keyR || _is_keyT || _is_keyY || + _is_keyU || _is_keyI || _is_keyO || _is_keyP || + _is_keyDELETE || _is_keyEND || _is_keyPAGEDOWN || + _is_keyCAPSLOCK || _is_keyA || _is_keyS || _is_keyD || + _is_keyF || _is_keyG || _is_keyH || _is_keyJ || + _is_keyK || _is_keyL || _is_keyENTER || + _is_keySHIFTLEFT || _is_keyZ || _is_keyX || _is_keyC || + _is_keyV || _is_keyB || _is_keyN || _is_keyM || + _is_keySHIFTRIGHT || _is_keyARROWUP || _is_keyCTRLLEFT || + _is_keyAPPLEFT || _is_keyALT || _is_keySPACE || _is_keyALTGR || + _is_keyAPPRIGHT || _is_keyMENU || _is_keyCTRLRIGHT || + _is_keyARROWLEFT || _is_keyARROWDOWN || _is_keyARROWRIGHT || + _is_keyPAD0 || _is_keyPAD1 || _is_keyPAD2 || + _is_keyPAD3 || _is_keyPAD4 || _is_keyPAD5 || + _is_keyPAD6 || _is_keyPAD7 || _is_keyPAD8 || + _is_keyPAD9 || _is_keyPADADD || _is_keyPADSUB || + _is_keyPADMUL || _is_keyPADDIV; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode Keycode to test. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.key(cimg::keyTAB)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool is_key(const unsigned int keycode) const { +#define _cimg_iskey_test(k) if (keycode==cimg::key##k) return _is_key##k; + _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3); + _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7); + _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11); + _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2); + _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6); + _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0); + _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME); + _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W); + _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y); + _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P); + _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN); + _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D); + _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J); + _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER); + _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C); + _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M); + _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT); + _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR); + _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT); + _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT); + _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2); + _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5); + _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8); + _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB); + _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV); + return false; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode C-string containing the keycode label of the key to test. + \note Use it when the key you want to test can be dynamically set by the user. + \par Example + \code + CImgDisplay disp(400,400); + const char *const keycode = "TAB"; + while (!disp.is_closed()) { + if (disp.is_key(keycode)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool& is_key(const char *const keycode) { + static bool f = false; + f = false; +#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(keycode,#k)) return _is_key##k; + _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3); + _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7); + _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11); + _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2); + _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6); + _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0); + _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME); + _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W); + _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y); + _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P); + _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN); + _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D); + _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J); + _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER); + _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C); + _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M); + _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT); + _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR); + _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT); + _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT); + _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2); + _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5); + _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8); + _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB); + _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV); + return f; + } + + //! Return \c true if specified key sequence has been typed on the associated window, \c false otherwise. + /** + \param keycodes_sequence Buffer of keycodes to test. + \param length Number of keys in the \c keycodes_sequence buffer. + \param remove_sequence Tells if the key sequence must be removed from the key history, if found. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + const unsigned int key_seq[] = { cimg::keyCTRLLEFT, cimg::keyD }; + while (!disp.is_closed()) { + if (disp.is_key_sequence(key_seq,2)) { ... } // Test for the 'CTRL+D' keyboard event + disp.wait(); + } + \endcode + **/ + bool is_key_sequence(const unsigned int *const keycodes_sequence, const unsigned int length, + const bool remove_sequence=false) { + if (keycodes_sequence && length) { + const unsigned int + *const ps_end = keycodes_sequence + length - 1, + *const pk_end = (unsigned int*)_keys + 1 + 128 - length, + k = *ps_end; + for (unsigned int *pk = (unsigned int*)_keys; pk[0,255]. + If the range of values of the data to display is different, a normalization may be required for displaying + the data in a correct way. The normalization type can be one of: + - \c 0: Value normalization is disabled. It is then assumed that all input data to be displayed by the + CImgDisplay instance have values in range [0,255]. + - \c 1: Value normalization is always performed (this is the default behavior). + Before displaying an input image, its values will be (virtually) stretched + in range [0,255], so that the contrast of the displayed pixels will be maximum. + Use this mode for images whose minimum and maximum values are not prescribed to known values + (e.g. float-valued images). + Note that when normalized versions of images are computed for display purposes, the actual values of these + images are not modified. + - \c 2: Value normalization is performed once (on the first image display), then the same normalization + coefficients are kept for next displayed frames. + - \c 3: Value normalization depends on the pixel type of the data to display. For integer pixel types, + the normalization is done regarding the minimum/maximum values of the type (no normalization occurs then + for unsigned char). + For float-valued pixel types, the normalization is done regarding the minimum/maximum value of the image + data instead. + **/ + unsigned int normalization() const { + return _normalization; + } + + //! Return title of the associated window as a C-string. + /** + \note Window title may be not visible, depending on the used window manager or if the current display is + in fullscreen mode. + **/ + const char *title() const { + return _title?_title:""; + } + + //! Return width of the associated window. + /** + \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual width of the associated window. + **/ + int window_width() const { + return (int)_window_width; + } + + //! Return height of the associated window. + /** + \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual height of the associated window. + **/ + int window_height() const { + return (int)_window_height; + } + + //! Return X-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_x() const { + return _window_x; + } + + //! Return Y-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_y() const { + return _window_y; + } + + //! Return X-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,width()-1]. + **/ + int mouse_x() const { + return _mouse_x; + } + + //! Return Y-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,height()-1]. + **/ + int mouse_y() const { + return _mouse_y; + } + + //! Return current state of the mouse buttons. + /** + \note Three mouse buttons can be managed. If one button is pressed, its corresponding bit in the returned + value is set: + - bit \c 0 (value \c 0x1): State of the left mouse button. + - bit \c 1 (value \c 0x2): State of the right mouse button. + - bit \c 2 (value \c 0x4): State of the middle mouse button. + + Several bits can be activated if more than one button are pressed at the same time. + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.button()&1) { // Left button clicked + ... + } + if (disp.button()&2) { // Right button clicked + ... + } + if (disp.button()&4) { // Middle button clicked + ... + } + disp.wait(); + } + \endcode + **/ + unsigned int button() const { + return _button; + } + + //! Return current state of the mouse wheel. + /** + \note + - The returned value can be positive or negative depending on whether the mouse wheel has been scrolled + forward or backward. + - Scrolling the wheel forward add \c 1 to the wheel value. + - Scrolling the wheel backward substract \c 1 to the wheel value. + - The returned value cumulates the number of forward of backward scrolls since the creation of the display, + or since the last reset of the wheel value (using set_wheel()). It is strongly recommended to quickly reset + the wheel counter when an action has been performed regarding the current wheel value. + Otherwise, the returned wheel value may be for instance \c 0 despite the fact that many scrolls have been done + (as many in forward as in backward directions). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.wheel()) { + int counter = disp.wheel(); // Read the state of the mouse wheel + ... // Do what you want with 'counter' + disp.set_wheel(); // Reset the wheel value to 0 + } + disp.wait(); + } + \endcode + **/ + int wheel() const { + return _wheel; + } + + //! Return one entry from the pressed keys history. + /** + \param pos Indice to read from the pressed keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a pressed key or \c 0 for a released key. + \note + - Each CImgDisplay stores a history of the pressed keys in a buffer of size \c 128. When a new key is pressed, + its keycode is stored in the pressed keys history. When a key is released, \c 0 is put instead. + This means that up to the 64 last pressed keys may be read from the pressed keys history. + When a new value is stored, the pressed keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int key(const unsigned int pos=0) const { + return pos<128?_keys[pos]:0; + } + + //! Return one entry from the released keys history. + /** + \param pos Indice to read from the released keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a released key or \c 0 for a pressed key. + \note + - Each CImgDisplay stores a history of the released keys in a buffer of size \c 128. When a new key is released, + its keycode is stored in the pressed keys history. When a key is pressed, \c 0 is put instead. + This means that up to the 64 last released keys may be read from the released keys history. + When a new value is stored, the released keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int released_key(const unsigned int pos=0) const { + return pos<128?_released_keys[pos]:0; + } + + //! Return keycode corresponding to the specified string. + /** + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + const unsigned int keyTAB = CImgDisplay::keycode("TAB"); // Return cimg::keyTAB + \endcode + **/ + static unsigned int keycode(const char *const keycode) { +#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k; + _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3); + _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7); + _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11); + _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2); + _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6); + _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0); + _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME); + _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W); + _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y); + _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P); + _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN); + _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D); + _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J); + _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER); + _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C); + _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M); + _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT); + _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR); + _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT); + _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT); + _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2); + _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5); + _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8); + _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB); + _cimg_keycode(PADMUL); _cimg_keycode(PADDIV); + return 0; + } + + //! Return the current refresh rate, in frames per second. + /** + \note Returns a significant value when the current instance is used to display successive frames. + It measures the delay between successive calls to frames_per_second(). + **/ + float frames_per_second() { + if (!_fps_timer) _fps_timer = cimg::time(); + const float delta = (cimg::time() - _fps_timer)/1000.f; + ++_fps_frames; + if (delta>=1) { + _fps_fps = _fps_frames/delta; + _fps_frames = 0; + _fps_timer = cimg::time(); + } + 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; + } + + //@} + //--------------------------------------- + // + //! \name Window Manipulation + //@{ + //--------------------------------------- + +#if cimg_display==0 + + //! Display image on associated window. + /** + \param img Input image to display. + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImg& img) { + return assign(img); + } + +#endif + + //! Display list of images on associated window. + /** + \param list List of images to display. + \param axis Axis used to append the images along, for the visualization (can be \c x, \c y, \c z or \c c). + \param align Relative position of aligned images when displaying lists with images of different sizes + (\c 0 for upper-left, \c 0.5 for centering and \c 1 for lower-right). + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImgList& list, const char axis='x', const float align=0) { + if (list._width==1) { + const CImg& img = list[0]; + if (img._depth==1 && (img._spectrum==1 || img._spectrum>=3) && _normalization!=1) return display(img); + } + CImgList::ucharT> visu(list._width); + 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]); + dims = std::max(dims,visu[l]._spectrum); + } + cimglist_for(list,l) if (visu[l]._spectrumimg.width() become equal, as well as height() and + img.height(). + - The associated window is also resized to specified dimensions. + **/ + template + CImgDisplay& resize(const CImg& img, const bool force_redraw=true) { + return resize(img._width,img._height,force_redraw); + } + + //! Resize display to the size of another CImgDisplay instance. + /** + \param disp Input display to take size from. + \param force_redraw Tells if the previous window content must be resized and updated as well. + \note + - Calling this method ensures that width() and disp.width() become equal, as well as height() and + disp.height(). + - The associated window is also resized to specified dimensions. + **/ + CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) { + return resize(disp.width(),disp.height(),force_redraw); + } + + // [internal] Render pixel buffer with size (wd,hd) from source buffer of size (ws,hs). + template + 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) { + typedef typename cimg::last::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; xstd::printf(). + \warning As the first argument is a format string, it is highly recommended to write + \code + disp.set_title("%s",window_title); + \endcode + instead of + \code + disp.set_title(window_title); + \endcode + if \c window_title can be arbitrary, to prevent nasty memory access. + **/ + CImgDisplay& set_title(const char *const format, ...) { + return assign(0,0,format); + } + +#endif + + //! Enable or disable fullscreen mode. + /** + \param is_fullscreen Tells is the fullscreen mode must be activated or not. + \param force_redraw Tells if the previous window content must be displayed as well. + \note + - When the fullscreen mode is enabled, the associated window fills the entire screen but the size of the + current display is not modified. + - The screen resolution may be switched to fit the associated window size and ensure it appears the largest + as possible. + For X-Window (X11) users, the configuration flag \c cimg_use_xrandr has to be set to allow the screen + resolution change (requires the X11 extensions to be enabled). + **/ + CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool force_redraw=true) { + if (is_empty() || _is_fullscreen==is_fullscreen) return *this; + return toggle_fullscreen(force_redraw); + } + +#if cimg_display==0 + + //! Toggle fullscreen mode. + /** + \param force_redraw Tells if the previous window content must be displayed as well. + \note Enable fullscreen mode if it was not enabled, and disable it otherwise. + **/ + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + return assign(_width,_height,0,3,force_redraw); + } + + //! Show mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& show_mouse() { + return assign(); + } + + //! Hide mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& hide_mouse() { + return assign(); + } + + //! Move mouse pointer to a specified location. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& set_mouse(const int pos_x, const int pos_y) { + return assign(pos_x,pos_y); + } + +#endif + + //! Simulate a mouse button release event. + /** + \note All mouse buttons are considered released at the same time. + **/ + CImgDisplay& set_button() { + _button = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a mouse button press or release event. + /** + \param button Buttons event code, where each button is associated to a single bit. + \param is_pressed Tells if the mouse button is considered as pressed or released. + **/ + CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) { + const unsigned int buttoncode = button==1U?1U:button==2U?2U:button==3U?4U:0U; + if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode; + _is_event = buttoncode?true:false; + if (buttoncode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all mouse wheel events. + /** + \note Make wheel() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_wheel() { + _wheel = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a wheel event. + /** + \param amplitude Amplitude of the wheel scrolling to simulate. + \note Make wheel() to return \c amplitude, if called afterwards. + **/ + CImgDisplay& set_wheel(const int amplitude) { + _wheel+=amplitude; + _is_event = amplitude?true:false; + if (amplitude) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all key events. + /** + \note Make key() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_key() { + std::memset((void*)_keys,0,128*sizeof(unsigned int)); + std::memset((void*)_released_keys,0,128*sizeof(unsigned int)); + _is_keyESC = _is_keyF1 = _is_keyF2 = _is_keyF3 = _is_keyF4 = _is_keyF5 = _is_keyF6 = _is_keyF7 = _is_keyF8 = + _is_keyF9 = _is_keyF10 = _is_keyF11 = _is_keyF12 = _is_keyPAUSE = _is_key1 = _is_key2 = _is_key3 = _is_key4 = + _is_key5 = _is_key6 = _is_key7 = _is_key8 = _is_key9 = _is_key0 = _is_keyBACKSPACE = _is_keyINSERT = + _is_keyHOME = _is_keyPAGEUP = _is_keyTAB = _is_keyQ = _is_keyW = _is_keyE = _is_keyR = _is_keyT = _is_keyY = + _is_keyU = _is_keyI = _is_keyO = _is_keyP = _is_keyDELETE = _is_keyEND = _is_keyPAGEDOWN = _is_keyCAPSLOCK = + _is_keyA = _is_keyS = _is_keyD = _is_keyF = _is_keyG = _is_keyH = _is_keyJ = _is_keyK = _is_keyL = + _is_keyENTER = _is_keySHIFTLEFT = _is_keyZ = _is_keyX = _is_keyC = _is_keyV = _is_keyB = _is_keyN = + _is_keyM = _is_keySHIFTRIGHT = _is_keyARROWUP = _is_keyCTRLLEFT = _is_keyAPPLEFT = _is_keyALT = _is_keySPACE = + _is_keyALTGR = _is_keyAPPRIGHT = _is_keyMENU = _is_keyCTRLRIGHT = _is_keyARROWLEFT = _is_keyARROWDOWN = + _is_keyARROWRIGHT = _is_keyPAD0 = _is_keyPAD1 = _is_keyPAD2 = _is_keyPAD3 = _is_keyPAD4 = _is_keyPAD5 = + _is_keyPAD6 = _is_keyPAD7 = _is_keyPAD8 = _is_keyPAD9 = _is_keyPADADD = _is_keyPADSUB = _is_keyPADMUL = + _is_keyPADDIV = false; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a keyboard press/release event. + /** + \param keycode Keycode of the associated key. + \param is_pressed Tells if the key is considered as pressed or released. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + CImgDisplay& set_key(const unsigned int keycode, const bool is_pressed=true) { +#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = is_pressed; + _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3); + _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7); + _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11); + _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2); + _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6); + _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0); + _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME); + _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W); + _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y); + _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P); + _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN); + _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D); + _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J); + _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER); + _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C); + _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M); + _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT); + _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR); + _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT); + _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT); + _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2); + _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5); + _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8); + _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB); + _cimg_set_key(PADMUL); _cimg_set_key(PADDIV); + if (is_pressed) { + if (*_keys) + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = keycode; + if (*_released_keys) { + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = 0; + } + } else { + if (*_keys) { + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = 0; + } + if (*_released_keys) + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = keycode; + } + _is_event = keycode?true:false; + if (keycode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all display events. + /** + \note Remove all passed events from the current display. + **/ + CImgDisplay& flush() { + set_key().set_button().set_wheel(); + _is_resized = _is_moved = _is_event = false; + _fps_timer = _fps_frames = _timer = 0; + _fps_fps = 0; + return *this; + } + + //! Wait for any user event occuring on the current display. + CImgDisplay& wait() { + wait(*this); + return *this; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \note Similar to cimg::wait(). + **/ + CImgDisplay& wait(const unsigned int milliseconds) { + cimg::wait(milliseconds,&_timer); + return *this; + } + + //! Wait for any event occuring on the display \c disp1. + static void wait(CImgDisplay& disp1) { + disp1._is_event = false; + while (!disp1._is_closed && !disp1._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1 or \c disp2. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2) { + disp1._is_event = disp2._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed) && + !disp1._is_event && !disp2._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) { + disp1._is_event = disp2._is_event = disp3._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4 or \c disp5. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, + CImgDisplay& disp5) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event) + wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp6. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp7. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp8. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp9. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp10. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9, + CImgDisplay& disp10) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = disp10._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event) + wait_all(); + } + +#if cimg_display==0 + + //! Wait for any window event occuring in any opened CImgDisplay. + static void wait_all() { + return _no_display_exception(); + } + + //! Render image into internal display buffer. + /** + \param img Input image data to render. + \note + - Convert image data representation into the internal display buffer (architecture-dependent structure). + - The content of the associated window is not modified, until paint() is called. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + template + CImgDisplay& render(const CImg& img) { + return assign(img); + } + + //! Paint internal display buffer on associated window. + /** + \note + - Update the content of the associated window with the internal display buffer, e.g. after a render() call. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + CImgDisplay& paint() { + return assign(); + } + + + //! Take a snapshot of the current screen content. + /** + \param x0 X-coordinate of the upper left corner. + \param y0 Y-coordinate of the upper left corner. + \param x1 X-coordinate of the lower right corner. + \param y1 Y-coordinate of the lower right corner. + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + cimg::unused(x0,y0,x1,y1,&img); + _no_display_exception(); + } + + //! Take a snapshot of the associated window content. + /** + \param[out] img Output snapshot. Can be empty on input. + **/ + template + const CImgDisplay& snapshot(CImg& img) const { + cimg::unused(img); + _no_display_exception(); + return *this; + } +#endif + + // X11-based implementation + //-------------------------- +#if cimg_display==1 + + Atom _wm_window_atom, _wm_protocol_atom; + Window _window, _background_window; + Colormap _colormap; + XImage *_image; + void *_data; +#ifdef cimg_use_xshm + XShmSegmentInfo *_shminfo; +#endif + + static int screen_width() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_width(): Failed to open X11 display."); + res = DisplayWidth(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width; + else res = DisplayWidth(dpy,DefaultScreen(dpy)); +#else + res = DisplayWidth(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static int screen_height() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_height(): Failed to open X11 display."); + res = DisplayHeight(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height; + else res = DisplayHeight(dpy,DefaultScreen(dpy)); +#else + res = DisplayHeight(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static void wait_all() { + if (!cimg::X11_attr().display) return; + pthread_mutex_lock(&cimg::X11_attr().wait_event_mutex); + pthread_cond_wait(&cimg::X11_attr().wait_event,&cimg::X11_attr().wait_event_mutex); + pthread_mutex_unlock(&cimg::X11_attr().wait_event_mutex); + } + + void _handle_events(const XEvent *const pevent) { + Display *const dpy = cimg::X11_attr().display; + XEvent event = *pevent; + switch (event.type) { + case ClientMessage : { + if ((int)event.xclient.message_type==(int)_wm_protocol_atom && + (int)event.xclient.data.l[0]==(int)_wm_window_atom) { + XUnmapWindow(cimg::X11_attr().display,_window); + _is_closed = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case ConfigureNotify : { + while (XCheckWindowEvent(dpy,_window,StructureNotifyMask,&event)) {} + const unsigned int nw = event.xconfigure.width, nh = event.xconfigure.height; + const int nx = event.xconfigure.x, ny = event.xconfigure.y; + if (nw && nh && (nw!=_window_width || nh!=_window_height)) { + _window_width = nw; _window_height = nh; _mouse_x = _mouse_y = -1; + XResizeWindow(dpy,_window,_window_width,_window_height); + _is_resized = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + if (nx!=_window_x || ny!=_window_y) { + _window_x = nx; _window_y = ny; _is_moved = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case Expose : { + while (XCheckWindowEvent(dpy,_window,ExposureMask,&event)) {} + _paint(false); + if (_is_fullscreen) { + XWindowAttributes attr; + XGetWindowAttributes(dpy,_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XSetInputFocus(dpy,_window,RevertToParent,CurrentTime); + } + } break; + case ButtonPress : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1); break; + case 3 : set_button(2); break; + case 2 : set_button(3); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonPressMask,&event)); + } break; + case ButtonRelease : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1,false); break; + case 3 : set_button(2,false); break; + case 2 : set_button(3,false); break; + case 4 : set_wheel(1); break; + case 5 : set_wheel(-1); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonReleaseMask,&event)); + } break; + case KeyPress : { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,true); + } break; + case KeyRelease : { + char keys_return[32]; // Check that the key has been physically unpressed + XQueryKeymap(dpy,keys_return); + const unsigned int kc = event.xkey.keycode, kc1 = kc/8, kc2 = kc%8; + const bool is_key_pressed = kc1>=32?false:(keys_return[kc1]>>kc2)&1; + if (!is_key_pressed) { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,false); + } + } break; + case EnterNotify: { + while (XCheckWindowEvent(dpy,_window,EnterWindowMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + } break; + case LeaveNotify : { + while (XCheckWindowEvent(dpy,_window,LeaveWindowMask,&event)) {} + _mouse_x = _mouse_y = -1; _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + case MotionNotify : { + while (XCheckWindowEvent(dpy,_window,PointerMotionMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + } + } + + static void* _events_thread(void *arg) { // Thread to manage events for all opened display windows + Display *const dpy = cimg::X11_attr().display; + XEvent event; + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0); + if (!arg) for ( ; ; ) { + cimg_lock_display(); + bool event_flag = XCheckTypedEvent(dpy,ClientMessage,&event); + if (!event_flag) event_flag = XCheckMaskEvent(dpy, + ExposureMask | StructureNotifyMask | ButtonPressMask | + KeyPressMask | PointerMotionMask | EnterWindowMask | + LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask,&event); + if (event_flag) + for (unsigned int i = 0; i_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window) + cimg::X11_attr().wins[i]->_handle_events(&event); + cimg_unlock_display(); + pthread_testcancel(); + cimg::sleep(8); + } + return 0; + } + + void _set_colormap(Colormap& _colormap, const unsigned int dim) { + XColor *const colormap = new XColor[256]; + switch (dim) { + case 1 : { // colormap for greyscale images + for (unsigned int index = 0; index<256; ++index) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].green = colormap[index].blue = (unsigned short)(index<<8); + colormap[index].flags = DoRed | DoGreen | DoBlue; + } + } break; + case 2 : { // colormap for RG images + for (unsigned int index = 0, r = 8; r<256; r+=16) + for (unsigned int g = 8; g<256; g+=16) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].blue = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } break; + default : { // colormap for RGB images + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap[index].pixel = index; + colormap[index].red = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index].blue = (unsigned short)(b<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } + } + XStoreColors(cimg::X11_attr().display,_colormap,colormap,256); + delete[] colormap; + } + + void _map_window() { + Display *const dpy = cimg::X11_attr().display; + bool is_exposed = false, is_mapped = false; + XWindowAttributes attr; + XEvent event; + XMapRaised(dpy,_window); + do { // Wait for the window to be mapped + XWindowEvent(dpy,_window,StructureNotifyMask | ExposureMask,&event); + switch (event.type) { + case MapNotify : is_mapped = true; break; + case Expose : is_exposed = true; break; + } + } while (!is_exposed || !is_mapped); + do { // Wait for the window to be visible + XGetWindowAttributes(dpy,_window,&attr); + if (attr.map_state!=IsViewable) { XSync(dpy,0); cimg::sleep(10); } + } while (attr.map_state!=IsViewable); + _window_x = attr.x; + _window_y = attr.y; + } + + void _paint(const bool wait_expose=true) { + if (_is_closed || !_image) return; + Display *const dpy = cimg::X11_attr().display; + if (wait_expose) { // Send an expose event sticked to display window to force repaint + XEvent event; + event.xexpose.type = Expose; + event.xexpose.serial = 0; + event.xexpose.send_event = 1; + event.xexpose.display = dpy; + event.xexpose.window = _window; + event.xexpose.x = 0; + event.xexpose.y = 0; + event.xexpose.width = width(); + event.xexpose.height = height(); + event.xexpose.count = 0; + XSendEvent(dpy,_window,0,0,&event); + } else { // Repaint directly (may be called from the expose event) + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height,1); + else XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#else + XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#endif + } + } + + template + void _resize(T pixel_type, const unsigned int ndimx, const unsigned int ndimy, const bool force_redraw) { + Display *const dpy = cimg::X11_attr().display; + cimg::unused(pixel_type); + +#ifdef cimg_use_xshm + if (_shminfo) { + XShmSegmentInfo *const nshminfo = new XShmSegmentInfo; + XImage *const nimage = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy); + if (!nimage) { delete nshminfo; return; } + else { + nshminfo->shmid = shmget(IPC_PRIVATE,ndimx*ndimy*sizeof(T),IPC_CREAT | 0777); + if (nshminfo->shmid==-1) { XDestroyImage(nimage); delete nshminfo; return; } + else { + nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0); + if (nshminfo->shmaddr==(char*)-1) { + shmctl(nshminfo->shmid,IPC_RMID,0); XDestroyImage(nimage); delete nshminfo; return; + } else { + nshminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,nshminfo); + XFlush(dpy); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(nshminfo->shmaddr); + shmctl(nshminfo->shmid,IPC_RMID,0); + XDestroyImage(nimage); + delete nshminfo; + return; + } else { + T *const ndata = (T*)nimage->data; + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + XShmDetach(dpy,_shminfo); + XDestroyImage(_image); + shmdt(_shminfo->shmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = nshminfo; + _image = nimage; + _data = (void*)ndata; + } + } + } + } + } else +#endif + { + T *ndata = (T*)std::malloc(ndimx*ndimy*sizeof(T)); + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + _data = (void*)ndata; + XDestroyImage(_image); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,ndimx,ndimy,8,0); + } + } + + void _init_fullscreen() { + if (!_is_fullscreen || _is_closed) return; + Display *const dpy = cimg::X11_attr().display; + _background_window = 0; + +#ifdef cimg_use_xrandr + int foo; + if (XRRQueryExtension(dpy,&foo,&foo)) { + XRRRotations(dpy,DefaultScreen(dpy),&cimg::X11_attr().curr_rotation); + if (!cimg::X11_attr().resolutions) { + cimg::X11_attr().resolutions = XRRSizes(dpy,DefaultScreen(dpy),&foo); + cimg::X11_attr().nb_resolutions = (unsigned int)foo; + } + if (cimg::X11_attr().resolutions) { + cimg::X11_attr().curr_resolution = 0; + for (unsigned int i = 0; i=_width && nh>=_height && + nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) && + nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height)) + cimg::X11_attr().curr_resolution = i; + } + if (cimg::X11_attr().curr_resolution>0) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy), + cimg::X11_attr().curr_resolution,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + } + } + } + if (!cimg::X11_attr().resolutions) + cimg::warn(_cimgdisplay_instance + "init_fullscreen(): Xrandr extension not supported by the X server.", + cimgdisplay_instance); +#endif + + const unsigned int sx = screen_width(), sy = screen_height(); + if (sx==_width && sy==_height) return; + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _background_window = XCreateWindow(dpy,DefaultRootWindow(dpy),0,0,sx,sy,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + const cimg_ulong buf_size = (cimg_ulong)sx*sy*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + void *background_data = std::malloc(buf_size); + std::memset(background_data,0,buf_size); + XImage *background_image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)background_data,sx,sy,8,0); + XEvent event; + XSelectInput(dpy,_background_window,StructureNotifyMask); + XMapRaised(dpy,_background_window); + do XWindowEvent(dpy,_background_window,StructureNotifyMask,&event); + while (event.type!=MapNotify); + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy,0); + else XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#else + XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#endif + XWindowAttributes attr; + XGetWindowAttributes(dpy,_background_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XDestroyImage(background_image); + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + Display *const dpy = cimg::X11_attr().display; + XUngrabKeyboard(dpy,CurrentTime); +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),0,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + cimg::X11_attr().curr_resolution = 0; + } +#endif + if (_background_window) XDestroyWindow(dpy,_background_window); + _background_window = 0; + _is_fullscreen = false; + } + + static int _assign_xshm(Display *dpy, XErrorEvent *error) { + cimg::unused(dpy,error); + cimg::X11_attr().is_shm_enabled = false; + return 0; + } + + void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + cimg::mutex(14); + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous display window if existing + if (!is_empty()) assign(); + + // Open X11 display and retrieve graphical properties. + Display* &dpy = cimg::X11_attr().display; + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Failed to open X11 display.", + cimgdisplay_instance); + + cimg::X11_attr().nb_bits = DefaultDepth(dpy,DefaultScreen(dpy)); + if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 && + cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Invalid %u bits screen mode detected " + "(only 8, 16, 24 and 32 bits modes are managed).", + cimgdisplay_instance, + cimg::X11_attr().nb_bits); + XVisualInfo vtemplate; + vtemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,DefaultScreen(dpy))); + int nb_visuals; + XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals); + if (vinfo && vinfo->red_maskblue_mask) cimg::X11_attr().is_blue_first = true; + cimg::X11_attr().byte_order = ImageByteOrder(dpy); + XFree(vinfo); + + cimg_lock_display(); + cimg::X11_attr().events_thread = new pthread_t; + pthread_create(cimg::X11_attr().events_thread,0,_events_thread,0); + } else cimg_lock_display(); + + // Set display variables. + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _title = tmp_title; + flush(); + + // Create X11 window (and LUT, if 8bits display) + if (_is_fullscreen) { + if (!_is_closed) _init_fullscreen(); + const unsigned int sx = screen_width(), sy = screen_height(); + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx - _width)/2,(sy - _height)/2,_width,_height,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + } else + _window = XCreateSimpleWindow(dpy,DefaultRootWindow(dpy),0,0,_width,_height,0,0L,0L); + + XSelectInput(dpy,_window, + ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask | + EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask); + + XStoreName(dpy,_window,_title?_title:" "); + if (cimg::X11_attr().nb_bits==8) { + _colormap = XCreateColormap(dpy,_window,DefaultVisual(dpy,DefaultScreen(dpy)),AllocAll); + _set_colormap(_colormap,3); + XSetWindowColormap(dpy,_window,_colormap); + } + + static const char *const _window_class = cimg_appname; + XClassHint *const window_class = XAllocClassHint(); + window_class->res_name = (char*)_window_class; + window_class->res_class = (char*)_window_class; + XSetClassHint(dpy,_window,window_class); + XFree(window_class); + + _window_width = _width; + _window_height = _height; + + // Create XImage +#ifdef cimg_use_xshm + _shminfo = 0; + if (XShmQueryExtension(dpy)) { + _shminfo = new XShmSegmentInfo; + _image = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,_shminfo,_width,_height); + if (!_image) { delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmid = shmget(IPC_PRIVATE,_image->bytes_per_line*_image->height,IPC_CREAT|0777); + if (_shminfo->shmid==-1) { XDestroyImage(_image); delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmaddr = _image->data = (char*)(_data = shmat(_shminfo->shmid,0,0)); + if (_shminfo->shmaddr==(char*)-1) { + shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); delete _shminfo; _shminfo = 0; + } else { + _shminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,_shminfo); + XSync(dpy,0); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(_shminfo->shmaddr); shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); + delete _shminfo; _shminfo = 0; + } + } + } + } + } + if (!_shminfo) +#endif + { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + _data = std::malloc(buf_size); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)_data,_width,_height,8,0); + } + + _wm_window_atom = XInternAtom(dpy,"WM_DELETE_WINDOW",0); + _wm_protocol_atom = XInternAtom(dpy,"WM_PROTOCOLS",0); + XSetWMProtocols(dpy,_window,&_wm_window_atom,1); + + if (_is_fullscreen) XGrabKeyboard(dpy,_window,1,GrabModeAsync,GrabModeAsync,CurrentTime); + cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this; + if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type::min(); } + cimg_unlock_display(); + cimg::mutex(14,0); + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + + // Remove display window from event thread list. + unsigned int i; + for (i = 0; ishmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = 0; + } else +#endif + XDestroyImage(_image); + _data = 0; _image = 0; + if (cimg::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap); + _colormap = 0; + XSync(dpy,0); + + // Reset display variables. + delete[] _title; + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + + cimg_unlock_display(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + (cimg::X11_attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))* + (size_t)_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + cimg::X11_attr().nb_bits==16?sizeof(unsigned short): + sizeof(unsigned int))*(size_t)_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + Display *const dpy = cimg::X11_attr().display; + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*width()/100), + tmpdimy = (nheight>0)?nheight:(-nheight*height()/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + show(); + cimg_lock_display(); + if (_window_width!=dimx || _window_height!=dimy) { + XWindowAttributes attr; + for (unsigned int i = 0; i<10; ++i) { + XResizeWindow(dpy,_window,dimx,dimy); + XGetWindowAttributes(dpy,_window,&attr); + if (attr.width==(int)dimx && attr.height==(int)dimy) break; + cimg::wait(5,&_timer); + } + } + if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) { + case 8 : { unsigned char pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + case 16 : { unsigned short pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + default : { unsigned int pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } + } + _window_width = _width = dimx; _window_height = _height = dimy; + cimg_unlock_display(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - _width)/2,(screen_height() - _height)/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height* + (cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4)); + void *image_data = std::malloc(buf_size); + std::memcpy(image_data,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,image_data,buf_size); + std::free(image_data); + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + cimg_lock_display(); + if (_is_fullscreen) _init_fullscreen(); + _map_window(); + _is_closed = false; + cimg_unlock_display(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (_is_fullscreen) _desinit_fullscreen(); + XUnmapWindow(dpy,_window); + _window_x = _window_y = -1; + _is_closed = true; + cimg_unlock_display(); + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + show(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XMoveWindow(dpy,_window,posx,posy); + _window_x = posx; _window_y = posy; + cimg_unlock_display(); + } + _is_moved = false; + return paint(); + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XUndefineCursor(dpy,_window); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + static const char pix_data[8] = { 0 }; + XColor col; + col.red = col.green = col.blue = 0; + Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8); + Cursor cur = XCreatePixmapCursor(dpy,pix,pix,&col,&col,0,0); + XFreePixmap(dpy,pix); + XDefineCursor(dpy,_window,cur); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XWarpPointer(dpy,0L,_window,0,0,0,0,posx,posy); + _mouse_x = posx; _mouse_y = posy; + _is_moved = false; + XSync(dpy,0); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XStoreName(dpy,_window,tmp); + cimg_unlock_display(); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(false); + } + + CImgDisplay& paint(const bool wait_expose=true) { + if (is_empty()) return *this; + cimg_lock_display(); + _paint(wait_expose); + cimg_unlock_display(); + return *this; + } + + template + CImgDisplay& render(const CImg& img, const bool flag8=false) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + if (cimg::X11_attr().nb_bits==8 && (img._width!=_width || img._height!=_height)) + return render(img.get_resize(_width,_height,1,-100,1)); + if (cimg::X11_attr().nb_bits==8 && !flag8 && img._spectrum==3) { + static const CImg::ucharT> default_colormap = CImg::ucharT>::default_LUT256(); + return render(img.get_index(default_colormap,1,false)); + } + + const T + *data1 = img._data, + *data2 = (img._spectrum>1)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>2)?img.data(0,0,0,2):data1; + + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + cimg_lock_display(); + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, no normalization + _set_colormap(_colormap,img._spectrum); + unsigned char + *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height], + *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + (*ptrd++) = (unsigned char)*(data1++); + break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, no normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (G>>1); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (G<<5) | (G>>1); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, no normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | + (unsigned char)*(data3++); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | + ((unsigned char)*(data1++)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = 0; + ptrd[3] = 0; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = 0; + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = (unsigned char)*(data2++); + ptrd[3] = (unsigned char)*(data3++); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)*(data3++); + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, with normalization + _set_colormap(_colormap,img._spectrum); + unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char R = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = R; + } break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, with normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (val>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (G<<5) | (val>>3); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, with normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<24) | (val<<16) | (val<<8); + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8) | + (unsigned char)((*(data3++) - _min)*mm); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data3++) - _min)*mm)<<24) | + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = 0; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = val; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = val; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[3] = (unsigned char)((*(data3++) - _min)*mm); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)((*(data3++) - _min)*mm); + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } + cimg_unlock_display(); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + Display *dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to open X11 display."); + } + Window root = DefaultRootWindow(dpy); + XWindowAttributes gwa; + XGetWindowAttributes(dpy,root,&gwa); + const int width = gwa.width, height = gwa.height; + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + + XImage *image = 0; + if (_x1>=0 && _x0=0 && _y0red_mask, + green_mask = image->green_mask, + blue_mask = image->blue_mask; + img.assign(image->width,image->height,1,3); + T *pR = img.data(0,0,0,0), *pG = img.data(0,0,0,1), *pB = img.data(0,0,0,2); + cimg_forXY(img,x,y) { + const unsigned long pixel = XGetPixel(image,x,y); + *(pR++) = (T)((pixel & red_mask)>>16); + *(pG++) = (T)((pixel & green_mask)>>8); + *(pB++) = (T)(pixel & blue_mask); + } + XDestroyImage(image); + } + } + if (!cimg::X11_attr().display) XCloseDisplay(dpy); + cimg_unlock_display(); + if (img.is_empty()) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to take screenshot " + "with coordinates (%d,%d)-(%d,%d).", + x0,y0,x1,y1); + } + + template + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned char *ptrs = (unsigned char*)_data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + switch (cimg::X11_attr().nb_bits) { + case 8 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = *(ptrs++); + *(data1++) = (T)(val&0xe0); + *(data2++) = (T)((val&0x1c)<<3); + *(data3++) = (T)(val<<6); + } + } break; + case 16 : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val0&0xf8); + *(data2++) = (T)((val0<<5) | ((val1&0xe0)>>5)); + *(data3++) = (T)(val1<<3); + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned short + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val1&0xf8); + *(data2++) = (T)((val1<<5) | ((val0&0xe0)>>5)); + *(data3++) = (T)(val0<<3); + } + } break; + default : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ++ptrs; + *(data1++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data3++) = (T)ptrs[2]; + ptrs+=3; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + *(data3++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data1++) = (T)ptrs[2]; + ptrs+=3; + ++ptrs; + } + } + } + return *this; + } + + // Windows-based implementation. + //------------------------------- +#elif cimg_display==2 + + bool _is_mouse_tracked, _is_cursor_visible; + HANDLE _thread, _is_created, _mutex; + HWND _window, _background_window; + CLIENTCREATESTRUCT _ccs; + unsigned int *_data; + DEVMODE _curr_mode; + BITMAPINFO _bmi; + HDC _hdc; + + static int screen_width() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsWidth; + } + + static int screen_height() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsHeight; + } + + static void wait_all() { + WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE); + } + + static LRESULT APIENTRY _handle_events(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) { +#ifdef _WIN64 + CImgDisplay *const disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA); +#else + CImgDisplay *const disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA); +#endif + MSG st_msg; + switch (msg) { + case WM_CLOSE : + disp->_mouse_x = disp->_mouse_y = -1; + disp->_window_x = disp->_window_y = 0; + disp->set_button().set_key(0).set_key(0,false)._is_closed = true; + ReleaseMutex(disp->_mutex); + ShowWindow(disp->_window,SW_HIDE); + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + return 0; + case WM_SIZE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam); + if (nw && nh && (nw!=disp->_width || nh!=disp->_height)) { + disp->_window_width = nw; + disp->_window_height = nh; + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_resized = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_MOVE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam)); + if (nx!=disp->_window_x || ny!=disp->_window_y) { + disp->_window_x = nx; + disp->_window_y = ny; + disp->_is_moved = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_PAINT : + disp->paint(); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + break; + case WM_ERASEBKGND : + // return 0; + break; + case WM_KEYDOWN : + disp->set_key((unsigned int)wParam); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_KEYUP : + disp->set_key((unsigned int)wParam,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MOUSEMOVE : { + while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {} + disp->_mouse_x = LOWORD(lParam); + disp->_mouse_y = HIWORD(lParam); +#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT) + if (!disp->_is_mouse_tracked) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = disp->_window; + if (TrackMouseEvent(&tme)) disp->_is_mouse_tracked = true; + } +#endif + if (disp->_mouse_x<0 || disp->_mouse_y<0 || disp->_mouse_x>=disp->width() || disp->_mouse_y>=disp->height()) + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + } break; + case WM_MOUSELEAVE : { + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_mouse_tracked = false; + cimg_lock_display(); + while (ShowCursor(TRUE)<0) {} + cimg_unlock_display(); + } break; + case WM_LBUTTONDOWN : + disp->set_button(1); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONDOWN : + disp->set_button(2); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONDOWN : + disp->set_button(3); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_LBUTTONUP : + disp->set_button(1,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONUP : + disp->set_button(2,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONUP : + disp->set_button(3,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case 0x020A : // WM_MOUSEWHEEL: + disp->set_wheel((int)((short)HIWORD(wParam))/120); + SetEvent(cimg::Win32_attr().wait_event); + } + return DefWindowProc(window,msg,wParam,lParam); + } + + static DWORD WINAPI _events_thread(void* arg) { + CImgDisplay *const disp = (CImgDisplay*)(((void**)arg)[0]); + const char *const title = (const char*)(((void**)arg)[1]); + MSG msg; + delete[] (void**)arg; + disp->_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + disp->_bmi.bmiHeader.biWidth = disp->width(); + disp->_bmi.bmiHeader.biHeight = -disp->height(); + disp->_bmi.bmiHeader.biPlanes = 1; + disp->_bmi.bmiHeader.biBitCount = 32; + disp->_bmi.bmiHeader.biCompression = BI_RGB; + disp->_bmi.bmiHeader.biSizeImage = 0; + disp->_bmi.bmiHeader.biXPelsPerMeter = 1; + disp->_bmi.bmiHeader.biYPelsPerMeter = 1; + disp->_bmi.bmiHeader.biClrUsed = 0; + disp->_bmi.bmiHeader.biClrImportant = 0; + disp->_data = new unsigned int[(size_t)disp->_width*disp->_height]; + if (!disp->_is_fullscreen) { // Normal window + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)disp->_width - 1; rect.bottom = (LONG)disp->_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - disp->_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - disp->_height - border1); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_OVERLAPPEDWINDOW | (disp->_is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT, + disp->_width + 2*border1, disp->_height + border1 + border2, + 0,0,0,&(disp->_ccs)); + if (!disp->_is_closed) { + GetWindowRect(disp->_window,&rect); + disp->_window_x = rect.left + border1; + disp->_window_y = rect.top + border2; + } else disp->_window_x = disp->_window_y = 0; + } else { // Fullscreen window + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_POPUP | (disp->_is_closed?0:WS_VISIBLE), + (sx - disp->_width)/2, + (sy - disp->_height)/2, + disp->_width,disp->_height,0,0,0,&(disp->_ccs)); + disp->_window_x = disp->_window_y = 0; + } + SetForegroundWindow(disp->_window); + disp->_hdc = GetDC(disp->_window); + disp->_window_width = disp->_width; + disp->_window_height = disp->_height; + disp->flush(); +#ifdef _WIN64 + SetWindowLongPtr(disp->_window,GWLP_USERDATA,(LONG_PTR)disp); + SetWindowLongPtr(disp->_window,GWLP_WNDPROC,(LONG_PTR)_handle_events); +#else + SetWindowLong(disp->_window,GWL_USERDATA,(LONG)disp); + SetWindowLong(disp->_window,GWL_WNDPROC,(LONG)_handle_events); +#endif + SetEvent(disp->_is_created); + while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg); + return 0; + } + + CImgDisplay& _update_window_pos() { + if (_is_closed) _window_x = _window_y = -1; + else { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_width - 1; rect.bottom = (LONG)_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - _width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + GetWindowRect(_window,&rect); + _window_x = rect.left + border1; + _window_y = rect.top + border2; + } + return *this; + } + + void _init_fullscreen() { + _background_window = 0; + if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0; + else { + DEVMODE mode; + unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U; + for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) { + const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight; + if (nw>=_width && nh>=_height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) { + bestbpp = mode.dmBitsPerPel; + ibest = imode; + bw = nw; bh = nh; + } + } + if (bestbpp) { + _curr_mode.dmSize = sizeof(DEVMODE); _curr_mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&_curr_mode); + EnumDisplaySettings(0,ibest,&mode); + ChangeDisplaySettings(&mode,0); + } else _curr_mode.dmSize = 0; + + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + if (sx!=_width || sy!=_height) { + CLIENTCREATESTRUCT background_ccs; + _background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs); + SetForegroundWindow(_background_window); + } + } + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + if (_background_window) DestroyWindow(_background_window); + _background_window = 0; + if (_curr_mode.dmSize) ChangeDisplaySettings(&_curr_mode,0); + _is_fullscreen = false; + } + + CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous window if existing + if (!is_empty()) assign(); + + // Set display variables + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _is_cursor_visible = true; + _is_mouse_tracked = false; + _title = tmp_title; + flush(); + if (_is_fullscreen) _init_fullscreen(); + + // Create event thread + void *const arg = (void*)(new void*[2]); + ((void**)arg)[0] = (void*)this; + ((void**)arg)[1] = (void*)_title; + _mutex = CreateMutex(0,FALSE,0); + _is_created = CreateEvent(0,FALSE,FALSE,0); + _thread = CreateThread(0,0,_events_thread,arg,0,0); + WaitForSingleObject(_is_created,INFINITE); + return *this; + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + DestroyWindow(_window); + TerminateThread(_thread,0); + delete[] _data; + delete[] _title; + _data = 0; + _title = 0; + if (_is_fullscreen) _desinit_fullscreen(); + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,sizeof(unsigned int)*_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100), + tmpdimy = (nheight>0)?nheight:(-nheight*_height/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + if (_window_width!=dimx || _window_height!=dimy) { + RECT rect; rect.left = rect.top = 0; rect.right = (LONG)dimx - 1; rect.bottom = (LONG)dimy - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int cwidth = rect.right - rect.left + 1, cheight = rect.bottom - rect.top + 1; + SetWindowPos(_window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS); + } + if (_width!=dimx || _height!=dimy) { + unsigned int *const ndata = new unsigned int[dimx*dimy]; + if (force_redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy); + else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy); + delete[] _data; + _data = ndata; + _bmi.bmiHeader.biWidth = (LONG)dimx; + _bmi.bmiHeader.biHeight = -(int)dimy; + _width = dimx; + _height = dimy; + } + _window_width = dimx; _window_height = dimy; + show(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - width())/2,(screen_height() - height())/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*4; + void *odata = std::malloc(buf_size); + if (odata) { + std::memcpy(odata,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,odata,buf_size); + std::free(odata); + } + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + _is_closed = false; + if (_is_fullscreen) _init_fullscreen(); + ShowWindow(_window,SW_SHOW); + _update_window_pos(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + _is_closed = true; + if (_is_fullscreen) _desinit_fullscreen(); + ShowWindow(_window,SW_HIDE); + _window_x = _window_y = 0; + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + if (!_is_fullscreen) { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_window_width - 1; rect.bottom = (LONG)_window_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 -_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + SetWindowPos(_window,0,posx - border1,posy - border2,0,0,SWP_NOSIZE | SWP_NOZORDER); + } else SetWindowPos(_window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER); + _window_x = posx; + _window_y = posy; + show(); + } + _is_moved = false; + return *this; + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = true; + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = false; + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed || posx<0 || posy<0) return *this; + _update_window_pos(); + const int res = (int)SetCursorPos(_window_x + posx,_window_y + posy); + if (res) { _mouse_x = posx; _mouse_y = posy; } + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + SetWindowTextA(_window, tmp); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(); + } + + CImgDisplay& paint() { + if (_is_closed) return *this; + WaitForSingleObject(_mutex,INFINITE); + SetDIBitsToDevice(_hdc,0,0,_width,_height,0,0,0,_height,_data,&_bmi,DIB_RGB_COLORS); + ReleaseMutex(_mutex); + return *this; + } + + template + CImgDisplay& render(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + + const T + *data1 = img._data, + *data2 = (img._spectrum>=2)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>=3)?img.data(0,0,0,2):data1; + + WaitForSingleObject(_mutex,INFINITE); + unsigned int + *const ndata = (img._width==_width && img._height==_height)?_data: + new unsigned int[(size_t)img._width*img._height], + *ptrd = ndata; + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } + if (ndata!=_data) { _render_resize(ndata,img._width,img._height,_data,_width,_height); delete[] ndata; } + ReleaseMutex(_mutex); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + HDC hScreen = GetDC(GetDesktopWindow()); + if (hScreen) { + const int + width = GetDeviceCaps(hScreen,HORZRES), + height = GetDeviceCaps(hScreen,VERTRES); + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + if (_x1>=0 && _x0=0 && _y0 + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned int *ptrs = _data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned int val = *(ptrs++); + *(data1++) = (T)(unsigned char)(val>>16); + *(data2++) = (T)(unsigned char)((val>>8)&0xFF); + *(data3++) = (T)(unsigned char)(val&0xFF); + } + return *this; + } +#endif + + //@} + }; + + /* + #-------------------------------------- + # + # + # + # Definition of the CImg structure + # + # + # + #-------------------------------------- + */ + + //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T. + /** + This is the main class of the %CImg Library. It declares and constructs + an image, allows access to its pixel values, and is able to perform various image operations. + + \par Image representation + + A %CImg image is defined as an instance of the container \c CImg, which contains a regular grid of pixels, + each pixel value being of type \c T. The image grid can have up to 4 dimensions: width, height, depth + and number of channels. + Usually, the three first dimensions are used to describe spatial coordinates (x,y,z), + while the number of channels is rather used as a vector-valued dimension + (it may describe the R,G,B color channels for instance). + If you need a fifth dimension, you can use image lists \c CImgList 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, ...). + Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions. + + Concerning the pixel value type \c T: + fully supported template types are the basic C++ types: unsigned char, char, short, unsigned int, int, + unsigned long, long, float, double, ... . + Typically, fast image display can be done using CImg images, + while complex image processing algorithms may be rather coded using CImg or CImg + images that have floating-point pixel values. The default value for the template T is \c float. + Using your own template types may be possible. However, you will certainly have to define the complete set + of arithmetic and logical operators for your class. + + \par Image structure + + The \c CImg structure contains \e six fields: + - \c _width defines the number of \a columns of the image (size along the X-axis). + - \c _height defines the number of \a rows of the image (size along the Y-axis). + - \c _depth defines the number of \a slices of the image (size along the Z-axis). + - \c _spectrum defines the number of \a channels of the image (size along the C-axis). + - \c _data defines a \a pointer to the \a pixel \a data (of type \c T). + - \c _is_shared is a boolean that tells if the memory buffer \c data is shared with + another image. + + You can access these fields publicly although it is recommended to use the dedicated functions + width(), height(), depth(), spectrum() and ptr() to do so. + Image dimensions are not limited to a specific range (as long as you got enough available memory). + A value of \e 1 usually means that the corresponding dimension is \a flat. + If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty. + Empty images should not contain any pixel data and thus, will not be processed by CImg member functions + (a CImgInstanceException will be thrown instead). + Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage). + + \par Image declaration and construction + + Declaring an image can be done by using one of the several available constructors. + Here is a list of the most used: + + - Construct images from arbitrary dimensions: + - CImg img; declares an empty image. + - CImg img(128,128); declares a 128x128 greyscale image with + \c unsigned \c char pixel values. + - CImg img(3,3); declares a 3x3 matrix with \c double coefficients. + - CImg img(256,256,1,3); declares a 256x256x1x3 (color) image + (colors are stored as an image with three channels). + - CImg img(128,128,128); declares a 128x128x128 volumetric and greyscale image + (with \c double pixel values). + - CImg<> img(128,128,128,3); declares a 128x128x128 volumetric color image + (with \c float pixels, which is the default value of the template parameter \c T). + - \b Note: images pixels are not automatically initialized to 0. You may use the function \c fill() to + do it, or use the specific constructor taking 5 parameters like this: + CImg<> img(128,128,128,3,0); declares a 128x128x128 volumetric color image with all pixel values to 0. + + - Construct images from filenames: + - CImg img("image.jpg"); reads a JPEG color image from the file "image.jpg". + - CImg img("analyze.hdr"); reads a volumetric image (ANALYZE7.5 format) from the + file "analyze.hdr". + - \b Note: You need to install ImageMagick + to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io). + + - Construct images from C-style arrays: + - CImg img(data_buffer,256,256); constructs a 256x256 greyscale image from a \c int* buffer + \c data_buffer (of size 256x256=65536). + - CImg img(data_buffer,256,256,1,3); constructs a 256x256 color image + from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others). + + The complete list of constructors can be found here. + + \par Most useful functions + + The \c CImg class contains a lot of functions that operates on images. + Some of the most useful are: + + - operator()(): Read or write pixel values. + - display(): displays the image in a new window. + **/ + template + struct CImg { + + unsigned int _width, _height, _depth, _spectrum; + bool _is_shared; + T *_data; + + //! Simple iterator type, to loop through each pixel value of an image instance. + /** + \note + - The \c CImg::iterator type is defined to be a T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + CImg img("reference.jpg"); // Load image from file + // Set all pixels to '0', with a CImg iterator. + for (CImg::iterator it = img.begin(), it::const_iterator type is defined to be a \c const \c T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + 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::value_type type of a \c CImg is defined to be a \c T. + - \c CImg::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimg_plugin +#include cimg_plugin +#endif +#ifdef cimg_plugin1 +#include cimg_plugin1 +#endif +#ifdef cimg_plugin2 +#include cimg_plugin2 +#endif +#ifdef cimg_plugin3 +#include cimg_plugin3 +#endif +#ifdef cimg_plugin4 +#include cimg_plugin4 +#endif +#ifdef cimg_plugin5 +#include cimg_plugin5 +#endif +#ifdef cimg_plugin6 +#include cimg_plugin6 +#endif +#ifdef cimg_plugin7 +#include cimg_plugin7 +#endif +#ifdef cimg_plugin8 +#include cimg_plugin8 +#endif + + //@} + //--------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //--------------------------------------------------------- + + //! Destroy image. + /** + \note + - The pixel buffer data() is deallocated if necessary, e.g. for non-empty and non-shared image instances. + - Destroying an empty or shared image does nothing actually. + \warning + - When destroying a non-shared image, make sure that you will \e not operate on a remaining shared image + that shares its buffer with the destroyed instance, in order to avoid further invalid memory access + (to a deallocated buffer). + **/ + ~CImg() { + if (!_is_shared) delete[] _data; + } + + //! Construct empty image. + /** + \note + - An empty image has no pixel data and all of its dimensions width(), height(), depth(), spectrum() + are set to \c 0, as well as its pixel buffer pointer data(). + - An empty image may be re-assigned afterwards, e.g. with the family of + assign(unsigned int,unsigned int,unsigned int,unsigned int) methods, + or by operator=(const CImg&). In all cases, the type of pixels stays \c T. + - 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 + \endcode + **/ + CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {} + + //! Construct image with specified size. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \note + - It is able to create only \e non-shared images, and allocates thus a pixel buffer data() + for each constructed image instance. + - Setting one dimension \c size_x,\c size_y,\c size_z or \c size_c to \c 0 leads to the construction of + an \e empty image. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values during construction (e.g. with \c 0), use constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead. + \par Example + \code + CImg 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; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value Initialization value. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), + but it also fills the pixel buffer with the specified \c value. + \warning + - It cannot be used to construct a vector-valued image and initialize it with \e vector-valued pixels + (e.g. RGB vector, for color images). + For this task, you may use fillC() after construction. + **/ + CImg(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value): + _is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(value); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified sequence of integers \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be an \e integer). + \param value1 Second value of the initialization sequence (must be an \e integer). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \warning + - You must specify \e exactly \c size_x*\c size_y*\c size_z*\c size_c integers in the initialization sequence. + Otherwise, the constructor may crash or fill your image pixels with garbage. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _CImg_stdarg(img,a0,a1,N,t) { \ + size_t _siz = (size_t)N; \ + if (_siz--) { \ + va_list ap; \ + va_start(ap,a1); \ + T *ptrd = (img)._data; \ + *(ptrd++) = (T)a0; \ + if (_siz--) { \ + *(ptrd++) = (T)a1; \ + for ( ; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \ + } \ + va_end(ap); \ + } \ + } + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + } + +#if cimg_use_cpp11==1 + //! Construct image with specified size and initialize pixel values from an initializer list of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... } + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param { value0, value1, ... } Initialization list + \param repeat_values Tells if the value filling process is repeated over the image. + + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + { 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64 }); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _cimg_constructor_cpp11(repeat_values) \ + auto it = values.begin(); \ + size_t siz = size(); \ + if (repeat_values) for (T *ptrd = _data; siz--; ) { \ + *(ptrd++) = (T)(*(it++)); if (it==values.end()) it = values.begin(); } \ + else { siz = std::min(siz,values.size()); for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); } + assign(size_x,size_y,size_z,size_c); + _cimg_constructor_cpp11(repeat_values); + } + + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, const unsigned int size_y, + std::initializer_list values, + const bool repeat_values=true): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, + std::initializer_list values, + const bool repeat_values=true):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x); + _cimg_constructor_cpp11(repeat_values); + } + + //! Construct single channel 1D image with pixel values and width obtained from an initializer list of integers. + /** + Construct a new image instance of size \c width x \c 1 x \c 1 x \c 1, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... }. Image width is + given by the size of the initializer list. + \param { value0, value1, ... } Initialization list + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int) with height=1, depth=1, and spectrum=1, + but it also fills the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img = {10,20,30,20,10 }; // Construct a 5x1 image with one channel, and set its pixel values + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + CImg(const std::initializer_list values): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(values.size(),1,1,1); + auto it = values.begin(); + unsigned int siz = _width; + for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); + } + + template + CImg & operator=(std::initializer_list values) { + _cimg_constructor_cpp11(siz>values.size()); + return *this; + } +#endif + + //! Construct image with specified size and initialize pixel values from a sequence of doubles. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initialize pixel values from the specified sequence of doubles \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be a \e double). + \param value1 Second value of the initialization sequence (must be a \e double). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...), but + takes a sequence of double values instead of integers. + \warning + - You must specify \e exactly \c dx*\c dy*\c dz*\c dc doubles in the initialization sequence. + Otherwise, the constructor may crash or fill your image with garbage. + For instance, the code below will probably crash on most platforms: + \code + const CImg img(2,2,1,1, 0.5,0.5,255,255); // FAIL: The two last arguments are 'int', not 'double'! + \endcode + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + } + + //! Construct image with specified size and initialize pixel values from a value string. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified string \c values. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param values Value string describing the way pixel values are set. + \param repeat_values Tells if the value filling process is repeated over the image. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with values described in the value string \c values. + - Value string \c values may describe two different filling processes: + - Either \c values is a sequences of values assigned to the image pixels, as in "1,2,3,7,8,2". + In this case, set \c repeat_values to \c true to periodically fill the image with the value sequence. + - Either, \c values is a formula, as in "cos(x/10)*sin(y/20)". + In this case, parameter \c repeat_values is pointless. + - For both cases, specifying \c repeat_values is mandatory. + It disambiguates the possible overloading of constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) with \c T being a const char*. + - 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 + (img1,img2).display(); + \endcode + \image html ref_constructor2.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values):_is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(values,repeat_values); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified \c t* memory buffer. + \param values Pointer to the input memory buffer. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param is_shared Tells if input memory buffer must be shared by the current instance. + \note + - If \c is_shared is \c false, the image instance allocates its own pixel buffer, + and values from the specified input buffer are copied to the instance buffer. + If buffer types \c T and \c t are different, a regular static cast is performed during buffer copy. + - Otherwise, the image instance does \e not allocate a new buffer, and uses the input memory buffer as its + own pixel buffer. This case requires that types \c T and \c t are the same. Later, destroying such a shared + image will not deallocate the pixel buffer, this task being obviously charged to the initial buffer allocator. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - You must take care when operating on a shared image, since it may have an invalid pixel buffer pointer data() + (e.g. already deallocated). + \par Example + \code + unsigned char tab[256*256] = { 0 }; + CImg 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 + CImg(const t *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a (%u,%u,%u,%u) shared instance " + "from a (%s*) buffer (pixel types are different).", + cimg_instance, + size_x,size_y,size_z,size_c,CImg::pixel_type()); + } + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + + } + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = is_shared; + if (_is_shared) _data = const_cast(values); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy(_data,values,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image from reading an image file. + /** + Construct a new image instance with pixels of type \c T, and initialize pixel values with the data read from + an image file. + \param filename Filename, as a C-string. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it reads the image + dimensions and pixel values from the specified image file. + - The recognition of the image file format by %CImg higly depends on the tools installed on your system + and on the external libraries you used to link your code against. + - Considered pixel type \c T should better fit the file format specification, or data loss may occur during + file load (e.g. constructing a \c CImg from a float-valued image file). + - A \c CImgIOException is thrown when the specified \c filename cannot be read, or if the file format is not + recognized. + \par Example + \code + const CImg img("reference.jpg"); + img.display(); + \endcode + \image html ref_image.jpg + **/ + explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(filename); + } + + //! Construct image copy. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance. + \param img Input image to copy. + \note + - Constructed copy has the same size width() x height() x depth() x spectrum() and pixel values as the + input image \c img. + - If input image \c img is \e shared and if types \c T and \c t are the same, the constructed copy is also + \e shared, and shares its pixel buffer with \c img. + Modifying a pixel value in the constructed copy will thus also modifies it in the input image \c img. + This behavior is needful to allow functions to return shared images. + - Otherwise, the constructed copy allocates its own pixel buffer, and copies pixel values from the input + image \c img into its buffer. The copied pixel values may be eventually statically casted if types \c T and + \c t are different. + - Constructing a copy from an image \c img when types \c t and \c T are the same is significantly faster than + with different types. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. not enough available memory). + **/ + template + CImg(const CImg& img):_is_shared(false) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image copy \specialization. + CImg(const CImg& img) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = img._is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Advanced copy constructor. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance, + while forcing the shared state of the constructed copy. + \param img Input image to copy. + \param is_shared Tells about the shared state of the constructed copy. + \note + - Similar to CImg(const CImg&), except that it allows to decide the shared state of + the constructed image, which does not depend anymore on the shared state of the input image \c img: + - If \c is_shared is \c true, the constructed copy will share its pixel buffer with the input image \c img. + For that case, the pixel types \c T and \c t \e must be the same. + - If \c is_shared is \c false, the constructed copy will allocate its own pixel buffer, whether the input + image \c img is shared or not. + - A \c CImgArgumentException is thrown when a shared copy is requested with different pixel types \c T and \c t. + **/ + template + CImg(const CImg& img, const bool is_shared):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a shared instance from a " + "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).", + cimg_instance, + CImg::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data); + } + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Advanced copy constructor \specialization. + CImg(const CImg& img, const bool is_shared) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image with dimensions borrowed from another image. + /** + Construct a new image instance with pixels of type \c T, and size get from some dimensions of an existing + \c CImg instance. + \param img Input image from which dimensions are borrowed. + \param dimensions C-string describing the image size along the X,Y,Z and C-dimensions. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it takes the image dimensions + (\e not its pixel values) from an existing \c CImg instance. + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values (e.g. with \c 0), use constructor CImg(const CImg&,const char*,T) + 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') + \endcode + **/ + template + CImg(const CImg& img, const char *const dimensions): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values. + /** + Construct a new image instance with pixels of type \c T, and size get from the dimensions of an existing + \c CImg instance, and set all pixel values to specified \c value. + \param img Input image from which dimensions are borrowed. + \param dimensions String describing the image size along the X,Y,Z and V-dimensions. + \param value Value used for initialization. + \note + - Similar to CImg(const CImg&,const char*), but it also fills the pixel buffer with the specified \c value. + **/ + template + CImg(const CImg& img, const char *const dimensions, const T& value): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions).fill(value); + } + + //! Construct image from a display window. + /** + Construct a new image instance with pixels of type \c T, as a snapshot of an existing \c CImgDisplay instance. + \param disp Input display window. + \note + - The width() and height() of the constructed image instance are the same as the specified \c CImgDisplay. + - The depth() and spectrum() of the constructed image instance are respectively set to \c 1 and \c 3 + (i.e. a 2D color image). + - The image pixels are read as 8-bits RGB values. + **/ + explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + disp.snapshot(*this); + } + + // Constructor and assignment operator for rvalue references (c++11). + // This avoids an additional image copy for methods returning new images. Can save RAM for big images ! +#if cimg_use_cpp11==1 + CImg(CImg&& 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); + } +#endif + + //! Construct empty image \inplace. + /** + In-place version of the default constructor CImg(). It simply resets the instance to an empty image. + **/ + CImg& assign() { + if (!_is_shared) delete[] _data; + _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; + return *this; + } + + //! Construct image with specified size \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (siz!=curr_siz) { + if (_is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignement request of shared instance from specified " + "image (%u,%u,%u,%u).", + cimg_instance, + size_x,size_y,size_z,size_c); + else { + delete[] _data; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + return *this; + } + + //! Construct image with specified size and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,T). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value) { + return assign(size_x,size_y,size_z,size_c).fill(value); + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a sequence of doubles \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a value string \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values) { + return assign(size_x,size_y,size_z,size_c).fill(values,repeat_values); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \inplace. + /** + In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int). + **/ + template + 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; + if (!values || !siz) return assign(); + assign(size_x,size_y,size_z,size_c); + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c); + if (_is_shared || values + siz<_data || values>=_data + size()) { + assign(size_x,size_y,size_z,size_c); + if (_is_shared) std::memmove((void*)_data,(void*)values,siz*sizeof(T)); + else std::memcpy((void*)_data,(void*)values,siz*sizeof(T)); + } else { + T *new_data = 0; + try { new_data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy((void*)new_data,(void*)values,siz*sizeof(T)); + delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + } + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + template + 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) { + if (is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignment request of shared instance from (%s*) buffer" + "(pixel types are different).", + cimg_instance, + CImg::pixel_type()); + return assign(values,size_x,size_y,size_z,size_c); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const bool is_shared) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + if (!is_shared) { if (_is_shared) assign(); assign(values,size_x,size_y,size_z,size_c); } + else { + if (!_is_shared) { + if (values + siz<_data || values>=_data + size()) assign(); + else cimg::warn(_cimg_instance + "assign(): Shared image instance has overlapping memory.", + cimg_instance); + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true; + _data = const_cast(values); + } + return *this; + } + + //! Construct image from reading an image file \inplace. + /** + In-place version of the constructor CImg(const char*). + **/ + CImg& assign(const char *const filename) { + return load(filename); + } + + //! Construct image copy \inplace. + /** + In-place version of the constructor CImg(const CImg&). + **/ + template + CImg& assign(const CImg& img) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum); + } + + //! In-place version of the advanced copy constructor. + /** + In-place version of the constructor CImg(const CImg&,bool). + **/ + template + CImg& assign(const CImg& img, const bool is_shared) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum,is_shared); + } + + //! Construct image with dimensions borrowed from another image \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions) { + if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum); + unsigned int siz[4] = { 0,1,1,1 }, k = 0; + CImg item(256); + for (const char *s = dimensions; *s && k<4; ++k) { + if (cimg_sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item._data)>0) s+=std::strlen(item); + if (*s) { + unsigned int val = 0; char sep = 0; + if (cimg_sscanf(s,"%u%c",&val,&sep)>0) { + if (sep=='%') siz[k] = val*(k==0?_width:k==1?_height:k==2?_depth:_spectrum)/100; + else siz[k] = val; + while (*s>='0' && *s<='9') ++s; + if (sep=='%') ++s; + } else switch (cimg::lowercase(*s)) { + case 'x' : case 'w' : siz[k] = img._width; ++s; break; + case 'y' : case 'h' : siz[k] = img._height; ++s; break; + case 'z' : case 'd' : siz[k] = img._depth; ++s; break; + case 'c' : case 's' : siz[k] = img._spectrum; ++s; break; + default : + throw CImgArgumentException(_cimg_instance + "assign(): Invalid character '%c' detected in specified dimension string '%s'.", + cimg_instance, + *s,dimensions); + } + } + } + return assign(siz[0],siz[1],siz[2],siz[3]); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*,T). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions, const T& value) { + return assign(img,dimensions).fill(value); + } + + //! Construct image from a display window \inplace. + /** + In-place version of the constructor CImg(const CImgDisplay&). + **/ + CImg& assign(const CImgDisplay &disp) { + disp.snapshot(*this); + return *this; + } + + //! Construct empty image \inplace. + /** + Equivalent to assign(). + \note + - It has been defined for compatibility with STL naming conventions. + **/ + CImg& clear() { + return assign(); + } + + //! Transfer content of an image instance into another one. + /** + Transfer the dimensions and the pixel buffer content of an image instance into another one, + and replace instance by an empty image. It avoids the copy of the pixel buffer + when possible. + \param img Destination image. + \note + - Pixel types \c T and \c t of source and destination images can be different, though the process is + designed to be instantaneous when \c T and \c t are the same. + \par Example + \code + CImg 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 + CImg& move_to(CImg& img) { + img.assign(*this); + assign(); + return img; + } + + //! Transfer content of an image instance into another one \specialization. + CImg& move_to(CImg& img) { + if (_is_shared || img._is_shared) img.assign(*this); + else swap(img); + assign(); + return img; + } + + //! Transfer content of an image instance into a new image in an image list. + /** + Transfer the dimensions and the pixel buffer content of an image instance + into a newly inserted image at position \c pos in specified \c CImgList instance. + \param list Destination list. + \param pos Position of the newly inserted image in the list. + \note + - When optional parameter \c pos is ommited, the image instance is transfered as a new + image at the end of the specified \c list. + - It is convenient to sequentially insert new images into image lists, with no + additional copies of memory buffer. + \par Example + \code + CImgList 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 + CImgList& move_to(CImgList& list, const unsigned int pos=~0U) { + const unsigned int npos = pos>list._width?list._width:pos; + move_to(list.insert(1,npos)[npos]); + return list; + } + + //! Swap fields of two image instances. + /** + \param img Image to swap fields with. + \note + - It can be used to interchange the content of two images in a very fast way. Can be convenient when dealing + with algorithms requiring two swapping buffers. + \par Example + \code + CImg img1("lena.jpg"), + img2("milla.jpg"); + img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena' + \endcode + **/ + CImg& swap(CImg& img) { + cimg::swap(_width,img._width,_height,img._height,_depth,img._depth,_spectrum,img._spectrum); + cimg::swap(_data,img._data); + cimg::swap(_is_shared,img._is_shared); + return img; + } + + //! Return a reference to an empty image. + /** + \note + This function is useful mainly to declare optional parameters having type \c CImg in functions prototypes, + e.g. + \code + void f(const int x=0, const int y=0, const CImg& img=CImg::empty()); + \endcode + **/ + static CImg& empty() { + static CImg _empty; + return _empty.assign(); + } + + //! Return a reference to an empty image \const. + static const CImg& const_empty() { + static const CImg _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Access to a pixel value. + /** + Return a reference to a located pixel value of the image instance, + being possibly \e const, whether the image instance is \e const or not. + This is the standard method to get/set pixel values in \c CImg images. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Range of pixel coordinates start from (0,0,0,0) to + (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 + 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 + 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' + const float + valR = img(10,10,0,0), // Read red value at coordinates (10,10) + valG = img(10,10,0,1), // Read green value at coordinates (10,10) + valB = img(10,10,2), // Read blue value at coordinates (10,10) (Z-coordinate can be omitted) + avg = (valR + valG + valB)/3; // Compute average pixel value + img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the color pixel (10,10) by the average grey value + \endcode + **/ +#if cimg_verbosity>=3 + T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (!_data || off>=size()) { + cimg::warn(_cimg_instance + "operator(): Invalid pixel request, at coordinates (%d,%d,%d,%d) [offset=%u].", + cimg_instance, + (int)x,(int)y,(int)z,(int)c,off); + return *_data; + } + else return _data[off]; + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->operator()(x,y,z,c); + } + + //! Access to a pixel value. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param wh Precomputed offset, must be equal to width()*\ref height(). + \param whd Precomputed offset, must be equal to width()*\ref height()*\ref depth(). + \note + - Similar to (but faster than) operator()(). + It uses precomputed offsets to optimize memory access. You may use it to optimize + the reading/writing of several pixel values in the same image (e.g. in a loop). + **/ + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) const { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } +#else + T& operator()(const unsigned int x) { + return _data[x]; + } + + const T& operator()(const unsigned int x) const { + return _data[x]; + } + + T& operator()(const unsigned int x, const unsigned int y) { + return _data[x + y*_width]; + } + + const T& operator()(const unsigned int x, const unsigned int y) const { + return _data[x + y*_width]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) { + return _data[x + y*_width + z*wh]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) const { + return _data[x + y*_width + z*wh]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) { + return _data[x + y*_width + z*wh + c*whd]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) const { + return _data[x + y*_width + z*wh + c*whd]; + } +#endif + + //! Implicitely cast an image into a \c T*. + /** + Implicitely cast a \c CImg 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 + std::printf("'img1' is not empty, 'img2' is empty."); + } + } + \endcode + - It also allows to use brackets to access pixel values, without need for a \c CImg::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) + \endcode + **/ + operator T*() { + return _data; + } + + //! Implicitely cast an image into a \c T* \const. + operator const T*() const { + return _data; + } + + //! Assign a value to all image pixels. + /** + Assign specified \c value to each pixel value of the image instance. + \param value Value that will be assigned to image pixels. + \note + - The image size is never modified. + - The \c value may be casted to pixel type \c T if necessary. + \par Example + \code + CImg 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) { + return fill(value); + } + + //! Assign pixels values from a specified expression. + /** + Initialize all pixel values from the specified string \c expression. + \param expression Value string describing the way pixel values are set. + \note + - String parameter \c expression may describe different things: + - If \c expression is a list of values (as in \c "1,2,3,8,3,2"), or a formula (as in \c "(x*y)%255"), + the pixel values are set from specified \c expression and the image size is not modified. + - If \c expression is a filename (as in \c "reference.jpg"), the corresponding image file is loaded and + replace the image instance. The image size is modified if necessary. + \par Example + \code + CImg img1(100,100), img2(img1), img3(img1); // Declare 3 scalar images 100x100 with unitialized values + img1 = "0,50,100,150,200,250,200,150,100,50"; // Set pixel values of 'img1' from a value sequence + img2 = "10*((x*y)%25)"; // Set pixel values of 'img2' from a formula + img3 = "reference.jpg"; // Set pixel values of 'img3' from a file (image size is modified) + (img1,img2,img3).display(); + \endcode + \image html ref_operator_eq.jpg + **/ + CImg& operator=(const char *const expression) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { + _fill(expression,true,1,0,0,"operator=",0); + } catch (CImgException&) { + cimg::exception_mode(omode); + load(expression); + } + cimg::exception_mode(omode); + return *this; + } + + //! Copy an image into the current image instance. + /** + Similar to the in-place copy constructor assign(const CImg&). + **/ + template + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy an image into the current image instance \specialization. + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy the content of a display window to the current image instance. + /** + Similar to assign(const CImgDisplay&). + **/ + CImg& operator=(const CImgDisplay& disp) { + disp.snapshot(*this); + return *this; + } + + //! In-place addition operator. + /** + Add specified \c value to all pixels of an image instance. + \param value Value to add. + \note + - Resulting pixel values are casted to fit the pixel type \c T. + For instance, adding \c 0.2 to a \c CImg 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 + // 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 + (img1,img2,img3).display(); + \endcode + \image html ref_operator_plus.jpg + **/ + template + CImg& operator+=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + value,524288); + return *this; + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the specified string \c expression. + \param expression Value string describing the way pixel values are added. + \note + - Similar to operator=(const char*), except that it adds values to the pixels of the current image instance, + instead of assigning them. + **/ + CImg& operator+=(const char *const expression) { + return *this+=(+*this)._fill(expression,true,1,0,0,"operator+=",this); + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the values of the input image \c img. + \param img Input image to add. + \note + - The size of the image instance is never modified. + - It is not mandatory that input image \c img has the same size as the image instance. + If less values are available in \c img, then the values are added periodically. For instance, adding one + WxH scalar image (spectrum() equal to \c 1) to one WxH color image (spectrum() equal to \c 3) + means each color channel will be incremented with the same values at the same locations. + \par Example + \code + CImg 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 + (img2,img1).display(); + \endcode + \image html ref_operator_plus1.jpg + **/ + template + CImg& operator+=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this+=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator++() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + 1,524288); + return *this; + } + + //! In-place increment operator (postfix). + /** + Add \c 1 to all image pixels, and return a new copy of the initial (pre-incremented) image instance. + \note + - Use the prefixed version operator++() if you don't need a copy of the initial + (pre-incremented) image instance, since a useless image copy may be expensive in terms of memory usage. + **/ + CImg operator++(int) { + const CImg copy(*this,false); + ++*this; + return copy; + } + + //! Return a non-shared copy of the image instance. + /** + \note + - Use this operator to ensure you get a non-shared copy of an image instance with same pixel type \c T. + Indeed, the usual copy constructor CImg(const CImg&) returns a shared copy of a shared input image, + and it may be not desirable to work on a regular copy (e.g. for a resize operation) if you have no + information about the shared state of the input image. + - Writing \c (+img) is equivalent to \c CImg(img,false). + **/ + CImg operator+() const { + return CImg(*this,false); + } + + //! Addition operator. + /** + Similar to operator+=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator+(const t value) const { + return CImg<_cimg_Tt>(*this,false)+=value; + } + + //! Addition operator. + /** + Similar to operator+=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator+(const char *const expression) const { + return CImg(*this,false)+=expression; + } + + //! Addition 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. + **/ + template + CImg<_cimg_Tt> operator+(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)+=img; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const t), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - value,524288); + return *this; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const char*), except that it performs a substraction instead of an addition. + **/ + CImg& operator-=(const char *const expression) { + return *this-=(+*this)._fill(expression,true,1,0,0,"operator-=",this); + } + + //! In-place substraction operator. + /** + Similar to operator+=(const CImg&), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this-=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator--() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - 1,524288); + return *this; + } + + //! In-place decrement operator (postfix). + /** + Similar to operator++(int), except that it performs a decrement instead of an increment. + **/ + CImg operator--(int) { + const CImg copy(*this,false); + --*this; + return copy; + } + + //! Replace each pixel by its opposite value. + /** + \note + - If the computed opposite values are out-of-range, they are treated as with standard C++ numeric types. + For instance, the \c unsigned \c char opposite of \c 1 is \c 255. + \par Example + \code + const CImg + img1("reference.jpg"), // Load a RGB color image + img2 = -img1; // Compute its opposite (in 'unsigned char') + (img1,img2).display(); + \endcode + \image html ref_operator_minus.jpg + **/ + CImg operator-() const { + return CImg(_width,_height,_depth,_spectrum,(T)0)-=*this; + } + + //! Substraction operator. + /** + Similar to operator-=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator-(const t value) const { + return CImg<_cimg_Tt>(*this,false)-=value; + } + + //! Substraction operator. + /** + Similar to operator-=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator-(const char *const expression) const { + return CImg(*this,false)-=expression; + } + + //! Substraction 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. + **/ + template + CImg<_cimg_Tt> operator-(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)-=img; + } + + //! In-place multiplication operator. + /** + Similar to operator+=(const t), except that it performs a multiplication instead of an addition. + **/ + template + CImg& operator*=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr * value,262144); + return *this; + } + + //! In-place multiplication operator. + /** + 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,1,0,0,"operator*=",this)); + } + + //! In-place multiplication operator. + /** + Replace the image instance by the matrix multiplication between the image instance and the specified matrix + \c img. + \param img Second operand of the matrix multiplication. + \note + - It does \e not compute a pointwise multiplication between two images. For this purpose, use + mul(const CImg&) instead. + - 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' + // 'A' is now a 1x2 vector whose values are [5;11]. + \endcode + **/ + template + CImg& operator*=(const CImg& img) { + return ((*this)*img).move_to(*this); + } + + //! Multiplication operator. + /** + Similar to operator*=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator*(const t value) const { + return CImg<_cimg_Tt>(*this,false)*=value; + } + + //! Multiplication operator. + /** + Similar to operator*=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator*(const char *const expression) const { + return CImg(*this,false)*=expression; + } + + //! Multiplication 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. + **/ + 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)", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + 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_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 *ps0 = img.data(), *ps1 = img.data(0,1); + Tt *pd0 = res.data(), *pd1 = res.data(0,1); + const Ttdouble + a0 = (Ttdouble)_data[0], a1 = (Ttdouble)_data[1], + a2 = (Ttdouble)_data[2], a3 = (Ttdouble)_data[3]; + cimg_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++); + *(pd0++) = (Tt)(a0*x + a1*y); + *(pd1++) = (Tt)(a2*x + a3*y); + } + return res; + } + case 3 : { // 3x3_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *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_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z); + *(pd1++) = (Tt)(a3*x + a4*y + a5*z); + *(pd2++) = (Tt)(a6*x + a7*y + a8*z); + } + return res; + } + case 4 : { // 4x4_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2), *ps3 = img.data(0,3); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *pd2 = res.data(0,2), *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_forX(img,col) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++), + c = (Ttdouble)*(ps3++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z + a3*c); + *(pd1++) = (Tt)(a4*x + a5*y + a6*z + a7*c); + *(pd2++) = (Tt)(a8*x + a9*y + a10*z + a11*c); + *(pd3++) = (Tt)(a12*x + a13*y + a14*z + a15*c); + } + return res; + } + } + } + + // Fallback to generic version. +#ifdef cimg_use_openmp + 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 + Tt *ptrd = res._data; + cimg_forXY(res,i,j) { + Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); *(ptrd++) = (Tt)value; + } +#endif + return res; + } + + //! In-place division operator. + /** + Similar to operator+=(const t), except that it performs a division instead of an addition. + **/ + template + CImg& operator/=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr / value,32768); + return *this; + } + + //! In-place division operator. + /** + 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,1,0,0,"operator/=",this)); + } + + //! In-place division operator. + /** + Replace the image instance by the (right) matrix division between the image instance and the specified + matrix \c img. + \param img Second operand of the matrix division. + \note + - It does \e not compute a pointwise division between two images. For this purpose, use + div(const CImg&) instead. + - It returns the matrix operation \c A*inverse(img). + - The size of the image instance can be modified by this operator. + **/ + template + CImg& operator/=(const CImg& img) { + return (*this*img.get_invert()).move_to(*this); + } + + //! Division operator. + /** + Similar to operator/=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator/(const t value) const { + return CImg<_cimg_Tt>(*this,false)/=value; + } + + //! Division operator. + /** + Similar to operator/=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator/(const char *const expression) const { + return CImg(*this,false)/=expression; + } + + //! Division 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. + **/ + template + CImg<_cimg_Tt> operator/(const CImg& img) const { + return (*this)*img.get_invert(); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const t), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::mod(*ptr,(T)value),16384); + return *this; + } + + //! In-place modulo operator. + /** + 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,1,0,0,"operator%=",this); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const CImg&), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this%=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> operator%(const t value) const { + return CImg<_cimg_Tt>(*this,false)%=value; + } + + //! Modulo operator. + /** + Similar to operator%=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator%(const char *const expression) const { + return CImg(*this,false)%=expression; + } + + //! Modulo 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. + **/ + template + CImg<_cimg_Tt> operator%(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)%=img; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const t), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr & (ulongT)value,32768); + return *this; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition. + **/ + CImg& operator&=(const char *const expression) { + return *this&=(+*this)._fill(expression,true,1,0,0,"operator&=",this); + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this&=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator&(const t value) const { + return (+*this)&=value; + } + + //! Bitwise AND operator. + /** + Similar to operator&=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator&(const char *const expression) const { + return (+*this)&=expression; + } + + //! Bitwise AND 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 is \c T. + **/ + template + CImg operator&(const CImg& img) const { + return (+*this)&=img; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr | (ulongT)value,32768); + return *this; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition. + **/ + CImg& operator|=(const char *const expression) { + return *this|=(+*this)._fill(expression,true,1,0,0,"operator|=",this); + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this|=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator|(const t value) const { + return (+*this)|=value; + } + + //! Bitwise OR operator. + /** + Similar to operator|=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator|(const char *const expression) const { + return (+*this)|=expression; + } + + //! Bitwise OR 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 is \c T. + **/ + template + CImg operator|(const CImg& img) const { + return (+*this)|=img; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const t) instead. + **/ + template + CImg& operator^=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr ^ (ulongT)value,32768); + return *this; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead. + **/ + CImg& operator^=(const char *const expression) { + return *this^=(+*this)._fill(expression,true,1,0,0,"operator^=",this); + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const CImg&) instead. + **/ + template + CImg& operator^=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator^(const t value) const { + return (+*this)^=value; + } + + //! Bitwise XOR operator. + /** + Similar to operator^=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator^(const char *const expression) const { + return (+*this)^=expression; + } + + //! Bitwise XOR 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 is \c T. + **/ + template + CImg operator^(const CImg& img) const { + return (+*this)^=img; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) << (int)value,65536); + return *this; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition. + **/ + CImg& operator<<=(const char *const expression) { + return *this<<=(+*this)._fill(expression,true,1,0,0,"operator<<=",this); + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator<<(const t value) const { + return (+*this)<<=value; + } + + //! Bitwise left shift operator. + /** + Similar to operator<<=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator<<(const char *const expression) const { + return (+*this)<<=expression; + } + + //! Bitwise left shift 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 is \c T. + **/ + template + CImg operator<<(const CImg& img) const { + return (+*this)<<=img; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) >> (int)value,65536); + return *this; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition. + **/ + CImg& operator>>=(const char *const expression) { + return *this>>=(+*this)._fill(expression,true,1,0,0,"operator>>=",this); + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs> (int)*(ptrs++)); + for (const t *ptrs = img._data; ptrd> (int)*(ptrs++)); + } + return *this; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + template + CImg operator>>(const t value) const { + return (+*this)>>=value; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator>>(const char *const expression) const { + return (+*this)>>=expression; + } + + //! Bitwise right shift 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 is \c T. + **/ + template + CImg operator>>(const CImg& img) const { + return (+*this)>>=img; + } + + //! Bitwise inversion operator. + /** + Similar to operator-(), except that it compute the bitwise inverse instead of the opposite value. + **/ + CImg operator~() const { + CImg res(_width,_height,_depth,_spectrum); + const T *ptrs = _data; + cimg_for(res,ptrd,T) { const ulongT value = (ulongT)*(ptrs++); *ptrd = (T)~value; } + return res; + } + + //! Test if all pixels of an image have the same value. + /** + Return \c true is all pixels of the image instance are equal to the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator==(const t value) const { + if (is_empty()) return false; + typedef _cimg_Tt Tt; + bool is_equal = true; + for (T *ptrd = _data + size(); is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)value)) {} + return is_equal; + } + + //! Test if all pixel values of an image follow a specified expression. + /** + Return \c true is all pixels of the image instance are equal to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator==(const char *const expression) const { + return *this==(+*this)._fill(expression,true,1,0,0,"operator==",this); + } + + //! Test if two images have the same size and values. + /** + Return \c true if the image instance and the input image \c img have the same dimensions and pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - The pixel buffer pointers data() of the two compared images do not have to be the same for operator==() + to return \c true. + Only the dimensions and the pixel values matter. Thus, the comparison can be \c true even for different + pixel types \c T and \c t. + \par Example + \code + const CImg 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 + **/ + template + bool operator==(const CImg& img) const { + typedef _cimg_Tt Tt; + const ulongT siz = size(); + bool is_equal = true; + if (siz!=img.size()) return false; + t *ptrs = img._data + siz; + for (T *ptrd = _data + siz; is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)*(--ptrs))) {} + return is_equal; + } + + //! Test if pixels of an image are all different from a value. + /** + Return \c true is all pixels of the image instance are different than the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator!=(const t value) const { + return !((*this)==value); + } + + //! Test if all pixel values of an image are different from a specified expression. + /** + Return \c true is all pixels of the image instance are different to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator!=(const char *const expression) const { + return !((*this)==expression); + } + + //! Test if two images have different sizes or values. + /** + Return \c true if the image instance and the input image \c img have different dimensions or pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - Writing \c img1!=img2 is equivalent to \c !(img1==img2). + **/ + template + bool operator!=(const CImg& img) const { + return !((*this)==img); + } + + //! Construct an image list from two images. + /** + Return a new list of image (\c CImgList instance) containing exactly two elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image \c img, at position [\c 1]. + + \param img Input image that will be the second image of the resulting list. + \note + - The family of operator,() is convenient to easily create list of images, but it is also \e quite \e slow + in practice (see warning below). + - Constructed lists contain no shared images. If image instance or input image \c img are shared, they are + inserted as new non-shared copies in the resulting list. + - The pixel type of the returned list may be a superset of the initial pixel type \c T, if necessary. + \warning + - Pipelining operator,() \c N times will perform \c N copies of the entire content of a (growing) image list. + This may become very expensive in terms of speed and used memory. You should avoid using this technique to + build a new CImgList instance from several images, if you are seeking for performance. + Fast insertions of images in an image list are possible with + CImgList::insert(const CImg&,unsigned int,bool) or move_to(CImgList&,unsigned int). + \par Example + \code + const CImg + 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' + \endcode + \image html ref_operator_comma.jpg + **/ + template + CImgList<_cimg_Tt> operator,(const CImg& img) const { + return CImgList<_cimg_Tt>(*this,img); + } + + //! Construct an image list from image instance and an input image list. + /** + Return a new list of images (\c CImgList instance) containing exactly \c list.size() \c + \c 1 elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image list \c list, from positions [\c 1] to [\c list.size()]. + + \param list Input image list that will be appended to the image instance. + \note + - Similar to operator,(const CImg&) const, except that it takes an image list as an argument. + **/ + template + CImgList<_cimg_Tt> operator,(const CImgList& list) const { + return CImgList<_cimg_Tt>(list,false).insert(*this,0); + } + + //! Split image along specified axis. + /** + Return a new list of images (\c CImgList instance) containing the splitted components + of the instance image along the specified axis. + \param axis Splitting axis (can be '\c x','\c y','\c z' or '\c c') + \note + - Similar to get_split(char,int) const, with default second argument. + \par Example + \code + const CImg 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 + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the number of image columns. + /** + Return the image width, i.e. the image dimension along the X-axis. + \note + - The width() of an empty image is equal to \c 0. + - width() is typically equal to \c 1 when considering images as \e vectors for matrix calculations. + - width() returns an \c int, although the image width is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._width. + **/ + int width() const { + return (int)_width; + } + + //! Return the number of image rows. + /** + Return the image height, i.e. the image dimension along the Y-axis. + \note + - The height() of an empty image is equal to \c 0. + - height() returns an \c int, although the image height is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._height. + **/ + int height() const { + return (int)_height; + } + + //! Return the number of image slices. + /** + Return the image depth, i.e. the image dimension along the Z-axis. + \note + - The depth() of an empty image is equal to \c 0. + - depth() is typically equal to \c 1 when considering usual 2D images. When depth()\c > \c 1, the image + is said to be \e volumetric. + - depth() returns an \c int, although the image depth is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._depth. + **/ + int depth() const { + return (int)_depth; + } + + //! Return the number of image channels. + /** + Return the number of image channels, i.e. the image dimension along the C-axis. + \note + - The spectrum() of an empty image is equal to \c 0. + - spectrum() is typically equal to \c 1 when considering scalar-valued images, to \c 3 + for RGB-coded color images, and to \c 4 for RGBA-coded color images (with alpha-channel). + The number of channels of an image instance is not limited. The meaning of the pixel values is not linked + up to the number of channels (e.g. a 4-channel image may indifferently stands for a RGBA or CMYK color image). + - spectrum() returns an \c int, although the image spectrum is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._spectrum. + **/ + int spectrum() const { + return (int)_spectrum; + } + + //! Return the total number of pixel values. + /** + Return width()*\ref height()*\ref depth()*\ref spectrum(), + i.e. the total number of values of type \c T in the pixel buffer of the image instance. + \note + - The size() of an empty image is equal to \c 0. + - The allocated memory size for a pixel buffer of a non-shared \c CImg instance is equal to + size()*sizeof(T). + \par Example + \code + 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 + **/ + ulongT size() const { + return (ulongT)_width*_height*_depth*_spectrum; + } + + //! Return a pointer to the first pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the first value in the pixel buffer of the image instance, + whether the instance is \c const or not. + \note + - The data() of an empty image is equal to \c 0 (null pointer). + - The allocated pixel buffer for the image instance starts from \c data() + and goes to data()+\ref size() - 1 (included). + - To get the pointer to one particular location of the pixel buffer, use + data(unsigned int,unsigned int,unsigned int,unsigned int) instead. + **/ + T* data() { + return _data; + } + + //! Return a pointer to the first pixel value \const. + const T* data() const { + return _data; + } + + //! Return a pointer to a located pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the value located at (\c x,\c y,\c z,\c c) in the pixel buffer + of the image instance, + whether the instance is \c const or not. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)). Thus, this method has the same + properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int). + **/ +#if cimg_verbosity>=3 + T *data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (off>=size()) + cimg::warn(_cimg_instance + "data(): Invalid pointer request, at coordinates (%u,%u,%u,%u) [offset=%u].", + cimg_instance, + x,y,z,c,off); + return _data + off; + } + + //! Return a pointer to a located pixel value \const. + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->data(x,y,z,c); + } +#else + T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } + + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } +#endif + + //! Return the offset to a located pixel value, with respect to the beginning of the pixel buffer. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)) - img.data(). + 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 + \endcode + **/ + longT offset(const int x, const int y=0, const int z=0, const int c=0) const { + return x + (longT)y*_width + (longT)z*_width*_height + (longT)c*_width*_height*_depth; + } + + //! Return a CImg::iterator pointing to the first pixel value. + /** + \note + - Equivalent to data(). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + iterator begin() { + return _data; + } + + //! Return a CImg::iterator pointing to the first value of the pixel buffer \const. + const_iterator begin() const { + return _data; + } + + //! Return a CImg::iterator pointing next to the last pixel value. + /** + \note + - Writing \c img.end() is equivalent to img.data() + img.size(). + - It has been mainly defined for compatibility with STL naming conventions. + \warning + - The returned iterator actually points to a value located \e outside the acceptable bounds of the pixel buffer. + Trying to read or write the content of the returned iterator will probably result in a crash. + Use it mainly as a strict upper bound for a CImg::iterator. + \par Example + \code + 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::iterator pointing next to the last pixel value \const. + const_iterator end() const { + return _data + size(); + } + + //! Return a reference to the first pixel value. + /** + \note + - Writing \c img.front() is equivalent to img[0], or img(0,0,0,0). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& front() { + return *_data; + } + + //! Return a reference to the first pixel value \const. + const T& front() const { + return *_data; + } + + //! Return a reference to the last pixel value. + /** + \note + - Writing \c img.back() is equivalent to img[img.size() - 1], or + img(img.width() - 1,img.height() - 1,img.depth() - 1,img.spectrum() - 1). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& back() { + return *(_data + size() - 1); + } + + //! Return a reference to the last pixel value \const. + const T& back() const { + return *(_data + size() - 1); + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to a specified default value in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note + - Writing \c img.at(offset,out_value) is similar to img[offset], except that if \c offset + is outside bounds (e.g. \c offset<0 or \c offset>=img.size()), a reference to a value \c out_value + is safely returned instead. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + **/ + T& at(const int offset, const T& out_value) { + return (offset<0 || offset>=(int)size())?(cimg::temporary(out_value)=out_value):(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions \const. + T at(const int offset, const T& out_value) const { + return (offset<0 || offset>=(int)size())?out_value:(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to the nearest pixel location in the image instance in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \note + - Similar to at(int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified offset, i.e. + - If \c offset<0, then \c img[0] is returned. + - If \c offset>=img.size(), then \c img[img.size() - 1] is returned. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int). + **/ + T& at(const int offset) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + T& _at(const int offset) { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions \const. + const T& at(const int offset) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + const T& _at(const int offset) const { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to a specified default value in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c x,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to operator()(), except that an out-of-bounds access along the X-axis returns the specified value + \c out_value. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || x>=width())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate \const. + T atX(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || x>=width())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to at(int,int,int,int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified X-coordinate. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _at(int,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + T& _atX(const int x, const int y=0, const int z=0, const int c=0) { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate \const. + const T& atX(const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + const T& _atX(const int x, const int y=0, const int z=0, const int c=0) const { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X and Y-coordinates. + **/ + T& atXY(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y coordinates \const. + T atXY(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || x>=width() || y>=height())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXY(int,int,int,int). + **/ + T& atXY(const int x, const int y, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + T& _atXY(const int x, const int y, const int z=0, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates \const. + const T& atXY(const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + const T& _atXY(const int x, const int y, const int z=0, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on + X,Y and Z-coordinates. + **/ + T& atXYZ(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates \const. + T atXYZ(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZ(int,int,int,int). + **/ + T& atXYZ(const int x, const int y, const int z, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + T& _atXYZ(const int x, const int y, const int z, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates \const. + const T& atXYZ(const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + const T& _atXYZ(const int x, const int y, const int z, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed on all + X,Y,Z and C-coordinates. + **/ + T& atXYZC(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions \const. + T atXYZC(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_value: + (*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed on all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZC(int,int,int,int). + **/ + T& atXYZC(const int x, const int y, const int z, const int c) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + T& _atXYZC(const int x, const int y, const int z, const int c) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Access to a pixel value, using Neumann boundary conditions \const. + const T& atXYZC(const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + const T& _atXYZC(const int x, const int y, const int z, const int c) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to atX(int,int,int,int,const T), except that the returned pixel value is approximated by + a linear interpolation along the X-axis, if corresponding coordinates are not integers. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + const Tfloat + Ic = (Tfloat)atX(x,y,z,c,out_value), In = (Tfloat)atXY(nx,y,z,c,out_value); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access along + the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that an out-of-bounds access returns + the value of the nearest pixel in the image instance, regarding the specified X-coordinate. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atX(): Empty instance.", + cimg_instance); + + return _linear_atX(fx,y,z,c); + } + + Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1); + const unsigned int + x = (unsigned int)nfx; + const float + dx = nfx - x; + const unsigned int + nx = dx>0?x + 1:x; + const Tfloat + Ic = (Tfloat)(*this)(x,y,z,c), In = (Tfloat)(*this)(nx,y,z,c); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X and Y-coordinates. + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + const Tfloat + Icc = (Tfloat)atXY(x,y,z,c,out_value), Inc = (Tfloat)atXY(nx,y,z,c,out_value), + Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXY(float,float,int,int). + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXY(): Empty instance.", + cimg_instance); + + return _linear_atXY(fx,fy,z,c); + } + + Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy; + const float + dx = nfx - x, + dy = nfy - y; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y; + const Tfloat + Icc = (Tfloat)(*this)(x,y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X,Y and Z-coordinates. + **/ + Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + const Tfloat + Iccc = (Tfloat)atXYZ(x,y,z,c,out_value), Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), + Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), + Iccn = (Tfloat)atXYZ(x,y,nz,c,out_value), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), + Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZ(float,float,float,int). + **/ + Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZ(): Empty instance.", + cimg_instance); + + return _linear_atXYZ(fx,fy,fz,c); + } + + Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z; + const Tfloat + Iccc = (Tfloat)(*this)(x,y,z,c), Incc = (Tfloat)(*this)(nx,y,z,c), + Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c), + Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c), + Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z,C-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved for all X,Y,Z and C-coordinates. + **/ + Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1, + c = (int)fc - (fc>=0?0:1), nc = c + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z, + dc = fc - c; + const Tfloat + Icccc = (Tfloat)atXYZC(x,y,z,c,out_value), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_value), + Icncc = (Tfloat)atXYZC(x,ny,z,c,out_value), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_value), + Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_value), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_value), + Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_value), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_value), + Icccn = (Tfloat)atXYZC(x,y,z,nc,out_value), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_value), + Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_value), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_value), + Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_value), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_value), + Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_value), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_value); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn -Icccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for all X,Y,Z and C-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved for all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZC(float,float,float,float). + **/ + Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZC(): Empty instance.", + cimg_instance); + + return _linear_atXYZC(fx,fy,fz,fc); + } + + Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1), + nfc = cimg::cut(fc,0,spectrum() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz, + c = (unsigned int)nfc; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z, + dc = nfc - c; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z, + nc = dc>0?c + 1:c; + const Tfloat + Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c), + Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c), + Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c), + Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c), + Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc), + Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc), + Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc), + Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn - Icccc); + } + + //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + The cubic interpolation uses Hermite splines. + \param fx d X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a \e cubic interpolation along the X-axis. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2; + const float + dx = fx - x; + const Tfloat + Ip = (Tfloat)atX(px,y,z,c,out_value), Ic = (Tfloat)atX(x,y,z,c,out_value), + In = (Tfloat)atX(nx,y,z,c,out_value), Ia = (Tfloat)atX(ax,y,z,c,out_value); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Similar to cubic_atX(float,int,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atX(fx,y,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access + along the X-axis. The cubic interpolation uses Hermite splines. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to cubic_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a cubic interpolation along the X-axis. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atX(): Empty instance.", + cimg_instance); + return _cubic_atX(fx,y,z,c); + } + + Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::type::is_nan(fx)?0:cimg::cut(fx,0,width() - 1); + const int + x = (int)nfx; + const float + dx = nfx - x; + const int + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2; + const Tfloat + Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c), + In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atX(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 + are achieved both for X and Y-coordinates. + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2; + const float dx = fx - x, dy = fy - y; + const Tfloat + Ipp = (Tfloat)atXY(px,py,z,c,out_value), Icp = (Tfloat)atXY(x,py,z,c,out_value), + Inp = (Tfloat)atXY(nx,py,z,c,out_value), Iap = (Tfloat)atXY(ax,py,z,c,out_value), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)atXY(px,y,z,c,out_value), Icc = (Tfloat)atXY(x, y,z,c,out_value), + Inc = (Tfloat)atXY(nx,y,z,c,out_value), Iac = (Tfloat)atXY(ax,y,z,c,out_value), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)atXY(px,ny,z,c,out_value), Icn = (Tfloat)atXY(x,ny,z,c,out_value), + Inn = (Tfloat)atXY(nx,ny,z,c,out_value), Ian = (Tfloat)atXY(ax,ny,z,c,out_value), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)atXY(px,ay,z,c,out_value), Ica = (Tfloat)atXY(x,ay,z,c,out_value), + Ina = (Tfloat)atXY(nx,ay,z,c,out_value), Iaa = (Tfloat)atXY(ax,ay,z,c,out_value), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXY(fx,fy,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved for both X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXY(float,float,int,int). + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXY(): Empty instance.", + cimg_instance); + return _cubic_atXY(fx,fy,z,c); + } + + Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::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: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), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)(*this)(px,y,z,c), Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Iac = (Tfloat)(*this)(ax,y,z,c), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c), + Ian = (Tfloat)(*this)(ax,ny,z,c), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c), + Iaa = (Tfloat)(*this)(ax,ay,z,c), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int) const, except that 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 { + 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 { + return cimg::type::cut(_cubic_atXY(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 + are achieved both for X,Y and Z-coordinates. + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2, + z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2; + const float dx = fx - x, dy = fy - y, dz = fz - z; + const Tfloat + Ippp = (Tfloat)atXYZ(px,py,pz,c,out_value), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_value), + Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_value), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_value), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_value), Iccp = (Tfloat)atXYZ(x, y,pz,c,out_value), + Incp = (Tfloat)atXYZ(nx,y,pz,c,out_value), Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_value), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_value), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_value), + Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_value), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_value), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_value), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_value), + Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_value), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_value), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)atXYZ(px,py,z,c,out_value), Icpc = (Tfloat)atXYZ(x,py,z,c,out_value), + Inpc = (Tfloat)atXYZ(nx,py,z,c,out_value), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_value), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)atXYZ(px,y,z,c,out_value), Iccc = (Tfloat)atXYZ(x, y,z,c,out_value), + Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), Iacc = (Tfloat)atXYZ(ax,y,z,c,out_value), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_value), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), + Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_value), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)atXYZ(px,ay,z,c,out_value), Icac = (Tfloat)atXYZ(x,ay,z,c,out_value), + Inac = (Tfloat)atXYZ(nx,ay,z,c,out_value), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_value), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)atXYZ(px,py,nz,c,out_value), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_value), + Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_value), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_value), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_value), Iccn = (Tfloat)atXYZ(x, y,nz,c,out_value), + Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_value), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_value), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), + Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_value), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_value), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_value), + Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_value), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_value), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)atXYZ(px,py,az,c,out_value), Icpa = (Tfloat)atXYZ(x,py,az,c,out_value), + Inpa = (Tfloat)atXYZ(nx,py,az,c,out_value), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_value), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)atXYZ(px,y,az,c,out_value), Icca = (Tfloat)atXYZ(x, y,az,c,out_value), + Inca = (Tfloat)atXYZ(nx,y,az,c,out_value), Iaca = (Tfloat)atXYZ(ax,y,az,c,out_value), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)atXYZ(px,ny,az,c,out_value), Icna = (Tfloat)atXYZ(x,ny,az,c,out_value), + Inna = (Tfloat)atXYZ(nx,ny,az,c,out_value), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_value), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_value), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_value), + Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_value), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_value), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the XYZ-coordinates. + /** + Similar to cubic_atXYZ(float,float,float,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXYZ(float,float,float,int). + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXYZ(): Empty instance.", + cimg_instance); + return _cubic_atXYZ(fx,fy,fz,c); + } + + Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + const float + nfx = cimg::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 + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2, + py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2, + pz = z - 1<0?0:z - 1, nz = dz>0?z + 1:z, az = z + 2>=depth()?depth() - 1:z + 2; + const Tfloat + Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c), + Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)(*this)(px,y,pz,c), Iccp = (Tfloat)(*this)(x, y,pz,c), + Incp = (Tfloat)(*this)(nx,y,pz,c), Iacp = (Tfloat)(*this)(ax,y,pz,c), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c), + Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c), + Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c), + Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)(*this)(px,y,z,c), Iccc = (Tfloat)(*this)(x, y,z,c), + Incc = (Tfloat)(*this)(nx,y,z,c), Iacc = (Tfloat)(*this)(ax,y,z,c), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c), + Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c), + Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c), + Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)(*this)(px,y,nz,c), Iccn = (Tfloat)(*this)(x, y,nz,c), + Incn = (Tfloat)(*this)(nx,y,nz,c), Iacn = (Tfloat)(*this)(ax,y,nz,c), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c), + Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c), + Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c), + Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)(*this)(px,y,az,c), Icca = (Tfloat)(*this)(x, y,az,c), + Inca = (Tfloat)(*this)(nx,y,az,c), Iaca = (Tfloat)(*this)(ax,y,az,c), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c), + Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c), + Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the XYZ-coordinates. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atXYZ(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 + the value is spread amongst several neighbors if the pixel coordinates are float-valued. + \param value Pixel value to set. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param is_added Tells if the pixel value is added to (\c true), or simply replace (\c false) the current image + pixel(s). + \return A reference to the current image instance. + \note + - Calling this method with out-of-bounds coordinates does nothing. + **/ + CImg& set_linear_atX(const T& value, const float fx, const int y=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + if (y>=0 && y=0 && z=0 && c=0 && x=0 && nx& set_linear_atXY(const T& value, const float fx, const float fy=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + if (z>=0 && z=0 && c=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx& set_linear_atXYZ(const T& value, const float fx, const float fy=0, const float fz=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + if (c>=0 && c=0 && z=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx=0 && nz=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx image whose buffer data() is a \c char* string describing the list of all pixel values + of the image instance (written in base 10), separated by specified \c separator character. + \param separator A \c char character which specifies the separator between values in the returned C-string. + \param max_size Maximum size of the returned image (or \c 0 if no limits are set). + \param format For float/double-values, tell the printf format used to generate the Ascii representation + of the numbers (or \c 0 for default representation). + \note + - The returned image is never empty. + - For an empty image instance, the returned string is "". + - If \c max_size is equal to \c 0, there are no limits on the size of the returned string. + - Otherwise, if the maximum number of string characters is exceeded, the value string is cut off + and terminated by character \c '\0'. In that case, the returned image size is max_size + 1. + **/ + CImg value_string(const char separator=',', const unsigned int max_size=0, + const char *const format=0) const { + if (is_empty() || max_size==1) return CImg(1,1,1,1,0); + CImgList items; + CImg s_item(256); *s_item = 0; + const T *ptrs = _data; + unsigned int string_size = 0; + const char *const _format = format?format:cimg::type::format(); + for (ulongT off = 0, siz = size(); off::format(*(ptrs++))); + CImg item(s_item._data,printed_size); + item[printed_size - 1] = separator; + item.move_to(items); + if (max_size) string_size+=printed_size; + } + CImg res; + (items>'x').move_to(res); + if (max_size && res._width>=max_size) res.crop(0,max_size - 1); + res.back() = 0; + return res; + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Test shared state of the pixel buffer. + /** + Return \c true if image instance has a shared memory buffer, and \c false otherwise. + \note + - A shared image do not own his pixel buffer data() and will not deallocate it on destruction. + - Most of the time, a \c CImg image instance will \e not be shared. + - A shared image can only be obtained by a limited set of constructors and methods (see list below). + **/ + bool is_shared() const { + return _is_shared; + } + + //! Test if image instance is empty. + /** + Return \c true, if image instance is empty, i.e. does \e not contain any pixel values, has dimensions + \c 0 x \c 0 x \c 0 x \c 0 and a pixel buffer pointer set to \c 0 (null pointer), and \c false otherwise. + **/ + bool is_empty() const { + return !(_data && _width && _height && _depth && _spectrum); + } + + //! Test if image instance contains a 'inf' value. + /** + Return \c true, if image instance contains a 'inf' value, and \c false otherwise. + **/ + bool is_inf() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_inf((float)*p)) return true; + return false; + } + + //! Test if image instance contains a NaN value. + /** + Return \c true, if image instance contains a NaN value, and \c false otherwise. + **/ + bool is_nan() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_nan((float)*p)) return true; + return false; + } + + //! Test if image width is equal to specified value. + bool is_sameX(const unsigned int size_x) const { + return _width==size_x; + } + + //! Test if image width is equal to specified value. + template + bool is_sameX(const CImg& img) const { + return is_sameX(img._width); + } + + //! Test if image width is equal to specified value. + bool is_sameX(const CImgDisplay& disp) const { + return is_sameX(disp._width); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const unsigned int size_y) const { + return _height==size_y; + } + + //! Test if image height is equal to specified value. + template + bool is_sameY(const CImg& img) const { + return is_sameY(img._height); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const CImgDisplay& disp) const { + return is_sameY(disp._height); + } + + //! Test if image depth is equal to specified value. + bool is_sameZ(const unsigned int size_z) const { + return _depth==size_z; + } + + //! Test if image depth is equal to specified value. + template + bool is_sameZ(const CImg& img) const { + return is_sameZ(img._depth); + } + + //! Test if image spectrum is equal to specified value. + bool is_sameC(const unsigned int size_c) const { + return _spectrum==size_c; + } + + //! Test if image spectrum is equal to specified value. + template + bool is_sameC(const CImg& img) const { + return is_sameC(img._spectrum); + } + + //! Test if image width and height are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameY(unsigned int) const are both verified. + **/ + bool is_sameXY(const unsigned int size_x, const unsigned int size_y) const { + return _width==size_x && _height==size_y; + } + + //! Test if image width and height are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameY(const CImg&) const are both verified. + **/ + template + bool is_sameXY(const CImg& img) const { + return is_sameXY(img._width,img._height); + } + + //! Test if image width and height are the same as that of an existing display window. + /** + Test if is_sameX(const CImgDisplay&) const and is_sameY(const CImgDisplay&) const are both verified. + **/ + bool is_sameXY(const CImgDisplay& disp) const { + return is_sameXY(disp._width,disp._height); + } + + //! Test if image width and depth are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXZ(const unsigned int size_x, const unsigned int size_z) const { + return _width==size_x && _depth==size_z; + } + + //! Test if image width and depth are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXZ(const CImg& img) const { + return is_sameXZ(img._width,img._depth); + } + + //! Test if image width and spectrum are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXC(const unsigned int size_x, const unsigned int size_c) const { + return _width==size_x && _spectrum==size_c; + } + + //! Test if image width and spectrum are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXC(const CImg& img) const { + return is_sameXC(img._width,img._spectrum); + } + + //! Test if image height and depth are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameYZ(const unsigned int size_y, const unsigned int size_z) const { + return _height==size_y && _depth==size_z; + } + + //! Test if image height and depth are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameYZ(const CImg& img) const { + return is_sameYZ(img._height,img._depth); + } + + //! Test if image height and spectrum are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYC(const unsigned int size_y, const unsigned int size_c) const { + return _height==size_y && _spectrum==size_c; + } + + //! Test if image height and spectrum are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYC(const CImg& img) const { + return is_sameYC(img._height,img._spectrum); + } + + //! Test if image depth and spectrum are equal to specified values. + /** + Test if is_sameZ(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameZC(const unsigned int size_z, const unsigned int size_c) const { + return _depth==size_z && _spectrum==size_c; + } + + //! Test if image depth and spectrum are the same as that of another image. + /** + Test if is_sameZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameZC(const CImg& img) const { + return is_sameZC(img._depth,img._spectrum); + } + + //! Test if image width, height and depth are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXYZ(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z) const { + return is_sameXY(size_x,size_y) && _depth==size_z; + } + + //! Test if image width, height and depth are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXYZ(const CImg& img) const { + return is_sameXYZ(img._width,img._height,img._depth); + } + + //! Test if image width, height and spectrum are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXYC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_c) const { + return is_sameXY(size_x,size_y) && _spectrum==size_c; + } + + //! Test if image width, height and spectrum are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYC(const CImg& img) const { + return is_sameXYC(img._width,img._height,img._spectrum); + } + + //! Test if image width, depth and spectrum are equal to specified values. + /** + Test if is_sameXZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXZC(const unsigned int size_x, const unsigned int size_z, const unsigned int size_c) const { + return is_sameXZ(size_x,size_z) && _spectrum==size_c; + } + + //! Test if image width, depth and spectrum are the same as that of another image. + /** + Test if is_sameXZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXZC(const CImg& img) const { + return is_sameXZC(img._width,img._depth,img._spectrum); + } + + //! Test if image height, depth and spectrum are equal to specified values. + /** + Test if is_sameYZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYZC(const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const { + return is_sameYZ(size_y,size_z) && _spectrum==size_c; + } + + //! Test if image height, depth and spectrum are the same as that of another image. + /** + Test if is_sameYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYZC(const CImg& img) const { + return is_sameYZC(img._height,img._depth,img._spectrum); + } + + //! Test if image width, height, depth and spectrum are equal to specified values. + /** + Test if is_sameXYZ(unsigned int,unsigned int,unsigned int) const and is_sameC(unsigned int) const are both + verified. + **/ + bool is_sameXYZC(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c) const { + return is_sameXYZ(size_x,size_y,size_z) && _spectrum==size_c; + } + + //! Test if image width, height, depth and spectrum are the same as that of another image. + /** + Test if is_sameXYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYZC(const CImg& img) const { + return is_sameXYZC(img._width,img._height,img._depth,img._spectrum); + } + + //! Test if specified coordinates are inside image bounds. + /** + Return \c true if pixel located at (\c x,\c y,\c z,\c c) is inside bounds of the image instance, + and \c false otherwise. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Return \c true only if all these conditions are verified: + - The image instance is \e not empty. + - 0<=x<=\ref width() - 1. + - 0<=y<=\ref height() - 1. + - 0<=z<=\ref depth() - 1. + - 0<=c<=\ref spectrum() - 1. + **/ + bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const { + return !is_empty() && x>=0 && x=0 && y=0 && z=0 && c img(100,100,1,3); // Construct a 100x100 RGB color image + const unsigned long offset = 1249; // Offset to the pixel (49,12,0,0) + unsigned int x,y,z,c; + if (img.contains(img[offset],x,y,z,c)) { // Convert offset to (x,y,z,c) coordinates + std::printf("Offset %u refers to pixel located at (%u,%u,%u,%u).\n", + offset,x,y,z,c); + } + \endcode + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z, t& c) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = (ulongT)(ppixel - _data); + const ulongT nc = off/whd; + off%=whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc; + return true; + } + + //! Test if pixel value is inside image bounds and get its X,Y and Z-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X,Y and Z-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((ulongT)(ppixel - _data))%whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; + return true; + } + + //! Test if pixel value is inside image bounds and get its X and Y-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X and Y-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y) const { + const ulongT wh = (ulongT)_width*_height, siz = wh*_depth*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((unsigned int)(ppixel - _data))%wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; + return true; + } + + //! Test if pixel value is inside image bounds and get its X-coordinate. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X-coordinate is set. + **/ + template + bool contains(const T& pixel, t& x) const { + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + size()) return false; + x = (t)(((ulongT)(ppixel - _data))%_width); + return true; + } + + //! Test if pixel value is inside image bounds. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that no pixel coordinates are set. + **/ + bool contains(const T& pixel) const { + const T *const ppixel = &pixel; + return !is_empty() && ppixel>=_data && ppixel<_data + size(); + } + + //! Test if pixel buffers of instance and input images overlap. + /** + Return \c true, if pixel buffers attached to image instance and input image \c img overlap, + and \c false otherwise. + \param img Input image to compare with. + \note + - Buffer overlapping may happen when manipulating \e shared images. + - If two image buffers overlap, operating on one of the image will probably modify the other one. + - Most of the time, \c CImg instances are \e non-shared and do not overlap between each others. + \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 + std::printf("Buffers overlap!\n"); + } + \endcode + **/ + template + bool is_overlapped(const CImg& img) const { + const ulongT csiz = size(), isiz = img.size(); + return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz)); + } + + //! Test if the set {\c *this,\c primitives,\c colors,\c opacities} defines a valid 3D object. + /** + Return \c true is the 3D object represented by the set {\c *this,\c primitives,\c colors,\c opacities} defines a + valid 3D object, and \c false otherwise. The vertex coordinates are defined by the instance image. + \param primitives List of primitives of the 3D object. + \param colors List of colors of the 3D object. + \param opacities List (or image) of opacities of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_checking to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + template + bool is_object3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true, + char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check consistency for the particular case of an empty 3D object. + if (is_empty()) { + if (primitives || colors || opacities) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no vertices but %u primitives, " + "%u colors and %lu opacities", + _width,primitives._width,primitives._width, + colors._width,(unsigned long)opacities.size()); + return false; + } + return true; + } + + // Check consistency of vertices. + if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) has invalid vertex dimensions (%u,%u,%u,%u)", + _width,primitives._width,_width,_height,_depth,_spectrum); + return false; + } + if (colors._width>primitives._width + 1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %u colors", + _width,primitives._width,colors._width); + return false; + } + if (opacities.size()>primitives._width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %lu opacities", + _width,primitives._width,(unsigned long)opacities.size()); + return false; + } + if (!full_check) return true; + + // Check consistency of primitives. + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + const unsigned int psiz = (unsigned int)primitive.size(); + switch (psiz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)primitive(0); + if (i0>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indice %u in " + "point primitive [%u]", + _width,primitives._width,i0,l); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + if (i0>=_width || i1>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + case 2 : 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 " + "segment primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + 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 " + "triangle primitive [%u]", + _width,primitives._width,i0,i1,i2,l); + return false; + } + } break; + 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 " + "quadrangle primitive [%u]", + _width,primitives._width,i0,i1,i2,i3,l); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid primitive [%u] of size %u", + _width,primitives._width,l,(unsigned int)psiz); + return false; + } + } + + // Check consistency of colors. + cimglist_for(colors,c) { + const CImg& color = colors[c]; + if (!color) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no color for primitive [%u]", + _width,primitives._width,c); + return false; + } + } + + // Check consistency of light texture. + if (colors._width>primitives._width) { + const CImg &light = colors.back(); + if (!light || light._depth>1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid light texture (%u,%u,%u,%u)", + _width,primitives._width,light._width, + light._height,light._depth,light._spectrum); + return false; + } + } + + return true; + } + + //! Test if image instance represents a valid serialization of a 3D object. + /** + Return \c true if the image instance represents a valid serialization of a 3D object, and \c false otherwise. + \param full_check Tells if full checking of the instance must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_check to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + bool is_CImg3d(const bool full_check=true, char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check instance dimension and header. + if (_width!=1 || _height<8 || _depth!=1 || _spectrum!=1) { + if (error_message) cimg_sprintf(error_message, + "CImg3d has invalid dimensions (%u,%u,%u,%u)", + _width,_height,_depth,_spectrum); + return false; + } + const T *ptrs = _data, *const ptre = end(); + if (!_is_CImg3d(*(ptrs++),'C') || !_is_CImg3d(*(ptrs++),'I') || !_is_CImg3d(*(ptrs++),'m') || + !_is_CImg3d(*(ptrs++),'g') || !_is_CImg3d(*(ptrs++),'3') || !_is_CImg3d(*(ptrs++),'d')) { + if (error_message) cimg_sprintf(error_message, + "CImg3d header not found"); + return false; + } + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + + // Check consistency of number of vertices / primitives. + if (!full_check) { + const ulongT minimal_size = 8UL + 3*nb_points + 6*nb_primitives; + if (_data + minimal_size>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has only %lu values, while at least %lu values were expected", + nb_points,nb_primitives,(unsigned long)size(),(unsigned long)minimal_size); + return false; + } + } + + // Check consistency of vertex data. + if (!nb_points) { + if (nb_primitives) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no vertices but %u primitives", + nb_points,nb_primitives,nb_primitives); + return false; + } + if (ptrs!=ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) is an empty object but contains %u value%s " + "more than expected", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs),(ptre - ptrs)>1?"s":""); + return false; + } + return true; + } + if (ptrs + 3*nb_points>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines only %u vertices data", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs)/3); + return false; + } + ptrs+=3*nb_points; + + // Check consistency of primitive data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines %u vertices but no primitive", + nb_points,nb_primitives,nb_points); + return false; + } + + if (!full_check) return true; + + for (unsigned int p = 0; p=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive [%u]", + nb_points,nb_primitives,i0,p); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + ptrs+=3; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==6) ptrs+=4; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "segment primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==9) ptrs+=6; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in " + "triangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,p); + return false; + } + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)), + i3 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==12) ptrs+=8; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in " + "quadrangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,i3,p); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines an invalid primitive [%u] of size %u", + nb_points,nb_primitives,p,nb_inds); + return false; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete primitive data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,p,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of color data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no color/texture data", + nb_points,nb_primitives); + return false; + } + for (unsigned int c = 0; c=c) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared sprite/texture indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,c); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete color/texture data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,c,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of opacity data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no opacity data", + nb_points,nb_primitives); + return false; + } + for (unsigned int o = 0; o=o) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared opacity indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,o); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete opacity data for primitive [%u]", + nb_points,nb_primitives,o); + return false; + } + } + + // Check end of data. + if (ptrs1?"s":""); + return false; + } + return true; + } + + static bool _is_CImg3d(const T val, const char c) { + return val>=(T)c && val<(T)(c + 1); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + // Define the math formula parser/compiler and expression evaluator. + struct _cimg_math_parser { + CImg mem; + CImg memtype; + CImgList _code, &code, code_begin, code_end; + 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; + + CImg _img_stats, &img_stats, constcache_vals; + CImgList _list_stats, &list_stats, _list_median, &list_median; + 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; + ulongT 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_vector(arg) (memtype[arg]>1) // Is vector? +#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_op(s) s_op = s; ss_op = ss +#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char) +#define _cimg_mp_check_constant(arg,n_arg,mode) check_constant(arg,n_arg,mode,ss,se,saved_char) +#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char) +#define _cimg_mp_check_list(is_out) check_list(is_out,ss,se,saved_char) +#define _cimg_mp_defunc(mp) (*(mp_func)(*(mp).opcode))(mp) +#define _cimg_mp_return(x) { *se = saved_char; s_op = previous_s_op; ss_op = previous_ss_op; return x; } +#define _cimg_mp_return_nan() _cimg_mp_return(_cimg_mp_slot_nan) +#define _cimg_mp_constant(val) _cimg_mp_return(constant((double)(val))) +#define _cimg_mp_scalar0(op) _cimg_mp_return(scalar0(op)) +#define _cimg_mp_scalar1(op,i1) _cimg_mp_return(scalar1(op,i1)) +#define _cimg_mp_scalar2(op,i1,i2) _cimg_mp_return(scalar2(op,i1,i2)) +#define _cimg_mp_scalar3(op,i1,i2,i3) _cimg_mp_return(scalar3(op,i1,i2,i3)) +#define _cimg_mp_scalar4(op,i1,i2,i3,i4) _cimg_mp_return(scalar4(op,i1,i2,i3,i4)) +#define _cimg_mp_scalar5(op,i1,i2,i3,i4,i5) _cimg_mp_return(scalar5(op,i1,i2,i3,i4,i5)) +#define _cimg_mp_scalar6(op,i1,i2,i3,i4,i5,i6) _cimg_mp_return(scalar6(op,i1,i2,i3,i4,i5,i6)) +#define _cimg_mp_scalar7(op,i1,i2,i3,i4,i5,i6,i7) _cimg_mp_return(scalar7(op,i1,i2,i3,i4,i5,i6,i7)) +#define _cimg_mp_vector1_v(op,i1) _cimg_mp_return(vector1_v(op,i1)) +#define _cimg_mp_vector2_sv(op,i1,i2) _cimg_mp_return(vector2_sv(op,i1,i2)) +#define _cimg_mp_vector2_vs(op,i1,i2) _cimg_mp_return(vector2_vs(op,i1,i2)) +#define _cimg_mp_vector2_vv(op,i1,i2) _cimg_mp_return(vector2_vv(op,i1,i2)) +#define _cimg_mp_vector3_vss(op,i1,i2,i3) _cimg_mp_return(vector3_vss(op,i1,i2,i3)) + + // Constructors / Destructors. + ~_cimg_math_parser() { + cimg::srand(rng); + } + + _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), + rng((cimg::_rand(),cimg::rng())),calling_function(funcname?funcname:"cimg_math_parser") { +#ifdef cimg_use_openmp + 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 && (cimg::is_blank(*_expression) || *_expression==';')) ++_expression; + CImg::string(_expression).move_to(expr); + char *ps = &expr.back() - 1; + 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 (!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_slot_nan 29 +#define _cimg_mp_slot_x 30 +#define _cimg_mp_slot_y 31 +#define _cimg_mp_slot_z 32 +#define _cimg_mp_slot_c 33 + + mem.assign(96); + for (unsigned int i = 0; i<=10; ++i) mem[i] = (double)i; // mem[0-10] = 0...10 + for (unsigned int i = 1; i<=5; ++i) mem[i + 10] = -(double)i; // mem[11-15] = -1...-5 + mem[16] = 0.5; + mem[17] = 0; // thread_id + mem[18] = (double)imgin._width; // w + mem[19] = (double)imgin._height; // h + mem[20] = (double)imgin._depth; // d + mem[21] = (double)imgin._spectrum; // s + mem[22] = (double)imgin._is_shared; // r + mem[23] = (double)imgin._width*imgin._height; // wh + mem[24] = (double)imgin._width*imgin._height*imgin._depth; // whd + mem[25] = (double)imgin._width*imgin._height*imgin._depth*imgin._spectrum; // whds + mem[26] = (double)listin._width; // l + mem[27] = std::exp(1.); // 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 = compile-time constant | N>1 = constant ptr to vector[N-1] }. + memtype.assign(mem._width,1,1,1,0); + for (unsigned int i = 0; i<_cimg_mp_slot_x; ++i) memtype[i] = 1; + memtype[17] = 0; + memtype[_cimg_mp_slot_x] = memtype[_cimg_mp_slot_y] = memtype[_cimg_mp_slot_z] = memtype[_cimg_mp_slot_c] = -2; + mempos = _cimg_mp_slot_c + 1; + variable_pos.assign(8); + + reserved_label.assign(128,1,1,1,~0U); + // reserved_label[4-28] are used to store these two-char variables: + // [0] = wh, [1] = whd, [2] = whds, [3] = pi, [4] = im, [5] = iM, [6] = ia, [7] = iv, + // [8] = is, [9] = ip, [10] = ic, [11] = xm, [12] = ym, [13] = zm, [14] = cm, [15] = xM, + // [16] = yM, [17] = zM, [18]=cM, [19]=i0...[28]=i9, [29] = interpolation, [30] = boundary + + // Compile expression into a serie of opcodes. + s_op = ""; ss_op = expr._data; + const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0,false); + if (!_cimg_mp_is_constant(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(); + } + + // 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(); + level.assign(); + variable_pos.assign(); + reserved_label.assign(); + expr.assign(); + pexpr.assign(); + opcode.assign(); + opcode._is_shared = true; + + // Execute begin() bloc 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_begin.end(); + for (p_code = code_begin; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + p_code_end = code.end(); + } + + _cimg_math_parser(): + code(_code),p_code_end(0),p_break((CImg*)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), + 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)), + rng((cimg::_rand(),cimg::rng())),calling_function(0) { +#ifdef cimg_use_openmp + mem[17] = 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) { + 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'.", + pixel_type(),_cimg_mp_calling_function, + (ss - 4)>expr._data?"...":"", + (ss - 4)>expr._data?ss - 4:expr._data, + se<&expr.back()?"...":""); + } + char c1, c2, c3, c4; + + // Simplify expression when possible. + do { + c2 = 0; + if (ssss && (cimg::is_blank(c1 = *(se - 1)) || c1==';')) --se; + } + while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) { + ++ss; --se; c2 = 1; + } + } while (c2 && ss::%s: %s%s Missing %s, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", + *s_op=='F'?"argument":"item", + (ss_op - 4)>expr._data?"...":"", + (ss_op - 4)>expr._data?ss_op - 4:expr._data, + ss_op + std::strlen(ss_op)<&expr.back()?"...":""); + } + + const char *const previous_s_op = s_op, *const previous_ss_op = ss_op; + const unsigned int depth1 = depth + 1; + unsigned int pos, p1, p2, p3, arg1, arg2, arg3, arg4, arg5, arg6; + char + *const se1 = se - 1, *const se2 = se - 2, *const se3 = se - 3, + *const ss1 = ss + 1, *const ss2 = ss + 2, *const ss3 = ss + 3, *const ss4 = ss + 4, + *const ss5 = ss + 5, *const ss6 = ss + 6, *const ss7 = ss + 7, *const ss8 = ss + 8, + *s, *ps, *ns, *s0, *s1, *s2, *s3, sep = 0, end = 0; + double val = 0, val1, val2; + mp_func op; + + // 'p_ref' is a 'unsigned int[7]' used to return a reference to an image or vector value + // linked to the returned memory slot (reference that cannot be determined at compile time). + // p_ref[0] can be { 0 = scalar (unlinked) | 1 = vector value | 2 = image value (offset) | + // 3 = image value (coordinates) | 4 = image value as a vector (offsets) | + // 5 = image value as a vector (coordinates) }. + // Depending on p_ref[0], the remaining p_ref[k] have the following meaning: + // When p_ref[0]==0, p_ref is actually unlinked. + // When p_ref[0]==1, p_ref = [ 1, vector_ind, offset ]. + // When p_ref[0]==2, p_ref = [ 2, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==3, p_ref = [ 3, image_ind (or ~0U), is_relative, x, y, z, c ]. + // When p_ref[0]==4, p_ref = [ 4, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==5, p_ref = [ 5, image_ind (or ~0U), is_relative, x, y, z ]. + if (p_ref) { *p_ref = 0; p_ref[1] = p_ref[2] = p_ref[3] = p_ref[4] = p_ref[5] = p_ref[6] = ~0U; } + + const char saved_char = *se; *se = 0; + const unsigned int clevel = level[ss - expr._data], clevel1 = clevel + 1; + bool is_sth, is_relative; + CImg ref; + CImg variable_name; + CImgList l_opcode; + + // Look for a single value or a pre-defined variable. + 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=='-'); + 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) 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 (ss1==se) switch (*ss) { // One-char reserved variable + case 'c' : _cimg_mp_return(reserved_label['c']!=~0U?reserved_label['c']:_cimg_mp_slot_c); + case 'd' : _cimg_mp_return(reserved_label['d']!=~0U?reserved_label['d']:20); + case 'e' : _cimg_mp_return(reserved_label['e']!=~0U?reserved_label['e']:27); + case 'h' : _cimg_mp_return(reserved_label['h']!=~0U?reserved_label['h']:19); + case 'l' : _cimg_mp_return(reserved_label['l']!=~0U?reserved_label['l']:26); + case 'r' : _cimg_mp_return(reserved_label['r']!=~0U?reserved_label['r']:22); + case 's' : _cimg_mp_return(reserved_label['s']!=~0U?reserved_label['s']:21); + case 't' : _cimg_mp_return(reserved_label['t']!=~0U?reserved_label['t']:17); + case 'w' : _cimg_mp_return(reserved_label['w']!=~0U?reserved_label['w']:18); + case 'x' : _cimg_mp_return(reserved_label['x']!=~0U?reserved_label['x']:_cimg_mp_slot_x); + case 'y' : _cimg_mp_return(reserved_label['y']!=~0U?reserved_label['y']:_cimg_mp_slot_y); + case 'z' : _cimg_mp_return(reserved_label['z']!=~0U?reserved_label['z']:_cimg_mp_slot_z); + case 'u' : + if (reserved_label['u']!=~0U) _cimg_mp_return(reserved_label['u']); + _cimg_mp_scalar2(mp_u,0,1); + case 'g' : + if (reserved_label['g']!=~0U) _cimg_mp_return(reserved_label['g']); + _cimg_mp_scalar0(mp_g); + case 'i' : + if (reserved_label['i']!=~0U) _cimg_mp_return(reserved_label['i']); + _cimg_mp_scalar0(mp_i); + case 'I' : + _cimg_mp_op("Variable 'I'"); + if (reserved_label['I']!=~0U) _cimg_mp_return(reserved_label['I']); + 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); + _cimg_mp_return(pos); + case 'R' : + if (reserved_label['R']!=~0U) _cimg_mp_return(reserved_label['R']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,0,0); + case 'G' : + if (reserved_label['G']!=~0U) _cimg_mp_return(reserved_label['G']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,0,0); + case 'B' : + if (reserved_label['B']!=~0U) _cimg_mp_return(reserved_label['B']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,0,0); + case 'A' : + if (reserved_label['A']!=~0U) _cimg_mp_return(reserved_label['A']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,0,0); + } + else if (ss2==se) { // Two-chars reserved variable + arg1 = arg2 = ~0U; + if (*ss=='w' && *ss1=='h') // wh + _cimg_mp_return(reserved_label[0]!=~0U?reserved_label[0]:23); + if (*ss=='p' && *ss1=='i') // pi + _cimg_mp_return(reserved_label[3]!=~0U?reserved_label[3]:28); + if (*ss=='i') { + if (*ss1>='0' && *ss1<='9') { // i0...i9 + pos = 19 + *ss1 - '0'; + if (reserved_label[pos]!=~0U) _cimg_mp_return(reserved_label[pos]); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 19,0,0); + } + switch (*ss1) { + case 'm' : arg1 = 4; arg2 = 0; break; // im + case 'M' : arg1 = 5; arg2 = 1; break; // iM + case 'a' : arg1 = 6; arg2 = 2; break; // ia + case 'v' : arg1 = 7; arg2 = 3; break; // iv + case 's' : arg1 = 8; arg2 = 12; break; // is + case 'p' : arg1 = 9; arg2 = 13; break; // 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; + _cimg_mp_return(mem_img_median); + break; + } + } + else if (*ss1=='m') switch (*ss) { + case 'x' : arg1 = 11; arg2 = 4; break; // xm + case 'y' : arg1 = 12; arg2 = 5; break; // ym + case 'z' : arg1 = 13; arg2 = 6; break; // zm + case 'c' : arg1 = 14; arg2 = 7; break; // cm + } + else if (*ss1=='M') switch (*ss) { + case 'x' : arg1 = 15; arg2 = 8; break; // xM + case 'y' : arg1 = 16; arg2 = 9; break; // yM + case 'z' : arg1 = 17; arg2 = 10; break; // zM + case 'c' : arg1 = 18; arg2 = 11; break; // cM + } + if (arg1!=~0U) { + if (reserved_label[arg1]!=~0U) _cimg_mp_return(reserved_label[arg1]); + if (!img_stats) { + img_stats.assign(1,14,1,1,0).fill(imgin.get_stats(),false); + mem_img_stats.assign(1,14,1,1,~0U); + } + if (mem_img_stats[arg2]==~0U) mem_img_stats[arg2] = constant(img_stats[arg2]); + _cimg_mp_return(mem_img_stats[arg2]); + } + } else if (ss3==se) { // Three-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d') // whd + _cimg_mp_return(reserved_label[1]!=~0U?reserved_label[1]:24); + } else if (ss4==se) { // Four-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s') // whds + _cimg_mp_return(reserved_label[2]!=~0U?reserved_label[2]:25); + } + + pos = ~0U; + is_sth = false; + for (s0 = ss, s = ss1; s='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg2)?4:2; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + if (_cimg_mp_is_vector(arg2)) + set_variable_vector(arg2); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + } + + + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), + arg2,p1,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), + arg2,p1,arg1,_cimg_mp_size(arg2)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg1,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ss1=='(' && *ve1==')') { // i/j/I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) is_parallelizable = false; + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + } else if (s1='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg5)?5:3; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + p_ref[4] = arg2; + p_ref[5] = arg3; + p_ref[6] = arg4; + if (_cimg_mp_is_vector(arg5)) + set_variable_vector(arg5); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg5)) memtype[arg5] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2; + if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -2; + } + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg5,p1,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), + arg5,p1,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), + arg5,p1,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg5,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg5,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg5,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } + _cimg_mp_return(arg5); + } + } + + // Assign vector value (direct). + if (l_variable_name>3 && *ve1==']' && *ss!='[') { + s0 = ve1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + is_sth = true; // is_valid_variable_name? + if (*ss>='0' && *ss<='9') is_sth = false; + else for (ns = ss; nsss) { + 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 + _cimg_mp_check_type(arg3,2,1,0); + + if (variable_name[1]) { // Multi-char variable + cimglist_for(variable_def,i) if (!std::strcmp(variable_name,variable_def[i])) { + arg1 = variable_pos[i]; break; + } + } else arg1 = reserved_label[*variable_name]; // Single-char variable + if (arg1==~0U) compile(ss,s0 - 1,depth1,0,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); + } + } + } + + // 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 + 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 && cimg::is_blank(*s)) ++s; + CImg(s,(unsigned int)(se - s + 1)).move_to(macro_body,0); + + p1 = 1; // Indice of current parsed argument + for (s = s0 + 1; s<=s1; ++p1, s = ns + 1) { // Parse function arguments + if (p1>24) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Too much specified arguments (>24) in macro " + "definition '%s()', in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + while (*s && 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; nsexpr._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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + is_sth?"Empty":"Invalid",p1, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (ns==s1 || *ns==',') { // New argument found + *s3 = 0; + p2 = (unsigned int)(s3 - s2); // Argument length + for (ps = std::strstr(macro_body[0],s2); ps; ps = std::strstr(ps,s2)) { // Replace by arg number + if (!((ps>macro_body[0]._data && is_varchar(*(ps - 1))) || + (ps + 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 (cimg::is_blank(*s0)) ++s0; + variable_name.resize(variable_name.end() - s0,1,1,1,0,0,1); + } + + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + // Assign variable (direct). + if (is_sth) { + arg3 = variable_name[1]?~0U:*variable_name; // One-char variable + if (variable_name[1] && !variable_name[2]) { // Two-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + if (c1=='w' && c2=='h') arg3 = 0; // wh + else if (c1=='p' && c2=='i') arg3 = 3; // pi + else if (c1=='i') { + if (c2>='0' && c2<='9') arg3 = 19 + c2 - '0'; // i0...i9 + else if (c2=='m') arg3 = 4; // im + else if (c2=='M') arg3 = 5; // iM + else if (c2=='a') arg3 = 6; // ia + else if (c2=='v') arg3 = 7; // iv + else if (c2=='s') arg3 = 8; // is + else if (c2=='p') arg3 = 9; // ip + else if (c2=='c') arg3 = 10; // ic + } else if (c2=='m') { + if (c1=='x') arg3 = 11; // xm + else if (c1=='y') arg3 = 12; // ym + else if (c1=='z') arg3 = 13; // zm + else if (c1=='c') arg3 = 14; // cm + } else if (c2=='M') { + if (c1=='x') arg3 = 15; // xM + else if (c1=='y') arg3 = 16; // yM + else if (c1=='z') arg3 = 17; // zM + else if (c1=='c') arg3 = 18; // cM + } + } else if (variable_name[1] && variable_name[2] && !variable_name[3]) { // Three-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + if (c1=='w' && c2=='h' && c3=='d') arg3 = 1; // whd + } else if (variable_name[1] && variable_name[2] && variable_name[3] && + !variable_name[4]) { // Four-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + c4 = variable_name[3]; + if (c1=='w' && c2=='h' && c3=='d' && c4=='s') arg3 = 2; // whds + } else if (!std::strcmp(variable_name,"interpolation")) arg3 = 29; // interpolation + else if (!std::strcmp(variable_name,"boundary")) arg3 = 30; // boundary + + arg1 = ~0U; + arg2 = compile(s + 1,se,depth1,0,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; } + + if (arg1==~0U) { // Create new variable + if (_cimg_mp_is_vector(arg2)) { // Vector variable + arg1 = is_comp_vector(arg2)?arg2:vector_copy(arg2); + set_variable_vector(arg1); + } else { // Scalar variable + if (is_const) arg1 = arg2; + else { + arg1 = _cimg_mp_is_comp(arg2)?arg2:scalar1(mp_copy,arg2); + memtype[arg1] = -1; + } + } + + if (arg3!=~0U) reserved_label[arg3] = arg1; + else { + if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); + variable_pos[variable_def._width] = arg1; + variable_name.move_to(variable_def); + } + + } else { // Variable already exists -> assign a new value + if (is_const || _cimg_mp_is_constant(arg1)) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid assignment of %sconst variable '%s'%s, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"already-defined ":"non-", + variable_name._data, + !_cimg_mp_is_constant(arg1) && is_const?" as a new const variable":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + _cimg_mp_check_type(arg2,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_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)). + move_to(code); + else // From scalar + CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg2). + move_to(code); + } else // Scalar + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + } + _cimg_mp_return(arg1); + } + + // Assign lvalue (variable name was not valid for a direct assignment). + arg1 = ~0U; + is_sth = (bool)std::strchr(variable_name,'?'); // Contains_ternary_operator? + if (is_sth) break; // Do nothing and make ternary operator prioritary over assignment + + if (l_variable_name>2 && (std::strchr(variable_name,'(') || std::strchr(variable_name,'['))) { + ref.assign(7); + arg1 = compile(ss,s,depth1,ref,is_single); // Lvalue slot + arg2 = compile(s + 1,se,depth1,0,is_single); // 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). + move_to(code); + _cimg_mp_return(arg2); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg3).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg2,p1,arg3,arg4,arg5,arg6).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg2,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg3,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg2,arg3,arg4,arg5).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg2,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V = value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) // From vector + CImg::vector((ulongT)mp_vector_copy,arg1,arg2,(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). + move_to(code); + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s = scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + } + + // No assignment expressions match -> error + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + arg1!=~0U && _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Apply unary/binary/ternary operators. The operator precedences should be the same as in C++. + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='*' || *ps=='/' || *ps=='^') && *ns==*ps && + level[s - expr._data]==clevel) { // Self-operators for complex numbers only (**=,//=,^^=) + _cimg_mp_op(*ps=='*'?"Operator '**='":*ps=='/'?"Operator '//='":"Operator '^^='"); + + ref.assign(7); + arg1 = compile(ss,ns,depth1,ref,is_single); // Vector slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Right operand + _cimg_mp_check_type(arg1,1,2,2); + _cimg_mp_check_type(arg2,2,3,2); + if (_cimg_mp_is_vector(arg2)) { // Complex **= complex + if (*ps=='*') + CImg::vector((ulongT)mp_complex_mul,arg1,arg1,arg2).move_to(code); + else if (*ps=='/') + CImg::vector((ulongT)mp_complex_div_vv,arg1,arg1,arg2).move_to(code); + else + CImg::vector((ulongT)mp_complex_pow_vv,arg1,arg1,arg2).move_to(code); + } else { // Complex **= scalar + if (*ps=='*') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_mul,arg2); + } else if (*ps=='/') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_div,arg2); + } else { + if (arg2==1) _cimg_mp_return(arg1); + CImg::vector((ulongT)mp_complex_pow_vs,arg1,arg1,arg2).move_to(code); + } + } + + // Write computed value back in image if necessary. + if (*ref==4) { // Image value (vector): I/J[_#ind,off] **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + + } else if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + } + + _cimg_mp_return(arg1); + } + + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || *ps=='%' || + *ps=='&' || *ps=='^' || *ps=='|' || + (*ps=='>' && *ns=='>') || (*ps=='<' && *ns=='<')) && + level[s - expr._data]==clevel) { // Self-operators (+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=) + switch (*ps) { + case '+' : op = mp_self_add; _cimg_mp_op("Operator '+='"); break; + case '-' : op = mp_self_sub; _cimg_mp_op("Operator '-='"); break; + case '*' : op = mp_self_mul; _cimg_mp_op("Operator '*='"); break; + case '/' : op = mp_self_div; _cimg_mp_op("Operator '/='"); break; + case '%' : op = mp_self_modulo; _cimg_mp_op("Operator '%='"); break; + case '<' : op = mp_self_bitwise_left_shift; _cimg_mp_op("Operator '<<='"); break; + case '>' : op = mp_self_bitwise_right_shift; _cimg_mp_op("Operator '>>='"); break; + case '&' : op = mp_self_bitwise_and; _cimg_mp_op("Operator '&='"); break; + case '|' : op = mp_self_bitwise_or; _cimg_mp_op("Operator '|='"); break; + default : op = mp_self_pow; _cimg_mp_op("Operator '^='"); break; + } + s1 = *ps=='>' || *ps=='<'?ns:ps; + + ref.assign(7); + arg1 = compile(ss,s1,depth1,ref,is_single); // Variable slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Value to apply + + // Check for particular case to be simplified. + if ((op==mp_self_add || op==mp_self_sub) && !arg2) _cimg_mp_return(arg1); + if ((op==mp_self_mul || op==mp_self_div) && arg2==1) _cimg_mp_return(arg1); + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k] += scalar + _cimg_mp_check_type(arg2,2,1,0); + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(arg1); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + _cimg_mp_return(arg1); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V += value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) self_vector_v(arg1,op,arg2); // Vector += vector + else self_vector_s(arg1,op,arg2); // Vector += scalar + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s += scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + + variable_name.assign(ss,(unsigned int)(s - ss)).back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + for (s = ss1; s::vector((ulongT)mp_if,pos,arg1,arg2,arg3, + p3 - p2,code._width - p3,arg4).move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='|' && *ns=='|' && level[s - expr._data]==clevel) { // Logical or ('||') + _cimg_mp_op("Operator '||'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] || mem[arg2]); + if (!arg1) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_or,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='&' && *ns=='&' && level[s - expr._data]==clevel) { // Logical and ('&&') + _cimg_mp_op("Operator '&&'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] && mem[arg2]); + if (arg1>0 && arg1<=16) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_and,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se2; s>ss; --s) + if (*s=='|' && level[s - expr._data]==clevel) { // Bitwise or ('|') + _cimg_mp_op("Operator '|'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_vector2_sv(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1] | (longT)mem[arg2]); + if (!arg2) _cimg_mp_return(arg1); + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_scalar2(mp_bitwise_or,arg1,arg2); + } + + for (s = se2; s>ss; --s) + if (*s=='&' && level[s - expr._data]==clevel) { // Bitwise and ('&') + _cimg_mp_op("Operator '&'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_bitwise_and,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='!' && *ns=='=' && level[s - expr._data]==clevel) { // Not equal to ('!=') + _cimg_mp_op("Operator '!='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]!=mem[arg2]); + _cimg_mp_scalar2(mp_neq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='=' && *ns=='=' && level[s - expr._data]==clevel) { // Equal to ('==') + _cimg_mp_op("Operator '=='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]==mem[arg2]); + _cimg_mp_scalar2(mp_eq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='=' && level[s - expr._data]==clevel) { // Less or equal than ('<=') + _cimg_mp_op("Operator '<='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_lte,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='=' && level[s - expr._data]==clevel) { // Greater or equal than ('>=') + _cimg_mp_op("Operator '>='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_gte,arg1,arg2); + } + + for (s = se2, ns = se1, ps = se3; s>ss; --s, --ns, --ps) + if (*s=='<' && *ns!='<' && *ps!='<' && level[s - expr._data]==clevel) { // Less than ('<') + _cimg_mp_op("Operator '<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 ('>') + _cimg_mp_op("Operator '>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_gt,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='<' && level[s - expr._data]==clevel) { // Left bit shift ('<<') + _cimg_mp_op("Operator '<<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_left_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_left_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]<<(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_left_shift,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='>' && level[s - expr._data]==clevel) { // Right bit shift ('>>') + _cimg_mp_op("Operator '>>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_right_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_right_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]>>(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_right_shift,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='+' && (*ns!='+' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Addition ('+') + _cimg_mp_op("Operator '+'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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' + 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]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_linear_add,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_increment,arg1); + if (arg1==1) _cimg_mp_scalar1(mp_increment,arg2); + _cimg_mp_scalar2(mp_add,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='-' && (*ns!='-' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Subtraction ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_sub,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_vector1_v(mp_minus,arg2); + _cimg_mp_vector2_sv(mp_sub,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] - mem[arg2]); + if (!arg1) _cimg_mp_scalar1(mp_minus,arg2); + if (code) { // Try to spot linear cases 'a*b - c' and 'c - a*b' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)(arg3==arg1?mp_linear_sub_left:mp_linear_sub_right), + arg3,arg4,arg5,arg3==arg1?arg2:arg1).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_decrement,arg1); + _cimg_mp_scalar2(mp_sub,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='*' && *ns=='*' && level[s - expr._data]==clevel) { // Complex multiplication ('**') + _cimg_mp_op("Operator '**'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + pos = vector(2); + CImg::vector((ulongT)mp_complex_mul,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='/' && *ns=='/' && level[s - expr._data]==clevel) { // Complex division ('//') + _cimg_mp_op("Operator '//'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + _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); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]); + if (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='*' && level[s - expr._data]==clevel) { // Multiplication ('*') + _cimg_mp_op("Operator '*'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + p2 = _cimg_mp_size(arg2); + if (p2>0 && _cimg_mp_size(arg1)==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); + _cimg_mp_return(pos); + } + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_mul,arg1,arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + + if (code) { // Try to spot double multiplication 'a*b*c' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_mul2,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='/' && level[s - expr._data]==clevel) { // Division ('/') + _cimg_mp_op("Operator '/'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2, ns = se1; s>ss; --s, --ns) + if (*s=='%' && *ns!='^' && level[s - expr._data]==clevel) { // Modulo ('%') + _cimg_mp_op("Operator '%'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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])); + _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)); + } + + if (*ss=='-' && (*ss1!='-' || (ss2='0' && *ss2<='9'))) { // Unary minus ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_minus,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(-mem[arg1]); + _cimg_mp_scalar1(mp_minus,arg1); + } + + if (*ss=='!') { // Logical not ('!') + _cimg_mp_op("Operator '!'"); + if (*ss1=='!') { // '!!expr' optimized as 'bool(expr)' + arg1 = compile(ss2,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_logical_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(!mem[arg1]); + _cimg_mp_scalar1(mp_logical_not,arg1); + } + + if (*ss=='~') { // Bitwise not ('~') + _cimg_mp_op("Operator '~'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bitwise_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(~(unsigned int)mem[arg1]); + _cimg_mp_scalar1(mp_bitwise_not,arg1); + } + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='^' && *ns=='^' && level[s - expr._data]==clevel) { // Complex power ('^^') + _cimg_mp_op("Operator '^^'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + pos = vector(2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vv,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vs,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::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); + _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); + _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])); + switch (arg2) { + case 0 : _cimg_mp_return(1); + case 2 : _cimg_mp_scalar1(mp_sqr,arg1); + case 3 : _cimg_mp_scalar1(mp_pow3,arg1); + case 4 : _cimg_mp_scalar1(mp_pow4,arg1); + default : + if (_cimg_mp_is_constant(arg2)) { + if (mem[arg2]==0.5) { _cimg_mp_scalar1(mp_sqrt,arg1); } + else if (mem[arg2]==0.25) { _cimg_mp_scalar1(mp_pow0_25,arg1); } + } + _cimg_mp_scalar2(mp_pow,arg1,arg2); + } + } + + // Percentage computation. + if (*se1=='%') { + arg1 = compile(ss,se1,depth1,0,is_single); + arg2 = _cimg_mp_is_constant(arg1)?0:constant(100); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(mp_div,arg1,arg2); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(mem[arg1]/100); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + is_sth = ss1ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { // Pre/post-decrement and increment + if ((is_sth && *ss=='+') || (!is_sth && *se1=='+')) { + _cimg_mp_op("Operator '++'"); + op = mp_self_increment; + } else { + _cimg_mp_op("Operator '--'"); + op = mp_self_decrement; + } + ref.assign(7); + arg1 = is_sth?compile(ss2,se,depth1,ref,is_single): + compile(ss,se2,depth1,ref,is_single); // Variable slot + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (is_sth) pos = arg1; // Determine return indice, depending on pre/post action + else { + if (_cimg_mp_is_vector(arg1)) pos = vector_copy(arg1); + else pos = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k]++ + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,1).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(pos); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off]++ + if (!is_single) 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); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off]++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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(pos); + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg1,arg3,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V++ + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s++ + CImg::vector((ulongT)op,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (is_sth) variable_name.assign(ss2,(unsigned int)(se - ss1)); + else variable_name.assign(ss,(unsigned int)(se1 - ss)); + variable_name.back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'. + if (*se1==']' && *ss!='[') { + _cimg_mp_op("Value accessor '[]'"); + is_relative = *ss=='j' || *ss=='J'; + s0 = s1 = std::strchr(ss,'['); if (s0) { do { --s1; } while (cimg::is_blank(*s1)); cimg::swap(*s0,*++s1); } + + if ((*ss=='I' || *ss=='J') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a vector + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff), + pos,p1,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Joff:mp_Ioff), + pos,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + if ((*ss=='i' || *ss=='j') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + if (s0>ss) { // Vector value + arg1 = compile(ss,s0,depth1,0,is_single); + if (_cimg_mp_is_scalar(arg1)) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Array brackets used on non-vector variable '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + + } + s1 = s0 + 1; while (s1 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); + arg3 = (unsigned int)mem[arg3]; + pos = vector(arg3); + CImg::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + // One argument -> vector value reference + arg2 = compile(++s0,se1,depth1,0,is_single); + if (_cimg_mp_is_constant(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::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'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data,nb, + variable_name._data,_cimg_mp_size(arg1), + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (p_ref) { + *p_ref = 1; + p_ref[1] = arg1; + p_ref[2] = arg2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; // Prevent from being used in further optimization + } + pos = scalar3(mp_vector_off,arg1,_cimg_mp_size(arg1),arg2); + memtype[pos] = -2; // Prevent from being used in further optimization + _cimg_mp_return(pos); + } + } + + // Look for a function call, an access to image value, or a parenthesis. + if (*se1==')') { + if (*ss=='(') _cimg_mp_return(compile(ss1,se1,depth1,p_ref,is_single)); // Simple parentheses + _cimg_mp_op("Value accessor '()'"); + is_relative = *ss=='j' || *ss=='J'; + 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 (s01) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + if (s1::vector((ulongT)(is_relative?mp_list_Jxyz:mp_list_Ixyz), + pos,p1,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Jxyz:mp_Ixyz), + pos,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + // i/j(_#ind,_x,_y,_z,_c,_interpolation,_boundary_conditions) + if ((*ss=='i' || *ss=='j') && *ss1=='(') { // Image value as scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + if (s1::vector((ulongT)mp_arg,0,0,p2,arg1,arg2).move_to(l_opcode); + for (s = ++s2; s::vector(arg3).move_to(l_opcode); + ++p3; + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (_cimg_mp_is_constant(arg1)) { + p3-=1; // Number of args + arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]); + if (arg1::vector((ulongT)mp_break,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"breakpoint(",11)) { // Break point (for abort test) + _cimg_mp_op("Function 'breakpoint()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_breakpoint,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"bool(",5)) { // Boolean cast + _cimg_mp_op("Function 'bool()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + + if (!std::strncmp(ss,"begin(",6)) { // Begin + _cimg_mp_op("Function 'begin()'"); + code.swap(code_begin); + arg1 = compile(ss6,se1,depth1,p_ref,true); + code.swap(code_begin); + _cimg_mp_return(arg1); + } + break; + + case 'c' : + if (!std::strncmp(ss,"cabs(",5)) { // Complex absolute value + _cimg_mp_op("Function 'cabs()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_complex_abs,arg1 + 1,arg1 + 2); + } + + if (!std::strncmp(ss,"carg(",5)) { // Complex argument + _cimg_mp_op("Function 'carg()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_atan2,arg1 + 2,arg1 + 1); + } + + if (!std::strncmp(ss,"cats(",5)) { // Concatenate strings + _cimg_mp_op("Function 'cats()'"); + CImg::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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cbrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::cbrt(mem[arg1])); + _cimg_mp_scalar1(mp_cbrt,arg1); + } + + if (!std::strncmp(ss,"cconj(",6)) { // Complex conjugate + _cimg_mp_op("Function 'cconj()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_conj,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ceil(",5)) { // Ceil + _cimg_mp_op("Function 'ceil()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + pos = vector(2); + CImg::vector((ulongT)mp_complex_exp,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"clog(",5)) { // Complex logarithm + _cimg_mp_op("Function 'clog()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_log,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"continue(",9)) { // Complex absolute value + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_continue,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"copy(",5)) { // Memory copy + _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 = constant(_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); + _cimg_mp_check_type(arg6,5,1,0); + 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); + } + + if (!std::strncmp(ss,"cos(",4)) { // Cosine + _cimg_mp_op("Function 'cos()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cos,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cos(mem[arg1])); + _cimg_mp_scalar1(mp_cos,arg1); + } + + if (!std::strncmp(ss,"cosh(",5)) { // Hyperbolic cosine + _cimg_mp_op("Function 'cosh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cosh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cosh(mem[arg1])); + _cimg_mp_scalar1(mp_cosh,arg1); + } + + if (!std::strncmp(ss,"critical(",9)) { // Critical section (single thread at a time) + _cimg_mp_op("Function 'critical()'"); + p1 = code._width; + arg1 = compile(ss + 9,se1,depth1,p_ref,true); + CImg::vector((ulongT)mp_critical,arg1,code._width - p1).move_to(code,p1); + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"crop(",5)) { // Image crop + _cimg_mp_op("Function 'crop()'"); + 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); + } + 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 + 2; + break; + case 3 : + CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode); + arg1 = arg2 + 2; + break; + case 4 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,_cimg_mp_boundary). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + break; + case 5 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,opcode[4]). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + 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 = arg2 + (is_sth?2:4); + break; + case 7 : + CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, + opcode[6]).move_to(opcode); + arg1 = arg2 + (is_sth?2:4); + 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 = arg2 + (is_sth?2:5); + break; + case 9 : + arg1 = arg2 + (is_sth?2:5); + 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); + 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()?"...":""); + } + 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); + _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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"cut(",4)) { // Cut + _cimg_mp_op("Function 'cut()'"); + s1 = ss4; while (s1val2?val2:val); + } + _cimg_mp_scalar3(mp_cut,arg1,arg2,arg3); + } + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_d,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"date(",5)) { // Current date or file date + _cimg_mp_op("Function 'date()'"); + s1 = ss5; while (s1::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_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); + *se1 = 0; + variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + ((CImg::vector((ulongT)mp_debug,arg1,0,code._width - p1), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code,p1); + *se1 = ')'; + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"display(",8)) { // Display memory, vector or image + _cimg_mp_op("Function 'display()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_display_memory,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + if (*ss8!='#') { // Vector + s1 = ss8; while (s1::string(ss8,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + if (_cimg_mp_is_vector(arg1)) + ((CImg::vector((ulongT)mp_vector_print,arg1,0,(ulongT)_cimg_mp_size(arg1),0), + variable_name)>'y').move_to(opcode); + else + ((CImg::vector((ulongT)mp_print,arg1,0,0), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + + ((CImg::vector((ulongT)mp_display,arg1,0,(ulongT)_cimg_mp_size(arg1), + arg2,arg3,arg4,arg5), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + *s1 = c1; + _cimg_mp_return(arg1); + + } else { // Image + p1 = compile(ss8 + 1,se1,depth1,0,is_single); + _cimg_mp_check_list(true); + CImg::vector((ulongT)mp_image_display,_cimg_mp_slot_nan,p1).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"det(",4)) { // Matrix determinant + _cimg_mp_op("Function 'det()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + } + + if (!std::strncmp(ss,"diag(",5)) { // Diagonal matrix + _cimg_mp_op("Function 'diag()'"); + 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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"dot(",4)) { // Dot product + _cimg_mp_op("Function 'dot()'"); + s1 = ss4; while (s1::vector((ulongT)mp_do,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_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; + } else { + if (s0::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; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"eig(",4)) { // Matrix eigenvalues/eigenvector + _cimg_mp_op("Function 'eig()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + _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); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"ellipse(",8)) { // Ellipse/circle drawing + if (!is_single) 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 (!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,"fact(",5)) { // Factorial + _cimg_mp_op("Function 'fact()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + 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])); + _cimg_mp_scalar1(mp_fibonacci,arg1); + } + + if (!std::strncmp(ss,"find(",5)) { // Find + _cimg_mp_op("Function 'find()'"); + + // First argument: data to look at. + s0 = ss5; while (s0::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); + _cimg_mp_return(p3); + } + + if (!std::strncmp(ss,"floor(",6)) { // Floor + _cimg_mp_op("Function 'floor()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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_mp_return(pos); + } + break; + + case 'g' : + if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function + _cimg_mp_op("Function 'gauss()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_h,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'i' : + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_median,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (*ss1=='f' && *ss2=='(') { // If..then[..else.] + _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); + _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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_int,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((longT)mem[arg1]); + _cimg_mp_scalar1(mp_int,arg1); + } + + if (!std::strncmp(ss,"inv(",4)) { // Matrix/scalar inversion + _cimg_mp_op("Function 'inv()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) { + _cimg_mp_check_matrix_square(arg1,1); + p1 = (unsigned int)cimg::round(std::sqrt((float)_cimg_mp_size(arg1))); + pos = vector(p1*p1); + CImg::vector((ulongT)mp_matrix_inv,pos,arg1,p1).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(1/mem[arg1]); + _cimg_mp_scalar2(mp_div,1,arg1); + } + + if (*ss1=='s') { // Family of 'is_?()' functions + + if (!std::strncmp(ss,"isbool(",7)) { // Is boolean? + _cimg_mp_op("Function 'isbool()'"); + if (ss7==se1) _cimg_mp_return(0); + arg1 = compile(ss7,se1,depth1,0,is_single); + 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. || 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); + } + + if (!std::strncmp(ss,"isfile(",7)) { // Is file? + _cimg_mp_op("Function 'isfile()'"); + *se1 = 0; + is_sth = cimg::is_file(ss7); + *se1 = ')'; + _cimg_mp_return(is_sth?1U:0U); + } + + if (!std::strncmp(ss,"isin(",5)) { // Is in sequence/vector? + if (ss5>=se1) _cimg_mp_return(0); + _cimg_mp_op("Function 'isin()'"); + pos = scalar(); + CImg::vector((ulongT)mp_isin,pos,0).move_to(l_opcode); + 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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"isinf(",6)) { // Is infinite? + _cimg_mp_op("Function 'isinf()'"); + if (ss6==se1) _cimg_mp_return(0); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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)); + _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); + 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])); + _cimg_mp_scalar1(mp_isnan,arg1); + } + + if (!std::strncmp(ss,"isval(",6)) { // Is value? + _cimg_mp_op("Function 'isval()'"); + val = 0; + if (cimg_sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1); + _cimg_mp_return(0); + } + + } + break; + + case 'l' : + if (*ss1=='(') { // Size of image list + _cimg_mp_op("Function 'l()'"); + if (ss2!=se1) break; + _cimg_mp_scalar0(mp_list_l); + } + + if (!std::strncmp(ss,"log(",4)) { // Natural logarithm + _cimg_mp_op("Function 'log()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log(mem[arg1])); + _cimg_mp_scalar1(mp_log,arg1); + } + + if (!std::strncmp(ss,"log2(",5)) { // Base-2 logarithm + _cimg_mp_op("Function 'log2()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log2,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::log2(mem[arg1])); + _cimg_mp_scalar1(mp_log2,arg1); + } + + if (!std::strncmp(ss,"log10(",6)) { // Base-10 logarithm + _cimg_mp_op("Function 'log10()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log10,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log10(mem[arg1])); + _cimg_mp_scalar1(mp_log10,arg1); + } + + if (!std::strncmp(ss,"lowercase(",10)) { // Lower case + _cimg_mp_op("Function 'lowercase()'"); + arg1 = compile(ss + 10,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_lowercase,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::lowercase(mem[arg1])); + _cimg_mp_scalar1(mp_lowercase,arg1); + } + break; + + case 'm' : + if (!std::strncmp(ss,"mul(",4)) { // Matrix multiplication + _cimg_mp_op("Function 'mul()'"); + s1 = ss4; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'n' : + if (!std::strncmp(ss,"narg(",5)) { // Number of arguments + _cimg_mp_op("Function 'narg()'"); + if (ss5>=se1) _cimg_mp_return(0); + arg1 = 0; + for (s = ss5; s::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::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); + if (arg1>0 && opcode._height==4) // Special case with one argument and p>=1 + _cimg_mp_scalar1(mp_abs,opcode[3]); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'p' : + if (!std::strncmp(ss,"permut(",7)) { // Number of permutations + _cimg_mp_op("Function 'permut()'"); + s1 = ss7; while (s1::vector((ulongT)mp_polygon,_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 (!std::strncmp(ss,"print(",6) || !std::strncmp(ss,"prints(",7)) { // Print expressions + is_sth = ss[5]=='s'; // is prints() + _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); + 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()?"...":""); + } + 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,"resize(",7)) { // Vector or image resize + _cimg_mp_op("Function 'resize()'"); + if (*ss7!='#') { // Vector + s1 = ss7; while (s1::vector((ulongT)mp_vector_resize,pos,arg2,arg1,(ulongT)_cimg_mp_size(arg1), + arg3,arg4).move_to(code); + _cimg_mp_return(pos); + + } else { // Image + if (!is_single) 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); + pos = 0; + for (s = s0; s10) { + *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 arguments, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + pos<1?"Missing":"Too much", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + l_opcode[0].move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"reverse(",8)) { // Vector reverse + _cimg_mp_op("Function 'reverse()'"); + arg1 = compile(ss8,se1,depth1,0,is_single); + 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); + _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 (s11) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + arg4 = compile(++s1,se1,depth1,0,is_single); + } else { + s2 = s1 + 1; while (s2::vector((ulongT)mp_rot3d,pos,arg1,arg2,arg3,arg4).move_to(code); + } else { // 2D rotation + _cimg_mp_check_type(arg1,1,1,0); + pos = vector(4); + CImg::vector((ulongT)mp_rot2d,pos,arg1).move_to(code); + } + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"round(",6)) { // Value rounding + _cimg_mp_op("Function 'round()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_s,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"same(",5)) { // Test if operands have the same values + _cimg_mp_op("Function 'same()'"); + s1 = ss5; while (s1::vector((ulongT)mp_shift,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sign(",5)) { // Sign + _cimg_mp_op("Function 'sign()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sign,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sign(mem[arg1])); + _cimg_mp_scalar1(mp_sign,arg1); + } + + if (!std::strncmp(ss,"sin(",4)) { // Sine + _cimg_mp_op("Function 'sin()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sin,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sin(mem[arg1])); + _cimg_mp_scalar1(mp_sin,arg1); + } + + if (!std::strncmp(ss,"sinc(",5)) { // Sine cardinal + _cimg_mp_op("Function 'sinc()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinc,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sinc(mem[arg1])); + _cimg_mp_scalar1(mp_sinc,arg1); + } + + if (!std::strncmp(ss,"sinh(",5)) { // Hyperbolic sine + _cimg_mp_op("Function 'sinh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sinh(mem[arg1])); + _cimg_mp_scalar1(mp_sinh,arg1); + } + + if (!std::strncmp(ss,"size(",5)) { // Vector size + _cimg_mp_op("Function 'size()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_constant(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_size(arg1)); + } + + if (!std::strncmp(ss,"solve(",6)) { // Solve linear system + _cimg_mp_op("Function 'solve()'"); + s1 = ss6; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_solve,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sort(",5)) { // Sort vector + _cimg_mp_op("Function 'sort()'"); + 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(); + } + } + + if (!std::strncmp(ss,"sqr(",4)) { // Square + _cimg_mp_op("Function 'sqr()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqr,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sqr(mem[arg1])); + _cimg_mp_scalar1(mp_sqr,arg1); + } + + if (!std::strncmp(ss,"sqrt(",5)) { // Square root + _cimg_mp_op("Function 'sqrt()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sqrt(mem[arg1])); + _cimg_mp_scalar1(mp_sqrt,arg1); + } + + if (!std::strncmp(ss,"srand(",6)) { // Set RNG seed + _cimg_mp_op("Function 'srand()'"); + arg1 = ss6::vector((ulongT)mp_image_stats,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"stov(",5)) { // String to double + _cimg_mp_op("Function 'stov()'"); + s1 = ss5; 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()?"...":""); + } + pos = vector(p1 + p2 + p2*p2); + CImg::vector((ulongT)mp_matrix_svd,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 't' : + if (!std::strncmp(ss,"tan(",4)) { // Tangent + _cimg_mp_op("Function 'tan()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tan,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tan(mem[arg1])); + _cimg_mp_scalar1(mp_tan,arg1); + } + + if (!std::strncmp(ss,"tanh(",5)) { // Hyperbolic tangent + _cimg_mp_op("Function 'tanh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tanh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tanh(mem[arg1])); + _cimg_mp_scalar1(mp_tanh,arg1); + } + + if (!std::strncmp(ss,"trace(",6)) { // Matrix trace + _cimg_mp_op("Function 'trace()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _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); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,p2, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(p3*p2); + CImg::vector((ulongT)mp_transp,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'u' : + if (*ss1=='(') { // Random value with uniform distribution + _cimg_mp_op("Function 'u()'"); + if (*ss2==')') _cimg_mp_scalar2(mp_u,0,1); + s1 = ss2; while (s1ss6 && *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 (arg20) || + !std::strncmp(ss,"vector(",7) || + (!std::strncmp(ss,"vector",6) && ss7::sequence(arg4,arg3 + 1,arg3 + arg4).move_to(l_opcode); + arg2+=arg4; + } else { CImg::vector(arg3).move_to(l_opcode); ++arg2; } + s = ns; + } + if (arg1==~0U) arg1 = arg2; + 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); + _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)mp_vtos,pos,p1,arg1,_cimg_mp_size(arg1),arg2).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'w' : + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_w,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_wh,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss4!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whd,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss5!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whds,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"while(",6)) { // While...do + _cimg_mp_op("Function 'while()'"); + s0 = *ss5=='('?ss6:ss8; + s1 = s0; while (s1::vector((ulongT)mp_while,pos,arg1,p2 - p1,code._width - p2,arg2, + pos>=arg6 && !_cimg_mp_is_constant(pos), + arg1>=arg6 && !_cimg_mp_is_constant(arg1)).move_to(code,p1); + _cimg_mp_return(pos); + } + break; + + case 'x' : + if (!std::strncmp(ss,"xor(",4)) { // Xor + _cimg_mp_op("Function 'xor()'"); + s1 = ss4; while (s1::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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (is_sth) _cimg_mp_constant(op(*this)); + opcode.move_to(code); + _cimg_mp_return(pos); + } + + // No corresponding built-in function -> Look for a user-defined macro call. + s0 = strchr(ss,'('); + if (s0) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + + // Count number of specified arguments. + p1 = 0; + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { + while (*s && 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 + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { // Parse function arguments + while (*s && cimg::is_blank(*s)) ++s; + if (*s==')' && p1==1) break; // Function has no arguments + if (p1>p2) { ++p1; break; } + ns = s; while (ns _pexpr(_expr._width); + ns = _pexpr._data; + for (ps = _expr._data, c1 = ' '; *ps; ++ps) { + if (!cimg::is_blank(*ps)) c1 = *ps; + *(ns++) = c1; + } + *ns = 0; + + CImg _level = get_level(_expr); + expr.swap(_expr); + pexpr.swap(_pexpr); + level.swap(_level); + s0 = user_macro; + user_macro = macro_def[l]; + pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref,is_single); + user_macro = s0; + level.swap(_level); + pexpr.swap(_pexpr); + expr.swap(_expr); + _cimg_mp_return(pos); + } + + if (arg3) { // Macro name matched but number of arguments does not + CImg sig_nargs(arg3); + arg1 = 0; + cimglist_for(macro_def,l) if (!std::strcmp(macro_def[l],variable_name)) + sig_nargs[arg1++] = (unsigned int)macro_def[l].back(); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (sig_nargs._width>1) { + sig_nargs.sort(); + arg1 = sig_nargs.back(); + --sig_nargs._width; + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,sig_nargs.value_string()._data,arg1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } else + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,*sig_nargs,*sig_nargs!=1?"s":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + } + } // if (se1==')') + + // Char / string initializer. + if (*se1=='\'' && + ((se1>ss && *ss=='\'') || + (se1>ss1 && *ss=='_' && *ss1=='\''))) { + if (*ss=='_') { _cimg_mp_op("Char initializer"); s1 = ss2; } + else { _cimg_mp_op("String initializer"); s1 = ss1; } + arg1 = (unsigned int)(se1 - s1); // Original string length + if (arg1) { + CImg(s1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + if (*ss=='_') { + if (arg1==1) _cimg_mp_constant(*variable_name); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Literal %s contains more than one character, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + ss1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg1); + CImg::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); + _cimg_mp_return(pos); + } + + // Vector initializer [ ... ]. + if (*ss=='[' && *se1==']') { + _cimg_mp_op("Vector initializer"); + 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 + if (arg1) { + CImg(s1 + 1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + pos = vector(arg1); + CImg::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); + } else { // Vector values provided as list of items + arg1 = 0; // Number of specified values + if (*ss1!=']') for (s = ss1; s::sequence(arg3,arg2 + 1,arg2 + arg3).move_to(l_opcode); + arg1+=arg3; + } else { CImg::vector(arg2).move_to(l_opcode); ++arg1; } + s = ns; + } + 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); + } + _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); + _cimg_mp_return(pos); + case 'R' : // R#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0, + 0,_cimg_mp_boundary); + case 'G' : // G#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1, + 0,_cimg_mp_boundary); + case 'B' : // B#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2, + 0,_cimg_mp_boundary); + case 'A' : // A#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3, + 0,_cimg_mp_boundary); + } + } + + if (*ss1 && *ss2=='#' && ss3::vector(listin[p1].median()).move_to(list_median[p1]); + _cimg_mp_constant(*list_median[p1]); + } + _cimg_mp_scalar1(mp_list_median,arg1); + } + if (*ss1>='0' && *ss1<='9') { // i0#ind...i9#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,*ss1 - '0', + 0,_cimg_mp_boundary); + } + switch (*ss1) { + case 'm' : arg2 = 0; break; // im#ind + case 'M' : arg2 = 1; break; // iM#ind + case 'a' : arg2 = 2; break; // ia#ind + case 'v' : arg2 = 3; break; // iv#ind + case 's' : arg2 = 12; break; // is#ind + case 'p' : arg2 = 13; break; // ip#ind + } + } else if (*ss1=='m') switch (*ss) { + case 'x' : arg2 = 4; break; // xm#ind + case 'y' : arg2 = 5; break; // ym#ind + case 'z' : arg2 = 6; break; // zm#ind + case 'c' : arg2 = 7; break; // cm#ind + } else if (*ss1=='M') switch (*ss) { + case 'x' : arg2 = 8; break; // xM#ind + case 'y' : arg2 = 9; break; // yM#ind + case 'z' : arg2 = 10; break; // zM#ind + case 'c' : arg2 = 11; break; // cM#ind + } + if (arg2!=~0U) { + if (!listin) _cimg_mp_return(0); + if (_cimg_mp_is_constant(arg1)) { + if (!list_stats) list_stats.assign(listin._width); + if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(listin[p1].get_stats(),false); + _cimg_mp_constant(list_stats(p1,arg2)); + } + _cimg_mp_scalar2(mp_list_stats,arg1,arg2); + } + } + + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4 error. + is_sth = true; // is_valid_variable_name + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + *se = saved_char; + c1 = *se1; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (is_sth) + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Undefined variable '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + s1 = std::strchr(ss,'('); + s_op = s1 && c1==')'?"function call":"item"; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + s_op,variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Evaluation procedure. + double operator()(const double x, const double y, const double z, const double c) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + return *result; + } + + // Evaluation procedure (return output values in vector 'output'). + template + void operator()(const double x, const double y, const double z, const double c, t *const output) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + if (result_dim) { + const double *ptrs = result + 1; + t *ptrd = output; + for (unsigned int k = 0; k_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + + // Return type of a memory element 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); + cimg_sprintf(res._data + 6,"%u",_cimg_mp_size(arg)); + } else CImg::string("scalar").move_to(res); + return res; + } + + // Insert constant value in memory. + unsigned int constant(const double val) { + + // Search for built-in constant. + if (cimg::type::is_nan(val)) return _cimg_mp_slot_nan; + if (val==(double)(int)val) { + if (val>=0 && val<=10) return (unsigned int)val; + if (val<0 && val>=-5) return (unsigned int)(10 - val); + } + if (val==0.5) return 16; + + // Search for constant already requested before (in const cache). + unsigned int ind = ~0U; + if (constcache_size<1024) { + if (!constcache_size) { + constcache_vals.assign(16,1,1,1,0); + constcache_inds.assign(16,1,1,1,0); + *constcache_vals = val; + constcache_size = 1; + ind = 0; + } else { // Dichotomic search + const double val_beg = *constcache_vals, val_end = constcache_vals[constcache_size - 1]; + if (val_beg>=val) ind = 0; + else if (val_end==val) ind = constcache_size - 1; + else if (val_end=constcache_size || constcache_vals[ind]!=val) { + ++constcache_size; + if (constcache_size>constcache_vals._width) { + constcache_vals.resize(-200,1,1,1,0); + constcache_inds.resize(-200,1,1,1,0); + } + const int l = constcache_size - (int)ind - 1; + if (l>0) { + std::memmove(&constcache_vals[ind + 1],&constcache_vals[ind],l*sizeof(double)); + std::memmove(&constcache_inds[ind + 1],&constcache_inds[ind],l*sizeof(unsigned int)); + } + constcache_vals[ind] = val; + constcache_inds[ind] = 0; + } + } + if (constcache_inds[ind]) return constcache_inds[ind]; + } + + // Insert new constant in memory if necessary. + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(-200,1,1,1,0); } + const unsigned int pos = mempos++; + mem[pos] = val; + memtype[pos] = 1; // Set constant property + if (ind!=~0U) constcache_inds[ind] = pos; + return pos; + } + + // Insert code instructions for processing scalars. + unsigned int scalar() { // Insert new scalar in memory + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(mem._width,1,1,1,0); } + return mempos++; + } + + unsigned int scalar0(const mp_func op) { + const unsigned int pos = scalar(); + CImg::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!=~0U && 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!=~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: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: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: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: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: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: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 + if (mempos + siz>=mem._width) { + mem.resize(2*mem._width + siz,1,1,1,0); + memtype.resize(mem._width,1,1,1,0); + } + const unsigned int pos = mempos++; + mem[pos] = cimg::type::nan(); + memtype[pos] = siz + 1; + mempos+=siz; + return pos; + } + + unsigned int vector(const unsigned int siz, const double value) { // Insert new initialized vector + const unsigned int pos = vector(siz); + double *ptr = &mem[pos] + 1; + for (unsigned int i = 0; i::vector((ulongT)mp_vector_copy,pos,arg,siz).move_to(code); + return pos; + } + + void self_vector_s(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_s,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1).move_to(code[code._width - 1 - siz + k]); + } + } + + void self_vector_v(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_v,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + } + + unsigned int vector1_v(const mp_func op, const unsigned int arg1) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vs(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_sv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector3_vss(const mp_func op, const unsigned int arg1, const unsigned int arg2, + const unsigned int arg3) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2,arg3).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + // Check if a memory slot is a positive integer constant scalar value. + // 'mode' can be: + // { 0=constant | 1=integer constant | 2=positive integer constant | 3=strictly-positive integer constant } + void check_constant(const unsigned int arg, const unsigned int n_arg, + const unsigned int mode, + char *const ss, char *const se, const char saved_char) { + _cimg_mp_check_type(arg,n_arg,1,0); + if (!(_cimg_mp_is_constant(arg) && + (!mode || (double)(int)mem[arg]==mem[arg]) && + (mode<2 || mem[arg]>=(mode==3)))) { + const char *s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ": + n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth ": + n_arg==9?"Ninth ":"One of the "; + *se = saved_char; + char *const s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_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); + 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // Evaluation functions, known by the parser. + // Defining these functions 'static' ensures that sizeof(mp_func)==sizeof(ulongT), + // so we can store pointers to them directly in the opcode vectors. +#ifdef _mp_arg +#undef _mp_arg +#endif +#define _mp_arg(x) mp.mem[mp.opcode[x]] + + static double mp_abs(_cimg_math_parser& mp) { + return cimg::abs(_mp_arg(2)); + } + + static double mp_add(_cimg_math_parser& mp) { + return _mp_arg(2) + _mp_arg(3); + } + + static double mp_acos(_cimg_math_parser& mp) { + return std::acos(_mp_arg(2)); + } + + static double mp_acosh(_cimg_math_parser& mp) { + return cimg::acosh(_mp_arg(2)); + } + + static double mp_asinh(_cimg_math_parser& mp) { + return cimg::asinh(_mp_arg(2)); + } + + static double mp_atanh(_cimg_math_parser& mp) { + return cimg::atanh(_mp_arg(2)); + } + + static double mp_arg(_cimg_math_parser& mp) { + const int _ind = (int)_mp_arg(4); + const unsigned int + nb_args = (unsigned int)mp.opcode[2] - 4, + ind = _ind<0?_ind + nb_args:(unsigned int)_ind, + siz = (unsigned int)mp.opcode[3]; + if (siz>0) { + if (ind>=nb_args) std::memset(&_mp_arg(1) + 1,0,siz*sizeof(double)); + else std::memcpy(&_mp_arg(1) + 1,&_mp_arg(ind + 4) + 1,siz*sizeof(double)); + return cimg::type::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; ival) { val = _val; argval = i - 3; } + } + return (double)argval; + } + + static double mp_asin(_cimg_math_parser& mp) { + return std::asin(_mp_arg(2)); + } + + static double mp_atan(_cimg_math_parser& mp) { + return std::atan(_mp_arg(2)); + } + + static double mp_atan2(_cimg_math_parser& mp) { + return std::atan2(_mp_arg(2),_mp_arg(3)); + } + + static double mp_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; i>(unsigned int)_mp_arg(3)); + } + + static double mp_bitwise_xor(_cimg_math_parser& mp) { + return (double)((longT)_mp_arg(2) ^ (longT)_mp_arg(3)); + } + + static double mp_bool(_cimg_math_parser& mp) { + return (double)(bool)_mp_arg(2); + } + + static double mp_break(_cimg_math_parser& mp) { + mp.break_type = 1; + mp.p_code = mp.p_break - 1; + return cimg::type::nan(); + } + + static double mp_breakpoint(_cimg_math_parser& mp) { + cimg_abort_init; + cimg_abort_test; + cimg::unused(mp); + return cimg::type::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_cbrt(_cimg_math_parser& mp) { + return cimg::cbrt(_mp_arg(2)); + } + + static double mp_ceil(_cimg_math_parser& mp) { + return std::ceil(_mp_arg(2)); + } + + static double mp_complex_abs(_cimg_math_parser& mp) { + return cimg::_hypot(_mp_arg(2),_mp_arg(3)); + } + + static double mp_complex_conj(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = *(ptrs++); + *ptrd = -*(ptrs); + return cimg::type::nan(); + } + + static double mp_complex_div_sv(_cimg_math_parser& mp) { + const double + *ptr2 = &_mp_arg(3) + 1, + r1 = _mp_arg(2), + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = r1*r2/denom; + *ptrd = -r1*i2/denom; + return cimg::type::nan(); + } + + static double mp_complex_div_vv(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = (r1*r2 + i1*i2)/denom; + *ptrd = (r2*i1 - r1*i2)/denom; + return cimg::type::nan(); + } + + static double mp_complex_exp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs), er = std::exp(r); + *(ptrd++) = er*std::cos(i); + *(ptrd++) = er*std::sin(i); + return cimg::type::nan(); + } + + static double mp_complex_log(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs); + *(ptrd++) = 0.5*std::log(r*r + i*i); + *(ptrd++) = std::atan2(i,r); + return cimg::type::nan(); + } + + static double mp_complex_mul(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = r1*r2 - i1*i2; + *(ptrd++) = r1*i2 + r2*i1; + return cimg::type::nan(); + } + + static void _mp_complex_pow(const double r1, const double i1, + const double r2, const double i2, + double *ptrd) { + double ro, io; + if (cimg::abs(i2)<1e-15) { // Exponent is real + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) { + if (cimg::abs(r2)<1e-15) { ro = 1; io = 0; } + else ro = io = 0; + } else { + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2), + phio = r2*phi1; + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + } else { // Exponent is complex + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) ro = io = 0; + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2)*std::exp(-i2*phi1), + phio = r2*phi1 + 0.5*i2*std::log(mod1_2); + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + *(ptrd++) = ro; + *ptrd = io; + } + + static double mp_complex_pow_ss(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_sv(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,ptr2[0],ptr2[1],ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vs(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vv(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],ptr2[0],ptr2[1],ptrd); + return cimg::type::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_cos(_cimg_math_parser& mp) { + return std::cos(_mp_arg(2)); + } + + static double mp_cosh(_cimg_math_parser& mp) { + return std::cosh(_mp_arg(2)); + } + + static double mp_critical(_cimg_math_parser& mp) { + const double res = _mp_arg(1); + cimg_pragma_openmp(critical(mp_critical)) + { + for (const CImg *const p_end = ++mp.p_code + mp.opcode[2]; + mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + } + --mp.p_code; + return res; + } + + static double mp_crop(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const unsigned int + dx = (unsigned int)mp.opcode[7], + dy = (unsigned int)mp.opcode[8], + dz = (unsigned int)mp.opcode[9], + dc = (unsigned int)mp.opcode[10]; + const 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 (!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, + z + dz - 1,c + dc - 1, + boundary_conditions); + return cimg::type::nan(); + } + + static double mp_cross(_cimg_math_parser& mp) { + CImg + vout(&_mp_arg(1) + 1,1,3,1,1,true), + v1(&_mp_arg(2) + 1,1,3,1,1,true), + v2(&_mp_arg(3) + 1,1,3,1,1,true); + (vout = v1).cross(v2); + return cimg::type::nan(); + } + + static double mp_cut(_cimg_math_parser& mp) { + double val = _mp_arg(2), cmin = _mp_arg(3), cmax = _mp_arg(4); + return valcmax?cmax:val; + } + + 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; + } + + 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++); + cimg::strellipsize(expr); + const ulongT g_target = mp.opcode[1]; + +#ifndef cimg_use_openmp + const unsigned int n_thread = 0; +#else + const unsigned int n_thread = omp_get_thread_num(); +#endif + cimg_pragma_openmp(critical(mp_debug)) + { + std::fprintf(cimg::output(), + "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" + "Start debugging expression '%s', code length %u -> mem[%u] (memsize: %u)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)mp.opcode[3],(unsigned int)g_target,mp.mem._width); + std::fflush(cimg::output()); + mp.debug_indent+=3; + } + const CImg *const p_end = (++mp.p_code) + mp.opcode[3]; + CImg _op; + for ( ; mp.p_code &op = *mp.p_code; + mp.opcode._data = op._data; + + _op.assign(1,op._height - 1); + const ulongT *ptrs = op._data + 1; + for (ulongT *ptrd = _op._data, *const ptrde = _op._data + _op._height; ptrd mem[%u] = %g", + (void*)&mp,n_thread,mp.debug_indent,' ', + (void*)mp.opcode._data,(void*)*mp.opcode,_op.value_string().data(), + (unsigned int)target,mp.mem[target]); + std::fflush(cimg::output()); + } + } + cimg_pragma_openmp(critical(mp_debug)) + { + 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)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)g_target,mp.mem[g_target],mp.mem._width); + std::fflush(cimg::output()); + } + --mp.p_code; + return mp.mem[g_target]; + } + + static double mp_decrement(_cimg_math_parser& mp) { + return _mp_arg(2) - 1; + } + + static double mp_det(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + return CImg(ptrs,k,k,1,1,true).det(); + } + + 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; + 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"); + return cimg::type::nan(); + } + + static double mp_display(_cimg_math_parser& mp) { + const unsigned int + _siz = (unsigned int)mp.opcode[3], + siz = _siz?_siz:1; + const double *const ptr = &_mp_arg(1) + (_siz?1:0); + const int + w = (int)_mp_arg(4), + h = (int)_mp_arg(5), + d = (int)_mp_arg(6), + s = (int)_mp_arg(7); + CImg img; + if (w>0 && h>0 && d>0 && s>0) { + if ((unsigned int)w*h*d*s<=siz) img.assign(ptr,w,h,d,s,true); + else img.assign(ptr,siz).resize(w,h,d,s,-1); + } else img.assign(ptr,1,siz,1,1,true); + + CImg expr(mp.opcode[2] - 8); + const ulongT *ptrs = mp.opcode._data + 8; + cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); + ((CImg::string("[" cimg_appname "_math_parser] ",false,true),expr)>'x').move_to(expr); + cimg::strellipsize(expr); + std::fputc('\n',cimg::output()); + img.display(expr._data); + return cimg::type::nan(); + } + + static double mp_div(_cimg_math_parser& mp) { + return _mp_arg(2)/_mp_arg(3); + } + + static double mp_dot(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[4]; + return CImg(&_mp_arg(2) + 1,1,siz,1,1,true). + dot(CImg(&_mp_arg(3) + 1,1,siz,1,1,true)); + } + + static double mp_do(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_body = ++mp.p_code, + *const p_cond = p_body + mp.opcode[3], + *const p_end = p_cond + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (mp.mem[mem_cond]); + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_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; + 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); + const CImg str = _str>'x'; + std::fprintf(cimg::output(),"\n%s",str._data); + return cimg::type::nan(); + } + + static double mp_ellipse(_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]; + CImg color(img._spectrum,1,1,1,0); + bool is_invalid_arguments = false; + unsigned int i = 4; + float r1 = 0, r2 = 0, angle = 0, opacity = 1; + int x0 = 0, y0 = 0; + if (i>=i_end) is_invalid_arguments = true; + else { + x0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + y0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + r1 = (float)_mp_arg(i++); + if (i>=i_end) r2 = r1; + 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) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_eq(_cimg_math_parser& mp) { + 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); +#endif + return cimg::type::nan(); + } + + static double mp_exp(_cimg_math_parser& mp) { + return std::exp(_mp_arg(2)); + } + + static double mp_eye(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int k = (unsigned int)mp.opcode[2]; + CImg(ptrd,k,k,1,1,true).identity_matrix(); + return cimg::type::nan(); + } + + static double mp_factorial(_cimg_math_parser& mp) { + return cimg::factorial((int)_mp_arg(2)); + } + + static double mp_fibonacci(_cimg_math_parser& mp) { + return cimg::fibonacci((int)_mp_arg(2)); + } + + static double mp_find(_cimg_math_parser& mp) { + const bool is_forward = (bool)_mp_arg(5); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const double + *const ptrb = &_mp_arg(2) + 1, + *const ptre = ptrb + siz, + val = _mp_arg(4), + *ptr = ptrb + ind; + + // Forward search + if (is_forward) { + while (ptr=ptrb && *ptr!=val) --ptr; + return ptr=(longT)siz1) return -1.; + const double + *const ptr1b = &_mp_arg(2) + 1, + *const ptr1e = ptr1b + siz1, + *const ptr2b = &_mp_arg(4) + 1, + *const ptr2e = ptr2b + siz2, + *ptr1 = ptr1b + ind, + *p1 = 0, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 + *const p_init = ++mp.p_code, + *const p_cond = p_init + mp.opcode[4], + *const p_body = p_cond + mp.opcode[5], + *const p_post = p_body + mp.opcode[6], + *const p_end = p_post + mp.opcode[7]; + const unsigned int vsiz = (unsigned int)mp.opcode[2]; + bool is_cond = false; + if (mp.opcode[8]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[9]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + + for (mp.p_code = p_init; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + + if (!mp.break_type) do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + + for (mp.p_code = p_post; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_fsize(_cimg_math_parser& mp) { + const CImg filename(mp.opcode._data + 3,mp.opcode[2] - 3); + return (double)cimg::fsize(filename); + } + + static double mp_g(_cimg_math_parser& mp) { + cimg::unused(mp); + return cimg::grand(&mp.rng); + } + + 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); + } + + static double mp_gcd(_cimg_math_parser& mp) { + return cimg::gcd((long)_mp_arg(2),(long)_mp_arg(3)); + } + + static double mp_gt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>_mp_arg(3)); + } + + static double mp_gte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>=_mp_arg(3)); + } + + static double mp_i(_cimg_math_parser& mp) { + return (double)mp.imgin.atXYZC((int)mp.mem[_cimg_mp_slot_x],(int)mp.mem[_cimg_mp_slot_y], + (int)mp.mem[_cimg_mp_slot_z],(int)mp.mem[_cimg_mp_slot_c],(T)0); + } + + static double mp_if(_cimg_math_parser& mp) { + const bool is_cond = (bool)_mp_arg(2); + const ulongT + mem_left = mp.opcode[3], + mem_right = mp.opcode[4]; + const CImg + *const p_right = ++mp.p_code + mp.opcode[5], + *const p_end = p_right + mp.opcode[6]; + const unsigned int vtarget = (unsigned int)mp.opcode[1], vsiz = (unsigned int)mp.opcode[7]; + if (is_cond) for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + else for (mp.p_code = p_right; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.p_code==mp.p_break) --mp.p_code; + else mp.p_code = p_end - 1; + if (vsiz) std::memcpy(&mp.mem[vtarget] + 1,&mp.mem[is_cond?mem_left:mem_right] + 1,sizeof(double)*vsiz); + return mp.mem[is_cond?mem_left:mem_right]; + } + + static double mp_image_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]; + 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()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.display(title); + cimg::mutex(6,0); + 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]; + 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]; + return (double)img.median(); + } + + static double mp_image_print(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.print(title); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_resize(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + const double + _w = mp.opcode[3]==~0U?-100:_mp_arg(3), + _h = mp.opcode[4]==~0U?-100:_mp_arg(4), + _d = mp.opcode[5]==~0U?-100:_mp_arg(5), + _s = mp.opcode[6]==~0U?-100:_mp_arg(6); + const unsigned int + w = (unsigned int)(_w>=0?_w:-_w*img.width()/100), + h = (unsigned int)(_h>=0?_h:-_h*img.height()/100), + d = (unsigned int)(_d>=0?_d:-_d*img.depth()/100), + s = (unsigned int)(_s>=0?_s:-_s*img.spectrum()/100), + interp = (int)_mp_arg(7); + if (mp.is_fill && img._data==mp.imgout._data) { + cimg::mutex(6,0); + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'resize()': " + "Cannot both fill and resize image (%u,%u,%u,%u) " + "to new dimensions (%u,%u,%u,%u).", + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,w,h,d,s); + } + const unsigned int + boundary = (int)_mp_arg(8); + const float + cx = (float)_mp_arg(9), + cy = (float)_mp_arg(10), + cz = (float)_mp_arg(11), + cc = (float)_mp_arg(12); + img.resize(w,h,d,s,interp,boundary,cx,cy,cz,cc); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_s(_cimg_math_parser& mp) { + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; + return (double)img.spectrum(); + } + + static double mp_image_sort(_cimg_math_parser& mp) { + const bool is_increasing = (bool)_mp_arg(3); + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()), + axis = (unsigned int)_mp_arg(4); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + img.sort(is_increasing, + axis==0 || axis=='x'?'x': + axis==1 || axis=='y'?'y': + axis==2 || axis=='z'?'z': + axis==3 || axis=='c'?'c':0); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + 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(); + 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(); + } + 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]; + 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]; + 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]; + 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]; + return (double)img.width()*img.height()*img.depth()*img.spectrum(); + } + + static double mp_increment(_cimg_math_parser& mp) { + return _mp_arg(2) + 1; + } + + static double mp_int(_cimg_math_parser& mp) { + return (double)(longT)_mp_arg(2); + } + + static double mp_ioff(_cimg_math_parser& mp) { + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3); + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off::is_inf(_mp_arg(2)); + } + + static double mp_isint(_cimg_math_parser& mp) { + return (double)(cimg::mod(_mp_arg(2),1.)==0); + } + + static double mp_isnan(_cimg_math_parser& mp) { + return (double)cimg::type::is_nan(_mp_arg(2)); + } + + static double mp_ixyzc(_cimg_math_parser& mp) { + const unsigned int + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7); + const CImg &img = mp.imgin; + const double + x = _mp_arg(2), y = _mp_arg(3), + z = _mp_arg(4), c = _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.imgin; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), + z = oz + _mp_arg(4), c = oc + _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx vals(i_end - 4); + double *p = vals.data(); + for (unsigned int i = 4; i &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(4); + const ulongT siz = (ulongT)img.size(); + longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const T + *const ptrb = img.data(), + *const ptre = img.end(), + *ptr = ptrb + ind; + const double val = _mp_arg(3); + + // Forward search + if (is_forward) { + while (ptr=ptrb && (double)*ptr!=val) --ptr; + return ptr &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(5); + const ulongT + siz1 = (ulongT)img.size(), + siz2 = (ulongT)mp.opcode[4]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz1 - 1); + if (ind<0 || ind>=(longT)siz1) return -1.; + const T + *const ptr1b = img.data(), + *const ptr1e = ptr1b + siz1, + *ptr1 = ptr1b + ind, + *p1 = 0; + const double + *const ptr2b = &_mp_arg(3) + 1, + *const ptr2e = ptr2b + siz2, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + x = _mp_arg(3), y = _mp_arg(4), + z = _mp_arg(5), c = _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), + z = oz + _mp_arg(5), c = oc + _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx::vector(mp.listin[ind].median()).move_to(mp.list_median[ind]); + return *mp.list_median[ind]; + } + + static double mp_list_set_ioff(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), y = (int)_mp_arg(4), + z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(3)), y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)), c = (int)(oc + _mp_arg(6)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Ixyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_set_Joff_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Jxyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_spectrum(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._spectrum; + } + + static double mp_list_stats(_cimg_math_parser& mp) { + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + k = (unsigned int)mp.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); + return mp.list_stats(ind,k); + } + + static double mp_list_wh(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height; + } + + static double mp_list_whd(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth; + } + + static double mp_list_whds(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth*mp.listin[ind]._spectrum; + } + + static double mp_list_width(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width; + } + + static double mp_list_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const CImg &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double x = _mp_arg(3), y = _mp_arg(4), z = _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_list_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], oz = (int)mp.mem[_cimg_mp_slot_z]; + const CImg &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), z = oz + _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_log(_cimg_math_parser& mp) { + return std::log(_mp_arg(2)); + } + + static double mp_log10(_cimg_math_parser& mp) { + return std::log10(_mp_arg(2)); + } + + static double mp_log2(_cimg_math_parser& mp) { + return cimg::log2(_mp_arg(2)); + } + + static double mp_logical_and(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (!val_left) { mp.p_code = p_end - 1; return 0; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_logical_not(_cimg_math_parser& mp) { + return (double)!_mp_arg(2); + } + + static double mp_logical_or(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (val_left) { mp.p_code = p_end - 1; return 1; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_lowercase(_cimg_math_parser& mp) { + return cimg::lowercase(_mp_arg(2)); + } + + static double mp_lt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<_mp_arg(3)); + } + + static double mp_lte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<=_mp_arg(3)); + } + + static double mp_matrix_eig(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptr1 = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + CImg val, vec; + CImg(ptr1,k,k,1,1,true).symmetric_eigen(val,vec); + CImg(ptrd,1,k,1,1,true) = val; + CImg(ptrd + k,k,k,1,1,true) = vec.get_transpose(); + 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(); + return cimg::type::nan(); + } + + static double mp_matrix_mul(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr1,l,k,1,1,true)*CImg(ptr2,m,l,1,1,true); + 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; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg U, S, V; + CImg(ptr1,k,l,1,1,true).SVD(U,S,V); + CImg(ptrd,k,l,1,1,true) = U; + CImg(ptrd + k*l,1,k,1,1,true) = S; + CImg(ptrd + k*l + k,k,k,1,1,true) = V; + return cimg::type::nan(); + } + + static double mp_max(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i=mp.mem.width()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds variable pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %u).", + mp.imgin.pixel_type(),siz,inc,off,eoff,mp.mem._width - 1); + return &mp.mem[off]; + } + + static float* _mp_memcopy_float(_cimg_math_parser& mp, const ulongT *const p_ref, + const longT siz, const long inc) { + const unsigned ind = (unsigned int)p_ref[1]; + const CImg &img = ind==~0U?mp.imgin:mp.listin[cimg::mod((int)mp.mem[ind],mp.listin.width())]; + const bool is_relative = (bool)p_ref[2]; + int ox, oy, oz, oc; + longT off = 0; + if (is_relative) { + ox = (int)mp.mem[_cimg_mp_slot_x]; + oy = (int)mp.mem[_cimg_mp_slot_y]; + oz = (int)mp.mem[_cimg_mp_slot_z]; + oc = (int)mp.mem[_cimg_mp_slot_c]; + off = img.offset(ox,oy,oz,oc); + } + if ((*p_ref)%2) { + const int + x = (int)mp.mem[p_ref[3]], + y = (int)mp.mem[p_ref[4]], + z = (int)mp.mem[p_ref[5]], + c = *p_ref==5?0:(int)mp.mem[p_ref[6]]; + off+=img.offset(x,y,z,c); + } else off+=(longT)mp.mem[p_ref[3]]; + const longT eoff = off + (siz - 1)*inc; + if (off<0 || eoff>=(longT)img.size()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds image pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %lu).", + mp.imgin.pixel_type(),siz,inc,off,eoff,img.size() - 1); + return (float*)&img[off]; + } + + static double mp_memcopy(_cimg_math_parser& mp) { + longT siz = (longT)_mp_arg(4); + const longT inc_d = (longT)_mp_arg(5), inc_s = (longT)_mp_arg(6); + const float + _opacity = (float)_mp_arg(7), + opacity = (float)cimg::abs(_opacity), + omopacity = 1 - std::max(_opacity,0.f); + if (siz>0) { + const bool + is_doubled = mp.opcode[8]<=1, + is_doubles = mp.opcode[15]<=1; + if (is_doubled && is_doubles) { // (double*) <- (double*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(double)); + else std::memmove(ptrd,ptrs,siz*sizeof(double)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,double) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } else if (is_doubled && !is_doubles) { // (double*) <- (float*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + _opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else if (!is_doubled && is_doubles) { // (float*) <- (double*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = (float)*ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = (float)(omopacity**ptrd + opacity**ptrs); ptrd+=inc_d; ptrs+=inc_s; } + } else { // (float*) <- (float*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(float)); + else std::memmove(ptrd,ptrs,siz*sizeof(float)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,float) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } + } + return _mp_arg(1); + } + + static double mp_min(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i vals(i_end - 3); + double *p = vals.data(); + 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.; + } + + static double mp_permutations(_cimg_math_parser& mp) { + return cimg::permutations((int)_mp_arg(2),(int)_mp_arg(3),(bool)_mp_arg(4)); + } + + 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 (!is_invalid_arguments) { + const int nbv = (int)_mp_arg(4); + if (nbv<=0) is_invalid_arguments = true; + else { + CImg points(nbv,2,1,1,0); + CImg color(img._spectrum,1,1,1,0); + float opacity = 1; + unsigned int i = 5; + cimg_foroff(points,k) if (i args(i_end - 4); + cimg_forX(args,k) args[k] = _mp_arg(4 + k); + if (ind==~0U) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_pow(_cimg_math_parser& mp) { + const double v = _mp_arg(2), p = _mp_arg(3); + return std::pow(v,p); + } + + static double mp_pow0_25(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return std::sqrt(std::sqrt(val)); + } + + static double mp_pow3(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val; + } + + static double mp_pow4(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val*val; + } + + static double mp_print(_cimg_math_parser& mp) { + const double val = _mp_arg(1); + const bool print_char = (bool)mp.opcode[3]; + cimg_pragma_openmp(critical(mp_print)) + { + 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::mutex(6); + if (print_char) + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g = '%c'",expr._data,val,(int)val); + else + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g",expr._data,val); + std::fflush(cimg::output()); + cimg::mutex(6,0); + } + return val; + } + + static double mp_prod(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i::nan(); + } + + static double mp_rot3d(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const float x = (float)_mp_arg(2), y = (float)_mp_arg(3), z = (float)_mp_arg(4), theta = (float)_mp_arg(5); + CImg(ptrd,3,3,1,1,true) = CImg::rotation_matrix(x,y,z,theta); + return cimg::type::nan(); + } + + static double mp_round(_cimg_math_parser& mp) { + return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4)); + } + + static double mp_self_add(_cimg_math_parser& mp) { + return _mp_arg(1)+=_mp_arg(2); + } + + static double mp_self_bitwise_and(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val & (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_left_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val<<(unsigned int)_mp_arg(2)); + } + + static double mp_self_bitwise_or(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val | (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_right_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val>>(unsigned int)_mp_arg(2)); + } + + static double mp_self_decrement(_cimg_math_parser& mp) { + return --_mp_arg(1); + } + + static double mp_self_increment(_cimg_math_parser& mp) { + return ++_mp_arg(1); + } + + static double mp_self_map_vector_s(_cimg_math_parser& mp) { // Vector += scalar + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2]; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode[2] = mp.opcode[4]; // Scalar argument + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1]; + while (siz-->0) { target = ptrd++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_map_vector_v(_cimg_math_parser& mp) { // Vector += vector + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,4); + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1], &argument = mp.opcode[2]; + while (siz-->0) { target = ptrd++; argument = ptrs++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_mul(_cimg_math_parser& mp) { + return _mp_arg(1)*=_mp_arg(2); + } + + static double mp_self_div(_cimg_math_parser& mp) { + return _mp_arg(1)/=_mp_arg(2); + } + + static double mp_self_modulo(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = cimg::mod(val,_mp_arg(2)); + } + + static double mp_self_pow(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = std::pow(val,_mp_arg(2)); + } + + static double mp_self_sub(_cimg_math_parser& mp) { + return _mp_arg(1)-=_mp_arg(2); + } + + static double mp_set_ioff(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + x = (int)_mp_arg(2), y = (int)_mp_arg(3), + z = (int)_mp_arg(4), c = (int)_mp_arg(5); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(2)), y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)), c = (int)(oc + _mp_arg(5)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Ixyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_set_Joff_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Jxyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_shift(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const unsigned int siz = (unsigned int)mp.opcode[3]; + const int + shift = (int)_mp_arg(4), + boundary_conditions = (int)_mp_arg(5); + CImg(ptrd,siz,1,1,1,true) = CImg(ptrs,siz,1,1,1,true).shift(shift,0,0,0,boundary_conditions); + return cimg::type::nan(); + } + + static double mp_sign(_cimg_math_parser& mp) { + return cimg::sign(_mp_arg(2)); + } + + static double mp_sin(_cimg_math_parser& mp) { + return std::sin(_mp_arg(2)); + } + + static double mp_sinc(_cimg_math_parser& mp) { + return cimg::sinc(_mp_arg(2)); + } + + static double mp_sinh(_cimg_math_parser& mp) { + return std::sinh(_mp_arg(2)); + } + + static double mp_solve(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr2,m,l,1,1,true).get_solve(CImg(ptr1,k,l,1,1,true)); + 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 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); + return cimg::type::nan(); + } + + static double mp_sqr(_cimg_math_parser& mp) { + return cimg::sqr(_mp_arg(2)); + } + + static double mp_sqrt(_cimg_math_parser& mp) { + return std::sqrt(_mp_arg(2)); + } + + static double mp_srand(_cimg_math_parser& mp) { + mp.rng = (ulongT)_mp_arg(2); + return cimg::type::nan(); + } + + static double mp_srand0(_cimg_math_parser& mp) { + cimg::srand(&mp.rng); +#ifdef cimg_use_openmp + 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; i0) mp.mem[ptrd++] = (double)*(ptrs++); + return cimg::type::nan(); + } + + static double mp_stov(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)_mp_arg(4); + const bool is_strict = (bool)_mp_arg(5); + double val = cimg::type::nan(); + if (ind<0 || ind>=(longT)siz) return val; + 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; + + int err = cimg_sscanf(ss,"%lf%c",&val,&sep); +#if cimg_OS==2 + // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able + // to read those particular values. + if (!err && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) { + bool is_positive = true; + const char *s = ss; + if (*s=='+') ++s; else if (*s=='-') { ++s; is_positive = false; } + if (!cimg::strcasecmp(s,"inf")) { val = cimg::type::inf(); err = 1; } + else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); err = 1; } + if (err==1 && !is_positive) val = -val; + } +#endif + if (is_strict && err!=1) return cimg::type::nan(); + return val; + } + + static double mp_sub(_cimg_math_parser& mp) { + return _mp_arg(2) - _mp_arg(3); + } + + static double mp_sum(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i(ptrs,k,k,1,1,true).trace(); + } + + static double mp_transp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg(ptrd,l,k,1,1,true) = CImg(ptrs,k,l,1,1,true).get_transpose(); + return cimg::type::nan(); + } + + static double mp_u(_cimg_math_parser& mp) { + return cimg::rand(_mp_arg(2),_mp_arg(3),&mp.rng); + } + + static double mp_uppercase(_cimg_math_parser& mp) { + return cimg::uppercase(_mp_arg(2)); + } + + 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; i::nan(); + } + + static double mp_vector_crop(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const longT + length = (longT)mp.opcode[3], + start = (longT)_mp_arg(4), + sublength = (longT)mp.opcode[5]; + if (start<0 || start + sublength>length) + throw CImgArgumentException("[" cimg_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)); + return cimg::type::nan(); + } + + static double mp_vector_init(_cimg_math_parser& mp) { + unsigned int + ptrs = 4U, + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[3]; + switch (mp.opcode[2] - 4) { + case 0 : std::memset(mp.mem._data + ptrd,0,siz*sizeof(double)); break; // 0 values given + case 1 : { const double val = _mp_arg(ptrs); while (siz-->0) mp.mem[ptrd++] = val; } break; + default : while (siz-->0) { mp.mem[ptrd++] = _mp_arg(ptrs++); if (ptrs>=mp.opcode[2]) ptrs = 4U; } + } + return cimg::type::nan(); + } + + static double mp_vector_eq(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(4) + 1; + unsigned int p1 = (unsigned int)mp.opcode[3], p2 = (unsigned int)mp.opcode[5], n; + const int N = (int)_mp_arg(6); + const bool case_sensitive = (bool)_mp_arg(7); + bool still_equal = true; + double value; + if (!N) return true; + + // Compare all values. + if (N<0) { + if (p1>0 && p2>0) { // Vector == vector + if (p1!=p2) return false; + if (case_sensitive) + while (still_equal && p1--) still_equal = *(ptr1++)==*(ptr2++); + else + while (still_equal && p1--) + still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p1--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p2--) still_equal = *(ptr2++)==value; + return still_equal; + } else { // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + else return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + } + + // Compare only first N values. + if (p1>0 && p2>0) { // Vector == vector + n = cimg::min((unsigned int)N,p1,p2); + if (case_sensitive) + while (still_equal && n--) still_equal = *(ptr1++)==(*ptr2++); + else + while (still_equal && n--) still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + n = std::min((unsigned int)N,p1); + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + n = std::min((unsigned int)N,p2); + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr2++)==value; + return still_equal; + } // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + + static double mp_vector_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + return off>=0 && off<(int)siz?mp.mem[ptr + off]:cimg::type::nan(); + } + + static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[5] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(4); + l_opcode[2] = mp.opcode[4]; // Scalar argument1 + l_opcode.swap(mp.opcode); + ulongT &argument2 = mp.opcode[3]; + while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_v(_cimg_math_parser& mp) { // Operator(vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode.swap(mp.opcode); + ulongT &argument = mp.opcode[2]; + while (siz-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_vs(_cimg_math_parser& mp) { // Operator(vector,scalar) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg 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); + l_opcode.swap(mp.opcode); + ulongT &argument1 = mp.opcode[2], &argument2 = mp.opcode[3]; + while (siz-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_neq(_cimg_math_parser& mp) { + return !mp_vector_eq(mp); + } + + 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); + const ulongT *ptrs = mp.opcode._data + 5; + 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); + unsigned int count = 0; + while (siz-->0) { + if (count>=64 && siz>=64) { + std::fprintf(cimg::output(),"...,"); + ptr = (unsigned int)mp.opcode[1] + 1 + siz0 - 64; + siz = 64; + } else std::fprintf(cimg::output(),"%g%s",mp.mem[ptr++],siz?",":""); + ++count; + } + if (print_string) { + CImg str(siz0 + 1); + ptr = (unsigned int)mp.opcode[1] + 1; + for (unsigned int k = 0; k::nan(); + } + + static double mp_vector_resize(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const unsigned int p1 = (unsigned int)mp.opcode[2], p2 = (unsigned int)mp.opcode[4]; + const int + interpolation = (int)_mp_arg(5), + boundary_conditions = (int)_mp_arg(6); + if (p2) { // Resize vector + const double *const ptrs = &_mp_arg(3) + 1; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p2,1,1,1,true). + get_resize(p1,1,1,1,interpolation,boundary_conditions); + } else { // Resize scalar + const double value = _mp_arg(3); + CImg(ptrd,p1,1,1,1,true) = CImg(1,1,1,1,value).resize(p1,1,1,1,interpolation, + boundary_conditions); + } + 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; + const unsigned int p1 = (unsigned int)mp.opcode[3]; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p1,1,1,1,true).get_mirror('x'); + return cimg::type::nan(); + } + + static double mp_vector_set_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + if (off>=0 && off<(int)siz) mp.mem[ptr + off] = _mp_arg(5); + return _mp_arg(5); + } + + static double mp_vtos(_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]; + 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); + } + 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); + } else { // Scalar expression + str.assign(sizd + 1); + cimg_snprintf(str,sizd + 1,format,_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_while(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_cond = ++mp.p_code, + *const p_body = p_cond + mp.opcode[3], + *const p_end = p_body + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + bool is_cond = false; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) // Evaluate body + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double x = _mp_arg(2), y = _mp_arg(3), z = _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], + oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), z = oz + _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + +#undef _mp_arg + + }; // struct _cimg_math_parser {} + +#define _cimg_create_pointwise_functions(name,func,min_size) \ + CImg& name() { \ + if (is_empty()) return *this; \ + cimg_openmp_for(*this,func((double)*ptr),min_size); \ + return *this; \ + } \ + CImg get_##name() const { \ + return CImg(*this,false).name(); \ + } + + //! Compute the square value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square value \f$I_{(x,y,z,c)}^2\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqr().normalize(0,255)).display(); + \endcode + \image html ref_sqr.jpg + **/ + _cimg_create_pointwise_functions(sqr,cimg::sqr,524288) + + //! Compute the square root of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square root \f$\sqrt{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqrt().normalize(0,255)).display(); + \endcode + \image html ref_sqrt.jpg + **/ + _cimg_create_pointwise_functions(sqrt,std::sqrt,8192) + + //! Compute the exponential of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its exponential \f$e^{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(exp,std::exp,4096) + + //! Compute the logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its logarithm + \f$\mathrm{log}_{e}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log,std::log,262144) + + //! Compute the base-2 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-2 logarithm + \f$\mathrm{log}_{2}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log2,cimg::log2,4096) + + //! Compute the base-10 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-10 logarithm + \f$\mathrm{log}_{10}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log10,std::log10,4096) + + //! Compute the absolute value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its absolute value \f$|I_{(x,y,z,c)}|\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(abs,cimg::abs,524288) + + //! Compute the sign of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sign + \f$\mathrm{sign}(I_{(x,y,z,c)})\f$. + \note + - The sign is set to: + - \c 1 if pixel value is strictly positive. + - \c -1 if pixel value is strictly negative. + - \c 0 if pixel value is equal to \c 0. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sign,cimg::sign,32768) + + //! Compute the cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its cosine \f$\cos(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cos,std::cos,8192) + + //! Compute the sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sine \f$\sin(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sin,std::sin,8192) + + //! Compute the sinc of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sinc + \f$\mathrm{sinc}(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinc,cimg::sinc,2048) + + //! Compute the tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its tangent \f$\tan(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tan,std::tan,2048) + + //! Compute the hyperbolic cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic cosine + \f$\mathrm{cosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cosh,std::cosh,2048) + + //! Compute the hyperbolic sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic sine + \f$\mathrm{sinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinh,std::sinh,2048) + + //! Compute the hyperbolic tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic tangent + \f$\mathrm{tanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tanh,std::tanh,2048) + + //! Compute the arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosine + \f$\mathrm{acos}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acos,std::acos,8192) + + //! Compute the arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arcsine + \f$\mathrm{asin}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asin,std::asin,8192) + + //! Compute the arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent + \f$\mathrm{atan}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atan,std::atan,8192) + + //! Compute the arctangent2 of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent2 + \f$\mathrm{atan2}(I_{(x,y,z,c)})\f$. + \param img Image whose pixel values specify the second argument of the \c atan2() function. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img_y,img_atan2).display(); + \endcode + **/ + template + CImg& atan2(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return atan2(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_atan2(const CImg& img) const { + return CImg(*this,false).atan2(img); + } + + //! Compute the hyperbolic arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosineh + \f$\mathrm{acosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acosh,cimg::acosh,8192) + + //! Compute the hyperbolic arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arcsine + \f$\mathrm{asinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asinh,cimg::asinh,8192) + + //! Compute the hyperbolic arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arctangent + \f$\mathrm{atanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atanh,cimg::atanh,8192) + + //! In-place pointwise multiplication. + /** + Compute the pointwise multiplication between the image instance and the specified input image \c img. + \param img Input image, as the second operand of the multiplication. + \note + - Similar to operator+=(const CImg&), except that it performs a pointwise multiplication + instead of an addition. + - It does \e not perform a \e matrix multiplication. For this purpose, use operator*=(const CImg&) instead. + \par Example + \code + CImg + img("reference.jpg"), + shade(img.width,img.height(),1,1,"-(x-w/2)^2-(y-h/2)^2",false); + shade.normalize(0,1); + (img,shade,img.get_mul(shade)).display(); + \endcode + **/ + template + CImg& mul(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return mul(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_mul(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).mul(img); + } + + //! In-place pointwise division. + /** + Similar to mul(const CImg&), except that it performs a pointwise division instead of a multiplication. + **/ + template + CImg& div(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return div(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_div(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).div(img); + } + + //! Raise each pixel value to a specified power. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its power \f$I_{(x,y,z,c)}^p\f$. + \param p Exponent value. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img1,img2).display(); + \endcode + **/ + CImg& pow(const double p) { + if (is_empty()) 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.5) return sqrt(); + if (p==1) return *this; + if (p==2) return sqr(); + 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; + } + + //! Raise each pixel value to a specified power \newinstance. + CImg get_pow(const double p) const { + return CImg(*this,false).pow(p); + } + + //! Raise each pixel value to a power, specified from an expression. + /** + Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition. + **/ + CImg& pow(const char *const expression) { + return pow((+*this)._fill(expression,true,1,0,0,"pow",this)); + } + + //! Raise each pixel value to a power, specified from an expression \newinstance. + CImg get_pow(const char *const expression) const { + return CImg(*this,false).pow(expression); + } + + //! Raise each pixel value to a power, pointwisely specified from another image. + /** + Similar to operator+=(const CImg& img), except that it performs an exponentiation instead of an addition. + **/ + template + CImg& pow(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return pow(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_pow(const CImg& img) const { + return CImg(*this,false).pow(img); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(unsigned int), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::rol(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const unsigned int n=1) const { + return (+*this).rol(n); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const char *const expression) { + return rol((+*this)._fill(expression,true,1,0,0,"rol",this)); + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const char *const expression) const { + return (+*this).rol(expression); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const CImg&), except that it performs a left rotation instead of a left shift. + **/ + template + CImg& rol(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return rol(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_rol(const CImg& img) const { + return (+*this).rol(img); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(unsigned int), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::ror(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const unsigned int n=1) const { + return (+*this).ror(n); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const char *const expression) { + return ror((+*this)._fill(expression,true,1,0,0,"ror",this)); + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const char *const expression) const { + return (+*this).ror(expression); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const CImg&), except that it performs a right rotation instead of a right shift. + **/ + template + CImg& ror(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return ror(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_ror(const CImg& img) const { + return (+*this).ror(img); + } + + //! Pointwise min operator between instance image and a value. + /** + \param val Value used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& min(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).min(value); + } + + //! Pointwise min operator between two images. + /** + \param img Image used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& min(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return min(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_min(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).min(img); + } + + //! Pointwise min operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& min(const char *const expression) { + return min((+*this)._fill(expression,true,1,0,0,"min",this)); + } + + //! Pointwise min operator between an image and an expression \newinstance. + CImg get_min(const char *const expression) const { + return CImg(*this,false).min(expression); + } + + //! Pointwise max operator between instance image and a value. + /** + \param val Value used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& max(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).max(value); + } + + //! Pointwise max operator between two images. + /** + \param img Image used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& max(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return max(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_max(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).max(img); + } + + //! Pointwise max operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& max(const char *const expression) { + return max((+*this)._fill(expression,true,1,0,0,"max",this)); + } + + //! Pointwise max operator between an image and an expression \newinstance. + CImg get_max(const char *const expression) const { + return CImg(*this,false).max(expression); + } + + //! Return a reference to the minimum pixel value. + /** + **/ + T& min() { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min; + cimg_for(*this,ptrs,T) if (*ptrsmax_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the maximum pixel value \const. + const T& max() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max; + cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value. + /** + \param[out] max_val Maximum pixel value. + **/ + template + T& min_max(t& max_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value \const. + template + const T& min_max(t& max_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + const T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the maximum pixel value as well as the minimum pixel value. + /** + \param[out] min_val Minimum pixel value. + **/ + template + T& max_min(t& min_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val arr(*this,false); + ulongT l = 0, ir = size() - 1; + for ( ; ; ) { + if (ir<=l + 1) { + if (ir==l + 1 && arr[ir]>1; + cimg::swap(arr[mid],arr[l + 1]); + if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]); + if (arr[l + 1]>arr[ir]) cimg::swap(arr[l + 1],arr[ir]); + if (arr[l]>arr[l + 1]) cimg::swap(arr[l],arr[l + 1]); + ulongT i = l + 1, j = ir; + const T pivot = arr[l + 1]; + for ( ; ; ) { + do ++i; while (arr[i]pivot); + if (j=k) ir = j - 1; + if (j<=k) l = i; + } + } + } + + //! Return the median pixel value. + /** + **/ + T median() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "median(): Empty instance.", + cimg_instance); + const ulongT s = size(); + switch (s) { + case 1 : return _data[0]; + case 2 : return cimg::median(_data[0],_data[1]); + case 3 : return cimg::median(_data[0],_data[1],_data[2]); + case 5 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4]); + case 7 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6]); + case 9 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8]); + case 13 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8], + _data[9],_data[10],_data[11],_data[12]); + } + const T res = kth_smallest(s>>1); + return (s%2)?res:(T)((res + kth_smallest((s>>1) - 1))/2); + } + + //! Return the product of all the pixel values. + /** + **/ + double product() const { + if (is_empty()) return 0; + double res = 1; + cimg_for(*this,ptrs,T) res*=(double)*ptrs; + return res; + } + + //! Return the sum of all the pixel values. + /** + **/ + double sum() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res; + } + + //! Return the average pixel value. + /** + **/ + double mean() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res/size(); + } + + //! Return the variance of the pixel values. + /** + \param variance_method Method used to estimate the variance. Can be: + - \c 0: Second moment, computed as + \f$1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2 = + 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right)\f$ + with \f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$. + - \c 1: Best unbiased estimator, computed as \f$\frac{1}{N - 1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 \f$. + - \c 2: Least median of squares. + - \c 3: Least trimmed of squares. + **/ + double variance(const unsigned int variance_method=1) const { + double foo; + return variance_mean(variance_method,foo); + } + + //! Return the variance as well as the average of the pixel values. + /** + \param variance_method Method used to estimate the variance (see variance(const unsigned int) const). + \param[out] mean Average pixel value. + **/ + template + double variance_mean(const unsigned int variance_method, t& mean) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_mean(): Empty instance.", + cimg_instance); + + double variance = 0, average = 0; + const ulongT siz = size(); + switch (variance_method) { + case 0 : { // Least mean square (standard definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = (S2 - S*S/siz)/siz; + average = S; + } break; + case 1 : { // Least mean square (robust definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + average = S; + } break; + case 2 : { // Least Median of Squares (MAD) + CImg buf(*this,false); + buf.sort(); + const ulongT siz2 = siz>>1; + const double med_i = (double)buf[siz2]; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val; + } + buf.sort(); + const double sig = (double)(1.4828*buf[siz2]); + variance = sig*sig; + } break; + default : { // Least trimmed of Squares + CImg buf(*this,false); + const ulongT siz2 = siz>>1; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val; + } + buf.sort(); + double a = 0; + const Tfloat *ptrs = buf._data; + for (ulongT j = 0; j0?variance:0; + } + + //! Return estimated variance of the noise. + /** + \param variance_method Method used to compute the variance (see variance(const unsigned int) const). + \note Because of structures such as edges in images it is + recommanded to use a robust variance estimation. The variance of the + noise is estimated by computing the variance of the Laplacian \f$(\Delta + I)^2 \f$ scaled by a factor \f$c\f$ insuring \f$ c E[(\Delta I)^2]= + \sigma^2\f$ where \f$\sigma\f$ is the noise variance. + **/ + double variance_noise(const unsigned int variance_method=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_noise(): Empty instance.", + cimg_instance); + + const ulongT siz = size(); + if (!siz || !_data) return 0; + if (variance_method>1) { // Compute a scaled version of the Laplacian + CImg tmp(*this,false); + if (_depth==1) { + 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) { + tmp(x,y,c) = cste*((double)Inc + (double)Ipc + (double)Icn + + (double)Icp - 4*(double)Icc); + } + } + } else { + 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) { + tmp(x,y,z,c) = cste*( + (double)Incc + (double)Ipcc + (double)Icnc + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + } + } + } + return tmp.variance(variance_method); + } + + // Version that doesn't need intermediate images. + double variance = 0, S = 0, S2 = 0; + if (_depth==1) { + const double cste = 1./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 + + (double)Icn + (double)Icp - 4*(double)Icc); + S+=val; S2+=val*val; + } + } else { + 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 * + ((double)Incc + (double)Ipcc + (double)Icnc + + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + S+=val; S2+=val*val; + } + } + if (variance_method) variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + else variance = (S2 - S*S/siz)/siz; + return variance>0?variance:0; + } + + //! Compute the MSE (Mean-Squared Error) between two images. + /** + \param img Image used as the second argument of the MSE operator. + **/ + template + double MSE(const CImg& img) const { + if (img.size()!=size()) + throw CImgArgumentException(_cimg_instance + "MSE(): Instance and specified image (%u,%u,%u,%u,%p) have different dimensions.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + double vMSE = 0; + const t* ptr2 = img._data; + cimg_for(*this,ptr1,T) { + const double diff = (double)*ptr1 - (double)*(ptr2++); + vMSE+=diff*diff; + } + const ulongT siz = img.size(); + if (siz) vMSE/=siz; + return vMSE; + } + + //! Compute the PSNR (Peak Signal-to-Noise Ratio) between two images. + /** + \param img Image used as the second argument of the PSNR operator. + \param max_value Maximum theoretical value of the signal. + **/ + template + double PSNR(const CImg& img, const double max_value=255) const { + const double vMSE = (double)std::sqrt(MSE(img)); + return (vMSE!=0)?(double)(20*std::log10(max_value/vMSE)):(double)(cimg::type::max()); + } + + //! Evaluate math formula. + /** + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. + **/ + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { + return _eval(this,expression,x,y,z,c,list_inputs,list_outputs); + } + + //! Evaluate math formula \const. + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { + return _eval(0,expression,x,y,z,c,list_inputs,list_outputs); + } + + double _eval(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) return 0; + if (!expression[1]) switch (*expression) { // Single-char optimization + case 'w' : return (double)_width; + case 'h' : return (double)_height; + case 'd' : return (double)_depth; + case 's' : return (double)_spectrum; + case 'r' : return (double)_is_shared; + } + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + const double val = mp(x,y,z,c); + mp.end(); + return val; + } + + //! Evaluate math formula. + /** + \param[out] output Contains values of output vector returned by the evaluated expression + (or is empty if the returned type is scalar). + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output 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); + } + + //! 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); + } + + 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=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + output.assign(1,std::max(1U,mp.result_dim)); + mp(x,y,z,c,output._data); + mp.end(); + } + + //! Evaluate math formula on a set of variables. + /** + \param expression Math formula, as a C-string. + \param xyzc Set of values (x,y,z,c) used for the evaluation. + \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. + **/ + 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); + } + + //! 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); + } + + 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 { + 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_pragma_openmp(parallel if (res._height>=512)) + { + _cimg_math_parser + _mp = omp_get_thread_num()?mp:_cimg_math_parser(), + &lmp = omp_get_thread_num()?_mp:mp; + cimg_pragma_openmp(for) + for (unsigned int i = 0; i[min, max, mean, variance, xmin, ymin, zmin, cmin, xmax, ymax, zmax, cmax, sum, product]. + **/ + CImg get_stats(const unsigned int variance_method=1) const { + if (is_empty()) return CImg(); + const ulongT siz = size(); + const longT off_end = (longT)siz; + double S = 0, S2 = 0, P = 1; + longT offm = 0, offM = 0; + T m = *_data, M = m; + + 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) + for (longT off = 0; offlM) { lM = val; loffM = off; } + S+=_val; + S2+=_val*_val; + P*=_val; + } + cimg_pragma_openmp(critical(get_stats)) { + if (lmM || (lM==M && loffM1?(S2 - S*S/siz)/(siz - 1):0): + variance(variance_method)), + variance_value = _variance_value>0?_variance_value:0; + int + xm = 0, ym = 0, zm = 0, cm = 0, + xM = 0, yM = 0, zM = 0, cM = 0; + contains(_data[offm],xm,ym,zm,cm); + contains(_data[offM],xM,yM,zM,cM); + return CImg(1,14).fill((double)m,(double)M,mean_value,variance_value, + (double)xm,(double)ym,(double)zm,(double)cm, + (double)xM,(double)yM,(double)zM,(double)cM, + S,P); + } + + //! Compute statistics vector from the pixel values \inplace. + CImg& stats(const unsigned int variance_method=1) { + return get_stats(variance_method).move_to(*this); + } + + //@} + //------------------------------------- + // + //! \name Vector / Matrix Operations + //@{ + //------------------------------------- + + //! Compute norm of the image, viewed as a matrix. + /** + \param magnitude_type Norm type. Can be: + - \c -1: Linf-norm + - \c 0: L0-norm + - \c 1: L1-norm + - \c 2: L2-norm + **/ + double magnitude(const int magnitude_type=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "magnitude(): Empty instance.", + cimg_instance); + double res = 0; + switch (magnitude_type) { + case -1 : { + cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } + } break; + case 1 : { + cimg_for(*this,ptrs,T) res+=(double)cimg::abs(*ptrs); + } break; + default : { + cimg_for(*this,ptrs,T) res+=(double)cimg::sqr(*ptrs); + res = (double)std::sqrt(res); + } + } + return res; + } + + //! Compute the trace of the image, viewed as a matrix. + /** + **/ + double trace() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "trace(): Empty instance.", + cimg_instance); + double res = 0; + cimg_forX(*this,k) res+=(double)(*this)(k,k); + return res; + } + + //! Compute the determinant of the image, viewed as a matrix. + /** + **/ + double det() const { + if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "det(): Instance is not a square matrix.", + cimg_instance); + + switch (_width) { + case 1 : return (double)((*this)(0,0)); + case 2 : return (double)((*this)(0,0))*(double)((*this)(1,1)) - (double)((*this)(0,1))*(double)((*this)(1,0)); + case 3 : { + const double + a = (double)_data[0], d = (double)_data[1], g = (double)_data[2], + b = (double)_data[3], e = (double)_data[4], h = (double)_data[5], + c = (double)_data[6], f = (double)_data[7], i = (double)_data[8]; + return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e; + } + default : { + CImg lu(*this,false); + CImg indx; + bool d; + lu._LU(indx,d); + double res = d?(double)1:(double)-1; + cimg_forX(lu,i) res*=lu(i,i); + return res; + } + } + } + + //! Compute the dot product between instance and argument, viewed as matrices. + /** + \param img Image used as a second argument of the dot product. + **/ + template + 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 get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + CImg res; + if (res._height!=_spectrum) res.assign(1,_spectrum); + const ulongT whd = (ulongT)_width*_height*_depth; + const T *ptrs = data(x,y,z); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get (square) matrix-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \note - The spectrum() of the image must be a square. + **/ + CImg get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const { + const int n = (int)cimg::round(std::sqrt((double)_spectrum)); + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(n,n); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get tensor-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + CImg get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + if (_spectrum==6) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd),*(ptrs + 3*whd),*(ptrs + 4*whd),*(ptrs + 5*whd)); + if (_spectrum==3) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd)); + return tensor(*ptrs); + } + + //! Set vector-valued pixel at specified position. + /** + \param vec Vector to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_vector_at(const CImg& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) { + if (x<_width && y<_height && z<_depth) { + const t *ptrs = vec._data; + const ulongT whd = (ulongT)_width*_height*_depth; + T *ptrd = data(x,y,z); + for (unsigned int k = std::min((unsigned int)vec.size(),_spectrum); k; --k) { + *ptrd = (T)*(ptrs++); ptrd+=whd; + } + } + return *this; + } + + //! Set (square) matrix-valued pixel at specified position. + /** + \param mat Matrix to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_matrix_at(const CImg& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + return set_vector_at(mat,x,y,z); + } + + //! Set tensor-valued pixel at specified position. + /** + \param ten Tensor to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_tensor_at(const CImg& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + T *ptrd = data(x,y,z,0); + const ulongT siz = (ulongT)_width*_height*_depth; + if (ten._height==2) { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[3]; + } + else { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[2]; ptrd+=siz; + *ptrd = (T)ten[4]; ptrd+=siz; + *ptrd = (T)ten[5]; ptrd+=siz; + *ptrd = (T)ten[8]; + } + return *this; + } + + //! Unroll pixel values along axis \c y. + /** + \note Equivalent to \code unroll('y'); \endcode. + **/ + CImg& 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. + **/ + CImg& diagonal() { + return get_diagonal().move_to(*this); + } + + //! Resize image to become a diagonal matrix \newinstance. + CImg get_diagonal() const { + if (is_empty()) return *this; + const unsigned int siz = (unsigned int)size(); + CImg res(siz,siz,1,1,0); + cimg_foroff(*this,off) res((unsigned int)off,(unsigned int)off) = (*this)[off]; + return res; + } + + //! Replace the image by an identity matrix. + /** + \note If the instance image is not square, it is resized to a square matrix using its maximum + dimension as a reference. + **/ + CImg& identity_matrix() { + return identity_matrix(std::max(_width,_height)).move_to(*this); + } + + //! Replace the image by an identity matrix \newinstance. + CImg get_identity_matrix() const { + return identity_matrix(std::max(_width,_height)); + } + + //! Fill image with a linear sequence of values. + /** + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + CImg& sequence(const T& a0, const T& a1) { + if (is_empty()) return *this; + const ulongT siz = size() - 1; + T* ptr = _data; + if (siz) { + const double delta = (double)a1 - (double)a0; + cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz); + } else *ptr = a0; + return *this; + } + + //! Fill image with a linear sequence of values \newinstance. + CImg get_sequence(const T& a0, const T& a1) const { + return (+*this).sequence(a0,a1); + } + + //! Transpose the image, viewed as a matrix. + /** + \note Equivalent to \code permute_axes("yxzc"); \endcode. + **/ + CImg& transpose() { + if (_width==1) { _width = _height; _height = 1; return *this; } + if (_height==1) { _height = _width; _width = 1; return *this; } + if (_width==_height) { + cimg_forYZC(*this,y,z,c) for (int x = y; x get_transpose() const { + return get_permute_axes("yxzc"); + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors. + /** + \param img Image used as the second argument of the cross product. + \note The first argument of the cross product is \c *this. + **/ + template + 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.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + + const T x = (*this)[0], y = (*this)[1], z = (*this)[2]; + (*this)[0] = (T)(y*img[2] - z*img[1]); + (*this)[1] = (T)(z*img[0] - x*img[2]); + (*this)[2] = (T)(x*img[1] - y*img[0]); + return *this; + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors \newinstance. + template + CImg<_cimg_Tt> get_cross(const CImg& img) const { + return CImg<_cimg_Tt>(*this).cross(img); + } + + //! Invert the instance image, viewed as a matrix. + /** + \param use_LU Choose the inverting algorithm. Can be: + - \c true: LU-based matrix inversion. + - \c false: SVD-based matrix inversion. + **/ + CImg& invert(const bool use_LU=true) { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "invert(): Instance is not a square matrix.", + cimg_instance); +#ifdef cimg_use_lapack + int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N]; + Tfloat + *const lapA = new Tfloat[N*N], + *const WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N + l] = (Tfloat)((*this)(k,l)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + else { + cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetri_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N + l]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] WORK; +#else + const double dete = _width>3?-1.: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. && _width==3) { + const double + a = _data[0], d = _data[1], g = _data[2], + b = _data[3], e = _data[4], h = _data[5], + c = _data[6], f = _data[7], i = _data[8]; + _data[0] = (T)((i*e - f*h)/dete), _data[1] = (T)((g*f - i*d)/dete), _data[2] = (T)((d*h - g*e)/dete); + _data[3] = (T)((h*c - i*b)/dete), _data[4] = (T)((i*a - c*g)/dete), _data[5] = (T)((g*b - a*h)/dete); + _data[6] = (T)((b*f - e*c)/dete), _data[7] = (T)((d*c - a*f)/dete), _data[8] = (T)((a*e - d*b)/dete); + } else { + if (use_LU) { // LU-based inverse computation + CImg A(*this,false), indx, col(1,_width); + bool d; + A._LU(indx,d); + cimg_forX(*this,j) { + col.fill(0); + col(j) = 1; + col._solve(A,indx); + cimg_forX(*this,i) (*this)(j,i) = (T)col(i); + } + } else { // SVD-based inverse computation + CImg U(_width,_width), S(1,_width), V(_width,_width); + SVD(U,S,V,false); + U.transpose(); + cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k]; + S.diagonal(); + *this = V*S*U; + } + } +#endif + return *this; + } + + //! Invert the instance image, viewed as a matrix \newinstance. + CImg get_invert(const bool use_LU=true) const { + return CImg(*this,false).invert(use_LU); + } + + //! 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 { + 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(); + cimg_forX(V,x) { + const Tfloat s = S(x), invs = s>tolerance?1/s:0; + cimg_forY(V,y) V(x,y)*=invs; + } + return V*U.transpose(); + } + + //! Solve a system of linear equations. + /** + \param A Matrix of the linear system. + \note Solve \c AX=B where \c B=*this. + **/ + template + CImg& solve(const CImg& A) { + if (_depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "solve(): Instance and specified matrix (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + if (A._width==A._height) { // Classical linear system + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } +#ifdef cimg_use_lapack + char TRANS = 'N'; + int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N]; + Ttfloat + *const lapA = new Ttfloat[N*N], + *const lapB = new Ttfloat[N], + *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*N + l] = (Ttfloat)(A(k,l)); + cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + + if (!INFO) { + cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrs_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK; +#else + CImg lu(A,false); + CImg indx; + bool d; + lu._LU(indx,d); + _solve(lu,indx); +#endif + } else { // Least-square solution for non-square systems +#ifdef cimg_use_lapack + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } + char TRANS = 'N'; + int INFO, N = A._width, M = A._height, LWORK = -1, LDA = M, LDB = M, NRHS = _width; + Ttfloat WORK_QUERY; + Ttfloat + * const lapA = new Ttfloat[M*N], + * const lapB = new Ttfloat[M*NRHS]; + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, &WORK_QUERY, LWORK, INFO); + LWORK = (int) WORK_QUERY; + Ttfloat *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*M + l] = (Ttfloat)(A(k,l)); + cimg_forXY(*this,k,l) lapB[k*M + l] = (Ttfloat)((*this)(k,l)); + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, WORK, LWORK, INFO); + if (INFO != 0) + cimg::warn(_cimg_instance + "solve(): LAPACK library function sgels() returned error code %d.", + cimg_instance, + INFO); + assign(NRHS, N); + if (!INFO) + cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l]; + else + assign(A.get_pseudoinvert()*(*this)); + delete[] lapA; delete[] lapB; delete[] WORK; +#else + assign(A.get_pseudoinvert()*(*this)); +#endif + } + return *this; + } + + //! Solve a system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve(A); + } + + template + CImg& _solve(const CImg& A, const CImg& indx) { + typedef _cimg_Ttfloat Ttfloat; + const int N = (int)size(); + 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; + (*this)(i) = (T)sum; + } + for (int i = N - 1; i>=0; --i) { + sum = (*this)(i); + for (int j = i + 1; j + CImg& solve_tridiagonal(const CImg& A) { + const unsigned int siz = (unsigned int)size(); + if (A._width!=3 || A._height!=siz) + throw CImgArgumentException(_cimg_instance + "solve_tridiagonal(): Instance and tridiagonal matrix " + "(%u,%u,%u,%u,%p) have incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + const Ttfloat epsilon = 1e-4f; + CImg B = A.get_column(1), V(*this,false); + for (int i = 1; i<(int)siz; ++i) { + const Ttfloat m = A(0,i)/(B[i - 1]?B[i - 1]:epsilon); + B[i] -= m*A(2,i - 1); + V[i] -= m*V[i - 1]; + } + (*this)[siz - 1] = (T)(V[siz - 1]/(B[siz - 1]?B[siz - 1]:epsilon)); + for (int i = (int)siz - 2; i>=0; --i) (*this)[i] = (T)((V[i] - A(2,i)*(*this)[i + 1])/(B[i]?B[i]:epsilon)); + return *this; + } + + //! Solve a tridiagonal system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(A); + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + const CImg& eigen(CImg& val, CImg &vec) const { + if (is_empty()) { val.assign(); vec.assign(); } + else { + if (_width!=_height || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "eigen(): Instance is not a square matrix.", + cimg_instance); + + if (val.size()<(ulongT)_width) val.assign(1,_width); + if (vec.size()<(ulongT)_width*_width) vec.assign(_width,_width); + switch (_width) { + case 1 : { val[0] = (t)(*this)[0]; vec[0] = (t)1; } break; + case 2 : { + const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a + d; + double f = e*e - 4*(a*d - b*c); + if (f<0) + cimg::warn(_cimg_instance + "eigen(): Complex eigenvalues found.", + cimg_instance); + + f = std::sqrt(f); + const double + l1 = 0.5*(e - f), + l2 = 0.5*(e + f), + b2 = b*b, + norm1 = std::sqrt(cimg::sqr(l2 - a) + b2), + norm2 = std::sqrt(cimg::sqr(l1 - a) + b2); + val[0] = (t)l2; + val[1] = (t)l1; + if (norm1>0) { vec(0,0) = (t)(b/norm1); vec(0,1) = (t)((l2 - a)/norm1); } else { vec(0,0) = 1; vec(0,1) = 0; } + if (norm2>0) { vec(1,0) = (t)(b/norm2); vec(1,1) = (t)((l1 - a)/norm2); } else { vec(1,0) = 1; vec(1,1) = 0; } + } break; + default : + throw CImgInstanceException(_cimg_instance + "eigen(): Eigenvalues computation of general matrices is limited " + "to 2x2 matrices.", + cimg_instance); + } + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \return A list of two images [val; vec], whose meaning is similar as in eigen(CImg&,CImg&) const. + **/ + CImgList get_eigen() const { + CImgList res(2); + eigen(res[0],res[1]); + return res; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + 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); + + 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 + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \return A list of two images [val; vec], whose meaning are similar as in + symmetric_eigen(CImg&,CImg&) const. + **/ + CImgList get_symmetric_eigen() const { + CImgList res(2); + symmetric_eigen(res[0],res[1]); + return res; + } + + //! Sort pixel values and get sorting permutations. + /** + \param[out] permutations Permutation map used for the sorting. + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + **/ + template + CImg& sort(CImg& permutations, const bool is_increasing=true) { + permutations.assign(_width,_height,_depth,_spectrum); + if (is_empty()) return *this; + cimg_foroff(permutations,off) permutations[off] = (t)off; + return _quicksort(0,size() - 1,permutations,is_increasing,true); + } + + //! Sort pixel values and get sorting permutations \newinstance. + template + CImg get_sort(CImg& permutations, const bool is_increasing=true) const { + return (+*this).sort(permutations,is_increasing); + } + + //! Sort pixel values. + /** + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + \param axis Tells if the value sorting must be done along a specific axis. Can be: + - \c 0: All pixel values are sorted, independently on their initial position. + - \c 'x': Image columns are sorted, according to the first value in each column. + - \c 'y': Image rows are sorted, according to the first value in each row. + - \c 'z': Image slices are sorted, according to the first value in each slice. + - \c 'c': Image channels are sorted, according to the first value in each channel. + **/ + CImg& sort(const bool is_increasing=true, const char axis=0) { + if (is_empty()) return *this; + CImg perm; + switch (cimg::lowercase(axis)) { + case 0 : + _quicksort(0,size() - 1,perm,is_increasing,false); + break; + case 'x' : { + perm.assign(_width); + get_crop(0,0,0,0,_width - 1,0,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(perm[x],y,z,c); + } break; + case 'y' : { + perm.assign(_height); + get_crop(0,0,0,0,0,_height - 1,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,perm[y],z,c); + } break; + case 'z' : { + perm.assign(_depth); + get_crop(0,0,0,0,0,0,_depth - 1,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,perm[z],c); + } break; + case 'c' : { + perm.assign(_spectrum); + get_crop(0,0,0,0,0,0,0,_spectrum - 1).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,z,perm[c]); + } break; + default : + throw CImgArgumentException(_cimg_instance + "sort(): Invalid specified axis '%c' " + "(should be { x | y | z | c }).", + cimg_instance,axis); + } + return *this; + } + + //! Sort pixel values \newinstance. + CImg get_sort(const bool is_increasing=true, const char axis=0) const { + return (+*this).sort(is_increasing,axis); + } + + template + CImg& _quicksort(const long indm, const long indM, CImg& permutations, + const bool is_increasing, const bool is_permutations) { + if (indm(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]>(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]>(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } else { + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]<(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } + if (indM - indm>=3) { + const T pivot = (*this)[mid]; + long i = indm, j = indM; + if (is_increasing) { + do { + while ((*this)[i]pivot) --j; + if (i<=j) { + if (is_permutations) cimg::swap(permutations[i],permutations[j]); + cimg::swap((*this)[i++],(*this)[j--]); + } + } while (i<=j); + } else { + do { + while ((*this)[i]>pivot) ++i; + while ((*this)[j] A; // Input matrix (assumed to contain some values) + CImg<> U,S,V; + A.SVD(U,S,V) + \endcode + **/ + 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 { + if (is_empty()) { U.assign(); S.assign(); V.assign(); } + else { + U = *this; + if (lambda!=0) { + const unsigned int delta = std::min(U._width,U._height); + for (unsigned int i = 0; i rv1(_width); + t anorm = 0, c, f, g = 0, h, s, scale = 0; + int l = 0, nm = 0; + + cimg_forX(U,i) { + l = i + 1; rv1[i] = scale*g; g = s = scale = 0; + if (i=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; --i) { + if (i=0; --i) { + l = i + 1; g = S[i]; + for (int j = l; j=0; --k) { + for (unsigned int its = 0; its=1; --l) { + nm = l - 1; + if ((cimg::abs(rv1[l]) + anorm)==anorm) { flag = false; break; } + if ((cimg::abs(S[nm]) + anorm)==anorm) break; + } + if (flag) { + c = 0; s = 1; + for (int i = l; i<=k; ++i) { + f = s*rv1[i]; rv1[i] = c*rv1[i]; + if ((cimg::abs(f) + anorm)==anorm) break; + g = S[i]; h = cimg::_hypot(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h; + cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c + z*s; U(i,j) = z*c - y*s; } + } + } + + const t z = S[k]; + if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; } + nm = k - 1; + t x = S[l], y = S[nm]; + g = rv1[nm]; h = rv1[k]; + f = ((y - z)*(y + z)+(g - h)*(g + h))/std::max((t)1e-25,2*h*y); + g = cimg::_hypot(f,(t)1); + f = ((x - z)*(x + z)+h*((y/(f + (f>=0?g:-g))) - h))/std::max((t)1e-25,x); + c = s = 1; + for (int j = l; j<=nm; ++j) { + const int i = j + 1; + g = rv1[i]; h = s*g; g = c*g; + t y = S[i]; + t z = cimg::_hypot(f,h); + rv1[j] = z; c = f/std::max((t)1e-25,z); s = h/std::max((t)1e-25,z); + f = x*c + g*s; g = g*c - x*s; h = y*s; y*=c; + cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c + z*s; V(i,jj) = z*c - x*s; } + z = cimg::_hypot(f,h); S[j] = z; + if (z) { z = 1/std::max((t)1e-25,z); c = f*z; s = h*z; } + f = c*g + s*y; x = c*y - s*g; + cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c + z*s; U(i,jj) = z*c - y*s; } + } + rv1[l] = 0; rv1[k]=f; S[k]=x; + } + } + + if (sorting) { + CImg permutations; + CImg tmp(_width); + S.sort(permutations,false); + cimg_forY(U,k) { + cimg_forY(permutations,y) tmp(y) = U(permutations(y),k); + std::memcpy(U.data(0,k),tmp._data,sizeof(t)*_width); + } + cimg_forY(V,k) { + cimg_forY(permutations,y) tmp(y) = V(permutations(y),k); + std::memcpy(V.data(0,k),tmp._data,sizeof(t)*_width); + } + } + } + return *this; + } + + //! Compute the SVD of the instance image, viewed as a general matrix. + /** + \return A list of three images [U; S; V], whose meaning is similar as in + SVD(CImg&,CImg&,CImg&,bool,unsigned int,float) const. + **/ + CImgList get_SVD(const bool sorting=true, + const unsigned int max_iteration=40, const float lambda=0) const { + CImgList res(3); + SVD(res[0],res[1],res[2],sorting,max_iteration,lambda); + return res; + } + + // [internal] Compute the LU decomposition of a permuted matrix. + template + CImg& _LU(CImg& indx, bool& d) { + const int N = width(); + int imax = 0; + CImg vv(N); + indx.assign(N); + d = true; + cimg_forX(*this,i) { + Tfloat vmax = 0; + cimg_forX(*this,j) { + const Tfloat tmp = cimg::abs((*this)(j,i)); + if (tmp>vmax) vmax = tmp; + } + if (vmax==0) { indx.fill(0); return fill(0); } + vv[i] = 1/vmax; + } + cimg_forX(*this,j) { + for (int i = 0; i=vmax) { vmax=tmp; imax=i; } + } + if (j!=imax) { + cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j)); + d =!d; + vv[imax] = vv[j]; + } + indx[j] = (t)imax; + if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20; + if (j + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + 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 " + "than number of nodes %u.", + pixel_type(),starting_node,nb_nodes); + CImg dist(1,nb_nodes,1,1,cimg::type::max()); + dist(starting_node) = 0; + previous_node.assign(1,nb_nodes,1,1,(t)-1); + previous_node(starting_node) = (t)starting_node; + CImg Q(nb_nodes); + cimg_forX(Q,u) Q(u) = (unsigned int)u; + cimg::swap(Q(starting_node),Q(0)); + unsigned int sizeQ = nb_nodes; + while (sizeQ) { + // Update neighbors from minimal vertex + const unsigned int umin = Q(0); + if (umin==ending_node) sizeQ = 0; + else { + const T dmin = dist(umin); + const T infty = cimg::type::max(); + for (unsigned int q = 1; qdist(Q(left))) || + (rightdist(Q(right)));) { + if (right + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + const unsigned int starting_node, const unsigned int ending_node=~0U) { + CImg foo; + return dijkstra(distance,nb_nodes,starting_node,ending_node,foo); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + /** + \param starting_node Indice of the starting node. + \param ending_node Indice of the ending node. + \param previous_node Array that gives the previous node indice in the path to the starting node + (optional parameter). + \return Array of distances of each node to the starting node. + \note image instance corresponds to the adjacency matrix of the graph. + **/ + template + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) { + return get_dijkstra(starting_node,ending_node,previous_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + template + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) const { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "dijkstra(): Instance is not a graph adjacency matrix.", + cimg_instance); + + return dijkstra(*this,_width,starting_node,ending_node,previous_node); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) { + return get_dijkstra(starting_node,ending_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const { + CImg foo; + return get_dijkstra(starting_node,ending_node,foo); + } + + //! Return an image containing the Ascii codes of the specified string. + /** + \param str input C-string to encode as an image. + \param is_last_zero Tells if the ending \c '0' character appear in the resulting image. + \param is_shared Return result that shares its buffer with \p str. + **/ + static CImg string(const char *const str, const bool is_last_zero=true, const bool is_shared=false) { + if (!str) return CImg(); + 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 vector(const T& a0) { + CImg r(1,1); + r[0] = a0; + return r; + } + + //! Return a \c 1x2 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + **/ + static CImg vector(const T& a0, const T& a1) { + CImg r(1,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + return r; + } + + //! Return a \c 1x3 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + **/ + static CImg vector(const T& a0, const T& a1, const T& a2) { + CImg r(1,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + return r; + } + + //! Return a \c 1x4 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + \param a3 Fourth vector value. + **/ + static CImg 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; + 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; + 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; + 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; + 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; + 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; + return r; + } + + //! Return a \c 1x10 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) { + 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; + return r; + } + + //! Return a \c 1x11 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) { + 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; + return r; + } + + //! Return a \c 1x12 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) { + 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; + return r; + } + + //! Return a \c 1x13 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) { + 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; + return r; + } + + //! Return a \c 1x14 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) { + 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; + return r; + } + + //! Return a \c 1x15 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) { + 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; + return r; + } + + //! Return a \c 1x16 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) { + 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; + return r; + } + + //! Return a 1x1 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg matrix(const T& a0) { + return vector(a0); + } + + //! Return a 2x2 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, + const T& a2, const T& a3) { + CImg r(2,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + *(ptr++) = a2; *(ptr++) = a3; + return r; + } + + //! Return a 3x3 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + \param a4 Fifth matrix value. + \param a5 Sixth matrix value. + \param a6 Seventh matrix value. + \param a7 Eighth matrix value. + \param a8 Nineth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, const T& a2, + const T& a3, const T& a4, const T& a5, + const T& a6, const T& a7, const T& a8) { + CImg r(3,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; + *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; + return r; + } + + //! Return a 4x4 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13, const T& a14, const T& a15) { + CImg r(4,4); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15; + return r; + } + + //! Return a 5x5 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, + const T& a5, const T& a6, const T& a7, const T& a8, const T& a9, + const T& a10, const T& a11, const T& a12, const T& a13, const T& a14, + const T& a15, const T& a16, const T& a17, const T& a18, const T& a19, + const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) { + CImg r(5,5); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; + *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9; + *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; + *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19; + *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24; + return r; + } + + //! Return a 1x1 symmetric matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg tensor(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 symmetric matrix tensor containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2) { + return matrix(a0,a1,a1,a2); + } + + //! Return a 3x3 symmetric matrix containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { + return matrix(a0,a1,a2,a1,a3,a4,a2,a4,a5); + } + + //! Return a 1x1 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1) { + return matrix(a0,0,0,a1); + } + + //! Return a 3x3 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2) { + return matrix(a0,0,0,0,a1,0,0,0,a2); + } + + //! Return a 4x4 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3) { + return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3); + } + + //! Return a 5x5 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { + return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4); + } + + //! Return a NxN identity matrix. + /** + \param N Dimension of the matrix. + **/ + static CImg identity_matrix(const unsigned int N) { + CImg res(N,N,1,1,0); + cimg_forX(res,x) res(x,x) = 1; + return res; + } + + //! Return a N-numbered sequence vector from \p a0 to \p a1. + /** + \param N Size of the resulting vector. + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + static CImg sequence(const unsigned int N, const T& a0, const T& a1) { + if (N) return CImg(1,N).sequence(a0,a1); + return CImg(); + } + + //! Return a 3x3 rotation matrix from an { axis + angle } or a quaternion. + /** + \param x X-coordinate of the rotation axis, or first quaternion coordinate. + \param y Y-coordinate of the rotation axis, or second quaternion coordinate. + \param z Z-coordinate of the rotation axis, or third quaternion coordinate. + \param w Angle of the rotation axis (in degree), or fourth quaternion coordinate. + \param is_quaternion Tell is the four arguments denotes a set { axis + angle } or a quaternion (x,y,z,w). + **/ + static CImg rotation_matrix(const float x, const float y, const float z, const float w, + const bool is_quaternion=false) { + double X, Y, Z, W, N; + if (is_quaternion) { + N = std::sqrt((double)x*x + (double)y*y + (double)z*z + (double)w*w); + if (N>0) { X = x/N; Y = y/N; Z = z/N; W = w/N; } + else { X = Y = Z = 0; W = 1; } + return CImg::matrix((T)(X*X + Y*Y - Z*Z - W*W),(T)(2*Y*Z - 2*X*W),(T)(2*X*Z + 2*Y*W), + (T)(2*X*W + 2*Y*Z),(T)(X*X - Y*Y + Z*Z - W*W),(T)(2*Z*W - 2*X*Y), + (T)(2*Y*W - 2*X*Z),(T)(2*X*Y + 2*Z*W),(T)(X*X - Y*Y - Z*Z + W*W)); + } + N = cimg::hypot((double)x,(double)y,(double)z); + if (N>0) { X = x/N; Y = y/N; Z = z/N; } + else { X = Y = 0; Z = 1; } + const double ang = w*cimg::PI/180, c = std::cos(ang), omc = 1 - c, s = std::sin(ang); + return CImg::matrix((T)(X*X*omc + c),(T)(X*Y*omc - Z*s),(T)(X*Z*omc + Y*s), + (T)(X*Y*omc + Z*s),(T)(Y*Y*omc + c),(T)(Y*Z*omc - X*s), + (T)(X*Z*omc - Y*s),(T)(Y*Z*omc + X*s),(T)(Z*Z*omc + c)); + } + + //@} + //----------------------------------- + // + //! \name Value Manipulation + //@{ + //----------------------------------- + + //! Fill all pixel values with specified value. + /** + \param val Fill value. + **/ + CImg& fill(const T& val) { + if (is_empty()) return *this; + if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val; + else std::memset(_data,(int)(ulongT)val,sizeof(T)*size()); // Double cast to allow val to be (void*) + return *this; + } + + //! Fill all pixel values with specified value \newinstance. + CImg get_fill(const T& val) const { + return CImg(_width,_height,_depth,_spectrum).fill(val); + } + + //! Fill sequentially all pixel values with specified values. + /** + \param val0 First fill value. + \param val1 Second fill value. + **/ + CImg& fill(const T& val0, const T& val1) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 1; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 2; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 3; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 4; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 5; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 6; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 7; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 8; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 9; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 10; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 11; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 12; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 13; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 14; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 15; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14,val15); + } + + //! Fill sequentially pixel values according to a given expression. + /** + \param expression C-string describing a math formula, or a sequence of values. + \param repeat_values In case a list of values is provided, tells if this list must be repeated for the filling. + \param allow_formula Tells that mathematical formulas are authorized for the filling. + \param list_inputs In case of a mathematical expression, attach a list of images to the specified expression. + \param[out] list_outputs In case of a math expression, list of images atatched to the specified expression. + **/ + CImg& 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?1:0,list_inputs,list_outputs,"fill",0); + } + + // 'formula_mode' = { 0 = does not allow formula | 1 = allow formula | + // 2 = allow formula but do not fill image values }. + CImg& _fill(const char *const expression, const bool repeat_values, const unsigned int formula_mode, + const CImgList *const list_inputs, CImgList *const list_outputs, + const char *const calling_function, const CImg *provides_copy) { + if (is_empty() || !expression || !*expression) return *this; + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + CImg is_error; + bool is_value_sequence = false; + cimg_abort_init; + + if (formula_mode) { + + // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser. + double value; + char sep; + const int err = cimg_sscanf(expression,"%lf %c",&value,&sep); + if (err==1 || (err==2 && sep==',')) { + if (err==1) return fill((T)value); + else is_value_sequence = true; + } + + // Try to fill values according to a formula. + _cimg_abort_init_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 + + bool do_in_parallel = false; +#ifdef cimg_use_openmp + cimg_openmp_if(*expression=='*' || *expression==':' || + (mp.is_parallelizable && _width>=(cimg_openmp_sizefactor)*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; + if (formula_mode==2) 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; + if (formula_mode==2) 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 cimg_openmp_collapse(2)) + cimg_forYZ(*this,y,z) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,0); + else { + 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 + } + + } else { // Scalar-valued expression + T *ptrd = *expression=='<'?end() - 1:_data; + if (*expression=='<') { + if (formula_mode==2) 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); } + } else if (*expression=='>' || !do_in_parallel) { + if (formula_mode==2) 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); } + } 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 cimg_openmp_collapse(3)) + cimg_forYZC(*this,y,z,c) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,c); + else { + 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 + } +#endif + } + } + mp.end(); + } catch (CImgException& e) { CImg::string(e._message).move_to(is_error); } + } + + // Try to fill values according to a value sequence. + if (!formula_mode || 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?1:0,list_inputs,list_outputs); + } + + //! Fill sequentially pixel values according to the values found in another image. + /** + \param values Image containing the values used for the filling. + \param repeat_values In case there are less values than necessary in \c values, tells if these values must be + repeated for the filling. + **/ + template + CImg& fill(const CImg& values, const bool repeat_values=true) { + if (is_empty() || !values) return *this; + T *ptrd = _data, *ptre = ptrd + size(); + for (t *ptrs = values._data, *ptrs_end = ptrs + values.size(); ptrs + CImg get_fill(const CImg& values, const bool repeat_values=true) const { + return repeat_values?CImg(_width,_height,_depth,_spectrum).fill(values,repeat_values): + (+*this).fill(values,repeat_values); + } + + //! Fill pixel values along the X-axis at a specified pixel position. + /** + \param y Y-coordinate of the filled column. + \param z Z-coordinate of the filled column. + \param c C-coordinate of the filled column. + \param a0 First fill value. + **/ + CImg& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const int a0, ...) { +#define _cimg_fill1(x,y,z,c,off,siz,t) { \ + va_list ap; va_start(ap,a0); T *ptrd = data(x,y,z,c); *ptrd = (T)a0; \ + for (unsigned int k = 1; k& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const double a0, ...) { + if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,double); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position. + /** + \param x X-coordinate of the filled row. + \param z Z-coordinate of the filled row. + \param c C-coordinate of the filled row. + \param a0 First fill value. + **/ + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const int a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,int); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position \overloading. + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position. + /** + \param x X-coordinate of the filled slice. + \param y Y-coordinate of the filled slice. + \param c C-coordinate of the filled slice. + \param a0 First fill value. + **/ + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position \overloading. + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position. + /** + \param x X-coordinate of the filled channel. + \param y Y-coordinate of the filled channel. + \param z Z-coordinate of the filled channel. + \param a0 First filling value. + **/ + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position \overloading. + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double); + return *this; + } + + //! Discard specified sequence of values in the image buffer, along a specific axis. + /** + \param values Sequence of values to discard. + \param axis Axis along which the values are discarded. If set to \c 0 (default value) + the method does it for all the buffer values and returns a one-column vector. + \note Discarded values will change the image geometry, so the resulting image + is returned as a one-column vector. + **/ + template + CImg& discard(const CImg& values, const char axis=0) { + if (is_empty() || !values) return *this; + return get_discard(values,axis).move_to(*this); + } + + template + CImg get_discard(const CImg& values, const char axis=0) const { + CImg res; + if (!values) return +*this; + if (is_empty()) return res; + const ulongT vsiz = values.size(); + const char _axis = cimg::lowercase(axis); + ulongT j = 0; + unsigned int k = 0; + int i0 = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) { + if ((*this)(i)!=(T)values[j]) { + if (j) --i; + res.draw_image(k,get_columns(i0,i)); + k+=i - i0 + 1; i0 = i + 1; j = 0; + } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = (int)i + 1; }} + } + const ulongT siz = size(); + if ((ulongT)i0& discard(const char axis=0) { + return get_discard(axis).move_to(*this); + } + + //! Discard neighboring duplicates in the image buffer, along the specified axis \newinstance. + CImg get_discard(const char axis=0) const { + CImg res; + if (is_empty()) return res; + const char _axis = cimg::lowercase(axis); + T current = *_data?(T)0:(T)1; + int j = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) + if ((*this)(i)!=current) { res.draw_image(j++,get_column(i)); current = (*this)(i); } + res.resize(j,-100,-100,-100,0); + } break; + case 'y' : { + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { res.draw_image(0,j++,get_row(i)); current = (*this)(0,i); } + res.resize(-100,j,-100,-100,0); + } break; + case 'z' : { + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { res.draw_image(0,0,j++,get_slice(i)); current = (*this)(0,0,i); } + res.resize(-100,-100,j,-100,0); + } break; + case 'c' : { + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { res.draw_image(0,0,0,j++,get_channel(i)); current = (*this)(0,0,0,i); } + res.resize(-100,-100,-100,j,0); + } break; + default : { + res.unroll('y'); + cimg_foroff(*this,i) + if ((*this)[i]!=current) res[j++] = current = (*this)[i]; + res.resize(-100,j,-100,-100,0); + } + } + return res; + } + + //! Invert endianness of all pixel values. + /** + **/ + CImg& invert_endianness() { + cimg::invert_endianness(_data,size()); + return *this; + } + + //! Invert endianness of all pixel values \newinstance. + CImg get_invert_endianness() const { + return (+*this).invert_endianness(); + } + + //! Fill image with random values in specified range. + /** + \param val_min Minimal authorized random value. + \param val_max Maximal authorized random value. + \note Random variables are uniformely distributed in [val_min,val_max]. + **/ + CImg& 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_pragma_openmp(parallel cimg_openmp_if_size(size(),524288)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; + } + + //! Fill image with random values in specified range \newinstance. + CImg get_rand(const T& val_min, const T& val_max) const { + return (+*this).rand(val_min,val_max); + } + + //! Round pixel values. + /** + \param y Rounding precision. + \param rounding_type Rounding type. Can be: + - \c -1: Backward. + - \c 0: Nearest. + - \c 1: Forward. + **/ + CImg& round(const double y=1, const int rounding_type=0) { + if (y>0) cimg_openmp_for(*this,cimg::round(*ptr,y,rounding_type),8192); + return *this; + } + + //! Round pixel values \newinstance. + CImg get_round(const double y=1, const unsigned int rounding_type=0) const { + return (+*this).round(y,rounding_type); + } + + //! Add random noise to pixel values. + /** + \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the + global value range. + \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper, + \p 3=Poisson or \p 4=Rician). + \return A reference to the modified image instance. + \note + - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on + the image value itself. + - Function \p CImg::get_noise() is also defined. It returns a non-shared modified copy of the image instance. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_noise(40); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_noise.jpg + **/ + CImg& noise(const double sigma, const unsigned int noise_type=0) { + if (is_empty()) return *this; + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + Tfloat nsigma = (Tfloat)sigma, m = 0, M = 0; + if (nsigma==0 && noise_type!=3) return *this; + if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M); + if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.); + switch (noise_type) { + case 0 : { // Gaussian noise + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) { + Tfloat val = (Tfloat)(_data[off] + nsigma*cimg::grand(&rng)); + if (val>vmax) val = vmax; + if (valvmax) val = vmax; + if (val::is_float()) { --m; ++M; } + else { m = (Tfloat)cimg::type::min(); M = (Tfloat)cimg::type::max(); } + } + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) if (cimg::rand(100,&rng)vmax) val = vmax; + if (val get_noise(const double sigma, const unsigned int noise_type=0) const { + return (+*this).noise(sigma,noise_type); + } + + //! Linearly normalize pixel values. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(160,220); + (img,res).display(); + \endcode + \image html ref_normalize2.jpg + **/ + CImg& normalize(const T& min_value, const T& max_value) { + 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); + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm. + /** + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_normalize.jpg + **/ + CImg& normalize() { + const ulongT whd = (ulongT)_width*_height*_depth; + 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) { + const T *ptrs = ptrd; + float n = 0; + cimg_forC(*this,c) { n+=cimg::sqr((float)*ptrs); ptrs+=whd; } + n = (float)std::sqrt(n); + T *_ptrd = ptrd++; + if (n>0) cimg_forC(*this,c) { *_ptrd = (T)(*_ptrd/n); _ptrd+=whd; } + else cimg_forC(*this,c) { *_ptrd = (T)0; _ptrd+=whd; } + } + } + return *this; + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm \newinstance. + CImg get_normalize() const { + return CImg(*this,false).normalize(); + } + + //! Compute Lp-norm of each multi-valued pixel of the image instance. + /** + \param norm_type Type of computed vector norm (can be \p -1=Linf, or \p greater or equal than 0). + \par Example + \code + const CImg img("reference.jpg"), res = img.get_norm(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_norm.jpg + **/ + CImg& norm(const int norm_type=2) { + if (_spectrum==1 && norm_type) return abs(); + return get_norm(norm_type).move_to(*this); + } + + //! Compute L2-norm of each multi-valued pixel of the image instance \newinstance. + CImg get_norm(const int norm_type=2) const { + if (is_empty()) return *this; + if (_spectrum==1 && norm_type) return get_abs(); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(_width,_height,_depth); + switch (norm_type) { + 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { const Tfloat val = (Tfloat)cimg::abs(*_ptrs); if (val>n) n = val; _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 0 : { // L0-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + unsigned int n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=*_ptrs==0?0:1; _ptrs+=whd; } + *(ptrd++) = (Tfloat)n; + } + } + } break; + case 1 : { // L1-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 2 : { // L2-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n); + } + } + } break; + default : { // Linf-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=std::pow(cimg::abs((Tfloat)*_ptrs),(Tfloat)norm_type); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::pow((Tfloat)n,1/(Tfloat)norm_type); + } + } + } + } + return res; + } + + //! Cut pixel values in specified range. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_cut(160,220); + (img,res).display(); + \endcode + \image html ref_cut.jpg + **/ + CImg& cut(const T& min_value, const T& max_value) { + if (is_empty()) return *this; + const T a = min_value get_cut(const T& min_value, const T& max_value) const { + return (+*this).cut(min_value,max_value); + } + + //! Uniformly quantize pixel values. + /** + \param nb_levels Number of quantization levels. + \param keep_range Tells if resulting values keep the same range as the original ones. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_quantize(4); + (img,res).display(); + \endcode + \image html ref_quantize.jpg + **/ + CImg& quantize(const unsigned int nb_levels, const bool keep_range=true) { + if (!nb_levels) + throw CImgArgumentException(_cimg_instance + "quantize(): Invalid quantization request with 0 values.", + cimg_instance); + + if (is_empty()) return *this; + Tfloat m, M = (Tfloat)max_min(m), range = M - m; + if (range>0) { + if (keep_range) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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; + } + + //! Uniformly quantize pixel values \newinstance. + CImg get_quantize(const unsigned int n, const bool keep_range=true) const { + return (+*this).quantize(n,keep_range); + } + + //! Threshold pixel values. + /** + \param value Threshold value + \param soft_threshold Tells if soft thresholding must be applied (instead of hard one). + \param strict_threshold Tells if threshold value is strict. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_threshold(128); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_threshold.jpg + **/ + CImg& threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) { + if (is_empty()) return *this; + if (strict_threshold) { + if (soft_threshold) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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(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(size(),65536)) + cimg_rofoff(*this,off) _data[off] = _data[off]>=value?(T)1:(T)0; + } + return *this; + } + + //! Threshold pixel values \newinstance. + CImg get_threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) const { + return (+*this).threshold(value,soft_threshold,strict_threshold); + } + + //! Compute the histogram of pixel values. + /** + \param nb_levels Number of desired histogram levels. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \note + - The histogram H of an image I is the 1D function where H(x) counts the number of 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. + \par Example + \code + const CImg img = CImg("reference.jpg").histogram(256); + img.display_graph(0,3); + \endcode + \image html ref_histogram.jpg + **/ + CImg& histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) { + return get_histogram(nb_levels,min_value,max_value).move_to(*this); + } + + //! Compute the histogram of pixel values \overloading. + CImg& histogram(const unsigned int nb_levels) { + return get_histogram(nb_levels).move_to(*this); + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) const { + if (!nb_levels || is_empty()) return CImg(); + const double + vmin = (double)(min_value res(nb_levels,1,1,1,0); + cimg_rof(*this,ptrs,T) { + const T val = *ptrs; + if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels - 1:(unsigned int)((val - vmin)*nb_levels/(vmax - vmin))]; + } + return res; + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels) const { + if (!nb_levels || is_empty()) return CImg(); + T vmax = 0, vmin = min_max(vmax); + return get_histogram(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values. + /** + \param nb_levels Number of histogram levels used for the equalization. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_equalize(256); + (img,res).display(); + \endcode + \image html ref_equalize.jpg + **/ + CImg& equalize(const unsigned int nb_levels, const T& min_value, const T& max_value) { + if (!nb_levels || is_empty()) return *this; + const T + vmin = min_value hist = get_histogram(nb_levels,vmin,vmax); + ulongT cumul = 0; + cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; } + if (!cumul) cumul = 1; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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; + } + + //! Equalize histogram of pixel values \overloading. + CImg& equalize(const unsigned int nb_levels) { + if (!nb_levels || is_empty()) return *this; + T vmax = 0, vmin = min_max(vmax); + return equalize(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels, const T& val_min, const T& val_max) const { + return (+*this).equalize(nblevels,val_min,val_max); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels) const { + return (+*this).equalize(nblevels); + } + + //! Index multi-valued pixels regarding to a specified colormap. + /** + \param colormap Multi-valued colormap used as the basis for multi-valued pixel indexing. + \param dithering Level of dithering (0=disable, 1=standard level). + \param map_indexes Tell if the values of the resulting image are the colormap indices or the colormap vectors. + \note + - \p img.index(colormap,dithering,1) is equivalent to img.index(colormap,dithering,0).map(colormap). + \par Example + \code + const CImg img("reference.jpg"), colormap(3,1,1,3, 0,128,255, 0,128,255, 0,128,255); + const CImg res = img.get_index(colormap,1,true); + (img,res).display(); + \endcode + \image html ref_index.jpg + **/ + template + CImg& index(const CImg& colormap, const float dithering=1, const bool map_indexes=false) { + return get_index(colormap,dithering,map_indexes).move_to(*this); + } + + //! Index multi-valued pixels regarding to a specified colormap \newinstance. + template + CImg::Tuint> + get_index(const CImg& colormap, const float dithering=1, const bool map_indexes=true) const { + if (colormap._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "index(): Instance and specified colormap (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + typedef typename CImg::Tuint tuint; + if (is_empty()) return CImg(); + const ulongT + 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 + const float ndithering = cimg::cut(dithering,0,1)/16; + Tfloat valm = 0, valM = (Tfloat)max_min(valm); + if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; } + CImg cache = get_crop(-1,0,0,0,_width,1,0,_spectrum - 1); + Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0); + const ulongT cwhd = (ulongT)cache._width*cache._height*cache._depth; + switch (_spectrum) { + case 1 : { // Optimized for scalars + cimg_forYZ(*this,y,z) { + if (yvalM?valM:_val0; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1, + _val2 = (Tfloat)*ptrs2, val2 = _val2valM?valM:_val2; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrpvalM?valM:_val; + dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd; + } + if (dist=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0=(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, + *ptrs_end = ptrs0 + _width; ptrs0::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0=(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::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrp img("reference.jpg"), + colormap1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255), + colormap2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255), + res = img.get_index(colormap1,0).map(colormap2); + (img,res).display(); + \endcode + \image html ref_map.jpg + **/ + template + CImg& map(const CImg& colormap, const unsigned int boundary_conditions=0) { + return get_map(colormap,boundary_conditions).move_to(*this); + } + + //! Map predefined colormap on the scalar (indexed) image instance \newinstance. + template + 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); + + const ulongT + whd = (ulongT)_width*_height*_depth, + cwhd = (ulongT)colormap._width*colormap._height*colormap._depth, + cwhd2 = 2*cwhd; + CImg res(_width,_height,_depth,colormap._spectrum==1?_spectrum:colormap._spectrum); + switch (colormap._spectrum) { + + case 1 : { // Optimized for scalars + const T *ptrs = _data; + switch (boundary_conditions) { + case 3 : // Mirror + cimg_for(res,ptrd,t) { + const ulongT ind = ((ulongT)*(ptrs++))%cwhd2; + *ptrd = colormap[ind& label(const bool is_high_connectivity=false, const Tfloat tolerance=0) { + return get_label(is_high_connectivity,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + CImg get_label(const bool is_high_connectivity=false, + const Tfloat tolerance=0) const { + if (is_empty()) return CImg(); + + // Create neighborhood tables. + int dx[13], dy[13], dz[13], nb = 0; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 0; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 0; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 0; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 0; + } + if (_depth>1) { // 3D version + dx[nb] = 0; dy[nb] = 0; dz[nb++]=1; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = -1; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = -1; + + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 1; + } + } + return _label(nb,dx,dy,dz,tolerance); + } + + //! Label connected components \overloading. + /** + \param connectivity_mask Mask of the neighboring pixels. + \param tolerance Tolerance used to determine if two neighboring pixels belong to the same region. + **/ + template + CImg& label(const CImg& connectivity_mask, const Tfloat tolerance=0) { + return get_label(connectivity_mask,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + template + CImg get_label(const CImg& connectivity_mask, + const Tfloat tolerance=0) const { + 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); + nb = 0; + cimg_forXYZ(connectivity_mask,x,y,z) if ((x || y || z) && + connectivity_mask(x,y,z)) { + dx[nb] = x; dy[nb] = y; dz[nb++] = z; + } + return _label(nb,dx,dy,dz,tolerance); + } + + CImg _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); + + // Init label numbers. + ulongT *ptr = _res.data(); + cimg_foroff(_res,p) *(ptr++) = p; + + // For each neighbour-direction, label. + for (unsigned int n = 0; n& _system_strescape() { +#define cimg_system_strescape(c,s) case c : if (p!=ptrs) CImg(ptrs,(unsigned int)(p-ptrs),1,1,1,false).\ + move_to(list); \ + CImg(s,(unsigned int)std::strlen(s),1,1,1,false).move_to(list); ptrs = p + 1; break + CImgList list; + const T *ptrs = _data; + cimg_for(*this,p,T) switch ((int)*p) { + cimg_system_strescape('\\',"\\\\"); + cimg_system_strescape('\"',"\\\""); + cimg_system_strescape('!',"\"\\!\""); + cimg_system_strescape('`',"\\`"); + cimg_system_strescape('$',"\\$"); + } + if (ptrs(ptrs,(unsigned int)(end()-ptrs),1,1,1,false).move_to(list); + return (list>'x').move_to(*this); + } + + //@} + //--------------------------------- + // + //! \name Color Base Management + //@{ + //--------------------------------- + + //! Return colormap \e "default", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_default.jpg + **/ + static const CImg& default_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,256,1,3); + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap(0,index,0) = (Tuchar)r; + colormap(0,index,1) = (Tuchar)g; + colormap(0,index++,2) = (Tuchar)b; + } + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "HSV", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hsv.jpg + **/ + static const CImg& HSV_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + CImg tmp(1,256,1,3,1); + tmp.get_shared_channel(0).sequence(0,359); + colormap = tmp.HSVtoRGB(); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "lines", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_lines.jpg + **/ + static const CImg& 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; + } + + //! Return colormap \e "hot", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hot.jpg + **/ + static const CImg& hot_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[1] = colormap[2] = colormap[3] = colormap[6] = colormap[7] = colormap[11] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cool", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cool.jpg + **/ + static const CImg& cool_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) colormap.assign(1,2,1,3).fill((T)0,(T)255,(T)255,(T)0,(T)255,(T)255).resize(1,256,1,3,3); + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "jet", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_jet.jpg + **/ + static const CImg& jet_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[2] = colormap[3] = colormap[5] = colormap[6] = colormap[8] = colormap[9] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "flag", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_flag.jpg + **/ + static const CImg& flag_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[0] = colormap[1] = colormap[5] = colormap[9] = colormap[10] = 255; + colormap.resize(1,256,1,3,0,2); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cube", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cube.jpg + **/ + static const CImg& cube_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,8,1,3,(T)0); + colormap[1] = colormap[3] = colormap[5] = colormap[7] = + colormap[10] = colormap[11] = colormap[12] = colormap[13] = + colormap[20] = colormap[21] = colormap[22] = colormap[23] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Convert pixel values from sRGB to RGB color spaces. + CImg& sRGBtoRGB() { + if (is_empty()) return *this; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + sval = (Tfloat)_data[off]/255, + val = (Tfloat)(sval<=0.04045f?sval/12.92f:std::pow((sval + 0.055f)/(1.055f),2.4f)); + _data[off] = (T)cimg::cut(val*255,0,255); + } + return *this; + } + + //! Convert pixel values from sRGB to RGB color spaces \newinstance. + CImg get_sRGBtoRGB() const { + return CImg(*this,false).sRGBtoRGB(); + } + + //! 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(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + val = (Tfloat)_data[off]/255, + sval = (Tfloat)(val<=0.0031308f?val*12.92f:1.055f*std::pow(val,0.416667f) - 0.055f); + _data[off] = (T)cimg::cut(sval*255,0,255); + } + return *this; + } + + //! Convert pixel values from RGB to sRGB color spaces \newinstance. + CImg get_RGBtosRGB() const { + return CImg(*this,false).RGBtosRGB(); + } + + //! Convert pixel values from RGB to HSI color spaces. + CImg& RGBtoHSI() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSI(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSI color spaces \newinstance. + CImg get_RGBtoHSI() const { + return CImg(*this,false).RGBtoHSI(); + } + + //! Convert pixel values from HSI to RGB color spaces. + CImg& HSItoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSItoRGB(): Instance is not a HSI image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSItoRGB() const { + return CImg< Tuchar>(*this,false).HSItoRGB(); + } + + //! Convert pixel values from RGB to HSL color spaces. + CImg& RGBtoHSL() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSL(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSL color spaces \newinstance. + CImg get_RGBtoHSL() const { + return CImg(*this,false).RGBtoHSL(); + } + + //! Convert pixel values from HSL to RGB color spaces. + CImg& HSLtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSLtoRGB(): Instance is not a HSL image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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.f/3 - ntr):p, + G = 6*ntg<1?p + (q - p)*6*ntg:2*ntg<1?q:3*ntg<2?p + (q - p)*6*(2.f/3 - ntg):p, + B = 6*ntb<1?p + (q - p)*6*ntb:2*ntb<1?q:3*ntb<2?p + (q - p)*6*(2.f/3 - ntb):p; + p1[N] = (T)cimg::cut(255*R,0,255); + p2[N] = (T)cimg::cut(255*G,0,255); + p3[N] = (T)cimg::cut(255*B,0,255); + } + return *this; + } + + //! Convert pixel values from HSL to RGB color spaces \newinstance. + CImg get_HSLtoRGB() const { + return CImg(*this,false).HSLtoRGB(); + } + + //! Convert pixel values from RGB to HSV color spaces. + CImg& RGBtoHSV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSV color spaces \newinstance. + CImg get_RGBtoHSV() const { + return CImg(*this,false).RGBtoHSV(); + } + + //! Convert pixel values from HSV to RGB color spaces. + CImg& HSVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSVtoRGB(): Instance is not a HSV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSVtoRGB() const { + return CImg(*this,false).HSVtoRGB(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& RGBtoYCbCr() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYCbCr(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_RGBtoYCbCr() const { + return CImg(*this,false).RGBtoYCbCr(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& YCbCrtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YCbCrtoRGB(): Instance is not a YCbCr image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_YCbCrtoRGB() const { + return CImg(*this,false).YCbCrtoRGB(); + } + + //! Convert pixel values from RGB to YUV color spaces. + CImg& RGBtoYUV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYUV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_RGBtoYUV() const { + return CImg(*this,false).RGBtoYUV(); + } + + //! Convert pixel values from YUV to RGB color spaces. + CImg& YUVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YUVtoRGB(): Instance is not a YUV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_YUVtoRGB() const { + return CImg< Tuchar>(*this,false).YUVtoRGB(); + } + + //! Convert pixel values from RGB to CMY color spaces. + CImg& RGBtoCMY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoCMY(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoCMY() const { + return CImg(*this,false).RGBtoCMY(); + } + + //! Convert pixel values from CMY to RGB color spaces. + CImg& CMYtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoRGB(): Instance is not a CMY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_CMYtoRGB() const { + return CImg(*this,false).CMYtoRGB(); + } + + //! Convert pixel values from CMY to CMYK color spaces. + CImg& CMYtoCMYK() { + return get_CMYtoCMYK().move_to(*this); + } + + //! Convert pixel values from CMY to CMYK color spaces \newinstance. + CImg get_CMYtoCMYK() const { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoCMYK(): Instance is not a CMY image.", + cimg_instance); + + CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT 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; } + pd1[N] = (Tfloat)cimg::cut(C,0,255), + pd2[N] = (Tfloat)cimg::cut(M,0,255), + pd3[N] = (Tfloat)cimg::cut(Y,0,255), + pd4[N] = (Tfloat)cimg::cut(K,0,255); + } + return res; + } + + //! Convert pixel values from CMYK to CMY color spaces. + CImg& CMYKtoCMY() { + return get_CMYKtoCMY().move_to(*this); + } + + //! Convert pixel values from CMYK to CMY color spaces \newinstance. + CImg get_CMYKtoCMY() const { + if (_spectrum!=4) + throw CImgInstanceException(_cimg_instance + "CMYKtoCMY(): Instance is not a CMYK image.", + cimg_instance); + + 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT N = 0; N& RGBtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoXYZ(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).RGBtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to RGB color spaces. + /** + \param use_D65 Tell to use the D65 illuminant (D50 otherwise). + **/ + CImg& XYZtoRGB(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoRGB(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_XYZtoRGB(const bool use_D65=true) const { + return CImg(*this,false).XYZtoRGB(use_D65); + } + + //! Convert pixel values from XYZ to Lab color spaces. + CImg& XYZtoLab(const bool use_D65=true) { +#define _cimg_Labf(x) (24389*(x)>216?cimg::cbrt(x):(24389*(x)/27 + 16)/116) + + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoLab(): Instance is not a XYZ image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N get_XYZtoLab(const bool use_D65=true) const { + return CImg(*this,false).XYZtoLab(use_D65); + } + + //! Convert pixel values from Lab to XYZ color spaces. + CImg& LabtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "LabtoXYZ(): Instance is not a Lab image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N216?cX*cX*cX:(116*cX - 16)*27/24389), + Y = (Tfloat)(27*L>216?cY*cY*cY:27*L/24389), + Z = (Tfloat)(24389*cZ>216?cZ*cZ*cZ:(116*cZ - 16)*27/24389); + p1[N] = (T)(X*white[0]); + p2[N] = (T)(Y*white[1]); + p3[N] = (T)(Z*white[2]); + } + return *this; + } + + //! Convert pixel values from Lab to XYZ color spaces \newinstance. + CImg get_LabtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).LabtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to xyY color spaces. + CImg& XYZtoxyY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoxyY(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?sum:1; + p1[N] = (T)(X/nsum); + p2[N] = (T)(Y/nsum); + p3[N] = (T)Y; + } + return *this; + } + + //! Convert pixel values from XYZ to xyY color spaces \newinstance. + CImg get_XYZtoxyY() const { + return CImg(*this,false).XYZtoxyY(); + } + + //! Convert pixel values from xyY pixels to XYZ color spaces. + CImg& xyYtoXYZ() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "xyYtoXYZ(): Instance is not a xyY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?py:1; + p1[N] = (T)(px*Y/ny); + p2[N] = (T)Y; + p3[N] = (T)((1 - px - py)*Y/ny); + } + return *this; + } + + //! Convert pixel values from xyY pixels to XYZ color spaces \newinstance. + CImg get_xyYtoXYZ() const { + return CImg(*this,false).xyYtoXYZ(); + } + + //! Convert pixel values from RGB to Lab color spaces. + CImg& RGBtoLab(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoLab(use_D65); + } + + //! Convert pixel values from RGB to Lab color spaces \newinstance. + CImg get_RGBtoLab(const bool use_D65=true) const { + return CImg(*this,false).RGBtoLab(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces. + CImg& LabtoRGB(const bool use_D65=true) { + return LabtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces \newinstance. + CImg get_LabtoRGB(const bool use_D65=true) const { + return CImg(*this,false).LabtoRGB(use_D65); + } + + //! Convert pixel values from RGB to xyY color spaces. + CImg& RGBtoxyY(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoxyY(); + } + + //! Convert pixel values from RGB to xyY color spaces \newinstance. + CImg get_RGBtoxyY(const bool use_D65=true) const { + return CImg(*this,false).RGBtoxyY(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces. + CImg& xyYtoRGB(const bool use_D65=true) { + return xyYtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces \newinstance. + CImg get_xyYtoRGB(const bool use_D65=true) const { + return CImg(*this,false).xyYtoRGB(use_D65); + } + + //! Convert pixel values from RGB to CMYK color spaces. + CImg& RGBtoCMYK() { + return RGBtoCMY().CMYtoCMYK(); + } + + //! Convert pixel values from RGB to CMYK color spaces \newinstance. + CImg get_RGBtoCMYK() const { + return CImg(*this,false).RGBtoCMYK(); + } + + //! Convert pixel values from CMYK to RGB color spaces. + CImg& CMYKtoRGB() { + return CMYKtoCMY().CMYtoRGB(); + } + + //! Convert pixel values from CMYK to RGB color spaces \newinstance. + CImg get_CMYKtoRGB() const { + return CImg(*this,false).CMYKtoRGB(); + } + + //@} + //------------------------------------------ + // + //! \name Geometric / Spatial Manipulation + //@{ + //------------------------------------------ + + static float _cimg_lanczos(const float x) { + if (x<=-2 || x>=2) return 0; + const float a = (float)cimg::PI*x, b = 0.5f*a; + return (float)(x?std::sin(a)*std::sin(b)/(a*b):1); + } + + //! Resize image to new dimensions. + /** + \param size_x Number of columns (new size along the X-axis). + \param size_y Number of rows (new size along the Y-axis). + \param size_z Number of slices (new size along the Z-axis). + \param size_c Number of vector-channels (new size along the C-axis). + \param interpolation_type Method of interpolation: + - -1 = no interpolation: raw memory resizing. + - 0 = no interpolation: additional space is filled according to \p boundary_conditions. + - 1 = nearest-neighbor interpolation. + - 2 = moving average interpolation. + - 3 = linear interpolation. + - 4 = grid interpolation. + - 5 = cubic interpolation. + - 6 = lanczos interpolation. + \param boundary_conditions Type of boundary conditions used if necessary. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100). + **/ + CImg& resize(const int size_x, const int size_y=-100, + const int size_z=-100, const int size_c=-100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + if (!size_x || !size_y || !size_z || !size_c) return assign(); + const unsigned int + _sx = (unsigned int)(size_x<0?-size_x*width()/100:size_x), + _sy = (unsigned int)(size_y<0?-size_y*height()/100:size_y), + _sz = (unsigned int)(size_z<0?-size_z*depth()/100:size_z), + _sc = (unsigned int)(size_c<0?-size_c*spectrum()/100:size_c), + sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1; + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return *this; + if (is_empty()) return assign(sx,sy,sz,sc,(T)0); + if (interpolation_type==-1 && sx*sy*sz*sc==size()) { + _width = sx; _height = sy; _depth = sz; _spectrum = sc; + return *this; + } + return get_resize(sx,sy,sz,sc,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c).move_to(*this); + } + + //! Resize image to new dimensions \newinstance. + CImg get_resize(const int size_x, const int size_y = -100, + const int size_z = -100, const int size_c = -100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + if (centering_x<0 || centering_x>1 || centering_y<0 || centering_y>1 || + centering_z<0 || centering_z>1 || centering_c<0 || centering_c>1) + throw CImgArgumentException(_cimg_instance + "resize(): Specified centering arguments (%g,%g,%g,%g) are outside range [0,1].", + cimg_instance, + centering_x,centering_y,centering_z,centering_c); + + if (!size_x || !size_y || !size_z || !size_c) return CImg(); + const unsigned int + sx = std::max(1U,(unsigned int)(size_x>=0?size_x:-size_x*width()/100)), + sy = std::max(1U,(unsigned int)(size_y>=0?size_y:-size_y*height()/100)), + sz = std::max(1U,(unsigned int)(size_z>=0?size_z:-size_z*depth()/100)), + sc = std::max(1U,(unsigned int)(size_c>=0?size_c:-size_c*spectrum()/100)); + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this; + if (is_empty()) return CImg(sx,sy,sz,sc,(T)0); + CImg res; + switch (interpolation_type) { + + // Raw resizing. + // + case -1 : + std::memcpy(res.assign(sx,sy,sz,sc,(T)0)._data,_data,sizeof(T)*std::min(size(),(ulongT)sx*sy*sz*sc)); + break; + + // No interpolation. + // + case 0 : { + const int + xc = (int)(centering_x*((int)sx - width())), + yc = (int)(centering_y*((int)sy - height())), + zc = (int)(centering_z*((int)sz - depth())), + cc = (int)(centering_c*((int)sc - spectrum())); + + switch (boundary_conditions) { + case 3 : { // Mirror + res.assign(sx,sy,sz,sc); + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),65536)) + cimg_forXYZC(res,x,y,z,c) { + const int + mx = cimg::mod(x - xc,w2), my = cimg::mod(y - yc,h2), + mz = cimg::mod(z - zc,d2), mc = cimg::mod(c - cc,s2); + res(x,y,z,c) = (*this)(mx sprite; + if (xc>0) { // X-backward + res.get_crop(xc,yc,zc,cc,xc,yc + height() - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc - 1; x>=0; --x) res.draw_image(x,yc,zc,cc,sprite); + } + if (xc + width()<(int)sx) { // X-forward + res.get_crop(xc + width() - 1,yc,zc,cc,xc + width() - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc + width(); x<(int)sx; ++x) res.draw_image(x,yc,zc,cc,sprite); + } + if (yc>0) { // Y-backward + res.get_crop(0,yc,zc,cc,sx - 1,yc,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc - 1; y>=0; --y) res.draw_image(0,y,zc,cc,sprite); + } + if (yc + height()<(int)sy) { // Y-forward + res.get_crop(0,yc + height() - 1,zc,cc,sx - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc + height(); y<(int)sy; ++y) res.draw_image(0,y,zc,cc,sprite); + } + if (zc>0) { // Z-backward + res.get_crop(0,0,zc,cc,sx - 1,sy - 1,zc,cc + spectrum() - 1).move_to(sprite); + for (int z = zc - 1; z>=0; --z) res.draw_image(0,0,z,cc,sprite); + } + if (zc + depth()<(int)sz) { // Z-forward + res.get_crop(0,0,zc +depth() - 1,cc,sx - 1,sy - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int z = zc + depth(); z<(int)sz; ++z) res.draw_image(0,0,z,cc,sprite); + } + if (cc>0) { // C-backward + res.get_crop(0,0,0,cc,sx - 1,sy - 1,sz - 1,cc).move_to(sprite); + for (int c = cc - 1; c>=0; --c) res.draw_image(0,0,0,c,sprite); + } + if (cc + spectrum()<(int)sc) { // C-forward + res.get_crop(0,0,0,cc + spectrum() - 1,sx - 1,sy - 1,sz - 1,cc + spectrum() - 1).move_to(sprite); + for (int c = cc + spectrum(); c<(int)sc; ++c) res.draw_image(0,0,0,c,sprite); + } + } break; + default : // Dirichlet + res.assign(sx,sy,sz,sc,(T)0).draw_image(xc,yc,zc,cc,*this); + } + break; + } break; + + // Nearest neighbor interpolation. + // + case 1 : { + res.assign(sx,sy,sz,sc); + CImg off_x(sx), off_y(sy + 1), off_z(sz + 1), off_c(sc + 1); + const ulongT + wh = (ulongT)_width*_height, + whd = (ulongT)_width*_height*_depth, + sxy = (ulongT)sx*sy, + sxyz = (ulongT)sx*sy*sz, + 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 = (x + one)*_width/sx; + *(poff_x++) = curr - old; + } + } + if (sy==_height) off_y.fill(_width); + else { + ulongT *poff_y = off_y._data, curr = 0; + cimg_forY(res,y) { + const ulongT old = curr; + curr = (y + one)*_height/sy; + *(poff_y++) = _width*(curr - old); + } + *poff_y = 0; + } + if (sz==_depth) off_z.fill(wh); + else { + ulongT *poff_z = off_z._data, curr = 0; + cimg_forZ(res,z) { + const ulongT old = curr; + curr = (z + one)*_depth/sz; + *(poff_z++) = wh*(curr - old); + } + *poff_z = 0; + } + if (sc==_spectrum) off_c.fill(whd); + else { + ulongT *poff_c = off_c._data, curr = 0; + cimg_forC(res,c) { + const ulongT old = curr; + curr = (c + one)*_spectrum/sc; + *(poff_c++) = whd*(curr - old); + } + *poff_c = 0; + } + + T *ptrd = res._data; + const T* ptrc = _data; + const ulongT *poff_c = off_c._data; + for (unsigned int c = 0; c tmp(sx,_height,_depth,_spectrum,0); + for (unsigned int a = _width*sx, b = _width, c = sx, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d; + if (!b) { + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)/=_width; + ++t; + b = _width; + } + if (!c) { ++s; c = sx; } + } + tmp.move_to(res); + instance_first = false; + } + if (sy!=_height) { + CImg tmp(sx,sy,_depth,_spectrum,0); + for (unsigned int a = _height*sy, b = _height, c = sy, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d; + else + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d; + if (!b) { + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)/=_height; + ++t; + b = _height; + } + if (!c) { ++s; c = sy; } + } + tmp.move_to(res); + instance_first = false; + } + if (sz!=_depth) { + CImg tmp(sx,sy,sz,_spectrum,0); + for (unsigned int a = _depth*sz, b = _depth, c = sz, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d; + else + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d; + if (!b) { + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)/=_depth; + ++t; + b = _depth; + } + if (!c) { ++s; c = sz; } + } + tmp.move_to(res); + instance_first = false; + } + if (sc!=_spectrum) { + CImg tmp(sx,sy,sz,sc,0); + for (unsigned int a = _spectrum*sc, b = _spectrum, c = sc, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d; + else + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d; + if (!b) { + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=_spectrum; + ++t; + b = _spectrum; + } + if (!c) { ++s; c = sc; } + } + tmp.move_to(res); + instance_first = false; + } + } break; + + // Linear interpolation. + // + case 3 : { + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *ptrs = data(0,y,z,c), *const ptrsmax = ptrs + _width - 1; + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + 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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height - 1)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + 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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth - 1)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + 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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum - 1)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrs resx, resy, resz, resc; + if (sx!=_width) { + if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + resx.assign(sx,_height,_depth,_spectrum,(T)0); + const int dx = (int)(2*sx), dy = 2*width(); + int err = (int)(dy + centering_x*(sx*dy/width() - dy)), xs = 0; + cimg_forX(resx,x) if ((err-=dy)<=0) { + cimg_forYZC(resx,y,z,c) resx(x,y,z,c) = (*this)(xs,y,z,c); + ++xs; + err+=dx; + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (sy<_height) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + resy.assign(sx,sy,_depth,_spectrum,(T)0); + const int dx = (int)(2*sy), dy = 2*height(); + int err = (int)(dy + centering_y*(sy*dy/height() - dy)), ys = 0; + cimg_forY(resy,y) if ((err-=dy)<=0) { + cimg_forXZC(resy,x,z,c) resy(x,y,z,c) = resx(x,ys,z,c); + ++ys; + err+=dx; + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (sz<_depth) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + resz.assign(sx,sy,sz,_spectrum,(T)0); + const int dx = (int)(2*sz), dy = 2*depth(); + int err = (int)(dy + centering_z*(sz*dy/depth() - dy)), zs = 0; + cimg_forZ(resz,z) if ((err-=dy)<=0) { + cimg_forXYC(resz,x,y,c) resz(x,y,z,c) = resy(x,y,zs,c); + ++zs; + err+=dx; + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (sc<_spectrum) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + resc.assign(sx,sy,sz,sc,(T)0); + const int dx = (int)(2*sc), dy = 2*spectrum(); + int err = (int)(dy + centering_c*(sc*dy/spectrum() - dy)), cs = 0; + cimg_forC(resc,c) if ((err-=dy)<=0) { + cimg_forXYZ(resc,x,y,z) resc(x,y,z,c) = resz(x,y,z,cs); + ++cs; + err+=dx; + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Cubic interpolation. + // + case 5 : { + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - 1):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + 1):val1, + val3 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sx):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sx):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxy):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxyz):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Lanczos interpolation. + // + case 6 : { + const double vmin = (double)cimg::type::min(), vmax = (double)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1, + *const ptrsmax = ptrs0 + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - 1):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + 1):val2, + val4 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx, + *const ptrsmax = ptrs0 + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sx):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sx):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sx):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy, + *const ptrsmax = ptrs0 + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxy):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxy):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz, + *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxyz):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxyz):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Unknow interpolation. + // + default : + throw CImgArgumentException(_cimg_instance + "resize(): Invalid specified interpolation %d " + "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | " + "5=cubic | 6=lanczos }).", + cimg_instance, + interpolation_type); + } + return res; + } + + //! Resize image to dimensions of another image. + /** + \param src Reference image used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + template + CImg& resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of another image \newinstance. + template + CImg get_resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window. + /** + \param disp Reference display window used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + CImg& resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window \newinstance. + CImg get_resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to half-size along XY axes, using an optimized filter. + CImg& resize_halfXY() { + return get_resize_halfXY().move_to(*this); + } + + //! Resize image to half-size along XY axes, using an optimized filter \newinstance. + CImg get_resize_halfXY() const { + if (is_empty()) return *this; + static const Tfloat kernel[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f, + 0.1231940459f, 0.1935127547f, 0.1231940459f, + 0.07842776544f, 0.1231940459f, 0.07842776544f }; + CImg I(9), res(_width/2,_height/2,_depth,_spectrum); + T *ptrd = res._data; + cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T) + if (x%2 && y%2) *(ptrd++) = (T) + (I[0]*kernel[0] + I[1]*kernel[1] + I[2]*kernel[2] + + I[3]*kernel[3] + I[4]*kernel[4] + I[5]*kernel[5] + + I[6]*kernel[6] + I[7]*kernel[7] + I[8]*kernel[8]); + return res; + } + + //! Resize image to double-size, using the Scale2X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_doubleXY() { + return get_resize_doubleXY().move_to(*this); + } + + //! Resize image to double-size, using the Scale2X algorithm \newinstance. + CImg get_resize_doubleXY() const { +#define _cimg_gs2x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=(res)._width, ptrd2+=(res)._width) + +#define _cimg_gs2x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs2x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(_width<<1,_height<<1,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width; + _cimg_gs2x_for3x3(*this,x,y,z,c,I,T) { + if (Icp!=Icn && Ipc!=Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = Ipc==Icn?Ipc:Icc; + *(ptrd2++) = Icn==Inc?Inc:Icc; + } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; } + } + } + return res; + } + + //! Resize image to triple-size, using the Scale3X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_tripleXY() { + return get_resize_tripleXY().move_to(*this); + } + + //! Resize image to triple-size, using the Scale3X algorithm \newinstance. + CImg get_resize_tripleXY() const { +#define _cimg_gs3x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=2*(res)._width, ptrd2+=2*(res)._width, ptrd3+=2*(res)._width) + +#define _cimg_gs3x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs3x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(3*_width,3*_height,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width, + *ptrd3 = ptrd2 + res._width; + _cimg_gs3x_for3x3(*this,x,y,z,c,I,T) { + if (Icp != Icn && Ipc != Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc; + *(ptrd2++) = Icc; + *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc; + *(ptrd3++) = Ipc==Icn?Ipc:Icc; + *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc; + *(ptrd3++) = Icn==Inc?Inc:Icc; + } else { + *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc; + *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; + *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc; + } + } + } + return res; + } + + //! Mirror image content along specified axis. + /** + \param axis Mirror axis + **/ + CImg& mirror(const char axis) { + if (is_empty()) return *this; + T *pf, *pb, *buf = 0; + switch (cimg::lowercase(axis)) { + case 'x' : { + pf = _data; pb = data(_width - 1); + const unsigned int width2 = _width/2; + for (unsigned int yzv = 0; yzv<_height*_depth*_spectrum; ++yzv) { + for (unsigned int x = 0; x get_mirror(const char axis) const { + return (+*this).mirror(axis); + } + + //! Mirror image content along specified axes. + /** + \param axes Mirror axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& mirror(const char *const axes) { + for (const char *s = axes; *s; ++s) mirror(*s); + return *this; + } + + //! Mirror image content along specified axes \newinstance. + CImg get_mirror(const char *const axes) const { + return (+*this).mirror(axes); + } + + //! Shift image content. + /** + \param delta_x Amount of displacement along the X-axis. + \param delta_y Amount of displacement along the Y-axis. + \param delta_z Amount of displacement along the Z-axis. + \param delta_c Amount of displacement along the C-axis. + \param boundary_conditions Border condition. Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) { + if (is_empty()) return *this; + if (boundary_conditions==3) + return get_crop(-delta_x,-delta_y,-delta_z,-delta_c, + width() - delta_x - 1, + height() - delta_y - 1, + depth() - delta_z - 1, + spectrum() - delta_c - 1,3).move_to(*this); + if (delta_x) // Shift along X-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_x,width()), ndelta_x = (ml<=width()/2)?ml:(ml-width()); + if (!ndelta_x) return *this; + CImg buf(cimg::abs(ndelta_x)); + if (ndelta_x>0) cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(0,y,z,c),ndelta_x*sizeof(T)); + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + std::memcpy(data(_width-ndelta_x,y,z,c),buf,ndelta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(_width + ndelta_x,y,z,c),-ndelta_x*sizeof(T)); + std::memmove(data(-ndelta_x,y,z,c),data(0,y,z,c),(_width + ndelta_x)*sizeof(T)); + std::memcpy(data(0,y,z,c),buf,-ndelta_x*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_x<0) { + const int ndelta_x = (-delta_x>=width())?width() - 1:-delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(_width - 1,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width())?width() - 1:delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(ndelta_x,y,z,c),data(0,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(0,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width()) return fill((T)0); + if (delta_x<0) cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(-delta_x,y,z,c),(_width + delta_x)*sizeof(T)); + std::memset(data(_width + delta_x,y,z,c),0,-delta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memmove(data(delta_x,y,z,c),data(0,y,z,c),(_width-delta_x)*sizeof(T)); + std::memset(data(0,y,z,c),0,delta_x*sizeof(T)); + } + } + + if (delta_y) // Shift along Y-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_y,height()), ndelta_y = (ml<=height()/2)?ml:(ml-height()); + if (!ndelta_y) return *this; + CImg buf(width(),cimg::abs(ndelta_y)); + if (ndelta_y>0) cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,0,z,c),_width*ndelta_y*sizeof(T)); + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + std::memcpy(data(0,_height-ndelta_y,z,c),buf,_width*ndelta_y*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,_height + ndelta_y,z,c),-ndelta_y*_width*sizeof(T)); + std::memmove(data(0,-ndelta_y,z,c),data(0,0,z,c),_width*(_height + ndelta_y)*sizeof(T)); + std::memcpy(data(0,0,z,c),buf,-ndelta_y*_width*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_y<0) { + const int ndelta_y = (-delta_y>=height())?height() - 1:-delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,_height-ndelta_y,z,c), *ptrs = data(0,_height - 1,z,c); + for (int l = 0; l=height())?height() - 1:delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,ndelta_y,z,c),data(0,0,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c); + for (int l = 0; l=height()) return fill((T)0); + if (delta_y<0) cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,-delta_y,z,c),_width*(_height + delta_y)*sizeof(T)); + std::memset(data(0,_height + delta_y,z,c),0,-delta_y*_width*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memmove(data(0,delta_y,z,c),data(0,0,z,c),_width*(_height-delta_y)*sizeof(T)); + std::memset(data(0,0,z,c),0,delta_y*_width*sizeof(T)); + } + } + + if (delta_z) // Shift along Z-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_z,depth()), ndelta_z = (ml<=depth()/2)?ml:(ml-depth()); + if (!ndelta_z) return *this; + CImg buf(width(),height(),cimg::abs(ndelta_z)); + if (ndelta_z>0) cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,0,c),_width*_height*ndelta_z*sizeof(T)); + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,_depth-ndelta_z,c),buf,_width*_height*ndelta_z*sizeof(T)); + } else cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,_depth + ndelta_z,c),-ndelta_z*_width*_height*sizeof(T)); + std::memmove(data(0,0,-ndelta_z,c),data(0,0,0,c),_width*_height*(_depth + ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,0,c),buf,-ndelta_z*_width*_height*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_z<0) { + const int ndelta_z = (-delta_z>=depth())?depth() - 1:-delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,_depth-ndelta_z,c), *ptrs = data(0,0,_depth - 1,c); + for (int l = 0; l=depth())?depth() - 1:delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,ndelta_z,c),data(0,0,0,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c); + for (int l = 0; l=depth()) return fill((T)0); + if (delta_z<0) cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,-delta_z,c),_width*_height*(_depth + delta_z)*sizeof(T)); + std::memset(data(0,0,_depth + delta_z,c),0,_width*_height*(-delta_z)*sizeof(T)); + } else cimg_forC(*this,c) { + std::memmove(data(0,0,delta_z,c),data(0,0,0,c),_width*_height*(_depth-delta_z)*sizeof(T)); + std::memset(data(0,0,0,c),0,delta_z*_width*_height*sizeof(T)); + } + } + + if (delta_c) // Shift along C-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_c,spectrum()), ndelta_c = (ml<=spectrum()/2)?ml:(ml-spectrum()); + if (!ndelta_c) return *this; + CImg buf(width(),height(),depth(),cimg::abs(ndelta_c)); + if (ndelta_c>0) { + std::memcpy(buf,_data,_width*_height*_depth*ndelta_c*sizeof(T)); + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + std::memcpy(data(0,0,0,_spectrum-ndelta_c),buf,_width*_height*_depth*ndelta_c*sizeof(T)); + } else { + std::memcpy(buf,data(0,0,0,_spectrum + ndelta_c),-ndelta_c*_width*_height*_depth*sizeof(T)); + std::memmove(data(0,0,0,-ndelta_c),_data,_width*_height*_depth*(_spectrum + ndelta_c)*sizeof(T)); + std::memcpy(_data,buf,-ndelta_c*_width*_height*_depth*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_c<0) { + const int ndelta_c = (-delta_c>=spectrum())?spectrum() - 1:-delta_c; + if (!ndelta_c) return *this; + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,_spectrum-ndelta_c), *ptrs = data(0,0,0,_spectrum - 1); + for (int l = 0; l=spectrum())?spectrum() - 1:delta_c; + if (!ndelta_c) return *this; + std::memmove(data(0,0,0,ndelta_c),_data,_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,1); + for (int l = 0; l=spectrum()) return fill((T)0); + if (delta_c<0) { + std::memmove(_data,data(0,0,0,-delta_c),_width*_height*_depth*(_spectrum + delta_c)*sizeof(T)); + std::memset(data(0,0,0,_spectrum + delta_c),0,_width*_height*_depth*(-delta_c)*sizeof(T)); + } else { + std::memmove(data(0,0,0,delta_c),_data,_width*_height*_depth*(_spectrum-delta_c)*sizeof(T)); + std::memset(_data,0,delta_c*_width*_height*_depth*sizeof(T)); + } + } + return *this; + } + + //! Shift image content \newinstance. + CImg get_shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) const { + return (+*this).shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions); + } + + //! Permute axes order. + /** + \param order Axes permutations, as a C-string of 4 characters. + This function permutes image content regarding the specified axes permutation. + **/ + CImg& permute_axes(const char *const order) { + return get_permute_axes(order).move_to(*this); + } + + //! Permute axes order \newinstance. + CImg get_permute_axes(const char *const order) const { + const T foo = (T)0; + return _permute_axes(order,foo); + } + + template + CImg _permute_axes(const char *const order, const t&) const { + if (is_empty() || !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; + } + } + if (!res) + throw CImgArgumentException(_cimg_instance + "permute_axes(): Invalid specified permutation '%s'.", + cimg_instance, + order); + return res; + } + + //! Unroll pixel values along specified axis. + /** + \param axis Unroll axis (can be \c 'x', \c 'y', \c 'z' or c 'c'). + **/ + CImg& unroll(const char axis) { + const unsigned int siz = (unsigned int)size(); + if (siz) switch (cimg::lowercase(axis)) { + case 'x' : _width = siz; _height = _depth = _spectrum = 1; break; + case 'y' : _height = siz; _width = _depth = _spectrum = 1; break; + case 'z' : _depth = siz; _width = _height = _spectrum = 1; break; + default : _spectrum = siz; _width = _height = _depth = 1; + } + return *this; + } + + //! Unroll pixel values along specified axis \newinstance. + CImg get_unroll(const char axis) const { + return (+*this).unroll(axis); + } + + //! Rotate image with arbitrary angle. + /** + \param angle Rotation angle, in degrees. + \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle \newinstance. + CImg get_rotate(const float angle, const unsigned int interpolation=1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res; + 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) { + case 1 : { // 90 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(y,hm1 - x,z,c); + } break; + case 2 : { // 180 deg + res.assign(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - x,hm1 - y,z,c); + } break; + case 3 : { // 270 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - y,x,z,c); + } break; + default : // 0 deg + return *this; + } + } else { // Generic angle + const float + rad = (float)(nangle*cimg::PI/180.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad), + ux = cimg::abs((_width - 1)*ca), uy = cimg::abs((_width - 1)*sa), + vx = cimg::abs((_height - 1)*sa), vy = cimg::abs((_height - 1)*ca), + w2 = 0.5f*(_width - 1), h2 = 0.5f*(_height - 1); + res.assign((int)cimg::round(1 + ux + vx),(int)cimg::round(1 + uy + vy),_depth,_spectrum); + const float rw2 = 0.5f*(res._width - 1), rh2 = 0.5f*(res._height - 1); + _rotate(res,nangle,interpolation,boundary_conditions,w2,h2,rw2,rh2); + } + return res; + } + + //! Rotate image with arbitrary angle, around a center point. + /** + \param angle Rotation angle, in degrees. + \param cx X-coordinate of the rotation center. + \param cy Y-coordinate of the rotation center. + \param interpolation Type of interpolation, { 0=nearest | 1=linear | 2=cubic | 3=mirror }. + \param boundary_conditions Boundary conditions, { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) { + return get_rotate(angle,cx,cy,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle, around a center point \newinstance. + CImg get_rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + _rotate(res,angle,interpolation,boundary_conditions,cx,cy,cx,cy); + return res; + } + + // [internal] Perform 2D rotation with arbitrary angle. + void _rotate(CImg& 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.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad); + + switch (boundary_conditions) { + case 3 : { // Mirror + + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const unsigned int interpolation, const unsigned int boundary_conditions) const { + if (is_empty()) return *this; + CImg res; + const float + w1 = _width - 1, h1 = _height - 1, d1 = _depth -1, + w2 = 0.5f*w1, h2 = 0.5f*h1, d2 = 0.5f*d1; + CImg R = CImg::rotation_matrix(u,v,w,angle); + const CImg + X = R*CImg(8,3,1,1, + 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), + zm, zM = X.get_shared_row(2).max_min(zm); + const int + dx = (int)cimg::round(xM - xm), + dy = (int)cimg::round(yM - ym), + dz = (int)cimg::round(zM - zm); + R.transpose(); + res.assign(1 + dx,1 + dy,1 + dz,_spectrum); + const float rw2 = 0.5f*dx, rh2 = 0.5f*dy, rd2 = 0.5f*dz; + _rotate(res,R,interpolation,boundary_conditions,w2,h2,d2,rw2,rh2,rd2); + return res; + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point. + /** + \param u X-coordinate of the 3D rotation axis. + \param v Y-coordinate of the 3D rotation axis. + \param w Z-coordinate of the 3D rotation axis. + \param 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 }. + \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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,cx,cy,cz,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const float cx, const float cy, const float cz, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + CImg R = CImg::rotation_matrix(u,v,w,-angle); + _rotate(res,R,interpolation,boundary_conditions,cx,cy,cz,cx,cy,cz); + return res; + } + + // [internal] Perform 3D rotation with arbitrary axis and angle. + void _rotate(CImg& res, const CImg& R, + const unsigned int interpolation, const unsigned int boundary_conditions, + const float w2, const float h2, const float d2, + const float rw2, const float rh2, const float rd2) const { + switch (boundary_conditions) { + case 3 : // Mirror + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + template + CImg& warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) { + return get_warp(warp,mode,interpolation,boundary_conditions).move_to(*this); + } + + //! Warp image content by a warping field \newinstance + template + CImg get_warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty() || !warp) return *this; + if (mode && !is_sameXYZ(warp)) + throw CImgArgumentException(_cimg_instance + "warp(): Instance and specified relative warping field (%u,%u,%u,%u,%p) " + "have different XYZ dimensions.", + cimg_instance, + warp._width,warp._height,warp._depth,warp._spectrum,warp._data); + + CImg res(warp._width,warp._height,warp._depth,_spectrum); + + if (warp._spectrum==1) { // 1D warping + if (mode>=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + cimg_pragma_openmp(parallel for 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),x + (float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),(float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)), Y = y + (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)), Y = (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++), + z + (float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = x + (int)cimg::round(*(ptrs0++)), + Y = y + (int)cimg::round(*(ptrs1++)), + Z = z + (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = (int)cimg::round(*(ptrs0++)), + Y = (int)cimg::round(*(ptrs1++)), + Z = (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) const { + if (is_empty() || _depth<2) return +*this; + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + const CImg + img_xy = get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1), + img_zy = get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).permute_axes("xzyc"). + resize(_depth,_height,1,-100,-1), + img_xz = get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1); + return CImg(_width + _depth,_height + _depth,1,_spectrum,cimg::min(img_xy.min(),img_zy.min(),img_xz.min())). + draw_image(0,0,img_xy).draw_image(img_xy._width,0,img_zy). + draw_image(0,img_xy._height,img_xz); + } + + //! Construct a 2D representation of a 3D image, with XY,XZ and YZ views \inplace. + CImg& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) { + if (_depth<2) return *this; + return get_projections2d(x0,y0,z0).move_to(*this); + } + + //! Crop image region. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param y0 = Y-coordinate of the upper-left crop rectangle corner. + \param z0 = Z-coordinate of the upper-left crop rectangle corner. + \param c0 = C-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param y1 = Y-coordinate of the lower-right crop rectangle corner. + \param z1 = Z-coordinate of the lower-right crop rectangle corner. + \param c1 = C-coordinate of the lower-right crop rectangle corner. + \param boundary_conditions = Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) { + return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,boundary_conditions).move_to(*this); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "crop(): Empty instance.", + cimg_instance); + const int + nx0 = x0 res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0); + if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum()) + switch (boundary_conditions) { + case 3 : { // Mirror + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for 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), + my = cimg::mod(ny0 + y,h2), + mz = cimg::mod(nz0 + z,d2), + mc = cimg::mod(nc0 + c,s2); + res(x,y,z,c) = (*this)(mx=(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 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 + res.fill((T)0).draw_image(-nx0,-ny0,-nz0,-nc0,*this); + } + else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this); + return res; + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int x1, const unsigned int boundary_conditions=0) { + return crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int x1, const unsigned int boundary_conditions=0) const { + return get_crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Autocrop image region, regarding the specified background value. + CImg& autocrop(const T& value, const char *const axes="czyx") { + if (is_empty()) return *this; + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + const CImg coords = _autocrop(value,axis); + if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels + else switch (axis) { + case 'x' : { + const int x0 = coords[0], x1 = coords[1]; + if (x0>=0 && x1>=0) crop(x0,x1); + } break; + case 'y' : { + const int y0 = coords[0], y1 = coords[1]; + if (y0>=0 && y1>=0) crop(0,y0,_width - 1,y1); + } break; + case 'z' : { + const int z0 = coords[0], z1 = coords[1]; + if (z0>=0 && z1>=0) crop(0,0,z0,_width - 1,_height - 1,z1); + } break; + default : { + const int c0 = coords[0], c1 = coords[1]; + if (c0>=0 && c1>=0) crop(0,0,0,c0,_width - 1,_height - 1,_depth - 1,c1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background value \newinstance. + CImg get_autocrop(const T& value, const char *const axes="czyx") const { + return (+*this).autocrop(value,axes); + } + + //! Autocrop image region, regarding the specified background color. + /** + \param color Color used for the crop. If \c 0, color is guessed. + \param axes Axes used for the crop. + **/ + CImg& autocrop(const T *const color=0, const char *const axes="zyx") { + if (is_empty()) return *this; + 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); + if (_width==w && _height==h && _depth==d && _spectrum==s) { + const CImg col2 = get_vector_at(w - 1,h - 1,d - 1); + autocrop(col2,axes); + } + return *this; + } + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + switch (axis) { + case 'x' : { + int x0 = width(), x1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'x'); + const int nx0 = coords[0], nx1 = coords[1]; + if (nx0>=0 && nx1>=0) { x0 = std::min(x0,nx0); x1 = std::max(x1,nx1); } + } + if (x0==width() && x1==-1) return assign(); else crop(x0,x1); + } break; + case 'y' : { + int y0 = height(), y1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'y'); + const int ny0 = coords[0], ny1 = coords[1]; + if (ny0>=0 && ny1>=0) { y0 = std::min(y0,ny0); y1 = std::max(y1,ny1); } + } + if (y0==height() && y1==-1) return assign(); else crop(0,y0,_width - 1,y1); + } break; + default : { + int z0 = depth(), z1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'z'); + const int nz0 = coords[0], nz1 = coords[1]; + if (nz0>=0 && nz1>=0) { z0 = std::min(z0,nz0); z1 = std::max(z1,nz1); } + } + if (z0==depth() && z1==-1) return assign(); else crop(0,0,z0,_width - 1,_height - 1,z1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background color \newinstance. + CImg get_autocrop(const T *const color=0, const char *const axes="zyx") const { + return (+*this).autocrop(color,axes); + } + + //! Autocrop image region, regarding the specified background color \overloading. + template 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)) { + case 'x' : { + int x0 = -1, x1 = -1; + cimg_forX(*this,x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); } + if (x0>=0) { + for (int x = width() - 1; x>=0; --x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); } + } + res = CImg::vector(x0,x1); + } break; + case 'y' : { + int y0 = -1, y1 = -1; + cimg_forY(*this,y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); } + if (y0>=0) { + for (int y = height() - 1; y>=0; --y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); } + } + res = CImg::vector(y0,y1); + } break; + case 'z' : { + int z0 = -1, z1 = -1; + cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); } + if (z0>=0) { + for (int z = depth() - 1; z>=0; --z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); } + } + res = CImg::vector(z0,z1); + } break; + default : { + int c0 = -1, c1 = -1; + cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); } + if (c0>=0) { + for (int c = spectrum() - 1; c>=0; --c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; } + } + res = CImg::vector(c0,c1); + } + } + return res; + } + + //! Return specified image column. + /** + \param x0 Image column. + **/ + CImg get_column(const int x0) const { + return get_columns(x0,x0); + } + + //! Return specified image column \inplace. + CImg& column(const int x0) { + return columns(x0,x0); + } + + //! Return specified range of image columns. + /** + \param x0 Starting image column. + \param x1 Ending image column. + **/ + CImg& columns(const int x0, const int x1) { + return get_columns(x0,x1).move_to(*this); + } + + //! Return specified range of image columns \inplace. + CImg get_columns(const int x0, const int x1) const { + return get_crop(x0,0,0,0,x1,height() - 1,depth() - 1,spectrum() - 1); + } + + //! Return specified image row. + CImg get_row(const int y0) const { + return get_rows(y0,y0); + } + + //! Return specified image row \inplace. + /** + \param y0 Image row. + **/ + CImg& row(const int y0) { + return rows(y0,y0); + } + + //! Return specified range of image rows. + /** + \param y0 Starting image row. + \param y1 Ending image row. + **/ + CImg get_rows(const int y0, const int y1) const { + return get_crop(0,y0,0,0,width() - 1,y1,depth() - 1,spectrum() - 1); + } + + //! Return specified range of image rows \inplace. + CImg& rows(const int y0, const int y1) { + return get_rows(y0,y1).move_to(*this); + } + + //! Return specified image slice. + /** + \param z0 Image slice. + **/ + CImg get_slice(const int z0) const { + return get_slices(z0,z0); + } + + //! Return specified image slice \inplace. + CImg& slice(const int z0) { + return slices(z0,z0); + } + + //! Return specified range of image slices. + /** + \param z0 Starting image slice. + \param z1 Ending image slice. + **/ + CImg get_slices(const int z0, const int z1) const { + return get_crop(0,0,z0,0,width() - 1,height() - 1,z1,spectrum() - 1); + } + + //! Return specified range of image slices \inplace. + CImg& slices(const int z0, const int z1) { + return get_slices(z0,z1).move_to(*this); + } + + //! Return specified image channel. + /** + \param c0 Image channel. + **/ + CImg get_channel(const int c0) const { + return get_channels(c0,c0); + } + + //! Return specified image channel \inplace. + CImg& channel(const int c0) { + return channels(c0,c0); + } + + //! Return specified range of image channels. + /** + \param c0 Starting image channel. + \param c1 Ending image channel. + **/ + CImg get_channels(const int c0, const int c1) const { + return get_crop(0,0,0,c0,width() - 1,height() - 1,depth() - 1,c1); + } + + //! Return specified range of image channels \inplace. + CImg& channels(const int c0, const int c1) { + return get_channels(c0,c1).move_to(*this); + } + + //! Return stream line of a 2D or 3D vector field. + CImg get_streamline(const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false) const { + if (_spectrum!=2 && _spectrum!=3) + throw CImgInstanceException(_cimg_instance + "streamline(): Instance is not a 2D or 3D vector field.", + cimg_instance); + if (_spectrum==2) { + if (is_oriented_only) { + typename CImg::_functor4d_streamline2d_oriented func(*this); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true, + 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.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.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.f,_height - 1.f,_depth - 1.f); + } + + //! Return stream line of a 3D vector field. + /** + \param func Vector field function. + \param x X-coordinate of the starting point of the streamline. + \param y Y-coordinate of the starting point of the streamline. + \param z Z-coordinate of the starting point of the streamline. + \param L Streamline length. + \param dl Streamline length increment. + \param interpolation_type Type of interpolation. + Can be { 0=nearest int | 1=linear | 2=2nd-order RK | 3=4th-order RK. }. + \param is_backward_tracking Tells if the streamline is estimated forward or backward. + \param is_oriented_only Tells if the direction of the vectors must be ignored. + \param x0 X-coordinate of the first bounding-box vertex. + \param y0 Y-coordinate of the first bounding-box vertex. + \param z0 Z-coordinate of the first bounding-box vertex. + \param x1 X-coordinate of the second bounding-box vertex. + \param y1 Y-coordinate of the second bounding-box vertex. + \param z1 Z-coordinate of the second bounding-box vertex. + **/ + template + static CImg streamline(const tfunc& func, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + if (dl<=0) + throw CImgArgumentException("CImg<%s>::streamline(): Invalid specified integration length %g " + "(should be >0).", + pixel_type(), + dl); + + const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1); + if (L<=0 || (is_bounded && (xx1 || yy1 || zz1))) return CImg(); + const unsigned int size_L = (unsigned int)cimg::round(L/dl + 1); + CImg coordinates(size_L,3); + const float dl2 = dl/2; + float + *ptr_x = coordinates.data(0,0), + *ptr_y = coordinates.data(0,1), + *ptr_z = coordinates.data(0,2), + pu = (float)(dl*func(x,y,z,0)), + pv = (float)(dl*func(x,y,z,1)), + pw = (float)(dl*func(x,y,z,2)), + X = x, Y = y, Z = z; + + switch (interpolation_type) { + case 0 : { // Nearest integer interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + const int + xi = (int)(X>0?X + 0.5f:X - 0.5f), + yi = (int)(Y>0?Y + 0.5f:Y - 0.5f), + zi = (int)(Z>0?Z + 0.5f:Z - 0.5f); + float + u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)), + v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)), + w = (float)(dl*func((float)xi,(float)yi,(float)zi,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 1 : { // First-order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u = (float)(dl*func(X,Y,Z,0)), + v = (float)(dl*func(X,Y,Z,1)), + w = (float)(dl*func(X,Y,Z,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 2 : { // Second order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u = (float)(dl*func(X + u0,Y + v0,Z + w0,0)), + v = (float)(dl*func(X + u0,Y + v0,Z + w0,1)), + w = (float)(dl*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + default : { // Fourth order interpolation + cimg_forX(coordinates,x) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,0)), + v1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,1)), + w1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; } + float + u2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,0)), + v2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,1)), + w2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; } + float + u3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,0)), + v3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,1)), + w3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; } + const float + u = (u0 + u3)/3 + (u1 + u2)/1.5f, + v = (v0 + v3)/3 + (v1 + v2)/1.5f, + w = (w0 + w3)/3 + (w1 + w2)/1.5f; + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } + } + if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0); + return coordinates; + } + + //! Return stream line of a 3D vector field \overloading. + static CImg streamline(const char *const expression, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=true, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + _functor4d_streamline_expr func(expression); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1); + } + + struct _functor4d_streamline2d_directed { + const CImg& ref; + _functor4d_streamline2d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0; + } + }; + + struct _functor4d_streamline3d_directed { + const CImg& ref; + _functor4d_streamline3d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref._linear_atXYZ(x,y,z,c); + } + }; + + struct _functor4d_streamline2d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline2d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,1,2); } + ~_functor4d_streamline2d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign2d(i,j) \ + if (I(i,j,0)*I(0,0,0) + I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z; + const float + dx = x - xi, + dy = y - yi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1); + I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1); + I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1); + I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1); + _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1); + } + return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0; + } + }; + + struct _functor4d_streamline3d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline3d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,2,3); } + ~_functor4d_streamline3d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0) + I(i,j,k,1)*I(0,0,0,1) + I(i,j,k,2)*I(0,0,0,2)<0) { \ + I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z - (z>=0?0:1), nzi = zi + 1; + const float + dx = x - xi, + dy = y - yi, + dz = z - zi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + if (zi<0) zi = 0; + if (nzi<0) nzi = 0; + if (zi>=ref.depth()) zi = ref.depth() - 1; + if (nzi>=ref.depth()) nzi = ref.depth() - 1; + I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1); + I(0,0,0,2) = (float)ref(xi,yi,zi,2); I(1,0,0,0) = (float)ref(nxi,yi,zi,0); + I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2); + I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1); + I(1,1,0,2) = (float)ref(nxi,nyi,zi,2); I(0,1,0,0) = (float)ref(xi,nyi,zi,0); + I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,nyi,zi,2); + I(0,0,1,0) = (float)ref(xi,yi,nzi,0); I(0,0,1,1) = (float)ref(xi,yi,nzi,1); + I(0,0,1,2) = (float)ref(xi,yi,nzi,2); I(1,0,1,0) = (float)ref(nxi,yi,nzi,0); + I(1,0,1,1) = (float)ref(nxi,yi,nzi,1); I(1,0,1,2) = (float)ref(nxi,yi,nzi,2); + I(1,1,1,0) = (float)ref(nxi,nyi,nzi,0); I(1,1,1,1) = (float)ref(nxi,nyi,nzi,1); + I(1,1,1,2) = (float)ref(nxi,nyi,nzi,2); I(0,1,1,0) = (float)ref(xi,nyi,nzi,0); + I(0,1,1,1) = (float)ref(xi,nyi,nzi,1); I(0,1,1,2) = (float)ref(xi,nyi,nzi,2); + _cimg_vecalign3d(1,0,0); _cimg_vecalign3d(1,1,0); _cimg_vecalign3d(0,1,0); + _cimg_vecalign3d(0,0,1); _cimg_vecalign3d(1,0,1); _cimg_vecalign3d(1,1,1); _cimg_vecalign3d(0,1,1); + } + return (float)pI->_linear_atXYZ(dx,dy,dz,c); + } + }; + + struct _functor4d_streamline_expr { + _cimg_math_parser *mp; + ~_functor4d_streamline_expr() { mp->end(); delete mp; } + _functor4d_streamline_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,"streamline",CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)(*mp)(x,y,z,c); + } + }; + + //! Return a shared-memory image referencing a range of pixels of the image instance. + /** + \param x0 X-coordinate of the starting pixel. + \param x1 X-coordinate of the ending pixel. + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_points(const unsigned int x0, const unsigned int x1, + const unsigned int y0=0, const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(x0,y0,z0,c0), + end = (unsigned int)offset(x1,y0,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_points(): Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).", + cimg_instance, + x0,x1,y0,z0,c0); + + return CImg(_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); + 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 rows of the image instance. + /** + \param y0 Y-coordinate of the starting row. + \param y1 Y-coordinate of the ending row. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_rows(const unsigned int y0, const unsigned int y1, + const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,y0,z0,c0), + end = (unsigned int)offset(0,y1,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_rows(): Invalid request of a shared-memory subset " + "(0->%u,%u->%u,%u,%u).", + cimg_instance, + _width - 1,y0,y1,z0,c0); + + return CImg(_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); + 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 one row of the image instance. + /** + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared-memory image referencing one row of the image instance \const. + const CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared memory image referencing a range of slices of the image instance. + /** + \param z0 Z-coordinate of the starting slice. + \param z1 Z-coordinate of the ending slice. + \param c0 C-coordinate. + **/ + CImg get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,0,z0,c0), + end = (unsigned int)offset(0,0,z1,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_slices(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,%u->%u,%u).", + cimg_instance, + _width - 1,_height - 1,z0,z1,c0); + + return CImg(_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); + 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 one slice of the image instance. + /** + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing one slice of the image instance \const. + const CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) const { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing a range of channels of the image instance. + /** + \param c0 C-coordinate of the starting channel. + \param c1 C-coordinate of the ending channel. + **/ + CImg get_shared_channels(const unsigned int c0, const unsigned int c1) { + const unsigned int + beg = (unsigned int)offset(0,0,0,c0), + end = (unsigned int)offset(0,0,0,c1); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_channels(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,0->%u,%u->%u).", + cimg_instance, + _width - 1,_height - 1,_depth - 1,c0,c1); + + return CImg(_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); + 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 one channel of the image instance. + /** + \param c0 C-coordinate. + **/ + CImg get_shared_channel(const unsigned int c0) { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory image referencing one channel of the image instance \const. + const CImg get_shared_channel(const unsigned int c0) const { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory version of the image instance. + CImg get_shared() { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Return a shared-memory version of the image instance \const. + const CImg get_shared() const { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Split image into a list along specified axis. + /** + \param axis Splitting axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param nb Number of splitted parts. + \note + - If \c nb==0, instance image is splitted into blocs of egal values along the specified axis. + - If \c nb<=0, instance image is splitted into blocs of -\c nb pixel wide. + - If \c nb>0, instance image is splitted into \c nb blocs. + **/ + CImgList 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 + 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>=(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); + } break; + case 'y': { + if (_height>dp) { + res.assign(_height/dp + (_height%dp?1:0),1,1); + const unsigned int pe = _height - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'z': { + if (_depth>dp) { + res.assign(_depth/dp + (_depth%dp?1:0),1,1); + const unsigned int pe = _depth - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'c' : { + if (_spectrum>dp) { + res.assign(_spectrum/dp + (_spectrum%dp?1:0),1,1); + const unsigned int pe = _spectrum - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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 + const unsigned int siz = _axis=='x'?_width:_axis=='y'?_height:_axis=='z'?_depth:_axis=='c'?_spectrum:0; + if ((unsigned int)nb>siz) + throw CImgArgumentException(_cimg_instance + "get_split(): Instance cannot be split along %c-axis into %u blocs.", + cimg_instance, + axis,nb); + if (nb==1) res.assign(*this); + else { + int err = (int)siz; + unsigned int _p = 0; + switch (_axis) { + case 'x' : { + cimg_forX(*this,p) if ((err-=nb)<=0) { + get_crop(_p,0,0,0,p,_height - 1,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'y' : { + cimg_forY(*this,p) if ((err-=nb)<=0) { + get_crop(0,_p,0,0,_width - 1,p,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'z' : { + cimg_forZ(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,_p,0,_width - 1,_height - 1,p,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'c' : { + cimg_forC(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,0,_p,_width - 1,_height - 1,_depth - 1,p).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } + } + } + } else { // Split by egal values according to specified axis + T current = *_data; + switch (_axis) { + case 'x' : { + int i0 = 0; + cimg_forX(*this,i) + if ((*this)(i)!=current) { get_columns(i0,i - 1).move_to(res); i0 = i; current = (*this)(i); } + get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + int i0 = 0; + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { get_rows(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,i); } + get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + int i0 = 0; + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { get_slices(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,i); } + get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + int i0 = 0; + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { get_channels(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,0,i); } + get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + longT i0 = 0; + cimg_foroff(*this,i) + if ((*this)[i]!=current) { + CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); + i0 = (longT)i; current = (*this)[i]; + } + CImg(_data + i0,1,(unsigned int)(size() - i0)).move_to(res); + } + } + } + return res; + } + + //! Split image into a list of sub-images, according to a specified splitting value sequence and optionally axis. + /** + \param values Splitting value sequence. + \param axis Axis along which the splitting is performed. Can be '0' to ignore axis. + \param keep_values Tells if the splitting sequence must be kept in the splitted blocs. + **/ + template + CImgList get_split(const CImg& values, const char axis=0, const bool keep_values=true) const { + 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 + const T value = (T)*values; + switch (_axis) { + case 'x' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_width && (*this)(i)==value) ++i; + if (i>i0) { if (keep_values) get_columns(i0,i - 1).move_to(res); i0 = i; } + while (i<_width && (*this)(i)!=value) ++i; + if (i>i0) { get_columns(i0,i - 1).move_to(res); i0 = i; } + } while (i<_width); + } break; + case 'y' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_height && (*this)(0,i)==value) ++i; + if (i>i0) { if (keep_values) get_rows(i0,i - 1).move_to(res); i0 = i; } + while (i<_height && (*this)(0,i)!=value) ++i; + if (i>i0) { get_rows(i0,i - 1).move_to(res); i0 = i; } + } while (i<_height); + } break; + case 'z' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_depth && (*this)(0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_slices(i0,i - 1).move_to(res); i0 = i; } + while (i<_depth && (*this)(0,0,i)!=value) ++i; + if (i>i0) { get_slices(i0,i - 1).move_to(res); i0 = i; } + } while (i<_depth); + } break; + case 'c' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_spectrum && (*this)(0,0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_channels(i0,i - 1).move_to(res); i0 = i; } + while (i<_spectrum && (*this)(0,0,0,i)!=value) ++i; + if (i>i0) { get_channels(i0,i - 1).move_to(res); i0 = i; } + } while (i<_spectrum); + } break; + default : { + const ulongT siz = size(); + ulongT i0 = 0, i = 0; + do { + while (ii0) { 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; + if (i>i1) { + if (i1>i0) get_columns(i0,i1 - 1).move_to(res); + if (keep_values) get_columns(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_width); + if (i0<_width) get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,i)==*values) { + i1 = i; j = 0; + while (i<_height && (*this)(0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_rows(i0,i1 - 1).move_to(res); + if (keep_values) get_rows(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_height); + if (i0<_height) get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,i)==*values) { + i1 = i; j = 0; + while (i<_depth && (*this)(0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_slices(i0,i1 - 1).move_to(res); + if (keep_values) get_slices(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_depth); + if (i0<_depth) get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,0,i)==*values) { + i1 = i; j = 0; + while (i<_spectrum && (*this)(0,0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_channels(i0,i1 - 1).move_to(res); + if (keep_values) get_channels(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_spectrum); + if (i0<_spectrum) get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + ulongT i0 = 0, i1 = 0, i = 0; + const ulongT siz = size(); + do { + if ((*this)[i]==*values) { + i1 = i; j = 0; + while (i=vsiz) j = 0; } + i-=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); + i0 = i; + } else ++i; + } else ++i; + } while (i(_data + i0,1,(unsigned int)(siz - i0)).move_to(res); + } break; + } + } + return res; + } + + //! Append two images along specified axis. + /** + \param img Image to append with instance image. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Append alignment in \c [0,1]. + **/ + template + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,true).insert(img).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \specialization. + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,img,true).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \const. + template + CImg<_cimg_Tt> get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList<_cimg_Tt>(*this,true).insert(img).get_append(axis,align); + } + + //! Append two images along specified axis \specialization. + CImg get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList(*this,img,true).get_append(axis,align); + } + + //@} + //--------------------------------------- + // + //! \name Filtering / Transforms + //@{ + //--------------------------------------- + + //! Correlate image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The correlation of the image instance \p *this by the kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} (*this)(x + i,y + j,z + k)*kernel(i,j,k). + **/ + template + CImg& correlate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_correlate(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + template + 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); + } + + //! 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; + 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>=(cimg_openmp_sizefactor)*32768, + is_outer_parallel = res_size>=(cimg_openmp_sizefactor)*32768; + _cimg_abort_init_omp; + cimg_abort_init; + + if (kernel._width==kernel._height && + ((kernel._depth==1 && kernel._width<=6) || (kernel._depth==kernel._width && kernel._width<=3))) { + + // 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); + + } 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(); + + 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; + } + } + } + } + + 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 cimg_openmp_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); + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0, N = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const Ttfloat _val = (Ttfloat)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); + val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm); + N+=_val*_val; + } + N*=M; + res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0); + } + } _cimg_abort_catch_omp2 + } else { // Classical correlation + cimg_pragma_openmp(parallel for cimg_openmp_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; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img._atXYZ(x + xm,y + ym,z + zm)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img.atXYZ(x + xm,y + ym,z + zm,0,(T)0)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + } + cimg_abort_test; + return res; + } + + //! Convolve image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The result \p res of the convolution of an image \p img by a kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*kernel(i,j,k) + **/ + template + CImg& convolve(const CImg& kernel, const bool boundary_conditions=true, const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_convolve(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + //! Convolve image by a kernel \newinstance. + template + 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); + } + + //! Cumulate image values, optionally along specified axis. + /** + \param axis Cumulation axis. Set it to 0 to cumulate all values globally without taking axes into account. + **/ + CImg& cumulate(const char axis=0) { + switch (cimg::lowercase(axis)) { + case 'x' : + 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; + cimg_forX(*this,x) { cumul+=(Tlong)*ptrd; *(ptrd++) = (T)cumul; } + } + break; + case 'y' : { + const ulongT w = (ulongT)_width; + 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; + cimg_forY(*this,y) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=w; } + } + } break; + case 'z' : { + const ulongT wh = (ulongT)_width*_height; + cimg_pragma_openmp(parallel for 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; + cimg_forZ(*this,z) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=wh; } + } + } break; + case 'c' : { + const ulongT whd = (ulongT)_width*_height*_depth; + cimg_pragma_openmp(parallel for 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 + Tlong cumul = (Tlong)0; + cimg_for(*this,ptrd,T) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; } + } + } + return *this; + } + + //! Cumulate image values, optionally along specified axis \newinstance. + CImg get_cumulate(const char axis=0) const { + return CImg(*this,false).cumulate(axis); + } + + //! Cumulate image values, along specified axes. + /** + \param axes Cumulation axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& cumulate(const char *const axes) { + for (const char *s = axes; *s; ++s) cumulate(*s); + return *this; + } + + //! Cumulate image values, along specified axes \newinstance. + CImg get_cumulate(const char *const axes) const { + return CImg(*this,false).cumulate(axes); + } + + //! Erode image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the erosion in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_erode(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Erode image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel) return *this; + if (!is_real && kernel==0) return CImg(width(),height(),depth(),spectrum(),0); + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2, + mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real erosion + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx1 + xm,my1 + ym,mz1 + zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) - mval); + if (cval=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); + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx1 + xm,my1 + ym,mz1 + zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval=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)) { + 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 + 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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val get_erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).erode(sx,sy,sz); + } + + //! Erode the image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& erode(const unsigned int s) { + return erode(s,s,s); + } + + //! Erode the image by a square structuring element of specified size \newinstance. + CImg get_erode(const unsigned int s) const { + return (+*this).erode(s); + } + + //! Dilate image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the dilation in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_dilate(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Dilate image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel || (!is_real && kernel==0)) return *this; + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx1 = kernel.width()/2, my1 = kernel.height()/2, mz1 = kernel.depth()/2, + mx2 = kernel.width() - mx1 - 1, my2 = kernel.height() - my1 - 1, mz2 = kernel.depth() - mz1 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx2 - xm,my2 - ym,mz2 - zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forYZ(*this,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,0,(T)0) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } else { // Binary dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx2 - xm,my2 - ym,mz2 - zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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,0,(T)0); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + cimg_abort_test; + return res; + } + + //! Dilate image by a rectangular structuring element of specified size. + /** + \param sx Width of the structuring element. + \param sy Height of the structuring element. + \param sz Depth of the structuring element. + **/ + CImg& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { + if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this; + if (sx>1 && _width>1) { // Along X-axis + const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(0,y,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sy>1 && _height>1) { // Along Y-axis + const int L = height(), off = width(), s = (int)sy, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,0,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sz>1 && _depth>1) { // Along Z-axis + const int L = depth(), off = width()*height(), s = (int)sz, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,y,0,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + return *this; + } + + //! Dilate image by a rectangular structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).dilate(sx,sy,sz); + } + + //! Dilate image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& dilate(const unsigned int s) { + return dilate(s,s,s); + } + + //! Dilate image by a square structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int s) const { + return (+*this).dilate(s); + } + + //! Compute watershed transform. + /** + \param priority Priority map. + \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity + in 2D case, and between 6(false)- or 26(true)-connectivity in 3D case. + \note Non-zero values of the instance instance are propagated to zero-valued ones according to + specified the priority map. + **/ + template + CImg& watershed(const CImg& priority, const bool is_high_connectivity=false) { +#define _cimg_watershed_init(cond,X,Y,Z) \ + if (cond && !(*this)(X,Y,Z)) Q._priority_queue_insert(labels,sizeQ,priority(X,Y,Z),X,Y,Z,nb_seeds) + +#define _cimg_watershed_propagate(cond,X,Y,Z) \ + if (cond) { \ + if ((*this)(X,Y,Z)) { \ + ns = labels(X,Y,Z) - 1; xs = seeds(ns,0); ys = seeds(ns,1); zs = seeds(ns,2); \ + d = cimg::sqr((float)x - xs) + cimg::sqr((float)y - ys) + cimg::sqr((float)z - zs); \ + if (d labels(_width,_height,_depth,1,0), seeds(64,3); + CImg::type> Q; + unsigned int sizeQ = 0; + int px, nx, py, ny, pz, nz; + bool is_px, is_nx, is_py, is_ny, is_pz, is_nz; + const bool is_3d = _depth>1; + + // Find seed points and insert them in priority queue. + unsigned int nb_seeds = 0; + const T *ptrs = _data; + cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) { // 3D version + if (nb_seeds>=seeds._width) seeds.resize(2*seeds._width,3,1,1,0); + seeds(nb_seeds,0) = x; seeds(nb_seeds,1) = y; seeds(nb_seeds++,2) = z; + px = x - 1; nx = x + 1; + py = y - 1; ny = y + 1; + pz = z - 1; nz = z + 1; + is_px = px>=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz::inf(); + T label = (T)0; + _cimg_watershed_propagate(is_px,px,y,z); + _cimg_watershed_propagate(is_nx,nx,y,z); + _cimg_watershed_propagate(is_py,x,py,z); + _cimg_watershed_propagate(is_ny,x,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_pz,x,y,pz); + _cimg_watershed_propagate(is_nz,x,y,nz); + } + if (is_high_connectivity) { + _cimg_watershed_propagate(is_px && is_py,px,py,z); + _cimg_watershed_propagate(is_nx && is_py,nx,py,z); + _cimg_watershed_propagate(is_px && is_ny,px,ny,z); + _cimg_watershed_propagate(is_nx && is_ny,nx,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_px && is_pz,px,y,pz); + _cimg_watershed_propagate(is_nx && is_pz,nx,y,pz); + _cimg_watershed_propagate(is_px && is_nz,px,y,nz); + _cimg_watershed_propagate(is_nx && is_nz,nx,y,nz); + _cimg_watershed_propagate(is_py && is_pz,x,py,pz); + _cimg_watershed_propagate(is_ny && is_pz,x,ny,pz); + _cimg_watershed_propagate(is_py && is_nz,x,py,nz); + _cimg_watershed_propagate(is_ny && is_nz,x,ny,nz); + _cimg_watershed_propagate(is_px && is_py && is_pz,px,py,pz); + _cimg_watershed_propagate(is_nx && is_py && is_pz,nx,py,pz); + _cimg_watershed_propagate(is_px && is_ny && is_pz,px,ny,pz); + _cimg_watershed_propagate(is_nx && is_ny && is_pz,nx,ny,pz); + _cimg_watershed_propagate(is_px && is_py && is_nz,px,py,nz); + _cimg_watershed_propagate(is_nx && is_py && is_nz,nx,py,nz); + _cimg_watershed_propagate(is_px && is_ny && is_nz,px,ny,nz); + _cimg_watershed_propagate(is_nx && is_ny && is_nz,nx,ny,nz); + } + } + (*this)(x,y,z) = label; + labels(x,y,z) = ++nmin; + } + return *this; + } + + //! Compute watershed transform \newinstance. + template + CImg get_watershed(const CImg& priority, const bool is_high_connectivity=false) const { + return (+*this).watershed(priority,is_high_connectivity); + } + + // [internal] Insert/Remove items in priority queue, for watershed/distance transforms. + template + bool _priority_queue_insert(CImg& is_queued, unsigned int& siz, const tv value, + const unsigned int x, const unsigned int y, const unsigned int z, + const unsigned int n=1) { + if (is_queued(x,y,z)) return false; + is_queued(x,y,z) = (tq)n; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; + (*this)(siz - 1,1) = (T)x; + (*this)(siz - 1,2) = (T)y; + (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); + cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); + cimg::swap((*this)(pos,3),(*this)(par,3)); + } + return true; + } + + CImg& _priority_queue_remove(unsigned int& siz) { + (*this)(0,0) = (*this)(--siz,0); + (*this)(0,1) = (*this)(siz,1); + (*this)(0,2) = (*this)(siz,2); + (*this)(0,3) = (*this)(siz,3); + const float value = (*this)(0,0); + for (unsigned int pos = 0, left = 0, right = 0; + ((right=2*(pos + 1),(left=right - 1))(*this)(right,0)) { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } else { + cimg::swap((*this)(pos,0),(*this)(right,0)); + cimg::swap((*this)(pos,1),(*this)(right,1)); + cimg::swap((*this)(pos,2),(*this)(right,2)); + cimg::swap((*this)(pos,3),(*this)(right,3)); + pos = right; + } + } else { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } + } + return *this; + } + + //! Apply recursive Deriche filter. + /** + \param sigma Standard deviation of the filter. + \param order Order of the filter. Can be { 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 }. + **/ + CImg& deriche(const float sigma, const unsigned int order=0, const char axis='x', + const bool boundary_conditions=true) { +#define _cimg_deriche_apply \ + CImg Y(N); \ + Tfloat *ptrY = Y._data, yb = 0, yp = 0; \ + T xp = (T)0; \ + if (boundary_conditions) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \ + for (int m = 0; m=0; --n) { \ + const T xc = *(ptrX-=off); \ + const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \ + xa = xn; xn = xc; ya = yn; yn = yc; \ + *ptrX = (T)(*(--ptrY)+yc); \ + } + const char naxis = cimg::lowercase(axis); + const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + if (is_empty() || (nsigma<0.1f && !order)) return *this; + const float + nnsigma = nsigma<0.1f?0.1f:nsigma, + alpha = 1.695f/nnsigma, + ema = (float)std::exp(-alpha), + ema2 = (float)std::exp(-2*alpha), + b1 = -2*ema, + b2 = ema2; + float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0; + switch (order) { + case 0 : { + const float k = (1-ema)*(1-ema)/(1 + 2*alpha*ema-ema2); + a0 = k; + a1 = k*(alpha - 1)*ema; + a2 = k*(alpha + 1)*ema; + a3 = -k*ema2; + } break; + case 1 : { + const float k = -(1-ema)*(1-ema)*(1-ema)/(2*(ema + 1)*ema); + a0 = a3 = 0; + a1 = k*ema; + a2 = -a1; + } break; + case 2 : { + const float + ea = (float)std::exp(-alpha), + k = -(ema2 - 1)/(2*alpha*ema), + kn = (-2*(-1 + 3*ea - 3*ea*ea + ea*ea*ea)/(3*ea + 1 + 3*ea*ea + ea*ea*ea)); + a0 = kn; + a1 = -kn*(1 + k*alpha)*ema; + a2 = kn*(1 - k*alpha)*ema; + a3 = -kn*ema2; + } break; + default : + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified filter order %u " + "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).", + cimg_instance, + order); + } + coefp = (a0 + a1)/(1 + b1 + b2); + coefn = (a2 + a3)/(1 + b1 + b2); + switch (naxis) { + case 'x' : { + const int N = width(); + const ulongT off = 1U; + cimg_pragma_openmp(parallel for 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 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 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 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; } + } + } + return *this; + } + + //! 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 { + return CImg(*this,false).deriche(sigma,order,axis,boundary_conditions); + } + + // [internal] Apply a recursive filter (used by CImg::vanvliet()). + /* + \param ptr the pointer of the data + \param filter the coefficient of the filter in the following order [n,n - 1,n - 2,n - 3]. + \param N size of the data + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive, 2nd derivative, 3rd derivative + \param boundary_conditions Boundary conditions. Can be { 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 double + 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. - 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.) * (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. - 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; + } + } 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; + } + } 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; + } + } break; + } + } + + //! Van Vliet recursive Gaussian filter. + /** + \param sigma standard deviation of the Gaussian filter + \param order the order of the filter 0,1,2,3 + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \note dirichlet boundary condition has a strange behavior + + I.T. Young, L.J. van Vliet, M. van Ginkel, Recursive Gabor filtering. + IEEE Trans. Sig. Proc., vol. 50, pp. 2799-2805, 2002. + + (this is an improvement over Young-Van Vliet, Sig. Proc. 44, 1995) + + Boundary conditions (only for order 0) using Triggs matrix, from + B. Triggs and M. Sdika. Boundary conditions for Young-van Vliet + recursive filtering. IEEE Trans. Signal Processing, + vol. 54, pp. 2365-2367, 2006. + **/ + CImg& vanvliet(const float sigma, const unsigned int order, const char axis='x', + const bool boundary_conditions=true) { + if (is_empty()) return *this; + 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; + const double + nnsigma = nsigma<0.5f?0.5f:nsigma, + m0 = 1.16680, m1 = 1.10783, m2 = 1.40586, + m1sq = m1 * m1, m2sq = m2 * m2, + q = (nnsigma<3.556?-0.2568 + 0.5784*nnsigma + 0.0561*nnsigma*nnsigma:2.5091 + 0.9804*(nnsigma - 3.556)), + qsq = q * q, + scale = (m0 + q) * (m1sq + m2sq + 2 * m1 * q + qsq), + b1 = -q * (2 * m0 * m1 + m1sq + m2sq + (2 * m0 + 4 * m1) * q + 3 * qsq) / scale, + b2 = qsq * (m0 + 2 * m1 + 3 * q) / scale, + b3 = -qsq * q / scale, + B = ( m0 * (m1sq + m2sq) ) / scale; + double filter[4]; + filter[0] = B; filter[1] = -b1; filter[2] = -b2; filter[3] = -b3; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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; + } + + //! 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 { + return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions); + } + + //! Blur image. + /** + \param sigma_x Standard deviation of the blur, along the X-axis. + \param sigma_y Standard deviation of the blur, along the Y-axis. + \param sigma_z Standard deviation of the blur, along the Z-axis. + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \param is_gaussian Tells if the blur uses a gaussian (\c true) or quasi-gaussian (\c false) kernel. + \note + - The blur is computed as a 0-order Deriche filter. This is not a gaussian blur. + - This is a recursive algorithm, not depending on the values of the standard deviations. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) { + if (is_empty()) return *this; + if (is_gaussian) { + if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions); + if (_height>1) vanvliet(sigma_y,0,'y',boundary_conditions); + if (_depth>1) vanvliet(sigma_z,0,'z',boundary_conditions); + } else { + if (_width>1) deriche(sigma_x,0,'x',boundary_conditions); + if (_height>1) deriche(sigma_y,0,'y',boundary_conditions); + if (_depth>1) deriche(sigma_z,0,'z',boundary_conditions); + } + return *this; + } + + //! Blur image \newinstance. + CImg get_blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*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. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) { + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur(nsigma,nsigma,nsigma,boundary_conditions,is_gaussian); + } + + //! Blur image isotropically \newinstance. + CImg get_blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*this,false).blur(sigma,boundary_conditions,is_gaussian); + } + + //! Blur image anisotropically, directed by a field of diffusion tensors. + /** + \param G Field of square roots of diffusion tensors/vectors used to drive the smoothing. + \param amplitude Amplitude of the smoothing. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + template + CImg& blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=1) { + + // Check arguments and init variables + if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6)) + throw CImgArgumentException(_cimg_instance + "blur_anisotropic(): Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).", + cimg_instance, + G._width,G._height,G._depth,G._spectrum,G._data); + if (is_empty() || dl<0) return *this; + const float namplitude = amplitude>=0?amplitude:-amplitude*cimg::max(_width,_height,_depth)/100; + 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; + + 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 + cimg_forZC(*this,z,c) { + cimg_abort_test; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + veloc = (Tfloat)(G(x,y,0,0)*ixx + 2*G(x,y,0,1)*ixy + G(x,y,0,2)*iyy); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + } + if (veloc_max>0) *this+=(velocity*=dl/veloc_max); + } + } else { // LIC-based smoothing + const ulongT whd = (ulongT)_width*_height*_depth; + const float sqrt2amplitude = (float)std::sqrt(2*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.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.f:datmp; + for (float theta = 0; theta<360; (theta+=da2),++N) { + const float + thetar = (float)(theta*cimg::PI/180), + vx = (float)(std::cos(thetar)*std::cos(phir)), + vy = (float)(std::sin(thetar)*std::cos(phir)), + vz = (float)std::sin(phir); + const t + *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2), + *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(0,0,0,3); + cimg_forXYZ(G,xg,yg,zg) { + const t a = *(pa++), b = *(pb++), c = *(pc++), d = *(pd++), e = *(pe++), f = *(pf++); + const float + u = (float)(a*vx + b*vy + c*vz), + v = (float)(b*vx + d*vy + e*vz), + w = (float)(c*vx + e*vy + f*vz), + n = 1e-5f + cimg::hypot(u,v,w), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)(w*dln); + *(pd3++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for 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_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,z,3), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y, + Z = (float)z; + switch (interpolation_type) { + case 0 : { // Nearest neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f), + cz = (int)(Z + 0.5f); + const float + u = (float)W(cx,cy,cz,0), + v = (float)W(cx,cy,cz,1), + w = (float)W(cx,cy,cz,2); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,cz,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,cz,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u = (float)(W._linear_atXYZ(X,Y,Z,0)), + v = (float)(W._linear_atXYZ(X,Y,Z,1)), + w = (float)(W._linear_atXYZ(X,Y,Z,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + default : { // 2nd order Runge Kutta + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)), + v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)), + w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2)), + u = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,0)), + v = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,1)), + w = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + } + Tfloat *ptrd = res.data(x,y,z); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,z,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + } 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); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2); + cimg_forXY(G,xg,yg) { + const t a = *(pa++), b = *(pb++), c = *(pc++); + const float + u = (float)(a*vx + b*vy), + v = (float)(b*vx + c*vy), + n = std::max(1e-5f,cimg::hypot(u,v)), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && _height>=2) + firstprivate(val)) + cimg_forY(*this,y) _cimg_abort_try_omp2 { + cimg_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,0,2), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y; + switch (interpolation_type) { + case 0 : { // Nearest-neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f); + const float + u = (float)W(cx,cy,0,0), + v = (float)W(cx,cy,0,1); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u = (float)(W._linear_atXY(X,Y,0,0)), + v = (float)(W._linear_atXY(X,Y,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + default : { // 2nd-order Runge-kutta interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)), + v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)), + u = (float)(W._linear_atXY(X + u0,Y + v0,0,0)), + v = (float)(W._linear_atXY(X + u0,Y + v0,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } + } + Tfloat *ptrd = res.data(x,y); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,0,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + const Tfloat *ptrs = res._data; + cimg_for(*this,ptrd,T) { + const Tfloat val = *(ptrs++)/N; + *ptrd = valval_max?val_max:(T)val); + } + } + cimg_abort_test; + return *this; + } + + //! Blur image anisotropically, directed by a field of diffusion tensors \newinstance. + template + CImg get_blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way. + /** + \param amplitude Amplitude of the smoothing. + \param sharpness Sharpness. + \param anisotropy Anisotropy. + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + CImg& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) { + const float nalpha = alpha>=0?alpha:-alpha*cimg::max(_width,_height,_depth)/100; + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur_anisotropic(get_diffusion_tensors(sharpness,anisotropy,nalpha,nsigma,interpolation_type!=3), + amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way \newinstance. + CImg get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, + const float da=30, const float gauss_prec=2, + const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec, + interpolation_type,is_fast_approx); + } + + //! Blur image, with the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_x Amount of blur along the X-axis. + \param sigma_y Amount of blur along the Y-axis. + \param sigma_z Amount of blur along the Z-axis. + \param sigma_r Amount of blur along the value axis. + \param sampling_x Amount of downsampling along the X-axis used for the approximation. + Defaults (0) to sigma_x. + \param sampling_y Amount of downsampling along the Y-axis used for the approximation. + Defaults (0) to sigma_y. + \param sampling_z Amount of downsampling along the Z-axis used for the approximation. + Defaults (0) to sigma_z. + \param sampling_r Amount of downsampling along the value axis used for the approximation. + Defaults (0) to sigma_r. + \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006 + (extended for 3D volumetric images). + It is based on the reference implementation http://people.csail.mit.edu/jiawen/software/bilateralFilter.m + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_bilateral(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || (!sigma_x && !sigma_y && !sigma_z)) return *this; + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return blur(sigma_x,sigma_y,sigma_z); + const float + edge_delta = (float)(edge_max - edge_min), + _sigma_x = sigma_x>=0?sigma_x:-sigma_x*_width/100, + _sigma_y = sigma_y>=0?sigma_y:-sigma_y*_height/100, + _sigma_z = sigma_z>=0?sigma_z:-sigma_z*_depth/100, + _sigma_r = sigma_r>=0?sigma_r:-sigma_r*(edge_max - edge_min)/100, + _sampling_x = sampling_x?sampling_x:std::max(_sigma_x,1.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, + derived_sigma_z = _sigma_z / _sampling_z, + derived_sigma_r = _sigma_r / _sampling_r; + const int + padding_x = (int)(2*derived_sigma_x) + 1, + padding_y = (int)(2*derived_sigma_y) + 1, + padding_z = (int)(2*derived_sigma_z) + 1, + padding_r = (int)(2*derived_sigma_r) + 1; + const unsigned int + bx = (unsigned int)((_width - 1)/_sampling_x + 1 + 2*padding_x), + by = (unsigned int)((_height - 1)/_sampling_y + 1 + 2*padding_y), + bz = (unsigned int)((_depth - 1)/_sampling_z + 1 + 2*padding_z), + br = (unsigned int)(edge_delta/_sampling_r + 1 + 2*padding_r); + if (bx>0 || by>0 || bz>0 || br>0) { + const bool is_3d = (_depth>1); + if (is_3d) { // 3D version of the algorithm + CImg bgrid(bx,by,bz,br), bgridw(bx,by,bz,br); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); bgridw.fill(0); + cimg_forXYZ(*this,x,y,z) { + const T val = (*this)(x,y,z,c); + const float edge = (float)_guide(x,y,z); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + Z = (int)cimg::round(z/_sampling_z) + padding_z, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,Z,R)+=(float)val; + bgridw(X,Y,Z,R)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + bgridw.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + + cimg_pragma_openmp(parallel for 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 + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + Z = z/_sampling_z + padding_z, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZC(X,Y,Z,R), bval1 = bgridw._linear_atXYZC(X,Y,Z,R); + (*this)(x,y,z,c) = (T)(bval0/bval1); + } + } + } else { // 2D version of the algorithm + CImg bgrid(bx,by,br,2); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); + cimg_forXY(*this,x,y) { + const T val = (*this)(x,y,c); + const float edge = (float)_guide(x,y); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,R,0)+=(float)val; + bgrid(X,Y,R,1)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,0,true).blur(0,0,derived_sigma_r,false); + + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(size(),4096)) + cimg_forXY(*this,x,y) { + const float edge = (float)_guide(x,y); + const float + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1); + (*this)(x,y,c) = (T)(bval0/bval1); + } + } + } + } + return *this; + } + + //! Blur image, with the joint bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) const { + return CImg(*this,false).blur_bilateral(guide,sigma_x,sigma_y,sigma_z,sigma_r, + sampling_x,sampling_y,sampling_z,sampling_r); + } + + //! Blur image using the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_r Amount of blur along the value axis. + \param sampling_s Amount of downsampling along the XYZ-axes used for the approximation. Defaults to sigma_s. + \param sampling_r Amount of downsampling along the value axis used for the approximation. Defaults to sigma_r. + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) { + const float _sigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100; + return blur_bilateral(guide,_sigma_s,_sigma_s,_sigma_s,sigma_r,sampling_s,sampling_s,sampling_s,sampling_r); + } + + //! Blur image using the bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) const { + return CImg(*this,false).blur_bilateral(guide,sigma_s,sigma_r,sampling_s,sampling_r); + } + + // [internal] Apply a box filter (used by CImg::boxfilter() and CImg::blur_box()). + /* + \param ptr the pointer of the data + \param N size of the data + \param boxsize Size of the box filter (can be subpixel). + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive and 2nd derivative. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + */ + static void _cimg_blur_box_apply(T *ptr, const float boxsize, const int N, const ulongT off, + const int order, const bool boundary_conditions, + const unsigned int nb_iter) { + // Smooth. + if (boxsize>1 && nb_iter) { + const int w2 = (int)(boxsize - 1)/2; + const unsigned int winsize = 2*w2 + 1U; + const double frac = (boxsize - winsize)/2.; + CImg win(winsize); + for (unsigned int iter = 0; iter=N) return boundary_conditions?ptr[(N - 1)*off]:T(); + return ptr[x*off]; + } + + // Apply box filter of order 0,1,2. + /** + \param boxsize Size of the box window (can be subpixel) + \param order the order of the filter 0,1 or 2. + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \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 nb_iter=1) { + if (is_empty() || !boxsize || (boxsize<=1 && !order)) return *this; + const char naxis = cimg::lowercase(axis); + const float nboxsize = boxsize>=0?boxsize:-boxsize* + (naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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); + } + } + return *this; + } + + // 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 nb_iter=1) const { + return CImg(*this,false).boxfilter(boxsize,order,axis,boundary_conditions,nb_iter); + } + + //! Blur image with a box filter. + /** + \param boxsize_x Size of the box window, along the X-axis (can be subpixel). + \param boxsize_y Size of the box window, along the Y-axis (can be subpixel). + \param boxsize_z Size of the box window, along the Z-axis (can be subpixel). + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \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 nb_iter=1) { + if (is_empty()) return *this; + if (_width>1) boxfilter(boxsize_x,0,'x',boundary_conditions,nb_iter); + if (_height>1) boxfilter(boxsize_y,0,'y',boundary_conditions,nb_iter); + if (_depth>1) boxfilter(boxsize_z,0,'z',boundary_conditions,nb_iter); + return *this; + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, + const bool boundary_conditions=true) 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 + \see deriche(), vanvliet(). + **/ + CImg& blur_box(const float boxsize, const bool boundary_conditions=true) { + const float nboxsize = boxsize>=0?boxsize:-boxsize*cimg::max(_width,_height,_depth)/100; + return blur_box(nboxsize,nboxsize,nboxsize,boundary_conditions); + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize, const bool boundary_conditions=true) const { + return CImg(*this,false).blur_box(boxsize,boundary_conditions); + } + + //! Blur image, with the image guided filter. + /** + \param guide Image used to guide the smoothing process. + \param radius Spatial radius. If negative, it is expressed as a percentage of the largest image size. + \param regularization Regularization parameter. + If negative, it is expressed as a percentage of the guide value range. + \note This method implements the filtering algorithm described in: + He, Kaiming; Sun, Jian; Tang, Xiaoou, "Guided Image Filtering," Pattern Analysis and Machine Intelligence, + IEEE Transactions on , vol.35, no.6, pp.1397,1409, June 2013 + **/ + template + CImg& blur_guided(const CImg& guide, const float radius, const float regularization) { + return get_blur_guided(guide,radius,regularization).move_to(*this); + } + + //! Blur image, with the image guided filter \newinstance. + template + CImg get_blur_guided(const CImg& guide, const float radius, const float regularization) const { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_guided(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || !radius) return *this; + const int _radius = radius>=0?(int)radius:(int)(-radius*cimg::max(_width,_height,_depth)/100); + float _regularization = regularization; + if (regularization<0) { + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return *this; + _regularization = -regularization*(edge_max - edge_min)/100; + } + _regularization = std::max(_regularization,0.01f); + const unsigned int psize = (unsigned int)(1 + 2*_radius); + CImg + mean_p = get_blur_box(psize,true), + mean_I = guide.get_blur_box(psize,true).resize(mean_p), + cov_Ip = get_mul(guide).blur_box(psize,true)-=mean_p.get_mul(mean_I), + var_I = guide.get_sqr().blur_box(psize,true)-=mean_I.get_sqr(), + &a = cov_Ip.div(var_I+=_regularization), + &b = mean_p-=a.get_mul(mean_I); + a.blur_box(psize,true); + b.blur_box(psize,true); + return a.mul(guide)+=b; + } + + //! Blur image using patch-based space. + /** + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_p Amount of blur along the value axis. + \param patch_size Size of the 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, + const unsigned int lookup_size=4, const float smoothness=0, const bool is_fast_approx=true) { + if (is_empty() || !patch_size || !lookup_size) return *this; + return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,is_fast_approx).move_to(*this); + } + + //! Blur image using patch-based space \newinstance. + CImg get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, + const unsigned int lookup_size=4, const float smoothness=0, + const bool is_fast_approx=true) const { + +#define _cimg_blur_patch3d_fast(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) \ + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch3d(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \ + alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch2d_fast(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) \ + if (cimg::abs((Tfloat)img(x,y,0,0) - (Tfloat)img(p,q,0,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + +#define _cimg_blur_patch2d(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, \ + alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + + if (is_empty() || !patch_size || !lookup_size) return +*this; + CImg 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 float + nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100, + sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p, + Pnorm = P.size()*sigma_p2; + const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1; + const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size; + cimg::unused(N2,N3); + if (_depth>1) switch (patch_size) { // 3D + case 2 : if (is_fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch3d_fast(3) else _cimg_blur_patch3d(3) break; + default : { + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) + private(P,Q)) + cimg_forXYZ(res,x,y,z) { // Fast + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + if (res._width>=32 && res._height*res._depth>=4) firstprivate(P,Q)) + cimg_forXYZ(res,x,y,z) { // Exact + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { + (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, dz = (float)z - r, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } + } + } else switch (patch_size) { // 2D + case 2 : if (is_fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break; + case 4 : if (is_fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break; + case 5 : if (is_fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break; + case 6 : if (is_fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break; + case 7 : if (is_fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break; + case 8 : if (is_fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break; + case 9 : if (is_fast_approx) _cimg_blur_patch2d_fast(9) else _cimg_blur_patch2d(9) break; + default : { // Fast + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Fast + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) + if ((Tfloat)cimg::abs(img(x,y,0) - (Tfloat)img(p,q,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Exact + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { + (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,0,c) = (Tfloat)((*this)(x,y,c)); + } + } + } + return res; + } + + //! Blur image with the median filter. + /** + \param n Size of the median filter. + \param threshold Threshold used to discard pixels too far from the current pixel value in the median computation. + **/ + CImg& blur_median(const unsigned int n, const float threshold=0) { + if (!n) return *this; + return get_blur_median(n,threshold).move_to(*this); + } + + //! Blur image with the median filter \newinstance. + CImg get_blur_median(const unsigned int n, const float threshold=0) const { + if (is_empty() || n<=1) return +*this; + CImg res(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg::unused(ptrd); + const int hr = (int)n/2, hl = n - hr - 1; + if (res._depth!=1) { // 3D + if (threshold>0) + cimg_pragma_openmp(parallel for 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + const Tfloat val0 = (Tfloat)(*this)(x,y,z,c); + CImg values(n*n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXYZ(*this,nx0,ny0,nz0,nx1,ny1,nz1,p,q,r) + if (cimg::abs((*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 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + res(x,y,z,c) = get_crop(nx0,ny0,nz0,c,nx1,ny1,nz1,c).median(); + } + } else { + if (threshold>0) + 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + const Tfloat val0 = (Tfloat)(*this)(x,y,c); + CImg values(n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXY(*this,nx0,ny0,nx1,ny1,p,q) + if (cimg::abs((*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 { + const int + w1 = width() - 1, h1 = height() - 1, + w2 = width() - 2, h2 = height() - 2, + w3 = width() - 3, h3 = height() - 3, + w4 = width() - 4, h4 = height() - 4; + switch (n) { // Without threshold + case 3 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(9); + cimg_for_in3x3(*this,1,1,w2,h2,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6],I[7],I[8]); + cimg_for_borderXY(*this,x,y,1) + res(x,y,c) = get_crop(std::max(0,x - 1),std::max(0,y - 1),0,c, + std::min(w1,x + 1),std::min(h1,y + 1),0,c).median(); + } + } break; + case 5 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(25); + cimg_for_in5x5(*this,2,2,w3,h3,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4], + I[5],I[6],I[7],I[8],I[9], + I[10],I[11],I[12],I[13],I[14], + I[15],I[16],I[17],I[18],I[19], + I[20],I[21],I[22],I[23],I[24]); + cimg_for_borderXY(*this,x,y,2) + res(x,y,c) = get_crop(std::max(0,x - 2),std::max(0,y - 2),0,c, + std::min(w1,x + 2),std::min(h1,y + 2),0,c).median(); + } + } break; + case 7 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(49); + cimg_for_in7x7(*this,3,3,w4,h4,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6], + I[7],I[8],I[9],I[10],I[11],I[12],I[13], + I[14],I[15],I[16],I[17],I[18],I[19],I[20], + I[21],I[22],I[23],I[24],I[25],I[26],I[27], + I[28],I[29],I[30],I[31],I[32],I[33],I[34], + I[35],I[36],I[37],I[38],I[39],I[40],I[41], + I[42],I[43],I[44],I[45],I[46],I[47],I[48]); + cimg_for_borderXY(*this,x,y,3) + res(x,y,c) = get_crop(std::max(0,x - 3),std::max(0,y - 3),0,c, + std::min(w1,x + 3),std::min(h1,y + 3),0,c).median(); + } + } break; + default : { + 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, + nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + res(x,y,c) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median(); + } + } + } + } + } + return res; + } + + //! Sharpen image. + /** + \param amplitude Sharpening amplitude + \param sharpen_type Select sharpening method. Can be { false=inverse diffusion | true=shock filters }. + \param edge Edge threshold (shock filters only). + \param alpha Gradient smoothness (shock filters only). + \param sigma Tensor smoothness (shock filters only). + **/ + CImg& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) { + if (is_empty()) return *this; + T val_min, val_max = max_min(val_min); + const float nedge = edge/2; + CImg velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum); + + 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 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); + CImg val, vec; + cimg_forX(G,x) { + G.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + if (val[2]<0) val[2] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = vec(0,2); + *(ptrG3++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1] + val[2],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + u = G(x,y,z,0), + v = G(x,y,z,1), + w = G(x,y,z,2), + amp = G(x,y,z,3), + ixx = Incc + Ipcc - 2*Iccc, + ixy = (Innc + Ippc - Inpc - Ipnc)/4, + ixz = (Incn + Ipcp - Incp - Ipcn)/4, + iyy = Icnc + Icpc - 2*Iccc, + iyz = (Icnn + Icpp - Icnp - Icpn)/4, + izz = Iccn + Iccp - 2*Iccc, + ixf = Incc - Iccc, + ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, + iyb = Iccc - Icpc, + izf = Iccn - Iccc, + izb = Iccc - Iccp, + itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else { // 2D + if (sharpen_type) { // Shock filters + CImg 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>=(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); + cimg_forX(G,x) { + G.get_tensor_at(x,y).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat + u = G(x,y,0), + v = G(x,y,1), + amp = G(x,y,2), + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + ixf = Inc - Icc, + ixb = Icc - Ipc, + iyf = Icn - Icc, + iyb = Icc - Icp, + itt = u*u*ixx + v*v*iyy + 2*u*v*ixy, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } + const Tfloat veloc_max = _veloc_max.max(); + if (veloc_max<=0) return *this; + return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this); + } + + //! Sharpen image \newinstance. + CImg get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) const { + return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma); + } + + //! Return image gradient. + /** + \param axes Axes considered for the gradient computation, as a C-string (e.g "xy"). + \param scheme = Numerical scheme used for the gradient computation: + - -1 = Backward finite differences + - 0 = Centered finite differences + - 1 = Forward finite differences + - 2 = Using Sobel kernels + - 3 = Using rotation invariant kernels + - 4 = Using Deriche recusrsive filter. + - 5 = Using Van Vliet recusrsive filter. + **/ + CImgList 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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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.f))), b = (Tfloat)(0.5f*(std::sqrt(2.f) - 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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 res; + for (unsigned int l = 0; axes[l]; ++l) { + const char axis = cimg::lowercase(axes[l]); + switch (axis) { + case 'x' : res.insert(grad[0]); break; + case 'y' : res.insert(grad[1]); break; + case 'z' : res.insert(grad[2]); break; + } + } + grad.assign(); + return res; + } + + //! Return image hessian. + /** + \param axes Axes considered for the hessian computation, as a C-string (e.g "xy"). + **/ + CImgList 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) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + + res.assign(lmax/2,_width,_height,_depth,_spectrum); + if (!cimg::strcasecmp(naxes,def_axes3d)) { // 3D + + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + const ulongT off = (ulongT)c*_width*_height*_depth; + Tfloat + *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off, + *ptrd3 = res[3]._data + off, *ptrd4 = res[4]._data + off, *ptrd5 = res[5]._data + off; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipcc + Incc - 2*Iccc; // Ixx + *(ptrd1++) = (Ippc + Innc - Ipnc - Inpc)/4; // Ixy + *(ptrd2++) = (Ipcp + Incn - Ipcn - Incp)/4; // Ixz + *(ptrd3++) = Icpc + Icnc - 2*Iccc; // Iyy + *(ptrd4++) = (Icpp + Icnn - Icpn - Icnp)/4; // Iyz + *(ptrd5++) = Iccn + Iccp - 2*Iccc; // Izz + } + } + } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // 2D + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; + Tfloat *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipc + Inc - 2*Icc; // Ixx + *(ptrd1++) = (Ipp + Inn - Ipn - Inp)/4; // Ixy + *(ptrd2++) = Icp + Icn - 2*Icc; // Iyy + } + } + } else for (unsigned int l = 0; laxis2) cimg::swap(axis1,axis2); + bool valid_axis = false; + if (axis1=='x' && axis2=='x') { // Ixx + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Ipc + Inc - 2*Icc; + } + } + else if (axis1=='x' && axis2=='y') { // Ixy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipp + Inn - Ipn - Inp)/4; + } + } + else if (axis1=='x' && axis2=='z') { // Ixz + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipcp + Incn - Ipcn - Incp)/4; + } + } + else if (axis1=='y' && axis2=='y') { // Iyy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Iccn + Iccp - 2*Iccc; + } + } + else if (!valid_axis) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + } + return res; + } + + //! Compute image Laplacian. + CImg& laplacian() { + return get_laplacian().move_to(*this); + } + + //! 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>=(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>=(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>=(cimg_openmp_sizefactor)*1048576 && + _height*_depth*_spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res.data(0,0,0,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc - 2*Icc; + } + } + return res; + } + + //! Compute the structure tensor field of an image. + /** + \param is_fwbw_scheme scheme. Can be { false=centered | true=forward-backward } + **/ + CImg& structure_tensors(const bool is_fwbw_scheme=false) { + return get_structure_tensors(is_fwbw_scheme).move_to(*this); + } + + //! Compute the structure tensor field of an image \newinstance. + CImg get_structure_tensors(const bool is_fwbw_scheme=false) const { + if (is_empty()) return *this; + CImg res; + if (_depth>1) { // 3D + res.assign(_width,_height,_depth,6,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ix = (Incc - Ipcc)/2, + iy = (Icnc - Icpc)/2, + iz = (Iccn - Iccp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=ix*iz; + *(ptrd3++)+=iy*iy; + *(ptrd4++)+=iy*iz; + *(ptrd5++)+=iz*iz; + } + } + } else { // Forward/backward finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixf = Incc - Iccc, ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, iyb = Iccc - Icpc, + izf = Iccn - Iccc, izb = Iccc - Iccp; + *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; + *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4; + *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4; + *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2; + *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4; + *(ptrd5++)+=(izf*izf + izb*izb)/2; + } + } + } + } else { // 2D + res.assign(_width,_height,_depth,3,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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 + ix = (Inc - Ipc)/2, + iy = (Icn - Icp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=iy*iy; + } + } + } else { // Forward/backward finite differences (version 2) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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; + } + } + } + } + return res; + } + + //! Compute field of diffusion tensors for edge-preserving smoothing. + /** + \param sharpness Sharpness + \param anisotropy Anisotropy + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param is_sqrt Tells if the square root of the tensor field is computed instead. + **/ + CImg& diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) { + CImg res; + const float + nsharpness = std::max(sharpness,1e-5f), + power1 = (is_sqrt?0.5f:1)*nsharpness, + power2 = power1/(1e-7f + 1 - anisotropy); + blur(alpha).normalize(0,(T)255); + + if (_depth>1) { // 3D + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for 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), + *ptrd3 = res.data(0,y,z,3), *ptrd4 = res.data(0,y,z,4), *ptrd5 = res.data(0,y,z,5); + CImg val(3), vec(3,3); + cimg_forX(*this,x) { + res.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + const float + _l1 = val[2], _l2 = val[1], _l3 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, l3 = _l3>0?_l3:0, + ux = vec(0,0), uy = vec(0,1), uz = vec(0,2), + vx = vec(1,0), vy = vec(1,1), vz = vec(1,2), + wx = vec(2,0), wy = vec(2,1), wz = vec(2,2), + n1 = (float)std::pow(1 + l1 + l2 + l3,-power1), + n2 = (float)std::pow(1 + l1 + l2 + l3,-power2); + *(ptrd0++) = n1*(ux*ux + vx*vx) + n2*wx*wx; + *(ptrd1++) = n1*(ux*uy + vx*vy) + n2*wx*wy; + *(ptrd2++) = n1*(ux*uz + vx*vz) + n2*wx*wz; + *(ptrd3++) = n1*(uy*uy + vy*vy) + n2*wy*wy; + *(ptrd4++) = n1*(uy*uz + vy*vz) + n2*wy*wz; + *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz; + } + } + } else { // for 2D images + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(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); + cimg_forX(*this,x) { + res.get_tensor_at(x,y).symmetric_eigen(val,vec); + const float + _l1 = val[1], _l2 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, + ux = vec(1,0), uy = vec(1,1), + vx = vec(0,0), vy = vec(0,1), + n1 = (float)std::pow(1 + l1 + l2,-power1), + n2 = (float)std::pow(1 + l1 + l2,-power2); + *(ptrd0++) = n1*ux*ux + n2*vx*vx; + *(ptrd1++) = n1*ux*uy + n2*vx*vy; + *(ptrd2++) = n1*uy*uy + n2*vy*vy; + } + } + } + return res.move_to(*this); + } + + //! Compute field of diffusion tensors for edge-preserving smoothing \newinstance. + CImg get_diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) const { + return CImg(*this,false).diffusion_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt); + } + + //! Estimate displacement field between two images. + /** + \param source Reference image. + \param smoothness Smoothness of estimated displacement field. + \param precision Precision required for algorithm convergence. + \param nb_scales Number of scales used to estimate the displacement field. + \param iteration_max Maximum number of iterations allowed for one scale. + \param is_backward If false, match I2(X + U(X)) = I1(X), else match I2(X) = I1(X - U(X)). + \param guide Image used as the initial correspondence estimate for the algorithm. + 'guide' may have a last channel with boolean values (0=false | other=true) that + tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask). + **/ + CImg& 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()) { + return get_displacement(source,smoothness,precision,nb_scales,iteration_max,is_backward,guide). + move_to(*this); + } + + //! Estimate displacement field between two images \newinstance. + CImg get_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()) const { + if (is_empty() || !source) return +*this; + if (!is_sameXYZC(source)) + throw CImgArgumentException(_cimg_instance + "displacement(): Instance and source image (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + source._width,source._height,source._depth,source._spectrum,source._data); + if (precision<0) + throw CImgArgumentException(_cimg_instance + "displacement(): Invalid specified precision %g " + "(should be >=0)", + cimg_instance, + precision); + + const bool is_3d = source._depth>1; + const unsigned int constraint = is_3d?3:2; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum0?nb_scales: + (unsigned int)cimg::round(std::log(mins/8.)/std::log(1.5),1,1); + + 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); + + CImg U, V; + floatT bound = 0; + for (int scale = (int)_nb_scales - 1; scale>=0; --scale) { + const float factor = (float)std::pow(1.5,(double)scale); + const unsigned int + _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1, + _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1, + _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1; + if (sw<5 && sh<5 && (!is_3d || sd<5)) continue; // Skip too small scales + const CImg + I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta, + I2 = (get_resize(I1,2)-=tm)/=tdelta; + if (guide._spectrum>constraint) guide.get_resize(I2._width,I2._height,I2._depth,-100,1).move_to(V); + if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3); + else { + if (guide) + guide.get_shared_channels(0,is_3d?2:1).get_resize(I2._width,I2._height,I2._depth,-100,2).move_to(U); + else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0); + } + + float dt = 2, energy = cimg::type::max(); + const CImgList dI = is_backward?I1.get_gradient():I2.get_gradient(); + cimg_abort_init; + + for (unsigned int iteration = 0; iteration=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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for 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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } + } + } else { // 2D version + if (smoothness>=0) // Isotropic regularization + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } + } + } + const float d_energy = (_energy - energy)/(sw*sh*sd); + if (d_energy<=0 && -d_energy<_precision) break; + if (d_energy>0) dt*=0.5f; + energy = _energy; + } + } + return U; + } + + //! Compute correspondence map between two images, using 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. + \param patch_height Height of the patch used for matching. + \param patch_depth Depth of the patch used for matching. + \param nb_iterations Number of patch-match iterations. + \param nb_randoms Number of randomization attempts (per pixel). + \param occ_penalization Penalization factor in score related patch occurrences. + \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. + **/ + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide,matching_score).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \newinstance. + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization, + guide,true,matching_score); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,guide,occ_penalization,false,CImg::empty()); + } + + template + 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 occ_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 + "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 + "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 + "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, + patch_image._data); + const unsigned int + _constraint = patch_image._depth>1?3:2, + constraint = guide._spectrum>_constraint?_constraint:0; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<_constraint)) + throw CImgArgumentException(_cimg_instance + "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 is_updated(_width,_height,_depth,1,3); + CImg score(_width,_height,_depth); + CImg occ, loop_order; + ulongT rng = (cimg::_rand(),cimg::rng()); + if (occ_penalization!=0) { + occ.assign(patch_image._width,patch_image._height,patch_image._depth,1,0); + loop_order.assign(_width,_height,_depth,_depth>1?3:2); + cimg_forXYZ(loop_order,x,y,z) { + loop_order(x,y,z,0) = x; + loop_order(x,y,z,1) = y; + if (loop_order._spectrum>2) loop_order(x,y,z,2) = z; + } + cimg_forXYZ(loop_order,x,y,z) { // Randomize loop order in case of constraints on patch occurrence + const unsigned int + X = (unsigned int)cimg::round(cimg::rand(loop_order._width - 1.,&rng)), + Y = (unsigned int)cimg::round(cimg::rand(loop_order._height - 1.,&rng)), + Z = loop_order._depth>1?(unsigned int)cimg::round(cimg::rand(loop_order._depth - 1.,&rng)):0U; + cimg::swap(loop_order(x,y,z,0),loop_order(X,Y,Z,0)); + cimg::swap(loop_order(x,y,z,1),loop_order(X,Y,Z,1)); + if (loop_order._spectrum>2) cimg::swap(loop_order(x,y,z,2),loop_order(X,Y,Z,2)); + } + } + 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 (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_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_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter2) z = loop_order(_x,_y,_z,2); else z = _z; + } else { x = _x; y = _y; 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 = map(x - 1,y,z,0); + v = map(x - 1,y,z,1); + w = map(x - 1,y,z,2); + if (u>=cx1 - 1 && u=cy1 && v=cz1 && w0 && (is_updated(x,y - 1,z)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,z,0); + v = map(x,y - 1,z,1); + w = map(x,y - 1,z,2); + if (u>=cx1 && u=cy1 - 1 && v=cz1 && w0 && (is_updated(x,y,z - 1)&cmask)) { // Compare with backward neighbor + u = map(x,y,z - 1,0); + v = map(x,y,z - 1,1); + w = map(x,y,z - 1,2); + if (u>=cx1 && u=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::inf()); + } else cimg_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter0 && (is_updated(x - 1,y)&cmask)) { // Compare with left neighbor + u = map(x - 1,y,0); + v = map(x - 1,y,1); + if (u>=cx1 - 1 && u=cy1 && v0 && (is_updated(x,y - 1)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,0); + v = map(x,y - 1,1); + if (u>=cx1 && u=cy1 - 1 && v=cx1 + 1 && u=cy1 && v=cx1 && u=cy1 + 1 && v& img1, const CImg& img2, const CImg& occ, + const unsigned int psizew, const unsigned int psizeh, + 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 occ_penalization, + const float max_score) { // 3D version + 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 - 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; + for (unsigned int k = 0; kmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + p1+=offy1; p2+=offy2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc,zc)); + } + + static float _matchpatch(const CImg& img1, const CImg& img2, const CImg& occ, + 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 occ_penalization, + const float max_score) { // 2D version + 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 - psizewc, + offx2 = (ulongT)img2._width - psizewc; + float ssd = 0; + for (unsigned int j = 0; jmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc)); + } + + //! Compute Euclidean distance function to a specified value. + /** + \param value Reference value. + \param metric Type of metric. Can be { 0=Chebyshev | 1=Manhattan | 2=Euclidean | 3=Squared-euclidean }. + \note + The distance transform implementation has been submitted by A. Meijster, and implements + the article 'W.H. Hesselink, A. Meijster, J.B.T.M. Roerdink, + "A general algorithm for computing distance transforms in linear time.", + In: Mathematical Morphology and its Applications to Image and Signal Processing, + J. Goutsias, L. Vincent, and D.S. Bloomberg (eds.), Kluwer, 2000, pp. 331-340.' + The submitted code has then been modified to fit CImg coding style and constraints. + **/ + CImg& distance(const T& value, const unsigned int metric=2) { + if (is_empty()) return *this; + if (cimg::type::string()!=cimg::type::string()) // 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 + } + return *this; + } + + //! Compute distance to a specified value \newinstance. + CImg get_distance(const T& value, const unsigned int metric=2) const { + return CImg(*this,false).distance((Tfloat)value,metric); + } + + static longT _distance_sep_edt(const longT i, const longT u, const longT *const g) { + return (u*u - i*i + g[u] - g[i])/(2*(u - i)); + } + + static longT _distance_dist_edt(const longT x, const longT i, const longT *const g) { + return (x - i)*(x - i) + g[i]; + } + + static longT _distance_sep_mdt(const longT i, const longT u, const longT *const g) { + return (u - i<=g[u] - g[i]?999999999:(g[u] - g[i] + u + i)/2); + } + + static longT _distance_dist_mdt(const longT x, const longT i, const longT *const g) { + return (x=0) && f(t[q],s[q],g)>f(t[q],u,g)) { --q; } + if (q<0) { q = 0; s[0] = u; } + else { const longT w = 1 + sep(s[q], u, g); if (w<(longT)len) { ++q; s[q] = u; t[q] = w; }} + } + for (int u = (int)len - 1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan + } + + CImg& _distance_core(longT (*const sep)(const longT, const longT, const longT *const), + longT (*const f)(const longT, const longT, const longT *const)) { + // Check for g++ 4.9.X, as OpenMP seems to crash for this particular function. I have no clues why. +#define cimg_is_gcc49x (__GNUC__==4 && __GNUC_MINOR__==9) + + const ulongT wh = (ulongT)_width*_height; +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) +#endif + cimg_forC(*this,c) { + CImg 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 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_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 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_forY(*this,y) g[y] = (longT)img(x,y,z,0,wh); + _distance_scan(_height,g,sep,f,s,t,dt); + cimg_forY(*this,y) img(x,y,z,0,wh) = (T)dt[y]; + } + } + if (_depth>1) { + g.assign(_depth); dt.assign(_depth); s.assign(_depth); t.assign(_depth); +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for 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_forZ(*this,z) g[z] = (longT)img(x,y,z,0,wh); + _distance_scan(_depth,g,sep,f,s,t,dt); + cimg_forZ(*this,z) img(x,y,z,0,wh) = (T)dt[z]; + } + } + } + return *this; + } + + //! Compute chamfer distance to a specified value, with a custom metric. + /** + \param value Reference value. + \param metric_mask Metric mask. + \note The algorithm code has been initially proposed by A. Meijster, and modified by D. Tschumperlé. + **/ + template + CImg& distance(const T& value, const CImg& metric_mask) { + if (is_empty()) return *this; + bool is_value = false; + cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,0:(T)999999999; + if (!is_value) return fill(cimg::type::max()); + const ulongT wh = (ulongT)_width*_height; + 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 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 y = height() - 1 - dy, ny = height() - 1; y>=0; --y,--ny) { + for (int x = width() - 1 - dx, nx = width() - 1; x>=0; --x,--nx) { + const T dd = img(nx,ny,nz,0,wh) + weight; + if (dd + CImg get_distance(const T& value, const CImg& metric_mask) const { + return CImg(*this,false).distance(value,metric_mask); + } + + //! Compute distance to a specified value, according to a custom metric (use dijkstra algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + \param is_high_connectivity Tells if the algorithm uses low or high connectivity. + \param[out] return_path An image containing the nodes of the minimal path. + **/ + template + CImg& distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) { + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm) \newinstance. + template + CImg::type> + get_distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) const { + if (is_empty()) return return_path.assign(); + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_dijkstra(): image instance and metric map (%u,%u,%u,%u) " + "have incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + typedef typename cimg::superset::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); + + cimg_forC(*this,c) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + CImg path = return_path?return_path.get_shared_channel(c):CImg(); + unsigned int sizeQ = 0; + + // Detect initial seeds. + is_queued.fill(0); + cimg_forXYZ(img,x,y,z) if (img(x,y,z)==value) { + Q._priority_queue_insert(is_queued,sizeQ,0,x,y,z); + res(x,y,z) = 0; + if (path) path(x,y,z) = (to)0; + } + + // Start distance propagation. + while (sizeQ) { + + // Get and remove point with minimal potential from the queue. + const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3); + const td P = (td)-Q(0,0); + Q._priority_queue_remove(sizeQ); + + // Update neighbors. + td npot = 0; + if (x - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x - 1,y,z) + P),x - 1,y,z)) { + res(x - 1,y,z) = npot; if (path) path(x - 1,y,z) = (to)2; + } + if (x + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y - 1,z) + P),x,y - 1,z)) { + res(x,y - 1,z) = npot; if (path) path(x,y - 1,z) = (to)8; + } + if (y + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y,z - 1) + P),x,y,z - 1)) { + res(x,y,z - 1) = npot; if (path) path(x,y,z - 1) = (to)32; + } + if (z + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y - 1,z) + P)),x - 1,y - 1,z)) { + res(x - 1,y - 1,z) = npot; if (path) path(x - 1,y - 1,z) = (to)10; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y - 1,z) + P)),x + 1,y - 1,z)) { + res(x + 1,y - 1,z) = npot; if (path) path(x + 1,y - 1,z) = (to)9; + } + if (x - 1>=0 && y + 1=0) { // Diagonal neighbors on slice z - 1 + if (x - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z - 1) + P)),x - 1,y,z - 1)) { + res(x - 1,y,z - 1) = npot; if (path) path(x - 1,y,z - 1) = (to)34; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z - 1) + P)),x,y - 1,z - 1)) { + res(x,y - 1,z - 1) = npot; if (path) path(x,y - 1,z - 1) = (to)40; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z - 1) + P)), + x - 1,y - 1,z - 1)) { + res(x - 1,y - 1,z - 1) = npot; if (path) path(x - 1,y - 1,z - 1) = (to)42; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z - 1) + P)), + x + 1,y - 1,z - 1)) { + res(x + 1,y - 1,z - 1) = npot; if (path) path(x + 1,y - 1,z - 1) = (to)41; + } + if (x - 1>=0 && y + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z + 1) + P)),x - 1,y,z + 1)) { + res(x - 1,y,z + 1) = npot; if (path) path(x - 1,y,z + 1) = (to)18; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z + 1) + P)),x,y - 1,z + 1)) { + res(x,y - 1,z + 1) = npot; if (path) path(x,y - 1,z + 1) = (to)24; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z + 1) + P)), + x - 1,y - 1,z + 1)) { + res(x - 1,y - 1,z + 1) = npot; if (path) path(x - 1,y - 1,z + 1) = (to)26; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z + 1) + P)), + x + 1,y - 1,z + 1)) { + res(x + 1,y - 1,z + 1) = npot; if (path) path(x + 1,y - 1,z + 1) = (to)25; + } + if (x - 1>=0 && y + 1 + CImg& distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) { + return get_distance_dijkstra(value,metric,is_high_connectivity).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm). \newinstance. + template + CImg get_distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) const { + CImg return_path; + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + **/ + template + CImg& distance_eikonal(const T& value, const CImg& metric) { + return get_distance_eikonal(value,metric).move_to(*this); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + template + CImg get_distance_eikonal(const T& value, const CImg& metric) const { + if (is_empty()) return *this; + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_eikonal(): image instance and metric map (%u,%u,%u,%u) have " + "incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + CImg result(_width,_height,_depth,_spectrum,cimg::type::max()), Q; + 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) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + unsigned int sizeQ = 0; + state.fill(-1); + + // Detect initial seeds. + Tfloat *ptr1 = res._data; char *ptr2 = state._data; + cimg_for(img,ptr0,T) { if (*ptr0==value) { *ptr1 = 0; *ptr2 = 1; } ++ptr1; ++ptr2; } + + // Initialize seeds neighbors. + ptr2 = state._data; + cimg_forXYZ(img,x,y,z) if (*(ptr2++)==1) { + if (x - 1>=0 && state(x - 1,y,z)==-1) { + const Tfloat dist = res(x - 1,y,z) = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x - 1,y,z); + } + if (x + 1=0 && state(x,y - 1,z)==-1) { + const Tfloat dist = res(x,y - 1,z) = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y - 1,z); + } + if (y + 1=0 && state(x,y,z - 1)==-1) { + const Tfloat dist = res(x,y,z - 1) = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z - 1); + } + if (z + 1=0) { + if (x - 1>=0 && state(x - 1,y,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + if (dist=0 && state(x,y - 1,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + if (dist=0 && state(x,y,z - 1)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + if (dist& res, const Tfloat P, + const int x=0, const int y=0, const int z=0) const { + const Tfloat M = (Tfloat)cimg::type::max(); + T T1 = (T)std::min(x - 1>=0?res(x - 1,y,z):M,x + 11) { // 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 + 1T2) cimg::swap(T1,T2); + if (T2>T3) cimg::swap(T2,T3); + if (T1>T2) cimg::swap(T1,T2); + if (P<=0) return (Tfloat)T1; + if (T31) { // 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 + void _eik_priority_queue_insert(CImg& state, unsigned int& siz, const t value, + const unsigned int x, const unsigned int y, const unsigned int z) { + if (state(x,y,z)>0) return; + state(x,y,z) = 0; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; (*this)(siz - 1,1) = (T)x; (*this)(siz - 1,2) = (T)y; (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); cimg::swap((*this)(pos,3),(*this)(par,3)); + } + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE. + /** + \param nb_iterations Number of PDE iterations. + \param band_size Size of the narrow band. + \param time_step Time step of the PDE iterations. + **/ + CImg& distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) { + if (is_empty()) return *this; + CImg velocity(*this,false); + for (unsigned int iteration = 0; iteration1) { // 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)0?(Incc - Iccc):(Iccc - Ipcc), + iy = gy*sgn>0?(Icnc - Iccc):(Iccc - Icpc), + iz = gz*sgn>0?(Iccn - Iccc):(Iccc - Iccp), + ng = 1e-5f + cimg::hypot(gx,gy,gz), + ngx = gx/ng, + ngy = gy/ng, + ngz = gz/ng, + veloc = sgn*(ngx*ix + ngy*iy + ngz*iz - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } else { // 2D version + CImg_3x3(I,Tfloat); + cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) if (band_size<=0 || cimg::abs(Icc)0?(Inc - Icc):(Icc - Ipc), + iy = gy*sgn>0?(Icn - Icc):(Icc - Icp), + ng = std::max((Tfloat)1e-5,cimg::hypot(gx,gy)), + ngx = gx/ng, + ngy = gy/ng, + veloc = sgn*(ngx*ix + ngy*iy - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } + if (veloc_max>0) *this+=(velocity*=time_step/veloc_max); + } + return *this; + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE \newinstance. + CImg get_distance_eikonal(const unsigned int nb_iterations, const float band_size=0, + const float time_step=0.5f) const { + return CImg(*this,false).distance_eikonal(nb_iterations,band_size,time_step); + } + + //! Compute Haar multiscale wavelet transform. + /** + \param axis Axis considered for the transform. + \param invert Set inverse of direct transform. + \param nb_scales Number of scales used for the transform. + **/ + CImg& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(axis,invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg 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.f); + if (nb_scales==1) { + switch (cimg::lowercase(axis)) { // Single scale transform + case 'x' : { + const unsigned int w = _width/2; + if (w) { + if ((w%2) && w!=1) + throw CImgInstanceException(_cimg_instance + "haar(): Sub-image width %u is not even.", + cimg_instance, + w); + + res.assign(_width,_height,_depth,_spectrum); + if (invert) cimg_forYZC(*this,y,z,c) { // Inverse transform along X + for (unsigned int x = 0, xw = w, x2 = 0; x& haar(const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg get_haar(const bool invert=false, const unsigned int nb_scales=1) const { + CImg res; + if (nb_scales==1) { // Single scale transform + if (_width>1) get_haar('x',invert,1).move_to(res); + if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); } + if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_to(res); } + if (res) return res; + } else { // Multi-scale transform + if (invert) { // Inverse transform + res.assign(*this,false); + if (_width>1) { + if (_height>1) { + if (_depth>1) { + unsigned int w = _width, h = _height, d = _depth; + for (unsigned int s = 1; w && h && d && s1) { + unsigned int w = _width, d = _depth; + for (unsigned int s = 1; w && d && s1) { + if (_depth>1) { + unsigned int h = _height, d = _depth; + for (unsigned int s = 1; h && d && s1) { + unsigned int d = _depth; + for (unsigned int s = 1; d && s1) { + if (_height>1) { + if (_depth>1) + for (unsigned int s = 1, w = _width/2, h = _height/2, d = _depth/2; w && h && d && s1) for (unsigned int s = 1, w = _width/2, d = _depth/2; w && d && s1) { + if (_depth>1) + for (unsigned int s = 1, h = _height/2, d = _depth/2; h && d && s1) for (unsigned int s = 1, d = _depth/2; d && s get_FFT(const char axis, const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],axis,is_invert); + return res; + } + + //! Compute n-d Fast Fourier Transform. + /* + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + CImgList get_FFT(const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],is_invert); + return res; + } + + //! Compute 1D Fast Fourier Transform, along a specified axis. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param axis Axis along which the FFT is computed. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + static void FFT(CImg& real, CImg& imag, const char axis, const bool is_invert=false) { + if (!real) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part is empty.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); +#ifdef cimg_use_fftw3 + cimg::mutex(12); + fftw_complex *data_in; + fftw_plan data_plan; + + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the X-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forYZC(real,y,z,c) { + T *ptrr = real.data(0,y,z,c), *ptri = imag.data(0,y,z,c); + double *ptrd = (double*)data_in; + cimg_forX(real,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); } + fftw_execute(data_plan); + const unsigned int fact = real._width; + if (is_invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); } + else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); } + } + } break; + case 'y' : { // Fourier along Y, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Y-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._height), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const unsigned int off = real._width; + cimg_forXZC(real,x,z,c) { + T *ptrr = real.data(x,0,z,c), *ptri = imag.data(x,0,z,c); + double *ptrd = (double*)data_in; + cimg_forY(real,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._height; + if (is_invert) + cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + case 'z' : { // Fourier along Z, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Z-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._depth), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const ulongT off = (ulongT)real._width*real._height; + cimg_forXYC(real,x,y,c) { + T *ptrr = real.data(x,y,0,c), *ptri = imag.data(x,y,0,c); + double *ptrd = (double*)data_in; + cimg_forZ(real,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._depth; + if (is_invert) + cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + default : + throw CImgArgumentException("CImgList<%s>::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } + fftw_destroy_plan(data_plan); + fftw_free(data_in); + cimg::mutex(12,0); +#else + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using built-in functions + const unsigned int N = real._width, N2 = N>>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the X-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forYZC(real,y,z,c) { + cimg::swap(real(i,y,z,c),real(j,y,z,c)); + cimg::swap(imag(i,y,z,c),imag(j,y,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = delta>>1; + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Y-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXZC(real,x,z,c) { + cimg::swap(real(x,i,z,c),real(x,j,z,c)); + cimg::swap(imag(x,i,z,c),imag(x,j,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Z-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXYC(real,x,y,c) { + cimg::swap(real(x,y,i,c),real(x,y,j,c)); + cimg::swap(imag(x,y,i,c),imag(x,y,j,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } +#endif + } + + //! Compute n-d Fast Fourier Transform. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + \param nb_threads Number of parallel threads used for the computation. + Use \c 0 to set this to the number of available cpus. + **/ + static void FFT(CImg& real, CImg& imag, const bool is_invert=false, const unsigned int nb_threads=0) { + if (!real) + throw CImgInstanceException("CImgList<%s>::FFT(): Empty specified real part.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); + +#ifdef cimg_use_fftw3 + cimg::mutex(12); +#ifndef cimg_use_fftw3_singlethread + const unsigned int _nb_threads = nb_threads?nb_threads:cimg::nb_cpus(); + static int fftw_st = fftw_init_threads(); + cimg::unused(fftw_st); + fftw_plan_with_nthreads(_nb_threads); +#else + cimg::unused(nb_threads); +#endif + fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u).", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width* + real._height*real._depth*real._spectrum), + real._width,real._height,real._depth,real._spectrum); + + fftw_plan data_plan; + const ulongT w = (ulongT)real._width, wh = w*real._height, whd = wh*real._depth; + data_plan = fftw_plan_dft_3d(real._width,real._height,real._depth,data_in,data_in, + is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forC(real,c) { + T *ptrr = real.data(0,0,0,c), *ptri = imag.data(0,0,0,c); + double *ptrd = (double*)data_in; + for (unsigned int x = 0; 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); +#endif + } + + //@} + //------------------------------------- + // + //! \name 3D Objects Management + //@{ + //------------------------------------- + + //! Shift 3D object's vertices. + /** + \param tx X-coordinate of the 3D displacement vector. + \param ty Y-coordinate of the 3D displacement vector. + \param tz Z-coordinate of the 3D displacement vector. + **/ + CImg& shift_object3d(const float tx, const float ty=0, const float tz=0) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "shift_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + get_shared_row(0)+=tx; get_shared_row(1)+=ty; get_shared_row(2)+=tz; + return *this; + } + + //! Shift 3D object's vertices \newinstance. + CImg 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. + /** + \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.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2; + return *this; + } + + //! Shift 3D object's vertices, so that it becomes centered \newinstance. + CImg get_shift_object3d() const { + return CImg(*this,false).shift_object3d(); + } + + //! Resize 3D object. + /** + \param sx Width of the 3D object's bounding box. + \param sy Height of the 3D object's bounding box. + \param sz Depth of the 3D object's bounding box. + **/ + CImg& resize_object3d(const float sx, const float sy=-100, const float sz=-100) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + if (xm0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; } + if (ym0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; } + if (zm0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; } + return *this; + } + + //! 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. + CImg resize_object3d() { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz); + if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; } + return *this; + } + + //! Resize 3D object to unit size \newinstance. + CImg get_resize_object3d() const { + return CImg(*this,false).resize_object3d(); + } + + //! Merge two 3D objects together. + /** + \param[in,out] primitives Primitives data of the current 3D object. + \param obj_vertices Vertices data of the additional 3D object. + \param obj_primitives Primitives data of the additional 3D object. + **/ + template + CImg& append_object3d(CImgList& primitives, const CImg& obj_vertices, + const CImgList& obj_primitives) { + if (!obj_vertices || !obj_primitives) return *this; + if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Specified vertice image (%u,%u,%u,%u,%p) is not a " + "set of 3D vertices.", + cimg_instance, + obj_vertices._width,obj_vertices._height, + obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data); + + if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); } + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + const unsigned int P = _width; + append(obj_vertices,'x'); + const unsigned int N = primitives._width; + primitives.insert(obj_primitives); + for (unsigned int i = N; i &p = primitives[i]; + switch (p.size()) { + case 1 : p[0]+=P; break; // Point + case 5 : p[0]+=P; p[1]+=P; break; // Sphere + case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment + case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle + case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle + } + } + return *this; + } + + //! Texturize primitives of a 3D object. + /** + \param[in,out] primitives Primitives data of the 3D object. + \param[in,out] colors Colors data of the 3D object. + \param texture Texture image to map to 3D object. + \param coords Texture-mapping coordinates. + **/ + template + const CImg& texturize_object3d(CImgList& primitives, CImgList& colors, + const CImg& texture, const CImg& coords=CImg::const_empty()) const { + if (is_empty()) return *this; + if (_height!=3) + throw CImgInstanceException(_cimg_instance + "texturize_object3d(): image instance is not a set of 3D points.", + cimg_instance); + if (coords && (coords._width!=_width || coords._height!=2)) + throw CImgArgumentException(_cimg_instance + "texturize_object3d(): Invalid specified texture coordinates (%u,%u,%u,%u,%p).", + cimg_instance, + coords._width,coords._height,coords._depth,coords._spectrum,coords._data); + CImg _coords; + if (!coords) { // If no texture coordinates specified, do a default XY-projection + _coords.assign(_width,2); + float + xmin, xmax = (float)get_shared_row(0).max_min(xmin), + ymin, ymax = (float)get_shared_row(1).max_min(ymin), + dx = xmax>xmin?xmax-xmin:1, + dy = ymax>ymin?ymax-ymin:1; + cimg_forX(*this,p) { + _coords(p,0) = (int)(((*this)(p,0) - xmin)*texture._width/dx); + _coords(p,1) = (int)(((*this)(p,1) - ymin)*texture._height/dy); + } + } else _coords = coords; + + int texture_ind = -1; + cimglist_for(primitives,l) { + CImg &p = primitives[l]; + const unsigned int siz = p.size(); + switch (siz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)p[0]; + const int x0 = _coords(i0,0), y0 = _coords(i0,1); + texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0).move_to(colors[l]); + } break; + case 2 : case 6 : { // Line + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,x0,y0,x1,y1).move_to(p); + } break; + case 3 : case 9 : { // Triangle + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p); + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2], i3 = (unsigned int)p[3]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1), + x3 = _coords(i3,0), y3 = _coords(i3,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,i3,x0,y0,x1,y1,x2,y2,x3,y3).move_to(p); + } break; + } + } + return *this; + } + + //! Generate a 3D elevation of the image instance. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param[out] colors The returned list of the 3D object colors. + \param elevation The input elevation map. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + CImgList colors3d; + const CImg points3d = img.get_elevation3d(faces3d,colors3d,img.get_norm()*0.2); + CImg().display_object3d("Elevation3d",points3d,faces3d,colors3d); + \endcode + \image html ref_elevation3d.jpg + **/ + template + CImg get_elevation3d(CImgList& primitives, CImgList& colors, const CImg& elevation) const { + if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1) + throw CImgArgumentException(_cimg_instance + "get_elevation3d(): Instance and specified elevation (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + elevation._width,elevation._height,elevation._depth, + elevation._spectrum,elevation._data); + if (is_empty()) return *this; + float m, M = (float)max_min(m); + if (M==m) ++M; + colors.assign(); + const unsigned int size_x1 = _width - 1, size_y1 = _height - 1; + for (unsigned int y = 0; y1?((*this)(x,y,1) - m)*255/(M-m):r), + b = (unsigned char)(_spectrum>2?((*this)(x,y,2) - m)*255/(M-m):_spectrum>1?0:r); + CImg::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.f,_height - 1.f,_width,_height); + } + + //! Generate the 3D projection planes of the image instance. + /** + \param[out] primitives Primitives data of the returned 3D object. + \param[out] colors Colors data of the returned 3D object. + \param x0 X-coordinate of the projection point. + \param y0 Y-coordinate of the projection point. + \param z0 Z-coordinate of the projection point. + \param normalize_colors Tells if the created textures have normalized colors. + **/ + template + CImg get_projections3d(CImgList& primitives, CImgList& colors, + const unsigned int x0, const unsigned int y0, const unsigned int z0, + const bool normalize_colors=false) const { + float m = 0, M = 0, delta = 1; + if (normalize_colors) { m = (float)min_max(M); delta = 255/(m==M?1:M-m); } + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + CImg img_xy, img_xz, img_yz; + if (normalize_colors) { + ((get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1)-=m)*=delta).move_to(img_xy); + ((get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_width,_depth,1,-100,-1). + move_to(img_xz); + ((get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_height,_depth,1,-100,-1). + move_to(img_yz); + } else { + get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1).move_to(img_xy); + get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1).move_to(img_xz); + get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).resize(_height,_depth,1,-100,-1).move_to(img_yz); + } + CImg points(12,3,1,1, + 0,_width - 1,_width - 1,0, 0,_width - 1,_width - 1,0, _x0,_x0,_x0,_x0, + 0,0,_height - 1,_height - 1, _y0,_y0,_y0,_y0, 0,_height - 1,_height - 1,0, + _z0,_z0,_z0,_z0, 0,0,_depth - 1,_depth - 1, 0,0,_depth - 1,_depth - 1); + primitives.assign(); + CImg::vector(0,1,2,3,0,0,img_xy._width - 1,0,img_xy._width - 1,img_xy._height - 1,0,img_xy._height - 1). + move_to(primitives); + CImg::vector(4,5,6,7,0,0,img_xz._width - 1,0,img_xz._width - 1,img_xz._height - 1,0,img_xz._height - 1). + move_to(primitives); + CImg::vector(8,9,10,11,0,0,img_yz._width - 1,0,img_yz._width - 1,img_yz._height - 1,0,img_yz._height - 1). + move_to(primitives); + colors.assign(); + img_xy.move_to(colors); + img_xz.move_to(colors); + img_yz.move_to(colors); + return points; + } + + //! Generate a isoline of the image instance as a 3D object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x The number of subdivisions along the X-axis. + \param size_y The number of subdisivions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + const CImg points3d = img.get_isoline3d(faces3d,100); + CImg().display_object3d("Isoline3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isoline3d.jpg + **/ + template + CImg get_isoline3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a scalar image.", + cimg_instance); + if (_depth>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a 2D image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.f,height() - 1.f,width(),height()); + } else { + const _functor2d_float func(*this); + 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. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x Number of subdivisions along the X-axis. + \param size_y Number of subdisivions along the Y-axis. + \param size_z Number of subdisivions along the Z-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img = CImg("reference.jpg").resize(-100,-100,20); + CImgList faces3d; + const CImg points3d = img.get_isosurface3d(faces3d,100); + CImg().display_object3d("Isosurface3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isosurface3d.jpg + **/ + template + CImg get_isosurface3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100, const int size_z=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isosurface3d(): Instance is not a scalar image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.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.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. + /** + \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 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 CImg isosurface3d(CImgList& primitives, const tfunc& func, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int size_x=32, const int size_y=32, const int size_z=32) { + static const unsigned int edges[256] = { + 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 + }; + + static const int triangles[256][16] = { + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 }, + { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 }, + { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 }, + { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 }, + { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 }, + { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 }, + { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, + { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, + { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, + { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 }, + { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, + { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 }, + { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, + { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 }, + { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, + { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 }, + { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, + { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 }, + { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 }, + { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, + { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 }, + { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 }, + { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 }, + { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 }, + { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, + { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 }, + { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 }, + { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, + { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 }, + { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 }, + { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, + { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 }, + { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, + { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, + { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 }, + { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 }, + { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 }, + { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, + { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 }, + { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 }, + { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, + { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 }, + { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, + { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, + { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, + { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 }, + { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 }, + { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, + { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 }, + { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 }, + { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, + { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 }, + { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, + { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 }, + { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, + { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 }, + { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 }, + { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, + { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, + { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 }, + { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, + { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 }, + { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, + { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, + { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, + { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 }, + { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 }, + { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 }, + { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, + { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 }, + { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, + { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 }, + { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, + { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 }, + { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, + { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 }, + { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, + { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, + { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, + { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, + { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 }, + { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, + { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 }, + { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, + { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 }, + { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 }, + { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, + { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 }, + { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, + { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 }, + { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 }, + { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, + { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } + }; + + const unsigned int + _nx = (unsigned int)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)), + _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)), + _nz = (unsigned int)(size_z>=0?size_z:cimg::round((z1-z0)*-size_z/100 + 1)), + nx = _nx?_nx:1, + ny = _ny?_ny:1, + nz = _nz?_nz:1, + nxm1 = nx - 1, + nym1 = ny - 1, + nzm1 = nz - 1; + primitives.assign(); + if (!nxm1 || !nym1 || !nzm1) return CImg(); + 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; + + // Fill the first plane with function values + Y = y0; + cimg_forY(values1,y) { + X = x0; + cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=dx; } + Y+=dy; + } + + // Run Marching Cubes algorithm + Z = z0; nZ = Z + dz; + for (unsigned int zi = 0; zi::vector(Xi,Y,Z).move_to(vertices); + } + if ((edge&2) && indices1(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val1)*dy/(val2-val1); + indices1(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,Z).move_to(vertices); + } + if ((edge&4) && indices1(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val3)*dx/(val2-val3); + indices1(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,Z).move_to(vertices); + } + if ((edge&8) && indices1(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val0)*dy/(val3-val0); + indices1(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,Z).move_to(vertices); + } + if ((edge&16) && indices2(xi,yi,0)<0) { + const float Xi = X + (isovalue-val4)*dx/(val5-val4); + indices2(xi,yi,0) = vertices.width(); + CImg::vector(Xi,Y,nZ).move_to(vertices); + } + if ((edge&32) && indices2(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val5)*dy/(val6-val5); + indices2(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,nZ).move_to(vertices); + } + if ((edge&64) && indices2(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val7)*dx/(val6-val7); + indices2(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,nZ).move_to(vertices); + } + if ((edge&128) && indices2(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val4)*dy/(val7-val4); + indices2(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,nZ).move_to(vertices); + } + if ((edge&256) && indices1(xi,yi,2)<0) { + const float Zi = Z+ (isovalue-val0)*dz/(val4-val0); + indices1(xi,yi,2) = vertices.width(); + CImg::vector(X,Y,Zi).move_to(vertices); + } + if ((edge&512) && indices1(nxi,yi,2)<0) { + const float Zi = Z + (isovalue-val1)*dz/(val5-val1); + indices1(nxi,yi,2) = vertices.width(); + CImg::vector(nX,Y,Zi).move_to(vertices); + } + if ((edge&1024) && indices1(nxi,nyi,2)<0) { + const float Zi = Z + (isovalue-val2)*dz/(val6-val2); + indices1(nxi,nyi,2) = vertices.width(); + CImg::vector(nX,nY,Zi).move_to(vertices); + } + if ((edge&2048) && indices1(xi,nyi,2)<0) { + const float Zi = Z + (isovalue-val3)*dz/(val7-val3); + indices1(xi,nyi,2) = vertices.width(); + CImg::vector(X,nY,Zi).move_to(vertices); + } + + // Create triangles + for (const int *triangle = triangles[configuration]; *triangle!=-1; ) { + const unsigned int + p0 = (unsigned int)*(triangle++), + p1 = (unsigned int)*(triangle++), + p2 = (unsigned int)*(triangle++); + const tf + i0 = (tf)(_isosurface3d_indice(p0,indices1,indices2,xi,yi,nxi,nyi)), + i1 = (tf)(_isosurface3d_indice(p1,indices1,indices2,xi,yi,nxi,nyi)), + i2 = (tf)(_isosurface3d_indice(p2,indices1,indices2,xi,yi,nxi,nyi)); + CImg::vector(i0,i2,i1).move_to(primitives); + } + } + } + } + cimg::swap(values1,values2); + cimg::swap(indices1,indices2); + } + return vertices>'x'; + } + + //! Compute isosurface of a function, as a 3D object \overloading. + template + static CImg isosurface3d(CImgList& primitives, const char *const expression, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int dx=32, const int dy=32, const int dz=32) { + const _functor3d_expr func(expression); + return isosurface3d(primitives,func,isovalue,x0,y0,z0,x1,y1,z1,dx,dy,dz); + } + + template + static int _isosurface3d_indice(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) { + case 0 : return indices1(x,y,0); + case 1 : return indices1(nx,y,1); + case 2 : return indices1(x,ny,0); + case 3 : return indices1(x,y,1); + case 4 : return indices2(x,y,0); + case 5 : return indices2(nx,y,1); + case 6 : return indices2(x,ny,0); + case 7 : return indices2(x,y,1); + case 8 : return indices1(x,y,2); + case 9 : return indices1(nx,y,2); + case 10 : return indices1(nx,ny,2); + case 11 : return indices1(x,ny,2); + } + return 0; + } + + // Define functors for accessing image values (used in previous functions). + struct _functor2d_int { + const CImg& ref; + _functor2d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref((int)x,(int)y); + } + }; + + struct _functor2d_float { + const CImg& ref; + _functor2d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref._linear_atXY(x,y); + } + }; + + struct _functor2d_expr { + _cimg_math_parser *mp; + ~_functor2d_expr() { mp->end(); delete mp; } + _functor2d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y) const { + return (float)(*mp)(x,y,0,0); + } + }; + + struct _functor3d_int { + const CImg& ref; + _functor3d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref((int)x,(int)y,(int)z); + } + }; + + struct _functor3d_float { + const CImg& ref; + _functor3d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref._linear_atXYZ(x,y,z); + } + }; + + struct _functor3d_expr { + _cimg_math_parser *mp; + ~_functor3d_expr() { mp->end(); delete mp; } + _functor3d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z) const { + return (float)(*mp)(x,y,z,0); + } + }; + + struct _functor4d_int { + const CImg& ref; + _functor4d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref((int)x,(int)y,(int)z,c); + } + }; + + //! Generate a 3D box object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the box (dimension along the X-axis). + \param size_y The height of the box (dimension along the Y-axis). + \param size_z The depth of the box (dimension along the Z-axis). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::box3d(faces3d,10,20,30); + CImg().display_object3d("Box3d",points3d,faces3d); + \endcode + \image html ref_box3d.jpg + **/ + template + static CImg box3d(CImgList& primitives, + const float size_x=200, const float size_y=100, const float size_z=100) { + primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5); + return CImg(8,3,1,1, + 0.,size_x,size_x, 0., 0.,size_x,size_x, 0., + 0., 0.,size_y,size_y, 0., 0.,size_y,size_y, + 0., 0., 0., 0.,size_z,size_z,size_z,size_z); + } + + //! Generate a 3D cone. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the cone basis. + \param size_z The cone's height. + \param subdivisions The number of basis angular subdivisions. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cone3d(faces3d,50); + CImg().display_object3d("Cone3d",points3d,faces3d); + \endcode + \image html ref_cone3d.jpg + **/ + template + static CImg cone3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,size_z, + 0.,0.,0.); + 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. + /** + \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). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cylinder3d(faces3d,50); + CImg().display_object3d("Cylinder3d",points3d,faces3d); + \endcode + \image html ref_cylinder3d.jpg + **/ + template + static CImg cylinder3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,0., + 0.,0.,size_z); + 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); + } + return vertices>'x'; + } + + //! Generate a 3D torus. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius1 The large radius. + \param radius2 The small radius. + \param subdivisions1 The number of angular subdivisions for the large radius. + \param subdivisions2 The number of angular subdivisions for the small radius. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::torus3d(faces3d,20,4); + CImg().display_object3d("Torus3d",points3d,faces3d); + \endcode + \image html ref_torus3d.jpg + **/ + template + static CImg torus3d(CImgList& primitives, + const float radius1=100, const float radius2=30, + const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) { + primitives.assign(); + if (!subdivisions1 || !subdivisions2) return CImg(); + 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); + } + } + return vertices>'x'; + } + + //! Generate a 3D XY-plane. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the plane (dimension along the X-axis). + \param size_y The height of the plane (dimensions along the Y-axis). + \param subdivisions_x The number of planar subdivisions along the X-axis. + \param subdivisions_y The number of planar subdivisions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::plane3d(faces3d,100,50); + CImg().display_object3d("Plane3d",points3d,faces3d); + \endcode + \image html ref_plane3d.jpg + **/ + template + static CImg plane3d(CImgList& primitives, + const float size_x=100, const float size_y=100, + const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) { + primitives.assign(); + if (!subdivisions_x || !subdivisions_y) return CImg(); + CImgList vertices; + const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1; + const float fx = (float)size_x/w, fy = (float)size_y/h; + for (unsigned int y = 0; y::vector(fx*x,fy*y,0).move_to(vertices); + for (unsigned int y = 0; y::vector(off1,off4,off3,off2).move_to(primitives); + } + return vertices>'x'; + } + + //! Generate a 3D sphere. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the sphere (dimension along the X-axis). + \param subdivisions The number of recursive subdivisions from an initial icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::sphere3d(faces3d,100,4); + CImg().display_object3d("Sphere3d",points3d,faces3d); + \endcode + \image html ref_sphere3d.jpg + **/ + template + static CImg sphere3d(CImgList& primitives, + const float radius=50, const unsigned int subdivisions=3) { + + // Create initial icosahedron + primitives.assign(); + 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); + // edge - length/2 + float he = (float)a; + + // Recurse subdivisions + for (unsigned int i = 0; i::vector(nx0,ny0,nz0).move_to(vertices); i0 = vertices.width() - 1; } + if (i1<0) { CImg::vector(nx1,ny1,nz1).move_to(vertices); i1 = vertices.width() - 1; } + if (i2<0) { CImg::vector(nx2,ny2,nz2).move_to(vertices); i2 = vertices.width() - 1; } + primitives.remove(0); + CImg::vector(p0,i0,i1).move_to(primitives); + CImg::vector((tf)i0,(tf)p1,(tf)i2).move_to(primitives); + CImg::vector((tf)i1,(tf)i2,(tf)p2).move_to(primitives); + CImg::vector((tf)i1,(tf)i0,(tf)i2).move_to(primitives); + } + } + return (vertices>'x')*=radius; + } + + //! Generate a 3D ellipsoid. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param tensor The tensor which gives the shape and size of the ellipsoid. + \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg tensor = CImg::diagonal(10,7,3), + points3d = CImg::ellipsoid3d(faces3d,tensor,4); + CImg().display_object3d("Ellipsoid3d",points3d,faces3d); + \endcode + \image html ref_ellipsoid3d.jpg + **/ + template + static CImg ellipsoid3d(CImgList& primitives, + const CImg& tensor, const unsigned int subdivisions=3) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImg S, V; + tensor.symmetric_eigen(S,V); + const float orient = + (V(0,1)*V(1,2) - V(0,2)*V(1,1))*V(2,0) + + (V(0,2)*V(1,0) - V(0,0)*V(1,2))*V(2,1) + + (V(0,0)*V(1,1) - V(0,1)*V(1,0))*V(2,2); + if (orient<0) { V(2,0) = -V(2,0); V(2,1) = -V(2,1); V(2,2) = -V(2,2); } + const float l0 = S[0], l1 = S[1], l2 = S[2]; + CImg 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. + /** + \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, + const CImgList& colors, + const to& opacities, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg& object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + 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. + CImg& object3dtoCImg3d(const bool full_check=true) { + return get_object3dtoCImg3d(full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \newinstance. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true) const { + 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).", + cimg_instance,_width,primitives._width,error_message.data()); + CImg res(1,_size_object3dtoCImg3d(primitives,colors,opacities)); + float *ptrd = res._data; + + // Put magick number. + *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f; + *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f; + + // Put number of vertices and primitives. + *(ptrd++) = cimg::uint2float(_width); + *(ptrd++) = cimg::uint2float(primitives._width); + + // Put vertex data. + if (is_empty() || !primitives) return res; + const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2); + cimg_forX(*this,p) { + *(ptrd++) = (float)*(ptrx++); + *(ptrd++) = (float)*(ptry++); + *(ptrd++) = (float)*(ptrz++); + } + + // Put primitive data. + cimglist_for(primitives,p) { + *(ptrd++) = (float)primitives[p].size(); + const tp *ptrp = primitives[p]._data; + cimg_foroff(primitives[p],i) *(ptrd++) = cimg::uint2float((unsigned int)*(ptrp++)); + } + + // Put color/texture data. + const unsigned int csiz = std::min(colors._width,primitives._width); + for (int c = 0; c<(int)csiz; ++c) { + const CImg& color = colors[c]; + const tc *ptrc = color._data; + if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; } + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (color.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImgList& opacities, float *ptrd) const { + cimglist_for(opacities,o) { + const CImg& opacity = opacities[o]; + const to *ptro = opacity._data; + if (opacity.size()==1) *(ptrd++) = (float)*ptro; + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (opacity.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImg& opacities, float *ptrd) const { + const to *ptro = opacities._data; + cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++); + return ptrd; + } + + template + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImgList& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + if (colors[c].is_shared()) siz+=4; + else { const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; } + } + if (colors._width + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImg& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; + } + if (colors._width + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) const { + CImgList opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const bool full_check=true) const { + CImgList colors, opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! 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); + cimglist_for(primitives,p) primitives(p,0) = p; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert CImg3d representation into a 3D object. + /** + \param[out] primitives Primitives data of the 3D object. + \param[out] colors Colors data of the 3D object. + \param[out] opacities Opacities data of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + **/ + template + CImg& CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) { + return get_CImg3dtoobject3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert CImg3d representation into a 3D object \newinstance. + template + CImg get_CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) const { + CImg error_message(1024); + if (!is_CImg3d(full_check,error_message)) + throw CImgInstanceException(_cimg_instance + "CImg3dtoobject3d(): image instance is not a CImg3d (%s).", + cimg_instance,error_message.data()); + const T *ptrs = _data + 6; + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + const CImg points = CImg(ptrs,3,nb_points,1,1,true).get_transpose(); + ptrs+=3*nb_points; + primitives.assign(nb_primitives); + cimglist_for(primitives,p) { + const unsigned int nb_inds = (unsigned int)*(ptrs++); + primitives[p].assign(1,nb_inds); + tp *ptrp = primitives[p]._data; + for (unsigned int i = 0; i + 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 int nx0 = x0>0?x0:0, nx1 = x1=0) { + const tc *col = color; + const ulongT off = whd - dx - 1; + T *ptrd = data(nx0,y); + if (opacity>=1) { // ** Opaque drawing ** + if (brightness==1) { // Brightness==1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)*(col++); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)*(col++); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else if (brightness<1) { // Brightness<1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else { // Brightness>1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } + } else { // ** Transparent drawing ** + if (brightness==1) { // Brightness==1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else if (brightness<=1) { // Brightness<1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*brightness*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else { // Brightness>1 + cimg_forC(*this,c) { + const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*maxval)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } + } + } + return *this; + } + + //! Draw a 3D point. + /** + \param x0 X-coordinate of the point. + \param y0 Y-coordinate of the point. + \param z0 Z-coordinate of the point. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \note + - To set pixel values without clipping needs, you should use the faster CImg::operator()() function. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_point(50,50,color); + \endcode + **/ + template + CImg& draw_point(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_point(): Specified color is (null).", + cimg_instance); + if (x0>=0 && y0>=0 && z0>=0 && x0=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + return *this; + } + + //! Draw a 2D point \simplification. + template + CImg& draw_point(const int x0, const int y0, + const tc *const color, const float opacity=1) { + return draw_point(x0,y0,0,color,opacity); + } + + // Draw a points cloud. + /** + \param points Image of vertices coordinates. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_point(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_point(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),color,opacity); + } break; + default : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),(int)points(i,2),color,opacity); + } + } + return *this; + } + + //! Draw a 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + \note + - Line routine uses Bresenham's algorithm. + - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,color); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; } + } + 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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 2D line, with z-buffering. + /** + \param zbuffer Zbuffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz && pattern&hatch) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 3D line. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + 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.f + nx1 - nx0; + ny0-=(int)((float)nx0*(1.f + ny1 - ny0)/D); + nz0-=(int)((float)nx0*(1.f + nz1 - nz0)/D); + nx0 = 0; + } + if (nx1>=width()) { + const float d = (float)nx1 - width(), D = 1.f + nx1 - nx0; + ny1+=(int)(d*(1.f + ny0 - ny1)/D); + nz1+=(int)(d*(1.f + 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.f + ny1 - ny0; + nx0-=(int)((float)ny0*(1.f + nx1 - nx0)/D); + nz0-=(int)((float)ny0*(1.f + nz1 - nz0)/D); + ny0 = 0; + } + if (ny1>=height()) { + const float d = (float)ny1 - height(), D = 1.f + ny1 - ny0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + nz1+=(int)(d*(1.f + 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.f + nz1 - nz0; + nx0-=(int)((float)nz0*(1.f + nx1 - nx0)/D); + ny0-=(int)((float)nz0*(1.f + ny1 - ny0)/D); + nz0 = 0; + } + if (nz1>=depth()) { + const float d = (float)nz1 - depth(), D = 1.f + nz1 - nz0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + ny1+=(int)(d*(1.f + 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.f); + for (unsigned int t = 0; t<=dmax; ++t) { + if (!(~pattern) || (~pattern && pattern&hatch)) { + T* ptrd = data((unsigned int)x,(unsigned int)y,(unsigned int)z); + const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } + } + } + return *this; + } + + //! Draw a textured 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + \note + - Line routine uses the well known Bresenham's algorithm. + \par Example: + \code + CImg img(100,100,1,3,0), texture("texture256x256.ppm"); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,texture,0,0,255,255); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + 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()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + if (pattern&hatch) { + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(const int x0, const int y0, const float z0, + const int x1, const 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 (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction and z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const 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_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a set of consecutive lines. + /** + \param points Coordinates of vertices, stored as a list of vectors. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If set to true, init hatch motif. + \note + - This function uses several call to the single CImg::draw_line() procedure, + depending on the vectors size in \p points. + **/ + template + 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 : + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i + CImg& draw_arrow(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const float angle=30, const float length=-10, + const unsigned int pattern=~0U) { + if (is_empty()) return *this; + const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v, + deg = (float)(angle*cimg::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.f, + l = (length>=0)?length:-length*(float)std::sqrt(sq)/100; + if (sq>0) { + const float + cl = (float)std::cos(ang - deg), sl = (float)std::sin(ang - deg), + cr = (float)std::cos(ang + deg), sr = (float)std::sin(ang + deg); + const int + xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl), + xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr), + xc = x1 + (int)((l + 1)*(cl + cr))/2, yc = y1 + (int)((l + 1)*(sl + sr))/2; + draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity); + } else draw_point(x0,y0,color,opacity); + return *this; + } + + //! Draw a 2D spline. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + \note + - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points + and corresponding velocity vectors. + - The spline is drawn as a serie of connected segments. The \p precision parameter sets the + average number of pixels in each drawn segment. + - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), + (\p xb,\p yb), (\p x1,\p y1) } where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point + and (\p xa,\p ya), (\p xb,\p yb) are two + \e control points. + The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from + the control points as + \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb). + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,255,255 }; + img.draw_spline(30,30,0,100,90,40,0,-100,color); + \endcode + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const tc *const color, const float opacity=1, + const float precision=0.25, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Specified color is (null).", + cimg_instance); + if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0; + for (float t = 0; t<1; t+=_precision) { + const float t2 = t*t, t3 = t2*t; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t + x0), + ny = (int)(ay*t3 + by*t2 + v0*t + y0); + draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; + } + return draw_line(ox,oy,x1,y1,color,opacity,pattern,false); + } + + //! Draw a 3D spline \overloading. + /** + \note + - Similar to CImg::draw_spline() for a 3D spline in a volumetric image. + **/ + template + 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. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param texture Texture image defining line pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch if \c true, reinit hatch motif. + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const CImg& texture, + const int tx0, const int ty0, const int tx1, const int ty1, + const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_empty()) return *this; + if (is_overlapped(texture)) + return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch); + if (x0==x1 && y0==y1) + return draw_point(x0,y0,texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0),opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0, otx = tx0, oty = ty0; + for (float t1 = 0; t1<1; t1+=_precision) { + const float t2 = t1*t1, t3 = t2*t1; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0), + ny = (int)(ay*t3 + by*t2 + v0*t1 + y0), + ntx = tx0 + (int)((tx1 - tx0)*t1), + nty = ty0 + (int)((ty1 - ty0)*t1); + draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; otx = ntx; oty = nty; + } + return draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false); + } + + //! Draw a set of consecutive splines. + /** + \param points Vertices data. + \param tangents Tangents data. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param is_closed_set Tells if the drawn spline set is closed. + \param precision Precision of the drawing. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + **/ + template + CImg& draw_spline(const CImg& points, const CImg& tangents, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || !tangents || points._width<2 || tangents._width<2) return *this; + bool ninit_hatch = init_hatch; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1); + int ox = x0, oy = y0; + float ou = u0, ov = v0; + for (unsigned int i = 1; i + CImg& draw_spline(const CImg& points, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || points._width<2) return *this; + CImg tangents; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + tangents.assign(points._width,points._height); + cimg_forX(points,p) { + const unsigned int + p0 = is_closed_set?(p + points._width - 1)%points._width:(p?p - 1:0), + p1 = is_closed_set?(p + 1)%points._width:(p + 1=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, + 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); + } + return *this; + } + + //! Draw a filled 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1); + return *this; + } + + //! Draw a outlined 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + draw_line(x0,y0,x1,y1,color,opacity,pattern,true). + draw_line(x1,y1,x2,y2,color,opacity,pattern,false). + draw_line(x2,y2,x0,y0,color,opacity,pattern,false); + return *this; + } + + //! Draw a filled 2D triangle, with z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param z0 Z-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param z1 Z-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param z2 Z-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param brightness Brightness factor. + **/ + template + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + } + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param brightness0 Brightness factor of the first vertex (in [0,2]). + \param brightness1 brightness factor of the second vertex (in [0,2]). + \param brightness2 brightness factor of the third vertex (in [0,2]). + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle, with z-buffering \overloading. + template + 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, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a color-interpolated 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color1 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the first vertex. + \param color2 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the seconf vertex. + \param color3 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the third vertex. + \param opacity Drawing opacity. + **/ + template + 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, + const float opacity=1) { + const unsigned char one = 1; + cimg_forC(*this,c) + get_shared_channel(c).draw_triangle(x0,y0,x1,y1,x2,y2,&one,color1[c],color2[c],color3[c],opacity); + return *this; + } + + //! Draw a textured 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param opacity Drawing opacity. + \param brightness Brightness factor of the drawing (in [0,2]). + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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 opacity=1, + const float brightness=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y, + nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + } + return *this; + } + + //! Draw a 2D textured triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int xleft = xleft0, xright = xright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xright=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval)); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle, with z-buffering. + template + 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, + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; pzl = pzn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + *ptrd = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + const T val = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param brightness0 Brightness factor of the first vertex. + \param brightness1 Brightness factor of the second vertex. + \param brightness2 Brightness factor of the third vertex. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y, + nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction \overloading. + template + 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, + 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, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright - cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc - (dx?dx*(dc/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errc = dx>>1; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + zleft-=xleft*(zright - zleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction and z-buffering \overloading. + template + 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, + 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, + 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 + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y, + nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (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 float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + zleft-=xleft*(zright - zleft)/dx; + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a filled 4D rectangle. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param z0 Z-coordinate of the upper-left rectangle corner. + \param c0 C-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param z1 Z-coordinate of the lower-right rectangle corner. + \param c1 C-coordinate of the lower-right rectangle corner. + \param val Scalar value used to fill the rectangle area. + \param opacity Drawing opacity. + **/ + CImg& draw_rectangle(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const T val, const float opacity=1) { + if (is_empty()) return *this; + const int + nx0 = x0=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.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; 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=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_rectangle(): Specified color is (null).", + cimg_instance); + cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,(T)color[c],opacity); + return *this; + } + + //! Draw an outlined 3D rectangle \overloading. + template + 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. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1) { + return draw_rectangle(x0,y0,0,x1,y1,_depth - 1,color,opacity); + } + + //! Draw a outlined 2D rectangle \overloading. + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true); + if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true); + const int + nx0 = x0 + CImg& draw_polygon(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Specified color is (null).", + cimg_instance); + if (points._width==1) return draw_point((int)points(0,0),(int)points(0,1),color,opacity); + if (points._width==2) return draw_line((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1),color,opacity); + if (points._width==3) return draw_triangle((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1), + (int)points(2,0),(int)points(2,1),color,opacity); + cimg_init_scanline(color,opacity); + int + xmin = 0, ymin = 0, + xmax = points.get_shared_row(0).max_min(xmin), + ymax = points.get_shared_row(1).max_min(ymin); + if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this; + if (ymin==ymax) return draw_line(xmin,ymin,xmax,ymax,color,opacity); + + ymin = std::max(0,ymin); + ymax = std::min(height() - 1,ymax); + CImg Xs(points._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 tn = an; + while (points(tn,1)==y1) (tn+=1)%=points._width; + + if (y0!=y1) { + const int + y2 = (int)points(tn,1), + x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1, + dy = cimg::sign(y01), + tmax = std::max(1,cimg::abs(y01)), + tend = tmax - (dy==cimg::sign(y12)); + unsigned int y = (unsigned int)y0 - ymin; + for (int t = 0; t<=tend; ++t, y+=dy) + if (yn; + n = nn; + nn = an; + } + + cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>=(cimg_openmp_sizefactor)*32)) + 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, + const tc *const color, const float opacity, const unsigned int pattern) { + if (is_empty() || !points || points._width<3) return *this; + bool ninit_hatch = true; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Invalid specified point set.", + cimg_instance); + case 2 : { // 2D version + CImg 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 + 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); + } + + //! Draw a filled 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity=1) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity); + } + + //! Draw an outlined 2D ellipse. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param r1 First radius of the ellipse. + \param r2 Second radius of the ellipse. + \param angle Angle of the first radius. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + 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); + return *this; + } + + //! Draw an outlined 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity, + const unsigned int pattern) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity,pattern); + } + + template + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_ellipse(): Specified color is (null).", + cimg_instance); + if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity); + if (r1==r2 && (float)(int)r1==r1) { + if (pattern) return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity,pattern); + else return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity); + } + cimg_init_scanline(color,opacity); + const float + nr1 = cimg::abs(r1) - 0.5, nr2 = cimg::abs(r2) - 0.5, + nangle = (float)(angle*cimg::PI/180), + u = (float)std::cos(nangle), + v = (float)std::sin(nangle), + rmax = std::max(nr1,nr2), + l1 = (float)std::pow(rmax/(nr1>0?nr1:1e-6),2), + l2 = (float)std::pow(rmax/(nr2>0?nr2:1e-6),2), + a = l1*u*u + l2*v*v, + b = u*v*(l1 - l2), + c = l1*v*v + l2*u*u; + const int + yb = (int)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 + Y = y - y0 + (y0?(float)std::sqrt(delta)/a:0.f, + 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 + 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 (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + cimg_init_scanline(color,opacity); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (y0>=0 && y0=0) { + const int x1 = x0 - x, x2 = x0 + x, y1 = y0 - y, y2 = y0 + y; + if (y1>=0 && y1=0 && y2=0 && y1=0 && y2 + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (!radius) return draw_point(x0,y0,color,opacity); + draw_point(x0 - radius,y0,color,opacity).draw_point(x0 + radius,y0,color,opacity). + draw_point(x0,y0 - radius,color,opacity).draw_point(x0,y0 + radius,color,opacity); + if (radius==1) return *this; + for (int f = 1 - radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x=0) { f+=(ddFy+=2); --y; } + ++x; ++(f+=(ddFx+=2)); + if (x!=y + 1) { + const int x1 = x0 - y, x2 = x0 + y, y1 = y0 - x, y2 = y0 + x, + x3 = x0 - x, x4 = x0 + x, y3 = y0 - y, y4 = y0 + y; + draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity). + draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity); + if (x!=y) + draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity). + draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity); + } + } + return *this; + } + + //! Draw an image. + /** + \param sprite Sprite image. + \param x0 X-coordinate of the sprite position. + \param y0 Y-coordinate of the sprite position. + \param z0 Z-coordinate of the sprite position. + \param c0 C-coordinate of the sprite position. + \param opacity Drawing opacity. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const t + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; x& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const T + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ), + slX = lX*sizeof(T); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; y + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,z0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const CImg& sprite, const float opacity=1) { + return draw_image(0,sprite,opacity); + } + + //! Draw a masked image. + /** + \param sprite Sprite image. + \param mask Mask image. + \param x0 X-coordinate of the sprite position in the image instance. + \param y0 Y-coordinate of the sprite position in the image instance. + \param z0 Z-coordinate of the sprite position in the image instance. + \param c0 C-coordinate of the sprite position in the image instance. + \param mask_max_value Maximum pixel value of the mask image \c mask. + \param opacity Drawing opacity. + \note + - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite. + - Dimensions along x,y and z of \p sprite and \p mask must be the same. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + if (is_empty() || !sprite || !mask) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_max_value); + if (is_overlapped(mask)) return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_max_value); + if (mask._width!=sprite._width || mask._height!=sprite._height || mask._depth!=sprite._depth) + throw CImgArgumentException(_cimg_instance + "draw_image(): Sprite (%u,%u,%u,%u,%p) and mask (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + sprite._width,sprite._height,sprite._depth,sprite._spectrum,sprite._data, + mask._width,mask._height,mask._depth,mask._spectrum,mask._data); + + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const ulongT + coff = (bx?-x0:0) + + (by?-y0*(ulongT)mask.width():0) + + (bz?-z0*(ulongT)mask.width()*mask.height():0) + + (bc?-c0*(ulongT)mask.width()*mask.height()*mask.depth():0), + ssize = (ulongT)mask.width()*mask.height()*mask.depth()*mask.spectrum(); + const ti *ptrs = sprite._data + coff; + const tm *ptrm = mask._data + coff; + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + if (lX>0 && lY>0 && lZ>0 && lC>0) { + T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0); + for (int c = 0; c + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw an image. + template + CImg& draw_image(const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a text string. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Pointer to \c spectrum() consecutive values, defining the foreground drawing color. + \param background_color Pointer to \c spectrum() consecutive values, defining the background drawing color. + \param opacity Drawing opacity. + \param font Font used for drawing text. + **/ + template + CImg& 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent background is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent foreground is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Array of spectrum() values of type \c T, + defining the foreground color (0 means 'transparent'). + \param background_color Array of spectrum() values of type \c T, + defining the background color (0 means 'transparent'). + \param opacity Drawing opacity. + \param font_height Height of the text font (exact match for 13,23,53,103, interpolated otherwise). + **/ + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc1 *const foreground_color, const tc2 *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + 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; + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc *const foreground_color, const int background_color=0, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + cimg::unused(background_color); + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",foreground_color,(const tc*)0,opacity,font_height,tmp._data); + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const int, const tc *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",(tc*)0,background_color,opacity,font_height,tmp._data); + } + + template + CImg& _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, + const bool is_native_font) { + if (!text) return *this; + if (!font) + throw CImgArgumentException(_cimg_instance + "draw_text(): Empty specified font.", + cimg_instance); + + const unsigned int text_length = (unsigned int)std::strlen(text); + if (is_empty()) { + // If needed, pre-compute necessary size of the image + int x = 0, y = 0, w = 0; + unsigned char c = 0; + for (unsigned int i = 0; iw) w = x; x = 0; break; + case '\t' : x+=4*font[' ']._width; break; + default : if (cw) w=x; + y+=font[0]._height; + } + assign(x0 + w,y0 + y,1,is_native_font?1:font[0]._spectrum,(T)0); + } + + int x = x0, y = y0; + for (unsigned int i = 0; i letter = font[c]; + if (letter) { + if (is_native_font && _spectrum>letter._spectrum) letter.resize(-100,-100,1,_spectrum,0,2); + const unsigned int cmin = std::min(_spectrum,letter._spectrum); + if (foreground_color) + for (unsigned int c = 0; c& __draw_text(const char *const text, const bool 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 label, labelmask; + const unsigned char labelcolor = 127; + const unsigned int fsize = 13; + label.draw_text(0,0,"%s",&labelcolor,0,1,fsize,tmp._data); + if (label) { + label.crop(2,0,label.width() - 1,label.height()); + ((labelmask = label)+=label.get_dilate(5)).max(80); + (label*=2).resize(-100,-100,1,3,1); + return draw_image(0,is_down?height() - fsize:0,label,labelmask,1,254); + } + return *this; + } + + //! Draw a 2D vector field. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Drawing opacity. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const t2 *const color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + return draw_quiver(flow,CImg(color,_spectrum,1,1,1,true),opacity,sampling,factor,is_arrow,pattern); + } + + //! Draw a 2D vector field, using a field of colors. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Opacity of the drawing. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const CImg& color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + if (is_empty()) return *this; + if (!flow || flow._spectrum!=2) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid dimensions of specified flow (%u,%u,%u,%u,%p).", + cimg_instance, + flow._width,flow._height,flow._depth,flow._spectrum,flow._data); + if (sampling<=0) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid sampling value %g " + "(should be >0)", + cimg_instance, + sampling); + const bool colorfield = (color._width==flow._width && color._height==flow._height && + color._depth==1 && color._spectrum==_spectrum); + if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,is_arrow,pattern); + float vmax,fact; + if (factor<=0) { + float m, M = (float)flow.get_norm(2).max_min(m); + vmax = (float)std::max(cimg::abs(m),cimg::abs(M)); + if (!vmax) vmax = 1; + fact = -factor; + } else { fact = factor; vmax = 1; } + + for (unsigned int y = sampling/2; y<_height; y+=sampling) + for (unsigned int x = sampling/2; x<_width; x+=sampling) { + const unsigned int X = x*flow._width/_width, Y = y*flow._height/_height; + float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax; + if (is_arrow) { + const int xx = (int)(x + u), yy = (int)(y + v); + if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y)._data,opacity,45,sampling/5.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), + color.get_vector_at(X,Y)._data,opacity,pattern); + else draw_line((int)(x - 0.5*u),(int)(y - 0.5*v),(int)(x + 0.5*u),(int)(y + 0.5*v), + color._data,opacity,pattern); + } + } + return *this; + } + + //! Draw a labeled horizontal axis. + /** + \param values_x Values along the horizontal axis. + \param y Y-coordinate of the horizontal axis in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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 + draw_line(0,y,_width - 1,y,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_x); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _xt = (width() - label.width())/2, + xt = _xt<3?3:_xt + label.width()>=width() - 2?width() - 3 - label.width():_xt; + draw_point(width()/2,y - 1,color,opacity).draw_point(width()/2,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_x[0]=width() - 2?width() - 3 - label.width():_xt; + draw_point(xi,y - 1,color,opacity).draw_point(xi,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw a labeled vertical axis. + /** + \param x X-coordinate of the vertical axis in the image instance. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + if (is_empty()) return *this; + int siz = (int)values_y.size() - 1; + CImg txt(32); + CImg label; + if (siz<=0) { // Degenerated case + draw_line(x,0,x,_height - 1,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_y); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _yt = (height() - label.height())/2, + yt = _yt<0?0:_yt + label.height()>=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,height()/2,color,opacity).draw_point(x + 1,height()/2,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_y[0]=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,yi,color,opacity).draw_point(x + 1,yi,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes. + /** + \param values_x Values along the X-axis. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for the X-axis. + \param pattern_y Drawing pattern for the Y-axis. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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; + if (sizx>=0) { + float ox = (float)*nvalues_x; + for (unsigned int x = sizx?1U:0U; x<_width; ++x) { + const float nx = (float)nvalues_x._linear_atX((float)x*sizx/wm1); + if (nx*ox<=0) { draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero); break; } + ox = nx; + } + } + const CImg nvalues_y(values_y._data,values_y.size(),1,1,1,true); + const int sizy = (int)values_y.size() - 1, hm1 = height() - 1; + if (sizy>0) { + float oy = (float)nvalues_y[0]; + for (unsigned int y = sizy?1U:0U; y<_height; ++y) { + const float ny = (float)nvalues_y._linear_atX((float)y*sizy/hm1); + if (ny*oy<=0) { draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero); break; } + oy = ny; + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes \overloading. + template + CImg& draw_axes(const float x0, const float x1, const float y0, const float y1, + const tc *const color, const float opacity=1, + const int subdivisionx=-60, const int subdivisiony=-60, + const float precisionx=0, const float precisiony=0, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U, + const unsigned int font_height=13) { + if (is_empty()) return *this; + const bool allow_zero = (x0*x1>0) || (y0*y1>0); + const float + dx = cimg::abs(x1 - x0), dy = cimg::abs(y1 - y0), + px = dx<=0?1:precisionx==0?(float)std::pow(10.,(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); + 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); + 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); + return *this; + } + + //! Draw 2D grid. + /** + \param values_x X-coordinates of the vertical lines. + \param values_y Y-coordinates of the horizontal lines. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for vertical lines. + \param pattern_y Drawing pattern for horizontal lines. + **/ + template + CImg& draw_grid(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) { + if (is_empty()) return *this; + if (values_x) cimg_foroff(values_x,x) { + const int xi = (int)values_x[x]; + if (xi>=0 && xi=0 && yi + CImg& draw_grid(const float delta_x, const float delta_y, + const float offsetx, const float offsety, + const bool invertx, const bool inverty, + const tc *const color, const float opacity=1, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) { + if (is_empty()) return *this; + CImg seqx, seqy; + if (delta_x!=0) { + const float dx = delta_x>0?delta_x:_width*-delta_x/100; + const unsigned int nx = (unsigned int)(_width/dx); + seqx = CImg::sequence(1 + nx,0,(unsigned int)(dx*nx)); + if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x) + offsetx,(float)_width); + if (invertx) cimg_foroff(seqx,x) seqx(x) = _width - 1 - seqx(x); + } + if (delta_y!=0) { + const float dy = delta_y>0?delta_y:_height*-delta_y/100; + const unsigned int ny = (unsigned int)(_height/dy); + seqy = CImg::sequence(1 + ny,0,(unsigned int)(dy*ny)); + if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y) + offsety,(float)_height); + if (inverty) cimg_foroff(seqy,y) seqy(y) = _height - 1 - seqy(y); + } + return draw_grid(seqx,seqy,color,opacity,pattern_x,pattern_y); + } + + //! Draw 1D graph. + /** + \param data Image containing the graph values I = f(x). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + + \param plot_type Define the type of the plot: + - 0 = No plot. + - 1 = Plot using segments. + - 2 = Plot using cubic splines. + - 3 = Plot with bars. + \param vertex_type Define the type of points: + - 0 = No points. + - 1 = Point. + - 2 = Straight cross. + - 3 = Diagonal cross. + - 4 = Filled circle. + - 5 = Outlined circle. + - 6 = Square. + - 7 = Diamond. + \param ymin Lower bound of the y-range. + \param ymax Upper bound of the y-range. + \param pattern Drawing pattern. + \note + - if \c ymin==ymax==0, the y-range is computed automatically from the input samples. + **/ + template + CImg& draw_graph(const CImg& data, + const tc *const color, const float opacity=1, + const unsigned int plot_type=1, const int vertex_type=1, + const double ymin=0, const double ymax=0, const unsigned int pattern=~0U) { + if (is_empty() || _height<=1) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_graph(): Specified color is (null).", + cimg_instance); + + // Create shaded colors for displaying bar plots. + CImg color1, color2; + if (plot_type==3) { + color1.assign(_spectrum); color2.assign(_spectrum); + cimg_forC(*this,c) { + color1[c] = (tc)std::min((float)cimg::type::max(),(float)color[c]*1.2f); + color2[c] = (tc)(color[c]*0.4f); + } + } + + // Compute min/max and normalization factors. + const ulongT + siz = data.size(), + _siz1 = siz - (plot_type!=3), + siz1 = _siz1?_siz1:1; + const unsigned int + _width1 = _width - (plot_type!=3), + width1 = _width1?_width1:1; + double m = ymin, M = ymax; + if (ymin==ymax) m = (double)data.max_min(M); + if (m==M) { --m; ++M; } + const float ca = (float)(M-m)/(_height - 1); + bool init_hatch = true; + + // Draw graph edges + switch (plot_type%4) { + case 1 : { // Segments + int oX = 0, oY = (int)((data[0] - m)/ca); + if (siz==1) { + const int Y = (int)((*data - m)/ca); + draw_line(0,Y,width() - 1,Y,color,opacity,pattern); + } else { + const float fx = (float)_width/siz1; + for (ulongT off = 1; off ndata(data._data,siz,1,1,1,true); + int oY = (int)((data[0] - m)/ca); + cimg_forX(*this,x) { + const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-m)/ca); + if (x>0) draw_line(x,oY,x + 1,Y,color,opacity,pattern,init_hatch); + init_hatch = false; + oY = Y; + } + } break; + case 3 : { // Bars + const int Y0 = (int)(-m/ca); + const float fx = (float)_width/siz1; + int oX = 0; + cimg_foroff(data,off) { + const int + X = (int)((off + 1)*fx) - 1, + Y = (int)((data[off] - m)/ca); + draw_rectangle(oX,Y0,X,Y,color,opacity). + draw_line(oX,Y,oX,Y0,color2.data(),opacity). + draw_line(oX,Y0,X,Y0,Y<=Y0?color2.data():color1.data(),opacity). + draw_line(X,Y,X,Y0,color1.data(),opacity). + draw_line(oX,Y,X,Y,Y<=Y0?color1.data():color2.data(),opacity); + oX = X + 1; + } + } break; + default : break; // No edges + } + + // Draw graph points + const unsigned int wb2 = plot_type==3?_width1/(2*siz):0; + const float fx = (float)_width1/siz1; + switch (vertex_type%8) { + case 1 : { // Point + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_point(X,Y,color,opacity); + } + } break; + case 2 : { // Straight Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y,X + 3,Y,color,opacity).draw_line(X,Y - 3,X,Y + 3,color,opacity); + } + } break; + case 3 : { // Diagonal Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y - 3,X + 3,Y + 3,color,opacity).draw_line(X - 3,Y + 3,X + 3,Y - 3,color,opacity); + } + } break; + case 4 : { // Filled Circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity); + } + } break; + case 5 : { // Outlined circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity,0U); + } + } break; + case 6 : { // Square + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_rectangle(X - 3,Y - 3,X + 3,Y + 3,color,opacity,~0U); + } + } break; + case 7 : { // Diamond + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X,Y - 4,X + 4,Y,color,opacity). + draw_line(X + 4,Y,X,Y + 4,color,opacity). + draw_line(X,Y + 4,X - 4,Y,color,opacity). + draw_line(X - 4,Y,X,Y - 4,color,opacity); + } + } break; + default : break; // No points + } + return *this; + } + + bool _draw_fill(const int x, const int y, const int z, + const CImg& ref, const float tolerance2) const { + const T *ptr1 = data(x,y,z), *ptr2 = ref._data; + const unsigned long off = _width*_height*_depth; + float diff = 0; + cimg_forC(*this,c) { diff += cimg::sqr(*ptr1 - *(ptr2++)); ptr1+=off; } + return diff<=tolerance2; + } + + //! Draw filled 3D region with the flood fill algorithm. + /** + \param x0 X-coordinate of the starting point of the region to fill. + \param y0 Y-coordinate of the starting point of the region to fill. + \param z0 Z-coordinate of the starting point of the region to fill. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param[out] region Image that will contain the mask of the filled region mask, as an output. + \param tolerance Tolerance concerning neighborhood values. + \param opacity Opacity of the drawing. + \param is_high_connectivity Tells if 8-connexity must be used. + \return \c region is initialized with the binary mask of the filled region. + **/ + template + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity, + CImg ®ion, + const float tolerance = 0, + const bool is_high_connectivity = false) { +#define _draw_fill_push(x,y,z) if (N>=stack._width) stack.resize(2*N + 1,1,1,3,0); \ + stack[N] = x; stack(N,1) = y; stack(N++,2) = z +#define _draw_fill_pop(x,y,z) x = stack[--N]; y = stack(N,1); z = stack(N,2) +#define _draw_fill_is_inside(x,y,z) !_region(x,y,z) && _draw_fill(x,y,z,ref,tolerance2) + + if (!containsXYZC(x0,y0,z0,0)) return *this; + const float nopacity = cimg::abs((float)opacity), copacity = 1 - std::max((float)opacity,0.f); + const float tolerance2 = cimg::sqr(tolerance); + const CImg ref = get_vector_at(x0,y0,z0); + CImg stack(256,1,1,3); + CImg _region(_width,_height,_depth,1,0); + unsigned int N = 0; + int x, y, z; + + _draw_fill_push(x0,y0,z0); + while (N>0) { + _draw_fill_pop(x,y,z); + if (!_region(x,y,z)) { + const int yp = y - 1, yn = y + 1, zp = z - 1, zn = z + 1; + int xl = x, xr = x; + + // Using these booleans reduces the number of pushes drastically. + bool is_yp = false, is_yn = false, is_zp = false, is_zn = false; + for (int step = -1; step<2; step+=2) { + while (x>=0 && x=0 && _draw_fill_is_inside(x,yp,z)) { + if (!is_yp) { _draw_fill_push(x,yp,z); is_yp = true; } + } else is_yp = false; + if (yn1) { + if (zp>=0 && _draw_fill_is_inside(x,y,zp)) { + if (!is_zp) { _draw_fill_push(x,y,zp); is_zp = true; } + } else is_zp = false; + if (zn=0 && !is_yp) { + if (xp>=0 && _draw_fill_is_inside(xp,yp,z)) { + _draw_fill_push(xp,yp,z); if (step<0) is_yp = true; + } + if (xn0) is_yp = true; + } + } + if (yn=0 && _draw_fill_is_inside(xp,yn,z)) { + _draw_fill_push(xp,yn,z); if (step<0) is_yn = true; + } + if (xn0) is_yn = true; + } + } + if (depth()>1) { + if (zp>=0 && !is_zp) { + if (xp>=0 && _draw_fill_is_inside(xp,y,zp)) { + _draw_fill_push(xp,y,zp); if (step<0) is_zp = true; + } + if (xn0) is_zp = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zp)) { _draw_fill_push(x,yp,zp); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zp)) { _draw_fill_push(xp,yp,zp); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zp)) { _draw_fill_push(xp,yn,zp); } + if (xn=0 && _draw_fill_is_inside(xp,y,zn)) { + _draw_fill_push(xp,y,zn); if (step<0) is_zn = true; + } + if (xn0) is_zn = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zn)) { _draw_fill_push(x,yp,zn); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zn)) { _draw_fill_push(xp,yp,zn); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zn)) { _draw_fill_push(xp,yn,zn); } + if (xn + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,z0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw filled 2D region with the flood fill algorithm \simplification. + template + CImg& draw_fill(const int x0, const int y0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw a random plasma texture. + /** + \param alpha Alpha-parameter. + \param beta Beta-parameter. + \param scale Scale-parameter. + \note Use the mid-point algorithm to render. + **/ + CImg& draw_plasma(const float alpha=1, const float beta=0, const unsigned int scale=8) { + if (is_empty()) return *this; + const int w = width(), h = height(); + const Tfloat m = (Tfloat)cimg::type::min(), M = (Tfloat)cimg::type::max(); + ulongT rng = (cimg::_rand(),cimg::rng()); + cimg_forZC(*this,z,c) { + CImg ref = get_shared_slice(z,c); + for (int delta = 1<1; delta>>=1) { + const int delta2 = delta>>1; + const float r = alpha*delta + beta; + + // Square step. + for (int y0 = 0; y0M?M:val); + } + + // Diamond steps. + for (int y = -delta2; yM?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. + /** + \param x0 X-coordinate of the upper-left pixel. + \param y0 Y-coordinate of the upper-left pixel. + \param x1 X-coordinate of the lower-right pixel. + \param y1 Y-coordinate of the lower-right pixel. + \param colormap Colormap. + \param opacity Drawing opacity. + \param z0r Real part of the upper-left fractal vertex. + \param z0i Imaginary part of the upper-left fractal vertex. + \param z1r Real part of the lower-right fractal vertex. + \param z1i Imaginary part of the lower-right fractal vertex. + \param iteration_max Maximum number of iterations for each estimated point. + \param is_normalized_iteration Tells if iterations are normalized. + \param is_julia_set Tells if the Mandelbrot or Julia set is rendered. + \param param_r Real part of the Julia set parameter. + \param param_i Imaginary part of the Julia set parameter. + \note Fractal rendering is done by the Escape Time Algorithm. + **/ + template + CImg& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1, + const CImg& colormap, const float opacity=1, + const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + if (is_empty()) return *this; + CImg palette; + if (colormap) palette.assign(colormap._data,colormap.size()/colormap._spectrum,1,1,colormap._spectrum,true); + if (palette && palette._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_mandelbrot(): Instance and specified colormap (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.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 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; + const double x = z0r + p*(z1r-z0r)/_width, y = z0i + q*(z1i-z0i)/_height; + double zr, zi, cr, ci; + if (is_julia_set) { zr = x; zi = y; cr = param_r; ci = param_i; } + else { zr = param_r; zi = param_i; cr = x; ci = y; } + for (iteration=1; zr*zr + zi*zi<=4 && iteration<=iteration_max; ++iteration) { + const double temp = zr*zr - zi*zi + cr; + zi = 2*zr*zi + ci; + zr = temp; + } + if (iteration>iteration_max) { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette(0,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(0,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)0; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)((*this)(p,q,0,c)*copacity); + } + } else if (is_normalized_iteration) { + const float + normz = (float)cimg::abs(zr*zr + zi*zi), + niteration = (float)(iteration + 1 - std::log(std::log(normz))/ln2); + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._linear_atX(niteration,c); + else cimg_forC(*this,c) + (*this)(p,q,0,c) = (T)(palette._linear_atX(niteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)niteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(niteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } else { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._atX(iteration,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(iteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)iteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(iteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } + } + return *this; + } + + //! Draw a quadratic Mandelbrot or Julia 2D fractal \overloading. + template + 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, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + return draw_mandelbrot(0,0,_width - 1,_height - 1,colormap,opacity, + z0r,z0i,z1r,z1i,iteration_max,is_normalized_iteration,is_julia_set,param_r,param_i); + } + + //! Draw a 1D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param sigma Standard variation of the gaussian distribution. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float sigma, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forX(*this,x) { + const float dx = (x - xc), val = (float)std::exp(-dx*dx/sigma2); + T *ptrd = data(x,0,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 2D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param yc Y-coordinate of the gaussian center. + \param tensor Covariance matrix (must be 2x2). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + typedef typename CImg::Tfloat tfloat; + 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + float dy = -yc; + cimg_forY(*this,y) { + float dx = -xc; + cimg_forX(*this,x) { + const float val = (float)std::exp(a*dx*dx + b*dx*dy + c*dy*dy); + T *ptrd = data(x,y,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + ++dx; + } + ++dy; + } + return *this; + } + + //! Draw a 2D gaussian function \overloading. + template + 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) { + const double + a = r1*ru*ru + r2*rv*rv, + b = (r1-r2)*ru*rv, + c = r1*rv*rv + r2*ru*ru; + const CImg tensor(2,2,1,1, a,b,b,c); + return draw_gaussian(xc,yc,tensor,color,opacity); + } + + //! 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. + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + typedef typename CImg::Tfloat tfloat; + if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + + const CImg 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forXYZ(*this,x,y,z) { + const float + dx = (x - xc), dy = (y - yc), dz = (z - zc), + val = (float)std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz); + T *ptrd = data(x,y,z,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 3D gaussian function \overloading. + template + 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. + /** + \param x0 X-coordinate of the 3D object position + \param y0 Y-coordinate of the 3D object position + \param z0 Z-coordinate of the 3D object position + \param vertices Image Nx3 describing 3D point coordinates + \param primitives List of P primitives + \param colors List of P color (or textures) + \param opacities Image or list of P opacities + \param render_type d Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud) + \param is_double_sided Tells if object faces have two sides or are oriented. + \param focale length of the focale (0 for parallel projection) + \param lightx X-coordinate of the light + \param lighty Y-coordinate of the light + \param lightz Z-coordinate of the light + \param specular_lightness Amount of specular light. + \param specular_shininess Shininess of the object + \param g_opacity Global opacity of the object. + **/ + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } +#endif + + template + static float __draw_object3d(const CImgList& opacities, const unsigned int n_primitive, CImg& opacity) { + if (n_primitive>=opacities._width || opacities[n_primitive].is_empty()) { opacity.assign(); return 1; } + if (opacities[n_primitive].size()==1) { opacity.assign(); return opacities(n_primitive,0); } + opacity.assign(opacities[n_primitive],true); + return 1.f; + } + + template + static float __draw_object3d(const CImg& opacities, const unsigned int n_primitive, CImg& opacity) { + opacity.assign(); + 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 + CImg& _draw_object3d(void *const pboard, CImg& zbuffer, + const float X, const float Y, const float Z, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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).", + cimg_instance,vertices._width,primitives._width,error_message.data()); +#ifndef cimg_use_board + if (pboard) return *this; +#endif + if (render_type==5) cimg::mutex(10); // Static variable used in this case, breaks thread-safety + + const float + nspec = 1 - (specular_lightness<0.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; + + // Create light texture for phong-like rendering. + CImg light_texture; + if (render_type==5) { + if (colors._width>primitives._width) { + static CImg default_light_texture; + static const tc *lptr = 0; + static tc ref_values[64] = { 0 }; + const CImg& img = colors.back(); + bool is_same_texture = (lptr==img._data); + if (is_same_texture) + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + if (ref_values[r++]!=img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum)) { + is_same_texture = false; break; + } + if (!is_same_texture || default_light_texture._spectrum<_spectrum) { + (default_light_texture.assign(img,false)/=255).resize(-100,-100,1,_spectrum); + lptr = colors.back().data(); + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + ref_values[r++] = img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum); + } + light_texture.assign(default_light_texture,true); + } else { + static CImg default_light_texture; + static float olightx = 0, olighty = 0, olightz = 0, ospecular_shininess = 0; + if (!default_light_texture || + lightx!=olightx || lighty!=olighty || lightz!=olightz || + specular_shininess!=ospecular_shininess || default_light_texture._spectrum<_spectrum) { + default_light_texture.assign(512,512); + const float + dlx = lightx - X, + dly = lighty - Y, + dlz = lightz - Z, + nl = cimg::hypot(dlx,dly,dlz), + nlx = (default_light_texture._width - 1)/2*(1 + dlx/nl), + nly = (default_light_texture._height - 1)/2*(1 + dly/nl), + white[] = { 1 }; + default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.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.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; + } + light_texture.assign(default_light_texture,true); + } + } + + // 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_size(projections.size(),4096)) + cimg_forX(projections,l) { // Perspective projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + const tpfloat projectedz = z + Z + absfocale; + projections(l,1) = Y + absfocale*y/projectedz; + projections(l,0) = X + absfocale*x/projectedz; + } + } else { + cimg_pragma_openmp(parallel for cimg_openmp_if_size(projections.size(),4096)) + cimg_forX(projections,l) { // Parallel projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + if (z visibles(primitives._width,1,1,1,~0U); + CImg zrange(primitives._width); + 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_size(primitives.size(),4096)) + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + switch (primitive.size()) { + case 1 : { // Point + CImg<_to> _opacity; + __draw_object3d(opacities,l,_opacity); + if (l<=colors.width() && (colors[l].size()!=_spectrum || _opacity)) is_forward = false; + const unsigned int i0 = (unsigned int)primitive(0); + const tpfloat z0 = Z + vertices(i0,2); + if (z0>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = z0; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + Xc = 0.5f*((float)vertices(i0,0) + (float)vertices(i1,0)), + Yc = 0.5f*((float)vertices(i0,1) + (float)vertices(i1,1)), + Zc = 0.5f*((float)vertices(i0,2) + (float)vertices(i1,2)), + _zc = Z + Zc, + zc = _zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(i1,0) - vertices(i0,0), + vertices(i1,1) - vertices(i0,1), + vertices(i1,2) - vertices(i0,2))*(absfocale?absfocale/zc:1), + xm = xc - radius, + ym = yc - radius, + xM = xc + radius, + yM = yc + radius; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && _zc>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = _zc; + } + is_forward = false; + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2); + tpfloat xm, xM, ym, yM; + if (x0=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1)/2; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (y0yM) yM = y2; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) { + const tpfloat d = (x1-x0)*(y2-y0) - (x2-x0)*(y1-y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2)/3; + } + } + } break; + case 4 : 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); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2), + x3 = projections(i3,0), y3 = projections(i3,1), z3 = Z + vertices(i3,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (x3xM) xM = x3; + if (y0yM) yM = y2; + if (y3yM) yM = y3; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin && z3>zmin) { + const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2 + z3)/4; + } + } + } break; + default : + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Invalid primitive[%u] with size %u " + "(should have size 1,2,3,4,5,6,9 or 12).", + cimg_instance, + l,primitive.size()); + } + } + + // Force transparent primitives to be drawn last when zbuffer is activated + // (and if object contains no spheres or sprites). + if (is_forward) + cimglist_for(primitives,l) + if (___draw_object3d(opacities,l)!=1) zrange(l) = 2*zmax - zrange(l); + + // Sort only visibles primitives. + unsigned int *p_visibles = visibles._data; + tpfloat *p_zrange = zrange._data; + const tpfloat *ptrz = p_zrange; + cimg_for(visibles,ptr,unsigned int) { + if (*ptr!=~0U) { *(p_visibles++) = *ptr; *(p_zrange++) = *ptrz; } + ++ptrz; + } + const unsigned int nb_visibles = (unsigned int)(p_zrange - zrange._data); + if (!nb_visibles) { + if (render_type==5) cimg::mutex(10,0); + return *this; + } + CImg permutations; + CImg(zrange._data,nb_visibles,1,1,1,true).sort(permutations,is_forward); + + // Compute light properties + CImg lightprops; + switch (render_type) { + case 3 : { // Flat Shading + lightprops.assign(nb_visibles); + 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(); + if (psize==3 || psize==4 || psize==9 || psize==12) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nx = dy1*dz2 - dz1*dy2, + ny = dz1*dx2 - dx1*dz2, + nz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + (x0 + x1 + x2)/3 - lightx, + ly = Y + (y0 + y1 + y2)/3 - lighty, + lz = Z + (z0 + z1 + z2)/3 - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max(cimg::abs(-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } else lightprops[l] = 1; + } + } break; + + case 4 : // Gouraud Shading + case 5 : { // Phong-Shading + CImg vertices_normals(vertices._width,6,1,1,0); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + for (int l = 0; l<(int)nb_visibles; ++l) { + const CImg& primitive = primitives[visibles(l)]; + const unsigned int psize = (unsigned int)primitive.size(); + const bool + triangle_flag = (psize==3) || (psize==9), + quadrangle_flag = (psize==4) || (psize==12); + if (triangle_flag || quadrangle_flag) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2), + i3 = quadrangle_flag?(unsigned int)primitive(3):0; + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nnx = dy1*dz2 - dz1*dy2, + nny = dz1*dx2 - dx1*dz2, + nnz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nnx,nny,nnz), + nx = nnx/norm, + ny = nny/norm, + nz = nnz/norm; + unsigned int ix = 0, iy = 1, iz = 2; + if (is_double_sided && nz>0) { ix = 3; iy = 4; iz = 5; } + vertices_normals(i0,ix)+=nx; vertices_normals(i0,iy)+=ny; vertices_normals(i0,iz)+=nz; + vertices_normals(i1,ix)+=nx; vertices_normals(i1,iy)+=ny; vertices_normals(i1,iz)+=nz; + vertices_normals(i2,ix)+=nx; vertices_normals(i2,iy)+=ny; vertices_normals(i2,iz)+=nz; + if (quadrangle_flag) { + vertices_normals(i3,ix)+=nx; vertices_normals(i3,iy)+=ny; vertices_normals(i3,iz)+=nz; + } + } + } + + if (is_double_sided) cimg_forX(vertices_normals,p) { + const float + nx0 = vertices_normals(p,0), ny0 = vertices_normals(p,1), nz0 = vertices_normals(p,2), + nx1 = vertices_normals(p,3), ny1 = vertices_normals(p,4), nz1 = vertices_normals(p,5), + n0 = nx0*nx0 + ny0*ny0 + nz0*nz0, n1 = nx1*nx1 + ny1*ny1 + nz1*nz1; + if (n1>n0) { + vertices_normals(p,0) = -nx1; + vertices_normals(p,1) = -ny1; + vertices_normals(p,2) = -nz1; + } + } + + if (render_type==4) { + lightprops.assign(vertices._width); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + vertices(l,0) - lightx, + ly = Y + vertices(l,1) - lighty, + lz = Z + vertices(l,2) - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max((-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } + } else { + const unsigned int + lw2 = light_texture._width/2 - 1, + lh2 = light_texture._height/2 - 1; + lightprops.assign(vertices._width,2); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + nnx = nx/norm, + nny = ny/norm; + lightprops(l,0) = lw2*(1 + nnx); + lightprops(l,1) = lh2*(1 + nny); + } + } + } break; + } + + // Draw visible primitives + const CImg default_color(1,_spectrum,1,1,(tc)200); + CImg<_to> _opacity; + + for (unsigned int l = 0; l& primitive = primitives[n_primitive]; + const CImg + &__color = n_primitive(), + _color = (__color && __color.size()!=_spectrum && __color._spectrum<_spectrum)? + __color.get_resize(-100,-100,-100,_spectrum,0):CImg(), + &color = _color?_color:(__color?__color:default_color); + const tc *const pcolor = color._data; + float opacity = __draw_object3d(opacities,n_primitive,_opacity); + if (_opacity.is_empty()) opacity*=g_opacity; + else if (!_opacity.is_shared()) _opacity*=g_opacity; + +#ifdef cimg_use_board + LibBoard::Board &board = *(LibBoard::Board*)pboard; +#endif + + switch (primitive.size()) { + case 1 : { // Colored point or sprite + const unsigned int n0 = (unsigned int)primitive[0]; + const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1); + + if (_opacity.is_empty()) { // Scalar opacity + + if (color.size()==_spectrum) { // Colored point + draw_point(x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height()-(float)y0); + } +#endif + } else { // Sprite + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(color._width*factor), + _sh = (unsigned int)(color._height*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + 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); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } else { // Opacity mask + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(std::max(color._width,_opacity._width)*factor), + _sh = (unsigned int)(std::max(color._height,_opacity._height)*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg(), + &sprite = _sprite?_sprite:color; + const CImg<_to> + _nopacity = (sw!=_opacity._width || sh!=_opacity._height)? + _opacity.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<_to>(), + &nopacity = _nopacity?_nopacity:_opacity; + draw_image(nx0,ny0,sprite,nopacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } break; + case 2 : { // Colored line + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity); + else draw_line(x0,y0,x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 5 : { // Colored sphere + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + is_wireframe = (unsigned int)primitive[2]; + const float + Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)), + Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)), + Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)), + zc = Z + Zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(n1,0) - vertices(n0,0), + vertices(n1,1) - vertices(n0,1), + vertices(n1,2) - vertices(n0,2))*(absfocale?absfocale/zc:1); + switch (render_type) { + case 0 : + draw_point((int)xc,(int)yc,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot(xc,height() - yc); + } +#endif + break; + case 1 : + draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } +#endif + break; + default : + if (is_wireframe) draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); + else draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + if (!is_wireframe) board.fillCircle(xc,height() - yc,radius); + else { + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } + } +#endif + break; + } + } break; + case 6 : { // Textured line + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for line primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + tx0 = (int)primitive[2], ty0 = (int)primitive[3], + tx1 = (int)primitive[4], ty1 = (int)primitive[5], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity); + else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 3 : { // Colored triangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity).draw_point(x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x0,y0,x2,y2,pcolor,opacity). + draw_line(x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)); + else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + } break; + } + } break; + case 4 : { // Colored quadrangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1), + x3 = (int)projections(n3,0), y3 = (int)projections(n3,1), + xc = (x0 + x1 + x2 + x3)/4, yc = (y0 + y1 + y2 + y3)/4; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity). + draw_point(x2,y2,pcolor,opacity).draw_point(x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opacity).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x1,y1,x2,y2,pcolor,opacity). + draw_line(x2,y2,x3,y3,pcolor,opacity).draw_line(x3,y3,x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity); + else + draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity,lightprops(l)); + else + _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)). + _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp),(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop2)/4; + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,xc,yc,zc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,light_texture,lx0,ly0,lx1,ly1,lxc,lyc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + case 9 : { // Textured triangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for triangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + tx0 = (int)primitive[3], ty0 = (int)primitive[4], + tx1 = (int)primitive[5], ty1 = (int)primitive[6], + tx2 = (int)primitive[7], ty2 = (int)primitive[8], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + break; + } + } break; + case 12 : { // Textured quadrangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for quadrangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + tx0 = (int)primitive[4], ty0 = (int)primitive[5], + tx1 = (int)primitive[6], ty1 = (int)primitive[7], + tx2 = (int)primitive[8], ty2 = (int)primitive[9], + tx3 = (int)primitive[10], ty3 = (int)primitive[11], + 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; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity). + draw_point(x3,y3,color.get_vector_at(tx3<=0?0:tx3>=color.width()?color.width() - 1:tx3, + ty3<=0?0:ty3>=color.height()?color.height() - 1:ty3)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop3)/4; + 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); + 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); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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,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); + 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); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + } + } + if (render_type==5) cimg::mutex(10,0); + return *this; + } + + //@} + //--------------------------- + // + //! \name Data Input + //@{ + //--------------------------- + + //! Launch simple interface to select a shape from an image. + /** + \param disp Display window to use. + \param feature_type Type of feature to select. Can be { 0=point | 1=line | 2=rectangle | 3=ellipse }. + \param XYZ Pointer to 3 values X,Y,Z which tells about the projection point coordinates, for volumetric images. + \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 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); + } + + //! 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 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); + } + + //! 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 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); + } + + //! 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 bool exit_on_anykey=false, + const bool is_deep_selection_default=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,XYZ,0,0,0,exit_on_anykey,true,false,is_deep_selection_default); + } + + CImg _select(CImgDisplay &disp, const char *const title, + const unsigned int feature_type, unsigned int *const XYZ, + const int origX, const int origY, const int origZ, + const bool exit_on_anykey, + const bool reset_view3d, + const bool force_display_z_coord, + const bool is_deep_selection_default) const { + if (is_empty()) return CImg(1,feature_type==0?3:6,1,1,-1); + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + } else { + if (title) disp.set_title("%s",title); + disp.move_inside_screen(); + } + + CImg thumb; + if (width()>disp.screen_width() || height()>disp.screen_height()) + get_resize(cimg_fitscreen(width(),height(),depth()),depth(),-100).move_to(thumb); + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0).set_wheel().show_mouse(); + + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + int area = 0, 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), + 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; + + bool is_deep_selection = is_deep_selection_default, + shape_selected = false, text_down = false, visible_cursor = true; + static CImg pose3d; + static bool is_view3d = false, is_axes = true; + if (reset_view3d) { pose3d.assign(); is_view3d = false; } + CImg points3d, opacities3d, sel_opacities3d; + CImgList primitives3d, sel_primitives3d; + CImgList colors3d, sel_colors3d; + CImg visu, visu0, view3d; + CImg text(1024); *text = 0; + + while (!key && !disp.is_closed() && !shape_selected) { + + // Handle mouse motion and selection + int + mx = disp.mouse_x(), + my = disp.mouse_y(); + + const float + mX = mx<0?-1.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 && mX=0 && mX=height()) { area = 2; X = mX; Z = mY - _height; Y = (float)(phase?Y1:Y0); } + if (mY>=0 && mX>=width() && mY=width() && mY>=height()) area = 4; + if (disp.button()) { if (!area_clicked) area_clicked = area; } else area_clicked = 0; + + CImg filename(32); + + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(1); key = 0; } break; + case cimg::keyPAGEDOWN : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(-1); key = 0; } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_axes = !is_axes; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_view3d = !is_view3d; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot...",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + } + + switch (area) { + + case 0 : // When mouse is out of image range + mx = my = -1; X = Y = Z = -1; + break; + + case 1 : case 2 : case 3 : { // When mouse is over the XY,XZ or YZ projections + const unsigned int but = disp.button(); + const bool b1 = (bool)(but&1), b2 = (bool)(but&2), b3 = (bool)(but&4); + + if (b1 && phase==1 && area_clicked==area) { // When selection has been started (1st step) + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } + if (!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 (phase) { + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } else { + if (_depth>1 && (X0!=(int)X || Y0!=(int)Y || Z0!=(int)Z)) visu0.assign(); + X0 = (int)X; Y0 = (int)Y; Z0 = (int)Z; + } + } + if (b3) { // Reset selection + 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 (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() && + !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT()) { + switch (area) { + case 1 : + if (phase) Z = (float)(Z1+=disp.wheel()); else Z = (float)(Z0+=disp.wheel()); + visu0.assign(); break; + case 2 : + if (phase) Y = (float)(Y1+=disp.wheel()); else Y = (float)(Y0+=disp.wheel()); + visu0.assign(); break; + case 3 : + if (phase) X = (float)(X1+=disp.wheel()); else X = (float)(X0+=disp.wheel()); + visu0.assign(); break; + } + disp.set_wheel(); + } else key = ~0U; + } + + if ((phase==0 && b1) || + (phase==1 && !b1) || + (phase==2 && b1)) switch (phase) { // Detect change of phase + case 0 : + if (area==area_clicked) { + X0 = X1 = (int)X; Y0 = Y1 = (int)Y; Z0 = Z1 = (int)Z; area_started = area; ++phase; + } break; + case 1 : + if (area==area_started) { + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++phase; + if (_depth>1) { + if (disp.is_keyCTRLLEFT()) is_deep_selection = !is_deep_selection_default; + if (is_deep_selection) ++phase; + } + } else if (!b1) { X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = 0; visu0.assign(); } + break; + case 2 : ++phase; break; + } + } break; + + case 4 : // When mouse is over the 3D view + if (is_view3d && points3d) { + X3d = mx - width()*disp.width()/(width() + (depth()>1?depth():0)); + Y3d = my - height()*disp.height()/(height() + (depth()>1?depth():0)); + if (oX3d<0) { oX3d = X3d; oY3d = Y3d; } + // Left + right buttons: reset. + if ((disp.button()&3)==3) { pose3d.assign(); view3d.assign(); oX3d = oY3d = X3d = Y3d = -1; } + else if (disp.button()&1 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Left button: rotate + const float + R = 0.45f*std::min(view3d._width,view3d._height), + R2 = R*R, + u0 = (float)(oX3d - view3d.width()/2), + v0 = (float)(oY3d - view3d.height()/2), + u1 = (float)(X3d - view3d.width()/2), + v1 = (float)(Y3d - view3d.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + pose3d.draw_image(CImg::rotation_matrix(u,v,w,-alpha)*pose3d.get_crop(0,0,2,2)); + view3d.assign(); + } else if (disp.button()&2 && pose3d && oY3d!=Y3d) { // Right button: zoom + pose3d(3,2)+=(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 + pose3d(3,0)-=oX3d - X3d; pose3d(3,1)-=oY3d - Y3d; view3d.assign(); + } + oX3d = X3d; oY3d = Y3d; + } + mx = my = -1; X = Y = Z = -1; + break; + } + + if (phase) { + if (!feature_type) shape_selected = phase?true:false; + else { + if (_depth>1) shape_selected = (phase==3)?true:false; + else shape_selected = (phase==2)?true:false; + } + } + + if (X0<0) X0 = 0; + if (X0>=width()) X0 = width() - 1; + if (Y0<0) Y0 = 0; + if (Y0>=height()) Y0 = height() - 1; + if (Z0<0) Z0 = 0; + if (Z0>=depth()) Z0 = depth() - 1; + if (X1<1) X1 = 0; + if (X1>=width()) X1 = width() - 1; + if (Y1<0) Y1 = 0; + if (Y1>=height()) Y1 = height() - 1; + if (Z1<0) Z1 = 0; + if (Z1>=depth()) Z1 = depth() - 1; + + // Draw visualization image on the display + if (mx!=omx || my!=omy || !visu0 || (_depth>1 && !view3d)) { + + if (!visu0) { // Create image of projected planes + if (thumb) thumb._get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + else _get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + visu0.resize(disp); + view3d.assign(); + points3d.assign(); + } + + if (is_view3d && _depth>1 && !view3d) { // Create 3D view for volumetric images + const unsigned int + _x3d = (unsigned int)cimg::round((float)_width*visu0._width/(_width + _depth),1,1), + _y3d = (unsigned int)cimg::round((float)_height*visu0._height/(_height + _depth),1,1), + x3d = _x3d>=visu0._width?visu0._width - 1:_x3d, + y3d = _y3d>=visu0._height?visu0._height - 1:_y3d; + CImg(1,2,1,1,64,128).resize(visu0._width - x3d,visu0._height - y3d,1,visu0._spectrum,3). + move_to(view3d); + if (!points3d) { + get_projections3d(primitives3d,colors3d,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0,true).move_to(points3d); + points3d.append(CImg(8,3,1,1, + 0,_width - 1,_width - 1,0,0,_width - 1,_width - 1,0, + 0,0,_height - 1,_height - 1,0,0,_height - 1,_height - 1, + 0,0,0,0,_depth - 1,_depth - 1,_depth - 1,_depth - 1),'x'); + CImg::vector(12,13).move_to(primitives3d); CImg::vector(13,14).move_to(primitives3d); + CImg::vector(14,15).move_to(primitives3d); CImg::vector(15,12).move_to(primitives3d); + CImg::vector(16,17).move_to(primitives3d); CImg::vector(17,18).move_to(primitives3d); + CImg::vector(18,19).move_to(primitives3d); CImg::vector(19,16).move_to(primitives3d); + CImg::vector(12,16).move_to(primitives3d); CImg::vector(13,17).move_to(primitives3d); + CImg::vector(14,18).move_to(primitives3d); CImg::vector(15,19).move_to(primitives3d); + colors3d.insert(12,CImg::vector(255,255,255)); + opacities3d.assign(primitives3d.width(),1,1,1,0.5f); + if (!phase) { + opacities3d[0] = opacities3d[1] = opacities3d[2] = 0.8f; + sel_primitives3d.assign(); + sel_colors3d.assign(); + sel_opacities3d.assign(); + } else { + if (feature_type==2) { + points3d.append(CImg(8,3,1,1, + X0,X1,X1,X0,X0,X1,X1,X0, + Y0,Y0,Y1,Y1,Y0,Y0,Y1,Y1, + Z0,Z0,Z0,Z0,Z1,Z1,Z1,Z1),'x'); + sel_primitives3d.assign(); + CImg::vector(20,21).move_to(sel_primitives3d); + CImg::vector(21,22).move_to(sel_primitives3d); + CImg::vector(22,23).move_to(sel_primitives3d); + CImg::vector(23,20).move_to(sel_primitives3d); + CImg::vector(24,25).move_to(sel_primitives3d); + CImg::vector(25,26).move_to(sel_primitives3d); + CImg::vector(26,27).move_to(sel_primitives3d); + CImg::vector(27,24).move_to(sel_primitives3d); + CImg::vector(20,24).move_to(sel_primitives3d); + CImg::vector(21,25).move_to(sel_primitives3d); + CImg::vector(22,26).move_to(sel_primitives3d); + CImg::vector(23,27).move_to(sel_primitives3d); + } else { + points3d.append(CImg(2,3,1,1, + X0,X1, + Y0,Y1, + Z0,Z1),'x'); + sel_primitives3d.assign(CImg::vector(20,21)); + } + sel_colors3d.assign(sel_primitives3d._width,CImg::vector(255,255,255)); + sel_opacities3d.assign(sel_primitives3d._width,1,1,1,0.8f); + } + points3d.shift_object3d(-0.5f*(_width - 1),-0.5f*(_height - 1),-0.5f*(_depth - 1)).resize_object3d(); + points3d*=0.75f*std::min(view3d._width,view3d._height); + } + + if (!pose3d) CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose3d); + CImg zbuffer3d(view3d._width,view3d._height,1,1,0); + const CImg rotated_points3d = pose3d.get_crop(0,0,2,2)*points3d; + if (sel_primitives3d) + view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width, + pose3d(3,1) + 0.5f*view3d._height, + pose3d(3,2), + rotated_points3d,sel_primitives3d,sel_colors3d,sel_opacities3d, + 2,true,500,0,0,0,0,0,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,1,zbuffer3d); + visu0.draw_image(x3d,y3d,view3d); + } + visu = visu0; + + if (X<0 || Y<0 || Z<0) { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + else { + if (is_axes) { if (visible_cursor) { disp.hide_mouse(); visible_cursor = false; }} + else { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + const int d = (depth()>1)?depth():0; + int _vX = (int)X, _vY = (int)Y, _vZ = (int)Z; + if (phase>=2) { _vX = X1; _vY = Y1; _vZ = Z1; } + int + w = disp.width(), W = width() + d, + h = disp.height(), H = height() + d, + _xp = (int)(_vX*(float)w/W), xp = _xp + ((int)(_xp*(float)W/w)!=_vX), + _yp = (int)(_vY*(float)h/H), yp = _yp + ((int)(_yp*(float)H/h)!=_vY), + _xn = (int)((_vX + 1.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.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, + zyc = (zyp + zyn)/2, + xf = (int)(X*w/W), + yf = (int)(Y*h/H), + zxf = (int)((Z + width())*w/W), + zyf = (int)((Z + height())*h/H); + + if (is_axes) { // Draw axes + visu.draw_line(0,yf,visu.width() - 1,yf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,yf,visu.width() - 1,yf,background_color,0.7f,0x00FF00FF). + draw_line(xf,0,xf,visu.height() - 1,foreground_color,0.7f,0xFF00FF00). + draw_line(xf,0,xf,visu.height() - 1,background_color,0.7f,0x00FF00FF); + if (_depth>1) + visu.draw_line(zxf,0,zxf,yM,foreground_color,0.7f,0xFF00FF00). + draw_line(zxf,0,zxf,yM,background_color,0.7f,0x00FF00FF). + draw_line(0,zyf,xM,zyf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,zyf,xM,zyf,background_color,0.7f,0x00FF00FF); + } + + // Draw box cursor. + if (xn - xp>=4 && yn - yp>=4) + visu.draw_rectangle(xp,yp,xn,yn,foreground_color,0.2f). + draw_rectangle(xp,yp,xn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,yp,xn,yn,background_color,1,0x55555555); + if (_depth>1) { + if (yn - yp>=4 && zxn - zxp>=4) + visu.draw_rectangle(zxp,yp,zxn,yn,background_color,0.2f). + draw_rectangle(zxp,yp,zxn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(zxp,yp,zxn,yn,background_color,1,0x55555555); + if (xn - xp>=4 && zyn - zyp>=4) + visu.draw_rectangle(xp,zyp,xn,zyn,background_color,0.2f). + draw_rectangle(xp,zyp,xn,zyn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,zyp,xn,zyn,background_color,1,0x55555555); + } + + // Draw selection. + if (phase && (phase!=1 || area_started==area)) { + 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.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.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); + if (d) { + visu.draw_arrow(zxc0,yc0,zxc,yc,background_color,0.9f,30,5,0x55555555). + draw_arrow(zxc0,yc0,zxc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA). + draw_arrow(xc0,zyc0,xc,zyc,background_color,0.9f,30,5,0x55555555). + draw_arrow(xc0,zyc0,xc,zyc,foreground_color,0.9f,30,5,0xAAAAAAAA); + } + } break; + case 2 : { + visu.draw_rectangle(X0=0 && my<13) text_down = true; else if (my>=visu.height() - 13) text_down = false; + if (!feature_type || !phase) { + if (X>=0 && Y>=0 && Z>=0 && X1 || force_display_z_coord) + cimg_snprintf(text,text._width," Point (%d,%d,%d) = [ ",origX + (int)X,origY + (int)Y,origZ + (int)Z); + else cimg_snprintf(text,text._width," Point (%d,%d) = [ ",origX + (int)X,origY + (int)Y); + CImg values = get_vector_at((int)X,(int)Y,(int)Z); + const bool is_large_spectrum = values._height>16; + if (is_large_spectrum) + values.draw_image(0,8,values.get_rows(values._height - 8,values._height - 1)).resize(1,16,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," (...)"); + ctext += std::strlen(ctext); + } + *(ctext++) = ' '; *ctext = 0; + } + std::strcpy(text._data + std::strlen(text),"] "); + } + } else switch (feature_type) { + case 1 : { + const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), + length = cimg::round(cimg::hypot(dX,dY,dZ),0.1); + 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 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 ", + origX + (X01 || force_display_z_coord) + cimg_snprintf(text,text._width," Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d) ", + origX + X0,origY + Y0,origZ + Z0,origX + X1,origY + Y1,origZ + Z1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1),1 + cimg::abs(Z0 - Z1)); + else cimg_snprintf(text,text._width," Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d) ", + origX + X0,origY + Y0,origX + X1,origY + Y1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1)); + } + if (phase || (mx>=0 && my>=0)) visu.__draw_text("%s",text_down,text._data); + } + + disp.display(visu); + } + if (!shape_selected) disp.wait(); + if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); } + omx = mx; omy = my; + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + + // Return result. + CImg res(1,feature_type==0?3:6,1,1,-1); + if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; } + if (shape_selected) { + if (feature_type==2) { + if (is_deep_selection) switch (area_started) { + case 1 : Z0 = 0; Z1 = _depth - 1; break; + case 2 : Y0 = 0; Y1 = _height - 1; break; + case 3 : X0 = 0; X1 = _width - 1; break; + } + if (X0>X1) cimg::swap(X0,X1); + if (Y0>Y1) cimg::swap(Y0,Y1); + if (Z0>Z1) cimg::swap(Z0,Z1); + } + if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1; + switch (feature_type) { + case 1 : case 2 : res[0] = X0; res[1] = Y0; res[2] = Z0; res[3] = X1; res[4] = Y1; res[5] = Z1; break; + case 3 : + res[3] = cimg::abs(X1 - X0); res[4] = cimg::abs(Y1 - Y0); res[5] = cimg::abs(Z1 - Z0); + res[0] = X0; res[1] = Y0; res[2] = Z0; + break; + default : res[0] = X0; res[1] = Y0; res[2] = Z0; + } + } + if (!exit_on_anykey || !(disp.button()&4)) disp.set_button(); + if (!visible_cursor) disp.show_mouse(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + if (key!=~0U) disp.set_key(key); + return res; + } + + // Return a visualizable uchar8 image for display routines. + CImg _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; + if (_depth>1) { + const int mdisp = std::min(disp.screen_width(),disp.screen_height()); + if (depth()>mdisp) { + crop.get_resize(-100,-100,mdisp,-100,0).move_to(img2d); + img2d.projections2d(x,y,z*img2d._depth/_depth); + } else crop.get_projections2d(x,y,z).move_to(img2d); + } else CImg(crop,false).move_to(img2d); + + // Check for inf and NaN values. + if (cimg::type::is_float() && normalization) { + bool is_inf = false, is_nan = false; + cimg_for(img2d,ptr,Tuchar) + if (cimg::type::is_inf(*ptr)) { is_inf = true; break; } + else if (cimg::type::is_nan(*ptr)) { is_nan = true; break; } + if (is_inf || is_nan) { + 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 + 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); + if (is_nan) + cimg_for(img2d,ptr,Tuchar) + 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 + } + } + + switch (normalization) { + case 1 : img2d.normalize((ucharT)0,(ucharT)255); break; + case 2 : { + const float m = disp._min, M = disp._max; + (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.f/(M - m>0?M - m:1); + } break; + } + if (img2d.spectrum()==2) img2d.channels(0,2); + return img2d; + } + + //! Select sub-graph in a graph. + CImg get_select_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "select_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title("CImg<%s>",pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth; + const unsigned int old_normalization = disp.normalization(); + disp.show().set_button().set_wheel()._normalization = 0; + + double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax; + if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; } + if (nymin==nymax) { --nymin; ++nymax; } + if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.; } + + 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 }; + + 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) { + ulongT 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; + CImg message(1024); + CImg_3x3(I,unsigned char); + + for (bool selected = false; !selected && !disp.is_closed() && !okey && !disp.wheel(); ) { + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + const unsigned int key = disp.key(), button = disp.button(); + + // Generate graph representation. + if (!visu0) { + visu0.assign(disp.width(),disp.height(),1,3,220); + const int gdimx = disp.width() - 32, gdimy = disp.height() - 32; + if (gdimx>0 && gdimy>0) { + graph.assign(gdimx,gdimy,1,3,255); + if (siz<32) { + if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0, + false,true,black,0.2f,0x33333333,0x33333333); + } else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333); + cimg_forC(*this,c) + graph.draw_graph(get_shared_channel(c),&colormap(0,c),(plot_type!=3 || _spectrum==1)?1:0.6f, + plot_type,vertex_type,nymax,nymin); + + axes.assign(gdimx,gdimy,1,1,0); + const float + dx = (float)cimg::abs(nxmax - nxmin), dy = (float)cimg::abs(nymax - nymin), + px = (float)std::pow(10.,(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); + + const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0); + axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero); + if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero); + if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero); + if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero); + if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero); + + cimg_for3x3(axes,x,y,0,0,I,unsigned char) + if (Icc) { + if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0; + else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3); + } + else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) + cimg_forC(graph,c) graph(x,y,c) = (unsigned char)((graph(x,y,c) + 511)/3); + + visu0.draw_image(16,16,graph); + visu0.draw_line(15,15,16 + gdimx,15,gray2).draw_line(16 + gdimx,15,16 + gdimx,16 + gdimy,gray2). + draw_line(16 + gdimx,16 + gdimy,15,16 + gdimy,white).draw_line(15,16 + gdimy,15,15,white); + } else graph.assign(); + text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1,13).resize(-100,-100,1,3); + visu0.draw_image((visu0.width() - text.width())/2,visu0.height() - 14,~text); + text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3); + visu0.draw_image(1,(visu0.height() - text.height())/2,~text); + visu.assign(); + } + + // Generate and display current view. + if (!visu) { + visu.assign(visu0); + if (graph && x0>=0 && x1>=0) { + const int + nx0 = x0<=x1?x0:x1, + nx1 = x0<=x1?x1:x0, + ny0 = y0<=y1?y0:y1, + ny1 = y0<=y1?y1:y0, + sx0 = (int)(16 + nx0*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sx1 = (int)(15 + (nx1 + 1)*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sy0 = 16 + ny0, + sy1 = 16 + ny1; + if (y0>=0 && y1>=0) + visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU); + else visu.draw_rectangle(sx0,0,sx1,visu.height() - 17,gray,0.5f). + draw_line(sx0,16,sx0,visu.height() - 17,black,0.5f,0xCCCCCCCCU). + draw_line(sx1,16,sx1,visu.height() - 17,black,0.5f,0xCCCCCCCCU); + } + if (mouse_x>=16 && mouse_y>=16 && mouse_x=7) + cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx, + (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2), + (double)(*this)(x,0,0,_spectrum - 4),(double)(*this)(x,0,0,_spectrum - 3), + (double)(*this)(x,0,0,_spectrum - 1)); + else { + cimg_snprintf(message,message._width,"Value[%u:%g] = ( ",x,cx); + cimg_forC(*this,c) cimg_sprintf(message._data + std::strlen(message),"%g ",(double)(*this)(x,0,0,c)); + cimg_sprintf(message._data + std::strlen(message),")"); + } + if (x0>=0 && x1>=0) { + const unsigned int + nx0 = (unsigned int)(x0<=x1?x0:x1), + nx1 = (unsigned int)(x0<=x1?x1:x0), + ny0 = (unsigned int)(y0<=y1?y0:y1), + ny1 = (unsigned int)(y0<=y1?y1:y0); + const double + cx0 = nxmin + nx0*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cx1 = nxmin + (nx1 + one)*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cy0 = nymax - ny0*(nymax - nymin)/(visu._height - 32), + cy1 = nymax - ny1*(nymax - nymin)/(visu._height - 32); + if (y0>=0 && y1>=0) + cimg_sprintf(message._data + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )", + x0,cx0,cy0,x1 + one,cx1,cy1); + else + cimg_sprintf(message._data + std::strlen(message)," - Range [ %u:%g - %u:%g ]", + x0,cx0,x1 + one,cx1); + } + text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3); + visu.draw_image((visu.width() - text.width())/2,1,~text); + } + visu.display(disp); + } + + // Test keys. + CImg filename(32); + switch (okey = key) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(CImgDisplay::screen_width()/2, + CImgDisplay::screen_height()/2,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving snapshot... ",false).display(disp); + screen.save(filename); + (+screen).__draw_text(" Snapshot '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving instance... ",false).display(disp); + save(filename); + (+screen).__draw_text(" Instance '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + } + + // Handle mouse motion and mouse buttons. + if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) { + visu.assign(); + if (disp.mouse_x()>=0 && disp.mouse_y()>=0) { + const int + mx = (mouse_x - 16)*(int)(siz - one)/(disp.width() - 32), + cx = cimg::cut(mx,0,(int)(siz - 1 - one)), + my = mouse_y - 16, + cy = cimg::cut(my,0,disp.height() - 32); + if (button&1) { + if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; } + } + else if (button&2) { + if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; } + } + else if (obutton) { x1 = x1>=0?cx:-1; y1 = y1>=0?cy:-1; selected = true; } + } else if (!button && obutton) selected = true; + obutton = button; omouse_x = mouse_x; omouse_y = mouse_y; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (visu && visu0) disp.wait(); + if (!exit_on_anykey && okey && okey!=cimg::keyESC && + (okey!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + okey = 0; + } + } + + disp._normalization = old_normalization; + if (x1>=0 && x1(4,1,1,1,x0,y0,x1>=0?x1 + (int)one:-1,y1); + } + + //! Load image from a file. + /** + \param filename Filename, as a C-string. + \note The extension of \c filename defines the file format. If no filename + extension is provided, CImg::get_load() will try to load the file as a .cimg or .cimgz file. + **/ + CImg& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load(): Specified filename is (null).", + cimg_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimg_load_plugin + cimg_load_plugin(filename); +#endif +#ifdef cimg_load_plugin1 + cimg_load_plugin1(filename); +#endif +#ifdef cimg_load_plugin2 + cimg_load_plugin2(filename); +#endif +#ifdef cimg_load_plugin3 + cimg_load_plugin3(filename); +#endif +#ifdef cimg_load_plugin4 + cimg_load_plugin4(filename); +#endif +#ifdef cimg_load_plugin5 + cimg_load_plugin5(filename); +#endif +#ifdef cimg_load_plugin6 + cimg_load_plugin6(filename); +#endif +#ifdef cimg_load_plugin7 + cimg_load_plugin7(filename); +#endif +#ifdef cimg_load_plugin8 + cimg_load_plugin8(filename); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) load_dlm(filename); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) load_jpeg(filename); + else if (!cimg::strcasecmp(ext,"png")) load_png(filename); + else if (!cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"pnm") || + !cimg::strcasecmp(ext,"pbm") || + !cimg::strcasecmp(ext,"pnk")) load_pnm(filename); + else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename); + else if (!cimg::strcasecmp(ext,"cr2") || + !cimg::strcasecmp(ext,"crw") || + !cimg::strcasecmp(ext,"dcr") || + !cimg::strcasecmp(ext,"mrw") || + !cimg::strcasecmp(ext,"nef") || + !cimg::strcasecmp(ext,"orf") || + !cimg::strcasecmp(ext,"pix") || + !cimg::strcasecmp(ext,"ptx") || + !cimg::strcasecmp(ext,"raf") || + !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"dcm") || + !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) load_analyze(filename); + else if (!cimg::strcasecmp(ext,"par") || + !cimg::strcasecmp(ext,"rec")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"mnc")) load_minc2(filename); + else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) return load_cimg(filename); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + + // Image sequences + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded) { + std::FILE *file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to open file '%s'.", + cimg_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + cimg::fclose(file); + is_loaded = true; + try { + if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename); + else if (!cimg::strcasecmp(f_type,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(f_type,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(f_type,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(f_type,"jpg")) load_jpeg(filename); + else if (!cimg::strcasecmp(f_type,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(f_type,"png")) load_png(filename); + else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename); + else if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file with other means. + if (!is_loaded) { + try { + load_other(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image from a file \newinstance. + static CImg get_load(const char *const filename) { + return CImg().load(filename); + } + + //! Load image from an Ascii file. + /** + \param filename Filename, as a C -string. + **/ + CImg& load_ascii(const char *const filename) { + return _load_ascii(0,filename); + } + + //! Load image from an Ascii file \inplace. + static CImg get_load_ascii(const char *const filename) { + return CImg().load_ascii(filename); + } + + //! Load image from an Ascii file \overloading. + CImg& load_ascii(std::FILE *const file) { + return _load_ascii(file,0); + } + + //! Loadimage from an Ascii file \newinstance. + static CImg get_load_ascii(std::FILE *const file) { + return CImg().load_ascii(file); + } + + CImg& _load_ascii(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_ascii(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg line(256); *line = 0; + int err = std::fscanf(nfile,"%255[^\n]",line._data); + unsigned int dx = 0, dy = 1, dz = 1, dc = 1; + cimg_sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc); + err = std::fscanf(nfile,"%*[^0-9.eEinfa+-]"); + if (!dx || !dy || !dz || !dc) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_ascii(): Invalid Ascii header in file '%s', image dimensions are set " + "to (%u,%u,%u,%u).", + cimg_instance, + filename?filename:"(FILE*)",dx,dy,dz,dc); + } + assign(dx,dy,dz,dc); + const ulongT siz = size(); + ulongT off = 0; + double val; + T *ptr = _data; + for (err = 1, off = 0; off& load_dlm(const char *const filename) { + return _load_dlm(0,filename); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(const char *const filename) { + return CImg().load_dlm(filename); + } + + //! Load image from a DLM file \overloading. + CImg& load_dlm(std::FILE *const file) { + return _load_dlm(file,0); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(std::FILE *const file) { + return CImg().load_dlm(file); + } + + CImg& _load_dlm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_dlm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + CImg delimiter(256), tmp(256); *delimiter = *tmp = 0; + unsigned int cdx = 0, dx = 0, dy = 0; + int err = 0; + double val; + assign(256,256,1,1,(T)0); + while ((err = std::fscanf(nfile,"%lf%255[^0-9eEinfa.+-]",&val,delimiter._data))>0) { + if (err>0) (*this)(cdx++,dy) = (T)val; + if (cdx>=_width) resize(3*_width/2,_height,1,1,0); + char c = 0; + if (!cimg_sscanf(delimiter,"%255[^\n]%c",tmp._data,&c) || c=='\n') { + dx = std::max(cdx,dx); + if (++dy>=_height) resize(_width,3*_height/2,1,1,0); + cdx = 0; + } + } + if (cdx && err==1) { dx = cdx; ++dy; } + if (!dx || !dy) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_dlm(): Invalid DLM file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + resize(dx,dy,1,1,0); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a BMP file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_bmp(const char *const filename) { + return _load_bmp(0,filename); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(const char *const filename) { + return CImg().load_bmp(filename); + } + + //! Load image from a BMP file \overloading. + CImg& load_bmp(std::FILE *const file) { + return _load_bmp(file,0); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(std::FILE *const file) { + return CImg().load_bmp(file); + } + + CImg& _load_bmp(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_bmp(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(54); + cimg::fread(header._data,54,nfile); + if (*header!='B' || header[1]!='M') { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_bmp(): Invalid BMP file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read header and pixel buffer + int + file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24), + offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24), + header_size = header[0x0E] + (header[0x0F]<<8) + (header[0x10]<<16) + (header[0x11]<<24), + dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24), + dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24), + compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24), + nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24), + bpp = header[0x1C] + (header[0x1D]<<8); + + if (!file_size || file_size==offset) { + cimg::fseek(nfile,0,SEEK_END); + file_size = (int)cimg::ftell(nfile); + cimg::fseek(nfile,54,SEEK_SET); + } + if (header_size>40) cimg::fseek(nfile,header_size - 40,SEEK_CUR); + + const int + dx_bytes = (bpp==1)?(dx/8 + (dx%8?1:0)):((bpp==4)?(dx/2 + (dx%2)):(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); + + CImg colormap; + if (bpp<16) { if (!nb_colors) nb_colors = 1<0) cimg::fseek(nfile,xoffset,SEEK_CUR); + + CImg buffer; + if (buf_size=2) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0x80, val = 0; + cimg_forX(*this,x) { + if (mask==0x80) val = *(ptrs++); + const unsigned char *col = (unsigned char*)(colormap._data + (val&mask?1:0)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask); + } + ptrs+=align_bytes; + } + } break; + case 4 : { // 16 colors + if (colormap._width>=16) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0xF0, val = 0; + cimg_forX(*this,x) { + if (mask==0xF0) val = *(ptrs++); + const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4)); + const unsigned char *col = (unsigned char*)(colormap._data + color); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask,4); + } + ptrs+=align_bytes; + } + } break; + case 8 : { // 256 colors + if (colormap._width>=256) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char *col = (unsigned char*)(colormap._data + *(ptrs++)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + } + ptrs+=align_bytes; + } + } break; + case 16 : { // 16 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char c1 = *(ptrs++), c2 = *(ptrs++); + const unsigned short col = (unsigned short)(c1|(c2<<8)); + (*this)(x,y,2) = (T)(col&0x1F); + (*this)(x,y,1) = (T)((col>>5)&0x1F); + (*this)(x,y,0) = (T)((col>>10)&0x1F); + } + ptrs+=align_bytes; + } + } break; + case 24 : { // 24 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + } + ptrs+=align_bytes; + } + } break; + case 32 : { // 32 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + ++ptrs; + } + ptrs+=align_bytes; + } + } break; + } + if (dy<0) mirror('y'); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a JPEG file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_jpeg(const char *const filename) { + return _load_jpeg(0,filename); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(const char *const filename) { + return CImg().load_jpeg(filename); + } + + //! Load image from a JPEG file \overloading. + CImg& load_jpeg(std::FILE *const file) { + return _load_jpeg(file,0); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(std::FILE *const file) { + return CImg().load_jpeg(file); + } + + // Custom error handler for libjpeg. +#ifdef cimg_use_jpeg + struct _cimg_error_mgr { + struct jpeg_error_mgr original; + jmp_buf setjmp_buffer; + char message[JMSG_LENGTH_MAX]; + }; + + typedef struct _cimg_error_mgr *_cimg_error_ptr; + + METHODDEF(void) _cimg_jpeg_error_exit(j_common_ptr cinfo) { + _cimg_error_ptr c_err = (_cimg_error_ptr) cinfo->err; // Return control to the setjmp point + (*cinfo->err->format_message)(cinfo,c_err->message); + jpeg_destroy(cinfo); // Clean memory and temp files + longjmp(c_err->setjmp_buffer,1); + } +#endif + + CImg& _load_jpeg(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_jpeg(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_jpeg + if (file) + throw CImgIOException(_cimg_instance + "load_jpeg(): Unable to load data from '(FILE*)' unless libjpeg is enabled.", + cimg_instance); + else return load_other(filename); +#else + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + struct jpeg_decompress_struct cinfo; + struct _cimg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr.original); + jerr.original.error_exit = _cimg_jpeg_error_exit; + if (setjmp(jerr.setjmp_buffer)) { // JPEG error + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_jpeg(): Error message returned by libjpeg: %s.", + cimg_instance,jerr.message); + } + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo,nfile); + jpeg_read_header(&cinfo,TRUE); + jpeg_start_decompress(&cinfo); + + if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) { + if (!file) { + cimg::fclose(nfile); + return load_other(filename); + } else + throw CImgIOException(_cimg_instance + "load_jpeg(): Failed to load JPEG data from file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + } + CImg buffer(cinfo.output_width*cinfo.output_components); + JSAMPROW row_pointer[1]; + try { assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); } + catch (...) { if (!file) cimg::fclose(nfile); throw; } + T *ptr_r = _data, *ptr_g = _data + 1UL*_width*_height, *ptr_b = _data + 2UL*_width*_height, + *ptr_a = _data + 3UL*_width*_height; + while (cinfo.output_scanline + // This is experimental code, not much tested, use with care. + CImg& load_magick(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_magick(): Specified filename is (null).", + cimg_instance); +#ifdef cimg_use_magick + Magick::Image image(filename); + const unsigned int W = image.size().width(), H = image.size().height(); + switch (image.type()) { + case Magick::PaletteMatteType : + case Magick::TrueColorMatteType : + case Magick::ColorSeparationType : { + assign(W,H,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + case Magick::PaletteType : + case Magick::TrueColorType : { + assign(W,H,1,3); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + ++pixels; + } + } break; + case Magick::GrayscaleMatteType : { + assign(W,H,1,2); + T *ptr_r = data(0,0,0,0), *ptr_a = data(0,0,0,1); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + default : { + assign(W,H,1,1); + T *ptr_r = data(0,0,0,0); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + ++pixels; + } + } + } + return *this; +#else + throw CImgIOException(_cimg_instance + "load_magick(): Unable to load file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Load image from a file, using Magick++ library \newinstance. + static CImg get_load_magick(const char *const filename) { + return CImg().load_magick(filename); + } + + //! 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. + **/ + CImg& load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return _load_png(0,filename,bits_per_pixel); + } + + //! Load image from a PNG file \newinstance. + static CImg get_load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return CImg().load_png(filename,bits_per_pixel); + } + + //! 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); + } + + //! 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); + } + + // (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) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_png(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_png + cimg::unused(bits_per_pixel); + if (file) + throw CImgIOException(_cimg_instance + "load_png(): Unable to load data from '(FILE*)' unless libpng is enabled.", + cimg_instance); + + else return load_other(filename); +#else + // Open file and check for PNG validity +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb"); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"rb"); +#endif + unsigned char pngCheck[8] = { 0 }; + cimg::fread(pngCheck,8,(std::FILE*)nfile); + if (png_sig_cmp(pngCheck,0,8)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Invalid PNG file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Setup PNG structures for read + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn); + if (!png_ptr) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'png_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'info_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'end_info' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Error handling callback for png file reading + if (setjmp(png_jmpbuf(png_ptr))) { + if (!file) cimg::fclose((std::FILE*)nfile); + png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Encountered unknown fatal error in libpng for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + png_set_sig_bytes(png_ptr, 8); + + // Get PNG Header Info up to data block + png_read_info(png_ptr,info_ptr); + png_uint_32 W, H; + int bit_depth, color_type, interlace_type; + bool is_gray = false; + png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0); + if (bits_per_pixel) *bits_per_pixel = (unsigned int)bit_depth; + + // Transforms to unify image data + if (color_type==PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + color_type = PNG_COLOR_TYPE_RGB; + bit_depth = 8; + } + if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + is_gray = true; + bit_depth = 8; + } + if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + color_type |= PNG_COLOR_MASK_ALPHA; + } + if (color_type==PNG_COLOR_TYPE_GRAY || color_type==PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + color_type |= PNG_COLOR_MASK_COLOR; + is_gray = true; + } + if (color_type==PNG_COLOR_TYPE_RGB) + png_set_filler(png_ptr,0xffffU,PNG_FILLER_AFTER); + + png_read_update_info(png_ptr,info_ptr); + if (bit_depth!=8 && bit_depth!=16) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Invalid bit depth %u in file '%s'.", + cimg_instance, + bit_depth,nfilename?nfilename:"(FILE*)"); + } + const int byte_depth = bit_depth>>3; + + // Allocate memory for image reading + png_bytep *const imgData = new png_bytep[H]; + for (unsigned int row = 0; row& load_pnm(const char *const filename) { + return _load_pnm(0,filename); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(const char *const filename) { + return CImg().load_pnm(filename); + } + + //! Load image from a PNM file \overloading. + CImg& load_pnm(std::FILE *const file) { + return _load_pnm(file,0); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(std::FILE *const file) { + return CImg().load_pnm(file); + } + + CImg& _load_pnm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pnm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + unsigned int ppm_type, W, H, D = 1, colormax = 255; + CImg item(16384,1,1,1,0); + int err, rval, gval, bval; + const longT cimg_iobuffer = (longT)24*1024*1024; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%u",&ppm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %u %u %u %u",&W,&H,&D,&colormax))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): WIDTH and HEIGHT fields undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (ppm_type!=1 && ppm_type!=4) { + if (err==2 || (err==3 && (ppm_type==5 || ppm_type==7 || ppm_type==8 || ppm_type==9))) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%u",&colormax)!=1) + cimg::warn(_cimg_instance + "load_pnm(): COLORMAX field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else { colormax = D; D = 1; } + } + std::fgetc(nfile); + + switch (ppm_type) { + case 1 : { // 2D b&w Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)(rval?0:255); else break; } + } break; + case 2 : { // 2D grey Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)rval; else break; } + } break; + case 3 : { // 2D color Ascii + assign(W,H,1,3); + T *ptrd = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forXY(*this,x,y) { + if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { + *(ptrd++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval; + } else break; + } + } break; + case 4 : { // 2D b&w binary (support 3D PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + unsigned int w = 0, h = 0, d = 0; + for (longT to_read = (longT)((W/8 + (W%8?1:0))*H*D); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + unsigned char mask = 0, val = 0; + for (ulongT off = (ulongT)raw._width; off || mask; mask>>=1) { + if (!mask) { if (off--) val = *(ptrs++); mask = 128; } + *(ptrd++) = (T)((val&mask)?0:255); + if (++w==W) { w = 0; mask = 0; if (++h==H) { h = 0; if (++d==D) break; }} + } + } + } break; + case 5 : case 7 : { // 2D/3D grey binary (support 3D PINK extension) + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } else { // 16 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } + } break; + case 6 : { // 2D color binary + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { // 16 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } + } break; + case 8 : { // 2D/3D grey binary with int32 integers (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const int *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + case 9 : { // 2D/3D grey binary with float values (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const float *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + default : + assign(); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM type 'P%d' found, but type is not supported.", + cimg_instance, + filename?filename:"(FILE*)",ppm_type); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PFM file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pfm(const char *const filename) { + return _load_pfm(0,filename); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(const char *const filename) { + return CImg().load_pfm(filename); + } + + //! Load image from a PFM file \overloading. + CImg& load_pfm(std::FILE *const file) { + return _load_pfm(file,0); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(std::FILE *const file) { + return CImg().load_pfm(file); + } + + CImg& _load_pfm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pfm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + char pfm_type; + CImg item(16384,1,1,1,0); + int W = 0, H = 0, err = 0; + double scale = 0; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%c",&pfm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): PFM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %d %d",&W,&H))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH and HEIGHT fields are undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else if (W<=0 || H<=0) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH (%d) and HEIGHT (%d) fields are invalid in file '%s'.", + cimg_instance,W,H, + filename?filename:"(FILE*)"); + } + if (err==2) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%lf",&scale)!=1) + cimg::warn(_cimg_instance + "load_pfm(): SCALE field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + std::fgetc(nfile); + const bool is_color = (pfm_type=='F'), is_inverted = (scale>0)!=cimg::endianness(); + if (is_color) { + assign(W,H,1,3,(T)0); + CImg buf(3*W); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forY(*this,y) { + cimg::fread(buf._data,3*W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,3*W); + const float *ptrs = buf._data; + cimg_forX(*this,x) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { + assign(W,H,1,1,(T)0); + CImg buf(W); + T *ptrd = data(0,0,0,0); + cimg_forY(*this,y) { + cimg::fread(buf._data,W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,W); + const float *ptrs = buf._data; + cimg_forX(*this,x) *(ptrd++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return mirror('y'); // Most of the .pfm files are flipped along the y-axis + } + + //! Load image from a RGB file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(0,filename,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(filename,dimw,dimh); + } + + //! Load image from a RGB file \overloading. + CImg& load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(file,0,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(file,dimw,dimh); + } + + CImg& _load_rgb(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgb(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/3UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a RGBA file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(0,filename,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(filename,dimw,dimh); + } + + //! Load image from a RGBA file \overloading. + CImg& load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(file,0,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(file,dimw,dimh); + } + + CImg& _load_rgba(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgba(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,4); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2), + *ptr_a = data(0,0,0,3); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/4UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + *(ptr_a++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a TIFF file. + /** + \param filename Filename, as a C-string. + \param first_frame First frame to read (for multi-pages tiff). + \param last_frame Last frame to read (for multi-pages tiff). + \param step_frame Step value of frame reading. + \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. + - 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 + 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) { + 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.", + cimg_instance, + filename); + return load_other(filename); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimg_instance + "load_tiff(): File '%s' contains %u image(s) while specified frame range is [%u,%u] (step %u).", + cimg_instance, + filename,nb_images,nfirst_frame,nlast_frame,nstep_frame); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + TIFFSetDirectory(tif,0); + CImg frame; + for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) { + frame._load_tiff(tif,l,voxel_size,description); + if (l==nfirst_frame) + assign(frame._width,frame._height,1 + (nlast_frame - nfirst_frame)/nstep_frame,frame._spectrum); + if (frame._width>_width || frame._height>_height || frame._spectrum>_spectrum) + resize(std::max(frame._width,_width), + std::max(frame._height,_height),-100, + std::max(frame._spectrum,_spectrum),0); + draw_image(0,0,(l - nfirst_frame)/nstep_frame,frame); + } + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "load_tiff(): Failed to open file '%s'.", + cimg_instance, + filename); + return *this; +#endif + } + + //! Load image from a TIFF file \newinstance. + static CImg 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); + } + + // (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) { + 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) { + 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) { + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (row = 0; rowny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, 0); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0; rr + void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { + t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (unsigned int vv = 0; vvny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, vv); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0;rr& _load_tiff(TIFF *const tif, const unsigned int directory, + 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; + const char *const filename = TIFFFileName(tif); + const bool is_spp = (bool)TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel); + TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx); + TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny); + TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat); + TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample); + TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo); + if (voxel_size) { + const char *s_description = 0; + float vx = 0, vy = 0, vz = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) { + const char *s_desc = std::strstr(s_description,"VX="); + if (s_desc && cimg_sscanf(s_desc,"VX=%f VY=%f VZ=%f",&vx,&vy,&vz)==3) { // CImg format + voxel_size[0] = vx; voxel_size[1] = vy; voxel_size[2] = vz; + } + s_desc = std::strstr(s_description,"spacing="); + if (s_desc && cimg_sscanf(s_desc,"spacing=%f",&vz)==1) { // Fiji format + voxel_size[2] = vz; + } + } + TIFFGetField(tif,TIFFTAG_XRESOLUTION,voxel_size); + TIFFGetField(tif,TIFFTAG_YRESOLUTION,voxel_size + 1); + voxel_size[0] = 1.f/voxel_size[0]; + voxel_size[1] = 1.f/voxel_size[1]; + } + if (description) { + const char *s_description = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) + CImg::string(s_description).move_to(*description); + } + const unsigned int spectrum = !is_spp || photo>=3?(photo>1?3:1):samplesperpixel; + assign(nx,ny,1,spectrum); + + if ((photo>=3 && sampleformat==1 && + (bitspersample==4 || bitspersample==8) && + (samplesperpixel==1 || samplesperpixel==3 || samplesperpixel==4)) || + (bitspersample==1 && samplesperpixel==1)) { + // Special case for unsigned color images. + uint32 *const raster = (uint32*)_TIFFmalloc(nx*ny*sizeof(uint32)); + if (!raster) { + _TIFFfree(raster); TIFFClose(tif); + throw CImgException(_cimg_instance + "load_tiff(): Failed to allocate memory (%s) for file '%s'.", + cimg_instance, + cimg::strbuffersize(nx*ny*sizeof(uint32)),filename); + } + TIFFReadRGBAImage(tif,nx,ny,raster,0); + switch (spectrum) { + case 1 : + cimg_forXY(*this,x,y) + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + break; + case 3 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 -y) + x]); + } + break; + case 4 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny - 1 - y) + x]); + } + break; + } + _TIFFfree(raster); + } else { // Other cases + uint16 config; + TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config); + if (TIFFIsTiled(tif)) { + uint32 tw = 1, th = 1; + TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw); + TIFFGetField(tif,TIFFTAG_TILELENGTH,&th); + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + } + } else { + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + } + } + } + return *this; + } +#endif + + //! Load image from a MINC2 file. + /** + \param filename Filename, as a C-string. + **/ + // (Original code by Haz-Edine Assemlal). + CImg& load_minc2(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_minc2(): Specified filename is (null).", + cimg_instance); +#ifndef cimg_use_minc2 + return load_other(filename); +#else + minc::minc_1_reader rdr; + rdr.open(filename); + assign(rdr.ndim(1)?rdr.ndim(1):1, + rdr.ndim(2)?rdr.ndim(2):1, + rdr.ndim(3)?rdr.ndim(3):1, + rdr.ndim(4)?rdr.ndim(4):1); + if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_byte(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_int(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_double(); + else + rdr.setup_read_float(); + minc::load_standard_volume(rdr,this->_data); + return *this; +#endif + } + + //! Load image from a MINC2 file \newinstance. + static CImg get_load_minc2(const char *const filename) { + return CImg().load_analyze(filename); + } + + //! Load image from an ANALYZE7.5/NIFTI file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_analyze(const char *const filename, float *const voxel_size=0) { + return _load_analyze(0,filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(const char *const filename, float *const voxel_size=0) { + return CImg().load_analyze(filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \overloading. + CImg& load_analyze(std::FILE *const file, float *const voxel_size=0) { + return _load_analyze(file,0,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(std::FILE *const file, float *const voxel_size=0) { + return CImg().load_analyze(file,voxel_size); + } + + CImg& _load_analyze(std::FILE *const file, const char *const filename, float *const voxel_size=0) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_analyze(): Specified filename is (null).", + cimg_instance); + + std::FILE *nfile_header = 0, *nfile = 0; + if (!file) { + CImg body(1024); + const char *const ext = cimg::split_filename(filename,body); + if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file + nfile_header = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".img"); + nfile = cimg::fopen(body,"rb"); + } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file + nfile = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".hdr"); + nfile_header = cimg::fopen(body,"rb"); + } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file + } else nfile_header = nfile = file; // File is a Niftii file + if (!nfile || !nfile_header) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid Analyze7.5 or NIFTI header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + // Read header. + bool endian = false; + unsigned int header_size; + cimg::fread(&header_size,1,nfile_header); + if (!header_size) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid zero-size header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); } + + unsigned char *const header = new unsigned char[header_size]; + cimg::fread(header + 4,header_size - 4,nfile_header); + if (!file && nfile_header!=nfile) cimg::fclose(nfile_header); + if (endian) { + cimg::invert_endianness((short*)(header + 40),5); + cimg::invert_endianness((short*)(header + 70),1); + cimg::invert_endianness((short*)(header + 72),1); + cimg::invert_endianness((float*)(header + 76),4); + cimg::invert_endianness((float*)(header + 108),1); + cimg::invert_endianness((float*)(header + 112),1); + } + + if (nfile_header==nfile) { + const unsigned int vox_offset = (unsigned int)*(float*)(header + 108); + std::fseek(nfile,vox_offset,SEEK_SET); + } + + unsigned short *dim = (unsigned short*)(header + 40), dimx = 1, dimy = 1, dimz = 1, dimv = 1; + if (!dim[0]) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with zero dimensions.", + cimg_instance, + filename?filename:"(FILE*)"); + + if (dim[0]>4) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with %u dimensions, reading only the 4 first.", + cimg_instance, + filename?filename:"(FILE*)",dim[0]); + + if (dim[0]>=1) dimx = dim[1]; + if (dim[0]>=2) dimy = dim[2]; + if (dim[0]>=3) dimz = dim[3]; + if (dim[0]>=4) dimv = dim[4]; + float scalefactor = *(float*)(header + 112); if (scalefactor==0) scalefactor = 1; + const unsigned short datatype = *(unsigned short*)(header + 70); + if (voxel_size) { + const float *vsize = (float*)(header + 76); + voxel_size[0] = vsize[1]; voxel_size[1] = vsize[2]; voxel_size[2] = vsize[3]; + } + delete[] header; + + // Read pixel data. + assign(dimx,dimy,dimz,dimv); + const size_t pdim = (size_t)dimx*dimy*dimz*dimv; + switch (datatype) { + case 2 : { + unsigned char *const buffer = new unsigned char[pdim]; + cimg::fread(buffer,pdim,nfile); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 4 : { + short *const buffer = new short[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 8 : { + int *const buffer = new int[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 16 : { + float *const buffer = new float[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 64 : { + double *const buffer = new double[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_analyze(): Unable to load datatype %d in file '%s'", + cimg_instance, + datatype,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a .cimg[z] file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(const char *const filename, const char axis='z', const float align=0) { + return CImg().load_cimg(filename,axis,align); + } + + //! Load image from a .cimg[z] file \overloading. + CImg& load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + return CImg().load_cimg(file,axis,align); + } + + //! Load sub-images of a .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Starting frame. + \param n1 Ending frame (~0U for max). + \param x0 X-coordinate of the starting sub-image vertex. + \param y0 Y-coordinate of the starting sub-image vertex. + \param z0 Z-coordinate of the starting sub-image vertex. + \param c0 C-coordinate of the starting sub-image vertex. + \param x1 X-coordinate of the ending sub-image vertex (~0U for max). + \param y1 Y-coordinate of the ending sub-image vertex (~0U for max). + \param z1 Z-coordinate of the ending sub-image vertex (~0U for max). + \param c1 C-coordinate of the ending sub-image vertex (~0U for max). + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load sub-images of a .cimg file \overloading. + CImg& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load image from an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_inr(const char *const filename, float *const voxel_size=0) { + return _load_inr(0,filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(const char *const filename, float *const voxel_size=0) { + return CImg().load_inr(filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \overloading. + CImg& load_inr(std::FILE *const file, float *const voxel_size=0) { + return _load_inr(file,0,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(std::FILE *const file, float *voxel_size=0) { + return CImg().load_inr(file,voxel_size); + } + + static void _load_inr_header(std::FILE *file, int out[8], float *const voxel_size) { + CImg item(1024), tmp1(64), tmp2(64); + *item = *tmp1 = *tmp2 = 0; + out[0] = std::fscanf(file,"%63s",item._data); + out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1; + if (cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0) + throw CImgIOException("CImg<%s>::load_inr(): INRIMAGE-4 header not found.", + pixel_type()); + + while (std::fscanf(file," %63[^\n]%*c",item._data)!=EOF && std::strncmp(item,"##}",3)) { + cimg_sscanf(item," XDIM%*[^0-9]%d",out); + cimg_sscanf(item," YDIM%*[^0-9]%d",out + 1); + cimg_sscanf(item," ZDIM%*[^0-9]%d",out + 2); + cimg_sscanf(item," VDIM%*[^0-9]%d",out + 3); + cimg_sscanf(item," PIXSIZE%*[^0-9]%d",out + 6); + if (voxel_size) { + cimg_sscanf(item," VX%*[^0-9.+-]%f",voxel_size); + cimg_sscanf(item," VY%*[^0-9.+-]%f",voxel_size + 1); + cimg_sscanf(item," VZ%*[^0-9.+-]%f",voxel_size + 2); + } + if (cimg_sscanf(item," CPU%*[ =]%s",tmp1._data)) out[7] = cimg::strncasecmp(tmp1,"sun",3)?0:1; + switch (cimg_sscanf(item," TYPE%*[ =]%s %s",tmp1._data,tmp2._data)) { + case 0 : break; + case 2 : + out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; + std::strncpy(tmp1,tmp2,tmp1._width - 1); // 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 + default : + throw CImgIOException("CImg<%s>::load_inr(): Invalid pixel type '%s' defined in header.", + pixel_type(), + tmp2._data); + } + } + if (out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0) + throw CImgIOException("CImg<%s>::load_inr(): Invalid dimensions (%d,%d,%d,%d) defined in header.", + pixel_type(), + out[0],out[1],out[2],out[3]); + if (out[4]<0 || out[5]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete pixel type defined in header.", + pixel_type()); + if (out[6]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete PIXSIZE field defined in header.", + pixel_type()); + if (out[7]<0) + throw CImgIOException("CImg<%s>::load_inr(): Big/Little Endian coding type undefined in header.", + pixel_type()); + } + + CImg& _load_inr(std::FILE *const file, const char *const filename, float *const voxel_size) { +#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \ + if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \ + Ts *xval, *const val = new Ts[(size_t)fopt[0]*fopt[3]]; \ + cimg_forYZ(*this,y,z) { \ + cimg::fread(val,fopt[0]*fopt[3],nfile); \ + if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \ + xval = val; cimg_forX(*this,x) cimg_forC(*this,c) (*this)(x,y,z,c) = (T)*(xval++); \ + } \ + delete[] val; \ + loaded = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_inr(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + int fopt[8], endian = cimg::endianness()?1:0; + bool loaded = false; + if (voxel_size) voxel_size[0] = voxel_size[1] = voxel_size[2] = 1; + _load_inr_header(nfile,fopt,voxel_size); + assign(fopt[0],fopt[1],fopt[2],fopt[3]); + _cimg_load_inr_case(0,0,8,unsigned char); + _cimg_load_inr_case(0,1,8,char); + _cimg_load_inr_case(0,0,16,unsigned short); + _cimg_load_inr_case(0,1,16,short); + _cimg_load_inr_case(0,0,32,unsigned int); + _cimg_load_inr_case(0,1,32,int); + _cimg_load_inr_case(1,0,32,float); + _cimg_load_inr_case(1,1,32,float); + _cimg_load_inr_case(1,0,64,double); + _cimg_load_inr_case(1,1,64,double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_inr(): Unknown pixel type defined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a EXR file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_exr(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_exr(): Specified filename is (null).", + cimg_instance); +#if defined(cimg_use_openexr) + Imf::RgbaInputFile file(filename); + Imath::Box2i dw = file.dataWindow(); + const int + inwidth = dw.max.x - dw.min.x + 1, + inheight = dw.max.y - dw.min.y + 1; + Imf::Array2D pixels; + pixels.resizeErase(inheight,inwidth); + file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y*inwidth, 1, inwidth); + file.readPixels(dw.min.y, dw.max.y); + assign(inwidth,inheight,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + cimg_forXY(*this,x,y) { + *(ptr_r++) = (T)pixels[y][x].r; + *(ptr_g++) = (T)pixels[y][x].g; + *(ptr_b++) = (T)pixels[y][x].b; + *(ptr_a++) = (T)pixels[y][x].a; + } +#elif defined(cimg_use_tinyexr) + float *res; + const char *err = 0; + int width = 0, height = 0; + const int ret = LoadEXR(&res,&width,&height,filename,&err); + if (ret) throw CImgIOException(_cimg_instance + "load_exr(): Unable to load EXR file '%s'.", + cimg_instance,filename); + CImg(out,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this); + std::free(res); +#else + return load_other(filename); +#endif + return *this; + } + + //! Load image from a EXR file \newinstance. + static CImg get_load_exr(const char *const filename) { + return CImg().load_exr(filename); + } + + //! Load image from a PANDORE-5 file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pandore(const char *const filename) { + return _load_pandore(0,filename); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(const char *const filename) { + return CImg().load_pandore(filename); + } + + //! Load image from a PANDORE-5 file \overloading. + CImg& load_pandore(std::FILE *const file) { + return _load_pandore(file,0); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(std::FILE *const file) { + return CImg().load_pandore(file); + } + + CImg& _load_pandore(std::FILE *const file, const char *const filename) { +#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \ + cimg::fread(dims,nbdim,nfile); \ + if (endian) cimg::invert_endianness(dims,nbdim); \ + assign(nwidth,nheight,ndepth,ndim); \ + const size_t siz = size(); \ + stype *buffer = new stype[siz]; \ + cimg::fread(buffer,siz,nfile); \ + if (endian) cimg::invert_endianness(buffer,siz); \ + T *ptrd = _data; \ + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \ + buffer-=siz; \ + delete[] buffer + +#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \ + if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \ + else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \ + else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \ + else throw CImgIOException(_cimg_instance \ + "load_pandore(): Unknown pixel datatype in file '%s'.", \ + cimg_instance, \ + filename?filename:"(FILE*)"); } + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pandore(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(32); + cimg::fread(header._data,12,nfile); + if (cimg::strncasecmp("PANDORE",header,7)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): PANDORE header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + unsigned int imageid, dims[8] = { 0 }; + int ptbuf[4] = { 0 }; + cimg::fread(&imageid,1,nfile); + const bool endian = imageid>255; + if (endian) cimg::invert_endianness(imageid); + cimg::fread(header._data,20,nfile); + + switch (imageid) { + case 2 : _cimg_load_pandore_case(2,dims[1],1,1,1,unsigned char,unsigned char,unsigned char,1); break; + case 3 : _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break; + case 4 : _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break; + case 5 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,unsigned char,unsigned char,unsigned char,1); break; + case 6 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break; + case 7 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break; + case 8 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,unsigned char,unsigned char,unsigned char,1); break; + case 9 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break; + case 10 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break; + case 11 : { // Region 1D + cimg::fread(dims,3,nfile); + if (endian) cimg::invert_endianness(dims,3); + assign(dims[1],1,1,1); + const unsigned siz = size(); + if (dims[2]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[2]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 12 : { // Region 2D + cimg::fread(dims,4,nfile); + if (endian) cimg::invert_endianness(dims,4); + assign(dims[2],dims[1],1,1); + const size_t siz = size(); + if (dims[3]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[3]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 13 : { // Region 3D + cimg::fread(dims,5,nfile); + if (endian) cimg::invert_endianness(dims,5); + assign(dims[3],dims[2],dims[1],1); + const size_t siz = size(); + if (dims[4]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[4]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 16 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,unsigned char,unsigned char,unsigned char,1); break; + case 17 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break; + case 18 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break; + case 19 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,unsigned char,unsigned char,unsigned char,1); break; + case 20 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break; + case 21 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break; + case 22 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 23 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4); break; + case 24 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 25 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break; + case 26 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 27 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break; + case 28 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 29 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break; + case 30 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned char,unsigned char,unsigned char,1); + break; + case 31 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break; + case 32 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned long,unsigned int,unsigned short,4); + break; + case 33 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break; + case 34 : { // Points 1D + cimg::fread(ptbuf,1,nfile); + if (endian) cimg::invert_endianness(ptbuf,1); + assign(1); (*this)(0) = (T)ptbuf[0]; + } break; + case 35 : { // Points 2D + cimg::fread(ptbuf,2,nfile); + if (endian) cimg::invert_endianness(ptbuf,2); + assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0]; + } break; + case 36 : { // Points 3D + cimg::fread(ptbuf,3,nfile); + if (endian) cimg::invert_endianness(ptbuf,3); + assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0]; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): Unable to load data with ID_type %u in file '%s'.", + cimg_instance, + imageid,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PAR-REC (Philips) file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_parrec(const char *const filename, const char axis='c', const float align=0) { + CImgList list; + list.load_parrec(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a PAR-REC (Philips) file \newinstance. + static CImg get_load_parrec(const char *const filename, const char axis='c', const float align=0) { + return CImg().load_parrec(filename,axis,align); + } + + //! Load image from a raw binary file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the image buffer. + \param size_y Height of the image buffer. + \param size_z Depth of the image buffer. + \param size_c Spectrum of the image buffer. + \param is_multiplexed Tells if the image values are multiplexed along the C-axis. + \param invert_endianness Tells if the endianness of the image buffer must be inverted. + \param offset Starting offset of the read in the specified file. + **/ + CImg& load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(0,filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \overloading. + CImg& load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(file,0,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(file,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + 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 ulongT offset) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename is (null).", + cimg_instance); + if (cimg::is_directory(filename)) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename '%s' is a directory.", + cimg_instance,filename); + + ulongT siz = (ulongT)size_x*size_y*size_z*size_c; + unsigned int + _size_x = size_x, + _size_y = size_y, + _size_z = size_z, + _size_c = size_c; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + if (!siz) { // Retrieve file size + const longT fpos = cimg::ftell(nfile); + if (fpos<0) throw CImgArgumentException(_cimg_instance + "load_raw(): Cannot determine size of input file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + cimg::fseek(nfile,0,SEEK_END); + siz = cimg::ftell(nfile)/sizeof(T); + _size_y = (unsigned int)siz; + _size_x = _size_z = _size_c = 1; + cimg::fseek(nfile,fpos,SEEK_SET); + } + cimg::fseek(nfile,offset,SEEK_SET); + assign(_size_x,_size_y,_size_z,_size_c,0); + if (siz && (!is_multiplexed || size_c==1)) { + cimg::fread(_data,siz,nfile); + if (invert_endianness) cimg::invert_endianness(_data,siz); + } else if (siz) { + CImg buf(1,1,1,_size_c); + cimg_forXYZ(*this,x,y,z) { + cimg::fread(buf._data,_size_c,nfile); + if (invert_endianness) cimg::invert_endianness(buf._data,_size_c); + set_vector_at(buf,x,y,z); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image sequence from a YUV file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the frames. + \param size_y Height of the frames. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param yuv2rgb Tells if the YUV to RGB transform must be applied. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + **/ + CImg& load_yuv(const char *const filename, + 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') { + return get_load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(const char *const filename, + 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') { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb).get_append(axis); + } + + //! Load image sequence from a YUV file \overloading. + CImg& load_yuv(std::FILE *const file, + 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') { + return get_load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(std::FILE *const file, + 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') { + 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. + /** + \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 + CImg& load_off(CImgList& primitives, CImgList& colors, const char *const filename) { + return _load_off(primitives,colors,0,filename); + } + + //! 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. + 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. + template + static CImg get_load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { + return CImg().load_off(primitives,colors,file); + } + + template + CImg& _load_off(CImgList& primitives, CImgList& colors, + std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_off(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0; + CImg line(256); *line = 0; + int err; + + // Skip comments, and read magic string OFF + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): OFF header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Invalid number of vertices or primitives specified in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read points data + assign(nb_points,3); + float X = 0, Y = 0, Z = 0; + cimg_forX(*this,l) { + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Failed to read vertex %u/%u in file '%s'.", + cimg_instance, + l + 1,nb_points,filename?filename:"(FILE*)"); + } + (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z; + } + + // Read primitive data + primitives.assign(); + colors.assign(); + bool stop_flag = false; + while (!stop_flag) { + float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f; + unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0; + *line = 0; + if ((err = std::fscanf(nfile,"%u",&prim))!=1) stop_flag = true; + else { + ++nb_read; + switch (prim) { + case 1 : { + if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 2 : { + if ((err = std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 3 : { + if ((err = std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line._data))<3) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 4 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line._data))<4) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 5 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line._data))<5) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 6 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line._data))<6) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 7 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i4,i3,i1).move_to(primitives); + CImg::vector(i0,i6,i5,i4).move_to(primitives); + CImg::vector(i3,i2,i1).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + case 8 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + CImg::vector(i0,i7,i6,i5).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + default : + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u (%u vertices) from file '%s'.", + cimg_instance, + nb_read,nb_primitives,prim,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } + } + } + if (!file) cimg::fclose(nfile); + if (primitives._width!=nb_primitives) + cimg::warn(_cimg_instance + "load_off(): Only %u/%u primitives read from file '%s'.", + cimg_instance, + primitives._width,nb_primitives,filename?filename:"(FILE*)"); + return *this; + } + + //! Load image sequence from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param axis Alignment axis. + \param align Apending alignment. + **/ + CImg& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return get_load_video(filename,first_frame,last_frame,step_frame,axis,align).move_to(*this); + } + + //! Load image sequence from a video file, using OpenCV library \newinstance. + static CImg get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame).get_append(axis,align); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg'. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return get_load_ffmpeg_external(filename,axis,align).move_to(*this); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg' \newinstance. + static CImg get_load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return CImgList().load_ffmpeg_external(filename).get_append(axis,align); + } + + //! Load gif file, using Imagemagick or GraphicsMagicks's external tools. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return get_load_gif_external(filename,axis,align).move_to(*this); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tool 'convert' \newinstance. + static CImg get_load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return CImgList().load_gif_external(filename).get_append(axis,align); + } + + //! Load image using GraphicsMagick's external tool 'gm'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_graphicsmagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + 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()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_graphicsmagick_external(): Failed to load file '%s' " + "with external command 'gm'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=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::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::graphicsmagick_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using GraphicsMagick's external tool 'gm' \newinstance. + static CImg get_load_graphicsmagick_external(const char *const filename) { + return CImg().load_graphicsmagick_external(filename); + } + + //! Load gzipped image file, using external tool 'gunzip'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimg_instance + "load_gzip_external(): Specified filename is (null).", + cimg_instance); + 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), + *const ext2 = cimg::split_filename(body,0); + + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load gzipped image file, using external tool 'gunzip' \newinstance. + static CImg get_load_gzip_external(const char *const filename) { + return CImg().load_gzip_external(filename); + } + + //! Load image using ImageMagick's external tool 'convert'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_imagemagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_imagemagick_external(): Specified filename is (null).", + cimg_instance); + 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::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_imagemagick_external(): Failed to load file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s%s \"%s\" \"%s\"", + cimg::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::imagemagick_path()); + 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 " + "external command 'magick/convert'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using ImageMagick's external tool 'convert' \newinstance. + static CImg get_load_imagemagick_external(const char *const filename) { + return CImg().load_imagemagick_external(filename); + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_medcon_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_medcon_external(): Specified filename is (null).", + cimg_instance); + 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=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::medcon_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + cimg::split_filename(filename_tmp,body); + + cimg_snprintf(command,command._width,"%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + throw CImgIOException(_cimg_instance + "load_medcon_external(): Failed to load file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + load_analyze(command); + std::remove(command); + cimg::split_filename(command,body); + cimg_snprintf(command,command._width,"%s.img",body._data); + std::remove(command); + return *this; + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon' \newinstance. + static CImg get_load_medcon_external(const char *const filename) { + return CImg().load_medcon_external(filename); + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_dcraw_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_dcraw_external(): Specified filename is (null).", + cimg_instance); + 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 + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\"", + cimg::dcraw_path(),s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_dcraw_external(): Failed to load file '%s' with external command 'dcraw'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.ppm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\" > \"%s\"", + cimg::dcraw_path(),s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::dcraw_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw' \newinstance. + static CImg get_load_dcraw_external(const char *const filename) { + return CImg().load_dcraw_external(filename); + } + + //! Load image from a camera stream, using OpenCV. + /** + \param camera_index Index of the camera to capture images from. + \param skip_frames Number of frames to skip before the capture. + \param release_camera Tells if the camera ressource must be released at the end of the method. + \param capture_width Width of the desired image. + \param capture_height Height of the desired image. + **/ + 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) { +#ifdef cimg_use_opencv + if (camera_index>99) + throw CImgArgumentException(_cimg_instance + "load_camera(): Invalid request for camera #%u " + "(no more than 100 cameras can be managed simultaneously).", + cimg_instance, + camera_index); + static CvCapture *capture[100] = { 0 }; + static unsigned int capture_w[100], capture_h[100]; + if (release_camera) { + cimg::mutex(9); + if (capture[camera_index]) cvReleaseCapture(&(capture[camera_index])); + capture[camera_index] = 0; + capture_w[camera_index] = capture_h[camera_index] = 0; + cimg::mutex(9,0); + return *this; + } + if (!capture[camera_index]) { + cimg::mutex(9); + capture[camera_index] = cvCreateCameraCapture(camera_index); + capture_w[camera_index] = 0; + capture_h[camera_index] = 0; + cimg::mutex(9,0); + if (!capture[camera_index]) { + throw CImgIOException(_cimg_instance + "load_camera(): Failed to initialize camera #%u.", + cimg_instance, + camera_index); + } + } + cimg::mutex(9); + if (capture_width!=capture_w[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_WIDTH,capture_width); + capture_w[camera_index] = capture_width; + } + if (capture_height!=capture_h[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_HEIGHT,capture_height); + capture_h[camera_index] = capture_height; + } + const IplImage *img = 0; + for (unsigned int i = 0; 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++); + } + } + cimg::mutex(9,0); + return *this; +#else + cimg::unused(camera_index,skip_frames,release_camera,capture_width,capture_height); + throw CImgIOException(_cimg_instance + "load_camera(): This function requires the OpenCV library to run " + "(macro 'cimg_use_opencv' must be defined).", + cimg_instance); +#endif + } + + //! Load image from a camera stream, using OpenCV \newinstance. + static CImg 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); + } + + //! Load image using various non-native ways. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_other(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_other(): Specified filename is (null).", + cimg_instance); + + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_magick(filename); } + catch (CImgException&) { + try { load_imagemagick_external(filename); } + catch (CImgException&) { + try { load_graphicsmagick_external(filename); } + catch (CImgException&) { + try { load_cimg(filename); } + catch (CImgException&) { + try { + cimg::fclose(cimg::fopen(filename,"rb")); + } catch (CImgException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to open file '%s'.", + cimg_instance, + filename); + } + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image using various non-native ways \newinstance. + static CImg get_load_other(const char *const filename) { + return CImg().load_other(filename); + } + + //@} + //--------------------------- + // + //! \name Data Output + //@{ + //--------------------------- + + //! Display information about the image data. + /** + \param title Name for the considered image. + \param display_stats Tells to compute and display image statistics. + **/ + const CImg& print(const char *const title=0, const bool display_stats=true) const { + + int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0; + CImg st; + if (!is_empty() && display_stats) { + st = get_stats(); + xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7]; + xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11]; + } + + const ulongT siz = size(), msiz = siz*sizeof(T), siz1 = siz - 1, + mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U, width1 = _width - 1; + + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImg<%s>",pixel_type()); + + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = (%u,%u,%u,%u) [%lu %s], %sdata%s = (%s*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum, + (unsigned long)(mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20))), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) + std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end() - 1),_is_shared?"shared":"non-shared"); + else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared"); + + if (!is_empty()) cimg_foroff(*this,off) { + std::fprintf(cimg::output(),"%g",(double)_data[off]); + if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" "); + if (off==7 && siz>16) { off = siz1 - 8; std::fprintf(cimg::output(),"... "); } + } + if (!is_empty() && display_stats) + std::fprintf(cimg::output(), + " ], %smin%s = %g, %smax%s = %g, %smean%s = %g, %sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), " + "%scoords_max%s = (%u,%u,%u,%u).\n", + cimg::t_bold,cimg::t_normal,st[0], + cimg::t_bold,cimg::t_normal,st[1], + cimg::t_bold,cimg::t_normal,st[2], + cimg::t_bold,cimg::t_normal,std::sqrt(st[3]), + cimg::t_bold,cimg::t_normal,xm,ym,zm,vm, + cimg::t_bold,cimg::t_normal,xM,yM,zM,vM); + else std::fprintf(cimg::output(),"%s].\n",is_empty()?"":" "); + std::fflush(cimg::output()); + return *this; + } + + //! Display image into a CImgDisplay window. + /** + \param disp Display window. + **/ + const CImg& display(CImgDisplay& disp) const { + disp.display(*this); + return *this; + } + + //! Display image into a CImgDisplay window, in an interactive way. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(CImgDisplay &disp, const bool display_info, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + return _display(disp,0,display_info,XYZ,exit_on_anykey,false); + } + + //! Display image into an interactive window. + /** + \param title Window title + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(const char *const title=0, const bool display_info=true, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display(disp,title,display_info,XYZ,exit_on_anykey,false); + } + + const CImg& _display(CImgDisplay &disp, const char *const title, const bool display_info, + unsigned int *const XYZ, const bool exit_on_anykey, + const bool exit_on_simpleclick) const { + unsigned int oldw = 0, oldh = 0, _XYZ[3] = { 0 }, key = 0; + int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1, + old_mouse_x = -1, old_mouse_y = -1; + + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + else disp.set_title("%s",title); + } else if (title) disp.set_title("%s",title); + disp.show().flush(); + + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(dtitle); + + CImg zoom; + for (bool reset_view = true, resize_disp = false, is_first_select = true; !key && !disp.is_closed(); ) { + if (reset_view) { + if (XYZ) { _XYZ[0] = XYZ[0]; _XYZ[1] = XYZ[1]; _XYZ[2] = XYZ[2]; } + else { + _XYZ[0] = (unsigned int)(x0 + x1)/2; + _XYZ[1] = (unsigned int)(y0 + y1)/2; + _XYZ[2] = (unsigned int)(z0 + z1)/2; + } + x0 = 0; y0 = 0; z0 = 0; x1 = width() - 1; y1 = height() - 1; z1 = depth() - 1; + 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) { + if (is_empty()) zoom.assign(1,1,1,1,(T)0); else zoom.assign(); + } else zoom = get_crop(x0,y0,z0,x1,y1,z1); + + const CImg& visu = zoom?zoom:*this; + const unsigned int + dx = 1U + x1 - x0, dy = 1U + y1 - y0, dz = 1U + z1 - z0, + tw = dx + (dz>1?dz:0U), th = dy + (dz>1?dz:0U); + if (!is_empty() && !disp.is_fullscreen() && resize_disp) { + const 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 + 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; + } + oldw = tw; oldh = th; + + bool + go_up = false, go_down = false, go_left = false, go_right = false, + go_inc = false, go_dec = false, go_in = false, go_out = false, + go_in_center = false; + + disp.set_title("%s",dtitle._data); + if (_width>1 && visu._width==1) disp.set_title("%s | x=%u",disp._title,x0); + if (_height>1 && visu._height==1) disp.set_title("%s | y=%u",disp._title,y0); + if (_depth>1 && visu._depth==1) disp.set_title("%s | z=%u",disp._title,z0); + + disp._mouse_x = old_mouse_x; disp._mouse_y = old_mouse_y; + CImg selection = visu._select(disp,0,2,_XYZ,x0,y0,z0,true,is_first_select,_depth>1,true); + old_mouse_x = disp._mouse_x; old_mouse_y = disp._mouse_y; + is_first_select = false; + + if (disp.wheel()) { + if ((disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) && + (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT())) { + go_left = !(go_right = disp.wheel()>0); + } else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) { + go_down = !(go_up = disp.wheel()>0); + } else if (depth()==1 || disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + go_out = !(go_in = disp.wheel()>0); go_in_center = false; + } + disp.set_wheel(); + } + + const int + sx0 = selection(0), sy0 = selection(1), sz0 = selection(2), + sx1 = selection(3), sy1 = selection(4), sz1 = selection(5); + if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) { + x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; + x0+=sx0; y0+=sy0; z0+=sz0; + if ((sx0==sx1 && sy0==sy1) || (_depth>1 && sx0==sx1 && sz0==sz1) || (_depth>1 && sy0==sy1 && sz0==sz1)) { + if (exit_on_simpleclick && (!zoom || is_empty())) break; else reset_view = true; + } + resize_disp = true; + } else switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : key = 0; break; + case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) { + // Special mode: play stack of frames + const unsigned int + w1 = visu._width*disp.width()/(visu._width + (visu._depth>1?visu._depth:0)), + h1 = visu._height*disp.height()/(visu._height + (visu._depth>1?visu._depth:0)); + float frame_timing = 5; + bool is_stopped = false; + disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0; + for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) { + if (disp.is_resized()) disp.resize(false); + if (!timer) { + visu.get_slice((int)_XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),_XYZ[2])); + (++_XYZ[2])%=visu._depth; + } + if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U; + if (disp.wheel()) { frame_timing-=disp.wheel()/3.f; disp.set_wheel(); } + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break; + case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break; + case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true; + (_XYZ[2]+=visu._depth - 2)%=visu._depth; timer = 0; key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false). + toggle_fullscreen().set_key(key,false); key = 0; + } break; + } + frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing); + disp.wait(20); + } + const unsigned int + w2 = (visu._width + (visu._depth>1?visu._depth:0))*disp.width()/visu._width, + h2 = (visu._height + (visu._depth>1?visu._depth:0))*disp.height()/visu._height; + disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(dtitle.data()).set_key().set_button().set_wheel(); + key = 0; + } break; + case cimg::keyHOME : reset_view = resize_disp = true; key = 0; break; + case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break; + case cimg::keyPADSUB : go_out = true; key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break; + case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break; + case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break; + case cimg::keyPAD7 : go_up = go_left = true; key = 0; break; + case cimg::keyPAD9 : go_up = go_right = true; key = 0; break; + case cimg::keyPAD1 : go_down = go_left = true; key = 0; break; + case cimg::keyPAD3 : go_down = go_right = true; key = 0; break; + case cimg::keyPAGEUP : go_inc = true; key = 0; break; + case cimg::keyPAGEDOWN : go_dec = true; key = 0; break; + } + if (go_in) { + const int + mx = go_in_center?disp.width()/2:disp.mouse_x(), + my = go_in_center?disp.height()/2:disp.mouse_y(), + mX = mx*(width() + (depth()>1?depth():0))/disp.width(), + mY = my*(height() + (depth()>1?depth():0))/disp.height(); + int X = (int)_XYZ[0], Y = (int)_XYZ[1], Z = (int)_XYZ[2]; + if (mX=height()) { + X = x0 + mX*(1 + x1 - x0)/width(); Z = z0 + (mY - height())*(1 + z1 - z0)/depth(); + } + if (mX>=width() && mY4) { x0 = X - 3*(X - x0)/4; x1 = X + 3*(x1 - X)/4; } + if (y1 - y0>4) { y0 = Y - 3*(Y - y0)/4; y1 = Y + 3*(y1 - Y)/4; } + if (z1 - z0>4) { z0 = Z - 3*(Z - z0)/4; z1 = Z + 3*(z1 - Z)/4; } + } + if (go_out) { + const int + delta_x = (x1 - x0)/8, delta_y = (y1 - y0)/8, delta_z = (z1 - z0)/8, + ndelta_x = delta_x?delta_x:(_width>1), + ndelta_y = delta_y?delta_y:(_height>1), + ndelta_z = delta_z?delta_z:(_depth>1); + x0-=ndelta_x; y0-=ndelta_y; z0-=ndelta_z; + x1+=ndelta_x; y1+=ndelta_y; z1+=ndelta_z; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=width()) x1 = width() - 1; } + if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; } + if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; } + if (x1>=width()) { x0-=(x1 - width() + 1); x1 = width() - 1; if (x0<0) x0 = 0; } + if (y1>=height()) { y0-=(y1 - height() + 1); y1 = height() - 1; if (y0<0) y0 = 0; } + if (z1>=depth()) { z0-=(z1 - depth() + 1); z1 = depth() - 1; if (z0<0) z0 = 0; } + const float + ratio = (float)(x1-x0)/(y1-y0), + ratiow = (float)disp._width/disp._height, + sub = std::min(cimg::abs(ratio - ratiow),cimg::abs(1/ratio-1/ratiow)); + if (sub>0.01) resize_disp = true; + } + if (go_left) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + } + if (go_right) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x1+ndelta1); + if (y0 - ndelta>=0) { y0-=ndelta; y1-=ndelta; } + else { y1-=y0; y0 = 0; } + } + if (go_down) { + const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1); + if (y1+ndelta1); + if (z0 - ndelta>=0) { z0-=ndelta; z1-=ndelta; } + else { z1-=z0; z0 = 0; } + } + if (go_dec) { + const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1); + if (z1+ndelta + const CImg& display_object3d(CImgDisplay& disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return _display_object3d(disp,0,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + template + const CImg& _display_object3d(CImgDisplay& disp, const char *const title, + 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 float light_x, const float light_y, const float light_z, + const float specular_lightness, const float specular_shininess, + const bool display_axes, float *const pose_matrix, + const bool exit_on_anykey) const { + typedef typename cimg::superset::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); + } 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).", + 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, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3); + if (!title) disp.set_title("CImg<%s> (%u vertices, %u primitives)", + pixel_type(),vertices._width,primitives._width); + } else if (title) disp.set_title("%s",title); + + // Init 3D objects and compute object statistics + CImg + pose, + rotated_vertices(vertices._width,3), + bbox_vertices, rotated_bbox_vertices, + axes_vertices, rotated_axes_vertices, + bbox_opacities, axes_opacities; + CImgList bbox_primitives, axes_primitives; + CImgList reverse_primitives; + CImgList bbox_colors, bbox_colors2, axes_colors; + unsigned int ns_width = 0, ns_height = 0; + int _is_double_sided = (int)is_double_sided; + bool ndisplay_axes = display_axes; + const CImg + background_color(1,1,1,_spectrum,0), + foreground_color(1,1,1,_spectrum,255); + float + Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1, + xm = 0, xM = vertices?vertices.get_shared_row(0).max_min(xm):0, + ym = 0, yM = vertices?vertices.get_shared_row(1).max_min(ym):0, + zm = 0, zM = vertices?vertices.get_shared_row(2).max_min(zm):0; + const float delta = cimg::max(xM - xm,yM - ym,zM - zm); + + rotated_bbox_vertices = bbox_vertices.assign(8,3,1,1, + xm,xM,xM,xm,xm,xM,xM,xm, + ym,ym,yM,yM,ym,ym,yM,yM, + zm,zm,zm,zm,zM,zM,zM,zM); + bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6); + bbox_colors.assign(6,_spectrum,1,1,1,background_color[0]); + bbox_colors2.assign(6,_spectrum,1,1,1,foreground_color[0]); + bbox_opacities.assign(bbox_colors._width,1,1,1,0.3f); + + rotated_axes_vertices = axes_vertices.assign(7,3,1,1, + 0,20,0,0,22,-6,-6, + 0,0,20,0,-6,22,-6, + 0,0,0,20,0,0,22); + axes_opacities.assign(3,1,1,1,1); + axes_colors.assign(3,_spectrum,1,1,1,foreground_color[0]); + axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3); + + // Begin user interaction loop + CImg 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; + int + x0 = 0, y0 = 0, x1 = 0, y1 = 0, + nrender_static = render_static, + nrender_motion = render_motion; + disp.show().flush(); + + while (!disp.is_closed() && !key) { + + // Init object pose + if (init_pose) { + const float + ratio = delta>0?(2.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); + else CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose); + if (pose_matrix) { + CImg pose0(pose_matrix,4,3,1,1,false); + pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0); + pose0(3,3) = pose(3,3) = 1; + (pose0*pose).get_crop(0,0,3,2).move_to(pose); + Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15]; + } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; } + init_pose = false; + redraw = true; + } + + // Rotate and draw 3D object + if (redraw) { + const float + r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0), + r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1), + r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2); + if ((clicked && nrender_motion>=0) || (!clicked && nrender_static>=0)) + cimg_forX(vertices,l) { + const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2); + rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + else cimg_forX(bbox_vertices,l) { + const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2); + rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + + // Draw objects + const bool render_with_zbuffer = !clicked && nrender_static>0; + visu = visu0; + if ((clicked && nrender_motion<0) || (!clicked && nrender_static<0)) + visu.draw_object3d(Xoff + visu._width/2.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.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.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.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 + n = 1e-8f + cimg::hypot(r00,r01,r02), + _r00 = r00/n, _r10 = r10/n, _r20 = r20/n, + _r01 = r01/n, _r11 = r11/n, _r21 = r21/n, + _r02 = r01/n, _r12 = r12/n, _r22 = r22/n, + Xaxes = 25, Yaxes = visu._height - 38.f; + cimg_forX(axes_vertices,l) { + const float + x = axes_vertices(l,0), + y = axes_vertices(l,1), + z = axes_vertices(l,2); + rotated_axes_vertices(l,0) = _r00*x + _r10*y + _r20*z; + rotated_axes_vertices(l,1) = _r01*x + _r11*y + _r21*z; + rotated_axes_vertices(l,2) = _r02*x + _r12*y + _r22*z; + } + axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.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)), + (int)(Yaxes + rotated_axes_vertices(4,1)), + "X",axes_colors[0]._data,0,axes_opacities(0,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(5,0)), + (int)(Yaxes + rotated_axes_vertices(5,1)), + "Y",axes_colors[1]._data,0,axes_opacities(1,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(6,0)), + (int)(Yaxes + rotated_axes_vertices(6,1)), + "Z",axes_colors[2]._data,0,axes_opacities(2,0),13); + } + visu.display(disp); + if (!clicked || nrender_motion==nrender_static) redraw = false; + } + + // Handle user interaction + disp.wait(); + if ((disp.button() || disp.wheel()) && disp.mouse_x()>=0 && disp.mouse_y()>=0) { + redraw = true; + if (!clicked) { x0 = x1 = disp.mouse_x(); y0 = y1 = disp.mouse_y(); if (!disp.wheel()) clicked = true; } + else { x1 = disp.mouse_x(); y1 = disp.mouse_y(); } + if (disp.button()&1) { + const float + R = 0.45f*std::min(disp.width(),disp.height()), + R2 = R*R, + u0 = (float)(x0 - disp.width()/2), + v0 = (float)(y0 - disp.height()/2), + u1 = (float)(x1 - disp.width()/2), + v1 = (float)(y1 - disp.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + (CImg::rotation_matrix(u,v,w,-alpha)*pose).move_to(pose); + x0 = x1; y0 = y1; + } + if (disp.button()&2) { + if (focale>0) Zoff-=(y0 - y1)*focale/400; + else { const float s = std::exp((y0 - y1)/400.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.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)) { + 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); + } + } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; } + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD: if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + if (!ns_width || !ns_height || + ns_width>(unsigned int)disp.screen_width() || ns_height>(unsigned int)disp.screen_height()) { + ns_width = disp.screen_width()*3U/4; + ns_height = disp.screen_height()*3U/4; + } + if (disp.is_fullscreen()) disp.resize(ns_width,ns_height,false); + else { + ns_width = disp._width; ns_height = disp._height; + disp.resize(disp.screen_width(),disp.screen_height(),false); + } + disp.toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyT : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Switch single/double-sided primitives. + if (--_is_double_sided==-2) _is_double_sided = 1; + if (_is_double_sided>=0) reverse_primitives.assign(); + else primitives.get_reverse_object3d().move_to(reverse_primitives); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyZ : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Enable/disable Z-buffer + if (zbuffer) zbuffer.assign(); + else zbuffer.assign(visu0.width(),visu0.height(),1,1,0); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Show/hide 3D axes + ndisplay_axes = !ndisplay_axes; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF1 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to points + nrender_motion = (nrender_static==0 && nrender_motion!=0)?0:-1; nrender_static = 0; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF2 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to lines + nrender_motion = (nrender_static==1 && nrender_motion!=1)?1:-1; nrender_static = 1; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF3 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat + nrender_motion = (nrender_static==2 && nrender_motion!=2)?2:-1; nrender_static = 2; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF4 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat-shaded + nrender_motion = (nrender_static==3 && nrender_motion!=3)?3:-1; nrender_static = 3; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF5 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Set rendering mode to gouraud-shaded. + nrender_motion = (nrender_static==4 && nrender_motion!=4)?4:-1; nrender_static = 4; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF6 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to phong-shaded + nrender_motion = (nrender_static==5 && nrender_motion!=5)?5:-1; nrender_static = 5; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save snapshot + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving snapshot... ",false).display(disp); + visu.save(filename); + (+visu).__draw_text(" Snapshot '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.save_off(reverse_primitives?reverse_primitives:primitives,colors,filename); + (+visu).__draw_text(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities). + save(filename); + (+visu).__draw_text(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving EPS snapshot... ",false).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving SVG snapshot... ",false,13).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; +#endif + } + if (disp.is_resized()) { + disp.resize(false); visu0 = get_resize(disp,1); + if (zbuffer) zbuffer.assign(disp.width(),disp.height()); + redraw = true; + } + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + if (pose_matrix) { + std::memcpy(pose_matrix,pose._data,12*sizeof(float)); + pose_matrix[12] = Xoff; pose_matrix[13] = Yoff; pose_matrix[14] = Zoff; pose_matrix[15] = sprite_scale; + } + disp.set_button().set_key(key); + return *this; + } + + //! Display 1D graph in an interactive window. + /** + \param disp Display window. + \param plot_type Plot type. Can be { 0=points | 1=segments | 2=splines | 3=bars }. + \param vertex_type Vertex type. + \param labelx Title for the horizontal axis, as a C-string. + \param xmin Minimum value along the X-axis. + \param xmax Maximum value along the X-axis. + \param labely Title for the vertical axis, as a C-string. + \param ymin Minimum value along the X-axis. + \param ymax Maximum value along the X-axis. + \param exit_on_anykey Exit function when any key is pressed. + **/ + const CImg& display_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + return _display_graph(disp,0,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + //! Display 1D graph in an interactive window \overloading. + const CImg& display_graph(const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_graph(disp,title,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + const CImg& _display_graph(CImgDisplay &disp, const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "display_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title(title?"%s":"CImg<%s>",title?title:pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth, siz1 = std::max((ulongT)1,siz - 1); + const unsigned int old_normalization = disp.normalization(); + disp.show().flush()._normalization = 0; + + double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax; + if (nxmin==nxmax) { nxmin = 0; nxmax = siz1; } + int x0 = 0, x1 = width()*height()*depth() - 1, key = 0; + + for (bool reset_view = true; !key && !disp.is_closed(); ) { + if (reset_view) { x0 = 0; x1 = width()*height()*depth() - 1; y0 = ymin; y1 = ymax; reset_view = false; } + CImg zoom(x1 - x0 + 1,1,1,spectrum()); + cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg(data(x0,0,0,c),x1 - x0 + 1,1,1,1,true); + if (y0==y1) { y0 = zoom.min_max(y1); const double dy = y1 - y0; y0-=dy/20; y1+=dy/20; } + if (y0==y1) { --y0; ++y1; } + + const CImg selection = zoom.get_select_graph(disp,plot_type,vertex_type, + labelx, + nxmin + x0*(nxmax - nxmin)/siz1, + nxmin + x1*(nxmax - nxmin)/siz1, + labely,y0,y1,true); + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + if (selection[0]>=0) { + if (selection[2]<0) reset_view = true; + else { + x1 = x0 + selection[2]; x0+=selection[0]; + if (selection[1]>=0 && selection[3]>=0) { + y0 = y1 - selection[3]*(y1 - y0)/(disp.height() - 32); + y1-=selection[1]*(y1 - y0)/(disp.height() - 32); + } + } + } else { + bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false; + switch (key = (int)disp.key()) { + case cimg::keyHOME : reset_view = true; key = 0; disp.set_key(); break; + case cimg::keyPADADD : go_in = true; go_out = false; key = 0; disp.set_key(); break; + case cimg::keyPADSUB : go_out = true; go_in = false; key = 0; disp.set_key(); break; + case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; go_right = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; go_left = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; go_down = false; key = 0; disp.set_key(); break; + case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; go_up = false; key = 0; disp.set_key(); break; + case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; disp.set_key(); break; + case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; disp.set_key(); break; + } + if (disp.wheel()) { + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) go_up = !(go_down = disp.wheel()<0); + else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0); + else go_out = !(go_in = disp.wheel()>0); + key = 0; + } + + if (go_in) { + const int + xsiz = x1 - x0, + mx = (mouse_x - 16)*xsiz/(disp.width() - 32), + cx = x0 + cimg::cut(mx,0,xsiz); + if (x1 - x0>4) { + x0 = cx - 7*(cx - x0)/8; x1 = cx + 7*(x1 - cx)/8; + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + const double + ysiz = y1 - y0, + my = (mouse_y - 16)*ysiz/(disp.height() - 32), + cy = y1 - cimg::cut(my,0.,ysiz); + y0 = cy - 7*(cy - y0)/8; y1 = cy + 7*(y1 - cy)/8; + } else y0 = y1 = 0; + } + } + if (go_out) { + if (x0>0 || x1<(int)siz1) { + const int delta_x = (x1 - x0)/8, ndelta_x = delta_x?delta_x:(siz>1); + const double ndelta_y = (y1 - y0)/8; + x0-=ndelta_x; x1+=ndelta_x; + y0-=ndelta_y; y1+=ndelta_y; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz1; } + if (x1>=(int)siz) { x0-=(x1 - siz1); x1 = (int)siz1; if (x0<0) x0 = 0; } + } + } + if (go_left) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + go_left = false; + } + if (go_right) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x1 + ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; } + else { x0+=(siz1 - x1); x1 = (int)siz1; } + go_right = false; + } + if (go_up) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0+=ndelta; y1+=ndelta; + go_up = false; + } + if (go_down) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0-=ndelta; y1-=ndelta; + go_down = false; + } + } + if (!exit_on_anykey && key && key!=(int)cimg::keyESC && + (key!=(int)cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + key = 0; + } + } + disp._normalization = old_normalization; + return *this; + } + + //! Save image as a file. + /** + \param filename Filename, as a C-string. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + \note + - The used file format is defined by the file extension in the filename \p filename. + - Parameter \p number can be used to add a 6-digit number to the filename before saving. + + **/ + const CImg& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save(): Specified filename is (null).", + cimg_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:(number>=0)?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimg_save_plugin + cimg_save_plugin(fn); +#endif +#ifdef cimg_save_plugin1 + cimg_save_plugin1(fn); +#endif +#ifdef cimg_save_plugin2 + cimg_save_plugin2(fn); +#endif +#ifdef cimg_save_plugin3 + cimg_save_plugin3(fn); +#endif +#ifdef cimg_save_plugin4 + cimg_save_plugin4(fn); +#endif +#ifdef cimg_save_plugin5 + cimg_save_plugin5(fn); +#endif +#ifdef cimg_save_plugin6 + cimg_save_plugin6(fn); +#endif +#ifdef cimg_save_plugin7 + cimg_save_plugin7(fn); +#endif +#ifdef cimg_save_plugin8 + cimg_save_plugin8(fn); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) return save_dlm(fn); + else if (!cimg::strcasecmp(ext,"cpp") || + !cimg::strcasecmp(ext,"hpp") || + !cimg::strcasecmp(ext,"h") || + !cimg::strcasecmp(ext,"c")) return save_cpp(fn); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn); + else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn); + else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn); + else if (!cimg::strcasecmp(ext,"png")) return save_png(fn); + else if (!cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn); + else if (!cimg::strcasecmp(ext,"pnk")) return save_pnk(fn); + else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn); + else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) return save_analyze(fn); + else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn); + else if (!cimg::strcasecmp(ext,"mnc")) return save_minc2(fn); + else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn); + else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + + // Image sequences + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); + return save_other(fn); + } + + //! Save image as an Ascii file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_ascii(const char *const filename) const { + return _save_ascii(0,filename); + } + + //! Save image as an Ascii file \overloading. + const CImg& save_ascii(std::FILE *const file) const { + return _save_ascii(file,0); + } + + const CImg& _save_ascii(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_ascii(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + std::fprintf(nfile,"%u %u %u %u\n",_width,_height,_depth,_spectrum); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g ",(double)*(ptrs++)); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .cpp source file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_cpp(const char *const filename) const { + return _save_cpp(0,filename); + } + + //! Save image as a .cpp source file \overloading. + const CImg& save_cpp(std::FILE *const file) const { + return _save_cpp(file,0); + } + + const CImg& _save_cpp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_cpp(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + CImg varname(1024); *varname = 0; + if (filename) cimg_sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname._data); + if (!*varname) cimg_snprintf(varname,varname._width,"unnamed"); + std::fprintf(nfile, + "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n" + "%s data_%s[] = { %s\n ", + varname._data,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname._data, + is_empty()?"};":""); + if (!is_empty()) for (ulongT off = 0, siz = size() - 1; off<=siz; ++off) { + std::fprintf(nfile,cimg::type::format(),cimg::type::format((*this)[off])); + if (off==siz) std::fprintf(nfile," };\n"); + else if (!((off + 1)%16)) std::fprintf(nfile,",\n "); + else std::fprintf(nfile,", "); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a DLM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_dlm(const char *const filename) const { + return _save_dlm(0,filename); + } + + //! Save image as a DLM file \overloading. + const CImg& save_dlm(std::FILE *const file) const { + return _save_dlm(file,0); + } + + const CImg& _save_dlm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_dlm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is volumetric, values along Z will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is multispectral, values along C will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g%s",(double)*(ptrs++),(x==width() - 1)?"":","); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a BMP file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_bmp(const char *const filename) const { + return _save_bmp(0,filename); + } + + //! Save image as a BMP file \overloading. + const CImg& save_bmp(std::FILE *const file) const { + return _save_bmp(file,0); + } + + const CImg& _save_bmp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_bmp(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_bmp(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_bmp(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(54,1,1,1,0); + unsigned char align_buf[4] = { 0 }; + const unsigned int + align = (4 - (3*_width)%4)%4, + buf_size = (3*_width + align)*height(), + file_size = 54 + buf_size; + header[0] = 'B'; header[1] = 'M'; + header[0x02] = file_size&0xFF; + header[0x03] = (file_size>>8)&0xFF; + header[0x04] = (file_size>>16)&0xFF; + header[0x05] = (file_size>>24)&0xFF; + header[0x0A] = 0x36; + header[0x0E] = 0x28; + header[0x12] = _width&0xFF; + header[0x13] = (_width>>8)&0xFF; + header[0x14] = (_width>>16)&0xFF; + header[0x15] = (_width>>24)&0xFF; + header[0x16] = _height&0xFF; + header[0x17] = (_height>>8)&0xFF; + header[0x18] = (_height>>16)&0xFF; + header[0x19] = (_height>>24)&0xFF; + header[0x1A] = 1; + header[0x1B] = 0; + header[0x1C] = 24; + header[0x1D] = 0; + header[0x22] = buf_size&0xFF; + header[0x23] = (buf_size>>8)&0xFF; + header[0x24] = (buf_size>>16)&0xFF; + header[0x25] = (buf_size>>24)&0xFF; + header[0x27] = 0x1; + header[0x2B] = 0x1; + cimg::fwrite(header._data,54,nfile); + + const T + *ptr_r = data(0,_height - 1,0,0), + *ptr_g = (_spectrum>=2)?data(0,_height - 1,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,_height - 1,0,2):0; + + switch (_spectrum) { + case 1 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + const unsigned char val = (unsigned char)*(ptr_r++); + std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; + } + } break; + case 2 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc(0,nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; + } + } break; + default : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc((unsigned char)(*(ptr_b++)),nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; ptr_b-=2*_width; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a JPEG file. + /** + \param filename Filename, as a C-string. + \param quality Image quality (in %) + **/ + const CImg& save_jpeg(const char *const filename, const unsigned int quality=100) const { + return _save_jpeg(0,filename,quality); + } + + //! Save image as a JPEG file \overloading. + const CImg& save_jpeg(std::FILE *const file, const unsigned int quality=100) const { + return _save_jpeg(file,0,quality); + } + + const CImg& _save_jpeg(std::FILE *const file, const char *const filename, const unsigned int quality) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_jpeg(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_jpeg(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + +#ifndef cimg_use_jpeg + if (!file) return save_other(filename,quality); + else throw CImgIOException(_cimg_instance + "save_jpeg(): Unable to save data in '(*FILE)' unless libjpeg is enabled.", + cimg_instance); +#else + unsigned int dimbuf = 0; + J_COLOR_SPACE colortype = JCS_RGB; + + switch (_spectrum) { + case 1 : dimbuf = 1; colortype = JCS_GRAYSCALE; break; + case 2 : dimbuf = 3; colortype = JCS_RGB; break; + case 3 : dimbuf = 3; colortype = JCS_RGB; break; + default : dimbuf = 4; colortype = JCS_CMYK; break; + } + + // Call libjpeg functions + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + jpeg_stdio_dest(&cinfo,nfile); + cinfo.image_width = _width; + cinfo.image_height = _height; + cinfo.input_components = dimbuf; + cinfo.in_color_space = colortype; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE); + jpeg_start_compress(&cinfo,TRUE); + + JSAMPROW row_pointer[1]; + CImg buffer(_width*dimbuf); + + while (cinfo.next_scanline& save_magick(const char *const filename, const unsigned int bytes_per_pixel=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_magick(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_magick + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_magick(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_magick(): Instance is multispectral, only the three first channels will be " + "saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_magick(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + Magick::Image image(Magick::Geometry(_width,_height),"black"); + image.type(Magick::TrueColorType); + image.depth(bytes_per_pixel?(8*bytes_per_pixel):(stmax>=256?16:8)); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = _spectrum>1?data(0,0,0,1):0, + *ptr_b = _spectrum>2?data(0,0,0,2):0; + Magick::PixelPacket *pixels = image.getPixels(0,0,_width,_height); + switch (_spectrum) { + case 1 : // Scalar images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++); + ++pixels; + } + break; + case 2 : // RG images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = 0; ++pixels; + } + break; + default : // RGB images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = (Magick::Quantum)*(ptr_b++); + ++pixels; + } + } + image.syncPixels(); + image.write(filename); + return *this; +#else + cimg::unused(bytes_per_pixel); + throw CImgIOException(_cimg_instance + "save_magick(): Unable to save file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Save image as a PNG file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving, when possible. + **/ + const CImg& save_png(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_png(0,filename,bytes_per_pixel); + } + + //! Save image as a PNG file \overloading. + const CImg& save_png(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_png(file,0,bytes_per_pixel); + } + + const CImg& _save_png(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_png(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + +#ifndef cimg_use_png + cimg::unused(bytes_per_pixel); + if (!file) return save_other(filename); + else throw CImgIOException(_cimg_instance + "save_png(): Unable to save data in '(*FILE)' unless libpng is enabled.", + cimg_instance); +#else + +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb"); + volatile double stmin, stmax = (double)max_min(stmin); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"wb"); + double stmin, stmax = (double)max_min(stmin); +#endif + + if (_depth>1) + cimg::warn(_cimg_instance + "save_png(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>4) + cimg::warn(_cimg_instance + "save_png(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + // Setup PNG structures for write + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, + user_warning_fn); + if (!png_ptr){ + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'png_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr,(png_infopp)0); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'info_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + + const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8); + + int color_type; + switch (spectrum()) { + case 1 : color_type = PNG_COLOR_TYPE_GRAY; break; + case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; + case 3 : color_type = PNG_COLOR_TYPE_RGB; break; + default : color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + const int interlace_type = PNG_INTERLACE_NONE; + const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT; + const int filter_method = PNG_FILTER_TYPE_DEFAULT; + png_set_IHDR(png_ptr,info_ptr,_width,_height,bit_depth,color_type,interlace_type,compression_type,filter_method); + png_write_info(png_ptr,info_ptr); + const int byte_depth = bit_depth>>3; + const int numChan = spectrum()>4?4:spectrum(); + const int pixel_bit_depth_flag = numChan * (bit_depth - 1); + + // Allocate Memory for Image Save and Fill pixel data + png_bytep *const imgData = new png_byte*[_height]; + for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width]; + const T *pC0 = data(0,0,0,0); + switch (pixel_bit_depth_flag) { + case 7 : { // Gray 8-bit + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++); + } + } break; + case 14 : { // Gray w/ Alpha 8-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + } + } + } break; + case 21 : { // RGB 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + } + } + } break; + case 28 : { // RGB x/ Alpha 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y){ + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x){ + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + *(ptrd++) = (unsigned char)*(pC3++); + } + } + } break; + case 15 : { // Gray 16-bit + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++); + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width); + } + } break; + case 30 : { // Gray w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width); + } + } break; + case 45 : { // RGB 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width); + } + } break; + case 60 : { // RGB w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + *(ptrd++) = (unsigned short)*(pC3++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width); + } + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_write_image(png_ptr,imgData); + png_write_end(png_ptr,info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + // Deallocate Image Write Memory + cimg_forY(*this,n) delete[] imgData[n]; + delete[] imgData; + + if (!file) cimg::fclose(nfile); + return *this; +#endif + } + + //! Save image as a PNM file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving. + **/ + const CImg& save_pnm(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(0,filename,bytes_per_pixel); + } + + //! Save image as a PNM file \overloading. + const CImg& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(file,0,bytes_per_pixel); + } + + const CImg& _save_pnm(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_pnm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pnm(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_pnm(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + stmin,stmax,filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const ulongT buf_size = std::min((ulongT)(1024*1024),(ulongT)(_width*_height*(_spectrum==1?1UL:3UL))); + + std::fprintf(nfile,"P%c\n%u %u\n%u\n", + (_spectrum==1?'5':'6'),_width,_height,stmax<256?255:(stmax<4096?4095:65535)); + + switch (_spectrum) { + case 1 : { // Scalar image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PGM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr_r++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Binary PGM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned short)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + } break; + case 2 : { // RG image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = 0; + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } break; + default : { // RGB image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = (unsigned char)*(ptr_b++); + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = (unsigned short)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PNK file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pnk(const char *const filename) const { + return _save_pnk(0,filename); + } + + //! Save image as a PNK file \overloading. + const CImg& save_pnk(std::FILE *const file) const { + return _save_pnk(file,0); + } + + const CImg& _save_pnk(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnk(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_pnk(): Instance is multispectral, only the first channel will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + const ulongT buf_size = std::min((ulongT)1024*1024,(ulongT)_width*_height*_depth); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T *ptr = data(0,0,0,0); + + if (!cimg::type::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 + 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; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else if (!cimg::type::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); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + int *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (int)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Save as P9: Binary float-valued 3D + if (_depth>1) std::fprintf(nfile,"P9\n%u %u %u\n%g\n",_width,_height,_depth,(double)max()); + else std::fprintf(nfile,"P9\n%u %u\n%g\n",_width,_height,(double)max()); + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PFM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pfm(const char *const filename) const { + get_mirror('y')._save_pfm(0,filename); + return *this; + } + + //! Save image as a PFM file \overloading. + const CImg& save_pfm(std::FILE *const file) const { + get_mirror('y')._save_pfm(file,0); + return *this; + } + + const CImg& _save_pfm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pfm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_pfm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pfm(): image instance is multispectral, only the three first channels will be saved " + "in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const unsigned int buf_size = std::min(1024*1024U,_width*_height*(_spectrum==1?1:3)); + + std::fprintf(nfile,"P%c\n%u %u\n1.0\n", + (_spectrum==1?'f':'F'),_width,_height); + + switch (_spectrum) { + case 1 : { // Scalar image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,(ulongT)buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } break; + case 2 : { // RG image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } break; + default : { // RGB image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = (float)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a RGB file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_rgb(const char *const filename) const { + return _save_rgb(0,filename); + } + + //! Save image as a RGB file \overloading. + const CImg& save_rgb(std::FILE *const file) const { + return _save_rgb(file,0); + } + + const CImg& _save_rgb(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgb(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=3) + cimg::warn(_cimg_instance + "save_rgb(): image instance has not exactly 3 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0; + switch (_spectrum) { + case 1 : { // Scalar image + for (ulongT k = 0; k& save_rgba(const char *const filename) const { + return _save_rgba(0,filename); + } + + //! Save image as a RGBA file \overloading. + const CImg& save_rgba(std::FILE *const file) const { + return _save_rgba(file,0); + } + + const CImg& _save_rgba(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgba(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=4) + cimg::warn(_cimg_instance + "save_rgba(): image instance has not exactly 4 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[4*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0, + *ptr4 = _spectrum>3?data(0,0,0,3):0; + switch (_spectrum) { + case 1 : { // Scalar images + for (ulongT k = 0; k{ 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 use_bigtiff Allow to save big tiff files (>4Gb). + \note + - libtiff support is enabled by defining the precompilation + directive \c cimg_use_tif. + - When libtiff is enabled, 2D and 3D (multipage) several + channel per pixel are supported for + char,uchar,short,ushort,float and \c double pixel types. + - If \c cimg_use_tif 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) + throw CImgArgumentException(_cimg_instance + "save_tiff(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_tiff + const bool + _use_bigtiff = use_bigtiff && sizeof(ulongT)>=8 && size()*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + cimg_forZ(*this,z) _save_tiff(tif,z,z,compression_type,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "save_tiff(): Failed to open file '%s' for writing.", + cimg_instance, + filename); + return *this; +#else + cimg::unused(compression_type,voxel_size,description,use_bigtiff); + return save_other(filename); +#endif + } + +#ifdef cimg_use_tiff + +#define _cimg_save_tiff(types,typed,compression_type) if (!std::strcmp(types,pixel_type())) { \ + const typed foo = (typed)0; return _save_tiff(tif,directory,z,foo,compression_type,voxel_size,description); } + + // [internal] Save a plane into a tiff file + template + const CImg& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, const t& pixel_t, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + if (is_empty() || !tif || pixel_t) return *this; + const char *const filename = TIFFFileName(tif); + uint32 rowsperstrip = (uint32)-1; + uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric; + if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB; + else photometric = PHOTOMETRIC_MINISBLACK; + TIFFSetDirectory(tif,directory); + TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,_width); + TIFFSetField(tif,TIFFTAG_IMAGELENGTH,_height); + if (voxel_size) { + const float vx = voxel_size[0], vy = voxel_size[1], vz = voxel_size[2]; + TIFFSetField(tif,TIFFTAG_RESOLUTIONUNIT,RESUNIT_NONE); + TIFFSetField(tif,TIFFTAG_XRESOLUTION,1.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()); + } + if (description) TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,description); + TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT); + TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp); + if (cimg::type::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3); + else if (cimg::type::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1); + else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2); + double valm, valM = max_min(valm); + TIFFSetField(tif,TIFFTAG_SMINSAMPLEVALUE,valm); + TIFFSetField(tif,TIFFTAG_SMAXSAMPLEVALUE,valM); + TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp); + TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG); + TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric); + TIFFSetField(tif,TIFFTAG_COMPRESSION,compression_type==2?COMPRESSION_JPEG: + compression_type==1?COMPRESSION_LZW:COMPRESSION_NONE); + rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip); + TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip); + TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB); + TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg"); + + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + for (unsigned int row = 0; row<_height; row+=rowsperstrip) { + uint32 nrow = (row + rowsperstrip>_height?_height - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif,row,0); + tsize_t i = 0; + for (unsigned int rr = 0; rr& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + _cimg_save_tiff("bool",unsigned char,compression_type); + _cimg_save_tiff("unsigned char",unsigned char,compression_type); + _cimg_save_tiff("char",char,compression_type); + _cimg_save_tiff("unsigned short",unsigned short,compression_type); + _cimg_save_tiff("short",short,compression_type); + _cimg_save_tiff("unsigned int",unsigned int,compression_type); + _cimg_save_tiff("int",int,compression_type); + _cimg_save_tiff("unsigned int64",unsigned int,compression_type); + _cimg_save_tiff("int64",int,compression_type); + _cimg_save_tiff("float",float,compression_type); + _cimg_save_tiff("double",float,compression_type); + const char *const filename = TIFFFileName(tif); + throw CImgInstanceException(_cimg_instance + "save_tiff(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + return *this; + } +#endif + + //! Save image as a MINC2 file. + /** + \param filename Filename, as a C-string. + \param imitate_file If non-zero, reference filename, as a C-string, to borrow header from. + **/ + const CImg& save_minc2(const char *const filename, + const char *const imitate_file=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_minc2(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_minc2 + cimg::unused(imitate_file); + return save_other(filename); +#else + minc::minc_1_writer wtr; + if (imitate_file) + wtr.open(filename, imitate_file); + else { + minc::minc_info di; + if (width()) di.push_back(minc::dim_info(width(),width()*0.5,-1,minc::dim_info::DIM_X)); + if (height()) di.push_back(minc::dim_info(height(),height()*0.5,-1,minc::dim_info::DIM_Y)); + if (depth()) di.push_back(minc::dim_info(depth(),depth()*0.5,-1,minc::dim_info::DIM_Z)); + if (spectrum()) di.push_back(minc::dim_info(spectrum(),spectrum()*0.5,-1,minc::dim_info::DIM_TIME)); + wtr.open(filename,di,1,NC_FLOAT,0); + } + if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_byte(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_int(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_double(); + else + wtr.setup_write_float(); + minc::save_standard_volume(wtr, this->_data); + return *this; +#endif + } + + //! Save image as an ANALYZE7.5 or NIFTI file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 consecutive values that tell about the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_analyze(const char *const filename, const float *const voxel_size=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_analyze(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + std::FILE *file; + CImg hname(1024), iname(1024); + const char *const ext = cimg::split_filename(filename); + short datatype = -1; + if (!*ext) { + cimg_snprintf(hname,hname._width,"%s.hdr",filename); + cimg_snprintf(iname,iname._width,"%s.img",filename); + } + if (!cimg::strncasecmp(ext,"hdr",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(iname._data + std::strlen(iname) - 3,"img"); + } + if (!cimg::strncasecmp(ext,"img",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(hname._data + std::strlen(iname) - 3,"hdr"); + } + if (!cimg::strncasecmp(ext,"nii",3)) { + std::strncpy(hname,filename,hname._width - 1); *iname = 0; + } + + CImg header(*iname?348:352,1,1,1,0); + int *const iheader = (int*)header._data; + *iheader = 348; + std::strcpy(header._data + 4,"CImg"); + std::strcpy(header._data + 14," "); + ((short*)&(header[36]))[0] = 4096; + ((char*)&(header[38]))[0] = 114; + ((short*)&(header[40]))[0] = 4; + ((short*)&(header[40]))[1] = (short)_width; + ((short*)&(header[40]))[2] = (short)_height; + ((short*)&(header[40]))[3] = (short)_depth; + ((short*)&(header[40]))[4] = (short)_spectrum; + if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"unsigned int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16; + if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64; + if (datatype<0) + throw CImgIOException(_cimg_instance + "save_analyze(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename); + + ((short*)&(header[70]))[0] = datatype; + ((short*)&(header[72]))[0] = sizeof(T); + ((float*)&(header[108]))[0] = (float)(*iname?0:header.width()); + ((float*)&(header[112]))[0] = 1; + ((float*)&(header[76]))[0] = 0; + if (voxel_size) { + ((float*)&(header[76]))[1] = voxel_size[0]; + ((float*)&(header[76]))[2] = voxel_size[1]; + ((float*)&(header[76]))[3] = voxel_size[2]; + } else ((float*)&(header[76]))[1] = ((float*)&(header[76]))[2] = ((float*)&(header[76]))[3] = 1; + file = cimg::fopen(hname,"wb"); + cimg::fwrite(header._data,header.width(),file); + if (*iname) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); } + cimg::fwrite(_data,size(),file); + cimg::fclose(file); + return *this; + } + + //! Save image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param is_compressed Tells if the file contains compressed image data. + **/ + const CImg& save_cimg(const char *const filename, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(filename,is_compressed); + return *this; + } + + //! Save image as a .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(file,is_compressed); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Index of the image inside the file. + \param x0 X-coordinate of the sub-image location. + \param y0 Y-coordinate of the sub-image location. + \param z0 Z-coordinate of the sub-image location. + \param c0 C-coordinate of the sub-image location. + **/ + const CImg& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(filename,n0,x0,y0,z0,c0); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(file,n0,x0,y0,z0,c0); + return *this; + } + + //! Save blank image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param dx Width of the image. + \param dy Height of the image. + \param dz Depth of the image. + \param dc Number of channels of the image. + \note + - All pixel values of the saved image are set to \c 0. + - Use this method to save large images without having to instanciate and allocate them. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(filename,1,dx,dy,dz,dc); + } + + //! Save blank image as a .cimg file \overloading. + /** + Same as save_empty_cimg(const char *,unsigned int,unsigned int,unsigned int,unsigned int) + with a file stream argument instead of a filename string. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(file,1,dx,dy,dz,dc); + } + + //! Save image as an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 values specifying the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_inr(const char *const filename, const float *const voxel_size=0) const { + return _save_inr(0,filename,voxel_size); + } + + //! Save image as an INRIMAGE-4 file \overloading. + const CImg& save_inr(std::FILE *const file, const float *const voxel_size=0) const { + return _save_inr(file,0,voxel_size); + } + + const CImg& _save_inr(std::FILE *const file, const char *const filename, const float *const voxel_size) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_inr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + int inrpixsize = -1; + const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { + inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"char")) { + inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { + inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"short")) { + inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { + inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"int")) { + inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"float")) { + inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"double")) { + inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; + } + if (inrpixsize<=0) + throw CImgIOException(_cimg_instance + "save_inr(): Unsupported pixel type '%s' for file '%s'", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(257); + int err = cimg_snprintf(header,header._width,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n", + _width,_height,_depth,_spectrum); + if (voxel_size) err+=cimg_sprintf(header._data + err,"VX=%g\nVY=%g\nVZ=%g\n", + voxel_size[0],voxel_size[1],voxel_size[2]); + err+=cimg_sprintf(header._data + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm"); + std::memset(header._data + err,'\n',252 - err); + std::memcpy(header._data + 252,"##}\n",4); + cimg::fwrite(header._data,256,nfile); + cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) cimg::fwrite(&((*this)(x,y,z,c)),1,nfile); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as an OpenEXR file. + /** + \param filename Filename, as a C-string. + \note The OpenEXR file format is described here. + **/ + const CImg& save_exr(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_exr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_exr(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + +#ifndef cimg_use_openexr + return save_other(filename); +#else + Imf::Rgba *const ptrd0 = new Imf::Rgba[(size_t)_width*_height], *ptrd = ptrd0, rgba; + switch (_spectrum) { + case 1 : { // Grayscale image + for (const T *ptr_r = data(), *const ptr_e = ptr_r + (ulongT)_width*_height; ptr_rPandore file specifications + for more information). + **/ + const CImg& save_pandore(const char *const filename, const unsigned int colorspace=0) const { + return _save_pandore(0,filename,colorspace); + } + + //! Save image as a Pandore-5 file \overloading. + /** + Same as save_pandore(const char *,unsigned int) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_pandore(std::FILE *const file, const unsigned int colorspace=0) const { + return _save_pandore(file,0,colorspace); + } + + unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const { + unsigned int nbdims = 0; + if (id==2 || id==3 || id==4) { + dims[0] = 1; dims[1] = _width; nbdims = 2; + } + if (id==5 || id==6 || id==7) { + dims[0] = 1; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==8 || id==9 || id==10) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + if (id==16 || id==17 || id==18) { + dims[0] = 3; dims[1] = _height; dims[2] = _width; dims[3] = colorspace; nbdims = 4; + } + if (id==19 || id==20 || id==21) { + dims[0] = 3; dims[1] = _depth; dims[2] = _height; dims[3] = _width; dims[4] = colorspace; nbdims = 5; + } + if (id==22 || id==23 || id==25) { + dims[0] = _spectrum; dims[1] = _width; nbdims = 2; + } + if (id==26 || id==27 || id==29) { + dims[0] = _spectrum; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==30 || id==31 || id==33) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + return nbdims; + } + + const CImg& _save_pandore(std::FILE *const file, const char *const filename, + const unsigned int colorspace) const { + +#define __cimg_save_pandore_case(dtype) \ + dtype *buffer = new dtype[size()]; \ + const T *ptrs = _data; \ + cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \ + buffer-=size(); \ + cimg::fwrite(buffer,size(),nfile); \ + delete[] buffer + +#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \ + if (!saved && (sy?(sy==_height):true) && (sz?(sz==_depth):true) && \ + (sv?(sv==_spectrum):true) && !std::strcmp(stype,pixel_type())) { \ + unsigned int *iheader = (unsigned int*)(header + 12); \ + nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \ + cimg::fwrite(header,36,nfile); \ + if (sizeof(unsigned long)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned int)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned short)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \ + __cimg_save_pandore_case(unsigned char); \ + } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \ + if (sizeof(unsigned long)==4) { __cimg_save_pandore_case(unsigned long); } \ + else if (sizeof(unsigned int)==4) { __cimg_save_pandore_case(unsigned int); } \ + else if (sizeof(unsigned short)==4) { __cimg_save_pandore_case(unsigned short); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \ + if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \ + else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pandore(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0, + 0,0,0,0,'C','I','m','g',0,0,0,0,0, + 'N','o',' ','d','a','t','e',0,0,0,0 }; + unsigned int nbdims, dims[5] = { 0 }; + bool saved = false; + _cimg_save_pandore_case(1,1,1,"unsigned char",2); + _cimg_save_pandore_case(1,1,1,"char",3); + _cimg_save_pandore_case(1,1,1,"unsigned short",3); + _cimg_save_pandore_case(1,1,1,"short",3); + _cimg_save_pandore_case(1,1,1,"unsigned int",3); + _cimg_save_pandore_case(1,1,1,"int",3); + _cimg_save_pandore_case(1,1,1,"unsigned int64",3); + _cimg_save_pandore_case(1,1,1,"int64",3); + _cimg_save_pandore_case(1,1,1,"float",4); + _cimg_save_pandore_case(1,1,1,"double",4); + + _cimg_save_pandore_case(0,1,1,"unsigned char",5); + _cimg_save_pandore_case(0,1,1,"char",6); + _cimg_save_pandore_case(0,1,1,"unsigned short",6); + _cimg_save_pandore_case(0,1,1,"short",6); + _cimg_save_pandore_case(0,1,1,"unsigned int",6); + _cimg_save_pandore_case(0,1,1,"int",6); + _cimg_save_pandore_case(0,1,1,"unsigned int64",6); + _cimg_save_pandore_case(0,1,1,"int64",6); + _cimg_save_pandore_case(0,1,1,"float",7); + _cimg_save_pandore_case(0,1,1,"double",7); + + _cimg_save_pandore_case(0,0,1,"unsigned char",8); + _cimg_save_pandore_case(0,0,1,"char",9); + _cimg_save_pandore_case(0,0,1,"unsigned short",9); + _cimg_save_pandore_case(0,0,1,"short",9); + _cimg_save_pandore_case(0,0,1,"unsigned int",9); + _cimg_save_pandore_case(0,0,1,"int",9); + _cimg_save_pandore_case(0,0,1,"unsigned int64",9); + _cimg_save_pandore_case(0,0,1,"int64",9); + _cimg_save_pandore_case(0,0,1,"float",10); + _cimg_save_pandore_case(0,0,1,"double",10); + + _cimg_save_pandore_case(0,1,3,"unsigned char",16); + _cimg_save_pandore_case(0,1,3,"char",17); + _cimg_save_pandore_case(0,1,3,"unsigned short",17); + _cimg_save_pandore_case(0,1,3,"short",17); + _cimg_save_pandore_case(0,1,3,"unsigned int",17); + _cimg_save_pandore_case(0,1,3,"int",17); + _cimg_save_pandore_case(0,1,3,"unsigned int64",17); + _cimg_save_pandore_case(0,1,3,"int64",17); + _cimg_save_pandore_case(0,1,3,"float",18); + _cimg_save_pandore_case(0,1,3,"double",18); + + _cimg_save_pandore_case(0,0,3,"unsigned char",19); + _cimg_save_pandore_case(0,0,3,"char",20); + _cimg_save_pandore_case(0,0,3,"unsigned short",20); + _cimg_save_pandore_case(0,0,3,"short",20); + _cimg_save_pandore_case(0,0,3,"unsigned int",20); + _cimg_save_pandore_case(0,0,3,"int",20); + _cimg_save_pandore_case(0,0,3,"unsigned int64",20); + _cimg_save_pandore_case(0,0,3,"int64",20); + _cimg_save_pandore_case(0,0,3,"float",21); + _cimg_save_pandore_case(0,0,3,"double",21); + + _cimg_save_pandore_case(1,1,0,"unsigned char",22); + _cimg_save_pandore_case(1,1,0,"char",23); + _cimg_save_pandore_case(1,1,0,"unsigned short",23); + _cimg_save_pandore_case(1,1,0,"short",23); + _cimg_save_pandore_case(1,1,0,"unsigned int",23); + _cimg_save_pandore_case(1,1,0,"int",23); + _cimg_save_pandore_case(1,1,0,"unsigned int64",23); + _cimg_save_pandore_case(1,1,0,"int64",23); + _cimg_save_pandore_case(1,1,0,"float",25); + _cimg_save_pandore_case(1,1,0,"double",25); + + _cimg_save_pandore_case(0,1,0,"unsigned char",26); + _cimg_save_pandore_case(0,1,0,"char",27); + _cimg_save_pandore_case(0,1,0,"unsigned short",27); + _cimg_save_pandore_case(0,1,0,"short",27); + _cimg_save_pandore_case(0,1,0,"unsigned int",27); + _cimg_save_pandore_case(0,1,0,"int",27); + _cimg_save_pandore_case(0,1,0,"unsigned int64",27); + _cimg_save_pandore_case(0,1,0,"int64",27); + _cimg_save_pandore_case(0,1,0,"float",29); + _cimg_save_pandore_case(0,1,0,"double",29); + + _cimg_save_pandore_case(0,0,0,"unsigned char",30); + _cimg_save_pandore_case(0,0,0,"char",31); + _cimg_save_pandore_case(0,0,0,"unsigned short",31); + _cimg_save_pandore_case(0,0,0,"short",31); + _cimg_save_pandore_case(0,0,0,"unsigned int",31); + _cimg_save_pandore_case(0,0,0,"int",31); + _cimg_save_pandore_case(0,0,0,"unsigned int64",31); + _cimg_save_pandore_case(0,0,0,"int64",31); + _cimg_save_pandore_case(0,0,0,"float",33); + _cimg_save_pandore_case(0,0,0,"double",33); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a raw data file. + /** + \param filename Filename, as a C-string. + \param is_multiplexed Tells if the image channels are stored in a multiplexed way (\c true) or not (\c false). + \note The .raw format does not store the image dimensions in the output file, + so you have to keep track of them somewhere to be able to read the file correctly afterwards. + **/ + const CImg& save_raw(const char *const filename, const bool is_multiplexed=false) const { + return _save_raw(0,filename,is_multiplexed); + } + + //! Save image as a raw data file \overloading. + /** + Same as save_raw(const char *,bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_raw(std::FILE *const file, const bool is_multiplexed=false) const { + return _save_raw(file,0,is_multiplexed); + } + + const CImg& _save_raw(std::FILE *const file, const char *const filename, const bool is_multiplexed) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_raw(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!is_multiplexed) cimg::fwrite(_data,size(),nfile); + else { + CImg buf(_spectrum); + cimg_forXYZ(*this,x,y,z) { + cimg_forC(*this,c) buf[c] = (*this)(x,y,z,c); + cimg::fwrite(buf._data,_spectrum,nfile); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .yuv video file. + /** + \param filename Filename, as a C-string. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if pixel values of the instance image are RGB-coded (\c true) or YUV-coded (\c false). + \note Each slice of the instance image is considered to be a single frame of the output video file. + **/ + const CImg& save_yuv(const char *const filename, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(filename,chroma_subsampling,is_rgb); + return *this; + } + + //! Save image as a .yuv video file \overloading. + /** + Same as save_yuv(const char*,const unsigned int,const bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(file,chroma_subsampling,is_rgb); + return *this; + } + + //! Save 3D object as an Object File Format (.off) file. + /** + \param filename Filename, as a C-string. + \param primitives List of 3D object primitives. + \param colors List of 3D object colors. + \note + - Instance image contains the vertices data of the 3D object. + - Textured, transparent or sphere-shaped primitives cannot be managed by the .off file format. + Such primitives will be lost or simplified during file saving. + - The .off file format is described here. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + const char *const filename) const { + return _save_off(primitives,colors,0,filename); + } + + //! Save 3D object as an Object File Format (.off) file \overloading. + /** + Same as save_off(const CImgList&,const CImgList&,const char*) const + with a file stream argument instead of a filename string. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file) const { + return _save_off(primitives,colors,file,0); + } + + template + const CImg& _save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_off(): Specified filename is (null).", + cimg_instance); + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "save_off(): Empty instance, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + CImgList opacities; + 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).", + cimg_instance, + filename?filename:"(FILE*)",error_message.data()); + + const CImg default_color(1,3,1,1,200); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + unsigned int supported_primitives = 0; + cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives; + std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width); + cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n", + (float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2))); + cimglist_for(primitives,l) { + const CImg& color = l1?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; + case 2 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 3 : std::fprintf(nfile,"3 %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,2), + (unsigned int)primitives(l,1),r,g,b); break; + case 4 : std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,3), + (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); break; + case 5 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 6 : { + const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3); + const float + rt = color.atXY(xt,yt,0)/255.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.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); + } break; + case 12 : { + const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5); + const float + rt = color.atXY(xt,yt,0)/255.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); + } break; + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save volumetric image as a video, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImg& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { + if (is_empty()) { CImgList().save_video(filename,fps,codec,keep_open); return *this; } + CImgList list; + get_split('z').move_to(list); + list.save_video(filename,fps,codec,keep_open); + return *this; + } + + //! Save volumetric image as a video, using ffmpeg external binary. + /** + \param filename Filename, as a C-string. + \param fps Video framerate. + \param codec Video codec, as a C-string. + \param bitrate Video bitrate. + \note + - Each slice of the instance image is considered to be a single frame of the output video file. + - This method uses \c ffmpeg, an external executable binary provided by + FFmpeg. + It must be installed for the method to succeed. + **/ + const CImg& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImgList list; + get_split('z').move_to(list); + list.save_ffmpeg_external(filename,fps,codec,bitrate); + return *this; + } + + //! Save image using gzip external binary. + /** + \param filename Filename, as a C-string. + \note This method uses \c gzip, an external executable binary provided by + gzip. + It must be installed for the method to succeed. + **/ + const CImg& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_gzip_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimg_instance, + filename); + + else cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using GraphicsMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c gm, an external executable binary provided by + GraphicsMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + +#ifdef cimg_use_png +#define _cimg_sge_ext1 "png" +#define _cimg_sge_ext2 "png" +#else +#define _cimg_sge_ext1 "pgm" +#define _cimg_sge_ext2 "ppm" +#endif + CImg 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=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::graphicsmagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_graphicsmagick_external(): Failed to save file '%s' with external command 'gm'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using ImageMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c convert, an external executable binary provided by + ImageMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_imagemagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick only writes the first image slice.", + cimg_instance,filename); +#ifdef cimg_use_png +#define _cimg_sie_ext1 "png" +#define _cimg_sie_ext2 "png" +#else +#define _cimg_sie_ext1 "pgm" +#define _cimg_sie_ext2 "ppm" +#endif + CImg 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=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::imagemagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_imagemagick_external(): Failed to save file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image as a Dicom file. + /** + \param filename Filename, as a C-string. + \note This method uses \c medcon, an external executable binary provided by + (X)Medcon. + It must be installed for the method to succeed. + **/ + const CImg& save_medcon_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_medcon_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + std::FILE *file; + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand()); + 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::medcon_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command); + std::remove(filename_tmp); + cimg::split_filename(filename_tmp,body); + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.img",body._data); + std::remove(filename_tmp); + + file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s",filename); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimg_instance + "save_medcon_external(): Failed to save file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + std::rename(command,filename); + return *this; + } + + // Save image for non natively supported formats. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note + - The filename extension tells about the desired file format. + - This method tries to save the instance image as a file, using external tools from + ImageMagick or + GraphicsMagick. + At least one of these tool must be installed for the method to succeed. + - It is recommended to use the generic method save(const char*, int) const instead, + as it can handle some file formats natively. + **/ + const CImg& save_other(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_other(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick or GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + + const unsigned int omode = cimg::exception_mode(); + bool is_saved = true; + cimg::exception_mode(0); + try { save_magick(filename); } + catch (CImgException&) { + try { save_imagemagick_external(filename,quality); } + catch (CImgException&) { + try { save_graphicsmagick_external(filename,quality); } + catch (CImgException&) { + is_saved = false; + } + } + } + cimg::exception_mode(omode); + if (!is_saved) + throw CImgIOException(_cimg_instance + "save_other(): Failed to save file '%s'. Format is not natively supported, " + "and no external commands succeeded.", + cimg_instance, + filename); + return *this; + } + + //! Serialize a CImg instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { + return CImgList(*this,true).get_serialize(is_compressed); + } + + // [internal] Return a 40x38 color logo of a 'danger' item. + static CImg _logo40x38() { + CImg res(40,38,1,3); + const unsigned char *ptrs = cimg::logo40x38; + T *ptr1 = res.data(0,0,0,0), *ptr2 = res.data(0,0,0,1), *ptr3 = res.data(0,0,0,2); + for (ulongT off = 0; off<(ulongT)res._width*res._height;) { + const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++); + for (unsigned int l = 0; l structure + # + # + # + #------------------------------------------ + */ + //! Represent a list of images CImg. + template + struct CImgList { + unsigned int _width, _allocated_width; + CImg *_data; + + //! Simple iterator type, to loop through each image of a list. + /** + \note + - 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 + for (CImgList<>::iterator it = list.begin(); it* iterator; + + //! Simple const iterator type, to loop through each image of a \c const list instance. + /** + \note + - The \c CImgList::const_iterator type is defined to be a const CImg*. + - Similar to CImgList::iterator, but for constant list instances. + **/ + typedef const CImg* const_iterator; + + //! Pixel value type. + /** + Refer to the pixels value type of the images in the list. + \note + - The \c CImgList::value_type type of a \c CImgList is defined to be a \c T. + It is then similar to CImg::value_type. + - \c CImgList::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimglist_plugin +#include cimglist_plugin +#endif +#ifdef cimglist_plugin1 +#include cimglist_plugin1 +#endif +#ifdef cimglist_plugin2 +#include cimglist_plugin2 +#endif +#ifdef cimglist_plugin3 +#include cimglist_plugin3 +#endif +#ifdef cimglist_plugin4 +#include cimglist_plugin4 +#endif +#ifdef cimglist_plugin5 +#include cimglist_plugin5 +#endif +#ifdef cimglist_plugin6 +#include cimglist_plugin6 +#endif +#ifdef cimglist_plugin7 +#include cimglist_plugin7 +#endif +#ifdef cimglist_plugin8 +#include cimglist_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + Destroy current list instance. + \note + - Any allocated buffer is deallocated. + - Destroying an empty list does nothing actually. + **/ + ~CImgList() { + delete[] _data; + } + + //! Default constructor. + /** + Construct a new empty list instance. + \note + - An empty list has no pixel data and its dimension width() is set to \c 0, as well as its + image buffer pointer data(). + - An empty list may be reassigned afterwards, with the family of the assign() methods. + In all cases, the type of pixels stays \c T. + **/ + CImgList(): + _width(0),_allocated_width(0),_data(0) {} + + //! Construct list containing empty images. + /** + \param n Number of empty images. + \note Useful when you know by advance the number of images you want to manage, as + it will allocate the right amount of memory for the list, without needs for reallocation + (that may occur when starting from an empty list and inserting several images in it). + **/ + explicit CImgList(const unsigned int n):_width(n) { + if (n) _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + else { _allocated_width = 0; _data = 0; } + } + + //! Construct list containing images of specified size. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \note Pixel values are not initialized and may probably contain garbage. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + } + + //! Construct list containing images of specified size, and initialize pixel values. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val Initialization value for images pixels. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + } + + //! Construct list containing images of specified size, and initialize pixel values from a sequence of integers. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val0 First value of the initializing integers sequence. + \param val1 Second value of the initializing integers sequence. + \warning You must specify at least width*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...): + _width(0),_allocated_width(0),_data(0) { +#define _CImgList_stdarg(t) { \ + assign(n,width,height,depth,spectrum); \ + const ulongT siz = (ulongT)width*height*depth*spectrum, nsiz = siz*n; \ + T *ptrd = _data->_data; \ + va_list ap; \ + va_start(ap,val1); \ + for (ulongT l = 0, s = 0, i = 0; iwidth*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const double val0, const double val1, ...): + _width(0),_allocated_width(0),_data(0) { + _CImgList_stdarg(double); + } + + //! Construct list containing copies of an input image. + /** + \param n Number of images. + \param img Input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of \c img. + **/ + template + CImgList(const unsigned int n, const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + } + + //! Construct list from one image. + /** + \param img Input image to copy in the constructed list. + \param is_shared Tells if the element of the list is a shared or non-shared copy of \c img. + **/ + template + explicit CImgList(const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(1); + _data[0].assign(img,is_shared); + } + + //! Construct list from two images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + } + + //! Construct list from three images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + } + + //! Construct list from four images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + } + + //! Construct list from five images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + } + + //! Construct list from six images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + } + + //! Construct list from seven images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + } + + //! Construct list from eight images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param img8 Eighth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + } + + //! Construct list copy. + /** + \param list Input list to copy. + \note The shared state of each element of the constructed list is kept the same as in \c list. + **/ + template + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + } + + //! Construct list copy \specialization. + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared); + } + + //! Construct list copy, and force the shared state of the list elements. + /** + \param list Input list to copy. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImgList& list, const bool is_shared):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],is_shared); + } + + //! Construct list by reading the content of a file. + /** + \param filename Filename, as a C-string. + **/ + explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) { + assign(filename); + } + + //! Construct list from the content of a display window. + /** + \param disp Display window to get content from. + \note Constructed list contains a single image only. + **/ + explicit CImgList(const CImgDisplay& disp):_width(0),_allocated_width(0),_data(0) { + assign(disp); + } + + //! Return a list with elements being shared copies of images in the list instance. + /** + \note list2 = list1.get_shared() is equivalent to list2.assign(list1,true). + **/ + CImgList get_shared() { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Return a list with elements being shared copies of images in the list instance \const. + const CImgList get_shared() const { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Destructor \inplace. + /** + \see CImgList(). + **/ + CImgList& assign() { + delete[] _data; + _width = _allocated_width = 0; + _data = 0; + return *this; + } + + //! Destructor \inplace. + /** + Equivalent to assign(). + \note Only here for compatibility with STL naming conventions. + **/ + CImgList& clear() { + return assign(); + } + + //! Construct list containing empty images \inplace. + /** + \see CImgList(unsigned int). + **/ + CImgList& assign(const unsigned int n) { + if (!n) return assign(); + if (_allocated_width(n<<2)) { + delete[] _data; + _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + } + _width = n; + return *this; + } + + //! Construct list containing images of specified size \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + return *this; + } + + //! Construct list containing images of specified size, and initialize pixel values \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const T). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of integers \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const int, const int, ...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) { + _CImgList_stdarg(int); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of doubles \inplace. + /** + \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const double,const double,...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, + const double val0, const double val1, ...) { + _CImgList_stdarg(double); + return *this; + } + + //! Construct list containing copies of an input image \inplace. + /** + \see CImgList(unsigned int, const CImg&, bool). + **/ + template + CImgList& assign(const unsigned int n, const CImg& img, const bool is_shared=false) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + return *this; + } + + //! Construct list from one image \inplace. + /** + \see CImgList(const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img, const bool is_shared=false) { + assign(1); + _data[0].assign(img,is_shared); + return *this; + } + + //! Construct list from two images \inplace. + /** + \see CImgList(const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const bool is_shared=false) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + return *this; + } + + //! Construct list from three images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + return *this; + } + + //! Construct list from four images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + return *this; + } + + //! Construct list from five images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + return *this; + } + + //! Construct list from six images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + return *this; + } + + //! Construct list from seven images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + return *this; + } + + //! Construct list from eight images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + return *this; + } + + //! Construct list as a copy of an existing list and force the shared state of the list elements \inplace. + /** + \see CImgList(const CImgList&, bool is_shared). + **/ + template + CImgList& assign(const CImgList& list, const bool is_shared=false) { + cimg::unused(is_shared); + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + return *this; + } + + //! Construct list as a copy of an existing list and force shared state of elements \inplace \specialization. + CImgList& assign(const CImgList& list, const bool is_shared=false) { + if (this==&list) return *this; + CImgList res(list._width); + cimglist_for(res,l) res[l].assign(list[l],is_shared); + return res.move_to(*this); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& assign(const char *const filename) { + return load(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& assign(const CImgDisplay &disp) { + return assign(CImg(disp)); + } + + //! Transfer the content of the list instance to another list. + /** + \param list Destination list. + \note When returning, the current list instance is empty and the initial content of \c list is destroyed. + **/ + template + CImgList& move_to(CImgList& list) { + list.assign(_width); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[l]); + assign(); + return list; + } + + //! Transfer the content of the list instance at a specified position in another list. + /** + \param list Destination list. + \param pos Index of the insertion in the list. + \note When returning, the list instance is empty and the initial content of \c list is preserved + (only images indexes may be modified). + **/ + template + CImgList& move_to(CImgList& list, const unsigned int pos) { + if (is_empty()) return list; + const unsigned int npos = pos>list._width?list._width:pos; + list.insert(_width,npos); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[npos + l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[npos + l]); + assign(); + return list; + } + + //! Swap all fields between two list instances. + /** + \param list List to swap fields with. + \note Can be used to exchange the content of two lists in a fast way. + **/ + CImgList& swap(CImgList& list) { + cimg::swap(_width,list._width,_allocated_width,list._allocated_width); + cimg::swap(_data,list._data); + return list; + } + + //! Return a reference to an empty list. + /** + \note Can be used to define default values in a function taking a CImgList as an argument. + \code + void f(const CImgList& list=CImgList::empty()); + \endcode + **/ + static CImgList& empty() { + static CImgList _empty; + return _empty.assign(); + } + + //! Return a reference to an empty list \const. + static const CImgList& const_empty() { + static const CImgList _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Return a reference to one image element of the list. + /** + \param pos Indice of the image element. + **/ + CImg& operator()(const unsigned int pos) { +#if cimg_verbosity>=3 + if (pos>=_width) { + cimg::warn(_cimglist_instance + "operator(): Invalid image request, at position [%u].", + cimglist_instance, + pos); + return *_data; + } +#endif + return _data[pos]; + } + + //! Return a reference to one image of the list. + /** + \param pos Indice of the image element. + **/ + const CImg& operator()(const unsigned int pos) const { + return const_cast*>(this)->operator()(pos); + } + + //! Return a reference to one pixel value of one image of the list. + /** + \param pos Indice of the image element. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note list(n,x,y,z,c) is equivalent to list[n](x,y,z,c). + **/ + T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + return (*this)[pos](x,y,z,c); + } + + //! Return a reference to one pixel value of one image of the list \const. + const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return (*this)[pos](x,y,z,c); + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + operator CImg*() { + return _data; + } + + //! Return pointer to the first image of the list \const. + operator const CImg*() const { + return _data; + } + + //! Construct list from one image \inplace. + /** + \param img Input image to copy in the constructed list. + \note list = img; is equivalent to list.assign(img);. + **/ + template + CImgList& operator=(const CImg& img) { + return assign(img); + } + + //! Construct list from another list. + /** + \param list Input list to copy. + \note list1 = list2 is equivalent to list1.assign(list2);. + **/ + template + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list from another list \specialization. + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& operator=(const char *const filename) { + return assign(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return a non-shared copy of a list. + /** + \note +list is equivalent to CImgList(list,false). + It forces the copy to have non-shared elements. + **/ + CImgList operator+() const { + return CImgList(*this,false); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end. + /** + \param img Image inserted at the end of the instance copy. + \note Define a convenient way to create temporary lists of images, as in the following code: + \code + (img1,img2,img3,img4).display("My four images"); + \endcode + **/ + template + CImgList& operator,(const CImg& img) { + return insert(img); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end \const. + template + CImgList operator,(const CImg& img) const { + return (+*this).insert(img); + } + + //! Return a copy of the list instance, where all elements of input list \c list have been inserted at the end. + /** + \param list List inserted at the end of the instance copy. + **/ + template + CImgList& operator,(const CImgList& list) { + return insert(list); + } + + //! Return a copy of the list instance, where all elements of input \c list have been inserted at the end \const. + template + CImgList& operator,(const CImgList& list) const { + return (+*this).insert(list); + } + + //! Return image corresponding to the appending of all images of the instance list along specified axis. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \note list>'x' is equivalent to list.get_append('x'). + **/ + CImg operator>(const char axis) const { + return get_append(axis,0); + } + + //! Return list corresponding to the splitting of all images of the instance list along specified axis. + /** + \param axis Axis used for image splitting. + \note list<'x' is equivalent to list.get_split('x'). + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to size() but returns result as a (signed) integer. + **/ + int width() const { + return (int)_width; + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to width() but returns result as an unsigned integer. + **/ + unsigned int size() const { + return _width; + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + CImg *data() { + return _data; + } + + //! Return pointer to the first image of the list \const. + const CImg *data() const { + return _data; + } + + //! Return pointer to the pos-th image of the list. + /** + \param pos Indice of the image element to access. + \note list.data(n); is equivalent to list.data + n;. + **/ +#if cimg_verbosity>=3 + CImg *data(const unsigned int pos) { + if (pos>=size()) + cimg::warn(_cimglist_instance + "data(): Invalid pointer request, at position [%u].", + cimglist_instance, + pos); + return _data + pos; + } + + const CImg *data(const unsigned int l) const { + return const_cast*>(this)->data(l); + } +#else + CImg *data(const unsigned int l) { + return _data + l; + } + + //! Return pointer to the pos-th image of the list \const. + const CImg *data(const unsigned int l) const { + return _data + l; + } +#endif + + //! Return iterator to the first image of the list. + /** + **/ + iterator begin() { + return _data; + } + + //! Return iterator to the first image of the list \const. + const_iterator begin() const { + return _data; + } + + //! Return iterator to one position after the last image of the list. + /** + **/ + iterator end() { + return _data + _width; + } + + //! Return iterator to one position after the last image of the list \const. + const_iterator end() const { + return _data + _width; + } + + //! Return reference to the first image of the list. + /** + **/ + CImg& front() { + return *_data; + } + + //! Return reference to the first image of the list \const. + const CImg& front() const { + return *_data; + } + + //! Return a reference to the last image of the list. + /** + **/ + const CImg& back() const { + return *(_data + _width - 1); + } + + //! Return a reference to the last image of the list \const. + CImg& back() { + return *(_data + _width - 1); + } + + //! Return pos-th image of the list. + /** + \param pos Indice of the image element to access. + **/ + CImg& at(const int pos) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "at(): Empty instance.", + cimglist_instance); + + return _data[cimg::cut(pos,0,width() - 1)]; + } + + //! Access to pixel value with Dirichlet boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZC(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + T _atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZ(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + T _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXY(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + T _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x) \const. + T atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atX(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x) \const. + T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + T _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:(*this)(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Return \c true if list is empty. + /** + **/ + bool is_empty() const { + return (!_data || !_width); + } + + //! Test if number of image elements is equal to specified value. + /** + \param size_n Number of image elements to test. + **/ + bool is_sameN(const unsigned int size_n) const { + return _width==size_n; + } + + //! Test if number of image elements is equal between two images lists. + /** + \param list Input list to compare with. + **/ + template + bool is_sameN(const CImgList& list) const { + return is_sameN(list._width); + } + + // Define useful functions to check list dimensions. + // (cannot be documented because macro-generated). +#define _cimglist_def_is_same1(axis) \ + bool is_same##axis(const unsigned int val) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(val); return res; \ + } \ + bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \ + return is_sameN(n) && is_same##axis(val); \ + } \ + +#define _cimglist_def_is_same2(axis1,axis2) \ + bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2(val1,val2); return res; \ + } \ + bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \ + return is_sameN(n) && is_same##axis1##axis2(val1,val2); \ + } \ + +#define _cimglist_def_is_same3(axis1,axis2,axis3) \ + bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, \ + const unsigned int val3) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2##axis3(val1,val2,val3); \ + return res; \ + } \ + bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, \ + const unsigned int val2, const unsigned int val3) const { \ + return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \ + } \ + +#define _cimglist_def_is_same(axis) \ + template 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; \ + } \ + 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)); \ + } \ + template bool is_sameN##axis(const CImgList& list) const { \ + return (is_sameN(list) && is_same##axis(list)); \ + } + + _cimglist_def_is_same(XY) + _cimglist_def_is_same(XZ) + _cimglist_def_is_same(XC) + _cimglist_def_is_same(YZ) + _cimglist_def_is_same(YC) + _cimglist_def_is_same(XYZ) + _cimglist_def_is_same(XYC) + _cimglist_def_is_same(YZC) + _cimglist_def_is_same(XYZC) + _cimglist_def_is_same1(X) + _cimglist_def_is_same1(Y) + _cimglist_def_is_same1(Z) + _cimglist_def_is_same1(C) + _cimglist_def_is_same2(X,Y) + _cimglist_def_is_same2(X,Z) + _cimglist_def_is_same2(X,C) + _cimglist_def_is_same2(Y,Z) + _cimglist_def_is_same2(Y,C) + _cimglist_def_is_same2(Z,C) + _cimglist_def_is_same3(X,Y,Z) + _cimglist_def_is_same3(X,Y,C) + _cimglist_def_is_same3(X,Z,C) + _cimglist_def_is_same3(Y,Z,C) + + //! Test if dimensions of each image of the list match specified arguments. + /** + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameXYZC(const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + bool res = true; + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_sameXYZC(dx,dy,dz,dc); + return res; + } + + //! Test if list dimensions match specified arguments. + /** + \param n Number of images in the list. + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameNXYZC(const unsigned int n, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + return is_sameN(n) && is_sameXYZC(dx,dy,dz,dc); + } + + //! Test if list contains one particular pixel location. + /** + \param n Index of the image whom checked pixel value belong to. + \param x X-coordinate of the checked pixel value. + \param y Y-coordinate of the checked pixel value. + \param z Z-coordinate of the checked pixel value. + \param c C-coordinate of the checked pixel value. + **/ + bool containsNXYZC(const int n, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width && x>=0 && x<_data[n].width() && y>=0 && y<_data[n].height() && + z>=0 && z<_data[n].depth() && c>=0 && c<_data[n].spectrum(); + } + + //! Test if list contains image with specified indice. + /** + \param n Index of the checked image. + **/ + bool containsN(const int n) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width; + } + + //! Test if one image of the list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \param[out] c C-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z,c). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const { + if (is_empty()) return false; + cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; } + return false; + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z) const { + t c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y) const { + t z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x). + **/ + template + bool contains(const T& pixel, t& n, t& x) const { + t y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \note If true, set coordinates (n). + **/ + template + bool contains(const T& pixel, t& n) const { + t x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + **/ + bool contains(const T& pixel) const { + unsigned int n, x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if the list contains the image 'img'. + /** + \param img Reference to image to test. + \param[out] n Index of image in the list, if test succeeds. + \note If true, returns the position (n) of the image in the list. + **/ + template + bool contains(const CImg& img, t& n) const { + if (is_empty()) return false; + const CImg *const ptr = &img; + cimglist_for(*this,i) if (_data + i==ptr) { n = (t)i; return true; } + return false; + } + + //! Test if the list contains the image img. + /** + \param img Reference to image to test. + **/ + bool contains(const CImg& img) const { + unsigned int n; + return contains(img,n); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + //! Return a reference to the minimum pixel value of the instance list. + /** + **/ + T& min() { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the maximum pixel value of the instance list \const. + const T& max() const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T max_value = *ptr_max; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + T& min_max(t& max_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well \const. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + const T& min_max(t& max_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well. + /** + \param[out] min_val Value of the minimum value found. + **/ + template + T& max_min(t& min_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + if (is_shared) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified shared image " + "CImg<%s>(%u,%u,%u,%u,%p) at position %u (pixel types are different).", + cimglist_instance, + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos); + + CImg *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 + _data = new_data; + *_data = img; + } else { + 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 + 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; + _data[npos] = img; + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \specialization. + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + CImg *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 + _data = new_data; + if (is_shared && img) { + _data->_width = img._width; + _data->_height = img._height; + _data->_depth = img._depth; + _data->_spectrum = img._spectrum; + _data->_is_shared = true; + _data->_data = img._data; + } else *_data = img; + } + else { + if (new_data) { // Insert with re-allocation + if (npos) std::memcpy((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)); + if (is_shared && img) { + new_data[npos]._width = img._width; + new_data[npos]._height = img._height; + new_data[npos]._depth = img._depth; + new_data[npos]._spectrum = img._spectrum; + new_data[npos]._is_shared = true; + new_data[npos]._data = img._data; + } else { + new_data[npos]._width = new_data[npos]._height = new_data[npos]._depth = new_data[npos]._spectrum = 0; + new_data[npos]._data = 0; + new_data[npos] = img; + } + std::memset((void*)_data,0,sizeof(CImg)*(_width - 1)); + delete[] _data; + _data = new_data; + } 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) { + _data[npos]._width = img._width; + _data[npos]._height = img._height; + _data[npos]._depth = img._depth; + _data[npos]._spectrum = img._spectrum; + _data[npos]._is_shared = true; + _data[npos]._data = img._data; + } else { + _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; + _data[npos]._data = 0; + _data[npos] = img; + } + } + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \newinstance. + template + CImgList get_insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(img,pos,is_shared); + } + + //! Insert n empty images img into the current image list, at position \p pos. + /** + \param n Number of empty images to insert. + \param pos Index of the insertion. + **/ + CImgList& insert(const unsigned int n, const unsigned int pos=~0U) { + CImg empty; + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i get_insert(const unsigned int n, const unsigned int pos=~0U) const { + return (+*this).insert(n,pos); + } + + //! Insert \c n copies of the image \c img into the current image list, at position \c pos. + /** + \param n Number of image copies to insert. + \param img Image to insert by copy. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of \c img or not. + **/ + template + CImgList& insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + insert(img,npos,is_shared); + for (unsigned int i = 1; i + CImgList get_insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,img,pos,is_shared); + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos. + /** + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos + l,is_shared); + else insert(CImgList(list),npos,is_shared); + return *this; + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos \newinstance. + template + CImgList get_insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(list,pos,is_shared); + } + + //! Insert n copies of the list \c list at position \c pos of the current list. + /** + \param n Number of list copies to insert. + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i + CImgList get_insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,list,pos,is_shared); + } + + //! Remove all images between from indexes. + /** + \param pos1 Starting index of the removal. + \param pos2 Ending index of the removal. + **/ + CImgList& remove(const unsigned int pos1, const unsigned int pos2) { + const unsigned int + npos1 = pos1=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + else { + if (tpos2>=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + + for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign(); + const unsigned int nb = 1 + npos2 - npos1; + if (!(_width-=nb)) return assign(); + if (_width>(_allocated_width>>2) || _allocated_width<=16) { // Removing items without reallocation + if (npos1!=_width) + std::memmove((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; + 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); + if (npos1!=_width) + std::memcpy((void*)(new_data + npos1),(void*)(_data + npos2 + 1),sizeof(CImg)*(_width - npos1)); + if (_width!=_allocated_width) + std::memset((void*)(new_data + _width),0,sizeof(CImg)*(_allocated_width - _width)); + std::memset((void*)_data,0,sizeof(CImg)*(_width + nb)); + delete[] _data; + _data = new_data; + } + } + return *this; + } + + //! Remove all images between from indexes \newinstance. + CImgList get_remove(const unsigned int pos1, const unsigned int pos2) const { + return (+*this).remove(pos1,pos2); + } + + //! Remove image at index \c pos from the image list. + /** + \param pos Index of the image to remove. + **/ + CImgList& remove(const unsigned int pos) { + return remove(pos,pos); + } + + //! Remove image at index \c pos from the image list \newinstance. + CImgList get_remove(const unsigned int pos) const { + return (+*this).remove(pos); + } + + //! Remove last image. + /** + **/ + CImgList& remove() { + return remove(_width - 1); + } + + //! Remove last image \newinstance. + CImgList get_remove() const { + return (+*this).remove(); + } + + //! Reverse list order. + CImgList& reverse() { + for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width - 1 - l]); + return *this; + } + + //! Reverse list order \newinstance. + CImgList get_reverse() const { + return (+*this).reverse(); + } + + //! Return a sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList& images(const unsigned int pos0, const unsigned int pos1) { + return get_images(pos0,pos1).move_to(*this); + } + + //! Return a sublist \newinstance. + CImgList get_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l]); + return res; + } + + //! Return a shared sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a shared sublist \newinstance. + const CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a single image which is the appending of all images of the current CImgList instance. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg get_append(const char axis, const float align=0) const { + if (is_empty()) return CImg(); + if (_width==1) return +((*this)[0]); + unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0; + CImg res; + switch (cimg::lowercase(axis)) { + case 'x' : { // Along the X-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx+=img._width; + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image(pos, + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._width; + } + } break; + case 'y' : { // Along the Y-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy+=img._height; + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + pos, + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._height; + } + } break; + case 'z' : { // Along the Z-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz+=img._depth; + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + pos, + (int)(align*(dc - img._spectrum)), + img); + pos+=img._depth; + } + } break; + default : { // Along the C-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc+=img._spectrum; + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + pos, + img); + pos+=img._spectrum; + } + } + } + return res; + } + + //! Return a list where each image has been split along the specified axis. + /** + \param axis Axis to split images along. + \param nb Number of spliting parts for each image. + **/ + CImgList& split(const char axis, const int nb=-1) { + return get_split(axis,nb).move_to(*this); + } + + //! Return a list where each image has been split along the specified axis \newinstance. + CImgList get_split(const char axis, const int nb=-1) const { + CImgList res; + cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U); + return res; + } + + //! Insert image at the end of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_back(const CImg& img) { + return insert(img); + } + + //! Insert image at the front of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_front(const CImg& img) { + return insert(img,0); + } + + //! Insert list at the end of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_back(const CImgList& list) { + return insert(list); + } + + //! Insert list at the front of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_front(const CImgList& list) { + return insert(list,0); + } + + //! Remove last image. + /** + **/ + CImgList& pop_back() { + return remove(_width - 1); + } + + //! Remove first image. + /** + **/ + CImgList& pop_front() { + return remove(0); + } + + //! Remove image pointed by iterator. + /** + \param iter Iterator pointing to the image to remove. + **/ + CImgList& erase(const iterator iter) { + return remove(iter - _data); + } + + //@} + //---------------------------------- + // + //! \name Data Input + //@{ + //---------------------------------- + + //! Display a simple interactive interface to select images or sublists. + /** + \param disp Window instance to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(CImgDisplay &disp, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + return _select(disp,0,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + //! Display a simple interactive interface to select images or sublists. + /** + \param title Title of a new window used to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(const char *const title, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + CImg _select(CImgDisplay &disp, const char *const title, const bool feature_type, + const char axis, const float align, const bool exit_on_anykey, + const unsigned int orig, const bool resize_disp, + const bool exit_on_rightbutton, const bool exit_on_wheel) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "select(): Empty instance.", + cimglist_instance); + + // Create image correspondence table and get list dimensions for visualization. + CImgList _indices; + unsigned int max_width = 0, max_height = 0, sum_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + if (h>max_height) max_height = h; + sum_width+=w; sum_height+=h; + if (axis=='x') CImg(w,1,1,1,(unsigned int)l).move_to(_indices); + else CImg(h,1,1,1,(unsigned int)l).move_to(_indices); + } + const CImg indices0 = _indices>'x'; + + // Create display window. + if (!disp) { + if (axis=='x') disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1); + else disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1); + if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else { + if (title) disp.set_title("%s",title); + 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); + } + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0); + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + // Enter event loop. + CImg visu0, visu; + CImg indices; + CImg positions(_width,4,1,1,-1); + int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1; + bool is_clicked = false, is_selected = false, text_down = false, update_display = true; + unsigned int key = 0; + + while (!is_selected && !disp.is_closed() && !key) { + + // Create background image. + if (!visu0) { + visu0.assign(disp._width,disp._height,1,3,0); visu.assign(); + (indices0.get_resize(axis=='x'?visu0._width:visu0._height,1)).move_to(indices); + unsigned int ind = 0; + const CImg onexone(1,1,1,1,(T)0); + if (axis=='x') + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)x0; + positions(ind,1) = positions(ind,3) = (int)(align*(visu0.height() - res.height())); + positions(ind,2)+=res._width; + positions(ind,3)+=res._height - 1; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + else + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)(align*(visu0.width() - res.width())); + positions(ind,1) = positions(ind,3) = (int)y0; + positions(ind,2)+=res._width - 1; + positions(ind,3)+=res._height; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + if (axis=='x') --positions(ind,2); else --positions(ind,3); + update_display = true; + } + + if (!visu || oindice0!=indice0 || oindice1!=indice1) { + if (indice0>=0 && indice1>=0) { + visu.assign(visu0,false); + const int indm = std::min(indice0,indice1), indM = std::max(indice0,indice1); + for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) { + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + background_color,0.2f); + if ((axis=='x' && positions(ind,2) - positions(ind,0)>=8) || + (axis!='x' && positions(ind,3) - positions(ind,1)>=8)) + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + foreground_color,0.9f,0xAAAAAAAA); + } + if (is_clicked) visu.__draw_text(" Images #%u - #%u, Size = %u",text_down, + orig + indm,orig + indM,indM - indm + 1); + else visu.__draw_text(" Image #%u (%u,%u,%u,%u)",text_down, + orig + indice0, + _data[indice0]._width, + _data[indice0]._height, + _data[indice0]._depth, + _data[indice0]._spectrum); + update_display = true; + } else visu.assign(); + } + if (!visu) { visu.assign(visu0,true); update_display = true; } + if (update_display) { visu.display(disp); update_display = false; } + disp.wait(); + + // Manage user events. + const int xm = disp.mouse_x(), ym = disp.mouse_y(); + int indice = -1; + + if (xm>=0) { + indice = (int)indices(axis=='x'?xm:ym); + if (disp.button()&1) { + if (!is_clicked) { is_clicked = true; oindice0 = indice0; indice0 = indice; } + oindice1 = indice1; indice1 = indice; + if (!feature_type) is_selected = true; + } else { + if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; } + else is_selected = true; + } + } else { + if (is_clicked) { + if (!(disp.button()&1)) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + else indice1 = -1; + } else indice0 = indice1 = -1; + } + + if (disp.button()&4) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; indice1 = indice0 = -1; } + if (disp.wheel() && exit_on_wheel) is_selected = true; + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(axis=='x'?sum_width:max_width,axis=='x'?max_height:sum_height,1),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot... ",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false).wait(); key = 0; + } break; + case cimg::keyO : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false).wait(); key = 0; + } break; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (ym>=0 && ym<13) { if (!text_down) { visu.assign(); text_down = true; }} + else if (ym>=visu.height() - 13) { if (text_down) { visu.assign(); text_down = false; }} + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + CImg res(1,2,1,1,-1); + if (is_selected) { + if (feature_type) res.fill(std::min(indice0,indice1),std::max(indice0,indice1)); + else res.fill(indice0); + } + if (!(disp.button()&2)) disp.set_button(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + disp.set_key(key); + return res; + } + + //! Load a list from a file. + /** + \param filename Filename to read data from. + **/ + CImgList& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load(): Specified filename is (null).", + cimglist_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimglist_load_plugin + cimglist_load_plugin(filename); +#endif +#ifdef cimglist_load_plugin1 + cimglist_load_plugin1(filename); +#endif +#ifdef cimglist_load_plugin2 + cimglist_load_plugin2(filename); +#endif +#ifdef cimglist_load_plugin3 + cimglist_load_plugin3(filename); +#endif +#ifdef cimglist_load_plugin4 + cimglist_load_plugin4(filename); +#endif +#ifdef cimglist_load_plugin5 + cimglist_load_plugin5(filename); +#endif +#ifdef cimglist_load_plugin6 + cimglist_load_plugin6(filename); +#endif +#ifdef cimglist_load_plugin7 + cimglist_load_plugin7(filename); +#endif +#ifdef cimglist_load_plugin8 + cimglist_load_plugin8(filename); +#endif + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) load_cimg(filename); + else if (!cimg::strcasecmp(ext,"rec") || + !cimg::strcasecmp(ext,"par")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded && !is_stdin) { + std::FILE *const file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to open file '%s'.", + cimglist_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + 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 is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file as a single image. + if (!is_loaded) { + assign(1); + try { + _data->load(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to recognize format of file '%s'.", + cimglist_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load a list from a file \newinstance. + static CImgList get_load(const char *const filename) { + return CImgList().load(filename); + } + + //! Load a list from a .cimg file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_cimg(const char *const filename) { + return _load_cimg(0,filename); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename) { + return CImgList().load_cimg(filename); + } + + //! Load a list from a .cimg file. + /** + \param file File to read data from. + **/ + CImgList& load_cimg(std::FILE *const file) { + return _load_cimg(file,0); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file) { + return CImgList().load_cimg(file); + } + + CImgList& _load_cimg(std::FILE *const file, const char *const filename) { +#ifdef cimg_use_zlib +#define _cimgz_load_cimg_case(Tss) { \ + Bytef *const cbuf = new Bytef[csiz]; \ + cimg::fread(cbuf,csiz,nfile); \ + raw.assign(W,H,D,C); \ + uLongf destlen = (ulongT)raw.size()*sizeof(Tss); \ + uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \ + delete[] cbuf; \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ +} +#else +#define _cimgz_load_cimg_case(Tss) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Unable to load compressed data from file '%s' unless zlib is enabled.", \ + cimglist_instance, \ + filename?filename:"(FILE*)"); +#endif + +#define _cimg_load_cimg_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; csiz = 0; \ + if ((err = cimg_sscanf(tmp,"%u %u %u %u #%lu",&W,&H,&D,&C,&csiz))<4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'.", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:("(FILE*)")); \ + if (W*H*D*C>0) { \ + CImg 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; \ + } \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + + const ulongT cimg_iobuffer = (ulongT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + unsigned long csiz; + int i, err; + do { + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; + } while (*tmp=='#' && i>=0); + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + assign(N); + _cimg_load_cimg_case("bool",bool); + _cimg_load_cimg_case("unsigned_char",unsigned char); + _cimg_load_cimg_case("uchar",unsigned char); + _cimg_load_cimg_case("char",char); + _cimg_load_cimg_case("unsigned_short",unsigned short); + _cimg_load_cimg_case("ushort",unsigned short); + _cimg_load_cimg_case("short",short); + _cimg_load_cimg_case("unsigned_int",unsigned int); + _cimg_load_cimg_case("uint",unsigned int); + _cimg_load_cimg_case("int",int); + _cimg_load_cimg_case("unsigned_long",ulongT); + _cimg_load_cimg_case("ulong",ulongT); + _cimg_load_cimg_case("long",longT); + _cimg_load_cimg_case("unsigned_int64",uint64T); + _cimg_load_cimg_case("uint64",uint64T); + _cimg_load_cimg_case("int64",int64T); + _cimg_load_cimg_case("float",float); + _cimg_load_cimg_case("double",double); + + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a sublist list from a (non compressed) .cimg file. + /** + \param filename Filename to read data from. + \param n0 Starting index of images to read (~0U for max). + \param n1 Ending index of images to read (~0U for max). + \param x0 Starting X-coordinates of image regions to read. + \param y0 Starting Y-coordinates of image regions to read. + \param z0 Starting Z-coordinates of image regions to read. + \param c0 Starting C-coordinates of image regions to read. + \param x1 Ending X-coordinates of image regions to read (~0U for max). + \param y1 Ending Y-coordinates of image regions to read (~0U for max). + \param z1 Ending Z-coordinates of image regions to read (~0U for max). + \param c1 Ending C-coordinates of image regions to read (~0U for max). + **/ + CImgList& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sublist list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \overloading. + CImgList& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + 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 z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { +#define _cimg_load_cimg_case2(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l<=nn1; ++l) { \ + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; \ + if (cimg_sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:"(FILE*)"); \ + if (W*H*D*C>0) { \ + if (l=W || ny0>=H || nz0>=D || nc0>=C) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const unsigned int \ + _nx1 = nx1==~0U?W - 1:nx1, \ + _ny1 = ny1==~0U?H - 1:ny1, \ + _nz1 = nz1==~0U?D - 1:nz1, \ + _nc1 = nc1==~0U?C - 1:nc1; \ + if (_nx1>=W || _ny1>=H || _nz1>=D || _nc1>=C) \ + throw CImgArgumentException(_cimglist_instance \ + "load_cimg(): Invalid specified coordinates " \ + "[%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " \ + "because image [%u] in file '%s' has size (%u,%u,%u,%u).", \ + cimglist_instance, \ + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,l,filename?filename:"(FILE*)",W,H,D,C); \ + CImg raw(1 + _nx1 - nx0); \ + CImg &img = _data[l - nn0]; \ + img.assign(1 + _nx1 - nx0,1 + _ny1 - ny0,1 + _nz1 - nz0,1 + _nc1 - nc0); \ + T *ptrd = img._data; \ + ulongT skipvb = nc0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int c = 1 + _nc1 - nc0; c; --c) { \ + const ulongT skipzb = nz0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + _nz1 - nz0; z; --z) { \ + const ulongT skipyb = ny0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + _ny1 - ny0; y; --y) { \ + const ulongT skipxb = nx0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + cimg::fread(raw._data,raw._width,nfile); \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); \ + const Tss *ptrs = raw._data; \ + for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \ + const ulongT skipxe = (W - 1 - _nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const ulongT skipye = (H - 1 - _ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const ulongT skipze = (D - 1 - _nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const ulongT skipve = (C - 1 - _nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + unsigned int + nn0 = std::min(n0,n1), nn1 = std::max(n0,n1), + nx0 = std::min(x0,x1), nx1 = std::max(x0,x1), + ny0 = std::min(y0,y1), ny1 = std::max(y0,y1), + nz0 = std::min(z0,z1), nz1 = std::max(z0,z1), + nc0 = std::min(c0,c1), nc1 = std::max(c0,c1); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + nn1 = n1==~0U?N - 1:n1; + if (nn1>=N) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Invalid specified coordinates [%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " + "because file '%s' contains only %u images.", + cimglist_instance, + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,filename?filename:"(FILE*)",N); + assign(1 + nn1 - n0); + _cimg_load_cimg_case2("bool",bool); + _cimg_load_cimg_case2("unsigned_char",unsigned char); + _cimg_load_cimg_case2("uchar",unsigned char); + _cimg_load_cimg_case2("char",char); + _cimg_load_cimg_case2("unsigned_short",unsigned short); + _cimg_load_cimg_case2("ushort",unsigned short); + _cimg_load_cimg_case2("short",short); + _cimg_load_cimg_case2("unsigned_int",unsigned int); + _cimg_load_cimg_case2("uint",unsigned int); + _cimg_load_cimg_case2("int",int); + _cimg_load_cimg_case2("unsigned_long",ulongT); + _cimg_load_cimg_case2("ulong",ulongT); + _cimg_load_cimg_case2("long",longT); + _cimg_load_cimg_case2("unsigned_int64",uint64T); + _cimg_load_cimg_case2("uint64",uint64T); + _cimg_load_cimg_case2("int64",int64T); + _cimg_load_cimg_case2("float",float); + _cimg_load_cimg_case2("double",double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_parrec(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_parrec(): Specified filename is (null).", + cimglist_instance); + + CImg body(1024), filenamepar(1024), filenamerec(1024); + *body = *filenamepar = *filenamerec = 0; + const char *const ext = cimg::split_filename(filename,body); + if (!std::strcmp(ext,"par")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.rec",body._data); + } + if (!std::strcmp(ext,"PAR")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.REC",body._data); + } + if (!std::strcmp(ext,"rec")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.par",body._data); + } + if (!std::strcmp(ext,"REC")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.PAR",body._data); + } + std::FILE *file = cimg::fopen(filenamepar,"r"); + + // Parse header file + CImgList st_slices; + CImgList st_global; + CImg line(256); *line = 0; + int err; + do { err = std::fscanf(file,"%255[^\n]%*c",line._data); } while (err!=EOF && (*line=='#' || *line=='.')); + do { + unsigned int sn,size_x,size_y,pixsize; + float rs,ri,ss; + err = std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&size_x,&size_y,&ri,&rs,&ss); + if (err==7) { + CImg::vector((float)sn,(float)pixsize,(float)size_x,(float)size_y,ri,rs,ss,0).move_to(st_slices); + unsigned int i; for (i = 0; i::vector(size_x,size_y,sn).move_to(st_global); + else { + CImg &vec = st_global[i]; + if (size_x>vec[0]) vec[0] = size_x; + if (size_y>vec[1]) vec[1] = size_y; + vec[2] = sn; + } + st_slices[st_slices._width - 1][7] = (float)i; + } + } while (err==7); + + // Read data + std::FILE *file2 = cimg::fopen(filenamerec,"rb"); + cimglist_for(st_global,l) { + const CImg& vec = st_global[l]; + CImg(vec[0],vec[1],vec[2]).move_to(*this); + } + + cimglist_for(st_slices,l) { + const CImg& vec = st_slices[l]; + const unsigned int + sn = (unsigned int)vec[0] - 1, + pixsize = (unsigned int)vec[1], + size_x = (unsigned int)vec[2], + size_y = (unsigned int)vec[3], + imn = (unsigned int)vec[7]; + const float ri = vec[4], rs = vec[5], ss = vec[6]; + switch (pixsize) { + case 8 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 16 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 32 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + default : + cimg::fclose(file); + cimg::fclose(file2); + throw CImgIOException(_cimglist_instance + "load_parrec(): Unsupported %d-bits pixel type for file '%s'.", + cimglist_instance, + pixsize,filename); + } + } + cimg::fclose(file); + cimg::fclose(file2); + if (!_width) + throw CImgIOException(_cimglist_instance + "load_parrec(): Failed to recognize valid PAR-REC data in file '%s'.", + cimglist_instance, + filename); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file \newinstance. + static CImgList get_load_parrec(const char *const filename) { + return CImgList().load_parrec(filename); + } + + //! Load a list from a YUV image sequence file. + /** + \param filename Filename to read data from. + \param size_x Width of the images. + \param size_y Height of the images. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of first image frame to read. + \param last_frame Index of last image frame to read. + \param step_frame Step applied between each frame. + \param yuv2rgb Apply YUV to RGB transformation during reading. + **/ + CImgList& load_yuv(const char *const filename, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(0,filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from a YUV image sequence file \newinstance. + static CImgList get_load_yuv(const char *const filename, + 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) { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \overloading. + CImgList& load_yuv(std::FILE *const file, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(file,0,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \newinstance. + static CImgList get_load_yuv(std::FILE *const file, + 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) { + return CImgList().load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + CImgList& _load_yuv(std::FILE *const file, const char *const filename, + 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) { + 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'.", + 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_frame YUV(size_x,size_y,1,3), UV(size_x/cfx,size_y/cfy,1,2); + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool stop_flag = false; + int err; + if (nfirst_frame) { + err = cimg::fseek(nfile,(uint64T)nfirst_frame*(YUV._width*YUV._height + 2*UV._width*UV._height),SEEK_CUR); + if (err) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_yuv(): File '%s' doesn't contain frame number %u.", + cimglist_instance, + filename?filename:"(FILE*)",nfirst_frame); + } + } + unsigned int frame; + for (frame = nfirst_frame; !stop_flag && frame<=nlast_frame; frame+=nstep_frame) { + YUV.get_shared_channel(0).fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(YUV._data),1,(size_t)YUV._width*YUV._height,nfile); + if (err!=(int)(YUV._width*YUV._height)) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + UV.fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(UV._data),1,(size_t)UV.size(),nfile); + if (err!=(int)(UV.size())) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + const ucharT *ptrs1 = UV._data, *ptrs2 = UV.data(0,0,0,1); + ucharT *ptrd1 = YUV.data(0,0,0,1), *ptrd2 = YUV.data(0,0,0,2); + const unsigned int wd = YUV._width; + switch (chroma_subsampling) { + case 420 : + cimg_forY(UV,y) { + cimg_forX(UV,x) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd2[wd] = V; *(ptrd2)++ = V; + ptrd2[wd] = V; *(ptrd2)++ = V; + } + ptrd1+=wd; ptrd2+=wd; + } + break; + case 422 : + cimg_forXY(UV,x,y) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + *(ptrd1++) = U; *(ptrd1++) = U; + *(ptrd2++) = V; *(ptrd2++) = V; + } + break; + default : + YUV.draw_image(0,0,0,1,UV); + } + if (yuv2rgb) YUV.YCbCrtoRGB(); + insert(YUV); + if (nstep_frame>1) cimg::fseek(nfile,(uint64T)(nstep_frame - 1)*(size_x*size_y + size_x*size_y/2),SEEK_CUR); + } + } + } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_yuv() : Missing data in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + if (stop_flag && nlast_frame!=~0U && frame!=nlast_frame) + cimg::warn(_cimglist_instance + "load_yuv(): Frame %d not reached since only %u frames were found in file '%s'.", + cimglist_instance, + nlast_frame,frame - 1,filename?filename:"(FILE*)"); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \note If step_frame==0, the current video stream is forced to be released (without any frames read). + **/ + CImgList& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { +#ifndef cimg_use_opencv + if (first_frame || last_frame!=~0U || step_frame>1) + throw CImgArgumentException(_cimglist_instance + "load_video() : File '%s', arguments 'first_frame', 'last_frame' " + "and 'step_frame' can be only set when using OpenCV " + "(-Dcimg_use_opencv must be enabled).", + cimglist_instance,filename); + return load_ffmpeg_external(filename); +#else + static CvCapture *captures[32] = { 0 }; + static CImgList filenames(32); + static CImg positions(32,1,1,1,0); + static int last_used_index = -1; + + // Detect if a video capture already exists for the specified filename. + cimg::mutex(9); + int index = -1; + if (filename) { + if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) { + index = last_used_index; + } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) { + index = l; break; + } + } else index = last_used_index; + cimg::mutex(9,0); + + // Release stream if needed. + if (!step_frame || (index>=0 && positions[index]>first_frame)) { + if (index>=0) { + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; filenames[index].assign(); positions[index] = 0; + if (last_used_index==index) last_used_index = -1; + index = -1; + cimg::mutex(9,0); + } else + if (filename) + cimg::warn(_cimglist_instance + "load_video() : File '%s', no opened video stream associated with filename found.", + cimglist_instance,filename); + else + cimg::warn(_cimglist_instance + "load_video() : No opened video stream found.", + cimglist_instance,filename); + if (!step_frame) return *this; + } + + // Find empty slot for capturing video stream. + if (index<0) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_video(): No already open video reader found. You must specify a " + "non-(null) filename argument for the first call.", + cimglist_instance); + else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); } + if (index<0) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', no video reader slots available. " + "You have to release some of your previously opened videos.", + cimglist_instance,filename); + cimg::mutex(9); + captures[index] = cvCaptureFromFile(filename); + CImg::string(filename).move_to(filenames[index]); + positions[index] = 0; + cimg::mutex(9,0); + if (!captures[index]) { + filenames[index].assign(); + 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::mutex(9); + const unsigned int nb_frames = (unsigned int)std::max(0.,cvGetCaptureProperty(captures[index], + CV_CAP_PROP_FRAME_COUNT)); + cimg::mutex(9,0); + assign(); + + // Skip frames if necessary. + bool go_on = true; + unsigned int &pos = positions[index]; + while (pos frame(src->width,src->height,1,3); + const int step = (int)(src->widthStep - 3*src->width); + const unsigned char* ptrs = (unsigned char*)src->imageData; + T *ptr_r = frame.data(0,0,0,0), *ptr_g = frame.data(0,0,0,1), *ptr_b = frame.data(0,0,0,2); + if (step>0) cimg_forY(frame,y) { + cimg_forX(frame,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); } + ptrs+=step; + } else for (ulongT siz = (ulongT)src->width*src->height; siz; --siz) { + *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); + } + frame.move_to(*this); + ++pos; + + bool skip_failed = false; + for (unsigned int i = 1; i=nb_frames)) { // Close video stream when necessary + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; + filenames[index].assign(); + positions[index] = 0; + index = -1; + cimg::mutex(9,0); + } + + cimg::mutex(9); + last_used_index = index; + cimg::mutex(9,0); + + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', unable to locate frame %u.", + cimglist_instance,filename,first_frame); + return *this; +#endif + } + + //! Load an image from a video file, using OpenCV library \newinstance. + static CImgList get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame); + } + + //! Load an image from a video file using the external tool 'ffmpeg'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_ffmpeg_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + 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=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::ffmpeg_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp2)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + unsigned int i = 1; + for (bool stop_flag = false; !stop_flag; ++i) { + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,i); + CImg img; + try { img.load_pnm(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + cimg::exception_mode(omode); + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_ffmpeg_external(): Failed to open file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + return *this; + } + + //! Load an image from a video file using the external tool 'ffmpeg' \newinstance. + static CImgList get_load_ffmpeg_external(const char *const filename) { + return CImgList().load_ffmpeg_external(filename); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gif_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_gif_external(): Specified filename is (null).", + cimglist_instance); + 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(); } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_gif_external(): Failed to open file '%s'.", + cimglist_instance,filename); + return *this; + } + + CImgList& _load_gif_external(const char *const filename, const bool use_graphicsmagick=false) { + 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()); + 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=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\"", + cimg::imagemagick_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + + // Try to read a single frame gif. + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png",filename_tmp._data); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + else { // Try to read animated gif + unsigned int i = 0; + for (bool stop_flag = false; !stop_flag; ++i) { + if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.%u",filename_tmp._data,i); + else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-%u.png",filename_tmp._data,i); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools \newinstance. + static CImgList get_load_gif_external(const char *const filename) { + return CImgList().load_gif_external(filename); + } + + //! Load a gzipped list, using external tool 'gunzip'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Specified filename is (null).", + cimglist_instance); + 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), + *ext2 = cimg::split_filename(body,0); + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=cimg::std_fopen(filename_tmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Failed to open file '%s'.", + cimglist_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load a gzipped list, using external tool 'gunzip' \newinstance. + static CImgList get_load_gzip_external(const char *const filename) { + 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] 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 + nfirst_frame = first_frame::get_load_tiff(filename)); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimglist_instance + "load_tiff(): Invalid specified frame range is [%u,%u] (step %u) since " + "file '%s' contains %u image(s).", + cimglist_instance, + nfirst_frame,nlast_frame,nstep_frame,filename,nb_images); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + assign(1 + (nlast_frame - nfirst_frame)/nstep_frame); + TIFFSetDirectory(tif,0); + cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimglist_instance + "load_tiff(): Failed to open file '%s'.", + cimglist_instance, + filename); + return *this; +#endif + } + + //! Load a multi-page TIFF file \newinstance. + static CImgList 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); + } + + //@} + //---------------------------------- + // + //! \name Data Output + //@{ + //---------------------------------- + + //! Print information about the list on the standard output. + /** + \param title Label set to the information displayed. + \param display_stats Tells if image statistics must be computed and displayed. + **/ + const CImgList& print(const char *const title=0, const bool display_stats=true) const { + unsigned int msiz = 0; + cimglist_for(*this,l) msiz+=_data[l].size(); + msiz*=sizeof(T); + const unsigned int mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U; + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImgList<%s>",pixel_type()); + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = %u/%u [%u %s], %sdata%s = (CImg<%s>*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_allocated_width, + mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end() - 1)); + else std::fprintf(cimg::output(),".\n"); + + char tmp[16] = { 0 }; + cimglist_for(*this,ll) { + cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll); + std::fprintf(cimg::output()," "); + _data[ll].print(tmp,display_stats); + if (ll==3 && width()>8) { ll = width() - 5; std::fprintf(cimg::output()," ...\n"); } + } + std::fflush(cimg::output()); + return *this; + } + + //! Display the current CImgList instance in an existing CImgDisplay window (by reference). + /** + \param disp Reference to an existing CImgDisplay instance, where the current image list will be displayed. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignmenet. + \note This function displays the list images of the current CImgList instance into an existing + CImgDisplay window. + Images of the list are appended in a single temporarly image for visualization purposes. + The function returns immediately. + **/ + const CImgList& display(CImgDisplay &disp, const char axis='x', const float align=0) const { + disp.display(*this,axis,align); + return *this; + } + + //! Display the current CImgList instance in a new display window. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \param axis Alignment axis for images viewing. + \param align Apending alignment. + \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. + 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, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + bool is_exit = false; + return _display(disp,0,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + //! Display the current CImgList instance in a new display window. + /** + \param title Title of the opening display window. + \param display_info Tells if list information must be written on standard output. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \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. + **/ + const CImgList& display(const char *const title=0, const bool display_info=true, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + CImgDisplay disp; + bool is_exit = false; + return _display(disp,title,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + const CImgList& _display(CImgDisplay &disp, const char *const title, const CImgList *const titles, + const bool display_info, const char axis, const float align, unsigned int *const XYZ, + const bool exit_on_anykey, const unsigned int orig, const bool is_first_call, + bool &is_exit) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "display(): Empty instance.", + cimglist_instance); + if (!disp) { + if (axis=='x') { + unsigned int sum_width = 0, max_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + sum_width+=w; + if (h>max_height) max_height = h; + } + disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:titles?titles->__display()._data:0,1); + } else { + unsigned int max_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + sum_height+=h; + } + disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:titles?titles->__display()._data:0,1); + } + if (!title && !titles) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else if (title) disp.set_title("%s",title); + else if (titles) disp.set_title("%s",titles->__display()._data); + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(disp.title()); + disp.show().flush(); + + if (_width==1) { + const unsigned int dw = disp._width, dh = disp._height; + if (!is_first_call) + disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false); + disp.set_title("%s (%ux%ux%ux%u)", + dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum); + _data[0]._display(disp,0,false,XYZ,exit_on_anykey,!is_first_call); + if (disp.key()) is_exit = true; + disp.resize(cimg_fitscreen(dw,dh,1),false).set_title("%s",dtitle.data()); + } else { + bool disp_resize = !is_first_call; + while (!disp.is_closed() && !is_exit) { + const CImg s = _select(disp,0,true,axis,align,exit_on_anykey,orig,disp_resize,!is_first_call,true); + disp_resize = true; + if (s[0]<0 && !disp.wheel()) { // No selections done + if (disp.button()&2) { disp.flush(); break; } + is_exit = true; + } else if (disp.wheel()) { // Zoom in/out + const int wheel = disp.wheel(); + disp.set_wheel(); + if (!is_first_call && wheel<0) break; + if (wheel>0 && _width>=4) { + const unsigned int + delta = std::max(1U,(unsigned int)cimg::round(0.3*_width)), + ind0 = (unsigned int)std::max(0,s[0] - (int)delta), + ind1 = (unsigned int)std::min(width() - 1,s[0] + (int)delta); + if ((ind0!=0 || ind1!=_width - 1) && ind1 - ind0>=3) { + const CImgList sublist = get_shared_images(ind0,ind1); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(ind0,ind1); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + ind0,false,is_exit); + } + } + } else if (s[0]!=0 || s[1]!=width() - 1) { + const CImgList sublist = get_shared_images(s[0],s[1]); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(s[0],s[1]); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + s[0],false,is_exit); + } + disp.set_title("%s",dtitle.data()); + } + } + return *this; + } + + // [internal] Return string to describe display title. + CImg __display() const { + CImg res, str; + cimglist_for(*this,l) { + CImg::string(_data[l]).move_to(str); + if (l!=width() - 1) { + str.resize(str._width + 1,1,1,1,0); + str[str._width - 2] = ','; + str[str._width - 1] = ' '; + } + res.append(str,'x'); + } + if (!res) return CImg(1,1,1,1,0).move_to(res); + cimg::strellipsize(res,128,false); + if (_width>1) { + const unsigned int l = (unsigned int)std::strlen(res); + if (res._width<=l + 16) res.resize(l + 16,1,1,1,0); + cimg_snprintf(res._data + l,16," (#%u)",_width); + } + return res; + } + + //! Save list into a file. + /** + \param filename Filename to write data to. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + **/ + const CImgList& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save(): Specified filename is (null).", + cimglist_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:number>=0?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimglist_save_plugin + cimglist_save_plugin(fn); +#endif +#ifdef cimglist_save_plugin1 + cimglist_save_plugin1(fn); +#endif +#ifdef cimglist_save_plugin2 + cimglist_save_plugin2(fn); +#endif +#ifdef cimglist_save_plugin3 + cimglist_save_plugin3(fn); +#endif +#ifdef cimglist_save_plugin4 + cimglist_save_plugin4(fn); +#endif +#ifdef cimglist_save_plugin5 + cimglist_save_plugin5(fn); +#endif +#ifdef cimglist_save_plugin6 + cimglist_save_plugin6(fn); +#endif +#ifdef cimglist_save_plugin7 + cimglist_save_plugin7(fn); +#endif +#ifdef cimglist_save_plugin8 + cimglist_save_plugin8(fn); +#endif + if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); +#ifdef cimg_use_tiff + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); +#endif + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + else { + if (_width==1) _data[0].save(fn,-1); + else cimglist_for(*this,l) { _data[l].save(fn,is_stdout?-1:l); if (is_stdout) std::fputc(EOF,cimg::_stdout()); } + } + return *this; + } + + //! Tell if an image list can be saved as one single file. + /** + \param filename Filename, as a C-string. + \return \c true if the file format supports multiple images, \c false otherwise. + **/ + static bool is_saveable(const char *const filename) { + const char *const ext = cimg::split_filename(filename); + if (!cimg::strcasecmp(ext,"cimgz") || +#ifdef cimg_use_tiff + !cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff") || +#endif + !cimg::strcasecmp(ext,"yuv") || + !cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return true; + return false; + } + + //! Save image sequence as a GIF animated file. + /** + \param filename Filename to write data to. + \param fps Number of desired frames per second. + \param nb_loops Number of loops (\c 0 for infinite looping). + **/ + const CImgList& save_gif_external(const char *const filename, const float fps=25, + const unsigned int nb_loops=0) { + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + +#ifdef cimg_use_png +#define _cimg_save_gif_ext "png" +#else +#define _cimg_save_gif_ext "ppm" +#endif + + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_ext,filename_tmp._data); + if ((file=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::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_snprintf(command,command._width,"%s -delay %u -loop %u", + 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()); + CImg::string(command).move_to(filenames); + CImg _command = filenames>'x'; + cimg_for(_command,p,char) if (!*p) *p = ' '; + _command.back() = 0; + + cimg::system(_command); + 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'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for_in(*this,1,filenames._width - 1,l) std::remove(filenames[l]); + return *this; + } + + //! Save list as a YUV image sequence file. + /** + \param filename Filename to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(const char *const filename=0, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(0,filename,chroma_subsampling,is_rgb); + } + + //! Save image sequence into a YUV file. + /** + \param file File to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(file,0,chroma_subsampling,is_rgb); + } + + const CImgList& _save_yuv(std::FILE *const file, const char *const filename, + const unsigned int chroma_subsampling, + const bool is_rgb) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified filename is (null).", + cimglist_instance); + if (chroma_subsampling!=420 && chroma_subsampling!=422 && chroma_subsampling!=444) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified chroma subsampling %u is invalid, for file '%s'.", + cimglist_instance, + chroma_subsampling,filename?filename:"(FILE*)"); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + const unsigned int + cfx = chroma_subsampling==420 || chroma_subsampling==422?2:1, + cfy = chroma_subsampling==420?2:1, + w0 = (*this)[0]._width, h0 = (*this)[0]._height, + width0 = w0 + (w0%cfx), height0 = h0 + (h0%cfy); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + cimglist_for(*this,l) { + const CImg &frame = (*this)[l]; + cimg_forZ(frame,z) { + CImg YUV; + if (sizeof(T)==1 && !is_rgb && + frame._width==width0 && frame._height==height0 && frame._depth==1 && frame._spectrum==3) + YUV.assign((unsigned char*)frame._data,width0,height0,1,3,true); + else { + YUV = frame.get_slice(z); + if (YUV._width!=width0 || YUV._height!=height0) YUV.resize(width0,height0,1,-100,0); + if (YUV._spectrum!=3) YUV.resize(-100,-100,1,3,YUV._spectrum==1?1:0); + if (is_rgb) YUV.RGBtoYCbCr(); + } + if (chroma_subsampling==444) + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height*3,nfile); + else { + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height,nfile); + CImg UV = YUV.get_channels(1,2); + UV.resize(UV._width/cfx,UV._height/cfy,1,2,2); + cimg::fwrite(UV._data,(size_t)UV._width*UV._height*2,nfile); + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param filename Filename to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(const char *const filename, const bool is_compressed=false) const { + return _save_cimg(0,filename,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, const bool is_compressed) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "save_cimg(): Unable to save compressed data in file '%s' unless zlib is enabled, " + "saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); +#endif + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little"; + if (std::strstr(ptype,"unsigned")==ptype) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",_width,ptype + 9,etype); + else std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = siz + siz/100 + 16; + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "save_cimg(): Failed to save compressed data for file '%s', saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); + else { + std::fprintf(nfile," #%lu\n",csiz); + cimg::fwrite(cbuf,csiz,nfile); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + if (failed_to_compress) { // Write in a non-compressed way + std::fputc('\n',nfile); + cimg::fwrite(ref._data,ref.size(),nfile); + } + } else std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param file File to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(std::FILE *file, const bool is_compressed=false) const { + return _save_cimg(file,0,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { +#define _cimg_save_cimg_case(Ts,Tss) \ + if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l0) { \ + if (l=W || y0>=H || z0>=D || c0>=D) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const CImg& img = (*this)[l - n0]; \ + const T *ptrs = img._data; \ + const unsigned int \ + x1 = x0 + img._width - 1, \ + y1 = y0 + img._height - 1, \ + z1 = z0 + img._depth - 1, \ + c1 = c0 + img._spectrum - 1, \ + nx1 = x1>=W?W - 1:x1, \ + ny1 = y1>=H?H - 1:y1, \ + nz1 = z1>=D?D - 1:z1, \ + nc1 = c1>=C?C - 1:c1; \ + CImg raw(1 + nx1 - x0); \ + const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int v = 1 + nc1 - c0; v; --v) { \ + const unsigned int skipzb = z0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + nz1 - z0; z; --z) { \ + const unsigned int skipyb = y0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + ny1 - y0; y; --y) { \ + const unsigned int skipxb = x0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + raw.assign(ptrs, raw._width); \ + ptrs+=img._width; \ + if (endian) cimg::invert_endianness(raw._data,raw._width); \ + cimg::fwrite(raw._data,raw._width,nfile); \ + const unsigned int skipxe = (W - 1 - nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "save_cimg(): Empty instance, for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+"); + bool saved = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + const unsigned int lmax = std::min(N,n0 + _width); + _cimg_save_cimg_case("bool",bool); + _cimg_save_cimg_case("unsigned_char",unsigned char); + _cimg_save_cimg_case("uchar",unsigned char); + _cimg_save_cimg_case("char",char); + _cimg_save_cimg_case("unsigned_short",unsigned short); + _cimg_save_cimg_case("ushort",unsigned short); + _cimg_save_cimg_case("short",short); + _cimg_save_cimg_case("unsigned_int",unsigned int); + _cimg_save_cimg_case("uint",unsigned int); + _cimg_save_cimg_case("int",int); + _cimg_save_cimg_case("unsigned_int64",uint64T); + _cimg_save_cimg_case("uint64",uint64T); + _cimg_save_cimg_case("int64",int64T); + _cimg_save_cimg_case("float",float); + _cimg_save_cimg_case("double",double); + if (!saved) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): Unsupported data type '%s' for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)",str_pixeltype._data); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param filename Filename to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(0,filename,n0,x0,y0,z0,c0); + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param file File to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(file,0,n0,x0,y0,z0,c0); + } + + static void _save_empty_cimg(std::FILE *const file, const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) { + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT siz = (ulongT)dx*dy*dz*dc*sizeof(T); + std::fprintf(nfile,"%u %s\n",nb,pixel_type()); + for (unsigned int i=nb; i; --i) { + std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dc); + for (ulongT off = siz; off; --off) std::fputc(0,nfile); + } + if (!file) cimg::fclose(nfile); + } + + //! Save empty (non-compressed) .cimg file with specified dimensions. + /** + \param filename Filename to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc); + } + + //! Save empty .cimg file with specified dimensions. + /** + \param file File to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(file,0,nb,dx,dy,dz,dc); + } + + //! Save list as a TIFF file. + /** + \param filename Filename to write data to. + \param compression_type Compression mode used to write data. + \param voxel_size Voxel size, to be stored in the filename. + \param description Description, to be stored in the filename. + \param use_bigtiff Allow to save big tiff files (>4Gb). + **/ + const CImgList& save_tiff(const char *const filename, const unsigned int compression_type=0, + const float *const voxel_size=0, const char *const description=0, + const bool use_bigtiff=true) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_tiff(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_tiff + if (_width==1) _data[0].save_tiff(filename,compression_type,voxel_size,description,use_bigtiff); + else cimglist_for(*this,l) { + CImg nfilename(1024); + cimg::number_filename(filename,l,6,nfilename); + _data[l].save_tiff(nfilename,compression_type,voxel_size,description,use_bigtiff); + } +#else + ulongT siz = 0; + cimglist_for(*this,l) siz+=_data[l].size(); + const bool _use_bigtiff = use_bigtiff && sizeof(siz)>=8 && siz*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + for (unsigned int dir = 0, l = 0; l<_width; ++l) { + const CImg& img = (*this)[l]; + cimg_forZ(img,z) img._save_tiff(tif,dir++,z,compression_type,voxel_size,description); + } + TIFFClose(tif); + } else + throw CImgIOException(_cimglist_instance + "save_tiff(): Failed to open stream for file '%s'.", + cimglist_instance, + filename); +#endif + return *this; + } + + //! Save list as a gzipped file, using external tool 'gzip'. + /** + \param filename Filename to write data to. + **/ + const CImgList& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Specified filename is (null).", + cimglist_instance); + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimglist_instance, + filename); + else cimg::fclose(file); + std::remove(filename_tmp); + } else { + CImg nfilename(1024); + cimglist_for(*this,l) { + cimg::number_filename(body,l,6,nfilename); + if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext); + _data[l].save_gzip_external(nfilename); + } + } + return *this; + } + + //! Save image sequence, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImgList& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { +#ifndef cimg_use_opencv + cimg::unused(codec,keep_open); + return save_ffmpeg_external(filename,fps); +#else + static CvVideoWriter *writers[32] = { 0 }; + static CImgList 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; + 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; + 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++); + } + } + cvWriteFrame(writers[index],ipl); + } + cvReleaseImage(&ipl); + cimg::mutex(9,0); + } + + cimg::mutex(9); + if (!keep_open) { + cvReleaseVideoWriter(&writers[index]); + writers[index] = 0; + filenames[index].assign(); + sizes(index,0) = sizes(index,1) = 0; + last_used_index = -1; + } else last_used_index = index; + cimg::mutex(9,0); + + return *this; +#endif + } + + //! Save image sequence, using the external tool 'ffmpeg'. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression. + \param bitrate Output bitrate + **/ + const CImgList& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + const char + *const ext = cimg::split_filename(filename), + *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video"; + + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + cimglist_for(*this,l) if (!_data[l].is_sameXYZ(_data[0])) + throw CImgInstanceException(_cimglist_instance + "save_ffmpeg_external(): Invalid instance dimensions for file '%s'.", + cimglist_instance, + filename); + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data); + if ((file=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.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_snprintf(command,command._width,"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\"", + cimg::ffmpeg_path(), + CImg::string(filename_tmp)._system_strescape().data(), + _codec,bitrate,fps, + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_ffmpeg_external(): Failed to save file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for(*this,l) std::remove(filenames[l]); + return *this; + } + + //! Serialize a CImgList instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "get_serialize(): Unable to compress data unless zlib is enabled, " + "storing them uncompressed.", + cimglist_instance); +#endif + CImgList 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::string(tmpstr,false).move_to(stream); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_snprintf(tmpstr,tmpstr._width,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + CImg::string(tmpstr,false).move_to(stream); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = (ulongT)compressBound(siz); + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "get_serialize(): Failed to save compressed data, saving them uncompressed.", + cimglist_instance); + else { + cimg_snprintf(tmpstr,tmpstr._width," #%lu\n",csiz); + CImg::string(tmpstr,false).move_to(stream); + CImg(cbuf,csiz).move_to(stream); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + 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); + } + } else CImg::string("\n",false).move_to(stream); + } + cimglist_apply(stream,unroll)('y'); + return stream>'y'; + } + + //! Unserialize a CImg serialized buffer into a CImgList list. + template + static CImgList get_unserialize(const CImg& buffer) { +#ifdef cimg_use_zlib +#define _cimgz_unserialize_case(Tss) { \ + Bytef *cbuf = 0; \ + if (sizeof(t)!=1 || cimg::type::string()==cimg::type::string()) { \ + cbuf = new Bytef[csiz]; Bytef *_cbuf = cbuf; \ + for (ulongT i = 0; i::get_unserialize(): Unable to unserialize compressed data " \ + "unless zlib is enabled.", \ + pixel_type()); +#endif + +#define _cimg_unserialize_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l::unserialize(): Invalid specified size (%u,%u,%u,%u) for " \ + "image #%u in serialized buffer.", \ + pixel_type(),W,H,D,C,l); \ + if (W*H*D*C>0) { \ + CImg 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 { \ + 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 (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ + } \ + } \ + loaded = true; \ + } + + if (buffer.is_empty()) + 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(); + bool loaded = false, endian = cimg::endianness(), is_bytef = false; + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + uint64T csiz; + int i, err; + cimg::unused(is_bytef); + do { + j = 0; while ((i=(int)*stream)!='\n' && stream::get_unserialize(): CImg header not found in serialized buffer.", + pixel_type()); + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + res.assign(N); + _cimg_unserialize_case("bool",bool); + _cimg_unserialize_case("unsigned_char",unsigned char); + _cimg_unserialize_case("uchar",unsigned char); + _cimg_unserialize_case("char",char); + _cimg_unserialize_case("unsigned_short",unsigned short); + _cimg_unserialize_case("ushort",unsigned short); + _cimg_unserialize_case("short",short); + _cimg_unserialize_case("unsigned_int",unsigned int); + _cimg_unserialize_case("uint",unsigned int); + _cimg_unserialize_case("int",int); + _cimg_unserialize_case("unsigned_int64",uint64T); + _cimg_unserialize_case("uint64",uint64T); + _cimg_unserialize_case("int64",int64T); + _cimg_unserialize_case("float",float); + _cimg_unserialize_case("double",double); + if (!loaded) + throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unsupported pixel type '%s' defined " + "in serialized buffer.", + pixel_type(),str_pixeltype._data); + return res; + } + + //@} + //---------------------------------- + // + //! \name Others + //@{ + //---------------------------------- + + //! Return a CImg pre-defined font with requested height. + /** + \param font_height Height of the desired font (exact match for 13,23,53,103). + \param is_variable_width Decide if the font has a variable (\c true) or fixed (\c false) width. + **/ + static const CImgList& 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. + CImg &basef = font_base[ind]; + if (!basef) { + basef.assign(256*font_width[ind],font_height[ind]); + + 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). + 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 }; + ind = ~0U; + for (int i = 0; i<16; ++i) + 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 + fonts->assign(); + std::memmove(fonts,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 + } + CImgList &font = fonts[ind]; + + // Render requested font. + if (!font) { + const unsigned int padding_x = requested_height<=64?1U:requested_height<=128?2U:3U; + is_variable_widths[ind] = is_variable_width; + font = basef.get_split('x',256); + 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, + font[0]._height>requested_height?2: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_forXY(letter,x,y) if (letter(x,y)) { if (xxmax) xmax = x; } + if (xmin<=xmax) letter.crop(xmin,0,xmax,letter._height - 1); + } + font[' '].resize(font['f']._width,-100,-100,-100,0); + if (' ' + 256& FFT(const char axis, const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],axis,invert); + return *this; + } + + //! Compute a 1-D Fast Fourier Transform, along specified axis \newinstance. + CImgList get_FFT(const char axis, const bool invert=false) const { + return CImgList(*this,false).FFT(axis,invert); + } + + //! Compute a n-d Fast Fourier Transform. + /** + \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed. + **/ + CImgList& FFT(const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],invert); + return *this; + } + + //! Compute a 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. + /** + **/ + CImgList& reverse_object3d() { + cimglist_for(*this,l) { + CImg& p = _data[l]; + switch (p.size()) { + case 2 : case 3: cimg::swap(p[0],p[1]); break; + case 6 : cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break; + case 9 : cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break; + case 4 : cimg::swap(p[0],p[1],p[2],p[3]); break; + case 12 : cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break; + } + } + return *this; + } + + //! Reverse primitives orientations of a 3D object \newinstance. + CImgList get_reverse_object3d() const { + return (+*this).reverse_object3d(); + } + + //@} + }; // struct CImgList { ... + + /* + #--------------------------------------------- + # + # Completion of previously declared functions + # + #---------------------------------------------- + */ + +namespace cimg { + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + // (throw a CImgIOException when macro 'cimg_use_r' is defined). + inline FILE* _stdin(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdin; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdin(): Reference to 'stdin' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stdout(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdout; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdout(): Reference to 'stdout' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stderr(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stderr; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stderr(): Reference to 'stderr' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + // Open a file (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 + // Try alternative method, with wide-character string. + int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0); + if (err) { + CImg wpath(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(err); + if (MultiByteToWideChar(CP_UTF8,0,mode,-1,wmode,err)) + return _wfopen(wpath,wmode); + } + } + } +#endif + return 0; + } + + //! 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 + _cimg_test_temporary_path("/tmp"); + _cimg_test_temporary_path("/var/tmp"); +#endif + if (!path_found) { + *s_path = 0; + std::strncpy(tmp,filename_tmp,tmp._width - 1); + if ((file=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"); + } + } + 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). +#if !defined(__INTEL_COMPILER) + if (!SHGetSpecialFolderPathA(0,s_path,0x0026,false)) { + const char *const pfPath = std::getenv("PROGRAMFILES"); + if (pfPath) std::strncpy(s_path,pfPath,MAX_PATH - 1); + else std::strcpy(s_path,"C:\\PROGRA~1"); + } +#else + std::strcpy(s_path,"C:\\PROGRA~1"); +#endif + } + cimg::mutex(7,0); + return s_path; + } +#endif + + //! Get/set path to the ImageMagick's \c convert binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c convert binary. + **/ + inline const char* imagemagick_path(const char *const user_path, const bool reinit_path) { + static CImg 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=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 + 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 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 + const char *const pf_path = 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\\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\\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\\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,"%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,"%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,"%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:\\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:\\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:\\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,"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,"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,"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:\\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:\\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:\\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:\\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 + 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); + } + 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; +#if cimg_OS==2 + const char *const pf_path = 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,"./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 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=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 \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=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,"./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 \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=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,"./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); + } + 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=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 \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=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,"./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); + } + cimg::mutex(7,0); + return s_path; + } + + //! Get/set path to the \c curl binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c curl binary. + **/ + inline const char *curl_path(const char *const user_path, const bool reinit_path) { + static CImg s_path; + cimg::mutex(7); + if (reinit_path) s_path.assign(); + if (user_path) { + if (!s_path) s_path.assign(1024); + std::strncpy(s_path,user_path,1023); + } else if (!s_path) { + s_path.assign(1024); + bool path_found = false; + std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + std::strcpy(s_path,".\\curl.exe"); + if ((file=cimg::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; } + } + if (!path_found) std::strcpy(s_path,"curl"); +#endif + winformat_string(s_path); + } + cimg::mutex(7,0); + return s_path; + } + + // [internal] Sorting function, used by cimg::files(). + inline int _sort_files(const void* a, const void* b) { + const CImg &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 +#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); + } + } + } 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); +#endif + + // Sort resulting list by lexicographic order. + if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg),_sort_files); + + return res; + } + + //! Try to guess format from an image file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + \return C-string containing the guessed file format, or \c 0 if nothing has been guessed. + **/ + inline const char *ftype(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::ftype(): Specified filename is (null)."); + static const char + *const _pnm = "pnm", + *const _pfm = "pfm", + *const _bmp = "bmp", + *const _gif = "gif", + *const _jpg = "jpg", + *const _off = "off", + *const _pan = "pan", + *const _png = "png", + *const _tif = "tif", + *const _inr = "inr", + *const _dcm = "dcm"; + const char *f_type = 0; + CImg 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; } + } + f_type = 0; break; + } + } + } 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)."); + + 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); +#endif + + CImg command((unsigned int)std::strlen(url) + 64); + cimg::unused(try_fallback); + + // Try with 'curl' first. + if (timeout) { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,timeout,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } else { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } 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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::std_fopen(filename_local,"rb"))) + throw CImgIOException("cimg::load_network(): Failed to load file '%s' with external commands " + "'wget' or 'curl'.",url); + cimg::fclose(file); + + // Try gunzip it. + cimg_snprintf(command,command._width,"%s.gz",filename_local); + std::rename(filename_local,command); + cimg_snprintf(command,command._width,"%s --quiet \"%s.gz\"", + gunzip_path(),filename_local); + cimg::system(command); + file = 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"); + } + } + 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.), + 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; + } + + // 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 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."); +#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; +#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); + } + + // End of cimg:: namespace +} + + // End of cimg_library:: namespace +} + +//! Short alias name. +namespace cil = cimg_library_suffixed; + +#ifdef _cimg_redefine_False +#define False 0 +#endif +#ifdef _cimg_redefine_True +#define True 1 +#endif +#ifdef _cimg_redefine_min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_PI +#define PI 3.141592653589793238462643383 +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif +// Local Variables: +// mode: c++ +// End: diff --git a/tutorial_3/CMakeLists.txt b/tutorial_3/CMakeLists.txt new file mode 100644 index 0000000..ed304bd --- /dev/null +++ b/tutorial_3/CMakeLists.txt @@ -0,0 +1,48 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# sets versions and names of project +cmake_minimum_required(VERSION 3.7) +project(opencl_tutorial_1) + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) +# specify our compiled binaries name +set(BINARY_NAME tut3) +# specify all our cpp files names +set(CPP_FILES tut3.cpp utils.h CImg.h) + +# build our project executable, and link opencl lib +find_package(OpenCL 2 REQUIRED) +# find x11 for linux desktop +find_package(X11 REQUIRED) +# get thread library used in the tutorials +find_package(Threads REQUIRED) + +add_executable(${BINARY_NAME} ${CPP_FILES}) +target_link_libraries(tut3 OpenCL::OpenCL ${X11_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + +# copy kernel files over to binary directory so it can access them +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kernels/my_kernels.cl ${CMAKE_CURRENT_BINARY_DIR}/kernels/my_kernels.cl COPYONLY) +# configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/test.ppm ${CMAKE_CURRENT_BINARY_DIR}/test.ppm COPYONLY) diff --git a/tutorial_3/Dockerfile b/tutorial_3/Dockerfile new file mode 100644 index 0000000..75b2ad6 --- /dev/null +++ b/tutorial_3/Dockerfile @@ -0,0 +1,36 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +FROM nvidia/opencl + +# setting up the place, making sure everything is installed, and getting the +# pesky CL/cl2.hpp +RUN mkdir -p /opencl_tutorials && \ + apt update -y && \ + apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev && \ + git clone https://github.com/KhronosGroup/opencl-clhpp && \ + cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ + rm -r /opencl-clhpp && \ + echo "cd /opencl_tutorials" >> /root/.bashrc + +COPY . /opencl_tutorials/ diff --git a/tutorial_3/Tutorial 3.vcxproj b/tutorial_3/Tutorial 3.vcxproj deleted file mode 100644 index 03b040a..0000000 --- a/tutorial_3/Tutorial 3.vcxproj +++ /dev/null @@ -1,194 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - Debug - x64 - - - Release - x64 - - - - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2} - Tutorial 1 - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - Unicode - true - - - Application - true - v142 - Unicode - - - Application - false - v142 - Unicode - true - - - - - - - - - - - - - - - - - false - - - true - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - - 0 - - - $(INTELOCLSDKROOT)include;%(AdditionalIncludeDirectories) - Win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x86;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - If exist "*.cl" copy "*.cl" "$(OutDir)\" - - - - - 0 - - - $(INTELOCLSDKROOT)include;%(AdditionalIncludeDirectories) - Win32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x86;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - If exist "*.cl" copy "*.cl" "$(OutDir)\" - - - - - 0 - - - $(INTELOCLSDKROOT)include;..\include;%(AdditionalIncludeDirectories) - __x86_64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MaxSpeed - false - Default - MultiThreadedDLL - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - true - true - - - xcopy /s /i /y "kernels" "$(OutDir)kernels" - - - - - 0 - - - $(INTELOCLSDKROOT)include;..\include;%(AdditionalIncludeDirectories) - __x86_64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - Disabled - false - EnableFastChecks - MultiThreadedDebugDLL - Level3 - ProgramDatabase - - - - $(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - true - - - xcopy /s /i /y "kernels" "$(OutDir)kernels" - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tutorial_3/Tutorial 3.vcxproj.filters b/tutorial_3/Tutorial 3.vcxproj.filters deleted file mode 100644 index 9fb61b1..0000000 --- a/tutorial_3/Tutorial 3.vcxproj.filters +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - {bc7a8fec-44e6-4521-a22b-e4595d5b0ed1} - - - {84aac619-8794-4eec-81d5-212689c31225} - - - - - kernels - - - - - include - - - \ No newline at end of file diff --git a/tutorial_3/build.sh b/tutorial_3/build.sh new file mode 100755 index 0000000..702c299 --- /dev/null +++ b/tutorial_3/build.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# @Author: GeorgeRaven +# @Date: 2020-02-24T17:54:52+00:00 +# @Last modified by: archer +# @Last modified time: 2020-02-24T17:58:22+00:00 +# @License: please see LICENSE file in project root + +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +rm -r build/ +mkdir -p build +cd build/ +cmake .. +make +./tut3 +cd .. diff --git a/tutorial_3/Tutorial 3.cpp b/tutorial_3/tut3.cpp similarity index 94% rename from tutorial_3/Tutorial 3.cpp rename to tutorial_3/tut3.cpp index 9b9aede..4592ca0 100644 --- a/tutorial_3/Tutorial 3.cpp +++ b/tutorial_3/tut3.cpp @@ -1,7 +1,17 @@ +/** + * @Author: GeorgeRaven + * @Date: 2020-02-24T17:47:58+00:00 + * @Last modified by: archer + * @Last modified time: 2020-02-24T17:57:21+00:00 + * @License: please see LICENSE file in project root + */ + + + #include #include -#include "Utils.h" +#include "utils.h" void print_help() { std::cerr << "Application usage:" << std::endl; @@ -114,4 +124,4 @@ int main(int argc, char **argv) { } return 0; -} \ No newline at end of file +} diff --git a/tutorial_3/utils.h b/tutorial_3/utils.h new file mode 100644 index 0000000..1539094 --- /dev/null +++ b/tutorial_3/utils.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define CL_HPP_MINIMUM_OPENCL_VERSION 120 +#define CL_HPP_TARGET_OPENCL_VERSION 120 +#define CL_HPP_ENABLE_EXCEPTIONS + +#include + +using namespace std; + +template +ostream& operator<< (ostream& out, const vector& v) { + if (!v.empty()) { + out << '['; + copy(v.begin(), v.end(), ostream_iterator(out, ", ")); + out << "\b\b]"; + } + return out; +} + +string GetPlatformName(int platform_id) { + vector platforms; + cl::Platform::get(&platforms); + return platforms[platform_id].getInfo(); +} + +string GetDeviceName(int platform_id, int device_id) { + vector platforms; + cl::Platform::get(&platforms); + vector devices; + platforms[platform_id].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + return devices[device_id].getInfo(); +} + +const char *getErrorString(cl_int error) { + switch (error){ + // run-time and JIT compiler errors + case 0: return "CL_SUCCESS"; + case -1: return "CL_DEVICE_NOT_FOUND"; + case -2: return "CL_DEVICE_NOT_AVAILABLE"; + case -3: return "CL_COMPILER_NOT_AVAILABLE"; + case -4: return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; + case -5: return "CL_OUT_OF_RESOURCES"; + case -6: return "CL_OUT_OF_HOST_MEMORY"; + case -7: return "CL_PROFILING_INFO_NOT_AVAILABLE"; + case -8: return "CL_MEM_COPY_OVERLAP"; + case -9: return "CL_IMAGE_FORMAT_MISMATCH"; + case -10: return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; + case -11: return "CL_BUILD_PROGRAM_FAILURE"; + case -12: return "CL_MAP_FAILURE"; + case -13: return "CL_MISALIGNED_SUB_BUFFER_OFFSET"; + case -14: return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST"; + case -15: return "CL_COMPILE_PROGRAM_FAILURE"; + case -16: return "CL_LINKER_NOT_AVAILABLE"; + case -17: return "CL_LINK_PROGRAM_FAILURE"; + case -18: return "CL_DEVICE_PARTITION_FAILED"; + case -19: return "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; + + // compile-time errors + case -30: return "CL_INVALID_VALUE"; + case -31: return "CL_INVALID_DEVICE_TYPE"; + case -32: return "CL_INVALID_PLATFORM"; + case -33: return "CL_INVALID_DEVICE"; + case -34: return "CL_INVALID_CONTEXT"; + case -35: return "CL_INVALID_QUEUE_PROPERTIES"; + case -36: return "CL_INVALID_COMMAND_QUEUE"; + case -37: return "CL_INVALID_HOST_PTR"; + case -38: return "CL_INVALID_MEM_OBJECT"; + case -39: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; + case -40: return "CL_INVALID_IMAGE_SIZE"; + case -41: return "CL_INVALID_SAMPLER"; + case -42: return "CL_INVALID_BINARY"; + case -43: return "CL_INVALID_BUILD_OPTIONS"; + case -44: return "CL_INVALID_PROGRAM"; + case -45: return "CL_INVALID_PROGRAM_EXECUTABLE"; + case -46: return "CL_INVALID_KERNEL_NAME"; + case -47: return "CL_INVALID_KERNEL_DEFINITION"; + case -48: return "CL_INVALID_KERNEL"; + case -49: return "CL_INVALID_ARG_INDEX"; + case -50: return "CL_INVALID_ARG_VALUE"; + case -51: return "CL_INVALID_ARG_SIZE"; + case -52: return "CL_INVALID_KERNEL_ARGS"; + case -53: return "CL_INVALID_WORK_DIMENSION"; + case -54: return "CL_INVALID_WORK_GROUP_SIZE"; + case -55: return "CL_INVALID_WORK_ITEM_SIZE"; + case -56: return "CL_INVALID_GLOBAL_OFFSET"; + case -57: return "CL_INVALID_EVENT_WAIT_LIST"; + case -58: return "CL_INVALID_EVENT"; + case -59: return "CL_INVALID_OPERATION"; + case -60: return "CL_INVALID_GL_OBJECT"; + case -61: return "CL_INVALID_BUFFER_SIZE"; + case -62: return "CL_INVALID_MIP_LEVEL"; + case -63: return "CL_INVALID_GLOBAL_WORK_SIZE"; + case -64: return "CL_INVALID_PROPERTY"; + case -65: return "CL_INVALID_IMAGE_DESCRIPTOR"; + case -66: return "CL_INVALID_COMPILER_OPTIONS"; + case -67: return "CL_INVALID_LINKER_OPTIONS"; + case -68: return "CL_INVALID_DEVICE_PARTITION_COUNT"; + + // extension errors + case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; + case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; + case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; + case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; + case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; + case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; + default: return "Unknown OpenCL error"; + } +} + +void CheckError(cl_int error) { + if (error != CL_SUCCESS) { + cerr << "OpenCL call failed with error " << getErrorString(error) << endl; + exit(1); + } +} + +void AddSources(cl::Program::Sources& sources, const string& file_name) { + //TODO: add file existence check + ifstream file(file_name); + string* source_code = new string(istreambuf_iterator(file), (istreambuf_iterator())); + sources.push_back((*source_code).c_str()); +} + +string ListPlatformsDevices() { + + stringstream sstream; + vector platforms; + + cl::Platform::get(&platforms); + + sstream << "Found " << platforms.size() << " platform(s):" << endl; + + for (unsigned int i = 0; i < platforms.size(); i++) + { + sstream << "\nPlatform " << i << ", " << platforms[i].getInfo() << ", version: " << platforms[i].getInfo(); + + sstream << ", vendor: " << platforms[i].getInfo() << endl; + // sstream << ", extensions: " << platforms[i].getInfo() << endl; + + vector devices; + + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + sstream << "\n Found " << devices.size() << " device(s):" << endl; + + for (unsigned int j = 0; j < devices.size(); j++) + { + sstream << "\n Device " << j << ", " << devices[j].getInfo() << ", version: " << devices[j].getInfo(); + + sstream << ", vendor: " << devices[j].getInfo(); + cl_device_type device_type = devices[j].getInfo(); + sstream << ", type: "; + if (device_type & CL_DEVICE_TYPE_DEFAULT) + sstream << "DEFAULT "; + if (device_type & CL_DEVICE_TYPE_CPU) + sstream << "CPU "; + if (device_type & CL_DEVICE_TYPE_GPU) + sstream << "GPU "; + if (device_type & CL_DEVICE_TYPE_ACCELERATOR) + sstream << "ACCELERATOR "; + sstream << ", compute units: " << devices[j].getInfo(); + sstream << ", clock freq [MHz]: " << devices[j].getInfo(); + sstream << ", max memory size [B]: " << devices[j].getInfo(); + sstream << ", max allocatable memory [B]: " << devices[j].getInfo(); + + sstream << endl; + } + } + sstream << "----------------------------------------------------------------" << endl; + + return sstream.str(); +} + +cl::Context GetContext(int platform_id, int device_id) { + vector platforms; + + cl::Platform::get(&platforms); + + for (unsigned int i = 0; i < platforms.size(); i++) + { + vector devices; + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + for (unsigned int j = 0; j < devices.size(); j++) + { + if ((i == platform_id) && (j == device_id)) + return cl::Context({ devices[j] }); + } + } + + return cl::Context(); +} + +enum ProfilingResolution { + PROF_NS = 1, + PROF_US = 1000, + PROF_MS = 1000000, + PROF_S = 1000000000 +}; + +string GetFullProfilingInfo(const cl::Event& evnt, ProfilingResolution resolution) { + stringstream sstream; + + sstream << "Queued " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Submitted " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Executed " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Total " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + + switch (resolution) { + case PROF_NS: sstream << " [ns]"; break; + case PROF_US: sstream << " [us]"; break; + case PROF_MS: sstream << " [ms]"; break; + case PROF_S: sstream << " [s]"; break; + default: break; + } + + return sstream.str(); +} From a1e7d221202e081cc43882fb1d471af220446e8d Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 22:53:51 +0000 Subject: [PATCH 46/69] Added reductions file --- tutorial_3/reductions.pdf | Bin 0 -> 451042 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tutorial_3/reductions.pdf diff --git a/tutorial_3/reductions.pdf b/tutorial_3/reductions.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6cc36caccdc3277d4fd1300c17b2cd96fa8cb25e GIT binary patch literal 451042 zcmeFYXIvB8*ESsF2#TW8r3r$7^cs3OO78**9g*HaO6WyBigW~|H>CuU(0dV3>0Kb9 z7eRXOP0BmLa?Zcp?|VOAo=@-aBiS?Av)8(=b*;5$&m>HbWaKz`xxfTW4F?%#1blq- zJoHYcwglqh^xP^ojus{^Hr^KI^t`|xy#Sb(M}Uu>TY=tyo|i{Nh@MYCklu)%Tb-Uy zke*wXo|jidKv;mDTa6yrg#g`o1O<5MB_#AH_0R-ap)PkN{SJT20 zfMMZi=(%;UOwjWK{{Rh&j^-AgzyW|mA^sl+ES=~DgaJo1>A5wXoZJAy ze(Y!gq|%H07}5O)QGEYS4!5EVy@5Cnj|f;;nBUYw#N3R>!qS{ah?j@Q5-h?GHWM-z z6cUpB|L$Rg4S}nhi-m~;foF=Dsri$yR!>ZyJQ2Riptt(b-t1QUA)!jevo;X!Emrr- zGRx1K6X7ouxbb7zY}bPGj;1da(T(?Ox?vO(-0E}_&xQQ`_SaRg7c zD*rS)H(-7-(fCnVi6I75p?_-|8}pww|L;ft->Lln_y5lrAh^)!uQKq+#LB{zUWn(1 zbbsWMw27OEy_3~HlA90wkA(ZLiKPV;2>!nlP*Ct+b@}88R+L-jPcHLK@?YWo6DOgY zfY{=R-WP`9M`cwAsHe7{X*Bvd9vuCRheedkN zuJ*KEXQX+ydaz@FG7{QWLYAxl{Lh5v4hI_aoOn&d^f+=bJpaH6K{n;m9^bEZv-59I z_MJtl2ZXr%y|_f#V}#dTUQ>p+dGPQIslO41c)ovgmo^t4w8$!y{hy)+3g6%L@`0nH zlbb700R(?=CCGb0nIN{NYGMn5w3DNog(J{~?`Kg^1?q{(Lnmz2z@7pY5u_IuxTqaK z{Q-LZW?j?5)d}We2E+kdl4L#IT?M+x0ksP(^sCKJ5B}+k9IpW2z|Z4A^Jl!}!1UlBIJo5m>A^p+aLWnNgRvDB zJDvzV_(xg8ntbHqWTs`|1_0s)W)@%?u<&#PN}~gyp@%=;rGLIFVkyy8H?_4e`{AXo zFg@Q7$AFQrgK4?Bz|7o!P~i$#h`rF$X3Vpfvy1I3ExHj|uQ+B|INK{|dULB&R3`!odN7aDYG1 z`4~tVg!ji~yvu*!;a$eV$H%)uaEpN8>Qw^L8#k}tq9DC}n}U>_oQjr-j*6O*hMb(9 zgPxI@g_Vt!l8%#y^F9yLeb)QfL2&T#@d>UFkPr}%+@~U^y8nN?oqq%oUBOAZq=AdW z0J=nkgG+>S-UOlnoV;`i7x47=b{X#vd>q^>mw-|vk}&Lvy` z4G}&u2|2}GUa0ol*nUznz6Tl*6X&Ry_k9Blf zF)>SrYJexe_&=@V{Ba2v;0gGUiwIy47Y7dy@a|_Hz&Oz# zmx)R5@@kOqKhQLBCS%|ec=$Z3c=LP|bRFn-i3pbnBn8@2_=)Dmt!XF~p--5x#CMVh zfu_;A4#is8M$KCyx=R{R{Wh55hluu$)mqig3Ny$IUkkFB^6i?o@aJ93%@-NxAbaz^ z0(C107)nu<(=sfq940s={$k25qoThgX-ZyQRSn%<+|*>t@}^C@rDcAe7cl{OSV*#2 z*yPK*|LL=#NyX7D^^<8Xo-yBT+U`M`DVIEBa(T8ljIQn#!P9NdQusuhi(~j#wgSf% z-Z(seS=H#Uv*=M?EK*&58!;gxy`mpE!}2DA{p*b6?e9-a?qJ6uEaHj0?NIM^J=G?i zsQy7d|BO(ZaN$d5sp}3uKe-|J(=;EfYHWlzO?n!ow!j89?OkRGIaDXwTpGi@>I_)? z^2?&R1^!9HgGOR}cdD@udPL2>H1ds^_SczOsGZUtRNNr`MqAnYKK8;A815^xO+QnA zgJn7t`w3ZPFZ~$)Rp$@@K>J|gnSpN*&_&J*pKSBOqo?s$U-5=-W2uvT?Xo8-_Gm5` zH9js`5_)F*E{)|UG%Q8O05oiOzZ>%_H{4axYzHKa8wQal6<${WgN)PR6?nwo2(bo# z`2VKk!r*JF0ZB&RAS?&Q00&r$X@0*f2C9|QHL2g-o_$^$IXKYzW~QE`$yZeH<3((C zsh^l!7^b9ra1P@B@^n4N@mrkK19A0F*iHb8rauG7Yv8+0DT)ptN|U~z{h@H-Um=I4 zYd=EP_H(>hJ{W-g=$4)RVfZ#lvoB-Fq1i8vf0+~;A5Iz{53}`v-kJly77|>sO@DOZ z)PD-5XW4yUnab;CR)i3kEVo&0nc>Di>B1`Eg5B%+fD+QA!?Ajh0c^^XZMOWP=%@KM z3p*9SCmIgt$`Uhiz;*S%`}~%HhY(_7I8S+|?YIN4QpnEBPiD&)5NPXff&K*u)E*FM zF(A-l#(>~ktAmMKdM8=V*lx{ZxW9Gm3Fx@Z{K6zX(1m*T!EZ7yJ_5)P$L0nfEccAT z>t^Koab9VtT#Hrv2(~04#F~?c$5)fhjyWiCrXgnglK|n)ybvW{nK&rxYUxWTottsHsqtj9< zr*jZ7#^)#wFKSCB!L$?q(dW_7e0zklJjsyrI<87FNo=K2MY6PtiZRw z`gZfW92z3?ur=EpV}t((cx7E9wm@ccnUSAgyZ|$3SQ^6FbH~hP8CA|JOY%zA&EXYY z&5ANfGvmP7djY#wB;NhK1@gz8&xBLlGpD4EXPqlg`*VaYjV;2orja2i&QC&mV1b@a zsY%-cr8Ha<>rPV0)S}(}LZTAtx#G1zMBb`-H@|)UrpOfx0j;`NsuXAewL#eu!P=Q0F9c@n{%OW~CwMwR-!tN1I z;n`sAgaff?sX>EnbR}afL_GC0(;pR6t9|G3e*GS_ckPv!ogwQd&Ycps$htzFZ?pw{ z8lAG5lb^k9X3d7{&=Aduq*q-7)Y2Lxr=1WxC;5}?$jotBm-h{;TEq}e`^fMC1Ju+= zH?)QKLjEg{ir=Sh>rM8LIeDki}k3|KROX{$_JB6w)PI~a_6>e2{wUGUQ z;a)tKu?y0MI=1Iq7ImyjSYGb?&uzk-uRTn)6>2+975tJi?Z6a5q z&Ppb4IML-EbiAhYnP7XNX~;wOU55YyQFeL`h6yIZ^%#6@>WdeKt8YyR8oUC}56C-b zc-JH$1v~Hf9I;tG%CVKCC_W9zn76cM+K7?WmKV{11T!%EHAOAH&KU7d+9S^iNTXd# z&Mi14Y3RMSO{&T~6VfT}rj4TFb@I?*H*p;h{6xi46YEp$Xr7AU4zaa2Ti5mPFOvXyo2!GCZ_p^;$iCsAdJxUPan3~))vQW*K zrmMB1=1;xeP|FB~l3jb=7+hKAG@$K~EnrtDv*0C_Ij&xkj1TFi&>CmWhDn&y&2>$j ze7IJhc9~NOX~#)asS)h_!Tw|v`tjrgJNZ)SqrENdJzgD8?<6bl1XN931n+fS148r! zQ}}>FSK@Wfsc3mFRi{DJELggz>F`yZLHG(w6;yG(CbmT6?Jgj`K?e8%OxZoz!{#YU z2Ubx3AjvaoIW1;G3KrEdT2r$sqb2i=%W(ibWXX`kLa8;8>3jdAVTAWoQ$lL_X_DY) zo7%y*$8!OBRe!x|wUyX82NiH*gX>y!IhvB(KnbdkBH4kF#?%#>9=RA3!TR7VRggjG z^tdvkMQxv4bgL%1o$V{Moi5&{tu_Q^DXmx6l9*5Rs@=T2Ct|al1|wZqQeNpezPXap zm{SVYanz}X%u_#COR7o8&0Rp`dh=bmy>w@94NL;ox~l|_HO%X)V1__tm3_w=jv?z! zmQUAYT+AI6tw{t8W}T~$`#8DordCEB^gFMS(c>6-Zzuf_^^+!ctwg!9p9&Nvu4Dw-trsve0fBxH;#cOy|K$-55i)FCcdwir{JwjXyU z3QZU?5$h1)SIbCVN7%_1)=^}zp?KLvjbOvI)B_0)NUzM?KoL`KNfD|D+Z+ed;6A## zvGf75bI|e~v&HFNf1E~jZ)BYV`Utsy4q^_J3-VhZ_fIGUQfOynsit#iDgGG_?CC$} zYY@(M_Klk_yW~&EZjSN#Y6jzfwq!gb2wh7U2sn&Op6Wt~Y9C5}3}9~9{Kk*|*k4%P z&vjV`K2uUo(wZCAtJUn#z?YsAIeKR92f_`U73L8L*evur=DS%xBfY0EL5HkW7TQyt zt@txylMA6X*tBDN40D%HmQpRTn-D*t!32;gA9e{+7L@I!4aGE^QQI!Xo`YB(+bpqU ze!=t`U;S`{q*z3pvv>J1*Tx<_=o7)(z%<3B07{{+h?-mdLn;$lh6F9{etLYT`W^F* zZzbY-&p}|Rvpw&+n@39~#sLq;1763hv*wmLZ4kGgh^bck%h$9z%emXBGv~A-v@{kc zG@dfWbFSyu7~7wN(j+nag;8{N_d(l{g3~?>+1XB>9_pk4L9fcOJ5af=)CDNNwkGvE zRf*)gl73QlR8pep6C!zlBm|NZ3T;6Am@CTS3vGO*(mHt4T?@AT2II)s ztNfr@An#*Uv9ip@(=WBKb?4`IGjy?9JcC<`b5zYaPoPs&eQq%8C+n*s2 z#U9lI5`4AJw5BdBufKn$m5`4yM%Q?!vlV8&0d3u+rpLcQx~IyOO5`)$at>;hKD=+g zqq7(e(~YN*5W9hT&cB((npk$z)#)&2S?X|K`$LWY`+R|kvzr7S-rrT@qZX!C=&0=5 z%c?Y1KEx068&f}iJ+wk2QGE`=r?!V>w}HM2XS}H}4h)s=1Jecd+bAf@*9^$UYo>-H z-|Ku@PlD}pA4@G8sm3gvg9e=6L;KSV3}$=}B?GFm5d`qqC>^d>fm=H>)vzv@?89fIP+IrMXs2)1-3CM4_&+Rq`9KTLD z7Pf^)ZuX1tJ+AoFuw!j*9XYwyxfF5^+Pf+FSN00GN6y}3&}GcWf5}zQyMYsCQ>m}k z^&_E61zVx%uX!Q5(b^J&-bwC#I(4}y^zsl}GuzohjW^qVsVvwiILY!Za@8`9IfkcM zjiGcv(I)E6q{9FclzM6y6~(7r_+0Dh5PEoWjN-uOtf1R1(nZO(g3Y}C@+4$4xgsuu zAlED{$$I{UOn0>0tc!$LZmut;PZL^BT#$A(N+?>Jv3?*tw_gJh6dKjnhPJUro3iiP z%(dRO5ZZwFq<^4{;OQvmtJ8Em+bY^JN*W?3`M}PJR?+V3LcAqZi8nCDq03}fE%Bo@ ziSKmEiruIfsP6G4bV07zd}Tc&ZsG6N+T1Tr>39u|;24Y2w}@VQ8soyIFIc_deX1Dk z*;yaD+3b)JSHI+|d%WisRnpVK14X1lPXCyt@iG*lE9W&ea5ZBl1{w4jg{9_^^wv##KnfeN##DudtAzUBv%MGr-(QS z3D0~v8T5H;=@hkY&dwALV!lAjg;F9^=Q{mJdq(Q1rdBX4JIQewx0e}pml@6T+`k)+ ze`$1x0H*vgBLcRKIkhQ`hwB}s?)j@K_l-v43ZQj8ROwc?tpan;K`G0*tqHbx$m~H% zqfv8_wuM4xqH7V6oPr0H#4BZ_h3LuUgv8I7c8Qkxt*7v^u_bdw*0tSY387RPPTS*K zjhht=e!Bh)Xmvfi@H@H%6k!ToI=PDZ%bHgph84qfoQe1`!eWM zHtPvEE1dV%;(&|>kt%No4ZD_RE+UCaup})ev?p;Lv%VVR^-k^h7IUovQKz=1(VlGp#JW=8LqCJ3p`ddhOb@mk zbJSf{p{$jPf|UdtPb|AH?31$DS>R2Uzk!i)6^%<#_e)sRq#VB=t2TcW9&GB9+tOMt zS@I%=dOA)X1)GWInh}Aj)ymRpM{$*TV+7ELYSG#dWeovaOunm*V0ln&P09g!{Cde~ z!kTkj$Grd;y<>n$N;tZb`804svZpi$<(<-1sXH>b3az7=+kGvPLil-~LZqBaqSTOQ zpCz|xFiFuazkJQi5%LA@QrxW)ZJFcb>ij4K-l}Ws+1FjGRqo>uBZ&0uQymW^l!Z@d zFjs3lP~-@$KlqF>Rw4saUDHpTM2aAdzl@>P_6)Og_niYhZ6l8YyEg~VL65QFP3ey< zdBG0HQ^-Z=Mm9WbUA}8;nH2BHHNWH!i$Jf&4%Sexxeun+XM9*yCZe1#6uUDh+NrGW zp4*~dD|pRpeh8Qhp)T6&PvE8->xJg4l~II!Ps+7MILfp~B{a1(KMyA&ryw$&;LXIM zW?FF@8WpqK6Z7SKGGb(4*%k8p#cFG}>MWZDZ6-4C5D)s}sH+TVrG1NvON`cRPu{tf zCSX?nmHDZ!@Sn0Z5oLS!=3ipB5q{|M6DBNddjCwnorTuUa>65idGcr8L zj>2@Gs3Ov#HoO&7?t9CTM_u|UcCtDw8YY3t-ZkyjG^AZnTW(m-q|9cr?Sw_S7K{uG$8o`3|oq3SVWU#=9zoTV=gE9Ae zRihi67A0zx-|nYd*EFvi74X6m8~lZsXEKteFzeo>3GZVI!(*;55DRVn_U{H32(SDuB*$@t@MwBHTX;H z3eNQ}Vz*kscPcQikYOj$3vu()MW&?&ccbowOvuv&RWFa=YWO!At{MHwV3voW&W;K2grrLc@9e#J#xXba}pgHHhjy*V+75HRi@69mnLdZZdgR)lLnU zM0oBTClk47BVQ}ZVLq06+^58tA!YU3^-XMt9e7Jl zxDPwnAi-aBmJ{AoGIa>fC0W$Celyig+;jbAHkz*YP>R|lU(NADe}KrVRJkZ~2lywN zu40AR<(16geA{9QSd`TW+lJMLzyi$0@hR+`1)`!IU@nLr`*%eQ_zr zFv_37(xjrNyWBE=ae+HYfiXKL8_u$%mXGD>L86`0<>dtXATOQiZvwBTs&p9JKpx zpZMJHzyMF_vuk#L{>v2z&fq@dXr4Dixd$0Vg*%9icyWWE-l}5^EUp#>QyZjNjMAx% zA-na>E`{@xT?f+=`KDQr+$wmGtM82J_0>s>ka5I4H!TSihPXf}s&Ip-hWlfJd!^jr z;{y0(VVa$VK%@smDG}Kcf{GSn_#R%jU>xu)eG?tphyO|Y#xXVx!Uk3Q8f!n)>rRN|X z=s9TEsJ)5hzgqcp`=#k`>-{3wvrV@Xy=AMeKlCtpG`etY)2mqc%i4@5z{ z1c3mG7ZU)A@Lza2znT&SLELLTpWI&8;#L9hR`GhN)G>_X(N6?M_QMH^zD)4&vs%-% zpr_skLWX9Iw8$dIC)^j%_Ou00EAhl)RR`#I8;{N8(~lz3*A?uGgVa{pezPbD3`~w4 zxbpe`!n{JJO(E{|cgIj?S_?AV%-oQ4K`|tgVLT)?+g)w}e3tHy@2tL=ejs2<5@c@R z<`qcikmb4-f-EYAdS^VI@!yKY!fgfM%7*kEGl$Mo9z5z|x%D@}{|GNYyko0{v|S$e zCr3aA5f>DEfIp6M#J!a|{F@Plp;5w5+Sk%>A~GtTo5!PUN75P?9PjnYOnN+>m@hl) zHOAXI2hkavWjqMty*dHViYN9{M%OQ7^sI@x=(mg#u<Xt7Nu}h z6V&LN*u((!UVp5`t$@`nFIKmXf0z;uLw<*00gKlT5I`JO#u`|em;w6Ye>~GgV)B>r zFjWW}zF-nOt1mNs0c-nwh8>@OD*r#?_ zKv==Nyc~LUI8RH|PPHH@DMO}x2VC8T*p3J9#w@yAkN&$Dd>s-_CpXnsG10In(a+~^ zCggzXuQ@=XMB3(D92s@j=1h}s4_38{#wfd@-`4+V6t8zOSH}7L z#j~8jT|KgjtSOoP7@6CNt&2)w(FMlB25PplWt+ zX^S0aGa?u5-Ph0Urx5HVDFGi7tp4eL_i9jne0Ou0Y`a0fsI7C8TU1u;;I%cdWL4Xs zp3P{uX>}XAcftdTe-k)g=pW|;7d+3O_FmvuzxGgE?-36(eaoB5TYF^ikEbqMTK8(! z?DIQh(U!q)m6JS`F9^pqNpnsfA)=Bx2Q{UuqJg4kAY+r8z`UB_{x^tBx=q2`SP&dn zr>({Hqr~pFo)89#V8i&kQylKAcU!hHs{mjw$_2{0DA^a^!$?%2FbTV2&USl^VpIKg zSXSs`s4T|LYa!i^J8u-PwuaLIrG%ld9OVL+5s!S!mhYE|?2w6FZ+C^P-NwK(NGO=K z*WQ|);Wg4s3zbm+Fx%o}E^A-!$YIK*H2G%4cU-s0Ft+Y)aYnVo-&)3&h}i|HwC~x=Kns(_OO7(xEwJhV zvIkI~CE$g%itpfFf`{t`)^rG||-t_e2cK&?3gY7Jn6>Ddo)U(~1zajRo% zl*Y6?TlePx`J6b~yhkVx*mJ zn8N@%)8lG;4}GNF%eTER^mR2BbBDK$_S8mW#<-J1Z-+)#W|>fVOX#UWy_tR{To_O_ z02y;#B;0>ijo|6EO#iHce*k!EwhrXSH-OTLUonbn^hn?yjMM3F70^J<2Ad_;#Pe=d z>jRJBGJx0CT3iZ68951TnupkLKM(OI zFOu)l@u$A5Z%ZE^Z}NmyUW)}wfaMwl z*xK&t-L4rA>FR?L`6!Pvuv;nVi!9%%pJC<*ossE^>H{CW zqI}uK(JHQ-{T_6iA#p4PO5Bp$C5+!m0YO71UFhU3Nk8JyUrdz1D}iGy8-pcD8h%?- znj#`?kZm2wq@suPELCT90SD~=)mTokN`afJy8!KnHszx?mT78YcTqaa;Gw=)2?Mqy zPvl)i)>-o?E-#7DByQ{Z4D0Dv+bX-}&KY&RkNa8dTS^SRmFrpOZ=N~Ekw2V5@bS%u zN4Ff#$+*&lgoKVg^t%Zal83$BNWLZ0u6tx&3so9qm`CFXiCeGSS^v}^Q5$bGHMcPqHnvmKqbbIa0xaN>LhpRMAlms13P^8^JMd%nJqYa8HDp9@{;0=W-3Q z4Z|pY!#D4(B|TGL>;)<`rDmn1%~*pEM+ zKPpxg3*WRI7nplhz-d+!t&qwoXE0jjp(S<4IS0+9&ZUrpat0AbeF5RA{0VqTwsu#o z=xcIIri6<=EGLpXl4W+Fe^&5Sed^mX0raX$BB^RP$;aR*RI!_c0n>}AQDkj@W=u`n zL;=!^QE=3qguyNcy5I#mE-~KF1pUiBpax5W5j-eRe0VBVLMEJCKdUst+MusL2?3tj zJAX-`Rg5fn+o$|!QFOc=Ox(4wzEW*fQ-6t2`Nr!rqV;r{)itP!e2-6iyKP>)KP%B2P2_Yzq`c!9&dqtztL`%DFa6>h)jMqzcJf&9L;rM>5Ln1cya!7tt z_pNU_xwVP&!7GG9N9E&y*cS2xl0yr$=aV}zfa}j3PTZ46u483Hx_kXyUU{#ohW3bP z4c*c>QVh|9Sb0Q=0rrD-Q;N!{q=-ZT6>mxqiLKI2s<{n4{>TnE+8 zN7-z4zDf1TWoHgjPU>F*p649FPhWfWJvhTlpHJSWU3mH0{ZE{WH7%~1P2^N+^5NA6 zVt(f&R~7%0)v1rFhf#3?z(Zbp?f_!MvzzW;L6?|#Dy>;|_xX+>>CW@h0{btd)XlhK z6%Xz>l6+kDU9O+_9?%8CHK50O{}0{#i-w-;T8nusj;beG4F_t3JDk!f0;zL7W=eN^ z>G-MLSr&+Tw68L)V`TT0)RZ)uCBBd@wfyw1`oWjItKTYGkFAzQ`@QHv^BjN)IxdJD zveqk0nOQRAn_hwL=dWu^1&V6Tr)?SG4UW0PwweN>NqP9Hy0a67xrqGZ?8FWAhwnXv zD_Ouw<3P3L`TQpiJqXv(h7zS7R^(@=Z^ zFXybzit=KVdTC}>`Vhf^SptYl;2Gcqb^}YM*)qU~(dEQ^_oI=9lXLAP+e&th^`o_C z$vy9vLTcEQ1~b`hzUG?g2q@}C$Qb9fD3fKr$Q1$ZkashGsm_Mm!0*D>LL7JElWR_G zr>WFM|0*0TAj;aw^`=@gD-#^j%`g9wQ9&uBnOfJpNkk=Dsz3kH(_hg6t*NklCAQa> z5DG=-JgYn!n|NRNtb9^mzObU;7JMhjpo~>VT_;QI?c10V>;7T1PO+=X9%fvKLN{NW z>_Pk3ve=RX8ltAtuQ8xi*B+gx!3gzzLhlWU0GF(#FMp&&)`9J>D1(WH}>>%kmHnwnJN;cs=dd- zr4L^xLQ4?InwE)1q#D`1I~fhwUR(E!SO;)1V{u6d z`IND|D4#${m7dB#b;3+I#lz#6V9e^a^P%gH9U;7ni`I)tyBS$IIT)uEjZD{VN*0D1 zcnut}rymp)_9qr42=u_L*YhxPaXG!wOVn4Hnkjs%qsfORk&VqVB(;%pHa^apTf6Cf zj@b$#syjFzqDN9?4%;7^+MRU1DZdvoZ~-kd?HAXu#W9`SpsN>Qd zbEW?XEjUSAW{{puV@9&J8%pYiW*g+-M3)N0i^p(&UKnJT2cl$o92{Qf?RS^tUHRl= ztAd(GXte$e9Q8tv)>y43P=YK#_%-UvyzZKci$q)pxdbYS?N2ns_%4HMIaNqG*#^2; zwyIKX6@;rr4+4cWgZ-QuJ(Mao8kISMzsVukwB25(R5|4)jtpV6`Z7E;6?H=wyM|nx zRXS{zhdr4m22<$FDm}7Ec?!dC2W2g7w`72cYTNv1EWTWD^$y~OkgFu5ow-d}%Va8zpfFv#mOnM($Eua!5S=RMo@L{Zg>&M-0CbI;@Ha>48+N(=o@>Lkf4f^q7Xza^vkAOaK124;P7FW3B&CQKXo>Mdj*`qyKjv^74 zAx3Sh@oqj=?L4MnPVG@!0uHlWINVg0n6rebJt&cR-LiVQLzMeT&faTs=iXz($Hjv* zdaY7R&fYCn#X;*;m(qXjx9U1u(VT;_n^#!)OvMU7|K$wxCJ#d#^_3eO-_m!h>df+l zOCtjMXZFI{|A&DnV~8f6UY7dRFQRCsM^w)V^NSuSdgd!FEwT?Lmfm9}w;#;(FioRiwV^@E%cV69b9KMk z!n=Vek=3|f`j{a^76vI{C}k_nP?Sn|2eCX@AV0xRCnpBSckoC z0BMP!T$fc9x?O6-*}J!(Xr^A|9HbmNXhtFt6ZGGRNwoC29kKF^_0*&}#q}(nA`kW} z2#LJ3Lm;i=#!%sfQ!Z}Z-<1BJGT&=H^UqL~a?4}(Drp3x$CruW1)?=b50x;5JsXA*FO0*RQdHea0x;rwH!JQ9I$ongd)_2dktn_f|Kc{h!3$RJa&-ZhurKeY zBxVfd8^my-H576idh(gulEv+GvO{i+aqcH(8{^JJ3+7}9k!|$ckLwH=um;`G{E_eVhg{nb~c(4LP&A(T-AcE7G%GHuolYV;wn&M}^6WegR8YWg6au7 z@ArJ~-M{fop20lcmai4RuASUyOHrlrDIQE@D@fUnbyd5alXn<;wIB9JsUoc#w1}Oy znoGYT&;N3_!JN<^`gIOT`I1w zLT&oY{rl8`LhS-Ny5DRq3;jrb)`SYE!f z79;-TUpmg15h9cGqQomsXWg;{t;oQ@@;oH*uq(BqXtsEM)CV;@yTRi4Uc0Y_vr$*F zRYR6gVqm*G24dO4;!ZG!&OtZXkeIGwNJx8b5~rBds)8eU)irET*ZT5F3lYzmA~+we zE5(4rWH-%oS5IUk9l|6g5Bz0j5MIqyw$oYH@b1DShQ!?X<#Nl^x}jRkNBqvQ-ae%? zh__lsM9UNJhWeJQZq#I4nWF6hCuuif{HT+X$_-=OhVQAU@&`Y|-r2yr6Vb=U3+F_w zJsefKXB^$y))0~=&2!jCDvDB_{JM7hy<8=?97bO^PuuShTx`e@GRO_~d2KoywUhlF?`0N^+_!9WmRXOU_#Tq>+|aKkzZ3 zJHcX0-;P}TRVBEjt-omTMF*y(-9xutcL+y}%cw-Smv~G2#p#2d!6$z}`kG}Ej7+#x zpC7!w$n>IG>O`|dc9$XkGyPD>iz*%IJ zNl~ZFSjo-fjk+V+#AK13{R%u^n>!I>A_Nw4APZl5aH^d z{|OmHM*=pY?yD;k-;sXq+)XCN+TX6QFQ3FXSdB~z+6i|3S1yQsfQJbSN9Qnm*?vS6+n z7+uA*|Ft!Qqv5ij;9-a#;YdnGT4$aSY3P`vgXuPmlzYK8G*!TyhGzjjJhDU{x zacDI7<;xh~9mmlq0}(&_PuGK+Aui)Q-7_Jv`T8MABJab=Tx5Nca?@g&p5yik471Zp ztCRYfX1Z6PE&U89#^0}U-R^kwn&YqayRl}B6E z1b=VSwNH9jZd3>vtU_>=VPJ%=8A>%m4iM zC*x#&(6D_hcs*nQ$#uKFp`59kb!^)-Q1En3uP8!dsoSMe#+2q6q5a37Y*E$kQE1wj zW`FOh8fEa`|NcI9u|D`w9M5i8s&W|5E2rhQWh4c6k6bhhoP=j-$x49TDB=mI4E>*E zV~>zh$IeVx^*3hK8d@(hMz;-?@9|gM^WKv_AcAV~oyZ&7SJb8iGku;?gfeO|*IVoF z$*eoRFUU>wHw{cLDm>~dHmPkqsH4Kre7H1+g(ww4kn^=LTl&Q~!jrbx6LoBDF= z$RP1I+0YczCBjq_E&<)@7OtqMVsVL&?mfzMx%TQepVF2_w=Un#?dI_N)#1(Wb111v z8c*=;XQPe-n>G({#zZ*$--kqEB1`j_T0$v=oBRK}eLBGYy+|~##&PjJ>}6G->B8+A zX)9gjaB?o^&@A)cR{tKIO-fs`rzoC^!|)r0g8j->=ZxkaUbnFD;OTYT3K!i|>EGy@ z<^T7XPIbTk;^*#fm-z%)URT{1X*b&={5?7u9g6O^e}cbUmXmARJO=@&@A<#~O_^!t zp4sXz2Lpfm+%}x{6ZP-muS5Lv{s&IS0Zxnmx19P$|0AaVFPv@yk(6HsIQ=>L4^Gno zPV2d%_#LV=#+UST98ftZc#MR~7o}*hi(x#IYWKF7X#$`e!)PfDvWyn_8x+yP34?ND z;XYJ@aD|{)&DkIsl?7r`-u9>WZ|6+g{o&i}FY{OGh`*h>e&113+06a1sxSeMVP|1%J!KN68B{dY@;itR5g1(TPHGj#`1I9u)J-Q zoqq)S@C9`Bzo+E?c=Sq&jvi;jwMljx!|+^qW|DQRmU!AQMyvnj_0S3Na`&VTZ!hEP z+fDoO>=aC9+vCQpc>$%31$*w)*%s^asG1VpL@KyKGe%O8f@`^LZktTW-Z&hd6QUaZ zFzK7d5v*RKYoNt#%*nH2LhbN=@gKZ0OXjf}oPGO_F4dh4V^Gc3Z!j{`-0VmqTM>&z z;O7lvuo)v!lLS`Vg@`~zLA-N-U{G)&1Gfzs)zrdfln-0WxaI5Q%)Nwx$6NLX1hym2 ze{dn-b|a*zj*)_lCB2njr9~?qBXiF*s6sWCcXh?LSM9L)t0T*>m{ewJ}P@XL`DEsn+I(Zxiedm>kEJRJT_FfLUAIYhIQG&uR5ZLx-rH6jYR z>&4J;c{I~|fk{9@kvZTFlkQOefFo7RRhcrVZuI1AJF62J8@kggy5K{4kbb}VS|F;9 zoE~RD`}d&#%f=NBuMN6er?gs!PAm$4w;4n~eup6(%rh1x~eL&_}YCg+b&<)d%vUH*_5`v`2Ga_mc90ir0ns%7 zT+jnB@bU7aMB^2Q<5IP_kT2rOg@lGQi@B%EMs4j3O^CH@_`0>~Fcr%o7Mpo4@(kWI zJML-DhCJ1M^dQmYXa7qYes#mS94YZ_hAtBsKsdXEi*0iQ?W?o$x+(&kmAeis-TbS3 zDt1o5&FdtqVUKyAPO^dNRw2AO{yOe={F1IhP6HE#?ivUGxOrW04oa=~dG~r&@I%wG zh`YO6+9Qw%3b&R7CB21Otqj>ysj1n3vT?)m{eeYGZ_>!Otjx3w0&m z4o&%elM-YM2>X&r};MTO$(+a}dFsZ9&zRt<1`cTK8_lFQ&P2nwzWioaSZ z!7_-^b*hCeP}a!qQXa-0@C*nVUbaHD4kuv*hQOH_FQOH0$8Kk2v_;!ix=u_!94ObO zUZ3##>=<uR}A4vJ941Dev1rQHK2-(L%VBX0Ke*GE#T9PO7Dt)8eU>rj$CAsJyLX?tcw0E*HO& zNL5S0T=)MF_m07$G+mnLw!P1`ZQHhO+h^OhZQHhO+c?{{Ij8%6{q}V9+=;m{KW_f0 zs>sM%cq&#^#mbdWDuk9{buPDhC@X)B?biJLg|^A+R{mboXr3SYxB7^G8p2x7?y5GG zd+SlgvcfbFnX~q=pz)vWh%n4n@a1Y3l*Z-W#*_rI&e3w|@r2|MTM5IKo@E^oXX_+q z$-!3U0VYS(ZMS z_)v3Bx07!1U6e$2Tw|=tpN&kIbxz!lJ916AH9a#MazNlF%sXf9rk#1GtV<%hZm?IC zE=MEf99@+9{MTjB+;=!?O4p;4=3TQ7GS0oz?kuitC)^OY{zC!7KRR(~c&69ofWZHe z&-#zb;nMa_iaq|z|ByfOk4m?u7d8`a2%LZ9|D!S%HRt&9f9Vs${^HjE-!2*dHC*PZVRLq}!~3!j>wC8N#L`X!(cd1Fn7tB<=kbL^ zq6V?O90z1p`7=_<8L_?K^;hR*!C&0D|KdJv??ASkG&<1R;AQv0o0@&rZDjnq`|M4} zwdgVam89^JeUfqRL1gmF`^5dYBPN_{{#uN8>~9H{@A_lcjm!lW+256kjc4vVnJaRa z1+o7oRAC;Ayzqu=mSxFF^wBfMJNfFL(Y!5k(LVrY3lH6FRk`aTcoWJIH=Klu<^>tphuUx5_$DIb>|9B)} z>mvV8$P};@ZaH51{WGL7I_cl-xRSY`{?8}!FFSbtWd~4E&twz>&GDD1mK0S%aSVD& z1xMq`g0RjCT`=?+a6`nuV*WqFo6@R;sGNc6^nV$~Q(c;#_5UxCA{Hsj1E z(+>5v3~>BlCHnuo;r?bz7=`}xL;OGW|LYRQ4_M>0nQwQR)SCwF-EBI}iU;-MbHQw3 z-nTSr-S5BO@NYd}{Qq_Y91(Z;xAXs5gAZ*-Usj_eQgT+|Kbdo$T1sIttSI0>VM8O5 z*>=kG?AfBExW!?Cwu$4J=e*F3aWrwTyO937T!)wO8lA^^f%SXBW{0+gtw^D>$?3Ub zyQaocjWsw@#dcYZ746x(u=grC7WGAgxqfb$7;e^{G?hkDynA`&K<=0aGX10jbkvCj z4f2cuwuHi}L^2|@xW2vpJ*AO%vTcbe({n+Ds(I{Y#ba6Sm1%Q1+I3~rGz2es!E?<= zN#@$lMBZu}+vs>-G9puDBeuFNARcj1iKCD+O){GaTlNiqgo#)=RF~biE48PUXoHwPVxvF#u+k%2qKN zRNlT9bnmGz2T3+cmxJVrhzJivcpgJ|Z+3$0;_iXUC&HW%nZZ*mR8yz=)Yi2p>9Ye` zesG8@+>jTMvH`REDX^qraWQBD{3)-!*uUfRMJYy_*b*1~^O&-D`c0p|w zwMf63^aJqc;Em=3?ntmZhz_=6^C=>PGv`9=4RgZmBXyY4meyiRcOeCK@VFzBW!|N% z0*_~Dy(Tg-%`t5X+f;3TS$^cN zy`pQrzg8GqY9PyPWuy}tB2+@OJ0r?_pW?8jwAv;k-UfdF4mU<8qg6Bt%1>ZbG+MPE zLaKCNwD^<|_fu+}seS;y@F?=FxVFnGd-g5M(0nLtq03$U9fVm6vd~2`b=QpcG-F=} z=->=Kbg`O;?HUzCB&2X3h-}|qR6MQzC>rCl4H;zO4`2|{^T3$zoXOVtSi7_TjV z#QNZ-VMxr+vGjk)8ilH^ZUis)ZAWq5Zed|{K&4IP^%X=vaa;pi9X;mmhJ^p0=IlJR$Qdk zJV$ITTiIcSVnF-2T$#p33}73u+e8=rlTSCVmNlCyytx{)+YU#lOC8VD!jM!gFb6d! z_skkhx44k>%iNKs0Ds8B0;c*mqqR^}q&8Tl72MK>jfLq5G%uaZxGx+11xZN)J7~tw-iKp_P03n=_S~x55Lt zRa8teq9K*UX-dYE^V35qoy6g`gyCc?>HX)whI*v0GDJVl%4CBv= zqbKj(k3`Ty6tzLd;3pD44lxS%nb6fPZD*9kMpfwy7SSt;>x%oOlsoN6`+{k5H%!Cg z7-A*&yIYETmd2o9ORtX@OQ)g3h?3sf9GyX$9G>GP<`~phC1V*L(AHhFE7KSgP@IN$ zK+>-XW^G1>6BfYKwZznEp}jb9hWFa%LIg2Z{5v6_@*w5OG#PkVU^@jqDB}G)2zJxu0Ej=r95m;ZI)-`Cv#?fYb@Kl}HN%1U% zjfiV4hrGsTZ~RI3$iJf_F)d-sz7aIAG!*2)B1!yR`oLeT#L0J>mhK1znOdh9=v2WL zo-_|t+QVhg7^J`-Q&)LQ%yDdul()eXtTkV)Sf`cjswi? z;IV>=odI!o8RbIuE49nISprun+;N=LC+FkdpY4)V$emL<)lwXQ@mn2lt!I2%?O4)Y z_sQMfqWHA!j7>EgZiu0p8k3mLa&Zhx%+}63FV_~d71@adASeB_J-eOn8PIAAZhJjh z5gjuNZrdI|PVS<~&<#`N-s#Xx@zTa}f_;Myrcy;7aOP$tJViK}CSX3q3)BA3r(!yJ zf`rIWw9t43Nz;DXX&!W&*KdzFe6OaQazOTyFt_tT*727)Ls=n*asjDbq$#@Xx zal3>)p?1x(54p5p&E44PL}n)fj2n}|b;SI$37-Ws zb}93UQgCqMm~ev(_d=kIG~;3_Co+Ol=H>uW(dmtmupB8wk|5A-JPvBPCbu48%(jw~ zcTagESFHQtn5`k3NF5XKPwF!S0yTWv=W^L`s(dA5Z2bHiIAVB1SBxmt zq_i2ix~->Va28?lxkJ|YLgNUFkwH{?uEX?tp-QC%aolY@XDk(DsTsU*ZWRjE>*@GE`$Z?>zA9YExfdKtj-~KvzHSr+d5h zs&VwEGus)3`8=EfX5q$gRePkJii%~DJ+F`1dr--hIg$iG34PejA=}I_Hsm+A=#g8r zK@1noeb;^y;N?MH;~+v8DwGKl8vUOk+rxDB61dPC?k_QXrtxqP&$vohc@ZOM`;OG)u4Rk{Noyh?tgu!{Xuw+y+9a zq92j;M+%Ne1UzpC4oU(jUD@|@;KZATk;)yeOucUz*Z6pUJOAv;+~b5sl^0;@43?)C zc-VF9=ZiHkdM~2&GXp9h*h6vL<&@#4b)#w3kv2`QRCsS(ptk=wuFV{RmeYU3f5T)n z9MQs%JS}g*-w@FbuDel;rs|26w5`d5sBDKZNCS;P&OA8)9XiKRf7jyxIV*Q#ColBL zgL)^uG602soJ~1fxX1YvK+8s6Y4$hEV9Dd&y>!^H_=48?5A4E4kkU*LEmB5z&7+mnwqxvsP*6u&@zMVpL;7&J6N-WPa&{5e6 z^vAL7_~6?T;@-GStcF|wp@lUNQKVdXkU({ov0Jf5s%RKFOMr$R)_(9Q@7wV0=a8yA zh_PeT%O}HHxWRb8!6UpzxU2`VhzZb+es>DR?2J=81U}OqbgO;+3IojzesYkGF8rn! z)ACYZ(xd4{HX`eN5|CM20D}CA*TVZo4TktC87&}_1p(LD%HT{>x4|FsKzH^BhFJ!a zPfwM1LOx27tO-_4cZLdpwR<|y0NiCjve_i=!+>AZBaEZul+H_YN#B+FRY1XIhbQF9 z!#F?4B}~Ol=?pLpSGnqb{-)1fIW|QAZRYIa?2&948E3AgJy=#p~Iz3IYVKz#g8;Y=2KRO98M*(<}Ny}bx*QsOI?j~fJLG-k;Zs9r>NMk?XZjK zWq&Is#j zSACSv;Jx)@=Hj(9x%p;P&gjhwdvefIuZMa+jcp(=DxdGia5@^I$*Q${PYE-N0Qacr zG*Nl2U4x3`%Cs=r?ZJ5Sa55FOm+!k>tPUZAJ^MR})Lk?{&(|k=gZuz!Cro9=s*^_0 z%Cm;8W&27*I8s=vGKeA38VI9SUgrc8^nsdYJC(Fp%*Crvp|`~s%X8zX@uK%3m(gc* zCBE8pqDiA^HXFyxl9vf;%H-x?R_%N(yd!ti<@nx8%jJ{$Fj!=9%Pze|JgtYTdLbG3 zUa|LUt87R0d}HRLoEp~%L-YWugC@}NfegC{5s=y=&m7j)FR2&i)sPLKSEel-vk@oSX zL8u7nC^zyg2FK-NIE}t`;l_KtQCot!u|iSu&St1L)Oi%H*^bU2scttYPRYc=d!0)V zMdh~mZ5Xwr=;AiwvYvQCFqRWcX>mUFP%;;%EDWAU$l$R;e+8jc(pWrEqiD87f>F+E zpc>B-gM9guqPqfs&84igCW|O9JXZ<$E!9i;&`<js*{#nH)21A8MAmY3I*v|az?-dkqGOK0|0iavyS}nMA@gCieAObXJH{|^N&?Eq|0Peam zJty!NXXdb!p4dwOwVikl#Mz+`a#Qbla=MgIU>-~JiXnPW1Q+MP1jiOvXJR)TLzDK@ zwiTJ|F715B5w-MC=WEf2w0t4Fo598(u6d5&`#dAC+z=JWUjE(ocQE~>FAIDR$Mv6t z=AM}Mm^-}y^*rP?$DUYlh3vnUie|WInasoHvNgfHVHww7Z+GbGC~*e-fhPCT`w-khgpk5ls&zJodXe<%0=C6#9J#TrCq+Mea4b#zvHa& z{gA8P<7mu_5*U5)Tj>TOqOZ7UxBBrThO>9MKFZ09XACgr3Az4mv;4@TzQLHf@}y@N zi$TMjBcaD|L#@8Lmcn66@x}8$VsCUz`ORoT?do^N+?{-X`4HT(W9`lTA6wH79xF=-~5&Hmm0-e zbtq5m*PFws;We(Gt6>DxD{2z%`?^QvA4LVttys=lzDg#{t#R!tS);L)5v;dn`P{b9t)#tkiQRf4o zyE1*i%LC-|gTV$3-_ZRT?Oh%Gb^p|>Yob~$8!x@mX8G`Pnqcp)I9`q94MUg9DW|bh zw>gvz3F&6rMeu-}lV0xm3j9WNXUJo9#f_zITKMIP?=PUSbB{q9;UW)O_YhchJL*Ng z29E;mIdHFmt;Ertpp>J*zz`9hLmyG}d|vxexUp)vzh!^moF4{V!6V8aaKA8aE}Wbt z!;l>Z&tdgWc;N0r*%r^XU&+8gu!SSqORk>c)@fy<3RW&`yPF52`|{xk5W{X0jGfOf zi9egvD-h8Wa|jGvxzj7y)X!e3cREUAUbz{*=>5!EjanDaQ~ zlOW1EqY7eURuhd!r|^DBQZtSjOm-f2E2mQ+aXByNiT@~$6UQB~N!1K>Vxq=wQB$<~ zjf6tFJ)}k1%Z>e%fLFZFpHBF|?-miso82Q?z5(epZxi_Ll`YX`dKz3g)R~5s=tGZcdG>8M_#bld z>O8@~ygc$}3bhF3i^jFHJVBKYVlqg8c}>9(M2vjaq%Y4({dUxa`ook6lI`}TY0C90 zKOOn$D*T4vvM+m9ytWV?37N+sLb&a;o|%X46y+tIc8{zJowUOeL`J2&uJ9^GUiYin z#C&*OZ*4$cfv#@GX{OKq3@=OJ#4*2RR1I??UJGOSdKS_xrxOlOr>Qo|AP>-n-cl{4 z2mb4$`Tb&b&r!S&nPKbi%8A_DvDC}yaa?zcT9~ZU5l#i6^I`On{!$Z^d9%sI&a|g9 z(Ya%_p*1z~vnr+CxR1WK1zBQgErZ}0dA-Ss(F+Kn0TUEXeRHQ6?f!d|eb z`m+0CD!mY=VEL!9=&<*+SEpvEcoXczwxOA(p|jmL<ey#+5(_8fSH`H6sdy$7`YSr!BKy*mrL-w_8Xv4TQ3tVc0X; zPBff#V6ay}#ViOL4CJ^fyqiBWf9<=+4tUe=ZyFcx{uWx9;;;=*f7(Q?rb?K~eYt^suv-IEV4 z8w@lZ7K5-@ILmA7yAh+0VRLCK?z$p!QQlZo?Pg9Vq(>bTJ(9z(uvFq$$`Ja>?s^+!7sRDq^-9^yPoEAW^C_6Vzjt&qjv<-%Siud+kQ9 zJZm_7^E!j#`=xAo_^%B<@H2&aV@m>~B~Uygv{ouL{Yf%24*~B=W!|#hKR;g-e*tmW zdl?LthOz+Ko?@q?zZoKdcB->}_~hqGQgYkKH>?vejOd$wnm7(1UyjHb4Q91Cb!+!=FV)%GTyiB@!^k#ESrj`|fKp zfG?iib8~(1km6?I{cf<{7Xg7GE%w6T{_21^dY`cP8Mo{0VHZrJEr!O)zPvxT`~j*f zy?;)kx%zsOvcYO8UkJ(Kv#jO$;p4Y+>-_II-GAG-=Kmn4%Ye_o!Sr`~o`1FhWB9v= z)_=+IUU_DG47reg#l3QCwm%$IpSYuDC(=l8ezJL2YbzNF*;s`tFE`{g1B=J72T9w=>vdZnjTHa!RpSnb zBSFqzdiwI_`NOwn#~T%w{;0PA4`b8e#3eW!E6CgUlhO~nNm3ybhbI&!tJ~huYW9c5 z+xD~;Yh=q?YBmJ$h`w03>{6y|w)(ui*sjSsDC9FY|5FRNaurT?D;f+jym|zmn3R3ef?`xq3cX^6;heMDgLaIN5%O;d z;CX=S_{}>xe;MP@(-Y!{F>`!@Ao2kKaj>Z2BmvZTImx-9AP9NEVY+c*(A8X3ZnE`2 zeeXG<$k(;CD2&rr5k9m+GFln0>^%^^4UgoIl|pf$R3g~3U`AQ(f*E|5{o5r>z3x+{ zaD_vAgkbq-)={cQ*%Dww&Y#{3*zViil%3ai4iA(s02}19lR5jxZ1x|nQ%`er4=*?y zga;gR){PiAU|V2WvW)QKd=Ybwq$+{fLZUlWDS0-^Agp|-xKr`04@}O`Zxmn1C)~G* zSNJ~i)&gYu1)?&*_e%Z`{bu+AyF#Hd#BJF2%2h2wUZJNv6muHR2*NqmsCN-trj3a6 zc^Udv&bS54CMeMxVYxyxc|1%Jc;Fw7rHCE^Z`|>6fr9dIK7~cn?;sa}&CoIdORDBn zTp+3*U~k|r)Gv(f(Cg@)+g+=ho3?8qy+9RfF;9WgY$uLwL07r+XFHu;aQ82C<3_-y zdyvxutW(}gLGXFFCXCAY)p^G?*J!72y_%cC?YWocRk=BdxJ|^l8{N1y&AG|mzDund zyNJd*q)SuK^C5{-=%!a+_Czc_V0~z;zWwU}d)wgT*1}?aoeTUcaCJ70R3vSGmw*+# zLHARkJ+(rvdCJQ^&{LBpWUV>UvLEL?-msiuPB8Q;M)HVmc2MTjXkBnO zjKgQ@cZRVW?xngs-_cjQSy0W(gh!HqBefHtm7bG~wI&qqBZb@AX?>wtzuF)4?bvUW zh3z;l7+fa+Z#3&Y$hu;Jw2NhXE<|CAamD}<3}Sziym9+c2;cpuaC`f$JK@UH9f|kd zzD!@(?=7JEec#%j?(c9kl8<}fanB{`aMb`ZUW1$jhI!ggG;($z+#i6w%|E&>2Q7~% z?^%au;c_IiqLn0^5~fZvk8o>~-#A}TUqIbKy!>{4CcPm(a%gjO^JZs!jN4JljI?j4 z@3@JvwnFeayLgQeXJ*Iru}s<0Jww26zt$=>=YiHLI}CceF!nK#I0qupXu8TP8w@6%RDqXTx z4qG#lVm)%IW)>+_CPB4JQyQ2<6>3|Dt6OeZsR>uHY$G-^9U8ZXd8`zh+JT&1!Ij25 zcyitEHxnZ92EateAErDj5GOec7kxgdn2MOAeTacG+qO71BaVRLnF-0UCoFGV|8B5b zC!6HTlNnN7y#BpLlmyMoln=oR%cIRc&;qb)5NmHLDlf(5kBoei3W%J{!3ogXBDR3E z9utElb59(UQ-4vy0UcXcXlW%w9*`gw|NaHRm|&SYvDbzWWqe1E_?JzU?J#=pkWaLZ zARv00{>Um6e|jcqn;)$C56aBOtwWr7+ed{o-7xX_o@iVAic2=0xcx$OyxMV7Y()~w zOa?CrecTv-p(M(0AwZ`WN8xP(M8qTe=C3V?Ly2wMz8M6Ccb3oJzItp7$Xi!10_g=M z-l0ZQxbs4qSo3#?u*!?p?{l)TOPVQ;#9WLGfsxoDXR;K|2r#1h{^hYtoCeINRW2ed z3gNDbHU%s*Fp(38E>FMX^GT=-TUHyn41Qt|Sqr}5!Og)RfoX=i;f)#4~>PYEWg zbo!$&&yiC*w6rnTsMIo3B3E;KYO_lfjCKT8{tn z9H9SLOUmCn{|uK~bBB2-4~MWZ4A>CH=?fE-DD;z{z)i0qEhx;$WbFL~xP(|H;DOFvbKb%uM!!NAZoG+sXwuh8sa zoY#_xyp0F^v##?jzI`17vAG>v8jeZ0Z9nvUu8Bny!l5Iq;;ii}NBvuw6UW3Q)V4u( z(pLrhg`>{r7I5E@=+Zqc6SE*OyOJpX7(^JtGl+(W78*HY6^?hFYNIx+PwiOn#ATnw zDYVk%ZL6)WkMUqc$Z#)<9gWkTJ&f;)lOT^IE*xgsBKC-*X~+i#QWy7eO-6DAiDbmR z$e3J#alLC3(xZ`i<_qhYXSqepNtOrgHsT&l$c~+H1lor}G{mV@-NhLJVxZYShP}a4YeeyOb&^T;J?nav|?G%CGCMJpT`PDp%qhy@1M`pLrpn<1s%_aeSEJ<8%T8*pQKo_#9};jg{Di55 z+5^NGF~;&lAv-Y&4)r}>MSgQ}>Jwow47;8{MDb*DxAf;c0>f_B0b+o$;ddbMXk02w zn(qQ2SF-XR?1W8CyYusW3)=!g(b5dSAd@btc@Rfc=)+0MI0~E}6-1O~EPvFHd`zO~ zNW9V;F{kjd0*PvP7p_2FLIJj7F53dr=o!!^hO^Vs{2(~t;#U#lJ8Xgc04n#m*z5Me z%Gd(=+3L)MLp(gkxWm#Hh*M@SufoR2xPmB|h@egd5N`l2m%?qvXaS-<`qe$N2qCS1 z$K=O|gUf;0=*e>Kv9rT3bCrq|U90CJv0;pDG8DMJ4_yhLN094qi5K{_$McgrT*d|W za~cXJlp7p(%aWg89rJI%oM%IijJ1u6S;X4E>#LbNHK@;9v4A9I~Yb+6IHE2g>SMUBaPu?<1Ep3Z=8NAGpYoSwHGqc;)hf_ez@`0F+wI;} zkBaP!O6w+{Y$sfGJSUC=zcQ@5krY1W)Scf|YC|4%LIku7f(44Uu$7#O3+m*q2$ZiMoH<`yDGCEPd^#h#^ zEbm>*;VZWttu{_We5*|e4`Y%CdCUqlZt;Ffa+YfOw0OxcVew$|COLTx8Pi2%0;XzZ zZrW5wb7-oW?<#ned(OjFxA89bY+O)k-82sa|H(g<`jTHwg>3y35!lfSmouYc4mzk$GLnTSe6Im!Ie- zogpN~ThU0&%jvtvD9In6R4aFjCGY3bYo^EqG0`X=(RdLlSvM{CY?^;*G_|#tk?VN$ z8`G9Ny)$NR%ay|4?KGO=O>JM2QSE*CC|f*_q6#22;w2dX@{ZvgS4zniPK~@$J{HTu zv`YBWXlY}Qz?GA(8mUr5t-s9=`)jKbEQ^4ziry?r{35*ZA2R&7HS|KWO*XEOjyd0; zgI`uQ)?5k>mPmJYyiEaaW4|p9>`}+X@nrPy?J}?6?s;51*b5XkGbxyHQ>JQDWY5?n zD=I=aMrxP-78W<1<3nULUR?ixX?mNPn(A_D5`leR@Lvck_m6iUFBl$1I`W_BX&}uF zcgtiNRFbZ$X$w=&hv6%)KS2{UBUN=gJBU4RTks8$ED6b z85b;3K$G%I#gsHLVRUYamta<3JYVS<1AX$KaeB>qW}nu)8B_z!m=VvT3*R&kM1LB3 zR`!n0u!OUh*RUi!H0MIw5fG?4?*RWn`P3srP^Ybru(=czgK2%Oqt7tWL=huh_xo3Ap|I7{hj916gdukc-TI!(GavfS z>oyB-F+n2&pKi*eDI2v-g%-nL$wShCXXzca&qvC7^kRhRanhEL6P5E%mEsHxZu()B zRX5V*I`5(ade4uGfuE3#8pyK`hD`_V zsiDHgt(|S;iz1icFkmfJPzasl!iRE!X7dbKNZ#Qd(w%#M-edw%*8Fia9xlviTC6W{ zF)OaO5sH$=bGA-P&vUy|^wbbEb?oUD=jp$WJ3inoD&Ex<-aC@msieQT+9O0wbIPK|QralFRgkb-NlJTy;CQ@b&73aZrA5X$ zWAfeut>p!vB8KiXbuXwfu&L+AfW`%cNEw>krwXV$3!`dDLPeu33=PT*&G7x6%+%Z2 zXm!m=?O{~xsxNicTAp6WYME=;RFPX|zRUG(`5EL2T9&(Bs{JulbEnB^z0XM(!zH!1 zXQh66Nf7&^+^o5m#MpjmE^cf5Bqme^={sSCIO>4SHj;fzjF~}nY&ZRT*|A*Ek~I5G zp7IUH{{Uk2ZmCWLx_DG$t11}wifK2kO`QP=Nd<9Qg;nqNW6UF*A@DdWkqb+w3K-aQ z590WxUn4S&nZxC+d?ymq%0<^nqh_;g>$D}q8BlM_HT$UnZ5>i|zer<6D>fe*)0^_O zX~m<%r&nL*PIV#qqq|D1e8zl&*l+?&!q~KgqoOj?l4Hd$({gcPgIq+FAd$(tGbmUd zKOX%HV%mz(Ua&YAQ%3ZF`{x;3;r9VIeuD;o8$EcK+AZgrxSwBueeO*Lkv5_JDH0&C zwlTH~23_18PZ;(`Us9bI7y}{3Kqwh1-cVY+^nQ37WO70o(jak88V>t9vFL+(8bg?Z zi9?`^>D*1Oa*IsJT!z5~Gwa-sCypt^~iTio!Rjv?ST={}sjhpond-c?jX{8z)wMX7ElDfKD`;?LS zEhuX0$(HNlCyisH^Za6_tA`*?@YRLX&F6i9r>?|FSHO(Eqfzg*?P&@RZr5f zH7~|#EYIsq4MD8BDOuBE;NjJan2n=98ga6tjZ_?3_7Bt5^El9y*>w-)PuemNQlT$P z#Y=2=^VRtlxRsQ)Z%#I@O7Wb)c3U7jkiFEpqrlx1EGKV!(ysq z2rDC@0qr>Imk#Ky(4`D^@Fv!|eVqb{9|xfIwEfoAR{xjN&iExKGp|)A?^lE{iIZMX4fPE^^?hgf)`cz}(1C$Q*90H@cx% zGkefCvRrz@5KK1J3~VNm#O@vLbgZ;X;@F-EWRz$%GSJKmDx5!DG||wW6P6y93&~)v zx{s`eGf;s%M7!B4Tx1WFau!a;hoz>gY#qfJ#;H^t{a1Bm;oe@_vS#%C4rrc>`z0Q+ zW-}!w^Efh#?2R4^D<{=8E~RzaN{$MT{#H_)V1gW^$(1z}Y#ws>X$H*X!vz$isEhOm zps?WcHiGWF3jDa?VC^7xAR0;m%)#Y_1X!%5e96g29GDXe$LGzUhjog5DKL{qDD52M zgBWC_vdB98oX;?ogEAO9L9{a?@D55td?cdmHE1Wwkto9yotPs0pHXb6sE1&km3;^0 zR*sRhNw;OOlPVIhI?$1l$0|qYN8`43g4oWjGgGq5#cV9Ugk6VWzBLWFN0^~xtrQ+6 z+UIeaQRo4LAss?Ip`V3eB?VvwIRlVqd;rspSvOkrMl#e*Pa zbpC1U(fge#Y5ncQcf|(~D*2#PSBe9h-h{CUbx>awXFZO2n>#RIF=kje88U7mA|H1E zRS!WkU&L{&k~i4~X9Jl{nsZ2M6mwVUuY>wfy=Hn|VpKYBsJg_CE1&ijVD4+0BQjGq zozo>Z!LkwYevF|ZCOXKsu~XM?Mv;rP(RNnYz(Et!#8v^43QDbEF;$Z(>bjC@w*ClJ z&^_Uo`SrO-^q1H5ZvXn5@_HHVPWGfKo#T#O^u!;Rr%X`lxv|8|i{z#?))x_$DC^3IfAYpwdD>}%u3g2;zyWwl+w#S- zYb8Z$hq}zX_;dxRb7Y4Wje_`6`J3q4(+jkyQcqCI<*b>FSk($P9&Z3Y?;r+-&)e^r z!@y4soq@(u?x{d>R61>R&mui^&n!JRDvqg$&^xP~%|Camb{Wi{6moA%!mSi*${P)9 z+Y7Bu&L-mtI5al9=GumO_4JJ`L6eqR^UW0v2YM^YyFGE7SXD9>RTJ>2ckQrJf{kh+ z3svK_yUc1AFvoAf``oTy2^lNV0V4no4l>#Wy81DJcytI*)705h7$B{!JNRYIPhih} z>%tVxid7}Wi~`{?A(sfE*^T! zWo9=jeu;TV*bEQGDsWG^9uJX>i12~S;YtNZEtM)NUWy4(h`NE?rXCH z>Q`H&Nhy)xmM))rS+Yw#>aO86j=5xsbg@1M(0peM@IAGmKA5KU7)?wYw{1#$s1fnRLd!Xj(AK%SkQzTLs@avLCa{g_EV z^LQu|qhVJcH!EfIuv@BO+?qbF(c@Dj3WkfpUgq=4t5qQJ(N~tF(OsZxCowS*)7!)Q zvGG?Z_%FpW$>YoCvz)SDzRL0jv*W?u!q;g8+Vx7g%;1WLhg~VyHVA4-@KhD@8crqe zvC6&*3XFi{#6~7rQL1x{$p8XcPd5G%e-Nw~Jsiv<*MTxCHK_Oq6Vug)?AJ-%P(#*$ zN?iY(W08ij>?gvi|DmW8^dVSR*tY{W!c~bN+hzAQNlxl&@_qPR*no3370p=Hn-rT} z$FYUy-E+QRkr>sT$LLx#thDVu*rT4FT+eE^t_ys(K5k+Tl%MR>bpg1ia9bpa-RH}`iU|uSdES5nU9G;~6h)ZqF4wVtyf;Pt8|HjqM zQm*?fLGtx!xYX7uv;A#j<1X-!5K;oRU?q+u+v6Ptr?EB?kg}PxI-1bvZ1n)A!n;#~%e1U=*DUoAYU{fL%q%2B+ zs4mn^ER`c-hLfOG?GY?Yn(WZ|^p0(nB51H~^_27EIZiSEu@0@f%T5rd=7n>|NN3rP z=DS=29VRA$hREx_s;j7|shF|0c%YXa=i0`cwNriq9hffal5;(&_V#gu`{zH{0MI=Q>er>xWKmEUS_^K@44_fszx!G#)>YUB1q zK$!V~|K3a5(~5&6h}k@Or!NlbT>QEXz|8{-Rrr#rSit|0N)31KXUYS&C;O#kRzk3O?Jpzy@Q}-Buz8= zw|b<_lYJ&J*eP-CpCP;Wq6VSBS{ls#h1#dt@cyt#PXBZ_s-D){BUqGTBampTuH%b zK`0rNvSq~xFCC)d;!x&2o+QnLq#;XX%bQoJA>W#sGvWm4EUJsN`~77_30>BP+K3*N zs7RdsRbMc0Asu%YJd@ZJGc-==khgKC(xIo}%-|``YE}}yAfZEPOt5G#pcIQ>a(21B zMH0nw!CF7$@xlPXKlPdn(V&$AH~Bo^Pf6>eM$t~kbZlk%nh5;tsqijw-jt5bvc7>_5VtB9VIUBY09?}k4vO7F{XEf&> z-9A4D^*8hJP93klM9g>eu8sB3KgJVw-l20gKS!|%$j+GaqG#J` zKb?J-gu5)mzMh|}Yd>DZx=U}Z?{8*$kL_!|{HHkky*>zEo=bAo!i{Hqg_5SANM!&V zv7@wCN>&0CSD=+8zKj1wEnKQ`6?8L}DSrSb2#*>sIeS_J2l) ze{#NrXy3UjUd5iC_`1_n%&Gdk?cej=-u1a*BX#eQG((8Z5e@NG>&7DA5fqgTfe*#Y zF*S$1Acf9S?K1=^A2qT}OKwSi|K+1s-0PM4-X0~#M-#=`cdB9*RYJf~8?wNBp0Tjc z0Q9lVN&!eNqeFRnFooTnVhHw?1|{q0zW>E*>M>sQ`N}(3(dg z@5>=S?dR#Q3tO#LSZt0UdRS8_#Lipai?jqqL=(uX2K1x)c$kEA4dxx6qY7-sKg81) zK247&S&)(uh@|B$17k^?SE^a`h$nA*Du_MLkVVUY_1= zdBeYF=SV8`B{)ATGS#sNd-z)fa&?IH-gl-}mkcqqaF(39&7aG^R~#V~c}Nd==mc_~ z4{4|v#GXOGD+8aFIP3v(KM~}v6XZ@I;FVs$D-M504#5n0C>`WZG~m@Fz>P=XlThH3 zTOf)oObK%TcR(APz@-cV5u~A6z*;>1wJd@TWC4?a6LHux$b1UECX%pHfVngREMx(( zz%OD5yMQq%gmy^5czk0>K|<-UrCgCuF+^X|eJR2o@_lGRUb_8Qk(xC$`Or?c|GXOst9RWMWQm=6^2sKD+kKsrprQ zUvyVj*ITuEtzOUby#0&^+!6vz2kK;jiX#m%ub8O)3Hk^*!G z6_WlEBZ;xEjZrT#-ic9OvEG?c(h~lOQIV281|ypw@R*2ONm-j@2mF#fx+C%u0@5Rl zWcAoCz^GIhLw!xMMf-2k3<8KAb2*Q72q0R3{X5HJdhPf zi+_1sEw11=|211qXV+#jFfoM{T(hu78iIn>J%wOB^jdu3b4%%e_GC#HQ!_m;9dAG-Q zx~^@|V{$Cbf*yp?0I9M;cztJ0xM$4Lz-~js%)=g{Q43S;r6dxRCJl@yQ)PL0S@Z(6 zwES7cugZ^*AE5+o4h)(#|s56mNh4ABE-BM^O2y6Boa$?|{z z_@dLy?u-@{8dlk?>ZO`1Iw}B6?*e~DFX&3$fbc_5c1Wnmt%M}|=CaMsVk*HRNWI;H zwcX3ghyO_G!km)r)f}*b2^zc;3ldS)l&llg`cYB%5vF6sd$a?3K>(uYP{9=<;bRM< zwipOsYEINXz7)Ug-{cPt#1P|Du8T~&T^n5j-nrZK?2vF8)wXjL)R&(Sr{1k=GA50m zIfhh)FO%7_MmZy`$jyYiV{zu%f~rVmi>r2wMJ!M8yleSdf)1wq{JhCXU1(N!JK zA6TMTGe0gv+i*X&Lj24Bk$D1h2WNoD0%w6Ik${w}T2bLs!BB?1^M^c|te&PNl&Pzq zg7J^L0O>~%QC%w1hdKt8nY@rYd?Hjak}ACtwq${VA6~uOeKf9{wgHkP2(>Lhfhm}6D$+A4J}Y3@dzPP1GNXS1i}140C}gsrJxmI zHM;cnN<~I5z+AxeBkgu~&`Hex@NV<&k=#F`7U5@nh1D&!sV}OU+PA+|` zgcbhquco0P9#AFmKL2+cKI|WOk4;-!11(uSOcLXZ?0M9%KK=}hGug)F3VU{KCLu9lHFobs5)UybLUVM{r^DpX4 zU_-p!26-np>;}Pa)Q$Qqd%Hc1`xX5wuUM248aV@r;}iluN+eWkyfk0y6NO>0C+xw2a-#o*^toy%dQ;rDnD@Z3P zf1Ns`JEB~25Nwj|D<3dzK?Ro8H6L*4IN}%IZhKc$<`E{L9q*tkdmZ?VM);9ZzWF60 z%fC+1aSFw;eDMlVVtt{hIg5Yr33({UanSRhJV46ab105Df3({a10c7%vGn#<)~vsP z+Bk;ZqsgKj<3_AGqmLFz0kona} z(ND@g0u-NLv@MLcL*qDFbv(}rJdJzbzmMU1URbdVTPvUv66Ic1RpMB*42f|3a`tz< z0Yk#QxI<6;zUq5r#HNnSmM6sKG$eVE3o;MGZU8fz>o^?%7Dl%^A=GHU7Y=bmM%XvW zZ{huZUWohkF31P3p<1qC6l{>4osf2f?nN&ovq0zpPaF$;k!NRP?-Q0@cbqlE!xWP% zqOoVSlClwN2Cj?PI6%MZH9H+#{300nHD|H^;W2aT)^W&m+Gc9n@xsZiY-bPGaCX}H za(NlG%Q=9;gi@nXKo*k#L5QFfSgTa0QoXE}t*^+A#%YgYOG~IkJTbUIP`k)wORA0lEMm-HwmPnWD2Go87)=HVv@0i!5%O6N(=?US6Lpc|^ZLs`A=_%rZNMu^r7H=!u82pZU4 zUx05@dC=%jWEnD)XMf(|O5ln|GFENxrI*HJI)JPxtBWgyO}sVucBm3}H+?fb^pqvqqDgF5M*uH;O@ilKs}2C*MCM`+Fga`T?XjlHb%fv2zpp|RAv=~Y zh#Flw!o-9$-A>n(w4m!7_Dsz$ep%Ar#z0i^J#8TDEjRU^^}lDYEEBqWravT1io9o(w5Nw1$Js7vX@P^VrHXD32wUCn6RrHoGB_u}|q0CCeUgkdZmIf?FvJ`3iF$ zgbQwl%z_Ur>vjnZar8HOn9BBDDt;NCRK9(JlLyxe{qLf%{{Iz)m4uyxlbh|orT9F& zti1oDN-Mq4{`#XpXaTpIXWw>ihb3Dt$~s@CjMa;kW{T+y`{}kVhb?$6rr+)?D16c9 zC@iBt%7F0}_#v`+C@EQ305p;$Y%y5{5=fZB6|a+7LRqGxVNaxRDcy7puKLT%b zVNMb<1+n&hKSNWdljZuf4PlgDX-94BJhrSr-Y1+Nra>&8^CPyyc|^6*1}Z%@pq?& zZTS0lssdGQCsgG=gFP^i4-m~IhJPK=WsvA7I#;|~6n@4g;T%H z{rP>8+9T74cm7H)6U?A;(J-z={Q?><;y`Wc7@Q=Dl@*1HJ|-kc|7Yol(M;@Ls=5^3 zovz>u-N+YVVnV>oO46+kEUcS0C|8WKe|-iR*h2(=o~jks0A`JE0KW`HR*yM?yM)o% zK@z*j!J2;~*i&v-ZzPK_saX-h39<|4IwYeL!hnu!%;uNHnX5Zw>xSl;t@}(rq5*rt zbD`aW$NcQBIJZA_-&fpxyz_RLBUC^K=5~amv@l3-2l}>$*44}CEC0OUnc+NciYNZa z7Cl+YJI6aNUtEC%^t^g819tFor9_r~Mhu%iG}~`b&K`TU=&FylIQdJ{O8%-%qN;K| z(yv8XWqq2-~ z^`_l$CuuKnWH8yA_-(sJpqRQyqC}ba*1@IlW@dy z$@qTGKq~67axwKt2EN!ZO)yfi_QBmf+^1slk*H1T?-Z(?aekmtY z!9wl%@ms+d!;Ej#v9cnQW z%`%mpyt!fGp(w08w5Ee9AjNodJytZ0B0Y@GC;2WNZs)pQW9RvIcl=E#m13Ify1@}D z#%CG}%(lZe(!Y}Y6Oi3qd>-;{TZ<7QV!od8#q6=?}mV$WMEmdMv!@`5ez+^GU zMQMSI-9;o9YJMz2i5F*DJ;06*-E0cy&BGZ{;$HSnLk^f@lts5^yK=V`Z0iwfzyk6jZ>Z}aQG=yk;a0RnLqKLI zp=4uMwIJUcp_?}M{xuC6{Pqj>z5VC9A|R*fWr&J0M*YMD12l=6YPSRU1ve*@vsLjOb5(QmA=@fEdBPh0 z;3XW^_*uRXVh*VH`AQju4C`-)Y<%r=$XxQ{iO?{Oxl&|Eq=@{0kWt!3QHg%>OBh4l zU@V^+fnn{jh9GBE8a-eEa11T*1dqC)P?xy4uqOByCg)9yju<=QgT=}|T%8&9S&0gt z>rWsl!hYYPAZeD6GHD3L@rFCfpaazSOA%L6Uh&Y3#1Ank3}It}uW`b}?H3CmNgLG* zRRShLtLW}2rUxeenY;S-Xy?wqNGdn<3)ch0FK~Vb`gCCau_J2*Db9`XWX7X77U>N* z(zWQYP!+CZoJMi9!BrDSvZaOYC{gu~s3aF?h1r0@Q}GYvhKTTdL4F#aNPMn4!(P$F z5t}K3v-ZrX5D*KbX%9-|)gXCRJcG-*)eNgDvahU29P=iD5nfdeFI^U$#11F1SqK4< z(Tl)(`cbW}SA931MQMy?kxcn7=R%E)Y~^4W&}%7^4~{z|90KGmm7f}(`U(Viv%06d zgMYNKl!l{8Qbnuzf|P>GGL|yj-q6v|bw2T?D6YX=AUQxzi@`CTl!hIV26R}U#0du& z!m}2!JvYYuhN(*%m&L20;9Lvddb^EFZi*DghNKQ?ZV@wgCW(m_kQQxH{Vr%Bk2o1= zZqcwBzZg9e^wW5M5EeNuTTFCA!?pgQDxMd6FB8wfa;i3jfPSL7XA^@xD|q?-Ll0(6 zeoNke^VLI!OZqv!$K_+AVM47x)8W1>e!zpj(;d;u;O0X6 zLK}}oAGvWF2kY0+HPvD1rKekgPl-juG59p}YJ4hlE>u17hWb@dQp#eU@UGCV&Xe+yTJ+N9VbIPU{ZLTf!~9m${gKqD zU(8QY6~#5gJ@QE0H>oepEYSy<=qXj`oQM;F_{R?XbyY|q|KD0h4O3cQFl&S4paS4k zC}SQjC2>o{F5Pxw(lMB859JxCc>$GDK7!*#ISXFWM!yIHURpZ^UN~k0PCvbNv4w&b zGz#=yzwB;KPfgV4DN(ab*#%uM8&6e@ZtZSMSSt)hUjn^Ed!x`4Am1nM&x7FFo zeAX=<0oQvi)2FV0n>|#=XE#R?4c}DU{_50vPcwBr#jcKuc;DdhF{!i|sE+# z4@uxh@#T6{i$XpZ<7;W(+@imcgqb$psS}#Jl6J58N|=`dyq+NBFrUXJ`gn<~=M`I|Q4(gZTu> zwhguJ`Qi6*Nb8v>I!5ndtDo&JTBkjI1eIuzF^{Jq(7TZuRcH+I4INicze7&s10&)LA>vJaPFu_u;}0-)4txSdvwC!>W0z)UrNj!eVw)5mQk86~t}1JSQffv1es$=g zii8c99mS?cRfzC+nh(3qNml55V#0ZEqFfIf^S7Bgd+aG!DYf*rMT;If^<|g5{?d>l zwtMfZhlP;=gi~H~Z8zW2N#Yb!{CkU~{pCdVf`;WPhF8PBv!kDjoGXdOI`6xZq*1!C zZidX|7?5@qs#M z!==MsT!4!l82IwJOO#~?Z|;kL)xMr%sw(YPf~YOjZuXbGyIjhZ94-~vmt>(CUu62% zL}J@WFQ=$IX^z=Hn#A%MEh`&GVSzvb>YUIm4K7F7f0%8hjCD}1cO<$|HD)N)%Hwni zkn^xHQ~-v|@9>tQKus5BRbY=PAUjM>#D8AP2hwo`y(TCA`NOB*`rJg1jBw*^FV5>V zPQEL!tcdTsiY&a*eQncv__x0$zLTP`UwV;qlP%ao))|$gAwV+fPu!JP?cKs)>06%e@8t}W)~h+=H0Q5 zye2q&SI!tqBevAi4Jk8+wbg>r=~xiiIQMJ_w9Yx3sanQsi-DTif?6%(rE>Ke@U&Ph zdMZ>8npBB8H%$$1xGFa^IsCP)GEZ&)FCx9jm%S6Gnqn(R00yQ}7$xDXjbkz>HN0e3 z<(#Q;yU4jJ1bhb_kAL^GTA-Cygbl^=)Hg$I&1%%ZRd3LY6||O$Fy^F(j9cDiEte3} zJjrzu^qDKC0DNWRPvQdTHtCQH@g7CCa#Kv*(GWfU!{*8I8B7R(NA~i3p+%J-YODml z*ngR3W%oZknU^f5#;bkClj{!_ZIS685e(*tS5+N%u8cQ9jd>%^%A~#MNIS1O|LZWm zGoP!dL6DN~=U}{d)KQdrX2ULE@!abGi_;f`#YQy zag)eE4cN=`mNQAAK8)j1vFf;*MSsGipBMDkD3Hf(Z()hM&9JL2180oy1o2g+2VDh$JI@gTulJ-;h>T)6uwp&=4#y9mLHZUuSW9mmaWVWKF< zpI}jz?EgYnoT?!~9W5grmU|$xgm!Kh{RotJ2ap9YHX#Gpq-zLh~EO_7MAKqZq6`*+F*#ih#bc>p6LM(!TF5^kG-KXl8R zJ{VBDWve&8pQFDKjh3QDJ1#UD6sA^IB9vA5fgE!fevsuHxrTC~MLZ74V&F3Ln=*5Z z>FlvdmYkb5Kcq9Yc$ZgoQMi1$;aZ7yMs|hnm;jkPZ3=VhuHpm|0st9{$L)Dlkl(Q{kD zDBL7$qxr3$|zVRv|$;n&4`4?e^u zay(OMj*$fGD8hxLj8zmM1TyCzGQJ$G#WD}R$IjUgf^gy9>D}pH67jfOPhyoc5A1_h z*kj36{&J2cq<06@Pt_)h@i()$yh9eBhNoKM}ZJ6RJYLsDoHED zO-s#*N1`mcs;%UI+R$)yqQ!Kh4{a$EYF1=NHE^_{RSLYThp1uKsBNm1U19xGR!BQ}cwXNrtimjn3??l({A?PJbpk`IufZ+RD#M*2VVGL-)-q@G^|(EBt-@DOYH~7uel&0}2TA^gi&RUt zN7hn;wp-%x%rS!aEQbv6#!w{udzPd+aIH>>s+capt=Y_}PN}L$xZixx6w)yovk8B& z$6AV6ig_lWbYB~Zx=ssPE znIT^*+ek;yEWm25K6_m%bGMk2m(emSyCSS-r)R@?vb8#7h*t2jC_Zj}Z5LujSyDcM z=g>UEj7*JRVyol3<1jUN`9E{#1^@7Dg|i;DT_%wB2RBt7;doeTv%{rM*zuff_61FE z^}L9ksU9McMUM1-{k+ncmJX*sxY{G(bm}pqV%YCdr!0^HkuL)^RbtafQq|6b6Th|Z z_!fIiXrf|%Jn9o*jbyPXd7vdF$K->8H9xp`63r?NuaoL;E#0S9z_TQ*46q;* zHycwM?B699Ska)-(q?lkD*qd0#Ay2!s7lRWKbkN?8QyqbIGvoWAVPF$rG(zHz;ekz zgK{J%_ff{Q3>^^vs)cBACbIKf9b;xBtzn}z3YfAaFHD;68NG33nyd##Zju~oLS1uH zTVMjnUEJe0oC2w8mO(P`w~)w4e_C(W(V#zl&wY3iy3Yx^40u| z;z@RQnUg4XE+0rQdTNTe3Kywsm`>FUMn*Br%Q$JJA)bv#Zi6SwxBVauLJ1KEpk{Ub zTKYUnF6q=UEFo|Hs;JzDJW zhU4Wc+8XQ-xv>rL6ef#ib@(<;0B@Z1^VqF--b%dH+->xF<>! zAzMbJLq;nfT`EAC(j%jjracMsPnX{`8kLIypR}S#_Kkz9Y!=dSHfeKUYrd3*0c~;L zgUS#@w|HTLu`vybG|eA{NCjS6;=dJ}m$VMz7G2!tcBz)CG^)4VC!zW+2N$&EAu zz{4-0YZ-@fEE2PUWL968EVX?MjcZO~xo$3+LCj<-FrBSsl=*X=tqHW|e{5eTX#)8Mqmx=g zsE9g5yLP^jCA=;XFAdGNTEAUl&x9WrDw>YUpy?3M8QRWFThJBg z6jWQ&_q$5V*|L`vu%g#wq0J(sqAAouRZgy+1g*f7x`X+gbGV7@*mtn*kRHOnV$;Yo zqZ-5_=zTP)oB1@lXiAzsDlbqENs`%pRHF)!USfCnl_OXCl$N@qt8`*@Y#zrCRpz7W#`!JAGk4d-Z^+4a%Yw|PR( zSG%d%)HMRfgTUkDNJEb+_72@ct~57shvK3_Osi`-&g|#Q`n8PL=Yss_3dQ0d^0Nlx?TAd2vzxniu{aD75 z86$>E+c768j#Fj%s3L%56;Mi2N7x9Kg8h1)C2{KCc%dJz^zn* zQpo6j4;*>eY|!pTt6HFYnT;S-!$4A3rC%!?AKEXVsTRrwIxTbN+;Y}m#xOfhuFwME z3J%jZQnD`(?TB|&RDoZ8lJrh`Nxg~v*3)U7D^)D#u~3_Bv%i`;9ppl%Yuj-=0GUf_ zy$Cps+-*?UCE#9 z^OFHoF`C!3Y^Aex$`!S~u?g%K|Ja92*J9+xH&O|2HV6?>n5kEJxL8$j$Wa=d zj>n1=tzWIq4>!PsPp4|9WW2rpsR=MmZKd16l4&R}!oou*T8bZen2?6iuL{|&i5FJ%0bzwBH>lZ_@Lcx!`mC;ikzd9J{m=`9s3nM{OCrOno7OIl02)M2CU z=UrQ1@4q}0iBJ!C3wMyO7W^nr5_Zt~(^@B}YuL>@jy<){q3<4RQq)C(MPBx_&rPn=f=vB6XMjaxCwpUb!R$bkudO;q_>o?fY zjVyHn=DtvrLzc(ev`i#uDu+P&6L$fmtfvr z&e^pg`9Q0rJ+5tkZmb`8(%j6GLo?U1D%&fHAM|rdVO-xzN#5`&!51b5-q4=Lv8&1A z`T4mp_MF}7Dogw4>KeZrAin#kb0Q+-tc#qpXm)-B#VOOD zx0?c-)gHC8vd2?)={jZpYP|D_26Da64p#%M3UTByZA{s7=##wTJxSgg_4r}zJj)!h zEcy7u?hh8*6`BPEpaN(F)1GUp&Y|)qv4YGkx zW6O^`MU;HVy&&>qB=%t?B*rATFzF7D=qx1J7e&_>o8Ga{z{UWV_~~#sR~bUL!UX80 zPev^q*~nv4=J1>xIQVQ~c{_NI^q;JVZ9hAI1W5tA{qIH;v`T^UP^WDyFs(sKFEV+V^N#(84bpk^V#lYCDCcnz`-5iuHn)7zsW4 ze>s{Rfwp!YW}dq8p51n8f0Ydf7&EmV6prox1>MVI;O}Dd^-q2<;oGKKE~hk47?qbV z7dKn3oTfS1lk|?Gw$fbddhj0Sjxp8@Gf8h1dH)=i<@` z%!n7{j_4;KKrhg?)710j-|kdjdbng~H1^+I$AMtf`QaFkZv&sgY{SFXo!)oo50cIB z-WoC>Mdn{n?GTHYmkJpF6&KEpq3iEG{Z9-M(#_nupb2{oyMcCf6Y7hXVC9-TaaY7 zi&;CAGqsHp%*s)o;E=;S{4omEqc+siHQqRnr7gnvghsJp5=9Fl+tr+@SgWqcH@dKC) zbPPGZ7ZXgj<(=$j4yj2a6TkS@WZ`qbt0 z2&V?x)~I~_ZU+1Q({QH?$v1{t9X^J>pUd7G6c$Yz0+tq1e$v{{LxSeIMJoDQ)^V#& zH8p>jJq{C@{ok9ZNP#qIwU%uv&#Y$%bD_`rX}b9)cKsg%ClT^k+PZ8P_o-fwkD81- zq_aOo0ba6;U2K0qUEYAKizcRcgnFi^@%nMU^4V2MHFIT8Z-kO(cbnXNu09BVZv;>Q0d>tMt(gf( z`F7S!^i_#?KIu{=Tv61mU1Lga%wq9+^RX@lj(UoER-5&0;12h)dMrjVdd$UY3Y638 z87*y=BaFBoo?FT$pRF#zn4a5Z+97n_P>b%&(u0EzZnXv)%51o!SV2 zZYilYRZ~v(X|_vvfrZz{oqBJM5 zlU+s+V*G2WE<-SwZhlJ~cLHN7!@PhIDA~4*wXPQVE2EVym!@d8XikXG_W44YKj$I( zrLNTSFNSKjn9bA0vTRo?HUBr*aP~BcXB)wAofd3x9D(%VLC1?}N%j@+$~<8s(){|j z{YPW#WO9b7=H!a&I)PKQu?qc=1Ac`IRM*fg)Fxd}XuT)}C-}n(9&l@q;;Apa_Tf%B zRN2{W3%TG2mmCMpe`wPwDNc@oE1nMje!{r636!P#$3Q>YUn)@O(UJ)@t0#HB3u-q^ zAZ0gYb$T~ofHhT_;aO2AfxTYs)Zi-Dd2~Ix%Vo(UbMXqu+e0Neetj_C(7MPQL*CBY z;OiH$W{ zg{#}e4nKc?M<=6Sfft{mlzjj5pH^;7I_fI;R~Uad|0*RNIw-^!7fRSo;4P3ZNIBm= z7c%OaCXw%>Nli5)KysQW&Y-mT;LaOEd*tz_q6}(Ks9T`z4@k?i$*e-a3(T0UJuy+5 z7|irtu3K^mQoGa;s8~d^!5$_!bWQRE_kE$U1$*Fttk#mk>SvpBrrqBsAb>rP0g?XT zIT%J|PAK1V$DQ5#w})F_JCMHK5!16^H%u7P>M!E04}L)hK>|HUBZS7z~ zO^j+8KTlhW=?Tk5>e$8&AI`X+5rg02j3qlwrlsEaJ2%t9Ve$ZvWObpQN-Qv6#&yqJ zafUXDBHfD#7%B6(IQGd&XFb}H_g3k;xO3zqAp|?coH?0lbnh$OHTUy;@ovs}3)Jvt z_V%p=Mea+UDRe!YoN}f1AZYlXLeQ4IvUj)kVCU!-KW_ugoxivAUv$NxM2YiS%}NPA zb#AjcUBE8?GV)klA&H-X> zBOZUMQFS9F-fcuJ-g;H01zzgzA!07C$Gwp*RICzGiyNh3N)I{?^O`QgD@@>TBxSq{p%?*`HPgihL7w5z{DY(QXz~a&aBNL8Zrl|5 zt>2nb5T`?5Seavs#WtxP#auRva-QvGtxGAh_(wLH@{C7Yrlr>Y+M8tc7r*P|>YZ!I z`UePETw(}qgO!D9&=&psG-?0|tl#NFX?QZS?6fJ7kg57soO`a>y2EooUPMew5|J^DO6l2eB9M3NZ#-q*Op`2$j11(KI?F$$#|0t8;djr#mmtqS$(&1Xv95wu zP9*uu+=3z(HS_!m+S5;#nYs)c7JSk<)3B0<;K2-Pr?n!sT>I-Kgr+7zWs5WxBM;-{ zcSEjcufH?ZbW0}K<7&ybp2_iyCRUiZxk_@>IV+<6YB+zX6)Z@crrliKb{@+iFTz+8 z;6!Y#j}!zDPqjb+q+M%P%Ec#XMsIq|a$wu2ZY=V}PTPB0TQrXu6#S4a`uFd% zuQ-q3G`9G*6n!T<>0636s7`$9JE&6RKB}LEd-JVWjT+CBv+c?i`WIZd?s8$x8>4^1 zD}0$ukW8{I^A96h0Zr!}-vNcc{I=uQOMGHVR@DaZQ)K#-Yr)Mho`@OLgr~9~{0w$o zI&|Ti+Aw@>F`E27@BC;oHfA4Kf70(3ZA%h{?iLf|DbZ+iP+~uNh7Jr#!#e@x-2Ad8 zuhX0fNz@~Fgd0%wtgr$-K`I7XHE*Q;cK#$?G$Uw#1l17T z+YLl^5IDlIkqwiUE*E5wy6UTMIKZnJVAk0ttf%E!MIZ-)*jEsI(_cJDlL>`S##&Kn(B*`q{feg@=Vw*L6Ya27=(tAB!mICI{M~8_eJIg5U@^_KFv3zxZ05HU= zT=12*KnfH@d|WE`90IQ~E2*&GIR#@%f8hRGhkvPAATeK{q=SE5egwa{9sS<($lk^x zG+H#sIAFeE8nGoE{Ud|Q^EQN?^6Au*o%bo9FH3)lLHD+(z2j^3`Q9G%aKV->1}fex zBG-y2_eh#X69tz@#{M)nv-pv4J6vjRdB;+$r7v1Y8HXA76IX-HsCYzQe?+n@dB}wO z5LtbyOU^rg)WOs5Nk_3ngX5Uy5%hJE5F@Pmrk3OhZn)He*n4v4Jz;z z5?C0#NNZk^l3zHub}!$_f0Vm^b`;G4TR6m7L4_|=!1yO0)2!VklNKU!{fdK*0MBlI z9|5q!{pdX`;b_rq9nWDQMO+M z3%$C6ggn;046?_3%r4gayXtkF#ZSqQ<;K^3rHAgAXoZ``hC~mvbto+@ao(HSLD&%l znpu9u;Qu98=99Bri_pWj-5SjC;k@%%G5i?b@ng1?bus{+4*CctZo6GK(*YN1SMV93 z{wXPN6(XLu6K6T!^TzHe*BC0ZjB?ApUS~1|I?4DO>|ocoZ=lwRQlU|K&o;uC1!LqU zDApwsj6q4{bOgvoFu0$_Peq)9h=XBPN;I-u^3hUNBptuGw)j>Jjq*qlDEoZQ6p#MQDW zY*4N3XCFM)Zc8~?uY;oa%9b}v(85j`yc5m@nRI>5YMbQleZ~_;1K^VOrzX%`)6#ah z`90@N{X<*7?=Rby&|&jBZ}zqxe>p?`dd7v*S&p7se^~=dyPcxqeh)5Vu!`M{zhj_f zK!Kf!Q)_+omXZ72891+ldIiMi3XoAZl%AQykmzjj;ea2R^-*@qW0gxX%R${db=@}0 zTAHh%6;#x7yVg8@Tn=we&JYe-IQat@17ffPCo_eav|0qdD29BS7^R$sH4u!-#9zOf zv_^Xng}pnjo%+wLsDv0fk`8y5PE?ITJwlYoA(NVaj60YU))d)eH(*#s^1#~!oZvv> zdq3JHZJPX=F^sfdOaaCP33)xuQ*II&;!W#uw)*kkl7>=itt78?yT)vDd&s)!-eBCq zbqclzunO>8E->G8KglNx3we)+_mq z$U&~~aRI;~`8em`S9G#u_whPM(D<=ux12;**hh6du#go?;x77{L{Q8l*c>y6qgh9A z(9V)DTjBUC-6yHb_W`M@h(WRcrpsPPryeNFFth$R+mHUM@Y|dvk{qzQeum0h7$0YT=0-SY_lL=CQsOcgJ~lU?xuZ@w2U-jJ71yK0FJ z!?qU=zmLi)-y5>yC7X?qM^4FkO(XCg;hQjjDGzRWk#FI$D0E z-ztt9S;=J&6UQ>2<$JBcX(yDsYy2>9y-kpcMdT_;S;O(z{RL2URH7{~q#*jM8Dcjv zsX06K1y6TsAe?08)a80jTd!iVJf%083nDjY)&5R^B)Iyk*XMFbr?!NQlT%RB;(0yJ zbI@2Go;UW?(XMhQa5xyzsdA?H6o_CyG(*xa#2_4F89JdBHDP(^j|oHh)BVF=)$vWj z@r_*HMW4Nnn$00+DTM#PoRbx-U1EYvo4}OZWoJ(n*8W?>e-RxeaZ5G*>BkXrEIjuC zukK<$Vu~#ug#&j|M2iSJ4&9_n+*C)0zDl&@O1ef?0!VtfTxzd5gxz6IMnP>qG}t}X zeM+R~2W8^c((%x%ACYmFTUBqSeXsXboc(_8H1*ZTNNhD_swe3gyo397{%BqUssjty zmVWPf6MT97O$|#mMvrI%(;ruBVy7t^8lEz$ptscK1zSCL1iA~kaB{$AMyRtlN7#ZyS*gq!XYIB&%G!@X?q^iQB+WI^omKB9E5QJomMSN+q&j!C6MPI zB>7#%6?HrOVoGLZM@KN^X1im(&XFQrKSemiNd6CPZy8fp80`H58+LJbm*VaYyHMPv zxKrGVI~yqO?ra>26{k29*NwYVT#NhdxhHv(bI-lW$<6yFvy%C+lJ#NcnKdhG=JzK- zfFt@0zq5z3cOmjQF=mB{2o>h^($+vyxpwsLx1v1KImhR8|Clt{hc2Jcm9b^R_T`rq#6EGJ7R`?!EYoEr{pD8p6dw`le; zg7JGK{%re^=U;oIFC0b$F(1Xvv*C4A5yi>|4!#|FbL*}q{vJPC&HvsSFT*SwuMa1B z?(qwjWx>_OzY$kPpG< zT-VkM`vKW5@_^^3;<}rkUiBkf5^<;r$szbwNZ#Q_>0hqmVZ7Tv<<0*QDLO&Tp);iC z5BttvOz!$^i+9Dl+&R*1>RFH99V}t>a)q3MDnA7OxJu%QQTs!Wx2V4ybMscqmMdF} zXR-QZG}7ZNL$HKBY3IiG7b8=Vv?DKlXF*y=)AK^w?sJ;>&*r|pZ^UkNEz5hH6$VD0 zkN{n~+ZW>QNwWQz-XP)_6Fr~5e1h5{tycCi6AA(dK3Xr?deIOJQkaqF>1>%qB>VJ1 z-2OAVPYFiH!q*nv@x?BGnbipRBlX$)5Pjt_59z=UDU_nLbao@>la^pN((jFRIA2*w z`jbglTRw*`(8UfXD^mx2?ELwSP%kR2yvV0%UC)#Ks zE6)<_B4w%!Ya`E%m9UTWc|ldyiz0^9yBuE>r{(M3mZIP!rso$f8iB&z+kQ=W*`^ay zf$4_C{&rT1^n;eYe-iNKj{^A}#O2aJNtkL$(p(_)sE4YsF7@kLkPLHVw;5f$Q%yf@ zYHX?k^JAmDfneXjPS_d;`p`Q*B7?$P;xa(5idbthhldfjEMzAd8!!{|)1)3fT8~B8 z_vDw`-%7X{<{vnLsUe$I6DKQghiy8Gb1ck=3Y9aZDo1Rj9nI{~%w6uiKlGo95SS^} zpiQohBDO*adhHO)-{w7&cv9%3rmy~LZT)8IuVY~Id1jNjmYkWQQlmrW{b{Lq3Mtc+I>=Z; z)s$@uPryRTAc_DwthVCd4-aY=7pdo7MTd(VVl)VXKGxyhCF?)fVA z>~Kz?nnrny9e5#!8``Qr)s^2(QZg!|%Gr%zad}@jclCf;pZ@oKBD@{PZnAulaCC1* zl^g1!9M^@heIiS`8D9g8^4AvY*j0SpfNav z;f;r`NMm#ZW7|*Bp?H|@d?Qk&VNkQiFMT|zX6Qsvd8BLAW@Mx>%tsOp+%ed?XFRhP zMFyJ3=UZl|ULz1emGi;T+Zmx}$1!Z_%cUJ`N<1=U4Z6=2Q-nUEN)?D~$JlQZX37Cx zFN&|19XQz*-j5sch!+x*7BtQ$c}!U>DwL;=e7M#jtGrixKHJ?Xj>y>xc8Y(NSvaKtXAG@?*F1JrT0sHdLoj%&=hV&Z421apucJ^A&i9vS-{A!&x zvk@~{4A~>tq`1TPewcht`bTl9$=8o_ekP&qF9|D=`mhFrJte3HeLapq4^C%JfH2Wg zZ;uDCnbR2ypii{a*Yh3K-qHjIKtiN5)guI4;QAQ`o=2@R+kyjXas3Pi|3PIjH(>-Q z5bBKgPy_L~IEH)RfcxAW@nBfsJ{Lzgcp3GE8#>g(0<`3Uj`g4deFzUNA<^I?R5vb; zSTH(pg~-|*k_yHLW)WGNK@!10;4x9TIV25Sf!a@0ZVJf)Goos8s*ZpV0Dn+ZI8{eM zNPq!UTu#+t5G>#kb&oS)41^9?M*Yf(X-=68Mg`6gMQ~x7QjUX&0aQSBB1A4slb)%d zcvyA-BdQmnGxwHR&qz=RtT2ENScR%X$iZ!51~Tax4g$l90)&A77A*)>xglntk)U8$ zK>!!f3n&2O0La35#ehEoictN=diYWOCVILFAELnQL=VB>K(0#+;M-)+CgDR4c#80W z8IaHAoCWUYxU>R_nQoZ_#mu%Ofp7giA5dQs!S7~U7^odnJ-(uW)b+(E}Nv z+hU6!wPTdhC-h1LYoNYFfsMH?QGiA!TL-8jQ$7D4 z#R#A$dO!k*TW$#gl{qe10ODp_M8G`DEdjs~p}=SlB0z@FD+>G@mDFsD5`Y8@BO)~0 zf&r*-UBUz6xqs$^hX}n=!2j;a0Q;kg^!J3Iz9fJ@5Vq!np9x#j!M=p8+2D4<)?~0S z;nG+S2H}GkpoQC+1kl3ei~`{2cE$nlb2$S5o90_Sz#8){J>VUOGa5jf+Zhj_&E<>$ z7(eU%|6wGACT|#)|3}Rc!}33E1Kslf!)k2cl>wTQ!#q@*bL$;a3{x(Z5t@0os{zg5>roh26=(UTq0*y#(BWGor|SHU0jBz< zl9Sb}-u$v3t!IcARM{| zjQXW`Gb$8}OJ)jh=K;s1GP$?U;rAiq$}|osx;Dk%xwtLpvglMTixYvPHz`NfS!rBo z7PeWXg^F^S)Z1C;ih0iNu*qtJv$Anv92T-)y<+N%y->(Y3Y-|9!|H2oR=TYZnMb>d zPf z0EnMx&|V49UcvSEjK1-uuX**JHhaP;ol46W49~2cfB1QB@GYF^NE?DW6P?H5?}RLB zr-zL-?wBlURqwbu|Im&a6(^Q4JmXn(7oQT`q!BPY3:)#t0-X^bkX-{JTi#M>MS zjw-9(u_!%bSxiBP%NT`~mpMn{)cGVXL?k3t)I2ikB~!>0m+XRQvF`+X!Wc;kPhl)n z)bHRe;x(G{>xZyCel9+!WsT9Kh$_zBsoh~(V5r|oj*>%814hq_PK~mbMS0TO8QY^v z*X5SUQqD?FKiq&XW}#;(>(a~BJZJb>V-qRspJyG5Pl2OdGRwCqhB6l&X<>J&9%vTY z8XkBS+I03IJvFjR_;Bqx^<&u0B|U5QzVqJOl?ntZDCU!QKc89r@jQUc`2r2)O!~Twf z7Gu^UX){CVs?8Z2zar|tr=Y1f2h~5OOcbS(Luj)VmPz%FmU&JB{w)NjOcV~}$_19E z?{kIJlaIDBp2+L$_LS0B?GXs!XccbEVlRvZxqqx$?pZlvbx3KVtJ)&i!Y8&Zt%{W9 zqpKnS*vabD!fECIJcrXt|9KA7%I4sQ%a+&um)xU2a4ZQouW*Tf$Y(r(zo=^^a$wCf zlvS~q+QMmNaooZ(%Vk&sm`iP>Qt;>$b$gHxb03j#Ds+tcK0jIhN6KH>KcVQR3|L!c zrdUA7XBeZ^@e5dTIItK?)bIR01pR%-#ih@MjQOy)aMh&R&pBrVO*geKw zhTka#4;d~6`KmLm!{W9Er|>`HQue?~2`P?;5XM?{Z#u^a-2s;5t=)qiKeVA8YC-ga z9K#MAgI^ry6t6cSM>l~g4da2G`&94lS>dnRm!|K3{4(FU4EDPlJv}dIpFQu-kJp!d zuikPyS8{dQXB!$E4(@WFJ>MLy)~cYVhwBUN{_o8j(LOCVj_che_k^CbdZUI|wa1;# z@B2o}mM8m4*^hztgU|fCPZI)8-1|^VQ&$u2+SR%C0?*wyv`*{aH)!qrs16BV&>g&- z0O5QlXv;pETUWeEvBzX2Hxj!~8%>=jceQ7R4Q}oi@(f#1^%k9D_v|Nzdd;n#BX0wp z(WCfx=xMBnxyF1@VW)S{#ajJxr|aF+`5F7kNiM?u6ifE>YI~rxlZvjk)BRh`#$>%s z=PYGcQB`$|f1tjTovy3X*xUX_Y5m{^uv|nQOC64}1BA@MiXa~>(#ki5bKi0BI8Gy( zM>U0n;l$VW>d5x{u+Kf0tz)5S%snKUU|Hy0~ut)l6xq}}_tZlSMftu!=cSjz~$ zJ(JNx$fso7#8O5YD&7HgI*aJO3^Ue^rbQD@yTmViF^U(NV%%5FR^QX%C$0OXcQX>B zNwz(TdY!h07c;=|B!3l1Vtu;vXpGH$HpmwxQAap`WQ!F|m*3j`+8}l)ON+L%J0{*; zIqi_{--Q`OMCtLuV?=0vBq=baXfziLyYlmrR$*D-E0kDwko>^9F zxYY;ob~104Kj!NGDV$TA-+Wm9EnSk+Z{T*ZNY80Cp{2~}P|PWC73v7%0yhXldGV8M z{%&aBVXcD`f0YNL>z{{<^Zp6ZiLQV!^5Z+7V=%`rzUb|%-L8Xo=xWf!CStx4%eUF? zz~lD|ikEJ~p!er|)6Z|_lNIJ`f2=t@iJ&E*beJ>X3W6IaR2f1BS_L=58p1c?S))L| zgR;SVu>V?pj7D^8RwyxK9#k2`2BU}62xlz_<${!e6oT*ptZ;gWJcx}j)?!c*2rT3m z$Ti3ka0|GFoq)?innU3Mlz%8EDMzsuQl*FTK+qu`5I%?&1Rr7vp@T?6fDj*u5JV4B z0dj#5Lu?_8pf8|Q5FY3c$O2RXq6H0rq(I+62%txhH|Q5g5VQ=^1#v?i-w7&?$(Ik~slk81*Y0c2EomNGY0tE{jqO zs}86LN&z8L3MSYe6Z&0)NHKvD)|_DiIq<{?PxyxS|q=nuIM3c$Ewg9?M30I%qW zEud~#ACyBS2!D{#2OqSy4I#7$ z6b~bc+K6#T1^ECDK)iv0zJl&y*HPRspz9z{*mWeg4^Sfr1&lw%p$B9&$Pu9p;op}` zc90`{8|jVQw64VPc z`3XyC4+H$)>4v?<7rdJvz7sx)g>}UjT$mp||3BULZin#q_`s+47L?1tm)#uki+9+O zCiM6J&X3~ypYfw8d3ZSg3ognqc_J>D1NZyAh*bJyA500Z94b;F9os(r?l^$bfGnvX zA^0EDYgDv+B*FUcdfxNw>;gObYKV>WcHmNt&$bv zZHHywE(NiTu*8)RLC*~z*3kDOt?=oMe(KtGoKCN|_UaM7eQNy8f$6KOmHY0wBqFIT zi_G$)Q>M~7O*qdm;6d-L0?Ocyxs86 zlqr2{VWTO|ot#&((Ipr3#vG5b2qO1FN;z764bs3 z8vuRr*JvwtN%xjJZ`^-vBse8BmQb{RT%M-2U3-smF!}m0gG;(i{a(Ywb)PbYbAP6s zXsRs8!Depmutxh5BkmV3x>9vpDvg;xtw2PxJ4TA1fpDkwrI+btGw8 zM@)KQI4$?V)eBhT?t1nGJ)6fK1G#cUod^Y6NX0f_;U`7ko0I=q8 zlklua9<%SdfRw29Tcz*Yv%>+zk48fC`bTVKt=D;p7o)_D0^-qr)%j*UmM&I2W;X2E z^887S-j|1sb}LLSZp1&0sAzXX?+>|SDwJP+@Q<0pU5Kth^`d@m{bEa5tcv7-y?Blt6RTdh2gi1sl0eLBGWU_43yg- z6csZPp50XXC$7exX9X$f=9Uk>+MN;2go|Q9mc`^OfgdICSDsYK$$zkd`y(O| z$tEcNs(voPBI>&%g5adIhQO+r?{K}WkgJG|<58eJJ&XO64WW^|B$Zq&SY`<0^QTJ0 z=#p2NWj6?CcBZ;3ynsly68iqcAyeK_a3OCCtlxHQL=z1^hY_Usqz}d9xb}BLHw5r9 zPd|-lef2|grfK|~6}J`+OHj}a7HDJR3?jgsrskd#4^JyPvxaRWdU|5@Av-oZWQPt7 z)B)+Y8De(Xyo@l8Oe?<8rqrf7Q%6Z&Am)Oyr#p^v87U-fZJCFy*oXg+KH)){3&=J3 zmnG5IdUOwsgD#xN|La zC=xtfW{>qH{tAViqU(ld*Q@(Y&EpPnJbdF=$0wGHlRqkIepE5Aqok+dhDRZXfP-H; z47#Vr4!qWubu)=_nAeYfu2$hUMDh~f2sva)SF1A-X7w>dfM}MfFwQ7xklv^k z?&FWYpJ6?2qjW3Gczh~8<-vsriJE7iTr&8or*9BqCEk9V>13lOvGM3ahBsi|M=+se z+52eA@X_q#nYuY*X#rg_Zv(;m%wV3@3yNzz#y_wKBePo!|p#JU_#I_Bt=6x@Sm{TO|aENrZ}_QBV-a zVDa{Hiov;O4Q>GPW^=5=tdgyfBYdn&H?Qbq;`sy@TjgBbZi4hXP{M>u79=Sdz&K%I zEB%sApeh>&0G6D!x_uH~^C0au;^J-lz;>#g#rlbO&P?DG1M{)iO^_Q%?6UO6EGhMB zNsnu<>Gf-tlHGdIBAUF0D2o!>&HQ>Jp9HoyRwKo2&#Hn?cGyE{G+AFimE^N!*U44H z5$xZcLu(u&vp`a5b_^mWwz&Kyc!C~|JZZJVH1cx&B-k5<8<|2m&H7&Kzr+=hQiW>q zES}a(Km0!6N_F_AsA@ibwhOuyL2@rc32`v&RKilu>XhZZn+zBl=>$AB$I0<{NMuh# zg>%M>nx*{o%c>A088J`N&@!@8M0k{%)64VRzghixnc1-H666zF8E?;)F)w=E#re(b z%|8~OH+Lk>&o{7^^l0Am{vf1mFGh;Dyo6bk`0?}bugG&=&!)(-A< zqddRlBRk>z!}9MfA;ve?X4WU{*SsIAMRXgdHRy6L=>|>)BI{_bFXi%@+O+?Kl21zn znKFw6vi4@+wnlBGWI#TNm+&I{3}fy2yHb3VOvJt_u1#wEc5vF^1xgA*I+$K_2M<;p zS4G34QLS429>FqO+O+q|!apG2)Dzc~it36>m2$PX2>Zj?MER|M|K$)>c68#p{u(wV zf6jW;f!Zg=V)LJv1~mcn&TcaMqHd>AM7F&*sW#Qh+dm-vcZp?Bz&nS zTUw1wBk_>|O%hBo!aNxk57AwCDR;rmNTaAf4(qe>8bZm(Kj=gyJ!_XpWwFx7Tmfry zW@9nO3!1#G@rG;Y*wPGh&PhWbgjJ9qwhC!G%S@zB#g^8eVZK)k>PX%c6CC@f{4z!R zXv!E&E359D3}^crbOtNW+H|0dgovil6cd^-3fLu-6L3ckpY9Q$-Ce?sgCnKcg-4>V zWy;Et>m;tK)dgSof@{-zI(WPFM^jtNwiw=t;49>Fi3FN@5g#;(*#kd?WuUz1D|&Z&IbgE)xfP2Bh#u^7@I$_7Zu_$6LeE$_vD2* z0Hja15GF(i-oKgf)cUVOFb0)}(%%1e&ee#H30Sl$sbng6ASRTm_RjwXHbXHV#KFwc zrhJOuxtqKZ@!(k&)a^hD;V3A~QNIfs_Ky>m)%^M3J$Ydn8c(ZE@CbVIdB21kJ@RWb zF`|A?GBYsJlKHYQ{l_9eYzS2G@HZ;T*#_UDL82G5YL8B09H&H%Bd1w1W3=V|Ah??& zJdV5`Cmo<1l_=}I<-3y=?!A`y z)w>XkIsa*ZK~GYHhzMEFMRV9hkwr5jM%o?{t5un{K;ERsV2`V_P`&XdlOikQu91PKZAez4yk7Bz)K|x)V>sQi zFHBcjJF!Wr)BWrQli4nZD(t(0_p3Ua<;-S_nuP*_wY4 zu9$kRk*KXqbnS5DYV=(T-b;HCpP^ef@gjt+6JdMGgGIL&RsDNH`{-nrbSc7eXi#yG z5+7mg;NP`Bp1$}|tL4u-dM=6lan}?)OyrGDLw5UBQ57-DdcTC4f_7;U(5(q^7^i>g z_=`fZut$j96f>Ea?!8^x6{J-06X7MLOC)_j<=BMs!qv75msU8$sKMa0&ONa-YlJr`-q z!OlcQ;v-JQY>O|A#|+FrOMPjo-iSk}^qW@m)GHTN8G;Oy?+Vvw(J}K}j6uNGZ%2!T z8H($;YzWb)vljgbrREn+3`yDH=f_C9$&o83?;LY6@4;A zY%ZDY=qH(9Oq75brpDjGl)siv$RK=GbK&vSROPg3Cyle;WqaCbJU0AleanboV%5c2 z@iaoG_MId%LhJW$dB)TXYmtby80se#(WD2Zoj)W$UwP2COm6Zv>33w}nriLFt2Viv z_k8g0cgBo<-`>-8rMJFM_xs*^s)?(-a9#Z^G@m;6LO#Ku-3a!^bTEeb#`LLSdMf$4 ze9xfQSwJ@2^y{bb>0SsdiTu$_5wI81&2I)#_{P9KIM<0a{tLO%bG6gYzty=#Ud#7x zV1~e?ex6dIu+da?B}G-yQu7QItYa`D!D}kft9=oN_)v-YUFj8bx7-tl=2EH7RsJ;-N}~rxgNs{ zwiWAaoZhL&PWf0Q2~%){EOo)U>CPv3%wUJli1nclq=7X@sW%SUmaMgGdh;zNo{e9J zD256*$%VdV&adip7FRi+ZB3q#@W1beeW|_KopiDvi{@Lnb;tYTf#K-1>TNu?qu9~r ztjpnK`b9~_D)Z;Hto?!HE4nZnjWL5Q6ot{(GxMaSyI7a&a66( zbEV$F*WQiTVOJo{atJWu^<9 zcAV)HHiXiJjT7UXdZc^j4{0O}#Iqfi~wY>viRtmQu)epNfkG>-)%% z_``Mcu`Rdb#;>z0ir3S~!aRcT~O1?=_0wsG{f5?N}Qv6ruGIrd)?H<}tZ= zF^J|ZuK7%ON`2kml(L#woSY-3k}6ik6ilMB#;wT*(B@F;S=d&~ypg5ZdZ+fjqkY!Y-|5lJc$0OtdEH-^F_MXL zRV|^Ka+?<(fUGY>}C1l-K(!!}R1?nb);HY#-G_;a-MGzwVD+EU5(pF!x{RsaJn`W~Kwy_`W@x ze#IEMX@)`fNK7SCWuKLz=^c__+UN<9O>jwa795?M)=tWSg0EbSA)6AVp4`GkKbAIj z*T-1Qlrj9~k!alPGT}XQ&rm;Qz(m*W$w2G}rN=x_;?dR6@=JYaL2m-LuNd!KF_#6t zH-(6WS95)p&qUN?J6lZCytg}Ag!D#e&(~l!Fj>KOft0;T_1;cOekC$>?U___xN5C% zofvtAf>(orIl?TZDRLYMmi8b%wqYYoJ&^DrLU8X|J;)??!tFYPyJ+6_3y4JOA9w*0n0Z7+<2&qBFyb-l7ZtW6 zlL47lk%MkNz1~dD<=Mp(p(xic9@t2fn6f9SmtRd7f>X_ogL*CO%W}5TsfBQd%T!ds$Gnd;BM9QkPSOFketJ z*s+CHXYt(bI-3%if4cF@dv&r?UraP{8q?b5I4@u0l&-Qs3#I{&B*=5EIKHNUF^e2C zW=qf$jF zkIMdsboSN8W!mSIo!Zr*;%$RqQu|YpKRk80#TsF>sDS3+z{Ce*I|V-3iyw|bgbx8IL{ER(0NSJ}c`r~yP6!&?7Qd>=$;i zNWPg!m%vAB6=HRYt!HN0+ZKMi;heNT$x%m}{`gDHYS^R%0T`flfI@$&D~Jz}w6lMM zi@;rKD5QmHcTA-fZIOexM+-BHYPpBNN`JuC$OE9n6&lma(3f}W*;oKv$Yrd-Sk|x- zq9md&%NEh`GHl#J2DBE{QIFd<&CQQPG!CVSEbh+5OqD;FD%qp5q=oi^V6X~=aLr6F z(sC007AUWMxsK@orC%coYL{J-@Qfu0(x^wu6GWJ@MWzxEP{**tw#uUgr2i(BGKN!D z9KN_3Zcaa5Yf0OO3<(J@e6F|2otUDnM|Qnw%SqV8V{y~hL{7V>?U3qFE@*J+LN&H3 zSUm3Pl?o=Y#OaA>Q~)ZN4Zh1 zEyxcW?Y0vy__Eb+Di3}}h{S^;$puglNgUF5d#}$GIjO1Xc0?md{g%ktbW^y9PV_{~ z`B+$-zq(`g_qYzRYZbB&*)8kk^{C3=2S^p1rHne&FNQBIAKHJob*WNcQ?tjmwf(8A zY+vBDlrOY~0`;^_&epoJ*4zxO)fnom20EsG9!s~?ZxHBxo7m}B%e<8}!s9gE%Eek3 zCzvuSzGbVo6UZXL;i+=WpFOg?=QMV{!)uJ1!dP0QBB+vn(NKy^!pF_8LcKsYCi@!;BEZ z0!QOQ*2z$FuJrnjIZBED>@jS;Rhzwysmefn@t>&1tOE9Yef=KYd8}J3AGOLx?{C{e z_3l2$ZfYvtGf~!NQUN!!AK5Dvb)x60vSy&R(vU_=hqyF8&X}|X?DswK?c2XYG)6_D z>zM%}JDMm=U-BsMhclD&F!6!2yXpOg-@4!xc|o*AtGopgc9hrwlu>ZtY0fsC6bWHi8~gF3XFNdEc&!$*QN>UwkbHYDayAA) zpY6BBe|wPQE%SEH2u%IU{Ps6Qy1Dv)rKJmwkFw3aTv^nxd{ zac9^KRmjeisubA@jl;4^GbNk0m7I%knvn`GJHJ{D#MoAPyQCsA*e6iWZqCntvz?RZ zidLPW6ZNLH&+2NpSNO!H-Sg}&NE=|R-88c|F|=5EcimRu=uDNg0-cSfL2>$pTwD9&*7;{Ke2osB*w?HeW?gIdW48|^if*OT z1J({vEJq!E<==$Jn7))Q>{&)oAU9aaN0NuDF&MBbjeHDuJt;9lnBr=}tBM%0{B2q% zdrnp9eJG>uI`Q#y~j$-@oS5?k#=`{mNzDj#d{$ zeI8owg2!^)1yz1E->$N!)uqtKc9^k8p57f3{C2 zx=h9_j}0TO@_%a_`LYC6w<6vdhlN`w_K6n{+w2R|4$SaxS4Vd56!x#7$CHH6(r88lvOKRL>RU22hTOM)T`kt< zdIn^|pY~hOrO$%Y4hplAir9WGpGo7KH)iUxZ8Tvi-usK(4Efk&;+ zVgzazQ&@g&tZ^!fcx%7B*Gqq-ijvvoV4M?*{=6)uV9#>j>$-v-m7U~IgfHS5FKgcA7n@UMUgz8`jeWY=?BA5tlxKZ*$691e;qX@s?nJS3+|#n&2+_;WEVYYbod>gvAVi?@Dt87UN$mcE~C8}70Y z^);6w-rXV5w)eMo@485ysI2?@G(GXde9>tMx7Ckx`_Y4^hlg`OHrg*(t^~p(dqr3@ zZzf&TA0J%(U192{N@&lH>^>@|@Wvh(ipl3~KgZ2ZCw)23zEgQc5@{;(!m^RQbX|4h zOZbQ7=$?~-Ga;gqJSC8d`_Gf7ROm~s++Til@lKuIHAR+yrxF3lu5M);ZnwIsR;=pW z@sv3da>#_3G*D$0X&_sj+&Q z1eCAIb6sg$N6F=>T^R1_PBau-@DS6?VsYB{_kVfEy= z-dEwJAUG*3K)Z#`bI~VgY`ZhVqc1mEo1l#=VVwYdW?2HhaFVwrvJn_}DQBD+Rt1YG z1}N_m@#C9OP3kqSI>290rHHtZCzHP0CHmtvGHz&6j<$Tazuo88L*zzm2WdM>?FeEh zFe1REk#Mk!$N}~g*hv0MO^``qz`4C(fUhTs&ruUDW0Z(zEtzMfKm=w94P*t-``+AF zzLvk5HJ2*yuc^4x#gA~%1urV$05IW`|4G5|*DD%giLR>iVT!G%T3S-IMT!ZnFM0e- zKFCG0@u#nvsh{(87W0SQ@0EO2n|kV+1_o;l0b_%`VBX3F)WL#{+$zsxpCf`$XC=Sr zm$a*fMaI2BT@2eCuRLE;t5PnaiWr*Ix-zbP&B2sWLJcB+rsT9yYjq-brXocP+zM10 zMcH2}A0O)A->G}~X&=Jijgz-3vHj$NrxY%$;PX?rxUv28f~`J26vO8yY}p`*G4&>6 zzZAe@C|n95#mQeLz}Kd31t7)AI_JaJCTZPrAvI$@w7Yu9 z45F~ZEqOQPu6nyau?HYMGJjv<-*Op{hB@JbY4z*OZTEZ4o2aQ?t!j>$w}Y+H1DH4u zkD{TUZSKi$nr%*Joc}^jI%XWwn4@?P1?UHclErVY84r-JJ}h0DkF&O@!)|MJ?9ln` zG2l_Y+xB%0SZLV+a{wx6&~w<%I=!d&5B3KwF9`FS#J|C5oPO7w zOg-e41V4<${?QWY<05WIMYT*37?fa`lU3**H4&?e7@Wz9B@?9YnE}4{TRk=FQ9b90 zIj14dE?Y3giLX&TVzLG$6S}4isjK4_eCA7~%uP1(_M>JbWc3Bqs{$S7n>|X-eoyJG`S{q`e7m8<#ZQSKk z^<=n_$@)^1<%SxX2Ki`8q#9I1jY^i~DWHB7>y?ifMbgnG_hvcYEo} z_ZFFcXR6hue03a;Ddekb)Yy9n>lwEjTi7#S<*9mTp}Q(7<$c|e@%#Mp(*_OKY$x3B zYqWtr^dm6)X+Q+@^f=5D>;?3PCmZ%p7)oP>&f_QHv*o4xFp_N3%iKz|o=X};Qzn6=Gp$+Ql+7QeXKpAkMD@K(?m-Udfl%=YRoHfH=M>aA=; z;+=#;{`CJvIg;mpHe*D|&B^m$W{M1xrW`{!PzNu(qe$B&5x_G|*b+>QsRF0*gLg1I zoQB0%g$d+d&j{^qGt>x21kcgkOP{~^SH<3m_;`lc_gpKJ{4pVQREF#B`9ea6UDJ5u z*;jk2KUPTjPdZZ4INQ<0d(!&`tFXuu$Czx+8BCAgDTtt)&1~%dMQLl_@(km~L?m8U z%^AV4^{Aw&)&WCAV!2I=s^+y3J*zurY6MOt0pV9-Nbqzswt-zu&tHM6;Ddvo$Uy1W z=nFabqdm_P4q>!qmz;it8GM^$?9|V~h1_c2KVlbh&(KM(oF{S>S{1H?nQ{_-;HX8O zpNrKG%8Po5l0l{85z@}+5tZVTWraX%+deu z8xX=ylKF~z8j^Uu5PSS=u-)hmLMhVy?^9m9|FZ~iQu6Tgas00#uYd92|1SX<$nf;j zupk%;RFTeZZd57&(V987HU^V1YV^PrD9I}@y1~#v5*p>QZ58bFY}KU67(%I{e5u($ z1Ny2gtk&Bu1;_iTV(HD{Qh0{RaTqs$gn-*956V{X8?Wehc5Wf z1kFBW0t)LIamo}UKB6XT+Ur|(E2jp+xDjMi0aosI(O+F}Z7lr-hajaD&D+;f@l^DE8a>~AvV)Jp6Okq(#0ASglxnyLgAq_XQLS7gd8tZkPtmkvoIcy$&rDdo=ey@8hSK5_g7vGmvL5gquA=(6r47 zCAtxz`z;(w9_N*4Tl69GkGeS*4I96ZN4Q7@LSp&l2EYo-&oDXJoD_CnseoYnwf`)Pw zFg{4V|3ydfe%p4xGb5zL4P)PoXg$_jXEJB1Bl==o|4`W=1kde5&47cOaLufjsAJjc zPZ&>Vbt|mrH1gVT2Lg%<|9Xs6YdX=Oe;HzVkD}Bj_I?yKS-B_W5)u#V!RojF;8r zTJ8{aD4;DxkZHzSWu_P52p@6T_p$9;`hncN;)wdM9};Y@;KT}vJ0e8K(=%Z0d+GOv z#vW3BTp^28pLZo_rjCfQ$oNMq~e+vn*B0q~^e z`)wwExY+UC_Z9f;c-mvyMkstmqtwgx@;UjcYY^iSMX4|RA(iP0D^3+b%recKKcI~3 zw3lLB<&s{hzib@o0X0yKQEX_FhurrH;~Isu4Eq!?E#Sup8dze7$J=HuFb`n8;lbZT z1mNxHn|>rfkI?zH1L07E?ZR;xzmO97vzs6b;S^wLO8|&Rd?t>ikix#EPgE?rY>+KT ze2{W05qV)!Kcapqy^1(grg7H1fowk*7Eg1$^mbqdMj7$o4uyVyOg~hTyySP~c*8oF zrX(fMB96f68~nHr6EJ!*_dzUOvlKcBEVl8O|2;d4>??Et_$R2bDIk<;{Pn&nv-0>? z<%%(sV(xvEGyGA{VC1!NAikK#v~-J{%3yidPL2EFO~5XtSrjqe*A^8+p;Um0+o zCj;nT12N1K>tbX&voA`&_`Or?2{KSl)FTR;-*Z1sl#9EW;lG3tk=A8h49&NxkkX8f z(J3>K8FSUNTH6nbkC38ntpn$BbmK83jIFbKfR^OG%b1={r|@F}!!hRpib$8&gP2q;@tX$0*}LiDcfSlie-a=^q!w7nt9R z(lMEvlq`rmMOUOpdsY0q({K&r>Bo`9lRA`xq}EW_oYqCw8A3De#dV%RD1M~QudCqI zsi?t!LCIfM>203Xsm@@3SI3@S+N>$Ps@?svX2|o|V5oA3vsHPT@AUvV&^V8y-6m}} zv}@MC8#T4Km|%0{k(WlPT*e^Uq1!MnFH@OK1Zxyrd?~D7qFeQ&rnDH7>Ys+B4YgaE zDnZ%-oOezFSf%aQ;iddrLvx*Jz1b=gKrn>wlq;DKvYQ&Gwtn5PNUH}^&@hiZaot1} zpU^-c?i|TuK|gU)=F@j0NB&H?YRH+F_QkB8=XS*<>0n9wj?lGjg&2e7;Em5M-Ls2L zeRKAWZ;YOf3um}Tj7;q$+D&V=3=6%2p+~1Bf7Il5g>>W-JCU4+!nD(br-q&tPa%eS zZrYzf=cf*VlK&WnLoUZ3R$O)oXl&aH!8p~jTJdJnT4R;ArsHDSyeW5vb(f833l(=} zc@-s!j>CZSglAl*FMfMRg-y+cy>yA5iojych1(rXhD#XcH8SsElnkZ6%F53)wXj8O z?$aQ228};hdBOHg#`@5b)uC_MaN+Rg6-?)duRIk!GN}_z?M1~uHx6zpgI#We+~+_; zm?aJolFZ)0CLwFRdgsXFDVKZ(VdtY{y3@~qSXQ`xd#ZbaQge+p-H9h#`OmCZ!t{h} zcXZ4o%#CZO9qs#W+kt`*GPKwhO1qeSPMjl_&SQ9?GLY4a+q6b-=hnOGGFjXQK#!Ne@RA>A&jdC^;++TiOEc; ztjTY>#P*+Aa!k(B5mBO=44ZrV34cqxMZmorPF73c=i0Rr6K8n2aS~S%bYPN5 zf!p4cXUGTRYGP+YkqhP3rrgXlXWZjF=qZS0L)c%sBD@q+HeP>uY_2$v+)iaVU3=Zj z*IS-mYD>$yoK`= zijg6+C7Qq#&*_T6xEDuS!|#kCW`T#r27~^DmfsbYvd4566D#ca{PEs@4-pjiwobXG zVIBP^^|<2TGK};>(<;5*1I~D!>b7#c^aie+^XOOpw8ip zx%lkej^4T9j2QZ+EN~Ua;JQ{ji<)HmnEOfVYxV_cjKDSR`(C9x^p@HLC=duKN#?9l zG-fh}onA%Qw7i1(z-AZIxtRI>UZuR2V`Yvm6nydv*7YY3?fG z4x6;^n0TT5!VWj~J&lVN7a1I**7j%*1_OSv^=)w8nM#vjeUmhQ`u&Wj5HRp|sb|JZMa=71-m4-P00>Wg^`BRpL~Zbg$FY zI#V9D2+i|p=&mk=ot-T#ESkK2Su$jxidr0e*;2zzG-v*ELgDzIO(?`HEUf=+B+-u- zwgxgGh(8L1rm{x$OD~bkC{UZtT3M|@aJ44l;;vFnk+#*Hhf%8;1;2fwZO<)#R8Zy# z^*Po-El~iW~A7f2+D_dJIOf~xWF?0z$fw+`z2%eS=!LQ ziyI@(!B%8VTYY%l$v%f$IVTP9{=7B*&1mj8E>V`kxGV)}3Ec9`vHq_%kfbmeRR;5$Y) z_A?_x&Vpj>C+nCQWguT;LMV#J52*m5f!K0bSurJa-L}GV*ER;NmR8IZV{k4w^{R!E zNS%_}#kJH{yNZPp4Ys81j;o1~fOek?_vd}iIMzJx>7FZwqsuEql4O)ZGF@23mvvsz z-PC!`O{7d@p_jB=W~Kutg?C627mBB|wA_|{3~L5ks898fQFPicjoj<`N9!wcF+VaA zncKgC_o6@rz7T97Nd#KO?Pe20gRN;8W_<;zUGJ(?WIFH1mO#xv+mO5FRYC=-GPE!i zym)V`RZ}Yt51VJMj{mkrVDkK5cIE;fp+Dd8f4Y9V>AsE*Yzl9Fbkh7W_k<2~&*63$ zzKL1S%Rv5cCw{9a=)cDsCO9w=$t0!o`L%gdKpAHT6Z8owLf3|p>mSN1R4hi)Bdvqr zZbtfyH7bg)jH1AJW6Yg>>(89&k$3% zc=V@IQgS2IQcDsuA3by!N4y@pB5zv7;Z~>kpPy4{4>cRS`^Z7`_GCZ(CW|K+1I{oT zlg`PJ6AY8K6VpaV)Wl!L#1Bw!ux<#S0Y1X8DXQ@DWgV1hZ?FD)g2}nU&2L3w62FyB zP4};cuWHgi( z&mFSqy50NbZ>>+YPqAo`tYS+E#!eioVjM}%qnJ*F+<9*(YcD^XsGm_+_Iw-+B^W5z z$1o0LoG_l5J2Le|Ysr@r&c+ZAB%IKmVLNcnJ!m^(&ZR<_KW!cK8N(Xmc}F)72%Q-J zDL7$0<2-XeLq7S#F7>5tAoDEbe+tZODEPsxbqSA~FmE!nlbUfxB%(G#uOYlk8%FcV zJS0E|AdA>sQbLc`jSkMl#)*szP@JYyoP55dKfQBtOZkcB5-TQ_jn2=oemSHQMGv@d z-X7teCqu%X4T$^57)1}iYk$Zo9&;UIM_PR3w_mqHp z%^L?P zf2MVgkI-zLiB_Hjekp=XFY|ut?E3+_@aWQ;w<~HM^a@>PwxEfTnjqo1lk10)B2tY` z`1l;JSWUFly001{)Z%o}ZJu7Gm!1cI;(esN$u}eDAdAN7#tm_SJEo^-<5I3YOgk{@ z2F zTUo+WSq3$eja`@v!|X>c<{ZgXQqHW*Dj)bfCvm|R!(+5Hk)N?3y~$>yqID_;fEVFo z%kn}q*0`Qpi15xq8RVe0W1ug_+vODsX8V3gsv{z6#a;7V9_(pwClA~>l+cRr6H7dcX zh_zC&mcKYBGbF^J)8?7XIqCLwQf)&XIS}ZgCN7Vy%H7*HdkSM0BCN#CTv0pHQ*GF3 zSvw|Lct2-t(?$@?r2U1OCYpX587qbm1!T2eBd)jV|6qn9?Z|zRx&)EAmEYV*^_KOL z{P-tiQsYd)l8;)kyyIy5nk=--UqqB$jwAKU%wVuAgj*Yfa^m~0G-qI=Fl>RJ9VhL$ zIddQ#n4+1DB=U>CSp=C(h*Cc1zoZH!H1kC*o3e*o*+ZBE6peKlTrHI7|1Oonq zcuT6py^0~2e1`@Ot<6=kt;BZvL=~(+U-P|Msq%u{l<~#-nm$7K(YYI!J3gt%k?k%0 zJ*Q~&q#twRe^DEjgo~bVTkq*~Q5_$6Q-weEc26DK$wU~_>z3!6y3<+`M^MIm@fw|@ zxFg+#Hx(jtq9?2w9%=OWvxBe9Yarn?$$UJmt4RKQn_@+uGhStQI5@m1|2 zJUhERuJUvM%eEzU?u{P1Hy_ai{0J859@L@YW`z87Dm2h-i}>J5wNOWG*x`K=oQFu^ zoER8dMNVfnev>$e*hm>XOaCi;!Ql{iWBVhQ+wwV#*npByt<<_Ilo zamJI~NRou$wQ_MZRm&Bpw7M-~9t>`%cCCvtUaZj1$nqXN^1*m(FPG@^E5|yy zzPI#rHIY;w31g82l^>?3QfG_|@zIrV?@hVM#Im&3wa$=De zJJdG7Q}(vb=nXruQDQXp;&wsMg74@0t+2Lq73c}aqlWCLbRNkkJBNWsbam=TvWi=kSa&9;z=fzCKhWlTS5&m07+b&K&)8)8Zy%r~y_~ z_^>BL7}lIDhrir00E^{JoNSC;>86AbMrZ8`+(veEPT}~JE3Fd0TpLNX5ZI=NoIc41g3FyN3h47c7B4!_>2qrn-`;v z#!kI9w_QNPw|1-C&+VopqoPBESNApY0YiTrb{YD7u8vtQ`0Q~wHsWk~`}JkwDWhVT z?K64VoKo*Ga+wlzdga=a;#6zid+(y#>#=FywJi`mE&kE!^G=p#Nw=iwFQbC7hwAtF zdL%IZw2;aVKN5U>e}leigQ6KREwMvJ{ z*Fc%rAG;ghan=$59*fWhX8~L0%4N-0(m4K%Wa=FA=2RQC5Z!YRLi^`Bs)oZOCTbts z%V&-4J)nhy%g?8yCKY2>L`BV`z>qe^r!8E&a79bYfTXxDBkPk(M?iTh^Y?Gr_0Xpbh58wS&tJozDA&HbFL;xfuJ@r4n|;Y#g6Zamd?u9CY##nDEgl)Q_| z*SbohF69)zwb`X(pj`Ek9Wi<{8Prmt#$m71nO1U0Pz4@kODU!T)jIwYOhwr3kC^^4 zA}=+i`{Wi|%TOT&^JVI<-a4*f+sMlKH|D=_=on*7U+vww^4ZD6IrcrAjHqNp&a;dO z8z;?Z7odlt#+FTq75AHHtj65TFD^DX3pVv29D=SsOBV6a9wV=ak(@(X_S9sIuKq&I zCCm&ydRQmIxCHi)=XO;UP}Lb8kH5wn)@2Ylr|rs9Q_JFmk$!%69SNmhT~EE|sra(m z5|-8(OWA~d=i%U^wOcaMvR4q=MvvH-w27*z+QKpHO5Q@aAar29|R5aV0#q3V(h-mYeeksGCM}M zLRJM@%LCZ@KQNM=hdN^+k>Zx>8S7VegN-U7wmHG-Q#i%L^_zie8c;oo7SeYTON#W= z8ThQIXDL4r8CA7yFcFpw?{4m!yzRVi3meTh?~ziX)5=|BeCs_uP1j2=-)6d<*Kjr2 z!kcMgM4XrwldK!fyK0;--wtB)l$}0(RKuo?Wgbf`K_ymvV2YFfHmjE2-|kf0V`Ytz znP_c^1cw^eNEU*zUn5}vs9k+@5Uw$*H! zsF*%}aAq@;xY^l^Mr{3h$&GMqa;(75b=A8$n2ba9%1#M@9ogXv%q$n!q(sHLP}`5 zTiL%G2FuozF_0fTX2NO56^6S-j5 z`zXoVy!7X3uu1Z*gl^*S$B7DK&ubI)(}vjvF=aL^vq5VUCV7I{6$O&bI75DhT&h_$ zEahe_PkRfdc@&$8l!>+NX}t+9Md~N6M(XfmvN|aa%a<5xVcc3;xIrc&BRJAD3RDh_ zzrK9*jY3mQv@_Qx_|rVIN9HyK)0#=kOoGBS^mNUkB9-}@xtRN@7h?=_ztna-0?g7} zi>((P-kZPC;o=K+j_mAOU=9t(!7+duaaP(e`)msa3iBs2=6BC?nVe(BG{!QaX;6^D z8y2I7pdQStvvFw{I50(r9zGtw^lzSgY9p*^d0qPRdh)bBAYzqtp+B%kYq3&&ic|2o(aSZWian-w% zV>0d-KUfO-^4sOEDkff?qRTv08}E7Aqk1Hbiz*P;3ZfU1bQ{PYu!Dy67X2<^S45JX zIVGKKqJMW6LnDrfk9~`p0}!v2QedbW>#tF^^Zu4Lt!J1_Ee&V~5`^go&8lgSrM6$C z{rze4@=Sbnoz2#xWt-t+GSswMt)a7b`|2|`R4bprO`eT4H6z8L%~`@x(!Nt2McYX;*R&i6M#}5vX)k(!%-D) z(7s1WD_cEP-CUT@$|!#^<+y%R`g~&(2_CB2#p!Zqdj)@bo`P2TY@Nr* z(fNU!hTi#BQsv#>Com5Brd1|Cp%PW;l7B;nkV^rf3}l?@w?oHbmMk_Z&a5oADvQ4p zf*rXbye75ZXp7Tf*f<;9Rn}xoHiGzpSCpzIdcj6aHykewF>5B7KVHmX=_6tUX{c6% z3{-w(=VBGP^VLdH?TsMruFF*k5H zjGZ4po9eZZR?yY2P^UgrMUXI*R`nRGY~E|#OI(7>)2K+0CAA{#+5#!C!stHyZhyM2 znK#01KE23Ype8x?;?&!8v4~neajwMQ~xt?5!sWlws4*UxgXaC#WbRT+#mi72Rh>$Q^GB9|v@s?S$+ z;n_OM@?oMq%2Qu;X4U7eI1R(YtXca_2c82r?L}=E%cSB18xeOlH%cq&c+7lKxO2l|~K}|LQfA z9fY%IPz|Hr4aF>H93>3Onr(h%%^{&C=fR(hDWG#9%&-44DLvDLw+xvT_x(+IPeFG@ zGQ0QL2omX0Dca$V5aBid2wRIG zv(%L5J@n&4>KI8bQesqxC$w5Z;Cn@Np{(_7Hu z^?4;-w#sTOa|<&%FZ;U1vrEoJJPNyy&}hJg`CWt=hYs>oXU=3+;UJsD;{8zj)1gT9 z+i8-GP5zu}kez7)88eQ6&I0)dVaWTedzaPR0@Z^gypAx3Co5c(qSGX54e733GL;%j z2Q&LP=wF%DrCF4JoT5!+GpaTqk%F9j>$Ezm^Og-l7_7B8Y7~m9aWoJ$>L23+h63vx&nM zijK#&m3?rW+QWXc@N{jrWA}GK+k(D6Ve9Ies5*q-bRtOm$OWrB`T2S}%c2a5Cg=X* zq5Gzx12Qyw;oQ+2cYvJ35Rk($6|8?1Ro@4b#F|)!$lk_epY8Kzjg~AT zEbC@^Dwmy}M9IL{B&;gY_m#p*1PP$cenVGe0*2c%<@J?IGyZ+REU}qbP3avVFB$6* zNt|c2GN>=1r=?vzIa#?&or@msY|xjUJn#)tQtdfyEcrcU!0H!mmo^m?lbpq7bU{R- zafzC!Kv&2pYj~~?A++r28=ggAU0BWySa?RwZuOB+T0xc~Lx-o1ru*W; zW$kH1<87AlW?-TSJ)EUN#jjj#^^9L7?=v#wX3~w>;z|Q_g@N|8*AA}|aB$U?*pC7E zXaIIu`bjGVdp3o1f>_J?)nJy!`37UUuZ$Gl`-(z&0j_z~X4oMD0y$GBnHmkhJ|42{ zB;9k4vHiMiJ>E*C;`S(?Et4{dx;TH~VB8iw1#Xpyx%?8w5$E1D9>b3$hQ)NT8l61z z*gjlFsj)vV^OH*c@D3gPQQy>QXvKc>+ytSR-o7XL6 z$5dm(nvl!+{dhnD&7*Z_{1B4IUlMNjKt%pPzkhxPl?w@-OFz57QbN%)b4EL%gvVd9 z6%=Z2RQB6~(o{p+&0!blpjL9oqwmR~KF;1W?u|PzoZY&l;eQ3vSPtRmHtm;&xUItd z*eJF+JYQ`UYT;y>w68VjsIfI^4dQ@Fg8yL~Z)qVhjZR6Q2%9uLi@IVRl@w3G4O?OS zEIs-k2&K81&lKLibp(s&R-E4hW}L2m;|Oj++JEpNnEcuMSCzn*n9u~Z zv$C;N+KpE#EC<@)I0RR;RZQ9Bl}>f}_CRZBMJGv$P9|lt6V}`JvC`gI2Yw8mqq3J> zR(@{AsmzrtK za)OL*!WTQ)Ms}||2u8w})xvJzv4)f+O5?J%h*;OMqA3h~ENSNyke`3|nMl{gQlK}X-1$f>4D*2}*nAot zlJ~Nl!7)tF&iH9h&O3aXUC}{pizUgaQJbq;Y)QGZe3m&#KP}r;>zp zO>+1moS;u8Au-dc@c`qG5Vuu;N2$76u??%fTZ`Jjbzj_UO5zHZut^?eXL9nn@2HzD%#?9%prId%wPROd`1DN z#VYZ z&aZ^iU#@T(P5oIMgBi77i&E&gB|O9~Y33;}s@Ka>^XwO-mN?Y8U6>n!JGVm;(?>V|khy@2t6Ur<~Pv)HFSRiWes(p<8V(Kj{a)-@k z&*&Ah$Y{mw8gyN(a?vewX*Y8i??h-s0f zARV@Fui4ChCBe~qub9}-M9$(U3+FN+Axw$xEoksz3Se{WF+T~b{PX?MziQxgO12&W z&1-YhKCWND;VE)>Qe(+sUc+2gZ|9lG1ECo>qPACYd2OT~NxMjuR$)Fj`Aow4`_1RV za=JiqKjT6gdryTL;hOSV4eQFrE|vDsNtQ#)2F+VxP!NoEHxmfZc)ccxNz$b|o`jtA zYhi2QzIH-gYD^+5t4F0#c-@1Qd(Wjw#?eOG_d0hr6ZAn%U@Z)7k*hZOnl% zX^};N z4P7L5@H5vc&cFZxwn<}()~PRhed!{*NIBYAh&2D7Y+&QbO}c8aAU9Le%LUfq0L zn=)(GT;U$~lO z^J%ZoI@C>%=z9IWW z^)D--`U$#`aBybnMrw*>%6N@uedCtE2eU6=oBWapzY8||$ppEag};7vmNOw!ls(>@ z*<0K9M#i3ROp$1=W8BGkd;=Fz#l11F4Ww>%cE5_@8A!bfCHj!uP}(h1T0SveWm0Y8 zx($BVkp)6wSC&Zm$^`+hx+=b%-<qRF5!(gl6fqz5CR;&5wlhWVC}p;x7eUhVCq*-xB4K!>87f81 zSc?5`?E>zs`0>-xwaw2B54_nQ?*^orzBE>p=Ls_;7C0GCWVk+MRJ#*PjJm%qnqsys z*+=5SAUt;+1at$JfbI?_b5($FGk%e|V`l79s2A=8)ZBF@6URzMQg)?Wp=iu(AH?XLw)lSe<7FiSKLGcmSozLp|9Gj%JH+5OVQDoK?w%dG}UAQm(F1itAL?I{l$4 zeD_x>4}0IK3xn+iUR`%H92}{#Py*5%fql1vJ6~P69I7mbwsnLYnC+#lZl>Tzi<>ke}Hp7Ej2VuMrfv7db8x2~yi0f6t z%sR)ZTCN~i9Zm1kr$5y=*%2-$OsB-FYx@L#6@!ub-uk4`MesEx_bEz{eY??so*ndj zu27}WAW3>ieQuI$*j?QE+V941)CDo~S*ol$;Km~@kn+9U2~J%@r3FRyPCQV@bVKj~ z!+V!B1=;bF`1Ja*UbnJfz5Le1GlQvX-mfqSj3|6;R|hxVn1BRs zKdKKPkh#r@K569KtAXc4u@q$~QV}G}OnQl>Pkgy+KbaD$9k-O^{ECz+;YgDL9d1lbG*o|oq+~B2RtXkUXxDqE|A zq{l0-NDZj2M$-C&emrSvsO0+1(^`;eEp)~+jm5&_D>L?&6LHEBMlK!2ljoMG&9-}O zsLqP%Sio&PT(Mh)x62^*i`_p|*+ilRDCY`sob{qJ6g1x;mv29BL%Fxs#<*oEYTMAl zp{AVQHa%3FD?x2$H*dYE@TQgvcE>p;Dt|qLh1J=dhwg4_D-je2PjN(blAt)gz$6V$gfXNvZd1xE;aeT56zxmHq+2LpDq zyDr6{r%v#hk@JTdeXN67g$^N>-#63))ykI16|c=pJ8Pht7hea?o03jQGTz>DW_6j>`U_o>CT5twoo_5WG z=;j-0lvNK!)6B>g`%nx|6(OcoO{4*R3$(T9kbGeBJ(R8wJJz=J(JbOn$rZUf=xR(= z)K7CX%^c)cv_M0Ceq?6orvudIawA$Vas^D+xdNr_WTTZUB4lFJE)-&wIU#igSxJ+$ zqhC9=2R=qwJ~&!KAS>+?aFcpAsWkX%MERd7xh5L-s-~oiq|!#g6PIqcJwBx&h&$dH z%PxV+irGsKvO6nyd9nl?%|PCb*O z{%Qs}I8w`_7tb@$HpaPL-$4~3DzXOhmyV|+M7{y)jky+o@bC9g3+EdQa}?H&)rQ*T zkv$+A?nALBaU44ilEVFR^C!-lqx#((6H=zhDc{zzY~7GpAR_v@Q91d%Nd);B>&p;m z(y3!98K|^R!;&o0>?go)Ve%2LEe|RA-Kg^3as^bK_M8V;K2Sx4UsDq%122b3t0Kdv zipJ_P^cPwRsH5ON@@)>)Z=K7kUk_*AWT8sE@2?rpL*+vYUcjrk+c?R8U-OW5)HRy&Hmw6yrALjEg)j z`N4)Q}z)A0BvYl>AKb598AE zdBM@XZF%k?4a=i^1>=qz!kD)iyz685jNmf}ZA4G7n0^1=AiPpqSNzG=;HT>QK`D?O z_`Zl}MBR~d8&AJ|FW~oU&wtm4{e1j-nsQDst{M#IX3hhp)VY(0zM(>rWy)df&_R2Rfyw^f)l>&rijxvC8F|GquWoV+?eH`-_HM^84^L7e>$f=PsL0#Ozqh>y@G^lR8K-{{^rlut2yaUZ}F z^zhds;O`^q70H%(ckmPMo;{$CG**l?C7>_=Y8oIY&MFtsR)kX#ASl5q63~WXPx=wy z0a4T)+q;ZXM`|G49sR_=$FxTdi~?+v7zpvjc_6_%!d%ns;Q;NK*UOkessKtzKoT|1C=;2?FZ3@2;2jW-f=p@xfJS*m@kaGVAp=mExU=yi0Epeh=cB+L z$u31VqE;fF%-MQi9FX_0JhrD%-O2pI+Tk53_mH~Z%K+=ByeMrXcH$dB(%aEbuv>m; zdlEoIz!~cDuJ1Bwt*~Cq6Z0MoFar*mGjS%WJOBqEAqG)Ms2pA{E++{Czy-1a)x_8f;g}(naauINX;2OU zEMnA!-<6|b$RbffNj-2|p!=~z%u$pBMkT+acmnjqkOt!Zk#sBTVi4o)MyzRtPerbk ze%mN_A;|{Fi9wMP>W~^he8}4FH&k?_dZ-qGpH}U@$}?tY}c2 z)c&HVgnaKOS3Mc|^+o8|fI(k}jW8FD)Gzc60|~|Xf;_OVX@OusKcHy(U2wV!LXzLN zq^}X1eY726#t73SXi<1E2zs5%p1wp6P1NP>g;aOi*%zprski53^+g zd;_DU8R9Zp2lVX{p!5BYBJPqnc(Wyb}+;T|Z*OD;I)D93LSqsHqKKL&3= zJ28$b#2W@}K-|E`=LK&d-mu1Z30;2gfH){4ZVNvD)*;{W_ZcldOUUkf2iSpE{FzWK zWCzrNbv#k9UWwje_m2a(_zU8+U^z`EL#L4)^xcErZ?&WfUVaM`Vf&uSXlFWjC}%|y zOD#(zOQc~a!2niNOJW7dYT@j#atSqYL(EO1gYSwQv#Ml6-bn$$b^8C#93>rj)y@0@eha&^7(cnFzQWwCF1;ndO#n~Dg5#6 z25R(-xK6mK8}0^fbdb0YocmiN(E&-kZUMC&Sj242uaik-va_4487@mQ4$>(R1hP|< zmS5o~Fks5T>0#-S>aD~oC>CMRp1DVbLASNxp0Vd(+%N;T-^ZPh3ej|Zky5K6nDJ&~ z(?frWPKY&=>Yz*lD1ZoJWQAbLaib#b4gVNI&=Mj>g^J1W0c>J41F?(EB-I0xf0*ML zg?qnoL)~z*{p%AJfcTOxml5X$_#o80ppFU(e<0m(#`gu`gFVBK<_Uj%uknUC(2lnh z@`Jx&8&xG%4B`X3!HW+jjtd3~5e8sxLpNfXA&bY85d z=+JHCHfJqcTWD^0J$--*kmNA`sz& zR%vnO9mLi5mv*bdI?!Q`Mwu2_TfDVuY&K?^YrWs6TsIS4$~0epXBBs~{*{1_Cdp1-WO`!0ysJ8VqA)1AMU>b#n6bubKP z2EESOk8?YTmw`t9k9$UjkA*qDoAAN6U0CPY+nadjC%MqqM^IgzUG8qK?ohm)N9X6q zcF0qwxsWfdMmCQVIW?Q~{iT^%dE?#5A=&hJtT|5whOX)s73N~WQ-ohaUxYurt_Fpbe zF*mCamubV9rmQ(yuhppJS}C~KeW$|D!i#n3FG9-Cq9#fwZwY%FGg_|iAZ%#pu@zhL zmYzWIv+(7*Z4@n{0LFOnGvOv7e?&N{DN)G(u@hmM@(bZR(_i$~6zuV~kGx1-!6bM*spsiLe?D==WIn_Y_TWD8yN)9A9Jb5Ep zo2A@NR7yL|7TGzQQc%#9kq8W}jdAm#t}MmJ+VfwAinvY$=x9<(4L;skAL!F7&6>`N0rgbNe@!bHBNj zS#fB2At`=Bppv>PGOU+s4E;q$;e0g(@MQKCI$isb^GY(n;QFkJooZjbaa)`=aS$#i zJo?jjCeSL0IaELuB@lcDsW^QB%*CWJF@tmi#gllWK2*gbA8nq(d`*}-E}tKUTXn2h zXcf~Dl?w8r9V|+9y9>{s1I~{x8AXciTZPXPZ0qqLDvok|5KBOfQ2$vYY>CLeNV?i+ z#SZaC5v}Z}111Td+|`dQF?-dG_&uwiGJhRJOD|LUX(uMH1g4Kn%l~FEyXATSJmW#A z)@=Ns?yDDW>yM9bbZO(M|GAIc|FeDk4+;b8f8W;s8-+nQHTpj(3^K|<9wiu2^tOBe ze0JN^9xw(1LktpKOi2ibG$<18i2sq|AK2FQ)obzeW3FqS=Q8#8Z>)pgtw%^ZO0~{^Yd+L~<#eDElcw^tEDpM`SK&Okq7nN9HS5x`-2go=9 zuyifXsp@NX_CjbbfwAHT6;;lz7aS|d*QAZ=Bgpsr8g$d<`rd3;f2N(=t8|t}j3x3V zXy{dxzC~>CL~ZdaBVT7)XC(49lW7+X5a_iQVCA)%b&~1 z@DJmh5!w&C-@mK{u77q5$rPO0BS7L}$*taDoe4jz7jV*{${L5j;ypp*$KoUED%I$I zKmHNT1M%tNm8~j65uNu9+A-pu$Q~_i=*83{S!o&bg)v&(+J2)}$=_O-^8cYQ-2NXZ z3?%SiKL z&@>m(kN1$=WARr9FtgH>j-LfzUe$vzVUQG&eE=tXT##;-ub@(21Pm z;NLGuz6pcfFz>&hSsa;dB{899+D3=?N!IW*sph?TIpxD|BZ< zN;g=i`I5mTHmQ$$4Kg$c$s z%UUkM${&LZQiUlD%@w1!V6GFQ~A@%lL zLSY!rnZaKJF%uRWVpU9Nx5p&SI~F<-+U=o)Vzi)ni1Zl9iK>QpjU8N&(?q{XC(*JA z>}Gg}*5B?}PsZ(xwt4<`!trCfjR4*ghK=&COm%U06T-_2^2`sq;V<%nY<;9Btut!sp0N&K{U3IyBQIeYq*m!QQg!KbJDTDQ zeLKA3++=$eH}d0-RVVD@9uCOYBh?Q0K7m({f8oNfvEyky2;mXYX!SbQ2xVg*^%y+XT3(>DzM;- zaj$@#-th%BJq3s;)#uiV@Cp=5@P)^fgH7+7g5Xz#SpSg)35@T|B)~5xcZ1MdO+xA*t0-rh1(vC=sty2{^Mv>CEuETVsMK`zS{M^ z+4DUwJb4P>Mm%fCVi*a3N_a+nm@$7!=f=o0VwB&d+*Um3FMG;(#(7A>%iI5^9~r|X zmec^-H}lh{z>(FjB;x6ZWI4FuE>+6DjRj8Yjyuhe##{eJn4lGSuW{^1+f$TGJ%Rr` z*ZCj8i7SVo$Obeq)9apZ7{+NRYJg3VkvHLED+E7us4w8v7lq*=Qp?FCk4ag;?}y_= z-WP?zxKAp7mW!At0Rmc_G-iM#0r)2Vb6yG`lbQ{@FLEk5ii|WRY@ahmQd|=^%(M^> zap;OPCTs^8ME<0Dm*3CDKsV7RridHTmjQ7d*B$`16H>3@pN-Bz!ORFM>sIjHah5lS zyZ9xPX`#u_$6wPZiA6s=|0B(}S))(UKg1O2UrT%9qb2*cP1Uo28a?N~BqRQyu*Vo@ z#V}O9EvUZBdb={eoiTkX4BlzqFYrQC!;IG7qOAevwmeSs(g4&P{QQqCeJ zSx?)>cxGy*iGqS0AdYot7#qf|Z&8oC*+xezhiAp;wZB*K}?MmCWZ9Ctz zZQDj?_l=H@8+URiJ^Fv6JvpPj&xu&;>}Nf{#4gpZm?1?3D)qX#Lc_6jHM^G+gg||1 z4xqv_mh1IxFQ|#8r@WMO+Y6SGeCFH8*R#m*xpEc^Klex}1nGKp`j@Km=X~k8V!gD$uuM39EuVn`6S2!CZ zWjWqg5WSU?e-&%O&AYaB+t(sTr{-5C|0P~12MDEpix(ol#S57KC0?)!()>m4o)(u@ z-J^-lD^=T`aoR$rx_tHzPR>dS;81-KU6^?RN)EgOeHI$Yj}kgbno5t`focC(2w$+c zxqDEjZ>47++}Sy}8@coq+aO=5xZfDvSZN?=tar6O63&KEF(F8|j$7xB*v!Agz%2j? z&PWC#u-GIQmHkRfrfm%ZFYaqeHVvf7TtJ|Gy?+;6C{+D3!O*bG(LO6d_y=dP>_0GRXqrs**o+{3E~e%z|q!m zO0?0jjekET7@<%zlAqEkz$9&@3%CtAfN=pZ2s!V^Rvvi@4`T;yb0oYX%rVnm&>OES z@~Xo8%gV~|fcwA23!f_6Mi0+7&mYiY!-O_cT7=1C=34u_kPnedL*D^bPj;HM$2;PT zr+aXr={~|?DmJH*ejjO2b?2gA9r962QTAgLYe{)oA{Bjb+QI%3aai^dZVoukS zk`QoD2DFq|sO~#nRZTF&)sv@2Me)~i*m;pz{K`Kj;WBZgcYN12*|iySO&ZUzOQ>({ z;f$~U<#Z_V-sy7s{^^+-e(Y9D!bWqZCi`#9eX{OR%%~rmNp$kVS@-~JVp10sVltl# zwS}7o_OcxC{Ztl4;zP#8edt&)i~c`L5v8b^^LVa&C_4x_;7TvtRT*0~^x1_-PnEO7Nh4Se%PeV{6?+DuR%{tR+3wW;OT2*bEnbKR z%`I9D#-7c|$o=pnhiAb_GvR3b zQl(v%m3_G)6GhDu=@G1VwA)D~Vq%ro%6DOt=A1Y1gGbK>`H?j@4b(eb**D;$51}_3 z%5BQ&5Aq#(p{lx?wKCp8<;Bgcr}x_*bE~B=9uc3e35tZtL8`2jVn<}piV;P}7i%=y z)`LMwtrI1eb|fjDq+NvCduh;arz00AX(hP3Uyh!~8T>|N?LryF+(+t*f>2b0n{E-j zl4gGfb+vX>R^8nbKFewviXmlWQd3i9%x9vsSV)7%#e2JdY=Zq+_#aIPE93vJDfvHK zyzm`=%SdFSZw|x5L&P9zX6a~T|Gir3IT{HY8Q2&a{lBJ#@0?UNPNx4aCzX?tlli}U z7t)}$ln(m7vX3%WmQ9erhz*DAY=7eY1tZ4{Rz^$IhEz1bR28?SW@93aw702cWm^L} zV$J_{pq;3&WUy(rkSKpDDoiw{>3{Q>WQkV2_`Ev0``UW?+I;%xVDX%~dZIhxn)06f z2}K_G94|@|Kf5ky(Fb+(J{QUbDoJacw1Dk#xg`pPA89K(xDK(WnrhQGw-8tYT7FhE zUU2rYMJ{~DN~}YyICgp_4#tFva!qr4Ry^KP?|yKzP6^FKH!RXsT1r#?{wY4K3Ux%x zTMB{9XJH6RJcJGPRF%5I*nQey!`DZp+s2dMlz0A_|1=@wcD0@MG)|YUd!#Cih@|h0 z^b%P=W~|cso`y+0e)?>wZRHj4=291oB!w(hovy9CR9{DUtBC^zO;1!;hQp?cn)zzi z2yY^6pU6+NfHLDI!H2^jp)eX3zJ8CbXv&J>fysAzmw9wzcp)qy$a?mRBwIeDzC0IF z-VEcXzimNynRp4xl!CZwGKhjSM8xP{^8^W#eBpIrJ}Bc(IZCeblmic5a9rin<_6%8(JweY}NfD5HeW14IqfP>s)HopqGB zv=~6-tFUT}XK-<8dT4cf+gYAR?OT17ciGYgYI|r=TeDFUZCsH0@esY-FBP#b=qrbG^-nfrh`UpkWd z5uW33dqQ2px>ypaRy0;tTMC`PIkP>#n=g;JN=%|5y8fkr4?t)Vl85O@NR`@69b1Mf zD09YhgUe!s``?KBEtXq`B~dxDPDDz`NPno;y~exfZD8Ep&~xM$>lZ~Gw!s_Oie%PM zDE}Xw0nWRY?%@vMrda#}@EgsDHD3A6zZNarbbec84v1)s%GRZK_$S$jt!YDD`1>GK`t;6` zGLR02>Fl_XJJoAN9x^W1Tsj(d^zv_5P%UAjB>qI|OzJM8&y` z*xf%vw{VQE?StMDzlcNoh<1_i#{u`HZh41x$cFYcZ}GWgynDJ{N%bdV@AgTCj%kKY zX;c8X#cKbQ5G(XxMQLC7O~}8JJqcb65|m`OVgXKVjZ4l<6<jf2Ce+%u!bb*-$UxgNt}dnGP$#u>6*i z8X|rMm-Cbvu6$AB=#jB;@jp_H#c+h|jw6+$0g&Y68ju4+_mOq?>`&ov@XW`c@17l< zjvA3T?cC%{sfQH``7CAgrQysbCf_w|D;%=Xl1Kt~WawBMQ}mo#nIX$&%3M^&0>?qa zUB;sYSLr5a0$gMylW|()&j>Z#}W|MRizuoBhHct-kX|9&BW_K?@&p?mPEvhVXy+RZR#l2W;ta^DTqO}*P zHJO1lnQkvp|v_3eYqDfI#-rG{c>&(>rJfx zQQr-i(!Ih4sxJ;$Yy>*)K%WNVzTHC`$ZK9E=im%9K#f8jHn?+ zByE|b%+RQpWEt4fm*tF-+`SJM1G#a5k+n?6UI)Yegax>n_DW`MBJgu{WyTJo5LeA4 zA#+(j4H)hk*y_nA!{3bxO$U?~;a?Dn7qpJxn2T@VLK*9rQZ$a`WN$JK;UryliGdDD zb7`r)r$*zJfQfB%L!;C?{ELA_r!u1)rj7+sh&U7#M3W~+I7ubr`uw_8Y;_!+uorbJ z$H4TAM-)fhuoZdbKGD$%m&ad0z|;g<8k1ZrjsuKTbzmb#<_vKVkleBXNpHNbH-jAw z+8r(##gH_Oef~ISN7%4mnTIQ-Jr@tmP|BTMDsH9pWM!ua1@-Gao+ilSw4jO8X(_Q# zY@m_*=IouYe8{05=7Ggn#YjuF7Z*m^gXWm}f^bPu-)pCpadNtLXBCzEXIm{B!VYCJ{gkq6QO_sqh`(*b6J>jf2W%+$kJVg{Z#i|<(@ZJ4XjQM#w% z*p{hC(lrpJ3uC;C+O0sGo&9W_D`=v^u)g@W{=lt31e-i5kN^vh6X}c@Rx_QeeB5## zw>}NH+J*y6*F1kbY#X0V(^SVkj7^+46dazmEi?y@64`O7l>(2$OH)k1zzv9`tQ?e1 zK5AY*x$T3Fa^@w7PR8WYLP?yJnv>LUC!LtF9+<2ekfcizsq>8mSCasTI!<`sxo{T5 zNmNQi$ql4krIO!uB#DX(XAc`b5y=STM31hgwL1?$hN%?z(yyLiRjI30S5uhN(&$sc zIEk_w_*S;x+3qHc_iL6E+&HDmav4r=uCi z2LvkeAsVR^)K-?4kLYm|csC6My0Ifm+j4g1)!vlT=f~K9<)h7zW<;VJRS*;`_f9To zh^i#~oeT$wH7b~cC3kg`{2fJifX9w$rvN?#0a}u=73y1GJD`p-0XR+3$Qw86j5kcT z8LYWuUXVW*T#P&X?ciU>Sc}j>4@_~*wJwWOI?ei`PFH^Wicp^htq+8kDJG!{X@ddf*@0j}0M6iHiB>`RCfueCZ>T7Y9 z$yXvj@1%Xg^|LdeAhrVVtR7reR}rGd^kP-e!$Y_RQ{JEuhcPP44;*1We}DHR97utT zhP50gty!xRPdzwhR z9c0xB>Cj}Ws4KaYM3qIAkw5S_wZCHCETnvfc*TF7xTFUZ*ste6wt8V`X2%&@lsM&%Ojq?2ck$nZiAobYq&GzRL5F zwTH#3NlZ>||7E~g5zPefD`vC5Ch_*rPCntAlXaR_>*x)=Z?n^^bo#?}ha3!QDWS(C z)Kh0>XUPblQi4-t7ry@a#mBcE`%iReIQ7+(wd1D*yXI~Wl>98UbvC)D>auGJ~TbK%=4XO!cL%4Z55;e4zPR z__FX9qgG(k>VrTVU)L!?=1(`BpiaKo(wQG?D+y?D$Yq~QLr07dq9@NKd=TjZ=?Md6 zU8uzod&*wWP4uqjliDru)j-Qkw?Tv zN1q**!nRGFr~V9Z^@6K#PD@pna$Xpc3E!xdXSp2=x04zkZYfGGt^`I`)mE-c|H6sh z)jS zVQc#}vRAI8RCv1e7Er^}{l~Kr^FgvY(2QS->nt)B;99)KRV`rM%)6 z>xOJ96nI2d%3`#(h$SWvWhZQL632TN7c${yH#A?_INEM#;BKUGyZ`UgoO(5G`s2o= zq3fqkxxc$18-AO`YL~KznM;>ej7~ZxZoF#pM#k_z0xaptme@{+NzvAX*){}8?2Aoa?M1rQ)&k?C zf25>vPTSS*f}K^$)7KT*2aBH&JqzJ^n;rRGU6|ayww^S4v2t~%Ijb-~T>+(Fz z4a#E^*4NV`$N8F2Fe%#_a^k-H+kpkuZ1s9k8UOrUQfrA&7=$Rv7IG3k8mtG+(=4i` z9PeJNm4B*X;196Fd~3HbHwdRT$JIA|b;}3%*X_nyE`{NuDk28Wu)<2n0hV%Y=aKOp z;Sf{Yvgcuwl9H?`C?_&biS;3iyvn@hBKG^C88e+GTYCs`mK>@t;|nFFe-<`y2wp4r z=%y_m0_-=Zb8>~>~*`)P*#^Hb5wnOb{LI@n93 zb+a%^Y?C{{KIw_sN1GBS?F~++*^{j4@>_)I*NY!bQ z2(BF?sHPAap;+`<*Yp$Y9&z-PwJ!5luMO(BAt%*buh)3;Gg;K4*U;jCnAJUaGOyk< zt)WT>Tl^|{akqfoK~2Snhj0sKCmKMSFOObmQO89xhhlvUW3e)u4H`tmoBh>I<@h_V z^Na5U^h~ba$5cVW{0PMCE9W28SB@WQ9XIBP2?E*CV2E@%ehNQTpI6-+$s(=>ivI(J zlgSx<;B(ExAub9{Y>a2N=LhyL7kTHK7%~=Ka#iy) zQha0uL>%Jz1GrqT3Lq)eLzEeC*-ivRRx)R_>2*ar{X>J`tRL>!PnUb##gL7B5EoxW zUV@!Y?>rqk!zT!DXo_lP1|V!MdN$q27+>0O$#8po97Ckz7>?H49n9A+Zq_)S_y5Ls z?;v1vOf>076trJWc+7QpZJ;hZ@$w%aJ`ICnbsD6Ea2Fz?Y83QzHh{~9NK`$YVvu1w za?jFlJ%ww)5n{;RMuLu*FKS!JqV&61*0=2&4G&SW$+j|1@sJc#O;Tr@W$U~y&UhR> zV6j@O9f7dFx$Gg-W2(xK1znH@H(kP9;cu?te$+SK{dTRQ%_G+a07e^bM zs#SyyV002{u55HRYG{l50+hQsx5p;>ilXGWZ54S2@6G{VDTITkqoU%Y5ld=GqNho1}DM7ge zxN4~5Ky+y=TlGS=LHgNlDADyhLLQ-!;ApL?>~(BUyhZcG=K*2T zro*2tiT;wHiY1KpizFi*sa%>TmC#7<_4I$uz?jI?ZH^Rl){uh!IoY`zM#5FW50J2ZWp~7ds?}6yk{Ys&O=7c zK<%l#UpXkDDAnJ$_~v{!H-s@URjNr?9s$&Q3Nk%6yK(Kmr|>50zdR_Pc{QEq(^0_H zHbZ=jH_BUlesTM#xa~a)uUJR~V#T82reUO5{!0H_BY&pH{tC?=a4WJlz8u8F10`p! zFz9n~>iIkTyUc7~%#@B+h<}qZcb5jVx2c<7;pKMV-gRe}m$mbqw3Vd|Z6#H|ohvHW zKmDDewR`w_(9QoG_L?4|r~$VGVd>2{n^k*fuR%M1e1^_?4wL46iE^7&6keBEa((Tt z68U=L`x@gJYz5Xx@l7li)!`t-G3QZ`|0Z$QCsz)9hKP$s(l71WN1jT{75>B9Z6%n7 z*t&gFcDB3loOS04FZpZmlUC#)NRC|n_=YFZBkVa|oq!=8jCp=fo+9kVl`UhWT7Gvp zH>zHOH&xQyt|@jP_9q#(7^y_RR+gmkwz()uv<-@c>h1g=1&IEST2`G!ONC-QAX2{t*AMDaDg%gyA9jl3 z@Uy3ha_9+-rhuqK!{65OdX`2D=cNwiGzR4sCTs(Up%D?ZS<1Sq%1n*!=E~%=ky(zU zQk3ufzSq5(u583U%X{L#m?UpytL3=ImMJ9qN;mYKs-7(DT_@uf^j#(67SvrMqb~Fu z@i?_!7uq4Lo-E8ArXC;W;Z&a|#dvlg7x8#;pC`%qaGxjHcu1co(fC_m2jzHsA0Fl* zzTPIrA-!G{^j#67Sdg1g3TYo6#-Wp*4(eeHBV-U4(YW__#`0YmqesvRsdPJ|RbL0m zxV4@$biHDVik?|G!uZqN$R_+B3m#kjj30s0}b9s$N7vECwdy-EsOUj>G7A|q{(I)Drg5D4;i)@O%UU(oi z5`}IGkDh%HHtjf;o+vbnLJFPUY@h`C5I{Pdu|E)-M7oYKAPAdGI*u`{4-RdJLfW74 zrf&e+0NwaEElX)Y!JByDym0tU!HjT816r;JvOA%eWCiDT*rH75KevR=+>g$;b>GO+_B^rnVNErn(lArj8c6+=gY1GzFTCEXGz&`#wKtg^fT()B?NO z!9nW52CAkS&cYZ1x`On?Z2#r=-Hzq;-8L=riNf@HGi`R>6d8;9Qr>jqL1PS)fg;S} z!6J;cwE}fOwQq&9SupEHB2w73Bl)_MT$%O5?S#8ewNfnsHzs3`ErW96G4hl682=0=j5*9o)hvSGRf|z~I82Y}ga(AF&*=%KZFV4nf}rL}o@i;--gwYhJf z0L0abvgH~-Z~|1@aSa?0C4*``I;;#IC<8oN&v#}g3n$Ria7I6^i$|;h?Jei~vtxsU zg+USVKm@>AYsa_|ZgO%<$H)OVV9LQQ3Rtg|wR6jwT-;)La7&gm^t75jDmzyK|R?b&k}?4PaKKzjhNWqr6X z4A`laWZxPBL;*x?0zHGWIRBA%fJ1&2_vv%IzXxxb4!7P#9rLk;3Rnv z&=%0&0(Zb12&~kKYyIxulZ7DcBzEIQjEFGPX*m(&ddRcHhU{wXv3tlD(F5r-hit=~ zo$7zTI-DHA7<$<20=!y;?P_gLCI-g~F)>jw^MNr?Vf(Z3Qh`$0KU;&OIFlIxye)zD z&^xo4QjEz&fQ=R~dnVf%2k7nHsln)oYFZ-5-TuM!2uw_TOnOXsOg2n(%+!d1Aba9R z098LccdxQ7xE4frfYdgJEwa{+ZhzgLscjxkNN*5+|N3pX=b!KJ@8}s2TLD)+&D*%w zY|k)`zKYvY*WjGcpCEjYJ`mY{{C=q0iq~O20{g?;q}RmHKROUML9hJxw(+moU19M+ zZT$1M;cdb2AgTkFdWiq(`XCL1{^ehW)B*SCC74E7?V^2aywFQ{pJ(%RXFbZqq7q9`B4 zVt`mL>9*cAk{aZqKigk6HE@+zR_J*NsWaPq| zcy*-0bs>Rb#tYzV5S@gMG~x@(k=8SFO;rJvuPH5bB^wpd=ZVUgzY)Qvl|R!zT%Liy z-VvXrAm6v{zGiis-Vpx)9;h@6*oMg5hG~KGhD`3U*(PcE;SJvHo89YXi|`KS z4QA4deGU4I`%KFT-vznlXVc5EZE=l${G+%hc^lRiN0RlrkE`!=U5WC#2v$Z${kHkB(V-p`QUvu!q8C_FI89`72&4wy%O-W>dfZ>PR> z4eU8sWe>Q9FY5V^|L1hCNnC&OAZCG1`J432{iSt9vHY3di~5&z`3C`HwBN(F{56y- z77y5>U*lhRHFy=j#J}LC5KjRIf3bU{7_C5_KsrF?$p136;L?G>Dz4c5Y(X7|y!)ZU zVFp?PSua4J0&eV&{g*ohqR1bI5nL&NMh=?hHy0IX;kGj)wloN8uT>5hXOAl*0$Kox z+>cuUf)Q|oUMxnKe_-lF@C6{h#DDhpAjQBz1PBm8lllAgK%o5Q2?bSQ^i}%wP`UJ3 zEZ+Y<3ZDy9$q}C)B}(mh{}YH{{@>m76ESoC_e2G4d7yP55!%Kbx(k=Kw5LlOBgLs9 z3Z_><6Jd^RBN;mMZeKTp&N(YJbd19l7bGW`lY>=415!t7YO|?cj3k!U;eu@-fmo>; zT0qP?L8uC!QyRGJx^={0seJ$VEgq4p%f?_Dyli2Lhv(nka%9s2oSl31W`ySQ#VyX< zI;6V1AASK2?w9vc9K>K`zrN+(g&UKycf-%itb)>Q1Jhu;QvFx|A&JA4+Z}p3jRM7{C_C;|4{J%q2T}jq2T|w5%vFP z#}BgpuXE&?*|@&jV+Ls>YZFIPA|_Tg7PkL}!7q41x@rx6-|#oNJZ)ZdVpcjg&Pr-L z=r>xhX-?FQt;Ut_Td)6O$HD_iNx@)5`li`Y1Tz93R((x#p;6+npon99pupmO!^jK5 z1Qsp)axhVyfXGOG^1ABmO#7I~;9~if#-*~D9O}^LAqi?YLNS?^v@@tM`&@RJ=e&dW zO`p>DuYGLBUXchp3AVXCY;JspFt~n(#C*~SX1HxUK4^Bm9Epyot_Q`8HF&!&Iq!=K z1Ui!TRO@!TeYJ5ct*1oC>~II30#<9BT_>_Bg0q9~nMbOZe}HX7&u{%MSwlEEhu3t@ ze&cs&EEdj8N1X|CAt*-gNn4I%nU zUTeNs<;@jKCYk5=y=%VGYWF&rL$win)oP)P#5%MyQFV zn`5P&rb~M`IlX&BX8TD9E=g>j&>e{YiVZ|yinfFBWTp7c*v~;u8gb5yf0Ppj z&GlQh2MGoXBm|4qJ2aRkmEYT(U-;{$#{lZ4nK*%)d+!l;hRYQ5-wg5bTH-O4J6>n7 zN-l%YIfdh|SBy4H^6B_vuKUw-=P!=zd2ef21r|gSaAtvW{wyphFW5e);!II47;4nB z>^$jX%1`cy$EEohXfJbNJz;+>6`^jhc^`#6RKhuUPF{JxxqH3PvwakwBO9rW+D0?({N{%UYj zKE5gCh%Luzz@wn2OPb*9QQpV#5AKwqyXhCCEyOLz{blfD!I<}Cikm!%j66?lZIB)P z=PopS(d69fAG=qn4XLH1$M%lk|BjT8Pe-2@ObT|NRc9WMssxt$N^~J_aP`i+qpU@n zN9Oa7A@13@9^f4z_qP}WFpp$zw!*DJtJY>X{O)9@vpkG%^e1g{#AXa~6sPhCeVcd@ zYKy0ZkbGc1AUeamqd$E=LqCN@N@2_iULjvG-G3HzBNRj#b!mTYLT#NUrI;xY-o4owc%!!CnuF+KO}QZmN|y5L#xLauTQj@yw)w-isL&pnRc zj`?c9hi9N2`Q3l;hKf?mQ4+HyW+zZ%XR*u?ibjuUTywe$5_}cUlt_Ev=_3|85dJyh zp&QN?Z$ zO`)7tya#av6zCS(_{aWUzlv2c*KXB+sVkI%fZYA?>g0uO$8>aZZfKs!URs@9pXXFH zFR3oCOjXoYR+lHBr5lM36k6$}%ige@K4Z^^c@Mjm^M{8#nqufV z*=9rvE=CSU@g{1V+?RL`F=YRemWAeAn%GT%DrOy(IQeKSX(HO%Lb<1uWrO@6osY}< z8LGR2hj&jowC|dXXVPyk2pabLhvc*7Id0kqwE4(ZoasjUjyO<`W!^;#EzMuXBFNv< zQ45KfMvx8}+L~Wh(NsDMj%gsKE%%fo<9E2h?N?hKliRzNqY~CNt!?X0_Wk7%{VuflJ-QHwh0ORWi1Ev zkFmuE{MS$q|B~wc@F$G!9qRhoD&D5nx1R8WkXD-p*}`n-;SEv|u$)Ei$ z=w{dvmjo$+En_>A6uYUHm|6bc?I{908o#fb`&4UcF_`Zewd_%%O${o2eC>1LaM#Hv z{0vx5{lHMo)1vFKv>MF1Xc@7HvZu`AXg4ST339UKQiy@0?6Gg2#&b{{)^b(dMpkzX z5R)YdQXGuhYpH7z2I>ViaO4TDoJS^gDM;g5IWuyR(tY`+Pn6SwU%zdwY#E`r$>{NV zy!z{shMV&lmS^e>e-m6ZB;8Mm$G+3VN#^`8G(mAhJ^pJkpNOr(#rsG055>{cm^IMw zEt)C--%HH)pw}^I1d#=byArAyURTG6E}(P;W|4lG8}I(?qf#tbi~^yO+g>X!%F+gV zl@W3Y*;3PiY?(vuEN*S}m+iFEkFBv#?vkj_q3@VR-pJvAc_ZFo^3%F5a!|yRUV*L( zA0i1w$2qyZ?HkJxT-TB47&1jO=|SO)>piflQcM)d)UAh@z-eNxC8bL2orM39vYZ4*CZ!@@rO zdZ?F{|Nc<2!VV+{g;qwh~iCT`=)hwr%(0P5zq*Gj9%o{P6g!Fq=R~9ljH>8 z%N*f*W@wUoPoaWT`j>ltj2$3;zu=~fOjv^)_{*GB<@J1ihmc1-HSyf7j9v1U*P%>m zBvUdeFoMU75a!I0VYbaFF(t?-Wr8J0pSD_b1B!95Hvs_!H}~)s&+zJ-;lLlFI#Qof@x5Llic@Ikd|c{=w(`0LtXTP1%cG1_d5aV@Z)osO|pV zOApLD+GTo{P%*aKs^+&(5DPzL^nIsKCyI+z5i~V-A-e zD9UIH77Z;}7i*}%g8nA){?g2;Sn0Jjl|~^Rbp?YrG9ne1QO9mvJw9O@YIP+uw=p$k zR2fFHl*?-}&#!EoUtv?xrz+IXCtrwCGczmw+}@dy2URYE{81{Vt+4IWy}Fy%8-tE0 zMV#3WhD8qwidI!pF!H?oOoaZo9(xFFwC)vNvdr}T!hPaUQ0KJb0UD z(LI6Si{k;bKur#Eo8;_UxR44gCPDpcMp7P$kZE9E{AAWHayTzB_N(1+4k>(2K&|<7 zf2}#qqou?N&>;A9S4};EAtQk?b=re?21uobxwQ( zZNIj`L^mrV0v9LGqeP(M69h<^%QFh5k+#>#-_L->-KaLm*Q`q&j-}-Kr;erM@4Tl#X~ka8rm(^b=7Qis9MZ}XOP)&Bfq*B^2Uw|lZ9uSx1^#neeJUvZ~jLpjlZ;mX-}T6s2dM z42Ovazm3VI)fe?2XIt6}$yH(^n0eLJD+tfMD`oQGr7V8EMO&U#JBo2PHvyJs6!nn6 zgAWpDq~@+a51m{t%tPsx9qztkUjbSudeLg9^+Gdp{YA9aZG}!dy&^BYrgGIGW=9RF z<6Ht_8(n+tDtu1?HoD6gRHMB|8K61zQ@%~h@(3C%nyN_J)TMad**e8brJkf{$*d1O z-kfiN1!;VJ*o{AaW;}a}=7GUrdDZ9?Bk}|J48}_d*B$ytk`-*sEZxDM5D4!Sg3rXd zd7k{%{i@G7U$ky}v@zk7xKdYJRdw7}+;F+?TswjPLRxm#ukbDTeQNF%g(g;|s@t)n zFBZeP>rgWY8}mX;9?uPRSP5H2OS(4@Hya33MUTfI%krJGn#&1u?f;TbLm^9Ghr!wq!9dpGGLnl`2ZR~B3jhm*lv93s!Hq+PAVmZ zODg_mzo!eG8e@S0kERC}?ADxv^;3hhRntZih9Yiy&a}DZ&gKGN&k(0h#6JWp^*9qX ziu0_Ue?7BRh?bhKIlCQ+OzEs@bevy%G2ol>GR~W-*6Hrjvxi8G1E3Nay02f`Um+0~ zBHTLW`2w<4teCIAXs8cfcD4l2h*?^b#xajvG39C4^ z%b=oB8h>1@bA-#shh`A2XE_nNj)QL~S?nH{QkS2_YPyQ|gyZIii6mgnQ^_SV!NdGT zgh7BO9&@~Yhy7Y~^s$k5CvJ1U5-)!Q^In>eR=w9Ef*U#+962D)%gVkWE9XoZhd{a(2M9q& zE#zmpZaPD^VtS51KWlofA!>H&^iQsplFaC}hDhR}#O?+pbk5Fd`vWuNc@m$#fJOI^0x9A{y-C8=jNFUAe3(rwsB?9*j)L2F@ zxAX@zMt%2hr2Efu*pfUDN!8zB(gM91DH=8p$q#54rqbdyudwqN4&HQ(VrMWPBAe{eK2cEeoGk)sMioq5 zh!IWUtbEoR7k!GJ;Aun!*_^N9Yu~+6v=~D7X-Hy~*SK1u&`xwc8-e+$#a>GzanwLUD&@`=LP>}|gk5c%xN`*$*kDtzOG7;M6>r!^~^X2ZirTl)@KMAH>nmQpY3O1@60 znr3jSKOrnTt=O}(^1sUHoSfI{GmRhKnm}kP;g74qCazbk_yYXXOq_8O&HvDeQ?5lf zJ#EggRAn|mZiN{_PilR+0@ovOJWxCD4J749>#o5!(VLQyQ}p=Mpc%O1T?!x5NhFmK zw2T!E1N8}5*Q99*71BYy??2)y#iZs5{{3(+=#3`;HHg*O|JxbwRTUz)ZwX`U8MZAg zuWqFi%m8~7*pE#}w_9?&<}nw%Z?91Uj>p~|2Dy^}Mj_c&M~kuaN>~zq?f~La(eAKW z_NdSu<(BXH)U=Q}qx3Ti!BuyJAj0rQ(n(@X&g6p#YyKws-KY{@n~FY~aU>sWA>U>q zVHlLXTwy}wukI{^nIy>qfepG=D{_^4U>=#3br<(;W0|Go<{q?)03~6A6L`qL@_bA@z1F_KdmC>h_KqwMkolw&9C= zT-P=w=tHZ?DywA?+hq{ZEbl++`?+{PfG)Dfvd*fim;UxpZ@>nzao)i@JTJnWA6w~G z8VF7zM1#b%SxMfl2z>92uB_xs7oM&&aMeiN;kKf5GVhXYd2=25b_4u&La_cK5{5d_ zxqhsGHCk@xjeIM?|6g>UrO$y<=_gv!OzJI58p{Es%pL!VHX5BT1hLbGCSz(H9>Tdq zM{1h@tuJcXBJ}tmj-QvRUTyn$+8^um47T!r?wh*}nmwyR?a{LT>}1t`<(mR4oY(TG zwwu*Nh}R?D{TE0e@+7a#dfcYCi0$UIk04g=+_)}2j=NF_T`Zcea){b%smp7(x^{xa znlJg9Hm_RX-)yJ(uXn83j!&B%owZup27f5Q>hMd$gu6fVjQG6<2Owq-tFhpJ+%te; zBJN`%c(?k52YbA)4h+AGOz&wpK2;E`v8^!4!v)O?$|DO7t~fi@wzU*25_kr>|5%a3 z&?ajo_b{^2sm>8-`WvfeF$FUy$;!g3aZCsW*7iD<;@wbyqoVWcobp<-oImkp$lg9n zMntiko~}-b0%NFMg~5tA;(Xp-Q@@eMwd%Lt?V+2en=IRc$ah9(Jj*F?%%!v!*BDNq zn{=rp<&UDz0qbJS6nF&lf|Md*jk)H?#po=J5^<%#AR|p@u-nU=APR^1%GlLlzqos|rnvz9{PjMZyEW`uE2d(GrkT1RXhDLXJ zm$MChqx^Ikmd+Kcztu%5mcgdJ_rJap2<;a}FJdp#Q#zpod3UjrH(CGGYWXO+Egz(l zKD359*T6?fc2tFheb+u1is_`M*!s?0A&Ip?N8`ol;D%`K;7q@z;hM|scsM$^Gj+6q zws@|pj4qkR)mg|m9z?0BhWHpsitLF=mR}EYl|^nZlW9S?XI4pS!(6Ix2s&!|`u)iD zQd0{2(N<XntsYwB;g11?7A5j-|ShOM1E81a-Do-{;0{}SkUk5-nL z-4Ku_WB)kzr?Lp2KV91=9L$CE@KmU{;+yC+U^LsbdEUeck&ALu#vK_`e~wqTc|vVLFj8C+;D!&hlh2ahjG4sT`iAk`N=kEYjp5f4et1= zg-QlXqLtECPAbaZ6aDz`ZlAY@FL$@vm5r?rno)pH6)A(6zK37VeLUx2qX7LZp@ZXV zIxENa*q^YosMK#R1H=yn?^8W%O!nZz_URQ4C>urt~{t+RAW^_6ivte)z2ZN4|W z?(Y$Nf9zxH0YR4fUpUB$EK3_V&%4Ekorj}mhY#*bZlrteI^Lr8%aZoP<5(FK%L=`? zIQ20V2TeIX^Avha2`DsHLxEtLa+HW+v(YgVdkw!51(KkqV^Wv5j1?9*+EC9QAqNT? zaa?Y3cqZES4KJg{lZ00BCSa-1=Hz$vzN3@%oV_NGz`J^5JDHI_%?R$vR!^c;bzNh1 zF=*rN{>y+&l~eSTqJx`5{qC+>t5>oQ=bU`vCDp~*#mn@&1q;B|yeSMCcL`c`@1ksf zbhUY^?z+chRAo^AnZ;DAGn04~?EQ&KjZILE@nqaOs2h1A3`VJ_X=MQn&b-c$CBl%X zX{4cmz#t+G?Og{%K-<2F+r{aY_~z(I(A4r@OdQ<)c%{dep1PI&Rx|Dh1?))wi(xpc zJQIRpKLf0!qe9OvuX4u~;8JG4LwU2r8F#o64SM-J)q>N9g(L(6{1-Ck5aTVuVKctiBz3|s_v^K)NHbC6bv7o~6El}%hVRuFrgyvp|YXiTmupRzbF zrL&^?zZiSRU`>K%U3A&DHOsbb+qN~!o@Lv%ZQHhOo9`^%`S#iO#@TV=Mx0-jRh=1G z)m7b*Ro(eK=ejy4wB=&UqI#E^gL0D^+uP_;q99y;(X>oXZUHs!39 zP4bm`N`b&IN$datdGxDruw2U!LrKe_Wn8ejODx%R2|A zB1W3Jk-V=bDQ-OsKP5Z8_{>6=wK9pe6fI0e=@G?a8?}1kEjHv_7^+q-O80YQ63X?Z zqXSdvnQErFf9M%ATR2x0<+bdNk0Oers~qDPAYy>Kw`xi^`9g&TQ=7=9X=~o1T;ihs zs==^;E!s6xh{^wD|KrKWr43$&p^0j4hy{UVjddTN%-*#^NpQ{QV1!VnOvBmz%^o=X z@U9c(H9w)nqNAd%uA{ToG=EgZ;;u#K)TC>cRaaJy>%YkE; zLul^4v76W~_1EY|)MVkgc%(}DADERbYfmXwx$-aU!0MkH93#_q76f1by+wzu_w!S$ zpI!MKO$T?vp@&kPvc($0Y>g%_hX$i*#QUn5P8AP3YkNciryZ5Svg+Z|1;~lfhBO*I zZyOIvJv4dFpmj@@Xw;JRj6*CP9|zK<oJ zCzg7CXhd8FSoMwb!hqHhGOdg09C`v0+RfoMb1I83sahK(KFB1!s)w^qPmbg$`5Oa>!v~TFdNVn%kD~^>^i^>EKvjvh%uXF8=3L5L0LkE5CW&*K-&IAWH&1BSZ z3NNd>l$M(_Qk^mp*QJ9lT!tb&w?*l-{OS9&{(X?TRN?-U*G#W2`&I#N!6{$A)P`N` zGsu(pTDlO%CJjRz_V~takioyKTb@)uKfaIhcZKYMoU@nU7}hezP9hBEg^E845yxO; zgeOJ?4FcdtL{P2uTlh}psKY^l@pjRLZ?famHnml1Z)#B(W1_<9GLuRQeYPE@g$d1%G$6w zF6`CaP1w0SUzW|nY8+zbz9b!E)!y(wCPFNiG z0vpW86v^uqz0t7p2t5!pb}jDCT>Zq;f6fo)7JEOB&RM}<+-GB)TUeoROm8GzijDE7Jl2inz-rQbmx}?a{TEMJSm*4uJD`j`V6`YQI@s&Jz%%r2s zojSSLLc67`*Hykak*p|>SV;tWj*7qnJ! zXFhPss>&AW@f(tnrII}_7q&`47BXl-{Q{vdLbOF<(4RJxqQnVCGODKhYL!6fC zdKoz+{ik_muOhSKdVd3a2v~%X3t^RkJ|$Ppg29@+;Mm>&+YEy`m!>MKM|LLO{3#OO zRWtONX8!8otW5MY%cSNCpqp^Q4&UF|4D<>l07|u?2nRo%jLNU@CPi(!wq=ECGB+xp zTK?MYU_JM>(@giG`A#I__eYGA&pR`W`=oEqLoLY6@3fX+{jKFh!~lETc}9zN4C1NR zf$Fxl@?ZRe&#WDVsk#Dxgw<2^b){iYU)boluc+{`R@dn$W@fuTpO_euvTM0jY3E%j zjiK&YbW_!O7n*WDwCp+^HQ&a&j;+tL^#?9lqnzy|)2VCJoFWWKDXX)PxBpR$Fl5UP zrz?-Gb2EBp#ljg1_*DJXXtp=dN_ir~@0L#vd)D!jh{p^3=EZE4vnDWQ1A<4`Iy;pk zE+1i&e4W>=Hf?(kw2i^y@w~w2x5{rp2?O4<%q#jm-nH{MZWUOT*XS=65AS@;F%W#` zr%8;vtJqlv=e58#cXt~ZsUI$$wj+G@apaE!a7peP(XmWDO(M2(v4VOaXb|dU%)nYS z^@%P!ivWKt`o+>Tm?kZm2Z?~!Ohhsn%-yB?UuO( z|6p~~Tls-?M7$MZiO*Z8(5C6CXRh3_Jp<}C9Muf0w=TrbYPV-EX=630(AK%D$VboJ zg+OY~y;;caDX~s|JK|4S)}Xbl_J;&^GEb~L3J=er|3(>e9E4Tw`YD2%Tsm8@CTDmJ zJ?4IbP6+^r0mzuTt480GzxI~do`zx7MHzT5=w@8#j5Goje9xOj_te6!k>XL*b}8(t%ba~q_BG5u?vvbn1BNfhXh8xUV`HAtd0mpLuf zB-+Q39@Ww%k)SWi?@z|9mAllKGry-dg9^0^*sbWpl?%0AH8`o?W91EFD{0lMge0L; znMJH*$;w4@M$cNFy46+JWxA-B3Hl6Ve5;wPMEtI3y0u3dso8#D{9qdK)hG9u;|jH# zEmHBCM0u!d>8KFQd9Ed!EiickTga$u8)vpse+r844ohdDmKe&CQ==2xT-M0AyyVd+53c21h=8 zaI^450J(BnHH#Az_(Bm2yhM;nuLic###GVuATyN`BJ}W<{?W@VuiV(MvVc*qniw-X zn4mIm^iM2)3GPrC2u;1??2hN){!}+uFD#@lpe$K9b9o<_bvU(Fe{nx%B)QhR)(4&2 zhUW>U>+}4=tP*2WXp*k!9~J|x8@K~{!dYoosln=|;Of-ajdP?vKU>-V5dirN6TvOjw*R(Ol^knEBLuaX(~~kRm>QdeURl>$|k3}rHX}S*l`;0**%=-nQ>ilfpOgsT=%QLyfu$#mtDXzoagCLJf8R^h8 zQ!IC-CXw%w7W+}E7#LKe8Pk(K`TWTJd~g(R56|>KZa&9|x3_)07$~BIQg$IqK#;_N zF{`?%7YUle@Csf9=?X=V)}BaRORZt|+KSGJjLxnsot>? z>ov{w1DkX9Q_^BL=sF5kV+lUrAOkXob+- ztxs4`>{FOo%Us6u`9vdbdEsF|DHb-0@U*Iu%^-0Lo@ zNS^c(x2f5!X-9t`d>UfOD7v`C_yG)#)q}y>5-XzH+6l}3GC`-G+z&4yopz%hxc({q z0y(Yk>59M~su%e6Z;`PX?aIuJQue39f6t;3ug$|m7y<6+Y!J@-Eb(x9I^;m5ZAJ|u zbtkB&Ak8KjPmvM*5v&tjB<)Q~202xhhQCpTmD$$Q(g!^D>W zi^C}!dPray$OY<4|M5?ts`8TlnxQP~)FMn6+lXRYEMW!`p@~DuuQ_e95Mx12zsu@b{=#5;ETMlHgM@gnK^vwYruwJKs;D@3y>iT?jz;S0A zJm6qD!&f%8$wv-*Zh$Q=0wXt5YQU|9pkYImRrcCQ{@lLbR>t$2sY7|C@MoS(GE;F0 zFBdK`kWlfnStx2_-XW8)x!|9iBvpr!@XMWeC{eh zHgjIXiNE;QiZUlI{9K_) z9&H>JB3bf#l1H!bJNs)Y;qVkp1l5!$9uT(t_L3OCfl5!_tJzh1>6Ra&dLc2}yn4Js z&mILUYBf9DTdtaZ84q?+TsW#v+KmP?Eo~!WfKm39lAz!aexY!EvWSlEP8khiJi2E7 zGN3ohNbN3X^ep6eSQmYODZ(}8GQZTGJ&t+zF-rRP4fxpei%UH7;p0iqx`k$skg{Ks zF*gc2u!(3&0cxGcHq7mGBJqI_re-^0d0ccP$>;1jR0*)4Yq?6EJ9z|_!zVD6W2f#^ zIGc+53O4g8x9LbaRjZz<3*jrzX}APMPSJ}f5z|2b4q_@aONtT`r^H>Hoyg~B=pn7% zJI(sbLAV2p_t&t(m8&&QX`H@k?yB`gN%zJ9qs_Ee$Or;upJx|^D`L(*y#%r9Jrc7@ z8aepv7nCe|u2l&C{X=7o3Lb~|(4 znr3r7f`2X-95uIUJdev3fx5}VmaRR0+vV-@uL{C4{l0Raxn8~!>!9%f=zTY(X?TOe zYE^=jd%JbC0J*gwf?kN4=G@iEvlDeckeY5$j+IHxAyt|Tm@-~yXJ(rC6-y@AVLgbM zdpY*R&@r}jH`$BhMf!5W z8{O%bt7aiAPW7@#2ONHvV+}c@4W9m9WQ0JE3QmkpOUDb=}KQouJ0Dl3Y#xQ@nS|{se^Z{8~ZW^;}b^H zk33l9`pB~ZT9}gqbsws$ONQVlD#$c*s_L2<8qY9#mq{9IFl}=b{HAl0aevYH?r`|H z^Ho0Q)TFh`mNufj_z&_MAEUZ;_XJw(UsXv9*V{v2gPmLl%Jk3aoZ9|4a?R6A06&$!E6mE|R_aB;x|cyL4T$zl@! z3==AV3>Yv9W9BUXg$>?^8X?v56$7FmHM;CeU3A0j6AExo-@lqn@$rL3!eX4lEu$)$ zGH(?JXd~pGBMn z*dRE-9$L=!*6BK5xK%UI7=_#(*_I>qk+G>rixH9Wq$(r3FwWtz{hc9aaf(G za--Zn`3b6LYbK<7YC;RmRO5l1k7H}pRYD$*Mg?c{co}Ff^H$SIL1r;cX0!PclqM?k zNNP1t>J%ylj3>+wANfCUmQ2Ud=qF6(eHh$VG}r~2Qe+jf+D+`W!Te)5_mlAW4A|g( zG1qhxquc2iJJB~>V#Aj4cYg_2c;r4xd&;s>Z}MULz?2fj`=#w)dZ53R4L+EH1|`A@OTU`&G1_1{ zB~g%-y{&iYXz5R#B`7b2rV3Y?BJQ#t2vY&We@;d5kXqQ)3uU9NX(^3$dlg+TPTu(f`IbJYQ>Htm}vjezyJy)Ihaxh9$ zu;#xk8nj~ySIg~q05vDQgO+EVxr)BMdL~aeI&PTaU zkC;@*FE7}AlCOAGIns&@M8kg0hhuqfQP$Te%X1iodQP7VfFE!EkD-bM^;eZ;nWpl$ zXjd^sh9*3CN`0{&-Q#Iq7C=*}$2u^I#jcRMlh!|^Jb)*m7v0Pgz?2ISu%G9Sh*)Xz zu5{+2e@eE@mKC?lqy+gl1_`HJLvbkMvhbT%X=pp*@^^0`A)BFohd!qE{l;4!fYI6E z9kM*&fhfhN@2&`6aEm`HuLB}yE+)S!!xxo-c`d*pwczYfZ|!FT(OKYK^LA}`_{Ax! zaRDP!n4mJ~ji`RvU+a#}GM1uc&b z(DtHR^XkWtA3EWY`;SH9%@W@`U2I4Up=x!O>U`hWFD?^%JO!zGNUK7#c>92E6wW@; zUX8zS?>$M+(&0J!47IMfimg+7LxRM148hqQieu*e`uPWY^4stwj@EQUw`ckQ{UDgG zj}MsD>Ya|?cbjbBSFLtCdb*mbs%U$iHXoZSxNCO0T@a677d`^BV|{^K5By*}-+Z%6 z0%K~^P`*SLHE%KVAJExZ&yheMP}g!^XF%7)^;z}T(q1+|Zsa{JKs->k6!ml$HK6O# zhOkui8U3Zj7WDmGVC>|4F+jM9x#a#YV5`WQGKTO0E5-fP%7*FvNE13zhGL65Aobb( z%pfw*w8V^(hW`D=AW=}m(9fvnWb-11!~ymou!*wnw@CLETkx0NBp~0j&Xa}*0_9^y%?UU_G zabp6tL$@c++5=GlLjvw)|sr~N~6F9Gt0$ny@#Bdh-o`ZKovGqqnQ z7(gDdhvX5|ehlC}kJwUIdKv4q^q=4+fY^4Wt4;C;6{psIt z0!Bb~4;2t5)e#02jdU0UMnL2!5Re2Wk0go0DL+XjsdM|Q;tmx+VDy(hzyz#UWM3a- zkNo787`#_3+#R(&sy_$pmgrs%qzx4S5wMk*ss_}C(oSZj1@Gv**a)$IB{ik(pOu*E z3dDf|zzM*G(pA{!29bc)&1!}N)fKt?q^@|fRz6b$t0h%45Q5Q_--kN*gfZ%%wTb}6 zBTvzjrtS*v9|?Y%Do%}(d|*K46x|mF!9?Pe-1h-FL*|Tc_LnPtP2$Oxsoa$?WatP0eiU5uWpAsId>)&jvI8BPgqC$Z`UGW1;hH1ju;dLjdoNg~$_t{{_2!ZeKU7hEI- zq9w1Vv+aw153<~YAKC#3d~9l@1Z;^hXVmrTx^7tPi&Kss5q$Qnz`9%H5@~Mu@!IG%? zP()TBjvxSK|05*+kbCIhxPVt=v`?hNpU`{Y;5Xt=nYPI{1*o6-n%6JAw$V3&;65P$ zO)yjb5BOkL{F{a35A+}*@h9*gkKiBN!8_t_p8h>y4_Lr%@UsI^H}HWgRA1;lPOyR1 z+Q+v<*T9<$RGqK~_#lJ-EA##waW~k3MZ6#KJvh{y2q*YK8Im8!!5C6*(EYC&@;yYh zEZ;fnN1r*MSnEe$1<)I+aDj@A!zA+D`RpTa-KK>%&>4t5llZ6d()Bl};4os|;@tyW zrMqXo^4$|}!JaL5f#4)0c|mSy4?GVPq}!l-U#PvHdp)QzA|J@Xo(KgHh1{8+~pi^_d3wkhSkgvB6+Wo^GsGDHW z6)9G@U44DOOT2yjP3^#ZZ)LEiw<_2HPz&~sjzn)H+#8iay_Z%&y(jh{^1y)4gVx9B z$LKfE5cKXAm|C-ZdcfbVFfQJ$reemxC=)+Sie)wJ7T~wa8t-+_!JB=m2Z-y%F zp0}++_KwMJlQ-g19p7>TpXw7qr6rT0_q7X6J)h5gt^Yjm+U$ z4);Y%3bJTf-rvS$Q(G{UIejWc4m-$u7<+!UIzCsObPvw?0k~DGV;p!oga#TdEVD$piXW>R6f@T?7MPD=EJw8U3-?b|!AF$~vY^@GL>+?zIb>&%PgoqZzEDe3T^Z*lXz}H%hEF z_zD`+WSPrXo?GKAKeH2mnSCrIwhBLb_B${q^}6G44fhso>)!gexrtmNU0Xf6){A;y zO2dAyR@b|ZnXM(crY`+k%M=b=+a+!C>|xV99Y1UcoCD4-3uEfo3&XipPVC|Zxl3~~ zs9VlzS;@m%aB4#HNJKz6fLE- z7~Dx2rRW7P7MVS`*+a`=l0D6cQhhGr-CdM8k2oM=H#XSmU=hq!i)1;huOycYdc+JG z&7jd`pHobaEnGq51J(S~rp@U+k)}>GCO0;?%z@~4mqq=r-%undv9f|-p0PSgM}{sV zwR~NL3p+IXHY2q=N`DcRmkF{XjY+<@T5uNuo4?8|KC8zs2nQkRoiKgeEW*14`j&s% z2rqx!K)(rp)-`Gc&;J1TEHE+upV&6+|999nM6AqA|05kzFKH(>@IOqwo{{>Un4!w` zDZ}wZ)`uhCraQhs!3$(suoWH$TMijZmW`zGJ~tT`(?&QNA2YjdMd1Rvdw7;;WwWiZ zwO=!Aq_Hlyw#k$WZZlZneKRElh3k-nw`&RX-rko*E7IYBr8|8Icg&{7o499`k-J*5 zwT-59Uk%8)ur0>V#gJ=CQ&qIWlBBt3ZA6Ec5YcI)C*P~Lt_S|N|B&_i5*4JQsxCd# z>4kL?YizNI(Ol#SSG#>vku=`)`JxZTmYtTl(JJ)lN|V8sMpMZM@+giLugbzwu3!|W zD}yrU<0S`ClgDIZC*yFjP`(6OH2~kFvsyE8&RHr>G>g(vCCdy~zyw#SP&!BS(5Y}G zBuc>uV}P51Qz)yd(+P2HVSPRZAzgcK#@v!hj~hw^lB2q$4Wxj%QiY7LO$&Kifs6&x z95B!tLOPibI8wHRnhdzJ7A}N`s^Q>c0dC(skfnoy;N&^Zw}~twOk_-zWb}250MyAe zK{WM7nN1h`yrJzKTxVx#4Dg7ztc^J(V?8+UMv~SSfeD~_kB|ZXXdR6x@ zgn)GPM+QeDh`F*L1CS`KX;Di#gE>JC`}S1$h7Rt@lcF)0n zgI5Nr4zeA!&-X)E28)TZ(M_uzga1Sje)y~D1A-ldL}M@uB{67{=3&0vBJW5*c%mstvFqBF+~PUD!{A{~|gz#)npT~E&) zAaM-ffrg=q0Ft5|Hrv4G2Uyyr^rJXHU!leZM<&D#9w6(WyhcFC{Pr8Jk~-iyNC>cQ z9}WPi6os@A)rC;!icAOj6OPtk5ZEhilm-@(5kzsWNjN=1hAR=_%!KsyM^55o#>0!|-GXDJ8AoH!>rU+F@SyXZoksKr zl^^{(`H$?FrY4k5Uj|{gAHU_)fIFvq`->s>yJ5@QKu_DrvH8`-_xaMrN7)avPkE^O z)(w6f|N32#o7=N|OA+_`bNkct%DoC?#xDYVzh(ur+o6C_S3~bQfU3tGYr8#m^!y*tbEQUS~OqFG8rygxqjreOL z|DN8=9YNb0WLtWhCAVr{Il8jfL{6-PEl6p}*UnB|J#=U5=U^UDC1)G-Fw{m}bayh0 zreyE<>`s9MnAS&v5KT;4wg~A(k+wW-79Da`Bnrp)bYRR2NoB!Vm$ayBo5W`nCq2XX z0IL&U6pFK6n+3=%dys}LE^u1E`qxPG|H6+;`$OaYhjlH>u!Jc>%SiOz8_~AVve+F= z{Uj-Alkx2150oFGjwaIjJhUGo(Y@wl;?iC}$fyUSrbGPYZjfQn`Y#CoU`rxXR>lEX zc0md-Rw-3M#VC0wEQ$_7Zmb_7ZLO4^r^8nx=I*^nVF_o7o+RPyWJJKB*w5Y(F8|On zjqA6|UzQbjYp7rgTZAXRdl_Q=$_`68? zD>cj{j-@TB0Jg8F%JvV$cO`5UQb4&0a_WsAFDSNGaS{)gxBG)#tlVsA-V|PUpL2OV zjXs_3L*}m&x9I$La{ZnTs=BDP>#Pl1M<<0chzsF*V`e-)@KshXNsr}as|t0 z8~o@rPiR;N_3q6d7Kxh@$4L(HGbr(1X>_Re7)x+rt)T6b+IJ(D z-AySIsy0HjB38>5>{a+a;d=;h&_0hUQlB8RUCoN|qHEkTGpFtufA3Kl4+?gBmvI!{ z0eRD8(i+yTRMW`mGT!Y&)$r=-H}OK$IW5uDk>&etkcr86PG)A$=D+w_ln~JRsU|}m z)9ndK`R2*4n!Lwr*tN-xlvE#klap~KAz^?kMhF<{z^L1atOXJ4?`=d6mjRWiS?8K{ z`qX`eb`{w2U#_pTh{lGhbsn!vwPOUGueqtaO&6g1G^bzCwQG|D&|olkHFHnU*FllB ztlbk1I;%s*0&@W?hDF5fXpA=gc=d{g_}m)nF_KBQwKjE70YV6+SGot;*SxrVAn`+m z{9`1!Eh3VGs0|UU)h`G?0d6#>WO(4*^~vW#rf^I=Sd@c7)$-zSs}Iy*yh7)7`KfQ= z;;{(`*2J7x*@Fnzff%!93tkXwx{xuF2pwmf&&F2;=+Dens6Cl~XVjmaT56#ddKRiS zZZqbm2%`0OM=&C>dipt8M{uU_@`)|L!Xldd!9k1nq02~EC@N(b8{xxW)z$1%k%D2Z6AwQ!a0IsNgK$B~?{f#HhP`C%)$_c+<~ zZf6pzxo*}uhbzfe@&W!9os!_W9oiG2e+f&0%Qb=+x#08V1Lusz3!pTXU$tuX-6RlL zSz2l${9RyN(K%_6Gx_qUaJQGtLETMNF>z|1Otm=BDj?*~keuW^sAfiUNp@wsq)9hp3@g7gCG1^~fEiF(K zhD^3@a;aX2ka=5g``P<1@1C)Ul=lm;<2vQUbvennAz^fu5^B8%W7&_G;vd=m@)c{l zzxj|$lu+1kV%R|}mX(Slupzi|Ked(SXco6#vZ4_=!ygbjx&q}OTDB$tblbX((jQkiI_$}YESXlTq(>hs^v`6$!01K~Qwr(eN? zXrHmyAb5}argy}47HA29WlizHX(+UiPKFyaP(PG{2KqN5HF=2A9Fi|n zfr}o~l7^7Ug&D6MNqWG`>x_Z)`Tq_rvzKG?wz=<;DsH*GW#~IeClfrxQ&%ep?GPL` z_Ym`-l(xQdCp5y?1v5Pbs?oE4k0K_jGz%>nXv*Wu&%BjVV32D6jne)gmQy z{R_=pRzb`QAr>94|1<~TtNWwXc~wfV88WJb1IV#{^(B(hvi+1r;0aYA;KvlxR}5{r z)kQlF(VB3l$nVH{pWRN%EZfU)bsJ0neEJ9^SXr8Ftx%>ZQmiKmP7|Rib(V?EqP*o4 zI_Z^hC3rAoT^{yup6t+V?dJBFtjJR7vT*aBbDP}YH<0Cufg@guOW)++QJHn~l?lf3 zaT>MDaZ-*SU3;%n;A-;iwi{*GfiBzR-Y^|3R)<=8?I_ka7~++!7&}drz%=Ld1Xna{ z-UQ1TygtA*pW}gT+Rc`!{SobhRlv~M5|Md!2AwSMokP^FFz_glSCH^af}XK$J1Wc{ zFy+$YrV@{uxN~CwZ#Gr}O=)0d<;A)sPPW~Js&7hvt36#8pV0ro23YxlXzsmae7XS` zfJHz5Xce0W&y3q!{*}>vJ^cgR5kX4#e?+_gJO8POgT0HHy^Ax^f1py8i5TR}Os$NB z9XyD1{?!?Y*tpn%Y|a`Ts@6|6Kf6vp5qo5!-(v?SJOtOe{ofod2)P`p?mS%gl^K?Eirk z{g2MSofZ3k(0u+Q|5N#I&*JPvZ2#R`aSkH3|E2sNM~Y4kCMsqw|GLhgC@N0Gplasf zLc}0tXJla}{NJU>f0t7KM)V&O6&EL06PN!rG-o2Fe>VRW{44%5`A;1G@7Dc?DE9vr zMwWw#^MCFIZ}hezNu`{8@9M@a28Q3iK#U72yKM5vaGrq^-Gu4`^}Yngo0v(Kc$XAH zGrUDrUFT$n6!mrw+&OsM)+p4zKTD^65}V3?K0ng__;CGL!>8sPp{RQ|)YdqX* zmVbRd>XZMeOn!eoTZ;Sni$YNoILDOtv)~=Ha0jog!0w%t)6vr1+ZIek=1|kg=Hu!t z@~kg`>HrVT2DWS{1e&YfD?6pIIb!MC&TiW|jQN0K1iDGbTjQcN@zn& zUO(ThqXz+f+b;TqoieGru?^yl-QBDZUOa%VKu=AlaLBIn+exOG`%fa<4(+D$C9r6Bw8g?{3np4) zcG?30Rs#zUJ_keq)Mly`rIP)-yvGb`$}AsU0o`sY*nkM~l44xdM?ZCUM2^~jDIzP$ zF0^xjz1SsH@{_W80zYmH_8Hx5Ok`rQ5(y9r^kKfB@cIF@*X|cs414qY1`AjY*$}H( zb$gdFEi||`Ce$7inorjrt5788!M%z%z8}mG5*};ourH&aho&T9aQh0J49nDyUc#AF zhRvhv%lj2?S;~BRWrs&IKGH0{PSH&B)kH!@_82xUXBPa0&d4()*kB7pm>fEtBAAr* z9s%;=!e{ki)SL$&kk@hv83;s#xi4#6Z8(F6!UjUHu;(83Pd z2(k}8#CGq!lC}-uQKKxanDDV>xWic?jsy;l($d5aC9t%?rM`Q@=8DRk?51z=L1t_U z)X8msR)uS4R&|eybV=>edu+~GZWXfMK&b5dp`iFEIX$vnH7mE<6pMi&cyN4Xt3%GK zdK*4>QI4&*yL%cGnaaj{89cZ@0$uq`@Z(jD$)V$rc4$1x76{Z$I~Pk8t+*s+%`id?V=&8iW*4As(9-q>^`dLs&5qulQX#gi?e|~Vm1^-^11e^AXoqJl{WbIa z5n)IQtt2ERO|Q;1^5{Eq0O|uQEK|YZh!8joxqMh`pgMPVuw9XMr-z9_V!Y1ztNRB( zbVY@q&F_3|QAYUg{@q8{ZI#Z*$u=_@4=?;VSt|tcfGl~!d=^Bg&);>mmeX5-l(IwV zUUA8lRr#z(6O)8Q+oF=!2Uv=f6~pF30jIBxU7PZilCx4A!JB2KJrBI=jy3stEG!-V zy)@QWAinA&EfkOqJ7dNqq_B0$grS{mT|{?>nq(80~^7)_V1D*L^$ z#umWT6=4>L%f-eF_VIvkb|1>kVE!8p=p+F&FG2@cskZ27F3;k=H@8ghn4kow2@3=AN4*IFz=f& z=vE<@R!R+1(^n|GJNBZ3v6+j5!M~xfm~56DDr}qQvi<_9!zIjDYp>cQ!6cEN2z5XV zIDLQN;ihZnZpQZjy)cacEopj!l&;D?KM3#@R(XWDra6b)1@6n&u~B9wZbKYI$%5A1 z!hMt8iYGt1(%CVpY8jX_5b*|CrkITJH(t8xY<^{|7#V4vkCbK~kS2?@?-*K^a0kn( zJD5antB^(IcLcJlgVZ*uo4=iulbmm*eTDCTbPM~1@(HHnH*e=JP6}1C3QpGAg!&GW zN#4pDZ9Ejd-v5iRC=tWOXWuUd!mzrha6wWtAD_6bc z26LCtG^dz`wIoJ}O%s9Y`Fs2gzbkQJMmp|*0Yv90v--p1{5~o+3;~h?kCck0q;9z# z-ZkuMQmGdfZH_RsWL*VO2!GD?A6rB$>~L3LGktiP&sBn=)9L}-#sKAsBPiFp(PJ0( z=J>rxe8BO3)EOn* zS{s>cE$4_n1 zJl(OQPPF%dPR9wjQ1$aWHGv44lQpRd{2bf6T}{CE*F)kPy{y~4JFpDf1fz9V+lsC) zAzMqe`t#KgLpN3;Y!LXLFI{j88;)*SG-&1j-uaRiwPk}aiKi#!SfXQjqLAbg_}(E$ zU)|D-xg)G&t;m0aT!r#RO}RZz50Pkhu^u(|dt@bD9J!8hx-=PGIyQAZRlUMuQd|k1 zmEET`XcZB-+lbP%ukc0sA`;N2FwlZdEHwm}C(&w=GJ{W$^Bp@S&SxN~HZ*hng`j`6 zd}SNx2&@kLmgP!;qFS3V@*q*qHqu}?c42UjLKRtm0a9!Q z$31Hio&t0%_NclKTO|^sKG>^|h6r745V;#^&BeZoB})MAz*KowIg07cfV=0cMSIOt z;d{dqvg^l$(gJ@qsyE~|;ceNUpRObTA2s!}r4Q8T6G7H$!Q&y&MEim1ZtadzHy94- zFdx4zc1HM|b@GC9n22!%qu0%P@?}`Pkp$5Gu$)^7oR;*Uf?bBI1NaKWish`$XAQ4P zV&{YIr(Mw1Qfb~0?imp*<>YsUA2XzlV3NHU_9z%|#D1xZP+B*tJu4lc%K z!(^P_-{;X!whlu?hoeN2fFI(e$0v7F{$wzvq5U4CabYv}CqTRNaA|G~LYWDc5fL)7 zi3k!!Ms-GT5I|!(sN;9xq3VMiSerY|d@HP7qK2K0Uc_=r?=IQf#=wcD>M2>yidq@e;GCu`vEaIzZCR2D4KWRt8?pFL}=7DoE39V)t$;B%t za3Y$A7A43LHIM{925}-l#n|FK164}1tt03tdb*_l`*?b4^0#RHw2Zpd)A41p6cfk0 zY=8gpBp^|=-Vfy(ssu5Srh0_DJ7`KqPw3xlubu^I6sG?zM0sKo=6PCU+N1(`+_4 z)$Q*Fm)h|mwXN5k>vJ>(Y?ZU|WDvv{II~s=f7Rn?dGiK-vo?eE#UQJNXHP&80hxr> zJWlhaBwI_)2a^0oy$bWyKyghFaJNb9U))rxeQ-NxTQfydY-iJ|aH`E`cvNvgKA2Sm zA|J9en~dC}@tojWHHi@W>Tfg)RivxCwrPXj43_8AQ%*cK&QoHtHtK71R7aOWW>PS? z-hntdo>e0_(NJzrb+c=c;8A}HGKH>&tic-YO42}i;&Q4}8R=Erz{^1cx4)TXn2Jx1KjT*-;Qg!TEjlPQ0p5Q3=7;i|qjV8lW6 zICzBYaSI1&+S4xs=*QA{N&?SL$nd4>re$2?dYVUsT5!p|Hxb?+8hsucFPbpq87NKX zC5Qx8_39WgmdS;tr=-CLaZRR{Q6Du=>6!!t2m1qp@rjSUORA99q%Zj?{q!L?M)vnu z%y;}XF{7VaTX5uE)>3=RCCa1SCXxFiB9d)(Vii*+e&O+CyItH+B}!!7a3Z1VU(m;A zI8M`az#tf#MW&V!9RFogTMU@9qtNX9#cd$KNDL2DH=xb<=omgJ;Vp(aRs(L5loc)A zTWY zl=Z3942H(ztyjKrbIl5#ogQzuCVq&l?162b3rrXIpN8&z4%vNb!ftMtKSAO;5DEDd zohtn5h)JdS*=y6`B(c@Jso}2_elv=N{-I2%2ZcqufC0}6zso^jTEGhOlaiUA!3#A$ zB3?2DkH4x^Zn@u(rnD_&C_fYoyMV6rlb5+Y@X|Y{#y1 zgi$`z`wHr>9-I?5)J$kfEQH?LD4JOpxbu?W(Omuy2A2AXd456SK}saFP`u@7P9yyz zN{dNR3l@PbRP=GI8xPqImdsP!?*!dnt41`*HlGLQgSwFPTNN%fb|Z?NiTK0JJ)Rst zl5Y1EiTKqeT|U<^5Clhf;N>))f#m~W(5$?!dS`!Wg+OG91&2bkuu&B4Gxw~tL6H`Q z;fuo_4j4J{C6enPs&;DXiiAgnD~)^}xi&Xj>k&V=j~LzCop3VkZTBgs7MdM(yVH*f z)<}CVA17i{E37U*r_Pm-gV^LLh}pNQ-${5EsVzT^$zTqYqFn{7-N<8HC4RF5YqkM3 zs1Q`6uc3CA^R(T)Tth3#txj9sz_h*3Ylqj|a0t3aTbYc6?FkNL9<`A=Ct+FcGaOqP z?=SL96Cd7uX(B;0n+VZ%+tc^}j4hq3Oo3UWq>Xh6hTT`;LlCENm1J2p(ogUb11F^q3A9A|DGx?z-Ht0oCdZI9SNE~C= zA4g2qFZ&lu5T4a`Qy)X&*@E8@B_|B==3y?P?M;V8-t+Ll1d|kJV7Sz1Ake>)UXB%KF+9SN=0|Xjcs;E~VO$n~OOA6iV{4*e{*hE1n1-W*!%ghWBmKT$K&}f7pA^s3zC%ZSWjDb^)a-Ra8WJmnNX}4$?wRq)V@%1%ir* z1nJV7(tGF~qDZd-3B5>%&`Sse0`G&K^ZUPL-Zg7x&8+z_$1f~Xo@YPzz3*MFeeLY@ z1&?>uIHz3(H=axv3-t_Or|{yMmDog8f%M=tt+&1zIEveL)q zZI^^A#{x1eVDT!sq^kEA%;pPM?8Gy1dbf*Y!iv=QV#j}sics4@5+>uFv{#EhW;A3p zzj?Ezy8CS za!y~5+Pn}k71UAUE-iF-nxocSW;@^dYEz1Lj2G%%oq&X zx8VHkYgnB1oIM8y=D5D}fAzVq=FdWsaP^2l z_bQhZ;h{^SPFEpq{!p-u4$F=hF-;41$>OPd4zU%>!i;6V+J`yr!KD{(d>MQZWQ>1X zg$qkATe^$W$%WmPOzUxD5X-wSm2(U(c^hl_SKe24Y<@=SLoJzSo1C6yf#V|d{#Qn3 z-zh|PyHB!wrxyLeO_@~OX3v)#mGofot0gPhNq+$EVw1n?^4pF5I6Cf&8AiNpsZ*#J zR~+g7{gDz0S6jCj_B9)-u}AmU$3icdezBf;mR~%3)gZYqc==eF`f46r!P5Tlj|(+A zGEMV^#<{y4X&0rre_|rrn?mpMiWZ0%QgnoTjk(g^_25q1&GjC!7n~QxyL3LwBvn^O zuY}n>gT&7B)*AOerM&Ux6ARs-Mz;9V?xC>}8}MBhs=7k&;p}W?dc*ixcHw4c)%8!T z3c0TO*RE`BlbYn2kI=<;?4?r9^HqH0BV(TyC9!%nrQ)X%{$1gHNr#L}VZ1={c7%C= z;}SF7K|;WLwNDxH_9dI!^U?Bpp0Z2eM_C)0#+pjU2bSiuEqr+KJN|9Eaw31QA~3QK zl)6W2U$y-7{c$BjFXe@#wamn^g$k3Ut)Jw$Xhw08da{7APmkNy4@RiB+b?DW57RgqBc?Z1qySj@lz2?x< zP$HE7=VWKr$G7SG>_&wimOom9n8R!HmRyI+(dG0jjp&PXCGcIEV8mNF4(5^9UD!0P zS+b{Wj9(YOa1oS6uM2q3C1G0n=AT!Na=CIce#{cEVo7+`B>kcz9FOR~eJqO*Dw?=d zRS4p^(-yKj$y4(D^ik=Lqc_5T-FRkZnJ{VCt0H`Ti)%NiFd#X?Hw>4t%UQM^^-Q0P z<*(8xZ(iH*EG?+kRMtpfq_O>q@Evqe`rK}9a(_qyZ51rM(c^mkLSQ;O&{-!xd;+^s zPA_YESYzUv5GF?H`n_5t@Fw`Drk5~AhF_N%ZTs=+Qw9FHEK?$}Xg#U&!s$W&)#nRnUJ zoCe$+Ln7SE@8q`mSa?b>CC#^CtCj0h?#+m(`$*|GmyhLX1u?h3@-(@%eKu+QY+#Z$ zJ!DWyo6ooM6IuD7s46tHQJmRf&Y~}iKP}~lSbDj~?)YPEob<(@f^MIeU0l!q@_BlE zAxFnamgaG9!RLyLn!z$b3y&33-Bzv^(HSemP#CySKD%Y|eJW;thf8K_k(9K(K-_>- zIigjDmYEcvDsyKyvq*AlUhKtc-r~LY?2k`wfJkq#rr$PF)^}~)YM&;CEc``2(VDr$ zgFLbL^U#on z81N@|W66_BdWvfpA0zKDr*IZF*Mz~}7sNS=7v^UkYIO{%-Ioc6RzOqV(LBtsqYW13lR;4BmD)0T9D{Vy;%a(a_HhE zIZs1yL^A%m2YdhZHLvc1>Ur`!&I_zkWb<@NEpuacS*q*;lT>M6zA-tvz>%M%^Zo8Y zQ$F9ia?^$;*VVK?IY~H>`moSf;fwg#mAv7u*H86c$HH)W(~4oakG$Tp^t;8IaqL}X z1YLcm8_N>>4E({TGeM>VOrzDH7ukOETI8Ri$wwX1+YFcG+2Wrwxqo^U%ewp?#-Y$$ zAm5bpIA{dpF{dCWbo0xc>r!VDRTew~ffR3eD${U_1~Xnx#S;^=inv6QO(79NDE&#c zetoO>&Y@{owTZ-WqbBJMsI+ViP1hGZqV@Xa7kxg5UoWaqpbTF{wSK(%m?{fNJ$05@n!HbiCKf!kjo+wxVtv!pvh0lBO^Yj3+KV48NNiN#FOJuWmU zdcJL;d`6-*R2Y5awAg=kQET^F&i_({{daa^;qoOSu87jSqNFJq>d*UgZ!4Z3Pc_Uk zeC5j6EfnU?g(w5Lt(SiP&QAPycH+OY6aSr^`0wn*e`hEDJ3H~;*@^$oPW*Rv;=i*K z|KFLN2#_t$36JNLPI=(RL@ix`zxDvFQ`XYl$pYw#`g;iA|G!5k{`Zp;pF27_0qj|$ zdr!_^)w&1tf1UpU__F_D*8a_M{eOm8%Lj0Bf8UAcf6KDvyC?Kp3V>z%f01$ff5Nim zkr%xu^qZ0Ue}iz#qYpIIS(^VQ1MBk(-uo>jaHap1ip&4Mq2m7cU76B0?rxfvt};#z z&Q6Z!++>mSRkwF?)pj;D2b5LT(!<8wQcD3K|Nb8;v!bPq^(&xf58xs5XsemJ+1>k_ zoU9EsbN|Hy{>2^sN0NV}=KV@@Ql-}~pHtFlUCLD2YQ%%H@KNt5qasrOP`cW>J60n6RrEG?`N6_h(y1X%460e58m0XSd0G zhhRGlWAgc9O_m?7tasSGdEuX;7-OcLdFOVg7W(cg<#XU`3zdqo>5E@9-`$~pP<**( zOh9hq4K47Ujv+!Q%+KIQ!=6^N0UMT6h~w?Ot8W#V%$!wY%%o!a_GuR~4nC6rpInfW zSvrvN6V@`0EszukK6LLYSGxfl`{{5eE+@8f-!A`P<=5w!iEVhLI*Mf8ff((_wf;~SBA489Glh^&Xq$5a<# znPZKOoCC#QZ@TW0se_1wwDViL(^_ZcwwyK3&>ts`&x-=zjj&T27hHP#DVNYHb^gFB z!dh9Qfk9UJ5}6l)w`+$xvShqw@14IjdN(ffVP%jz@bW#+e8cM9JgES$U8>)2*=RM4 zXNa9&P#oj11O+QQ561W+C{ymY*6>GVy}~T>kOo`C<#3N!Y!y;2641e#e(li2I%Dmp zpsbO_u=U04$hl<7W&CqcwDmAJ7lg+1~?E@Qzh;C&%aviBt9LZAeo1VU5w78yyEuwv31q@u1wU#_OF0H(+ zVv;Df)4xJR!|47r#ZtquD&CXzgr@0{Z!m{8Yg1lF!Jl(R(G#ERZB?;Ebnr!MTxV;s zY6>%KRwe!Px^ncKES)>Z_X!i5NAlFqxkximSN2|?=u0`Kuv2mUjW-N{&>boCzz;Og zY?#qPUnq}Lmb-2z9MO;^Jjclom*;+~(ipkph{aDAhB(J1@dYm0gn?{+V%s0nT z1N@@Eu~q-qge^pye}#o}Mi@`6Bn$&7Bn(6`0}2FjeznBH)~T=i!~LKo*+fvQM`qsf z9h`T=A+qjv$d$axL(|OC`QUY~8_68j8ky?lW6^Pj!SdiJb(HXt+Q9B?$mK@yJmlKm zl2i-G*Dn1}Vh&EFj)A#JEtH3YpIT-9W1dQ4rQP5UZ>Brp22|>v&2|rbf_WUje)Lp7 zan?+hku4AeOR5P`C$uV-vcPda$MEuq3zwK&T&9&j`O7o1UG9fF9j`)ZNUju`_d(}m z?ic@6~GfY+;Csb>SE8R78b9Kpov~?-AyY@jGiLSWJaOLLMB(D}QB{=gl{1PCJh|)Ul z7L0pDJCuV3Fdo?la0}DHY`#3&`cf5DULM6@6(Qq>C_mnfjb4`mLEL-gfyD(QPE4QL z@Z}ya)*Y0yT1?4c=HU-Wn2vvG;&^RmbE(t8tNf7^KFM*#WM0Tw2V`T?!c=#qIRmmW ztaGrSpzhb;^X~kE??^JVHeksNI1WILuS#a8ouN5kdYt{M9~B2TGNzj`JTgFp)!$7#8viorlp8H}+iWO7 za6P(wO}Fs-hp@!$tTnh2|UY6`T-c$AiLFG_=Iid`+MIL;JH7xj%PJ_mkN z<&Qs{qv+Ad7Wf=IqVmu{!!h{g$kY)H#cQ?)!ul1nCBS{W)%OSTqry@Se$ieyt z{)$e*P_Rzg2~mHjn1n0o6S74HhyUbl3d_5!kgClAbv;OY7w7Myu!?w)T9H^cm6z0~ zwBAUH9Q<2YT4rHWe5)+_s-mYKM^)&A!z`FGvEP^B*G>CUIl@D6vFLE0(u_+%)c{pV%=H@Ob-Zpx{ z$dojlmv5K zhF8zn!oZS-dfIt=wToj1W-)Ung=n)_`lNY680(C?jSv*vow`%MmKBKZyi$li%NG*a zPqq{nZyM<9W5q3s0G?29OUG|>inWe7?{emzV}(^6v%`Dq%8>k!uJ#lgKT8dxe|LvX zo|Dm{avplBYyO~>XIKFd4hrws{8Q!cU0)wYwvZt!JtkhRxyAUM?df>(n?Zf<8)EK% zNw*9D6xwAc@vlWIF=6yPP=(FU$YogAqg_T;ph@%dGb8=4iS}lPYYgotcPlX zs7<+*gAtd}@!AchuT3{1uxwpkhRcgQWi$nxfAo#=eT#9My`d$2JTeh4{CQA8&KAi% zi=0It-PK+uT2@}b491ka@7Z92eTS+94`$Y|8A;y%$C*PhqgCO&nMFHMrW=3Oe19jwg@n1Q>XcPm6v#9XZO;qd%agv9}Zhv zIK?rP>{4tUhv_vrlvL{|ltZPoKTs1UcMByOrr4n_g2HFGgGv+34#gtjgKWEl;?d#eK=ki%w4z}ZLImRIk?gQ`qHR?VS zSjAG!E)7udNN_UU$j@QvzQr0V|80>n&xoq7Ln!1!pUSljh+WaB&oabrS;Gu11~<AYl1lM~~{G$few{hA->6K97PdXMa&3lw{xZg*nO$pkLY3_yU9d0Xy&Kz6u

    l$U>}y8 z66kD4(j_b{&|rZGMT}o%Ck7Xb%}tH4m%}G9_BR@J_|hao?H1N^M6(nJ>iZq^GK(i> z7=73t9Nr}xegCH=RsGMm$3LU8asjvdba(W@(gdbeT3hY_JdX5Dt{>&evFhj5I=PFs zD+xc6U)x|Ws*jul%pmae{ zXwshUseIrapq*yVX%zx!MaJl>`u=>|GdWSc!s%fXIyI3l&(Uw_$? zbUv7hTh6R8y>TVcsp8~wvk4NTBSAMt;u7`3Vk?6spkH*)owX?u!#dPxhMQex#2#jx z$|J&NI1~^YIU{5ITAHW}z?LMpv=L%z96?W;@=hX}2?P$PsE=}`*qPRMUxlu^xL|@n zhqtgBi^_lkYTTiz^~rL%OfOG#@!YFEgN$`kAQP&ssLsRf2&yS6W5|7$PuASp5G zET=S*HbZyzAZVLQZTk%H=*SSRWXqeVw*z-$hv@YAjf_4j}?Sy=```(J#sjQU#T(>rUi&?O! z!`P`89S2|ZklDY!*Gsi!{46JrVmUz_rQ=_oADX?t?9-h|5PW&xr$H8hTQ>7`hqqRFun z&JacZ#Vd5aDP$tqQvj4mIg*F0K3bv;iXE9ZBo(EsZtj)ho$}ToQvni1utc-3o6xdU z(a>Pp?DNg%tBebqXoaBQ(*_^{J6dAg>&h7-#3VPyg=fLukLC>8ux4=^Ag&)Rmdv>? z!xV@Q7dTt;vZt0;|eO zO^56HYuv>MywrFMmesE>RhSIbWYs;R#1+^^BM8&OWAjNpGA~f#vqa?lRTxdt-VBXyf5=j z@x)2j!9e1u)s)45A}w;dHIONak?)La3z@%K2gk5ymE9+^QLoQ=q=pcf(aeZ&6*IZ~ zKw-pAh$^pq=eDir2f!o6!5LKP2Dh%?7v*GGP|4 zw~okmpx|$!x`;8jPs-7f?lK0G*-RPO=1K2MmLhq)Y5F} zf2|PTQpc`kuCwV8uWd+rgo~lwc_P-jYND;72F5|4qWU5xd{!Z zloom>+7UDMOr*4u49(8>lFfW&;frqs~;$sC%!5x2YF`t?z^ z-gR1a+zS;1Orc41k1*OOKZCG25hsT1LyhpxJOEC*$0H}fnTX=8*LwntCA#fW`j_X? zgqZZh&v+_X*8}#Ox?kY9-O{qU;>51Jg6e{bBlQ=*>?1lVfS?=jpnuip?#DErUz&}T zfU*xGlf`JLXF{&#NZJ{;5_GFkw)xX;>n9%Qd`P7zB#;mj`sEHASWHuXD@Xjd@AG(- z5667>DhQ!hPdQwvgR@9}`;_%jn!8mbgEra@3tCDks@BPAj%cO|k{ZMHC!V%RxyW}) z=imxB#gnv;lk)o4>fn|tqyox?GG%c$u%N1BJm>emt*~E#+h>OHJP1zl%&|VvE#&xg zxiBep-w4J3G~CjuZn;!n*RHi3hzXgSXJ>S{b0NVd+l?DzN7b~g;xx|P&&o#;>WV3+ zSda;l9U^R_+d8X(Fj+e^$8m#fMKbnt)(A0+Fjfav^0f#=_4FEO&MjY!+cq+_P>7Vn zseeS_mtFfox`9UCbg~vV9QKuQ;1*fyV=x&(($cHrN$XY5L$~M;yMU8&|Ji&8czr<# z#1+MknbP6!Uau508Y?9l$KeX>o?wuw2p^i26FZWinbidAH4x^JxK*naATuTz?@mfS z3Rvo2>-lW+a|$39Cvu;kHU!P5u4e)o*YPZ^6%Ze~A1 z3gtfY_9zvuc*lFMb69<|X)z8&#p@qV6 zL?&eCu*XXldBt5=yh3vzEXk}8`BkqoMonsoXep$?bpZ_{D%0~fat^Xs7tV)cy7yt} zbK6(sqFgUtKbk&p*TD#mppnW=2Q`*)-B0PG8jbZ(QD-exe~s6h{8lLWSDA2@Y%d`z z8X6`r+}SkEUt&)JhJ<4{yNc_j_!D9l!U($UOa(^jKYS+rhf5Ff*r6>h;r#m2OqosW zpbb%3u-?F4Zb*S@P4n|mKSqG?Y>#~ShLMVBe)U=-9Ov7X7g3*#(wT7E5m;($Jcg15 zWpba(v=8seLk{#jhSe6r7h&ztztg-|@2IgLGoR6eq5Qz41wbZ$|GRuBJHJgZ?z3jV zNniZt;wB-++e{4_Z^m%F;w(FnxMu2Zh}w9!jSAlJ)D%NMu3bQ{M3D^nn{w@+mK$|`@|Zf+>CRVPBK;f12;cLwLP$@;kMyy0+> zd9HWfIdIJV)#pWdy2_TvA=i9>kS(*ZX8zm5iZx84!O}NPJHjHkX!Y()yYDIV8EX#( zj>Eh*8%S1EQHv37K%^&L{y*PL^32DF%U{pewU+Gq5gn`NoQA5C3bMTFodDQ6_R1Ql zNPF)UF@LQxQMXMp7k+0AWN@a3Ut^?Fc)I!ny%3Pz zjQ(iVF}y3y{&7@tE$E?lU3bO?3hIdt_^ehQ)u0K4%#&icZy6nO%6l(~`3i*PQdCWO zMQAicrvIeeD(Qx911i6b7~uG11bOn&(=HJnLZHHN1g&5AWkv6WYd69zC2RK@i9fly z_^b;XtKOX~lh*F2@o{wGa2!WP&e>_cHvV^m7Bs_g97FTyzXlHN7r&i?;uHLIz5;ck=T%fr}s-G28ldKbt#a4!{+zhGiV1p z>7IeoW-i2=)3aisMGZMYQLcDJ&wd|p5&(E`M8O{J$KD=8uaL=22H2X# zA;bfOCW_zB@EZ*ywN1s-ms}ac2uKrZ%s98Y{_{*UIJ%Tex@ z239@Q-{L7?TMC{#qjvB_Ue#_s<7?+u>;SSfqYCA`^&iVItA%h3@qN%L1-6iDb`(Tr zApk`qU8gtP0PQjoBYqki+9-=TKip+8e!Dn`9s3 z)M6JFHJIKFar7%Llezx7t^jg0h4a~ohRr~yNLWnH_U+5jFHl|H)%^^(g7l5+AIMZa zzgpdza~~nTzqt8&uR9c15NpkDnIMs76z1*n$8Vqh0C16a8J%z;V-q{1pKklF?4{*+ zA5I^vAM^*b3Z#aleSv2I9*X`zu;Ct$$p@yj15mG{KkUPiQ+YfBe^A;i6G*pM?Jr+g zPl$QtoV2H_$~4H1`LLZGLW^72Jpiskz44NuPvTv@|+#hw&9B)2KS`rS z?+e3VCewM41du(wR`D0OrS(BBWYMesH<%HsSpdhVBC$45~vGT;=NXGEtXx%%F6aDDzT>$s= z-`rpYFw!5JA^9G$C4ih=GlgdJxo-vj7rV=j<7o9&?(u+G($Ae$DHiVkATn(oR0qjS zv&R5F@_tYt-An<5$|h2ZY#1q>4`y=o)Kf!YFCQtjxJYbXcgDxW{XP7 z9B#+^%P5Zh$0#Zjm3w|p=1@mVZM3Fjl_xV2R4P>foN;T}a};(Z8Nvvtq6!othytOy z@D+`Cx7AWOi|3d7T)Pv~fh}a{DgD6;8@?cDkHCy8;AY*)fFwaePdtsyDa_|3a5(=b zN3ymCGUFe;Um9if29`EFD~{*P2{LnQt*7J_`g(fpC9nf`;inw|Xhb7IF6(QF)(a=OW3)F3FWN4<17Ss5usNU=dEx4f~Hy zjEo?ConVQ2RZpjg1AVCB5|{UOw$E^PTi1~n7Q=wR1*~B{@YVK%w`b$j>LbXMlh>%| zmmwQs*N>e|pBSvR=4tM3!1d$F-3o%q2dQ=VP=+dsRdvD-uB{ZL`W**G`OHRZ?PjbO zy+7&aDY?v74qg1F*4}cA`Mv7mUm(Ghka|<~Ou{#XN$|t$<+* zbUG`lU(KKm{8UcNz!q{apXn7*^^Y5LbqL`&N@;*+Yx12`{vajH1CC+Rs!{Uto;h1L zQ8;TG%?!WA^D?FwJ7N|oVosj%PkGJ*(4c>fPd@9a&?k3DiQCLx_5 zyqKezfa~d=7G2mp8aFPn(@Fpw&l7e{uC#u&weC>YZ4zt+oJDwgIW}k2;iEKWM?;s< zq=j-xj{M6(KKX%lkNp~<7-AVBGhP1jw?(c8+(Q?0`0@X|C_5q9{dr=guVTZQ`2*u` z0JD0g_OEUdj=K)0D(&>56tEz<#v3}cx|ufx1P3pUWD|kXv@S#Dp#A`7w=sd)ofVFLBz%6Gy@_uDuv|r4K`2eOfMo0#0 z714M~#w)uMSk^#WzzHG_Be7c?=H0?Z)_%bNGS_eh!j$%h48w?6IAG~c*KA1wx`adx zny@wQcEyRZpPcS!R!vATh9&wWWvK_@3JR)*k+wbD(;^kd1)sV|`;&klbcN^=a1!JRxRw^r)zuAxon2jW570v18pp+*hgfcRq6!3vvXU0R<59C;|?S z{_;OD;Xm=NEKt_qVd6--Va0+)BeN-o{BO%w5@O6}F6YW)bqXL*)aDl;4W$6G#c^;t z+|usxS@yiM<6#o;y>m2Fe&I8GQL<9M8U>YN(98ODEX9eR_C_ArG6R08@;E)enByAo zZQ7ui5hxaT2{WZRxK&KJTOt6F-2U@ctDXhcFRDeiK)qVXT9~3U#zU zg_2y{Z}jQaM2XF(6EHm~6z6$*QGB2FGG>vC2FQw&r#_2VK^wO<1e>t$(zE?sfVEhG zYtJnDL^fV$$_GNvad`f5Wcsw@Z;>C30zL(_UP%XwKa0i3VcaD+fa;1a40`PsND#x` zN(vF|cnS?w|1<=;vtcPQaFb`p+x9C&mp zJ_GoR_u>$g*o6-RW!;ujtq0O`48RLu_xU7nmgwu~lb`zMaoMuLL8El`lna>1u7nv4HdzzA51f1l|i> zyS7AU>sr=O9Ks7^q!laD-}-=vqvkP-hhKe$jSNsg8;0JN7X(MvH7-|+0SB}2VT;Cu z5>nK zDW4iGLI%~2k&P>5z$>&tvRtEX&~<=6hWZJtWznO00{2^UgZIHYp9_1XesgC+M(#e z>FOHD4X-A_+A78d1xgi%&aY=^^I`HaV&E+MUjoFl@{~}gEHmdMDCMv z<3^9?%I;cN;1g*>806%BgO6HrmeIp(*f3-P^O{rP`fnYL26Qwk?b;CR1M-Lm>(0&D zyey?5M{b`GGs?#h#>hL*u-Char+Q~bJ;Ry(_NZxHc`SaeB8E3ltEc(9K(8=sRq;?5 zYts(tOkL7e_qs&1c8VT~9MZ*VJxs%O^fSe;a^%wwo7>sPYGnPb{qZ?{-rnNooFhzU z9!@H#?Cf5NDclRZ#`+H$BN3JYI>6Vy6YnB7mpnxW+mrb>{+K5#b zG0vqgy=ENjT6loV(pLaU5why{bz8hleNda#9b^;Y)Ij)Xwsg!7T@%R}Hsw|f4i#&% z7@r(6VcIxsJY_UurJdYq$0ERS*a{YZSD?gT4k8hD;D+#3>sfQGmO78i?B+jRVv{>a z2BE~v>DivhBY9^gRj%N~v>IwMD(*vB{l%64MzaAmrew2{As7Vz;Qd_KwB#=FacEYW zgz?Et2tbud!E>evv|U&I>3K|*SHPE3v$^DcA1bt*ZPdZMbf~VD@?psH0aac%zDGt( zH3b9|KX@GM0ae*k&UQ_Xo~uJmC`C3p`v0nJT%Qr|!bDw>oZ4Im$FaAnkMZl3P63{D zrGUU{xCUUIr8Gd5VgtP%9BJk@$=)gtk<9OooC5%X_^no;o?P{}3;6l)$uP3-PORI* z1}PlZ-b@i+eN8_q)u)yUs97D8gr7r_Gl6xzwhU)h$nu=;K$*qz`Cd=8(QZt8J-8?@ zW3^<{DJDSys^vNR8w}%Ll!nI{9M?enAl5_#?5NlaGwQ%nLuu* z%5Xw=a6ch>zTp$^t6|vEM3H1hd_R{-D5bCjdnl(_(+x0*9V5|)>~}IFR|Vi|?ahzj zjo8DSeJ!Q!qmlAKh1Kkbfoa_CbL|0rO*BfW1Mi3)74RgQo@#KN(>HF={?2{rfev7W zRnL0sH$>{c<%JJC$x?S8@O;!{+O*nkyJ&|GbOg(;@L&$~w=3?$`nVIOL+W#~?T_?J zSE++z++Q@4vr_MH(KvpZY1mP@xFojq1;1npZC%|nQdwODicoU|SXaDo(=jYX!{mnh z^LC1)tA`$G_o2Fy@Giic*_5*Qp6wMr&Hs>2d@e;_Qudu^OY#{XXw$ zn`ZN%E8n*tRrO$#;>R4kB;sf(9+sN)k1vylSO6Y};OD7jUAgZSkY;i&-|XWIL>udL znDaOOJKmCy)6LVWR-5X-mbIa6sz*8*H0Wu;-D$d+jv(wKAm5XnZh7BUbfh@&dTEC$ zw7rnP)|HD2Uh?)gP#akHWId4Z6CM<5-2AwM`*ZDnY@+bz*D%RYPf^ryPJLCHE>y)6 zpERMM@;*}(_0v}hTH20!acZf6_kSt~uZz|m!6mw8<_@*&z3u%^&8kcn#oqwNZc!kV z-~N=$(x1>Muyr?c9VwWy*5x4e*BY=&!;96sa{IP<(ah3ifL{n^005d&%_ z0qvqg02eXwJ<$g($MIoPBWQUe&1wuLeyY}-NfpYQ9c z`&yat4oY@h+9QJb((PMQLZdsf$h8xr&?=e&{i-AF)sGxx`rfTJ8;`oKI7^_ibjXx4 zDz$e4X|LnRIJAYGhwEvw9>!($)!6gb>?KVjtaKQUKlO8IkQ|7Kj>GjTqguNP9Jkz6 z>!L#qC4Q;nS0&&vqn6zPZi(NqXX#099I50o4`#TpLcO8|lD~J79Tls#AyDSIKf`Kl z@FsFuD7gB;doKFiSE{vt20|uZ)aAGog<+Kh&v|zLFo&hBknd$!W37uc{L>6WT+>RpM6h z?<^0D_kQs1-AXw@SAIrvuIAH&^$wq+b^s4op z{LmUu1UKv)Vf5dQ<(}U#;SRr|9R=VbF+NjWvik95i{>;>>pREYMLg6F$=C8=xSjWJ zEY;k^yx#;N#_Sj&1GeH52aXAckeZO?W@~=0+_3f4;&2~scw57}EP1SLXE^@0==ydF zEORfsn8&J&W zsYY6lWu8zm`T+GyfMHWJ0*9)5u9jKXdMH+LI;XCYjS93yn)P&<>LuwYf26b_n_dQ@ zTSoUo0LxZsJgvcas88~W)&J+QU4dZJ+;|`o5EownOb_3VwdMd$Qqj~|e%+&%DgdtS zXi#blBB)J@5CA?|m+`0wLm0%>w{L=iU@_fyIVg4SvUP#|^eiMYia?Z+Uia*x0E|TRsd3Y)&k77m#s6YugVGtC1CI`u41$h)_^n%BY*(P*W(|x3I0IO3Jh8S63 zYY<*6()|+BSHBI{mHPk|_Gt~0xa{8j6v`ECETJVBUJgzZ-eH;(hfj6Tf290}Sz6{672s6q$U>wbV)tU38wO9G52NrXJX2f6vpV9oPV~hQ03#rT;4zBX}tokhHUs+ z^l!@rER=`Vjj2S>wHQ=)8*O0v6rOyz_+;1iS)cqrfl$lT)!rRf+MSTO>t~}QF!c1b zq<5#T3(1rP!{f}|=+>F*;y~CoZcy4;m08(VDZ?HIQFQ_M2=OKdjMy9xGy5`t$snSN zt|O}5W@}q?i+&QOC(dC2b6dB|*^( z?m8u&ioj-uv1Lh@^@>Wld zXu6V%@b~GY6RRS5MqAd-x<7|z##}sP5*f-xmn}8+>j1wr@NLc^e!O&Lg>yi015H!~ zmKO0KeQkmP-4_Avtru25#9Jg%BENxI4J$?AxMS^UEpeq`jMLspeKY~sgj>thYL*&; z^80lFb5Sz>v+&duj&rNI?cE=2Qs~X1 zT<3xUzDY4$CoX=7kFK*)XyN2vug);bxZg6|`qvPe5o7I8rMk9|of>T>1DE9Vcz&`M zZ|77ky5HKTJ#yjunFu$nn>vEx3nALgUd7~7ve>{lK!*M%MJx8KC1#$xd!n8KnB7i{ z&zn!-D|8%GXcxDOts7lqW`zsVmzzPr48z}r^2AR!z_ryOPHOuSLwp5ez{16A#kq@1 zOHKvv^`ndCjibe$`$Aj~r2T~^?>dO#^HcBAX|o2|WR6m~Lu)N)k)OMge0*^#*XwdwA8W-OylmG9 zIRk8eZmov--qb$45+hD=3};U%6)4>;FWR5p>Ct9A=)k5TGBi#sE^hf`(-&U_JG0Hp z@OSg}CxHp^7~YLQU-RsfmR(O)oG!h=)by%|2WDF%Ylu`d-OT{Y&&Wm&1o0tZJ^Wdv zy5UxB+=&t84Wh8jTh@#)c?;n2{vw@@n9rKH4hpqpVAdk;gGJ;YnQ z#ou#G3NU~Tel}oCLjZ2ERCE&`bfO0MPm6~98AGxMN;8819%XDxiXj)4ipP1Jvz3|eNjUK6QowVFzztM?g1=T`9SdKn|vixw=UR#^a(>4O|5KZ}yX>WR01{@9o zlu}A(Z^*Vr5J0AmRU*6X)^<8XXyhZrM5K{HBFOvcm=H1c`lS?E-zu5fvSeSRrvT)t zy@Pkcl*jyMKi!XrW`?O3?6?*l6dP2_lMQ;k^tm218*m~U9B_Zv@TUcAK?h~Tb% zVwSf0&(Hz@DmmxrvW4_%` zD{`1B7ZoNKV^hW7xokXLy}jLI`DTxxu@SxK=Ps+6vTD?j2K^szte?$LJ@lO zs`I7XgSg8*B*z<%S>lo{g#cPryYP5IRPEu4y(8UIH>Y0R=CB2l*e#~Qi5|PoQqh64 zzuc-{hRM)(F9mN1jj}HJd}s>+rF(xt(L2sgJY0Uj2Z;8%(0yg-$*RY@SnDf!S|8)d z6KN-#bCdvPtFnII4eu9v`IK}-&+SMa&nw2$!geE>s&x@t$0qOl&VCZ}oET1Lif?F{ zCGLoJT89eX5vzoBrtwsHZSOGzST*LnidhkOKEw3mw2Z{z{mR9;iXU~>ZrYN|Ovzt} zcy=-Ig=OPFqL2I6eb3a#hMEJ5K-zls=!n;(FI98$$!LiT5wJDmQWGIOes1ge$)vwp zee7d+{F`oQrDL^U_gG&k_)eWvoj@LYqxq!QeV1++FzdrdYEA%gG65;5?{1+701YL; z=&PBVDnd=E)3f2pQCrl&(N`>8jfJaS>Gzh}I29k)>Hoqgv%obw6TM_$!YIfS&hwam zlFLhc4xRQqJ%Ft)8HkKnofwlm&MZIW3+tko%Y@C_Wwp&9uM8AuTAvN96XU9uEmqFk z3?ls6o5nK6uGDtBfDsUYgNgpRBpF&8`2)vH?%2@IE4e$e?%6!KG>QJ9cLosI4ikJB@SSk1qC9>*kvUA6>~&FuwNF@wxaAowv|uqFs?% zu!*a$I=IO}^I?l~_0KU9?JD2GmFZA%)KjOkio+yR?}nP?h+9ELa}EK35>`SQ$X%3_ z%k1`l$;c4Xz)pW`h4h_>!w$XqC@t83BHujRnApB6;u#~oPq70{jy40@Oc)RMqaQ#v z4XH(VqDhDIoa$e62a%@WzZHK9rEzl)|8f#N=ZOc8iZ#!Pg1R+&$W9#n6B+gFUWqQm zfTod>kHK6W->XTwJa42P&VI(+--tcL0PbqA^#b!o`z5yL`-0kzMu&pIN38nC%N_&G zDq4eG5rMH&=lw^owHc0m!rM5c^Rx<&*g2Vjlg%Y8G7_}P2#n;+*^)>2GXnU{-eze1 z!P<{zkatc@q1S5qT-s1oD1|l}@M1tO)Ygo7{474+tj1{DVt?ivBHVVe_8YM3i`5MS zB|V19Mre$49uc&KU#D2+;?W z{S8>r64*&EZzv7&~zn95=U^@fasL0?4oj@3y;NCzjsn&*M67@L>qBGa)z~2E!kb7o1 z(l*Y?v}zQK);`>>CoYGQ*?|0n-JX9CY@52db|^2d{ho#~l@0l(Q=56$8EA>RRq4CZ zKt^fe2($_@OEIZubc>3~3Jb8*%A{5CoxSVmW)r&8*5!NR&Bl!Fzvq(Z>i@;IPr(3? z{Esm0`rsUe0IXvh-M-oBwXk8W4+kwXVxo3hA7HE)-WocA9k z*HL?|uSq0$q6|Zy%Hk=`dLTbd_oTLji#34Fm~)$Re3#z@seRPK=a{;XXVe}%&w*r6 z16t|c0*KuhReK>lHF4zHz>L|dYZ$4^ARMZ%p`AbP_}=D8CvcVyOeUACjE@XD%Y6^z zwa+E|_us!O@b?PrBezySvGi%>D;=CrbHE2j3x`u}Bvkv4BYs|rl`0W@xmouiOAq+T z^`B2M6KMhQ{?6}@z#qXoroRaGoj~fm>OLY!*Zp>hO{^aPPYQ;M>}9BJzPAf zSSga)E(%xI9%mO{H>u#a_;SdF(N?%s%hU3e&u!!(U3mM`+%q)X5U8k}cbS6T$G`Dd z9eKX-T)$qdah1W@dj@d-45wz+3zc8pwCwZqUDOfvk6D_YG{hy9zIu?YzMS>f-q~@2 z`%*_-y#n%3v-H7S2hMqKMB4l<0okFZeTF@($_7|)rasK6A_VZjC|J(7Luus~ z?qA5VQ6|~8Y)3PVw8KDw2;AH%1@z<5WMu+mc|ya;*a>JHHN7+{45M&T$ZZmT$1S~| z4z$-$QS6a z+o@Iu!u+O(ELloB=?(|N{me|#w`%Y8DZdDkoMA5hCQE}*;y#D|;v{ibARCg0W30AheTF-oV>mXgk2N03K+Ev0Gq}81 zmRY7UpHlUM@dHO2+XWzrML21mLUyW~TY6MVX?p8~fw~Xs@UAxv+5siO%sr?V$lbMY zJRVc0G$NjxcWcO*Pq_c#L?tZ@$W#~9p;*J8Euk5`XHGkVa*YrNE_F^?QRk~B?^f6W z{5(Yo3ENF*29VfWIhHw3WL)}alEn90!#t1`yT_mHZp=JN5Y!lru436WdqwM|-|Wo;M&TYat; zy3M|Vl+11IG0Zo_aSk~L`bCM?))XAnj30~yR#40pjL}zPi^3toPS~oJ%JdW<(gn&v zl$U~(!Bk7@DeRB5ZD=PV>ED(9pgqoW_c7nU-Wn$aU2D(w2O4>Dr_si@=HA^pSR3ex zv*f-J4^vVWJKbo4Ms`!4*#Wd{Bl2P62y!-7@-ZODKKFVr;_T2nSwPH0No2=#;yAi* zNf=1>${UMHx-GXHUCBOgrd9nYmcrQINVsYdRF z9GSl2k=yT@q>~@F=%?kJmO-(@E&ya@vyuMkyl=XK!t8Ctqt=PjI}wu>K)dMfOl`p_ zSA0->rP|nTsihq%s&7MSf*c?px`>o2zmpD28TtQ4)?3F# z-FEMyk1Zgeq?9~>pfH4hbSd3P3`36~AuUM9poF9#(%m%!4AL;bDAL_XOE*Zvz`(hE zfBU>=@3a5a`S87St!rItt?Ndgz5^jbn8aVEWqt0iUqt8~-QJXFk=w^Ono{FzXgu+KxZn2@y_z`5u3w93Fq}M zo_F#yU`1M7Li?!{bbb)KxmNq&reD!#*{`zZYR7>eJD&Hy2HY)Gj01037vw3QD1Z2s z?d2xmqUG_f(ighi!ZGB5H)QBT+p!avrPI_1l18I9fC||+Qy+b={@tJ6Ve_+|_|pktkuCugyu}Er zy1;ifrg}nu)-b2vm$+d+<{0Ae9$d}*Y28CkIKZ_%QOQ=_k~0A4hd|oeIG<+{;w6B%R^V%lez!L`vC+4p8Xix~g=GMQm-ztcotE{QpPTl{=g+GrSD5=H z^#^lOPZPNIP;V{?BmqfwD%b6OuhlOettmL;D7geWxpqPOX~8b?O6@_Qn>7801%I`@ zmIfosA6YaPCG`L4Wq-KO_js?r%Y<^|(NAO3u9a`pxw50frFWfz1g#ezYO@6QU1gdE zRw#OH-`tTl#S4F*`A7z--aQ;AkNyijp!3{bL|)e7RL~k64q(L z?>a=H7log+_Wt4>94X?YhO_s~^2>?6udg_{YWnvtPMq!?014opX_58CQ5L>AqP;g_ zF+vV%M&wSc|Ms3%y4A6{0lH`G#OuU-ai2@M<>ubMpsUaL{klpB6@7Bnn1MF9qy7Vq zA*kidUD85{2#hp9I|eBMAQ}h3i>=K(a4w-qY3(Yblw5G?x*m_3*W3;5vRj%Uh%ZLK zr^v<)@CRTj?|UctsTFK=NjqVQRo>2F6pOgk2M_AlTLx+O59WbcDgQP;YP0kJG*tAa zZ-M|ey@{?4;yMhvwktT>8wIn+fw5{Ye&}|HnxX!j_j_+s-T>eE4HX>TpKGLgKmY!d zFF&uQEY8dFSQhA;qyKZvCWz1En?aYKIGjSj#PL^P?TQ$$zX$FCkmUD!+6RI?R_VqLXT z8J#WKS&YaR{+R>KAzb*9RPUs)^yL@_g|{0`MrN47N)*^tuw>h|6atyrxZ%uVo$2XUQ)5)g(?|egY z{stz1s7egjQ%PB2OypLlKt758YvT^-inLU#9XN*)fS`b_)%A3?R{K!6gjZ`>gaKx8 z>l3i|C6{Qh`p)h%@;+*yLEAlJ>cR|FpX8HejB~9mY*a|H7Llz9t$Q@|=0y!Xy(H)o z8t$8yS%b*-CsnxHtT1eW1~>P|j$gVZkwU*dia9j&KI1<2>Hwrj`1?ta#OGIPo+6(u z^Xbr-8$>^J{B6NR6+JFPuMA+&^6QJC{31gsOP&8XA+bd>LB_3OYLG}xAM8=tY@zH1 zvrZEFbv`;q)40I#2Qz)O_QX@*^+ie9jSOF{O7rN5^2*a+TSgg#3PBgNRo8D3Ep6RR zZm_-wo0KJKJ}oSKh~mk;AC|za`=_X#{E7&a))Z(QjfwIQuz#AmN9^v`K6fwtQcT!T z0ge~s>6w){J4{nlcb7FGaboLixL(Y{fUA_qNeb8cpX&#b^EOc%>Y4>bx>hN*riYdGiy)EfF;?1{p=7lbrRCecO?oyz#T~gc&8N{h zW)dDr?FZTF{f5@xeyv>-Nd)q?YX++kJS*Lgo!dDzwn94Ee)PLPU%zlZ?mIu(R-N^b z6f3|?JC%yX^bV)xNFOzhIc}uJN_^w-9ukD1VO&eHWiul&##ZO`hGv)-vK7LDl**D6ntbgi?>z0Y`g94Ki+x)r%PT9$u6 z%w1F2h)US8tuM5~O|6`D1*AaxBO9caOF-v)DKVV68Y1*ULBl11k%Wbov*yAdsARku zmP-0!F$CJfV>G4ZIccAJ_fyct@R+ql5&%lx=$WO4qN%o^OJ`XhTW26*zo}Xz=6>a3<)Ock&s)7)D=c^i(2O}G)d0v(`wo}) z&5k*Ns*sq9Cp;`8a>q+<`Lj|pq5Q{y$6)NMIbmftyZPFN zxG2SrhMoT50)?ql>F~?rohS-Tpn?Kg_X;M(wS*sA(%uda&EHB&CtCf&(C{y7gUAY0 zjFg#c?IOQMG4cl1r6PPN7eJi;%Cqc|%f!9TKmZK5*Z%0d@zksb8Xfn>(`{Ok>_MpQ zg?&nSXJruW_O}`;t|+C!v-sP!z3gkNhwm*cjtf=KI2rsl`ZJ3RjlDJNU5%DT|B-;l z&SKK3gCo{%0}YfAU|S+D&s#j-v=7hAtp`M#&1WTXGqAg<{(4w3q{I?*%G@HZvi)&p zkJe+n;!5k~qZRczf$5fSzXu`&8q&964m?xClJigeF{^zV-KfK! zkC>vC?QSi8NiR^^pCWuK)KTqurfGXZOI6rKpUdYH(LqHgmylg|pxM$0x19l9o5Jsm zGDL+Iy%oidzVL zloWTYke1G$r(g!1tD?IwLjZ{M=x?>XPs`V~qGfLe85a^7&)mYf70>a4zE}y~v2c{d z;HK;`y>mVJaJ(doH>zAAa(NuPDWYH$n_lOKOscjAFKcnh^f@Hv5 z3bhu;&P`-3FSf>t?(w9U!Kx1JZG=@$HeXdPX_xk!bcV6E-vipaiG?=O(eVrS@P=_F7#9O=6q2jCn4{lxB=#Y1KJ-eHE}n^GAXU*_DfQ+u%1ZjZ#B+ zO0CB~fe4IcGTvM~rZ7D_qS+@m@!K0SK&{&t$4Y8eX8LGy$cG(ib~6ZTg%}gzEfj^|HPvyCX*y`e3agS`bbNE)Y712WMqAuMloC+CAE(=UVUEok4Xeu!R0*;!;;Fs z$-|zsn58GmMd=W^koU&T*e7%Js6kd{i<#~aP0pQ9@(P5!wqh69H1y}SJh${>vBR^- zKT?sle~jGQgthEK3`*TT@sWeZPS4MQ5}3b5%*S)M$2iliqc44yj+~>hfJ+6cM6d-v zpkE@!vZOQ&;($?gtxqi%>OOUo4Noblxm2R8JTw9~sxwTeLC%KQgs!7V>~B12^;G77 zwD11-!DItO+b8P*EE1KL)x#;ux0yPx$&`!{e-=R@B<(x9S?u@C96f#=2yp2BmABa#ivlU6 z5g>1ak(x~#fq{R^x~fF%zcJD2Ph^+%=2lh$Zbw^8NPq3vt^F^Dxj_qk#NJY@_V<@= z<*Xej+5YyEv-sl7PMu=*oWXYQfsn1>)N(skbC)S059rCip>(WVvd{>GA4L%4Am5 znAQ98KF2CJmGExrUb>NMRI>S^ef2_i{W&IW1Ss(URgu{Ik0YH?y$=>0K`rO43@@YY z;$KTaR8?y3{-_jDPgWaXvqr5q^4xUo%K1ItAO$bSgX@3ur4(nsuGZ z3ezP#a{>8NZ-APpK!G?XJgRrBPw^Xzr%a9eA+Oem%@Wg`RhCz)$s=}YKZzm0y%#8( zpJtn=6-^b#`_y;qgChG*4f^CtYgOR`L%=*EMnAdT_V0_l)#s^XJ(^2CNdE^eTv5XyNyJV59$;b+Ps%xXW+gWF3! zZiJ@1W-aOZ=z+yZ86df#s%gTvSbFygV zjTX07IqC1qzEvq6mLtu)Z`yh?r2NupEqjyWI^AiVmTT+%l(@>>w^AB`;8k~Fki{&= z+P4>JH*^EfD|_OHKotbg2wHGq@|pn*0l&>`Oz#0oSc}3-#1D-1-P?R%-&qX#jVyned1~pW^tGW>Q7o>nNqAO8EI*MbHZR3}&W& zrtoY)u#Vpc6kXh4`-)%ZcBlI^;fXd=y3hD_RyNJfE;z(b5aNl*6@5|p=dXJ^BgRQ6 zdAIyIKpjm&sOtk2hNH?Wdf6}vu2Ky??A~_giB8v)m_w9>QN$K+W=dj9nxG_7CGLJf zEy$21W#naev~G9Sj4IAdIRq7BX>sY=<0B2lWK3;$_l_DxixuR#e2N3S3NSS@=^ZZr zhT$MjTu2BSU~7^^ol2G)+zqYBvJ>P@h_(cNwcDVwHFQGm z72fx%O?7FEcah1fg`V%fRFAOJFX ztKVL(l&2g=rl2e5A6(};5Y9v1yKZ;19;v#K&JP)`es zh@h~dqtt%lb28)3?2U2TE}!fGk=F8W?zjdcELobp?E)<-L$@r!u=AD!t0g9GW=mJ3 zr^GpgP?NQ%?ESPdw@bIgKa|Dw7ttBnuHC9%2P;rM}L{Kdl`}cu=2CqtN z=7%4HLKB<6g!C~!a)hw%S~ly9qn_VpJJ3?=LpkmGBvW8TdvHuMXXCU6is15zxPoH1%E=Ls_^(BD z0ErDg1JAhox`VGIaqSPs8%83q4YG7QMtiu$ObbD`%x%9Z5<;X9V z?Z>Mfy?tfevu2zj;S|a6&7*8Iaut*|6BS_Yu(Yaevb46N6bhefwUm6;v#C;&!XL&= z2Cv_6-<`$dPv#UN9-2<{{XI5cMV&q-59S%z*%eO`sI2y-i7|WcVy~Jkv=z9no?%#v z#eq72q|}pd{fdy$&lfykOKei9Z(^XL^SbP>stSFY2W}63B@8a!J!-%Y>zr+JeH#QJ zf}#D_x5(7`E@b|W$#Fyx#Hw*&vl>W@Y!a<3@Oc2fPN~^yw!?dPv+pe(PmW~vk(zV}zNl!P=E9w_QGx&ASwtrpy*VQa{O=!op`&irv=FQS-x_M`7YbK^bEF84L zw*$2|0MKAQ9dBIsT`va9uD_g7N*x9IM&5o>cVh?#lJP*e=agV`CEK>A$h{GcA1i4p-uH&=|iQ9_p9mr2S{B=N+ai)E&W1z+Meq5Qy) zmPo(GHj36DHz5iLbAfQPexKmWf^FF4xoi?LkD~XXs7v8wMa@0d$B$JLpZ(b1YfAj) zDzw;e+n&q=kdoi)aBa46I4<4bRPez8#ttuFJ6rr8xty8Ehk)x@kl7!72f>d28K2oU%85w( zkw=}U&{V0V=53j57zY~25b;br5uUlBS$??1LP3eo`e`yVKa!Z8mv&Jomd{Ew{dIK2XG_7jQuDgGhCacH{(`hpwLS^JQ^ zt%RZIt-PeWOlWzSr9zmigG1Lw&{TP*=Y;6%B6w$eps0;E~%_H*s^`Ta}{i>Q?1DpN(J79(o63%Z^_NZ`V+dNhpV>p}U4S@iLAkw%8F zo_amh5N`uzb3z?!z;zr4g=hHPaFGa*?a1sFHA$v$tOMF&BSkfibGqI`@j(K%u?eSu zSgm#b;fAEB0QHidHwbH2ZJo<~R_AOAn-uwCzED$ma(U}UH_ogIV(flY&p#@){TO!p z`Msx8px4$wa)w4eOas;AqvJ_#+vAFv9ZH$0eVpc2k-0PDUwsO0wXg56aW0N@PvY#H z7nZLcK@nzqK2c$nA?zd#Q1EsF9AQ1qJwrs|b#INziIpx~{lF5hoI1?OnDwUC@Ez)y zt=Upjq^0Cgxn_Y8axxRQD=BQ-lb(I8wVjrhExEuej4x#@?fG*p(=FtDX#-1V*)t5H zK4?M?M<<$|WsU^@uWlT4;}acbe_SP@9@LB(>njQKwE9&(o@w4{_nk>k%W`w(Blgsc zS*8`FmNNYp8fFaivOI4*D&5vKi=UU*bOpN--FRPS4ssFC=MBPNcm3m*PyT*7+z4N{ ze8UN@WJY$~I?9?_!$VcKuA>pqow3?1amNkj^S4&LN}QFqcfB4Cn`IjEGr+27eYFLT zKB*8?xIq}*uda^!ZZz2-@r$NVb4LStp`2KXn>Q!C9SK9ih$!D>g@pOrnRm&X6Lx(n zS5Z;1o(Q^bLE65FL#;t%o*q~%`F*XdQ`)iLRaAF=e}0Zgk75i=NlksyDYAODoVx0A z(TZ}F^3)qY_Nm=iXiaGTXFS^U4EaVy2Sd!}u;;a>TxqCDA9{dIX8*k z@UfNobkFn8?~fDn$ts+N)vAbPGy>;us$Hg7@$O=>7mkN#{5~VB<{gxZ+*twB|`5R*sjx`wzUM}O?E|kUe}!Zf?pVS^u%tq%5;;j zq3d!APYE9+>hK*k00&xL&!;KO0h}8y+$G9r@WP*W`?@b{5kL>Gbw~=!fw+STcRq=1MZ|e%?>U z_C0Z%<_VkX#?wst@#kB5R|6%oZ`uwD>VAqL4B%Dji5J(E8z2o8`C{!!o6?J;7=n8# z_`X{?pP*)K=WS_p9X$W~F`!d9)(5d%#2y(BkL$UkQrnzvO`#M>Dd#7*|{N)&%yq+J#7;1}ZFE{Tfa;R2&< z)jYlzsn;ZKS(6Vv+KTgg%wf`S`t41758}bh@0=(}EeTgKzlpam44-hWYIa|v6SR9a z#kjNAM(0?F{BpXgOWl|N8ifyx3}DMGTRz3lD1V&i)D?fvT_9tNa(22phc~!x&6`ZB z5qlcwkXLkC=ooE{YCoWe|Bl9WqKs$;3bg}R4ilQAU%lN{YHhKT*AskuZBNx4~=v*WXlY80}9vAi~nZis;(%yeln`7`H)(R|c8U0wXJ`yR0G| zj|FYd&2Jbfc!jMAQIc02j4sJB?NXz2@1Pg>z<`J+Fxs~nynkzHM0X!SwD2w6hIDQ^ zJ)$Ly+Jn-Bs*a}YM%#HCz3i}Dgq*-$PU45RU-&YKXthSfV!kikA9igd)0~F}k?^l< zYP_|gXPvE>dyvxa85dyU61=QlHvK3bWBdJ(BYvvcS!(rZ!i+hiQHcm5ml-azDcNi7 z=l-uo6+ZG84GoQD@Y@}yi(0QyF6&_H;GatuhFEwC`h)b3x9{0{I0z#~rUj%P7Vn$v z$LS$w#abDr@#EFp=%>2UqlSh*h=zlfx9SUYPfIHkAoc70XpvQFnR zj*_MZH6$LteIy%Y7@dEI)UrN=!4!)yGRr>nzO zv&LfkSBB64-P<&ZOO!JMcIq_=!PIWqv`D<|cjtTEd1ccQHeI7?@!sE^4}$!0-=?eR ztxRV^l|!L+_h7j?^OV8sgaxa$Id&|q!%f!1Y-GmC4B2&`H?FOTlo4&Ged>6sUgcyG zH^}NegSN(rts%mx^Y&*ec8{|~l;0J;W@0yEDmO2$$mZKq3zBX36MXPv{somQRalUx%I_x;MZYc=MUlHi1fGHut<6-)HEdDq>05b)ayjZpM9$-`?Ni5Kk9c12c9YCkDv*4f+tv-(!W zZ?%>M{aMRrisSgUA34`*;}-^--A0i;0px52OzlAEFgo3=+uhb^o!z+(A@}G*-Z^D= z>KUe*3(%xLyv=}^uVHq}(Ra8$yzH7(YZD8Z?Y75_hPI!#`-w>-G6nX(|Dws5 zZQ*_<;+3(mBfZqqO@d(Oi8T6Pp-aN?dD?|?3ZbX6o5OJ>VQ?0g)qFi%?X3Gb6EWiD zvb@{-#r#2X_14CNrPjF15zO$6l~w4)@qNz#%F>*AilAEV?H?v*ft~)wK@p~7b~{@_ z@XQT~A#GJ7i7sldFj>UaEy~i|z0O`-+^?;Mt5(DP8(hfm_gOTRK3nH4yH04l-+!`B zq9eI`=q&)b?a~;OE2`mG{1n}d8}43Thk?CTtA838j^Fz+y=`}hx)(3Tbyr#7rdp_; z+Pt5;c4Clg`0|QHC@dTLx{g|Zr-q)jSfL86LcJ4xV`5|_3^;~|RFuq=HRhdU6+ij< zmDJVaF_AW$swTgdgQ+^gAv3N02HkT@W7}SzzmSG?qGU9HTs$c<88aKz>+Wwwg-?X` z3eR|RMcQcadgApwof3H9g6F*lscoO_33TOo=+uwWSS92(n|hZ^Rg{!?r>>>H-xy){ zG-fMdAjWr<7R@JkphzmsIU6AF)up2^!|Lf9H6T-uM7Tuix?Eq}3=cc3&97Q?v5nY0Fx3IHPaZkt6SHmHnizf8)ym zyMX-u<=eG7f&B$Sw{O4pg;H4mq{xahsTk@>*3wJtM(4ji_Zvfb6L!~gKaOY5P7G?# zt^cyeBeV(gnYq##pE>;9{ATl~TotD9mm1Fzck6P^F%Ub0qYQMg%=wdTP6w^f!=~{s zg}L33u&rY6>N}PwI6i7Q|C^ChQobyw)~lqiM=@nB76BXp!kKZ7XDpYRW54`KH#?q; zD2Z7Un0!2YW*5pFy$_COFG;uf6DFpYCe+A^>CS_T?LoQyg$v|TRViXLA>8Jnp}tAl zZmo-QDexz%xLr5sd#kM66Al&$x$|900)Nt(Ni}qVa5sZy-Og%;-+#8@HnCl3C|fbT zlIs}bI=dI~4u?B~HE)mG2eu{GBr5(Nap}x8>oAgRvhWR<|JVDOdam2E$7>EWQVENG zZn)9gs14XfIqh;2@!nNU=!(KmF7OZ7o@JwxrnjHOTomb z^MnfpXXVYKd@t2mwsDgcn@1F&HL9+9D_KuB@WBXHi*ag0tInoXWuyUk_+M~Aq&MMa zy@L=>VzSn3I_`tjrv?Q)ek20=Oq~KRWm12?fVeHK~#HcXuK)v?3mzeUu z@;qnh%2=G48O@Hm;6hxYLfV5BqayRXyYsv5>)*co$$woz9|{}dEY9Y17f8|9I~hUo z2*C~H)@9~txLvoxH1hmyMv)e7FY*b%-@W>2wf?z$>j;&8!WqA&;gDP9X_;a+_4Tiv zU6Sy|Bk`nA&Kd2>#>~^99|IRX$RYH?;$kmi`#IA%cvaT>;b>C5Gu~!@snc@Y1cEH! zUb(1I_YN~Eh2JWz>ao-!$szNT^XRHvn^>Yx)15TI?q^txwgxn?KKr=2#mHnZwS_!H zk;Ow_^D&CMa`3S2)I3xU(27GWk2iPm`KKWS%r|~ljI2Bajmiy^cA7u*|Ckh!QqIyz zUim8XS8gAnx=E09_uq-Iwe`P4 z&>ctXz(os>DX3#HZ+0iEy0^yF$vyX%qG1XH=(oKitfiX=Zlpz=bHivsX{?rzK(Jya zmsHp|x|nWY+w#KdY3e|wp^UN`F-^$>((dl^21J!S{%#&7Ub^=Nrh@@J+7eIw02jCE zmM(@HG@8=Vz^;sqp_lkW?(VA6D7wC?H_u?cR^>{W_)5VRA(edci1kQg+UJ!?{OA$H zuT|YkwQ)>CWSiZ&0wFisb&Ulc%V@EF25StGd-p)wj=gMODmr86)yFwzE0kF{J*poY zhUk%Od>I{_`V}GPp9XDDh$ZEILiya)$NCO#)Hgo7TeD%vf+?%k?p-85xbO>{Cw$#_ zAxJOAye3C0MtZ3<=lPW~{Zw~MO>l5<3d0xcg|^RhH|~(cA`hC&nqof@Ht$Gda_&IN zlaI^yFHn;7Q`>X2m;F-gP^;g11~^KQ?Mx?hm+|u3D5fd$fJURI%9C9!l^K5z&2W8fF zC~tOk!rEs)e>!vg(}2uY<(9tJEqbhtz^6`-(Y@G-eR+60@4U3#-4hzE+*(hlntvk5 zcV=GnRx-Z`-gxLFRksPg9V#JbsxJk{+8eLIM1FZf+nO&_+vkdtKN?jw8R;}uRnK!v z;KQE3ZJgWSb~iZ)_z_RCb~<7EM20fzJ_9~1qFfT5)A6O&=OZ@YC}5!nrATd_7gDUR zx5NFIB7wO6sYxi+zZZIWaTaCce%j{hnYr-2Ycoo%_p-0uxSa@=-l8XwSIf0<19n~t zvAn-b+Vci%eM|bX%`MWZymti!@FMRpan*J>XX+-}eI=5TurA$1WA9>McHf_~G2qes z&J1RMV*q$04PMuu!k+pn3ETphV}m-U#ZL|L1L!R^r=A!e_19pdq}3A_9H%&BHL%PD z_P@S0O?zI08wdwz7e0HE)xcaQzy+o$kiq$ogQxIN(N@LpLe~ogTvskUy3KC_A-9k& z-EUxoQ_+3Q1V%LxPU_jEk11iHO7B^!@qJx@g0rXZKH27Hnx-jbFu522_s9{s^sTtTR}w*MG4)SE zIf9bMJ#EzLerT`ppL4b~81Wn5tO5_O#+SHJmGYTy3Q>>jkBJK63~L-Z@$yh?a+9@x zc0rlsI2xWXeis+l*h#vVVY;m+tfQx`ZB*SV1jQpQT(pZa$#(UGcYX6W-T0Ra*#C15 zQ<9^@?LxixrCI}b`LVcU&gh~Bt$9%|T%64Yt<;^i1-@qTZTrzIup~mSDvs}EbM7uD zY;!p#BA|$Ro(RJGN}cV^Wabh!=a69X-T-zMgtNuEqtdl z&o?6)Ay&?DYBI?VUQ=(kg;DzW^Kl1!rY8<6FaK%cT9R(h5PCA5G#Ig*UE~#Z+mOSc zv5sE_OUr8(IKh*un+dnRhKtjVVbnq(HHTjp=BWv)U^yfcrC%icPF`OYjX6a5XzH3= zdy))9JLxU(Va}i4lOLg!=U$DW*Pa9mfgcM^{7U!tr^|vXc|-w|(~|Da;)$W2I3i?R zsga}{+eGxHgXE0V$`!Qz>ClrS$imaXkBvK{xztYH2?$nYy%vKqBZ7n@Rwt%;2P+~1 zM?$Og%_a{4L&aId@Q*xc?Eb^K0W@EVaLtyw`5{Y5Z<^~7q>1R=N=7k>^wkOpF0TCr zcIf&cq<~GT)_AlrtTX{M`JD_nFi=ueN@goUaqa?W+_IYbOkxelPq z|J=rhrIt)#1tv{&c6GQl9o7B`Wj4$p+(cA-;(i&nyAH$GPHM4hjCGD5Dc{Vika|O3!^TkUAltf1o*BuJ}ej6pIffXaY0x6^f>SV)i4ff~;kmwpfrzT(A-@wJ){}GBK%WD{J%7L@v z`t9~?i&E%(Rk6%9ESyP2wXsFDi5g^b>Dz>G-j^w-<%fP9zUo-Q)2~HkUkDOY^uw;~ z=KZieKI(@JYE8gU?2azxzsoA#C$la}#{K83rz z{s>l6Fv9CU5k}jhkQ+t>T6Z54h9g>RV3t1M+YWYa|LW~LB@>ZUQFwYFk5@KO6#lTR zTvseGO0kq5YxlVL4yVv?jzxeFeQ$$5B98`aZ@Ldh281{*OdY#wf=5oS<{2Go)k)sU zFqmh=EB2e851_?wYXucnob0V5QD@*cywV|YxEZ_2K>?!p2^2j1bZCBoI{&_0-UyR88BySJ9JRrv2!rU|vs*8)`}fvh z1|Ja1BTK=rM$lxZozM0?t^xbGLm1jC{(kluneF5S8;aF7!mzl2sUX{Eya*h;s9Y-nAlj?VrSkhS8;4;aP zqayfAtB4VnBI`MHcD_$u3B;vgciH0ZP_9R7%DmB8P{Bb@=mf6MaWGR29@)swI(xQE z<$AwtY}Ch+u@>8M3U#Cf1q$3;XXm!1iZ7J({BmIP>6r!O&i^cwDwboWcUL5<VJJv)V|c5!b*8x$)l<5_<^Yy{6fkr}~4tx;zuUtV2W= zKh`bIAL{>8I}qwfqSh|$D-Apy$DYYbQpVbMo}c$rsL1ZK^1tL&-K$CR5ePaz(zVp$ z^zAyL#49IzQvP9;GW?3=0gES}{ymep+Nyl%8Jlc@k1QBZbb-p{j*ec3x5mhgR(qvz zZ0Bj!!d1~bsmsq}TvlmO8?Y8{j!gLAPflPvn4EQK)>EPK30tmMOSgh;+k&L$V)u(# z3lG7`Of7RV%k;PmvCi|Bvdmj}p6~1S)&!ssvN1+Zp@B4gy<$72`5W=)&Axj!rI|*{ zoF*@^L!YA7VR@ps_4H!)V76wVLrAcw3FEv~OS*ySdEey4m;C>0b)FwWB+JkJN#}H4 zV7rQ_yzN5a{(@R_t(+CF7zfZ~iQlUt;I@~Q8pmXT{<5vuPrHk9T+!c;2GA5d>YXS2 z{hb=Ppu={Z`Fo?NYS_=Xx zuq?RdPg%UOsOi9$?B?5A>WI08w;?VeZGT!44j~J{Y)VNu=+Cscj2KquC`6$Z$HO9F zG>}Xs*sL%6GH_(1C!kmQw432|9qXgs24}gyc7qt)i)1mzF|@fFxuLiIO5iM&f^;T4 ziRci*^#bc`voCWvjwbWi>tY%}JC_E*Q+oJiu+BMzp{m~-dWPfaSJo;CE=u1qd!p7( zZW_R`gc{Ll{Ih!^KffmVr}6p4V6!A=4{qUYNVQLIO<6MtW-Wb=KSYUBx~BLqdjp(X za!AM@^f3R$3S$=7Tnc>HO$Vl*c6&Nf)CzNfmraDVkWi)(7GC6BZ`@6xL^#?xzVp>#(0y#X!XM$vVyn@Gxd+i3B>{+KuxtOzj|UZ*5^eMimgm3_DRCSZh!*EQ**3w)3 z>rnlaPb`fcy78a8G&hFE-94M4YqLa9vA<$<#O$J8VEL&3JBzi{D~{;?EVTs^YZiV) z5*gq^xIPkuR>|2@2HOAT;IiUCTkelr5I$eMdU2WHo;J&&yEBO9KzDiF#jNR6g54<< zXHpUzUl$JO*vG{|Mpc(2*miu^$G;NHlHirCLw(Fd*)g=%%N`QXz=?m5SPFMNtwVBu zjm3=rz5%Wrbii9KFGj^`#kItbnS+#@`O^2C@rYnK_tl^>F0S&+ANw>avM}(DSDhct z0J?Ji4Gy|qis*N$YW;lR{H&3d5I~KP8{5XRm*q39e{d;~NnC6Oa$`+&4VHfGzqo4V zpyuFfr1K5oHJJKhEn&FbIDE@Be|nX~!C`_xb=mRI@RF^b`(zEKZ=gND3a*`IyE2?# zb+*U(XtI$P27GJ-=EA@--q7^gF{94;6Az-RZ!)>B=6d9c2hb5eDDX&YAeZE!v92!VM}JQnE_gY)!brb&Ve~l@ ze*=ae4S;)FI{@dD`+>8WN z(I-USwf}D`u~(VC^mp(atD73;9Nez!mn-P*g2}$rU-{vi#b|g?i)A{$hV>du)KHI4 z+e_9mD^F^8x3ca9mYGW?D5%;QzxoGVXEJ+m4Ic)FrC0fWfUB^XTdnwhL|ELry$SOb zu3kh#^Rxw&?~VwG#cXPZ0y^W&i-zqzP5uIWS*b0g|0wJS7f{qE8{>-i{aziTg>Vecjdr>TXnIO+TGmC3-zw4Q1BB*#pN(<$Z$pAw;@v z_7=wE^NUN!`~P$F@d(O(3WnF1OJ<%uKNbrNp~?#R{phL31&4~hJIUa(c%rP*`3o+j z2hh{5cI68{24G5m?D5L1CUNIRy8_s;adfkGqW=t^hM{vI5C8#3>+~pKFzQ!5imxh% z&D%c{?)m!h^O)`rYF`5sQ>PTZ++k(8RdtmWc9@dS{<8k23)L9f;jVUBX~lFmC#;oW z4@uF@MG<-Au|jzW={*GzX#@0Q>6NU@WsU(!ezHzcUflsP9(j;4I%UmX_(X2z7X+HwJD zj1afGr=l>2FCwN6D}*FTM6)kp*@QiDIJQ|D0+Hmv;u z!oSChN9tHfdG}7t9@^~q$Y7a;q@Tqlv~ccd;%f~+9$Q7q!Cf<$?hXjX{?Ccz!3j(j zSOf;?mC5;p!gP%CW!_nN;yFUuFQmtmh*+-T9&yKU(U}w8rGsy9m;~WU5v~0V25gYx zo7&d5f+S?PO5m(~m;4p8EvpQi%0b|IkvS<^xz884tz5VDDVwo45 zHn9FhounTT%k`QgXrp&;{s`uG5LMgg8UVAJSYOS*nflA`RmEQSZ3_q#!kxDtc%F-B z4Tpsde1yv>c6yJEqL&CZIk;D-$;hZXxqQ$iysK5i<``o8su`9w7<2v_Y`?f3FnTqSCfC0$E=~;j%l&o$c`9^ zvz8A*8ROffFR@>rnPABlYyUi8%_Xn*s9saB8)^AU{xYaKp)uL)OP-D+3y z)SRGka{ZYmSXDOOm`tlula9!-K74PoV41)0muKjSVW3`mtn`aIZc#eSOm31bQP+e`|QHLl? zQxGfs1MpU_G4RRRj4!eKg#YJ32a!x|kDq*eZ%?OGE~lri{olP<4nxQx*|VlB-!)~! z_t@L9P@TZp9u&~MlEX6dIR5r(^Q+#zSY{)-&OS=9r#pteFyMXk$`l~iG+sIE23H8j z5W$T(?#X`Z%)LZnA5@5o;(^Q$+VN+9vr}0vn=z({2cE6u^?U@|`V(d{&Y5kAeZZC1 z+PzW$8 zZ?*~Er+{d$+nRXy_i^}jQ|u%_7ak*M*sRz5Yc>wP%Yc)lf@Ri}UzI|stQOa>{Tuu% z@S=7TYWN$z7|u8OlNscvS~g018l1r!T*iaw4pOh_FbY!kNnyXk8)_llN{{Qb*gW-CJNN4~0x1F2$ zmJh_Sc@RziK$Q*=qKdrgFz_3Sv z=Ma~h7d4sXeN2}gbc8Z4H`cBAR%U+5RW0#ZX~MB{-F=}`p>i_B2zVN6q z`2b9D!kqEYwaLgBajir>&?e$37ZM(AP;>R8I;8yUCG1I-oK&H{tB>WwheuEmu8$Q0 zho}@z_ZfWPcB?*jORv6O=uXdn@4pzpUmaH~uO)V6Kl!{x<#SJ!{N?xl4_okd*)hs0 ztRZzdn_G3jGA|*pW%6+>L`?EP$dl#t^ZcRGTRT_0a-lQ;A)BO$=tNUFWdfuArIO?` z^iqF)Nkc^wLXIBlpgbl++0*YzFo&8YRky%R^MRG}X+CzeD>%yN4BT~J5p9%x2uv5Z zaP_au9x@2`peHNaA%p-tendujT+2W+aHX`~v(->uqVeg7yqcQg^F{GWYWcDkbu9eD zLF*@OSyrl1v?$(T$`}Qr_Dw^fY7uJgN6`9kILN(v(SJ3R!FIi_^P8qXcSKQFOuvYS zsMs?x9YToz6DOQ~LO^Xb&kBQFJ&~Fd;5!5O-tdT#~L}jWd7%>{!bJO0H(v? zeoc&`TJ~#Lev8wvhAX?z+1XtUK1NSDd?3Hfui_2HeZ}U$= zDCs&dD!G6Y>rh{VMLPPFcKM@wY<49>#`lp2Y7^E>lu{6}UCAodG3dJctabKx zhL*36!WxWL9e9ow5U}5uIsGTgqx)k2fZ9uRF2>aZq9ZgHQ+3dFRW#Z;u-1;d>7qM= z_Uf)4LkfP2-82ANrp~qU_-pJ^pye{EBq5 z>^Kz}2tEE|lhPd5VBA_(79UI`DE=>)H+F$C_DR&WEMCxEDQR9Jp0_}WUkk%pDc@CD zI#ls9_rxoEv~_&$F#Or)Z5oC=>mNJPtf@y-1ye((HzGEKUah!T*4reJmx}EcNdP=F`zs1I*%ix^_ ze3)NJQ?F0BXNk!i$QVGWOtRreeUrFUy~BWHiCy{QhIT`=ck}{$gKkqFhW*+w|Ks|1 z$?~67MjsM3U^M=>M}wBbYFSKrE$a;V_LLDwsvVxk(`k#+(tkG{_?ELv+Q%kG)#Zph zo2pbA%5mUb|2NteMDrm$oY6wG3(!K+b(kQ0dnKs&3xj^OameD^|A)P|jEgF2-$p?M z6a*9$1f)wqKtMW0>Fx&U?vff*Ny1R#gQKVCbjsc~IA!Nt_2HvgDbKduSJ73TF zo&T4ay=Tv^75BQ=eO=eR6hb76fz*D@Du*uHoKNMpE1FtCEXS$H(m67F7tAoz9OB~P zWjlxkVwg!gr3)bl2eJGBrWH(MU?3*6Rq}VN{NrOm@_MeX3Sl^cN0ax5V5Dov6AjM^ zZ{fT${5yF5qP|Qu{D+%NnF0>(|MAp;_9wd#qy$ZR@!BE9WZMcry3Ti=SO9BJh&KU? zhP0jsnLrD-f!VR4G}IEXLsl#0<~H@qx@pvFH;vz^efjHj$}q{BW>y_q+(2F=_bnj0 z|F<7}0F{(<01=%AU`X-D``_`nzm_awSHHh~T>q34=K+A+sMDNqx~u^IEciS40CP#e zg^lyi1*I~)SU~oAhQlA#*%A!v60fCtqe2X8pNyn>+c6Z-jj)IqDF8D?9KiK`v+Jw5@L5kSHGmXKBF9is>!0Nicb9sSpb+#$ADUF$Ef}Spcw#c zm~FsuSrD1+6A+bIVvgIbTa45lIe!c|^)8Fe=%WF;G0^rai$ajk#^9sB5d6OhwnBkD zrp=#Ek^^w#IDUrdzkESMoLyqh8;-{G+1uy|^^Z=~0mQyDm8Sq4SXgIx6;33e4h?8z zBZDlUBydkSACUla-z&Nrt{#ATv|?neBclu51(*~aNUHtDvpoUNRpb0u3ec=(+h)jD z#Uf=7zoO|iSio!cXm9_m)K0)nBG==b^R&=?G^|A7E2xvVap?x2yH4ud$rL*E?_U2y zcPaVhDv50{9BMPmrw|H<%fPa}f9N>if-^Q>t)L|Orl* zan=#*VjOoPJW!4OaWqc8`z@a9{Y3t;U(FY?cgw#CkjE>X{n0L929Wny(V6tl@sNnV z)G+wvGYgCVY5hY2;8L zdIHeCS5;#udHhV*d!+#{rHiiVR7h7xT*mwFk>EgYO#}f;$){5F=j+l?@Pwz>-LCRd z{v*dUu@Stck?_;3rtmOb!&=FaT-y#YU%U1vJ}+b$Fq*KsK@qX($iEH3R@dK zK0Z7W+84T!l^~ysvjbNg(P203x;SLo9SuJtBv(h{o7qPD}o`p1>k-8p3}5Aw=etLG*#CC(I?&#$rSz1X&~9G zES9USAwrI$?v~T4`nQsPInR`UNZ@wb;q0_)zY;E5pzS~Sq$NtI8669deHOu8b8DiI zpK_q=7d7Ahti)KLW}5%LA&WVVY&m3cc!ew=x@Tgk5(hKv08g gg2U37M&;PW{9G zSuL{STH2vJ!BvQ@0Ep576GT$NB?*~MAQINg?LGj8t|)TYpKUZQ2g|cBA2uyVJY#v` zvG6Ve??@@B{>HjPs7bwzg_~M@T>--ATL%zQsZBpCiSwhx)eTNFbu@BCeMX4~Up07b zt97h`p|epi4F=PvE6E1NW+o?A$E#xTvCV>~R*ye*3eT&qZdCx&_nIe~MZLfNc8M$JH--nEz-W?$H)tQnIpQmXC~7oj>{wSp_ATG92f;WdZMEvP>67P%% zwnS|T+BPL5TyLIK2N@dmaw`5e8sA)$2^jI$k!z^{Ad2bqx=2-*%xNb~);39mR=c&@iqDLHg{@g?e;IUHZ9|pX$>zF37 z?PB7oU1qLQyCzh&REpPVKNyrS`cdg^p#gTEaQ64;TWzl28m$Uxyq{chN9kEtx{8ZN z!1J^8*yy~%xR$_Ztnh>(=Ie$&2d{KMuAqA<{0Z?roZRWLxuID)ju2USt2oEp{ieB5 zfJ-}jtX2%+rXH{Tdaj%MmZc^@D4C*-hx>Eqz5;aEm4N}hy0Dl=B$`q`xL{`6ac4M3 z|FkLJpJfpz93;SHdrc{7{tSkT*==H>8;Z%63h7?p&D;hSe32xBU5&GJ*K-1k6;bE| zEd=RGz4(_+T4jI0?G89514#W47i|H*w7jlaVFr!K8+51?kgGfGq_*4CE)onqH;}VIJF22!wRuysWfZ{@K z2a+=_N5{QW&VE?a9BIZ^qG<5m1xerGBtXhX6_bAv4jd~49BnBI+41x!{v9GEMW!== zIfhzO3bxjL#Oh355kr1NdHTx3#6`&TI3id9fJZqX{_PP_ zE60{+wOH5ntFa2F)n|s!dAJ4G!#a~=|B_~S8~5+^*@U)x1#adx%rJFo?;)D8DxGhWAqI`2ov%wULz{p7Ha5FzF`x*96QkR zun7>KoXEP=1tbq1Vtgm-Yo3R>WvzGOfTjeOI8+VMHp`J+5?!Hf`jP~g)^ZRcquq%2 zRl~t`6+kF^!eyjWIk;o$J+@NQ* zJa9XY+9rbZ-*0q%RgK243(_zI>Z4|-;7cz+nLSO+lfeXMDEe4P$hywf!NL*Gl+VUf z=?t7S{W<%+H?peKxobRox{;5INUt{AHZRGW3z2(lo-Z>E{T+y&s1d}*L)rzo2$sRm zGN-I7jSa;}NwYbuP3AndQkDLJHTRq9J$CDRL*6DaNcRw7`pug<~E z$A^lf1HBaEI}W3j2q-s^cl*pjg;3F5dLW?KYP|@Sd}a)e$sZ0pIjQn4C^INsX3fqu zACGRDQy?=1qD>rgB_>fNi`vXL6$WLioqldj=920c2c3rm*V~* zZgDP|!pL9akyD>H^F!8i@bJ^VUc?cnyjE=Z6q+O)U+2p8VLN_YWBZ7mg-c1j^Us1X zA7zE@yyM2#ztfRv`U(04Q!y?)o&y!&xL1pB>>xFsH2331<0h8Z7h9YvIescXV5Fq% z8H>-a+W~b`!o*eNO#?r}>}~%c&w{!_kR{tIiV+ zs39L@T(GSQru+o*D2VwTWMIg_184iu&+GwKy2+ZFMzDqKq0io`9$ecXA(r~dkf_nA ztM5?Dzp(OfBD0xlJ8Rn=B8px6D1P^dDoJ`ofscZ z=&RZ8IvdBXV`ZTUb%d97?4}v2V3e&nf{Q@AW|)(3!e!pdnpX}*X6O@t-HutUTZLmS z|I|f5A@lRITtDrUkg7(iF~TXPBBVDkRS(ECC*WfM{;6Fp-rDYL)3-gyXH_$&{Ww^6 zA3T<>*8$Vs@WgQBriV=7#RbdyyuYI3YLXD`7FP_XR zdl0GYcZ$4@Og(e|OLRG%P_8(ToN?|qoj{^Q&zYRHdtYRI#=BdxEAg6JpvJv+R+& z>Nw8sbm_FC=Qa0xyV5_E?6Vmirje7*=@k`%eI@TC0O|W*+^zKJ0?oqbqt$tQjtNU(RQL9b9|IE)Cw#bX} z#2BF{tTNq&Ki>b#V6JjE-K>1FhLm_*wlGqvY;y~HljucyO9(r?$( zF}BQ=!(38*qCF0b+72CyZlMDtUu(X7_KgxN-~jh_>ILk6KIh=p4m^ajhr({;J!+cm zDcYOuR`*$W+Ws7?3=(smK7LL8RB96+d{F17)&ZXCyRXn;0%epEbLW5U(+;HJJKHRT zNWJ`BMj~N5;;qe7A`%oRv`=pAZMq9**5yDk#-jW>X)W&Gk1??Z)NmYgeHf}Yfr_7Z zz*Zz@#MzkI%9CS^*U-Vi?slXWGOVh_Lbj+XUncz<#Xu8&5z3@k5AYuUh~>1NYnaw z*#H96DxXB2hUsmAaVOD3i?BS)c=0jZ0+*#${W>#&D^u?$sjFLTK^7lY-_Fqlwx6$~ z539I;?JYFfcJ>nKP(Yvi#~~$Txu@$CoYJn}7Xm=max2I2)S12#f7lzUhaI@rPp$>G{9zkUq5>B-(+xl)^b+bxFH*EiA~iVY^SMSB+SqKfG>L`=sjWl4{0N4c=;F z>+%?VzF^`B{~Z29Ctb50)NOke8n_&NB6FB%&wZ&mwwpXXf_Fs}CFyRJZc+52VY9)l z`940`r`vX&*v|c6vIbXKkK3vi&xU}7cYrnoV0S&BvWb$j1oxZfX_^jgjb-zgw7>IR z-pY`=xnL3&Ht_jglm=1}6A2x@81=b@uN-vfRdQNvr@DZi&x;+;C06a87zSpF-30IGfCj{~f4v`NEJzf4WJ@ zAPo);fzE9)uQtj`h!VsQtN@Bprhzj=Tx<0chT1D2Yr~kYR%^9klbp}@maN$?7@H#F zm66=bRNn7(B2_YWmUo=io;S3~<@D04ug1o5D>ou_4emlv9;uPzWDeG65Ui`!N- zq@Dm z-;<0mvsYonIl!xImz}Xx_`L;wY~aj00F_-QIgdpzx>|8X_@g(|$3U-VOu*g_!;d>B zU^0uop}2OF3hE8sw?S7(2f+P2Mb3?#rDZkQO#t%OnHxvrQ`aq5^+d5#&VmY&lmMZM zC8yjybzMhcsIDkoHnVl6V&Z3eX2H6~eE-6tP@@p>!>;xC2U%0xl@G>9LYt^o>-9 z--P&yKvHj-!dk+jY@tnBUTu;3?>?WSX5HOe`k9H6sS)gP%WyJ@dDk|5n0C-kEFiq@ zZVt0(TJ`ZeB|G698ax~ttQ?@urOB&zP||2a3V_XGcP`b#9x}b9bSQqS#m(LYs0pYJ zs`vg7bQ0|dv-wtjUkxuwv$I)T+YzFXNO_>`g zACOSF9Px*aA>m*j7rL6L$3a%n=L*uiT(&aX=}TgeO4h(TUBzYg%_AP_lczJp{B5d&C1v!vFZ}=L z+)ra6e_I^2`Gy?&zv1#+=Zn zZ=~&cBnJu!mc@w|Lhd-`0RT4xq6jUf8{=bd3prj1Tp!3Rgv*3qzWkmYe%1Xtq3MCIDJgU%mM55r ztUM2%sR$_LJE1F?XS8d?s-yQ+`tfx02)y3Ad^hGZmA{eRwX8>Olx%zaF^#dCl1*;~ z61k?CpdQ*WZJ!8CS z8a-g2wL5*xL~{j%4++mlJ=*{;qQo%>h)8Y!Spw(u-~Y&^^(KpsSg>EgaKgDN<(TEK z;2t%b!Kd~xw)uT}`#KYO1qEs4`(3A7a``V_G>2VG45wBcTkeVz#0Y`lsk==^&POzm zZ#2eY8wd{ zAw(jR{Ffj*MY6;nr-1BH%nQkk>V7I^ti|WK{u!x9Yn;=Bltgck!ET-+L}eTu9f{L* zy3C%yw4b$S{`zZ%`Q)3G zm2z!NPM16_&Kq-s?}4~f+un-FbFmBWZ;aRNl$GRMmnW~-F_8;gGJ1e~>$gOBXp14i zO7{2!?a^1D0oWWSylL}2^ls)J603N)rCuv|T05ytPP2F9^ zwq?#*vW)3RzLcu<;=l@cQe4DP%a8faRJ(L~wS^nSKXw*bmGCQ9c&zthm-PmfR&8&8 zj^E35xYOTk1sMf%CUd!@g5X9CbF#aDcu#C3X&bL@o4`_deDUQAaY%>!`t&!0vV`#IM7qDTO z<>O}Es&7&<*2JZ7y+#zOh5F@kJ8^kl3Uz2zHnFi;wB&m(5bT`5{Xl7mP|OO+++@1O zp*Ftt$&pZ^U6Yu0yAWYy~231rtlI5(v(r4=GfbSlUb_J~a|C zGTc|~ymuRJJXQ9`EkC%r5qW9cED{e*ktS7H<`y01< zoByys&8U?o%DK)F*=Uk33Y6qR&Fv{|x8y~M+~I4e(u8Bl`L&;X)ykMs3XiR?ZX-*j zhxJq~kvicj1*Ev*k&y%70C>9?R7Hil7<5Ih*~GBiHvI?`ZLcof8tl}@w`r>!QQr9@ zg9Ot3CHnOSQaO5Ph&o+uw5~`r1{@XHHAU!diE)ki1bMaHj1rVp;i%aCxxluAl-4Lu z?6DJ9Rv_c2}BnNnNMPu^49H1~~s%a2u^vYIVkd z#&EgE0?nRv{Vo_#O-GDen8|ExflDEvKJ5Vqnz19z9K)wCC|p4adhdp zmW97Yq?}rfoh4fpuI`ymw zg8wzEb#QNwC-<>u^9?P0Ti%aFo80fnqmimPS3}G4q0HE>J8Y)HQ?q%F^qpF_Gk}?o zD?0AI+f2XLY7#@kc~Ggxh2FufkffYD{ETi+R&0@cN})fP-x5jU_x@GsCy(=AT)fNvIh!cB zv4eRGtcA3yy*$ry`WO?DaXn*oGWVm+*8gxmvo6npsu@mHMcn~mJ>C;nek(zwPPH znkW36r7f^OZ9>(XrobYy4Dd537|=@9>4EVkv>iGPWd;D`*->kCk!Kk?uYFIZi&!GO z9h12*1Iss3b=o~$(8VvR+*#i$716iHcIj zEs5d9?7PE#HX8dlo&lkapEYpjBC2Ln`Rdo50RToq+Qn9CS)YI`u41WWnlR4#C4owZ zn<3{b{rDGaonWV#_p2nC(iythXhFn*#T>7np|s6xe_^a#)&UQ zx*f7kbXeMD!3tQe^+ z`wVI_9@#0K?wV`)J^v)XR2#|OoBV$N8GjpKXsEjc_v+mo)x;N}H>1&IOVb6x1IV_j zPSiZ<>eCe4Rv5`S5bS1Qz;T&-#3W=7q+{B!jj9#1sn9c;oE9H-b{xD-QffJv%Bfs6 zI&tKC_cp{68>XGk?c}I({>8YK=`il%S0XgX?J|2T@Kq^hT2`=J4ID9QG4mz{z-?@R zp~TKXT#C)t5%AN9-rF(d)ha1XpjITE{3k5b8;+LS)Z0MOt*cbcRaaw)vO0kTo_RH^ zc1?s8sgs(}q-^0SE$5g)jb1D=J2oZTOXOF$PY?O7T)Wz)#FWI>nDcR}O;PsAgo*aJ z>A|OS=zc;@KN|n|2Z46MKLU5yn>)wr^Lp$S!QS~u_Hp+}=*z3@PBU-7uN5I!MaH_7 z1lLvYR;NmCbB4l_`3@=9+nT!h1Qqm~VNX*6cEJ3==Pr1voA0^A0F6MWewhqhuZ72B zKsDsuyO&QN;DvsFcR+PtYilpZ@yuO|E`Vq^;8Cd7zXPwb>vr|SfRsn?(YreT`RmJ! zE&wat?o$^Ed{IH|<)?OxdZ795GX7B0U$D^wn8VTTX9~bsFbe3V`*-!12Cn>)@Ub6V zbmNqkX9ikAE1BXiPt{2AO8#@6vt9c%kakP3Q@Lz#G1+|GiQ+$}WrXiN_Ok~D4%>J} zA6o#H>+;7j|MwJ-h2GJ9?1Ig)g9&ym$@0B!|iwZ7o*>(x7&~ZmVjq#^78lKx%CA(fl*pR|Osea()FAkK+<@3O zO?gaW1aIFTteOi?Zv#e473ib?R&l+z#n`Wp)Sck&#qWC_g$2!sCC1I&@u_8oDGbTq z(=*x@*bjcn9?Y+Gd5s;^s+M}B+x=||AH{&1s;J~t6aVKS@c3c9iSz#4F2KNAb0sJg z7-oM5z%2lPE+|5|@9X1#g*K@W`94jI^meHfh)HC^H3Mk?2%G%e>A`<$_UY)$rC|T; z1uN+~od49#rtM!R(*I~`>>w8Vf0i@+pAz_gbqPo(-r^7UhSJ-(T6=kW+E};{09$UB zjs!yVFX{h&i;L6q$T)a=Y1nwaadUBZbG32xrWdB?dE@5n=Be#&VP!+l^VY`K!OBM7 z)54#gN6x|7+r|_4cDC@gd23_kW)1v7!N$SP-kY9Z060L7&g6j&&vhR=Y3gX=w&Z2AH8wU9a5nMruk}*1tzV(vG*$9V1U7P@ zu0NAKVq<*t|6G~Uj{2r6u4XI4+5F>7Wv{fU#t~GOY7?Zjo0sU+TR3(;{Qq| zbS#m79Hta|LQe9HQ^oANd5dXDn?F|GQx=IVdhc0fw-YS!2HAj@uXm;;*}7(M{(R#= zG~BiDjyv%s`PO2tNWy%t1P!B>#G30)q*ZvRl=^R5*pGsdptvmv%QZKVR{o(H_1|r7 zt|VHyhg#-JZkJdli`)`l=!<=?H3F9Ke~k+wEbdwenEl-*rXZ0S6h^qvec;qC#(xw> zj>evkz5ehRg;P(B#%ABZe^ zXhjSyum5eEw!qf!^*9url#+a_aYT6B{hh+owTa_i^*Z3$>*uM;ZWmZ4fyM#!ub=Dw z+wRQ4q5orkx~d2qSVkJMN~F;TnI*66VZXPiIx+b4bHOY2M33YfS!A(1S=bmO!iDyO z&`sNk{ig&&#;zfpKf?itqq_x-N`E^QLuvi5Y#W%d7?ygf0tv^xs9+T{Y?0Ebc`C$5 zT#Nc!HdwYO)`X9SUGV_aYDsy?pUmm0~Wy4$i zF2sr+oV@#rI$`W^GWT+w^0ch;Q?;#Jw^+mZz?=V8J_F?n>-zlG)-`_2gh=fsMQS1= z<#WOe64IT%`x&o9^0>C6FEAH~C>@$6Q%+5N6vIX047zZh(0)EaXJR;Wj4fTd+hVtU zkRHam+F>bq<*0hXH{Q?vXBV9|)hMJoCYs2b++Qq)$_x=>4SEC+R25Xc!{?^;Rf=zU zvRZugDK^lL?ckem&_=eIx6HtR77tbQ?n%;A6PaS^S`h5FLNiI{+fFR{Ij%Wv1CprJ ze46ZztYaBgo=f+GlipOGD*YP2rKm@=w4&yX6Ns7W0dYqju~l|k6u}3N%clxs@z#z~ z`_BaQsRQh;hYv8c!Yhv%2IA6g=VgIJT9{0E(^m<9d@H63Y~tU;jLuFrX7Zq<2X{Z@ zv`NRm_q#A}!+4FeD?MRZ5IRD9#6-!O^gr+ysgjG>Wf$i;sP#MMZ^-04^2%GJvpU52 zz;oh5|AgeqRKP8ge-U*3rPH>5V?uR#+z->Jn{K=KArGQuW@6jr4V{Wa!>(u(efyqW zVf{)@ojlr75i?ircJHNhmI~Zh)AM+jm~Xn+aX-MqmF>ebngxyRj8moN#g?Xv76x zvbtlto7U`u9YY5i-gtr)f?%(%RK6~M1(~N@nS1aO7BEMF@9Y+xu#QhGk&0RfJ=sWI zn$ci=EYi0nanMNPw$l|aaVrm|!0LHxu26n6<(*nZ=i9%ijX#=}8FcNwrgVDUt!}n* zITeARi}%(=Y)PucCgau`9gV8{1UaW4;C@IjtRC?Bp>5zFK5l;%MS`D+Uu%{qT;9#( zX2Ho4iKOe=uN=N(XIFVSwPez=mO0|>XV852!|KUnJsbH@+?6l6zb=Q4@tj`G*=)oh zvl%yfS8(duu8(4TT-U9Jg&JA|I~;nI0}hM+bi%r@9!ieWq}0Bgem@|VThQtWEjUXK zD7$`dqa9rh`a01PyXto)X<{Py-s-mhUDmFmkc9h<2WuVHJ;P%B_x#!hUT27g)|;jS zIU~0>`4jqw4$%piDbxu|%w@0XsoI&#cw8>R$Gyg`%e$&850Yfs@<{qUqNQHV2h|O4 z=*<1@i=aQuR-5uU{9>~Bm|;`Q?{M0w46<^DOv<3lj@JHhcvK(HjgVFL_1JoX#c)Xv zz7%p>f~>WTYXlly=l5^R;_2tAjM`LC3f+oU`PK%Co?U9w`7gf~>MmLj9l^sMoE(r= z3j}|f(%Gi>8cV;!`Konmb}awUUMb-RSy@+#15Svml5GH!G0?w4IUXiR15Z#ysPBnX#F`mR+~m|r9xvE|y4*BljPQ?~EY+nHhC zyK6_lStR?@m|w&0EHo$#;3xM_Qc??+j5CC8Q1J zqHjKy7}?KasI2@`voE?6>ywPS#Y}tqv|F>6UwI{s)Sp^qS`XrP&0u@Q-?Ln*VvIg+ zUz{4suQrP;%P{0MsIv6T9fLxHV*+XS9rkLJvzr|+SL>(Cfsdlw9JXP35GC;1!Iz;! z7PV2Mw%<*qbiK13qWnjA=AxOIq{O*6_fj?kN~OAtFIrA0g4K|joVveO_a96N>72jV z#7G`=UR7RRg8T1@7z{ti#b8k;X$CPd@xIESRt#vnHqt3Fr4}5n1md4|y<|OP%|j@Bqx|;qzG( zR0uPeXq>Hxd$W!8NkQF0pt$qf9t%${?1yvsZ?P#`^#S=G)Rd5~=jHPv`hmVeuSy0v z>#Sj)9N@y<#PnZJUZ0>MVF9~)^P@946c7B^otV7O`8rIev-mL4jUD@piTCLygyD}F zs-#Tqs*^L(U6QpSnSf#ZDgc=NQ&|@&H|YxgX4f67Ze>HN`Gkdy8KrG;ECIf5CCl@H zPCA$91S@&5v8P6JN*Kg7u7$;yO=3?A)Ku5 zR*K%puPJvY&S<+)znYAZluY~#@cKEskT!Cch`vxMat>lFhl^LDD!C$3hw%D`^P(4i`Ufn2{=FVImq@VxzVRYsX;Wu-2Jja=>2`Ksv)ji*?!Efov+@@{VN*xv*U^+r5$kU0GvH7+Ka)X!D2Ma!-BOh+G+Pr1{WPKhXV8b>uTVp*%z z^&3w%rP6Kedq#}2s9&s@wFW@r$wjwCwl`^qZ^oM^Y78g$ zzFtOmprwYv6@n*IfplIfEyP$WFl~d>xmTd0+3xwLl=DtwWD&C^Pw1#E=kSO}&*Ip4 zbIobMtqcB!fibS0GbW^M`n>+Q-&oNB3e!SG`|msXMfDES4>Uz{y5d{8sUd;j?&^u7ZLt7`8PSSpc%jqctx!=DpcL|pMuCPb-Y`O>Ay%FBZ; z6Y&jq8{!TYmrS40s4Mm9xRvqaTKQ_c1}4Eu%WRyZ#3#80@0a2A#JH%MNj&YGg9eu1gYxdc+ZAz%3liL zs7fxFsgtYH-VN=sxpz0r&LZ_ztzFqAK$d4d?*ttGX@2HY-#TVbYkA{{j4Wga7K>{I z3xYERprSj8F2k>-zs)ei<)8@%M#*&ICRIG~=$T~qi)TWwvn8KzFZMTG5rQSxul2{9G=;|D?z==!vC96HVEcLTLu zoz^xG;8tTqee6aRp2{lI26LWKuN-}ZN;t~?Zsm%CzCSWv&r>_yFca-FJRX+Xz`w)& z2hGdnyM|b1;DYW0U7ihAbh1-)T~pWIDbgx)H5YQ$A+I=^fdZ|?n>B!}V6GCCY<1l} zLcSSqK=i-vgggF z7)zjmm?7%OaRti{5JTN{MJl>}qTS{Eey_c3zl_^o~zu6$*dx@ErnfqF-a zlUZhz3%P_$`^yL32|PVZF-#_-mOicYiPBZN)N4Hd-C3MJ77CNVd8R0&iMW1WxSZLs zm}40We=?arhj;X%8a){odo$~op8o}7dARc{Bp!DDu1<`Dl=}Noqs(^aZHZkQD!h|i z^a$Fs;VNOgvw13$E$vI|&|z~1@l$<>T89;kM~6I-t;pY6hMlGY$ z7gTj+o{rFt=$Sbfacf|EaYyWaR3+INe^R4jI<*ueq|W##L<7_8F*sW`YbpjkSfcbR-D4q6$tZPSkLW-`h5}zaD>OMt8&DWh25c8Q1>z%U&JaZV1k9Z@W>3dFr#$LL&J+O)6 zn44LZ^eVq@B3Tzm)>;BAB3C#y_v&`Mso^S-0?ItYlAd*@K%@SEk^^4wlV0i_^}_X_ zlgYND^kUsld}ovh&KsjTD;9n9DG8u+Q9XQ zK^8w=`JD6aAjv#y_Ovt$$z`>k!K+|>}Mt$WH6pG$qjpu$o1IjqxJS9rHWG1 zsquKzq(iJhBH7zHQ_!=7J@Fp)zJ2Yf(fE)pOO|gq>^Hm=Axfz0i$2$(TBd4~?E#jg zE_~1aD14KRfv!(9^6$C)vgw{w7~v<>3Yl!k=UNXuGAeOBE|P0JmnQZ|q)UR!eGcB@ zlNwj>r5$A0z;|wfnSz2C7X0Ybt@G)Yv;+vBHuac->VMy%^=Cj{Ul=tm-D`l)F)uJr zjgX|lam77fAg|wHi{7I|a7fMHVu^b`2lj7qWFk6;NM^aQAxS=<7ioI}GGn?RmBc-f z1ntK0cyv;iTzMj$G<^bHJuw>l?j1BXGU0!A|9@N>?Zfd7NfP*;O0#Gns$TWsbuFrB zT3PRE9OD|+%Y!@BK}EuesjSNLc>_t8Wv!v*yA?EV>5f#AF@$T@jWG(kBDG`yy+ zkVSXJ(i!{EILkR%g-6O)`HW_eWnmkCYA~SzXxMKZnZ-yp684Unk}3_qW$1Dr7a27k zOCLOElzNxB*uZ`K2G}D;sDr-oDXLSd2uabV?@1(xF*sh8QU}e~>qK`kt5!2jWSc!?t|!F~g7$)<(o*y1rK^CX{pq(2>=K{4#-#JSg|? z$gO)iU7~4HuKe>F^#+O3T)M=Xzr7xJh~#Z)CY{Nfd(f_JMNp9X9~!KVUr0irOYbLP zMKSHpW4D^p^RqC)F6{gAmu*49bZ>^*LLZhZbZZ&Q z^rIg|shGB=i1Fe$7+EDGBVrz39My@oIw5Pn1oF-RCj+R$?8%g*CK+5)8UA>xN9T84BQ{GK^m_#VA zp}o&$ra^9&W3o+_^Ov+;tLC1fv9=}&Ipr~j39wm8+z!q}0Joplb_m1JZ7V&%i!c`?pHwjoSoXk zc5Gm=iGcM$K)tstI%flDsET~6tk3nKqZU5O&_T%9C74HUf8r}Zm^}HRZ2w_<_ z8Kr=3;-f#kR9@%4t_x_wPk=4`kg$ zfjly`@ZDY+IQ7oi^`jW4SWp}#vGy3|uj;y}C9+wsyc?e&>b?!$c8c*66#xrBqxnVoRg;`tb z?DQ|Tgrv3|leNl1oWOsE*m9X6Q}04-CD_`+XMKgm4!PBd(4p$zwrqB zQ0wlB9^PfY{@e)-Uz4oXcZ?(P`S1|EJ)!$%Eulj){rvS5OYmsC`LiR7^P*u&7oz-m zenWPN;jHm^|Aw9CVvnz&9Wv_dKFpqsAl22z8=zeTxd96psb|&R2^(p;^jdzqJ%CfH ze3?#{{`S@+RR7a)#sccQ59*XR}-@Jup|54kn5h+jc37=i1t-&)VchXoQTg?-hyr z1iYJ~=t!Z1a>S7@?!RH!kFa7Tp%B-rRkDgKxgL!_FKQvEdpX^HA6!b30$X(il9-<_kshb|BoES&cT;v~9*=nst)s z@`C4Rz{_@^7o30Np9Xv1tcqKQ^kU}2xWbJg_P?a3+WPtn<$Vf1t+>KXz z=S6_sz_d@;#B<%Gi(Sqsv<1iqi~dU%P!EwR6HNA?je*oj?De19rB%)ExTU~^n*-^1 z$5vmp%1FzmfO?hEEoVBiu8ry#-Ju z%hoQqak}xQad#Sb*|=Nd?(XhR&sL>xGpTUf4q`(G;HAcS;_jH=+|6H&DX;r>0-?9Hbn40 zj-_qG1qjh!6sDN;W_&Z@5V7MN_5X7lJvSv41Sq=|Mc*So~N>QW6~lu zt5V9|iHrNgQqjn-RSfF6JLLF2F@JbL$l}VAP6Eu*vwSuxSK6P#rPQ8(4;j8^xrWdDWYZLrI5Q8GI^pHqYGPOCOUcR$EIOK&xXY3x-+QGE) z$A6Gv^S*w+fizZ_Qbm4)yD;F z;wAU#F^6;=``U!76M<$4gtZsvmm`zkGwQIkVo*ir3Quo1+!ytuZOc1!gNV1jbsofY zuZ1c<3LGzNS=<3F6myS?ev5SnG|t6d>R(hZH-eeX3rv5#v|J=Up1`?_(ck_AZK6xs>x>qr1RM=||xe^Ym;nY61#AJksZoSUremDChRX-IoE?8M7 zkFP+h(Lhzx;Y*K`9*{+u|N9q<2PcH6LD!^6^+z7Ujre^VEd}gl@fUc zvOI>&HTmom3d;*4w(p-qdz5a0J$W0KW2gnu6IHI4tDC;fH>W@HG}av$a$+@;Qz>tM zgxU~dn#|Xj1GF>f>t!lR0-|nqIAu(2C71U2n_D$ExUnc z?dF6*Z`*YRha*%Xut+eBq*jY|K7mw@zI7A6L|OY($b3DW|L*jZZh+jF{+Oc1WG=$j z-Dqd&Ketpir+AO z&y#W`Jst{w$LE{jmiaIgKpaD z%hHf9z_sf1Xup3M{M8* zzL8W~2q+)(3?7JO-U~l{7nqvbV+78nJl;(;@=bM@JT?58-1B^zPZe;x{k~-R@{$$l z=So+T&Zu9ET|`WQ8YFjvSC~-pDKy@ctiDEFDWqM;8dG0jjraL7b;9kbosz}4-s{*; z#7n$U+2QrxSY5)R)B#nUtNNT5srCG|wfS873$Y(A?nIPE;(2iB7ENc##euVX_=r3;qOnfkDe`qCd!`Am=U zQ>sghjTB8kaM8~g5@@27R(p-`R)yS*h2cX6&hujAON3888vK?Nt3XYr{i(1j`PFS$ z%k*RIk9J5BvETI;yG{0Fl%8Xdc-=1Y#TB8edm2PLqC>BDhCTP(&`x$&5S zS#&`iOvgi$5JoGUqir~ocOynr;dRi^u9+3TYqd{^elx z7Y<4D^~XAf-HS%iJ;Pl&YtL3?j`V%UlUl&|%|lVs_&()JuaJY*!sLF*M=by-0CxG9 zd1?44iRL>&=G0Ao(Cx{32#m&Q*CC&%3rnD_atzeO<3zjl&N8fu@jjPQ&7!*}yF9}u zH57arZXCyNpW=bZ{aJ=6ObO2;#RG~TE`)*#%ifXCrAlA*TB8_E2rww!68{MqAQ)6t zXBPl}#kDn@zY#X}OK{IUuWPM#c?M8tNS2+BJimjXM@CRFX4%X+v%jBDFbx)c7i8xEYYTsJ1zYjhk% zAR&^kP5Ce&5*!IYzM>dS8IHI!o0D21VW|ok8tWm=noDv|$66JY=Y3`Rx)w5TvAawx z=7Ff6^dVa`F@`Cdc6Qf+5^^-kPU;Uvy7Sr#z!M=DhJy=VO5$T8Uh;+2{+x`Go2u!- zRD~%+>t~DuQ%Sr!+z0*|*a7gP-?*1&AYc64>JI`d43*PBY1>QL1zm8ve4<^K7fFT_ zu>@R&a1;dQr6DGre?jjGhTA;(Wgjz| zvL%8)G0!a80UX(6B%oOy>I*C%n_A#JxsMl%oY-~MdUzrk$Yw>zZ#+-@>+g5xsl8RX z*`pzT)!-AW?b>L-9(D*fH^ZZfgxZ>WzytSHnPO3#Vp;XMrlA96Ub&$Nu`PZ)lxZ@CH+xdF*gjjHUm(?N%t@aZ> zvFBza#YFs=8KsNk{IKE;Z?*5W>_o3y)+R6i2UV1r4;G^X6NY6HdG3aaP=8sqXAp|U zLl}k-V?HZ&p7z>VoSig9H!DQdp;_kx-ogc9-4E)(*Oi7jpqH3H(HvNCz35z~lg*5f za^Yoe+W5l=IsYyI1B)l}L%l^AVQ^LbRI0Wk^Gy(;@fHrf9c-Br!~4_(Hzx#e?_6O z9gQbCA&65I9BMjJ;!cOW+F~91oz1YHDmrxtCRyf#Tc?ns1GWa5`B0&JvD&qj4p>`1 zW#?fT4B9uu;xIq?B*8#viMy!N{`TmQ8VuB9>uG#n(IIm+80O3$2CM>?D;Q*eO3FS< z!M+kRC;#bMHz9th4G2ju{2^+2Sf=sjGcNip&Ug&ep>-J2q)8%fX~6t4n_}-G?4>2k@ zXCKSk-2Kh6N`z(lm8vHYh0n*O%4!jVT}ay{JMGD&<%po#sG~U{V!Dc`cPr;N_mGz;Zs!1A4QHkwO?QT7 zA?nFFRKG0=M!s6%UXI#`6`D$tE@N@4I-zT*V$ZKj2g}hvG}Px5ti$R}fl}wKaCc)V zAiZIRgp~L&j(8NvPkuVB&~`k5f1S0)4a!VY27Kr9e3s@EtB*o>p0d%+hwLJ)a}`{r ziF9FJP=pw<sTu*h*KOj_c=i3OW{v zX^}$tE%hE@YECXyYR$yuVhA=oq!h<*x&;v&(PqOf_~<@m7$w>1=E|p@Idr|xOxImh zxYUUssU_kos*F5cYp7UEaire|xD?|;ZDyvb2Hz>smQ+(;8i!Cp%Gign=0ubT@IyGt z4U@?AAjSvCP#=bSOodNQdCz*PjWIWXg9JHJ4ep-xNlJop&smuO7A$p?iIrXmLU)${ zOoFTK=LV{Xcv@NDW@+uOC303|ADY1CY)Lo>4o`U=eJhSZFgv1KFriB(c%rP za^AntYQNpC$f`(~Czoah-BYi@7I7q0HB-sS* z{vrl1*ZBr$C{5SbW;AINBi>o9wi;IS(ULmR;uR5)ti94anxQ!J6DVTb>0%;O7l=Lh zVC8Ct$;ZcYyM6>Q5CiFVT*!R5O6#x{7bq1lMc=qYi62fw#%b{6wi@{ReJ>ji{!j@w z2IeV0e6ez+Mg8Gywr#@lK~V!6^}4}SFySXE(>d=9F?`Gi5#>pKzpBm6cF8n_OhGDN zH#!_)89bj%c)a+)G~zTt2gmqle~Pmn6xf2t5velhoGh^37}9T8q}a2lxSgFj?@tu! z*L_J$H@+{IPrR#4_$fb;`Wznaabnx%#AI%M_PB-*ZfE!%LqDKT#vk4*#7J7Z0 z_v6-g)RBb2XRLo2m+yHAUD*oD>EUdhlLIUzz~_zz*}X5pha7D988Zk{a(Y?PcrhyN z(flN$dA9t4p~!DJwnUe7)4a}d`%5yvFPn~-k;|s+DZKaKW5DK9Zwu`KY+EsNaUU`R zmF;VY0y^x^twUF?%TIt)3_@aWv!x5)Bz3mqEg9pz8Am6m4Q(7Eg2j+@*BNe+f$*{B z9-wjR!HF~oK+?9|{)&v4*ZsKO|LLJta{`I^&-Rb+)I`OxMfWLb`OXVt0A!K5=R)cL zsWO8Pyp7-YBQMn?tYEtuVAiSjZXVdWM;VZ73NYyR+c3LUjC9t6$)hjFNeW*HZ z;|M2Vid=go|KdIco1@M_*cKA!q@N6!;qk^#8(hNV5`g0=*pa+Tc2PAr#=t?~hnugO za3|5IJ0|Tesu%WL6)eDO>E>~gW=gg*S7G=tD=a&Nq>((yF5L{?7jL!uo~pCRaD72x zP*<3$`l|-M7mDjr4}0-~#qr~l!zXIYJE%1rF_P)@zhq z3&_VME6c;>Ux{=pTu;w03jh*F=8=0DVijH3FC;2uR_N4Y$J^?Hlx|^j&9_}ASg3@T zP)HTQk4DF`P7UYvFMr@Ryj}>Ehc2hU6c!Mo)bv(f`dz#}CS8*xPc+<*dHtN;F_aiaiw~hc9-1fp2>UI5?c-*JaIWp0$j9g;IW|_{@T%p}pO& z!ovPdl3Uso)Q*TLX5_jr6kR8QzJ{Ul3HoC={U3<(gQSu`dMDYrw!W0<^M;l}l>hg&T0vkA8gck8vIS&QUHNO9gzA387)SRiRkA$m5vqL95-*g6w{DWTAA# zonQhHMMH#BGSniu<@p^XBMJ*-{*^ylRP>aF>mRq0WhU^1e{b{1#z(XQhxJlpsHr}< zH*&7^1gHlpsy@S)LKU(g2WNeU;0tnTjA=ag;Wmf%T6HKmBO1a{Ty}t@cQbn;-N)@sf4k(;gGIv)g`Y=Tw&@YGEPL7g zwgb|=1WR(vhu#+#-a()1HoN!U zwk3R;h_8%|n5o9vY7BZNTbV?s`10!l>V~z^I)~>mBLq~=YIN~nW+f7hI@{2=hYL+< zX3k}va&|bZzO%tbefsi~7We6*72%JsN2$OqM+F_D@X1$R-4`!9c8`Ju2!s3xg8E zAKe#wDNKE0rpBy1CyggN?97e3+!&c!FF$+~gNPuSq|p1BF`p3gzEQr#5-LYf7ExRz z^60A$*Mt01&zr?0ZbQGqIath5R(ISHUAKqOgc%yT=4 zWgPykyjuE~(+DSP%7WQ*d{Kok{7}c|`yEX6oGo&Hs9;s3JByVTkukXPD1z6ZAE;8+ z?#%6d#RTbumnWUYwnBKAsOKg0Ek}b?Q-h9-j0L(`Iwru9`T<6~Ft32l>_ueK8_V7s zxMDpwbvWS~3sREe1&z{~W9v?xJQjvk60L9wOaK1<(lT|Af@z#$v0U3|Ds;!D3=JmArs2U(>h8-KDT+VJFksKgxQC@_v2~ z`y@pB$&TB}a;`7t0wuX`P)BXkW&e}inft%korUb|>|LCRwTU_YqFpKxbNpqLRwCB@ zi)Lx(^3J$)CT3>-M-Zc|sfnebu)W87WTy9>otuN0i;eZKs0xNoe{nN8{|Z(jW>hkD zws&cL%=Q-+rFY}@J8knlzkfs#f6tJe zauc(&{p0j6%EH@6Z_)M8)5!pQaw~jL(1YK!yL7B7aM%ze@BkQs`d=&|qaEenztdI!pAcJFm^sOD~;*sHFViMC4lTlKAq4|PCM9)M| z%|t={h59cg5O8pCAK^cufBc9}jf;s({lC1u{RE)FLx;l(LP3xMAkiS8&>-IW0r>AA zA)z4t!uzic8Wsu$4iWI=QCIU~wubs|F+_=QlL=%+Ag4kQ=$AVsjY7Q-}m!1_c-P zQHqM078Eu0Z%c#}_g>+i3eVBy{`3ZlK&B_t#qEDY592K`qo z2uL)NODJX`MZ^3V=ueIbbv-boECJi-pM`s8uP|7Z*o^AQoC=ch-&O#KQ18Ie zpwIvUfYUYI(;pGr9cR(9kxRK|-Ha9o;=O(2i!ByuL2DN*BYl3uw-2$MU}lxd)>}l= zs}XFNp8hzjl%e5|YmQh5+fJMvGzTn2izbA;aUsQ%(*T>38bQcuhXJ4p3rFR)W1JoT zOe`8XqP3Qc)avc@^~1_WTqnOnTf(3DV@6fQ^&teK+rv98xe4aNnQ==}GMp^NkmsvL zAd=>)S%))ojkO+UX=7909&u$k&j-^_2dy#mmLya6h21|Mu@K#?zINbqTD<{qG#&D@ zP1LTLWQDba-e;4IR%#}2It>jVMkXi;C$Ad=VNY~$PvnShV_ofIxi{*RGf<8yMACKX z^MQBbK56EggJQAss?<0&j5%Hv#5YJ`1~$ZFzw^!*?P_CLDL^PMVL@=GaGlip)E@HN;(Q z-8KB-fOo^Lu%HgB+1+)YqG{4qcTtUUrTWCa9K6GkJ~(>JJr)t^!BHl$<)er>hj^i$&uX0x-1ECB!9Zzlj5aWpU4=Vco zj<)Y|a%!CVIlQ%@L5}Q|K&oL5p4effEZgmGdJ$Zrmwj2TuH-sVFauybe1%~Yw5aA# zvok4&grzXYxtKYpQ5PQBSaJWC>ztSwNAT>kpWCP0%*EC72F#2I@Z#iofzrryhZgllg-ILbxG`4KDxh z)NI1t!ULVbq0f6KPgJ|LDL0Injmph2Z6FGNS<d#ZsBM)uP|R%SA*z2#LE1_{gJ zj$29OM6Lo$hs}hT4`Fj@(}V6WL+>zq1Mb`bCJa5f5V;*M95lLmzs?SL!*fw!Xd|^9ibIYLcl41-LRZ&N5`B(_`_qw z@V-X(6{0&+Go!g#DoH8pUDmF>xwdWFR4aw}a>(MY zv561KR%fLnNj3ciAhiW*f98g6EGm{`hc=z&rq2&}XWqYEdVsNf&}@xG@VRE6tfdj6 z>i4z9t@4E>M z(~$$_YR**N=F#HV_dlW5?gG{ZZ`=%;3I|slCY`y+<*+6z+S?eXxF*R)clhF3Xi{k- zeyLW`TWVU9c&&SlSMl1$rRK*kro@nC944Hi@OHfcN@7axQ60&CT_Fv#UezsB8-H0c zwJ=JpJsS`q4;bi}f@}K57!Bll^z?MKo+#u3+s0(OijA6-?O3*LsGV7W~ZjIyptrS>Y1FLn?3CUbRE7;#c2W?QF{xQfew#a1Mifr9t^q%ty`W~ z{wfCP!@A#fCX91%G)oo|SlpDAlQ2D}Zdu%t)6-YkbB0A`%%IgwIjpYZTq@y+Up2NSvzEfcTT+q&0;fN{0q7PQxYw54yw?{tza$n$l$>~KH~mnx zL}JxUsjL0u0^uBvNACaG&Abj}m_LZ6k-#&N14|tf+r)ZIrccJbc4sjSVlz+PK}Vm+ z{Y!r{my(Q*q0*D_#NYuBj#Cq8P*=$vjp~`sFsz@L4dlmjCxx`X?%)HaBhX^*Np7;y z-x<3-u(|5xUg7=HZ(9cvn~bTZnwqw7(Q0ZsR9AD;W4--mU8MukN+#7#C8a#@OYCV5K>9pY&cvRRb9BF+x}+AyO13ji&UE4**hz5x zUY?x?@9I(5o-_JnjLS-u^(3I~6g3eA_mdirv((pB6T0CJrQzuH;!9c_;c<9ggVYMX zKlXdJM=}r%u^zzkP{fWp(u3aZSkXV4merzrrfiyp?z$AwJ#jB}>$!c*xoq$*@XSmH z4IE~B(lH#HOJiw0`@lT4!Gk-LV0#np4eSl04WSixcUoqPCvf`EQpyV!n@&>1Ly1?a zsA|jjQuVlsTiv_WxcKh8$Hl*)$SY~1pPLxb9qj!lhLwAg8q;}+WIWSmXs_<~ZUIgI zomC(02dFg(=9!F1yDT;dJ;OY5v4fc`+w4^}@b#|~iF=7yt;DUNh9m})+vQNZ*8G$i7<0Pb=rCG4OxH(a9{vN)!RO*6HoOTF61sRICK z#`q_+k-QfhBuklXpI_ar%*-|yN58^bB#<1D!kzq%Sv6OuK-R$(Iv8Z8X27xUt7+jY z#$dYp7Zl+)0N%hfn$g6b(^;S9grhZ{GEu)xL@RDH%>)+NG561m7>ixEoSmN{fF}|{ z)*#7E_k;68-N@RmZ!zoV#cfN*F3G1NMjV|ptuws5aE+<0MRC-`fxN+VWAQ6HtLKRw znz5Ake9^wom2#s_>h>`(v693GDY4PEA%|rL?&j0?7qAlH9`!zM!kf+4yOcu?)1xX(}eF?IAa$RiX(BR6Z)k^K;dl@ur8WQXqh$;z4sMYG z_rROG^~uO|-i;iW-+wf9!te;g$^!MZ0u>L7GI=mhyINviejJvwjp$s8Xp!Mh5WWGb znh)D!*I4UgJk&-7Go`CsJNc5h z)Q!pLAgY~FxGl}qQxg%RZ3n@VJ4SQNZN8@$8I_BSFpA9$o-`W2COx8Mo!o?bcRnB? zJ3@KEl@6;!a?+Z7>9Y$7w^hx^zmi4hTfWQLSwar>$ye-*=VkC{!QOVl$hCY_!?x3m z0>#Qd$Pn^;$4e&|2R;-bw-W$qqm;u#ZlhMuZn?e^Havc{o`uSY_s}Pg7;Nn;s-N;~_oQl|B|EM;lpFi!4Ro zqc^4AFFSyB=R<~~GJLqw4f;r8d19jtAu9 z7c--z1TVNm`_!Bej%AM*f7BQK7CtQSUn{QxGfX%i@OR6G>ZNIM37!RSV!mi#q zS8tcoO+T!mHHrBH1UgH>m615vaQ*1JcDR$^#PWA&bYo#rzAS5}E^E^asJ;*6??*IK zm_2j43+Vg+;rf+Bgdn+eyTu#hOeLj13S*4`zsQB0%f;QTnRC5{)N$P7a6XF?oD01t zs(^+Dfrk43mxB=q{jh>fQJ&tC+&CRvfW#P`karLUxgL8}E0Kkvb%e&3+3iL=}%~YsRAu3q_x|{-sd34oO;7yWRt{K z*P{*!#?v_{EDeo%qFv65|r7_ai#q0ItlFJOCv3q-kI|DKRN2k_ygf zy(3aIT~%u5(Js0ur3F$0Vdy3r^bKswb&Ct}iLsoeGfGxPIw(Fe$Tabz6TGfWc#sSM3k22>soRA^KmSG~DZif3$)LN- zu5nT7{EqBSy&Cjk|JtW*r6CX=njkPss@H}TJx@MErG+z+@qo)Q<9Q$7j0~OKY&BMG zPxp&8JecK13{NwE$#9b3NYhqKu5ifXX_2uDfvaTam$nV;?+n=R1H(!x#^})0mY9UG zaoEg5`;ye%pocV^ftHW0(R62-m1l55XU>Ig0d(KK5Odks*cWK?lW(mR&@|;RpsJ6b z=bTS>6TqL`b=$ji=9-*c|AuSAJ8~^`KPJOzc8(Zm=`o_+w$=!&*!QMt;sfEplk;m5 zkoiv&aO~D3O*Yd?Frx2VJ>2U|?mIj@@KQ=oq%5t1)7U5RJI;|H{#dT^2BS4pRfUfz zP%<$rP)fzPAun{X<$cls-Z~&TE`1X@5O&>3jlxGCFyf!>s7&QI+A!ndviStR{63Bd zP-~)Mw&R7@#0~FP_de<}NSnzsdBRjV8<`kzisa3~`TaRlH{C7E4}&)=(q?}SPNs}& zCc0cp#E!ZXJRm4FTXzvKf!k9)y85a{ugQV*F_}1Vj>qyGwsWFdYiSr;yIOGPaz}UP z8r1#0fO(L$+>3!ZmoTxOUIK0V)XXZJ_TbthHU41u7b&tO1nvxr_>1+hcHvT|S4Dq1 zkE^}rkBq@7tq9V!IxXW2o!HVw;^VfNjoY;*jV|s!9W%Rm(ADh~Hh3r4MAtDpxaUgz z^4u>5%E{?I`(%GM39DDKt3`uZh1RGph50wxX}M}dt?)$B=%7CU{L2gGi}lW=ky+`s z9*B476- zxiu;&od;C5tn=b`s5E$Y@&KCawEW>%LTyxO!s>RkT`?427iWF0ZH#*_MhmVzbc$(V z%F(A9tE!B@vf~5YIG7w(#0KG+rVK=3Y7I`bon__X30hXW%jU#TU9lhbRnnJGPQ)Q? zS2(;9BD!);kUOUF%}%~rQh2ZF+_(J3pLEXJ&4Zm1gcD2Ri4J2A9yU4yimSKlNzVdV zpn~*fXN8DkjvgZ?;@Z_Ir18>En6!VedKTtt!H^Y_Iyc~ zK9UsjA03#RHk$qyb1c5F(WTug4;`Ukz~P;JoZK=GZo>M?NpYR1pr|PH|E>pYyUEe{ z!yF;9jL&x7!HR7X@vu|0xCmK3KOyBEV4RTAwkuWEFyvu^2RLHAn5C_kD0)^3#t1^f zb6zIM5f2?_run3JjyNP#4%)B6yTV>ZCfs+?d{LefnZ6Gpi>)0}ow}zaS~cgpdMk-J zli#c2Xlqz#EU`t?O#_x_a;w(Ks4 z_O>Y*QBP~>A+plLfSOP1ps7h{WsSF1kK|7+e2oqB8-fc#w1(;5YWBu!Y9cVbT&HrX zO``Kg7%P8f+r)#3MegX<>d>urB#UpHtYk*X!wz2G*WUmEyR$xYRA>PEw`SadJ6U4!@vdH<w> zqnC3`sc(PZ1L>$2K?@#C;G>txtl85c$OxXmb_gPOrW=zGYbHO%hmnVK)@3xU-3D>- zGMxrY+ZW&eeV3^IE|gZs#^VzD*#~T~uC=P8A|8KxSpqm&3hZpY?{6u?$bpHa3osl5jp%2Ld{!#^S|xkwai%&h5*-Jfb1l|VTirj;ZMPE+gQ^|H zVRbs_hL$SO_4`t5LV5WmTAI!R#%De;c~nyw1XVG_fOQ|{^@U#dPOG1-81vq%%aJj7 z(7olHHDG^Oq1YToqqzw$gL*YCRr`UMq>k3qoa}v}GHyX@8BYY{VK`f*hoI04!)p!m z9%Ve*Ge;QBa+TD+bvzzA>%x7PkdRg6@c37CA}`mjsEWJ4!Sc{{<3=3`tR-;lkJ^Dc zA2z>h_IN~b`S2!eUOL)M{5Y2(F#4+4riuS5{t1HEUZ)=^I~(cPK3Q(`VzA&3$~~2- z`>vud+*87*8PWfN`d>8x-Kh94{n;s-F=D@o&V}ed|9jGS88P(w_N%h4pqG(`t66EWH_p^D95;YF6qcA%(poEfZ+}^N};pBf*WHrP1Oi`+lehoDj!>yUA+0 zjD8lUMpu}Qju&~3wo{jD&(Bk|e;9nzKC57RY^*)3X_#7kj|cI_cTPlH*98PSb1`?Z zwh;zd_4VQK+;4jE#;8jyOF}Dq@WykP5gdhfmyzjuJu28MqCsFHq|oYy)wk;5q1pQ+ z*7CLA990_z*J&e(keD?Cvs$qZh?ENVq9M?Tt;_I`2)s~$;_c?<%BJg@7~&UbdcCF{ zSWGL;q`vCQk<#NX<~Go+Mck7&EXGszO3BODBXJNkE&jQ~7YaD!doiY|p z?yuW*N9(_||N4p(E7*B{-2TIUh`QvEYKWG)5C>NSPn=_Ey6;YWJPLGm_CIcdq-%bk z^I%Ywy{tL-;a(%7s#!;C>i|;An-NcEg+P!v(M1pj`!HmrmofYO+fk)|dX$hY}JJ<}mApDU(XEfa>YD?_cj^Is>Z&5JwHW9vffBUAB9aN}WkqOFy9?FPL$V zF1DU!R62T4obJy71Kr08(Dk6Vt6atOwyf1`FE8xf8_y;xgeDT6_uFy=@w%x20Di%y z2R+74H++h*WwY7A{Z>uH9IO@T5&YhRn}&G6+~_++fls>`WCFX2$IwzxBrz0&#nNON zg-8DTc*?O(X#@cUk`yX~36UV_*>G-<@To?w-bqLC8$n1AiB}G8oZST)==vo{1i@`u z&q%<8&qoj*m!BoA_r%5qM0$wTaARV2xxzu$r!`^2b~Q*&mMr7gvktL1<}z1($-Y*i zdM8$F9gY`gICRIN?Tg)K-?aE7e(DVDW>}dtOX#w_CsaU@;qrI`#QqopR(BT6T(Vt* zw>m!GnflC0Nn>?M;p-f7)!HO_C4wTywQS%cTl>M*8Wj$06&YMUcI!%;*w*Ha%O^Dg zJFmaeUGF?DcAA`E7JPDeA`eX=+EJ0>opyCQNxdB4NM;lus^ zH#vMPWkg>aUP*E?>iOvAqF= zXir<(Ts>q{MDJ+ks^Al@Rlkv~;d1QO9sZKfhV6WoO6^bW4!@$0EsV{!Dtufe+UM** zNn5OnAH%<_*U(!UybEKW;6a9K2~pd^rkC$ad@?6brP1_`Myfw|zazJRBT5_Z^d`V! zMR&{a^iipi)uKZ0Mn#pV%xfYg{V0b=Kj7Kn&GMPFS4>Tvr@`hx=@KgZq&i0|bx=FP zQQfhS5tCFp^^D~Q``3UohI4+-o!1c6_r?|n?a=OszHBF~S#r7+ku{P0fuPIq+UDCA zWovMqeVB)v_S|yj%#c@10749DD)DyLpIEW6Z$ERlAVk)$s5@v=dmc! zi>Fk6FM$yagl8Oi1`N^jl8=aSQM~RlIuFX;(DjPfQ&ir}m{c0yD&M?omaOMFJT>yx z#ep(`)DgNM_(miKwJz?y@-;_CB8L43m?_+w^QJTZC;G%xo>v^nbv|~@Q9+xbF{YWt zoJm*~%HVpnXJ4!J!v?~3i=$2DaW&WnuXYI&WbdvQOi`D9d7uxUCM%}fvwuF(kHaf2 zs_xRHHG)Hj=nS>8y>Ce+@vsoV6?nyKWI-O$AKF=5s{4mbhA33+pYsvqU_#dS zD~#3}$0?nYoK^ZJ7Ec!U@3V{QrmikiqP5Ou$kRJ@ zw~yAxh$sjBZxPW`=nikZ8Ix1E-*_m`RmUF2i(}%!g9lvf$OOhqYLpz}>3|xmB5+3q zajnP|QJyhqH9zW4>KgvM{tYFI1b~9k|&ffc^wh3aRqewCxW{EyB^hZC)kOkOoTM+2|sZuvYmLgT2Omh zspIv9H9zYIB>b4~=Q#rv4UNp#~TKnVP;UGzS#){GnP4@;n1@15@!#jLFt>y4)&p^b`0drO`iY>GjK zAuJ4aqyECQRks7Smp8@Gky=cBA$hXoJA)h8f0c{!3uj}w956SyT3)7Kt8_d!KGBL8 zKQnd~vU@g5&2E~6#!M*iqTUUv_!tsImUy{|_}=;s&22`apztLkMhd2dEG_LKrg3o_ zNX_}hI}W`i;84=5r(2$@#jRB{$n6v5hQA|v2Sw>Y8vqc*Te-cHb@btd;y}V`d)LlF zg55ndqYqEixCgBrgKIzvgh;MB9B3I)?)$F538bMha=XwEGTb^s)}C47 zYp@$z+_6ph74?*_l*Qt?kn=UVwNgnV;85VFT|<-VaCid`1CsnYP}!bc1o-C6#wm*v zW7VZf#P1f5-N4n5!kBOqJxV6ibNS5G*b5}FuRSj{r049(noD8T4ZFKl0gadmHeph( zp4FR`v6eK6&c*6T+VvcAYoE_@M?rLbq%2Mz7L%6JXRUh!7`fn2fgPc1hansprakcZ zVB6?-$agEP@_WVfL&`5H&FH86R| zjQfF64SQsIHy2G$)fEnrn|Og&%R{Sf>)(0w^yK^$C&o6b9@82XJVL3rq9qV*Vwu+# z*#NwqJtBV(oXU&M^D|-zNlgjSYhRaG{WsO~Mx)^VTfag)`JaIQ+Ry%{KK|1_{>!lY zpXTuYMROQRDa4e=GfRoNQ?9K|*x>E=W4Ki;?*C+9^`zLpV>K1inal~s@p0nRvOMQ! z!aWjtR2}>0`wZ5QR@p&nn+H6<@MT~QS5-|uCTHk4*eBh<+#c-2TzYQ@1bn+)&}h*| zWlFcOOuI|G^{MweKKvoD{w!@xH*YXU8BjQm4|%(t(#4E21QQGV&earq87pUWh+c)6`{kzc&ST--y|_pD5<){FF;MAzH7lD(>%MSOT__2! z?|S%{qBzngmSWPmAUaV#Ih)b7Y^!`6T%kHB!s>gn8_Ut+M~Z5jU~oCQi5+$r{I|ea zq-A!yEe>l;WSA-LxXR{k$6e+Z?szb@j~2P<_4ubi;_YB3NK$OYj}<;;&mlKq1@bFx z0kJS^v8?xLG36oR0!y6wr`KB6{H+fH>ACws?F~@6KAlqM+MECFl}oBW zYGa0Hxk7Z>Sp5IN-dl&oxh(s_Lx7NkAPE722MZy%y9I}!3GNKR-C=MN+@0VS5@2w5 zg1b8m?moE8@Xb1VWu3kETIb&LJm;M6`R;f3KQpgZcU5;+*IWIo>d*=d!g0+J^|39t zVLciZBVqntf3|5azUn&l-; zi?Es5+v7CCi4&D%hFDTgAir2$gF`Tl{LX|E)Xci^A8aP3@WBKzaA1iZkImceK1+@8 zvZmY!VWPz>@HPLQQR(k||6)Iz;)Fds0KexR;NF!;;^v2CS|!Rwz~ZZXxK3m;(;dcl zI!o>)lsR$Vf;=^Cu1J2#Ne`b`Eq2B|Zc;7U( ztcX~M=OOSBWIUhFPh3z=SyB&ej4w!ad|O^((V{#01Z5esRvw?}*iiBr_#%Ly(t`SE z%8GL0@;2po%y{*K^kDNLUOZ#)ces3JEbPg=rYl&4_e&+EqfGYWK?4;M~1O`&l_FI14aIKw<^S5kWm zmxgB}tlX|uFHuvnQomaQzdh&3S1=%Sz)EiK@iz*pnq3$qCSiKqGdNqCu3Cvvw>2=j z@rLtF0qH*2`8Nu~ewU{gz?8({zM5M4#itvhKoaI?FfZuQc#_8usTdIz!s~=_5?seqIoWgG*=#edIF1EWL;=@>tEmcjA%oVt!l0h9yY20Mj zEV2b^K1U7571U+u1-2&gw5#q+OfB-WaklE8*0N)un&ntG-9&5V(;1h%*#yonynDJG zQkS|~e|6hVCR21xpG?HD7Y@bZTfF>EZ~6mzVl7LGQa40cBmb>>XC=8B87-|X)09q* z$mwVzWRgdV!%dQn6taqKf&gF^jA?Pbdxq|KKkJ)~E#h*ru?+AA+ zrejpm!u>SIB;fKvud~1S*HGWR7F=%wCv>`QxayCf4x;R>?|#dqU_g7mRb*Ja_Cv8N z^-F)3{E*;`7(gH8oKmOrAqt}}UG<+1hBif&(}=9YIgOM})cOyAtgs3Ky4w34rW z@)GsvwxqJSM%!09n}T+mf**;D5It6PEuGQkNr+bdihRXzNjdZ(bZ=eYuI=OEem&C8 z7tA}=;cHXg0>%%wOy1X#h|YQsn5oA{-{=CNs!`3ne_(r5mgr!=)7f-GbSP3Orp`1M z5>AA-|D4>9O9uLI$Y=Ka)lf(Z8QD|*fRsHYkX{f4ZSZ$j3GCI_Vzp z9Nq8j1#-ylrai0DC;l(8^r1EDHIE{bQ;>l_pz#L6V(PTq_4xSD6)axudiZWh{npXn zHtU6-xZXzOBU>6VvhR$3h^%A~Gz7K8np5fauxrq1#dd#U8Li!xHLQCI7jS}q#%I@k z^_361+?2{Pn%{wApAVDNR;}_kUvbD}khxk+XR^i%mI#xkEn!sc?vr>>^{&Jmeh%1L zlo-!Q|Eb$3<+lr@(P=Tf&;PR+AyguoW2+Xidw`PiN6U=1Y8Vbo{9$D`u)ISZjAk{{ z;g&K)1R3G~p&3^JiN1X6Y|Bf-94JLrt~a=TXL5}nFzY)4oUwXXRqEPSVZSqHT$6J( zU9C|y3XxIz zm<^vIoJ5c0PnYD*U`6+%c(vXw%I7I_PLXC{HL5bQy2kjYao z&f(}$>GkP2FD7n>)Dq&2@`N8ttRM1i$cAT}mADg~eK;0L1|U9ePD>j)ElB3Fwe(g$ zde{^+dGxZf1~&aXEmBXM2>IpWS5kHO(@X{~c%0+})Sp-|iQQ_L!1i)^eZiR!r zgSy~|tQlx2jo0y9kE2UzMz_QxgM{#BlslPE3wyjUO~LLs z%B9nf2SsjByLv*nYViHpa3sPah>NTDkPdA6 zru-D32NnX6Ouc@+!|gm@H|fS?c5+fJLs24fGdg(5VkIRTdEZgIilq6;>`p6SsrwZ5(A8t%w^SY=bo!ivfS zuC!y-=b3=g$8JO;+>tEsdqBq{wTy%{&J$6p4U)owE4`|e6A+_4M9%Bz+vU=(D*F42 zhA?Ig%~HXV>I3G5;+32Mt${JgL*Ju2y|+<&>WL@X9`D#(SClIG$zrRC=eHP_y!#iI zssc&)YHLCY)|bN%egctiACoS+iWL)5VKKvx;7)6v+2({2B^g+h}ujB-$+ajxV#GRMUL{Gw$o6F)j$%iwO033BR+{P$M(q`Dz_$ zK@@cCdm%E5RgOjGxd_o?jf{?(W@01d7|BRHFX-n6K4c9T zS=DSFNjlT#lr+^C9GrEzg;+Oxuk99b+jA!dZ7=2_1qPlxb$FY|6(>hd++Ygp7bd*Z zqaD#j;JRJm&oV|O_uQj$x{t%)AwpUcp9#HKS}gE?&dSbQT_33Vj!E#by$&L|3*S9( zK?Ej^uf)G#+@xr%rd8D!;9PH!z-P2<3Gg3sMjW}&{>rMdMSu(O&$)RLzee5#u8 zW-F!v@^fnNrJ18qqz8|$b+@-70==(6jO0_LaWFjYAH>jeDRrA{J~Bvaht$O0+69=Z zGy|KSxMVxan+GSQ(93`8_bXQKIrhRl*a2xdziFo*vky3J8bC?I^fyZPwN|YOIIgU9 zL6v+K8YC{C`tD7ug{FMNf}1cAjZvBtReZU2{D9mCmLFdYN1wg9XkJnn%lLjy#%8FV zqWt7&f1`@e0BeLlM|_CVldEqWeEl(0&nACIVZyyV;RkinUUQv(nXd658P6*cOKd4L zEi+q4q

    8}-&ERE4T?Xh1DO%8dfKeACgtPl4q`(px^Q&EivCv<;Rq{l}gQaLLf_ z#m1Ki$8byiH2Z+ct9hh3%6z*ct(2=IYoffdK_c+9l}l33_K3E-C22Fy(cJZch8O1~ zof=ppqQjVC2gS;PP$H6vl90byQOUMJ;C6H*({hOPm`C$vTaCcbD~_BED&I{_;%euK zV#I!47Lz71Gk$jck7HPRbU6lA+e^91$}$hv@>+UELSQti@xvC*(;SGnQ{pjnZ9zq0 z!B5ZWiRnF9`wcrT2!KD|kKvsPw0-fxCVc3xV2eCmJq1)2!dtgg?vft>wOe1v$8lG3 zOLUzxQZ|&>UMut{4<@gy5=OJ`FT`>Ow$aO3iZp({irhdQ-bBu{+g1<(|IkRq<6cu~(J7Dv%;t_D!DCfbZG)I*Gn59V2l^W^RSB-zQ{O7P zreiFqGs{WR^+&n@3LhZ+O$u@h+k}K9Jy!p&U2=Ml!$?CEw6GDTBVjr-Lj7yB+z{LY z3hnRCfAUM(1eAlTKAwslaGFZ*P*B%?i*mMFkyCXAbWhD6%_b)-+quna&U8)h4&q*h$IiY@1Bu-a8 zAJn4tT$%(H6;vZGo%!ZEzpXw!wZr+~Te-hbVcJp=0(d+w%qXA`g#(1pr*!K+_r{k5edsWfJ^=o>R~a=?K$~k@Bi0uTI3D z9N1>6fk<}MMI&7z==(OMZOZP76f2S=AtT1}bAZM^9#7|x9WM!zz!Ibw%2F<|y^b_s z%U~<0sDgzScS%55>UQY5U3JKAgBR1yoCd9pSmi=|p^uW+<#jFa#8lz(PIpwuyAigd z_;6>?sP2d^&jc_9y9Q#Q$tVf(F|+lO@i}is#=1|hez5nj^2^gprz_J=UZnK@~0(0CM6)`s5$&e)}lS9H0$*AKMw-Pg$25us!$d}x3x`8uv~9rw%g)1o^f?9LM= z?3K(N8Jce6hi;Tx)(fLh8_hdeRBbqNAl*8d-s^$S)8JmKQnhi2Hq6yu>(nKUArfWH zTEb=ndJFnG2ul*%wrTA$pNK; z*A(BI^XNhxG+eDVipP4ItF&>=r^r;wBsLeA7#)2#1{sl~0FZZ!+%awee&Kp79m@4p zbF-w2vn7~*U;@K1aaE~~2%oC9tnOSRp|l~C24EZpouJld+I6VmLc)*ksBksJ{T+uq0c1LjG`;?1}UIZWzqFWAt= zEXD?!^!1Pv_fDgjUVsBwv+aa>&V_Dd)v}^2t6Bt+?RorDvVRha(=hOY0($6Tmd>mn z>8V#XhO0)?#=-RSr9Ht*-Ba!gdSi-Pr2(xuNpc6Q5Yd+-T7Aoe0CG*A5r7M-vkA}X zp1eEK6C6O_YNnkJjg#xX}|F$NbYcL z4sPPC`?$GZyW684YU8~$DgRTgu!C);oA~m1n@~8>o=#Pt(X=F&=BvFvEWK!rrm*5E ze}7x2yyWmQ{Q-)w41${&NfN9mi ztR}7vL2>m}eVuM%Dnd7H@m8#qeG$c4mgL(7l@M#=_l>rbG!+gxUiD5Jc7mb{(o1q! z9nZO+cu{5qmrdQD!&X;(4$0>)iuB!K-$s3er0> zuCeg%0khs^cKl{H5dC%r%;GKkZ79ZKQz;f~^qZgvcQVltzE+(4a7 zvO8&^TJJNH<3)|r>qo5Pfo;J>hvv+CaeW8R=D6L^ybx|;Njbmv4JgM>XD8D}cPnxj z(tJYOk!kDzTAa(xw0OW7RXHciAPM0^A0(8H|5?Wf$}4OUBD6+avUUpCz&$|t9x#zk zfPZLmg9gtf&&leGI(ywyzas#PQpg5uUCae0c8hmC$+fmwUhuv?T!e zAhDVx>UgNT^uXDrcW|(O^Jq8sdovjtyk?~3oF=dh;XXWLf=N+RgsvQW?|CA8;>xZb zWH(EQoE8)Q96=Nn=qqJ7)L+)*jlwZ40{gJsr$H%*mym%%U%PMOTCn`@0b1L7H?e<| z{_@94S==`q<*u<3X2PwIhh=kj`n0=Dzen=ui`@fYfs9)h+mE| z-Hds;L=RdAeML`chLKTp{!!*1tobRw`LhsM>shV38N2D!H!}o#ZFcG2{)J5Zl&>y0 z_8mw^Hfv4&I3FoB3Sz-*vb=W{en}$A0@kMw@|vFu(Y{@p3uuY0vsUja3kMPsPK5&= z`aI$K3rWbX%}kdKfJDEpWIviAm-wRrJubRhn5{Q9DfOKZZS)Eh*Lne~sd`a&*;0k! zO=G|C8`&;)4lp4A1MtG;R}JD!w#vdlUvK3szB-Cn_@Z57TaKE;MhxNH!(Y9XL7AO* zb{XvO+~?~?`_2~HJu6&zo-M6hjU zO!%1yv_M|n0O2pTYHmBo-h2Pdy8T7#QY7?hP0$P`_1a+`wu<#lXhr^iK}hLSvWf4Q zBsu|6f3W43j;mZ3`$*onDz9x8Z(@@{p&tUj>WY!gp!cVG_?w2R9s1*+ba{;5#4bK$ zS_`|*KYukxG5P1(a0dM)(T?rj8sY|GFz~C#Ep^NH@1+0Pf*a*tI7k4$a!R#BrhP=< z)WyH}mi|?tD640GA!;Hq&6ZzcO#f9Fiq-;?eCOq2p}>Gso!~q*ts9?S9}2d2v`7BW ziIpaj{shK#%MKTu!~P*kLFY;U0v`3BmQbU@(+?fnTj4QXhZhKN-96x$=Fy)j@X*~g z)ryu+M*Sv!9X6p@6?iTDLAB(UL}`8rUZhf&CV+O;(V?ps=mOFmS@sSByZpNONAK_# z;Fsr&OUtA8l!1EfzR_&I%cL#*&$bhl*t}vOOPTOyVpD6f6fYx9L*j-8&jI`dZJg}* z`=7afoO*E&aP{Aby!w#7bcF56f>_B5{M7{i-Y1fWZ7zcZ5w7|#|DCkl^NCjd6XbR6 zFS!HKd~0x)?#!|NERMhIq|+alQ|WO}My2(BdZ49B>>-sfS%d~#bX5v7QaNr5U%Mn& zWBqIfv5Cv@Lgh3jU62V|yK1?41_2gHffgp7fNK}FKQ|8>i8AlYo5~eNN0n8}_e^|L z>;Nw4nKIsAwWLxBaA*DH)Y1>#aZTq@b}Lu3^%W!Z5;6J1{09tZUaZ(z zmRnX3$tB2!HW>e$YB6a%MQSSgi=mN@s(0CP50EMW=Ob$qPnr6cO?=n=rE~sh>i;3l z`d?Mom0Y#$f!`&jA^j6Y-K2^;89k3rEw4Wc&EosFJT4O&(z+SZj8^eB(#5=c-Ej}# zvDs7j*RB6S`O73$cp7@i2KzUn2xP+l|A_uu zv_{KU@NW-O-wl!-U3o(e{ltmJpAqrbYyd)>qD#czU z0UVN`-bQ1>Mb2o(JHy}*=-ppcDlDe-eIa7)lcPwhINb_=d*67@ofH*fA(|pwcimyG zO2@bDd||3FdgLIhU9Uq?`p?NE#C6-Yj1%sXiYAn%`YNnVjc<8oZ@<~xNP3@b)4wSk z-!iU9k0b~P;km{4cXm07!MoOcbmRt)+?f1=r0=pg>dtjDS5bs{ThD~}p43v5(q3XX z&KrJ@(Ccb{cdRJ(k0py7zFA@qk9eIZ&@GbF7|%6l8QGD4{e_tfL-`DRUYzNlcqwZ8MprlL>Q@_NH zWWQOI9!}CFqANA~QNl`K+!P~*#}V_+A9T&T)06=Wpig`vsotk={jfG{0HOvxxY;BX zEk3Sbv%ES}4_!-3(=L04EAm(A*Nj4Fskio%wxC>T3pwtjPT?|Ly~ccHgxX!6J6TOG zP%o;NGpoQ1$_Rf=P8p~Oia!K5`;@Xobs{ODgTX~}f3X64T5^B=-~-td`UTlz4+e)(zP&0h{? zcsm^{s8t=={h(WONiN2nKCX+Y-7YW?<>3h9aeDvKg=g}T)y9$#Q}bTaUBPnDo61gn zvF?5Q%s_utQ;>Ja-PGXz15YX~&tMKx2sSfIL z?O5_Cy7+o_B-OBwgkFbBu~kOj#zG*!n+<*=@r<0_4qDSmO1m>^oHqI6LxWVCQl-3X zxoO3vfGyDCLTf>i^K8+#ocD{fSQvC$-tAoRO($-Mor+zEraw#1R3||MYdc<#g*30h z>`oK5N9U&+Vq25qZfj1iz*#D}M{JEv0|%0l1VQJ*(Pb>S3~)mJ?CZoWeFLf)de?wc?}6 zG8e@Y*SVyE?5amZPx%-}sA3e{sLp9lGK+2Hf==|0&*%1kLCIsFPee>W0IsGo$L7wN_-) zlkWlQ#ea948T5}qrv3&#SLTU%Hee^z{^MV>{{NeSs6qnX2TI3YH1Mn?6)#7bytiC zGLyjey7yUP?%u4BjbYvc7_8GsoH}=c6KJI6aKdKw!22m(xb~+;%L7e`#$V#sUHur< zSklk%?4kRX=TkT7O{%fM+_4NDQx-Eri=Ms9pVcBHtFqdBVvt^8q{U9`-xXZmJ~e82 z!Yz5`-_oxz1EjuE`YDu40@s}0Hlw*3j*&=_BrJ&JVi2D}?6-h`z;#qFTb{Gt@$oY* z(MM~<5AyQzQV6*C;pyG+*_j+ipzmzn18D7dh(F;Q-viof@Vp-_H>cY`WA6c&3mSKn zem8C{eIi{-&NhGlywLGMXU0!%ru>w<-2cP4~<-j*f7}`IHdCv8Z4_OQ@7aJ z>jp;=x#}<4*H2%KFMAPpdrx$q`@sT#>Y;0&$=@94MKTLv0$X;0H*H?+D@HFUJ|aZBq+WzBAXJIf=Q8z%je-ITXAsv_j~>eUCW&*a zsSS+0L2UsWF=+X6at|2NxS6V$rMm}E8+qT7orkL&vUVy31{_MOelguJHNcF(3&bL6 z*hNb*@8H$RsZ`7Pnw=Cz0dFF~qa~oXilM36x1HA>FO}?3%j>Vz3o10Mh*~SV2p>@C zMJ6Dy@GorY*5UD)clN%4p1(+{NUXRBaXf=?bd});Z&+eW)3q@GWrIj=kS+9r`dVK1 zYnZB4~7(l{pBDBfjJt{6U^^y*(dzq zfXA<$`_;!zi(yDr`B@!G8Zkj%VO(|E6c?%Cx zjm0ZpZgEVx0Ub06SH})j8(W0^FPj!Wrnps$+dr8!n{A>RNx|S4@M!e$ZE89=jaVl# zQs3*@F#s;%tT+S=*f22K^tefr^~m)6uA|C&`B|ii?PZCmqN2L$TJfWovNlAb@%V7s zb=jzcfg#Mon;?IQVHf3cM4HkE({>md)+_rWI$2{4iE%jK9fZf$ekS|Ld4==**F9pv zUUUgxr=X#hxYtC_F$!aKyQaUy*gMg*Lok*aEN*xN zEI-5%wKz|o*}Kp9l=ZXQSs+3cmt5F<=6ZsdU3LBz^76O8q5tiLfmOn>Eo=Z0)iwUa zOMb?`MdbeN&qVD1YZpAN3Jz^8OoCz@0j|3h6#s=e=tJj)N&ZiQ+mD+G=*+;P-WO&3 zRVPhl+=1*7nSMwpOuZT9Ld0GKb~HQE2qoE~apGxqXdB=K=KhpEaJ9$M`QkmJN2H@X z5*YahRTKanYO-(FP3GGueZ%N|oaxnbXLt3fBO{Ln0VV&(lWt@9>_+w1R7 z(Doy{$pW;cZ%-$hVY_R1jtV;SK3UH;IqN+-px1~W z>w#&+VbpcRW&-YnnL5bIx{19Wp3rR9-2;BM=-oarp8eU}aYfGw!tID>RcY{=B6GOR z-PlZ*tm(3(BExMx6Z}Z>16OTvlXBGq(QmAcyn7T}k|KjoXkc~G_W*L#f8G3-5dPH@ z{?8l^XjJ!rxt!%Y1(;}a!QFPlG<ujn#r>31=cv0nMwEgFEVg;J$^0M5>IMg;p zukQFfNb5{M<@2@BOaQ5FG@}V46FKhp-F}IA`X0`nG<5$3K@o8JYCV|tJ?YN{AvzP> zewM4t$Dus_kMwP$u zV{kNmO&hw#N8@M`x+Tbz^R0-elqQ)O)Z>(EZZCBN@QK8~<|UYS&P`SBH1fprHDkAtW#Vj)*Z7X>aypEF63_f9#Exs5EyYcO?E#1YU3qYAj{G@ z3rxSo;5hN;v;aALHh7}Hwp=swKPkVy=D)!^=0Wsx5u*jRPqV<5PAvX2$2Aq^0yOGhPon# zR8Sssu)l{b#9En{m=}?7Cc7A!2Q_GeUxMnXEp>oD!1mP9dD8K$a+*-1fVT0y#KCDZ z5Tq8jy70pB&3tiBajk}dqcVmxo-hA0>FOgAFWqye?!}U*JluNw$FJh7ib?foz^1M_ zpUsTtA4PD#?VS&P{gt~FG@Db_!b9Z(z7UM}@O)G7AxsiQRL&IY=4o30#BpNh?a_Qg zJ7`m3dH%N*pN|ERSXvzo0ofsNFTN;Obg@Y?Dk=#ci5r%zTara-xQdH-+MPEeWQ)CC z?+`@4&|-B^2=q@($xX)?g2-7M)ihgqUiRkk&iKrg!=LVlrB4se*MAFQb=(T5qszen=SnY~m*Xvnu zY=gW{HhaazWU}B=tebYLSxnHSQ5Dvv-eOJr3XDwc_cD{^s!QO(B|cw@aQdNco~9D# zz?Qkzqc3cM6-m2|yt{{GFQ&yReC27-2B1b>KKgsplM`p^WwnJIP*=^@>*qB7FE*P- z?78;^QfwloF>KbMbU&#KBAgUdQm-4|bxY#Bye6aj3Sjc8M#R7%wRAkV_&xDfWur>_ zu^sBePRd2p?>cj8rS18MKs$uvMZ8|@1urpQ%HaB|l(5_{lcv!#Q$AO*jov3@H!YbM zhgZK5Ku5h*Qpz!0SP#+-p?xtNG7i0H_jkjuO_kaz08rV~^ z$trUpxRo~y;U;M*o*2(5u=qw>RZ`;s#xHB=i-F~>bL>D;BpCj6W&oj z@HQNo@hcyIszE8x41~|1*#CP-7u0_u5vPXxGIopZ+d&gOeo?nf+(kZ>_*3iXb&NEm z@(QpXuJ?vVH;i~mF>^w^E9~oQSv=$*s_+9$i+Kfu)=O-+`Y(Hn-uHmhM#;N=SYKoB zma(WmFp_&Nnm5hb+^EsH2`viq!{CvJAzwWm-gAw`q>b3nw?-Ok-fi74eV^usf_wKybOwW~ylI;PA%9M4h?sM~#(?veG-*~^-} z#zWvu%BL#SSL}y`T7ho2IPV(|U13vo5`ig0{+~dHbfn>syfpo$OWa6_BjsC%nq%oZ zoY?K6CO| zP+~~!%lC!)qcj(Z{Cf}^- z(&=zXt~lZ^oo}Ulhucj=Fe{BIG|xr+9B=S+RRA$B3Od-#Nu!5iClgWthkY28Z0Do|}a)F^yBB9d@)c9vvbs_S|Z|BMDx zdlOPT5E`g5bb_Za8|crAAf@a;wv+^1LnyXOo zK?9?G<(F?i`>}aWF8O&n!I&t~WffIs5odE;9d2jRp_1;d(7m!70erLz*8w}+2pTI! zoZf|8R}kLa;*@Iq0M5?A)8M;YOT4NranB3O)uc~0Wu4P@4`tDP`rhv_%w$>1talh% z1oZQ!;cUK@V8Lm&IyexNWihKkhqmN)r!-_QFJRz8nEFoeu%2~#sF?UQl0K$0ZX_R_ z`{Z*_&zTOvhFAA)Sx%f7nbwLaTGtAuigsDXdgO@Q2J{==o4!h413S_5eJiv>Yz)NZRDpmI zU{;&wvF_dLYP(qmkt|yq^bytAqv$D0IPTL-Za{H44^z5QcscV81?H~8U zoZhasS-f8}Njn5SesxCZh5`RrvOb;Br*vvQZrZCqT{gPh>VUUTAP=)XJN@|CVwt<2^C5aX6LrVCvlva6YZ>f zy}1Wuec9-##g!91kww5bW^G*B@}X4xf?4>;EDCu`Bd&fkG;HlO)pQ^0k@RxXWv`&7 zRO*(o@xt=sv+fME%Ov0-)!a%B9-e9g+I^2*X(NlAQiEDOvw z>H|T#N-VjV(5?1@{otkR?b_`8>DBqED}Nq|Zv@48L9UjUMpb>JPch;ivNOjVG>AP1 z5b{w$i(Xo@_AUe>Il~{mRd4Jz?#WnL%spw^XR_fpWQKeIKOj|5s*b4FnolobwVh63 z+%C{&>*P9}`dn&Q2vWQ@`mBI^e7!=?NM`)G^r2;qCYBu$5pprO;GfOAf3o)em#0|6 zdbb}v$b#} zPxEqCT9QtXST#W3ES2k(*(`6mTuWZ>o$fMPk(rL0<}~CK8*;yI50%awHG6$Nj>B(A zmf2#|Vz|^ca0HD!9lwyhV7Mb5x(B?u2MBn1iuc|^5LV-U;gC8k!vv9Iq&rSX(GZr- zO@k}J>&T)pQz+T2>a5hDMV0cuOe?Uvk$9cLUnyKEzZDmaAx;dSi)D_*wKGj3+0LFbdHPKGlL=?P<}^N9&q0J5w}k?!9`Dw+pBLK7fD7!sw>r~|2la8)u-H-Y)YtUpD$pvk)TJZF8yLyI&@HYsL7j@Lj&Ap+D>YvhV$^ z-%gwa!j%URQFFsEt%O;FCRMtLqK~DI16&tzT6w992?e~LX=a0}#QHh9-IIeH?o0@I zi}490(Zdp%1hgHMHCd#4W`x6=xN>S-&fu^;A*{`}CZ5rTEh2ZLLTK6JM@ffEg*uz$ znpX~}l7VG|`Qc$zDf8)vC2ps+*D`J4D+pyMWWyv+BWc{vLptIiQvUdr+v8U@h$X9r zDzi#5A?MexZOoQD57T%_xcVV!nrklp&Q(xm5rZgaqm3D5U8K{?`GO+8hxqY%KNo+f%fKME~ zda?>Mr!7xdqFm7A6O?P*et_A{CPK1&$=DbKJ}$Ggrl9coP7G?h4sDbUUoFOHkjTd# z^*w*@_Gn@E!oY5c@|xcu-wB28hUWP#aYnE!epqed%u>*a#=xQAl*!xe#TV~^+4+hb zgVaU54nx-pADZhAcdH@2#JJI`)+8%mU%dW7JTnk3>p+mcJk9#b&EIjnViVHxQs*&l zQwu3?XTFS6W9ITnCs(Y%_2r0<%Zbc_1M|k`fRz@KAJMrN>a9 zI8{D4I_(jue3STd=HbA1#wV5sdvAxBkc@NhK5jKXSSVVRLY6)e1{3RY$)~enIo}{c zs-GP%b{$_nR%BH>D0{Bzv6Td^l_apJb9dTxw~?6(S(;DCw%Z~#XIUAjDt?paHAzpR z@pf?N4$Rf4S@jZaUp}$|3Cu^d5J|#|u^rk+5&cWI9-Qw5j9<}7_X z9NBgE3t`I~!DPC5I7E)>({c7T%sy@755hM}89TqI6*rpW!fN+unO(bwv4dB;sM$KY<-W3xQ+X(LF*O^Y&5GOEsgi(8w&iT%a4 z%(AQ3_aUvkIfYsAk3-`FJfU?WqdSX}l3s6<5&LEGfov>^q0O`!7c@t8qkAc$!qjTV zyf!D^_LJ+(GX-I#I)=i#(5f#11M(h&kW|eP&N03-m+_kiX@9OqQ?FlENIx(qNx^6~ z6%Wm<=-Iw0H{Q8`dqhS@Ve)3{KxegT@TkPBHbi+@qD^29S#(__{|_$yU_n~!S<@d} z)Txzs5>B$7lc~O3VeOD@Cj?ND!m!@%^~Zx)JZK`$7;k!Llm5FMnLY&(tD(^OI+Ca4 z6}WDTNKtbZoST^cgWoW zrt{4YW=-D%N?5BIEnJXd;}iEf4;STT|aSbAaFW{qhfe4Fa|C#skfU{-kae!7d?8aIb8 zgwoADy`GQqB;z%`5UzMs<6Ne)V@$`6p_LXmZIPq85x}D}JtQeQov&D)UC`ZesvdK| zogCq`y~pePb{jme(-{Nb0P8;`tZJ#BI z@49Pm4z%(lzs%#w9HdFDwUo7y#WhB(L0u3*vz%vtqoT5qt~eKe{IQg7GHe$FIuxq2 z=_^+`D7&8df$pFgVA|i4yf9=ys&gHJ0rmeKOZZ~r;n89hj5xje6}^2hW&=%GvzKwA zx2wj3+uUc@mhfAwQL+;nhKwbH8%j7erS$)G7n#6T5e9?wVm9BOAy%KS>DOJ01r1<( z_&nFxM!!&dMd5ZdvP8{?p`$HR6VLIv`aNhJ#)bB9$No5c@$8=_Qp-QE`gLB&D+Rjo z%~ccsj6?jVSN>a1(Y8wd-VG3X<^rEMX#`?uW1KEC$xRhN zKs3`M;%F~1ODF~1X$>!NgkG9yCUi<`J&mb}UreDcSvr=f6*}&QmP5Ew+gFNw^6@+zJ`My%HKIR3s(ugE-678q?Az&I zm)B~e!@u{JBqXx$Hh$Ho>SP*Ri@oL&3pJL!Ak+Xg z(CQRNil9A!#7pJ6)lYIh7M!@njpoK5UfVzFK z#_{~E!Y>vkT$+aM92T_KAkwoUTz}!6xbtj}Znh@x>?V>;zg6PMbdz=+gZcl$|B0CE zkzKaLEvf6Nb;FJ}xTdhBG*Lu85_+B$*!HQ%eh!1xT93q znZKFx2A15VA#!Q%XH4-}Hf42qqbkC!hbQtyp4y7dWQIp~jD9N%2OvQKdF=01+-zj? zM~5Qo#N^p8=7eolj&1r%5jyVC9JW?4ljH)%&g@atLrbCBF z3Edr+n4sx}j-m16<{F=aC+*LRr2Zf3-aDwt@7>!)QBbLZ)FiY}q<2Cuq7)IZ(5n!t zlmMZJqM~#{?_EShdgz2AHPq0A5PGknOB3XK*z@k+KJVV=J#+TFb7sz*8UA`^o`DPt z)>`*{eXiRk49q`&K+5X?SM{ViggM>svaE`!QUxd4jA)uqbbhJ8w|&Qn$BN$B86?^^ zIHZ{&+cn%;OyvIB)fz!cXKJy&*)VXFReheRhnV z^7v1y&RXTaZKZdMhWi8(#xWho44)g@_d!c#0Jb2>1``5S;S3u3{ODep@}6%FqJE8Z z^)Wy0ZD2rdh*(yF`aOqYYS`33`1V<@P~>o!&5;dg(IbKdNP$KY9p}#D+jwrU)Ppy6 z73sZd{CLS7Wq*zH2qCv)DzVu&WpuKHk7#1B!zO7NiS56(Q{Mi6frcu-IqoQ%t(PJE zvO?_~k7XR>07p0*uTpPka@X2%tpXufRNr_vT2Am{h76QiIZ2?=yisxfpok^+)n^e+ z1rlO*ymD1LB;Q$#gq?n9p-TLM{vmaHQ0n}mdNLmMQJS+?ssd>X*vRQ=hGaZ+*$ z7iw@&asSshn&C$*Is<9d{iI2cR$fr@Wd_DoNmNM!kc`hS9_q*4?wKO-PB$5HP)d%O zd5>Sa`x>k$wY?h~AK$gbi7*DJi{A{SCe8XW$r=?{pM&0d3)ks3^8V7-DxFT&gUs!< z*$vTYlExHN25wD07obET9} zVv?c+HjqcBIRCeLG!OE`lbqfFbFB zR<|fQ1utJ8d^c0yYdW>-tTa!4?u#|-!UtdFvpaAVp9{-E|fV5CJ7&CRoU zp7X8ecZ*JMAWPMMh;B;v=WJ8frJ69-*MkR&HPui1SjqnWwyB{Ruau41X%N8vdxB9< z-dnYUK<{mq;plx)|F<9d|Ep8cUBl>eZ}MGDe)BTW57c{B+EY1nIQTml%OK@b`HiR{ z{z>nhGcut~bcTapoLk$J?yWUVpenZ*{sNm@JE6nv1o{a8sH@!>{S zb65LnYoS=-=Z)3iS{L1d+usDs`fTgENh>WDw`I$n2z}1WxF@sU zaI>cM7O>fW5@#u?&iP;)I)AnD9k@SIBKAm0ujnAhML}QBh(oR)WL}K7z1}prpBIvI z`M>k6__81!=cSU9(*=<>-uQi<6hn>8YlDxTR0Cz7#~K8j*r6k=r4xCk*W~AFm^`H$ z#^~Sa{{JV&=zSC@VyTSP@g|2A>wiO=s~;sZ%mly=J}VW$b_{saCn%oL>4@I%&AWuf zTvHeQ-u?Sd-*2g!)zQLe?dAn&NZ8S*3_Hno34O-F8-aav`2V_g{_nY+2mjg){!i>Y zWb^60_a#O-)J5uYKD_)x=^N=sJ-GVrur$J^pYFiAfu4$Khg-54gnGJfEPe>-5N+Ki z968h=y?N2(<3|_&e_y=2BadnKR-z56)VFmU3}ApBLU!&Ns8lcgLxeWp94-|c{~v#p z6NUfv$^TC-FaOuj-lKrj_Q{qTtG33gBTG_~-?}G{fZ6{g+6OxqUjnea|La|Ld*Q&o zLrUS+pIsG~|K357ZcmbMMGKnoIJE$7JAjrn(o+Y45F*zb6nrj0eYf|9<6f&5~*#QKJ6BV!?Ca zZ}&e$>%-UU-`ouC7t>iVsi*_IjEr3Ew;fB4lY}sUj$QT7%9t6yT5Gkgy?=<td4sfGOD3BJs* zI;wk&hkh$Q9HMQ|Z+Ld0nD&tQPzfa{x|qKt&~JXr)WAou9>qcyhtVfAUJ35oL!=sI zjLhfCTc$1fSMRx~*{~mId0U){vAB*0pcT(45Tk9I`g|6nIoy8?pJyFlcx%pFa`BfR>*hYynxKL@lAe%d2NU$*ojqh2Z-y~ zB*&qo8~w62KvV(BH*YdYCp1g$w@#RPZ4@%5JU^2BycX6t#NPdtrC`BNb_=?q!j2iG zmWhsAsOa6OPtQA!p}7TM?RiAWjnT9WJ5H^@+eX0+AEt^a4n;$CemZILG$&~CoF~P+ z5zk`x?y@(9v$%ZuMQu^w@u6~0BUc)lb{4dej&vg(8Y;VxYJHaBdFHHQ%H;2j%dAYW zOH(DTxqmiB>h#o)zFyKl$GgQjh`YOL=V!6wO!H+fQK&mYjt2kPah|z zXt~?nRi8~igJ*@y<*Xs$kEM87HU*#TjkSb=WO*(yeMf9FMmfxf=>l57S072mZ)P?k znv&yRZo2KkAaNgE#S$3wzoQ$a$&IAvz(8&?R<6+n-BSK4P{o4c`j2q~svoEyc z`(bp$h7HWL^bgC!lGmr7lxiq81G4GnURBat)nkToYGM)?mZflzfz!7YkNNc*s_H$2 zLja-ubA_P9*JhtM9ujjpHCjk)-9b|LJTd8z1<`(~*Yl8sX?yUYPGgH8Ay^rW)x_j- z+$&r`hPSMjS^udMUHGyn&t3ON^up79;V`$6cvQN@mX=+7)n61teGei_{t2Td%)+*? zcg5||&fy84m-}y2`G7uG@`{brT_4%vb>>^MpVu(ZBB`rl9nsT`FoEZlxorY|OY3_Y zjmZ&}0dzg}Y>0Qu4n`f_bd1nwHTcwV$E@rXaq+=GU|jR9 z1|_&2QnFEb;jXyD>AzXSca;gXXR}|A{vN4``eIwNCav)YJAKnjy%U){_N>5W9KSjc zh3Qx4w;byCDFYqJHC#NZ?XEr#AoC50$;(G#szniD%mk;W6fRMBN2i-M4%cK)>X7IF%qY0OS#N3{^C2*(FS4r_=|R=R;EW4ZaDAGhuhnVRqbyv|0OC zvXfvzlGiUn_)!KKQrWL{+(_mpYrA4-$8|t|^tT#LYZwHidRPTnq*@k;Qn^jZN>JvZ zF(26DAZZ_l`tn2Ja)__YSRKJRpof7 zRiIqNB7)c- z#hqVehd8p;st{rw>=P}H6M0)7fXE(0TXiEe7ZWV5vKHXtBd$~5ubKL~74VQ?{63)v z@*uD=<#GAK0K{u$jYcN6YHnJxFuxR@?&Vl!ri5!qSk2Mwv34=YbTt$M*j}@F2-qT+aJ|K&bk$Y|g8i?5l&H52>nzF7(+JQi<7gc%RtSxur*`KgffuKc zlQoq&ts$Ws?Pp$Yz4w0CTN0`_%<7QqneDx_FG}FxcpM(izOG1Cb)I?hv4md-x`<|t zXfxtNQRGtno!(aqv~tbXm{4MU&F#8vUT_z7+*Dj+d+TvPfAk z&G(GnwimTZzj+SwEekUyzwK+wRf>?Y@T#Y9oc#)NzWcq3VAq4|)5qADnq-%fiM&$V z?oof*bG-dz&wVfeKCX&xLG)e%+t}^8#@8+(nlUA65LMIUQB8|C{M>h%Lsp!+!F}`f zQmst5^W!5c=Nr@P%~!wTsUp*IY4Ahi-pNAjfCNjAE?^epl-@k#1_!Rksa?kU%Dp@< zLRQX73WnOSocRhkXY|F4?$JwcO4nj3ecWXo+(-8I=4bBe@|w`V{iI>jY>Fm<30zt)?&b%8sQhv@8Pi8$sLkHUcx=wtZqOq{^>2m zZ^$>ohw7uaVxZ+uL*K}?3mwlUdUKpMvX)hVhMYCei}jWv(|wZ*XjnVH3`EyP?%T~TEDP#;Vn z#wE)tSq1$%1NWUCA!AkpJ`ZsQNfvjz4>*$#R6sZ0>-A@u+lf?RtGF|W)noP}I8R21 z2S&>F?^_vno-?1H%4?ja_6fXq$}wzc*~2Bey7SdTh!pnwQfyV_oJ-&my=Cdc@_#%@iV z$a-NGxH-&W?cW4QNt^~hLG zTrA-8^$@DJ_fjfU?H*KBljqy4f8wv)uDrP=U5e$Ibidu2iQ1^ss*|Uja;e!^p^Y1{ zKw6AP>Ci!)+ji9tSeErwc?cECl1fv2C16bbM-G5C0@^itOmd!&iPI$$^;&ZNcg-0DRWgkI*Tf9&-AwMCPIg3m}<$vFcU@tO7gkUMReenhmb zjMaDc6tRwRz^eQnjZ9(L9yw__CPU#LBIwKoJS!EFT_IWeO_5nB(1e5@gI5@K>NMv+ zo0wDkv-2(GAfi(#34<2RcE2!U?a>4G**@01TW6fl1W*r-+)^ce* zoT|``kJGjcC?^+o7nEbGK{uQgpEbOZvpSaY$f7+80?*tU{K*5 zp5IMZweU6`UB&9i2-nG8TUU^!pqBD4FMPsOG65Bs@=9+??Kc6SbmJ(OR*8q!6t-jG zQ`f)7O{v#8kYDi)aF>{016emJTX>rJG{r|F2~WxD`BKvp4toRE3-#K*HXjP#Ke{cM z%vXU?2?e?G^zTel^@z&2HBqEr1utCU zfH-Zvp>{}?)0tZRiF#VHo)3{_7#EBeIjnBVeU+8YXyYa1JB{aXhD8u|Z~3ynC&+xI z^Tntr_~+LPoa=wo%4M?$YaUCYWm&AMj}rX9zB@XulOJjg4*gO8NUzp^_g$rN+F0^| z&Z&oEtdmTQIz(h@kCP}k-cj_r&$q;FEayd#MXl5C1@ELWWgM9{)GupN5(#hY^goJ& ztWFQbFOdK*8$yi>4yNu*2bvTnYSX4Ivd6b$Y8-gpdhA;@9qpC1XEDSLA^-=gGW?UG zKHpbB7L_iLc1cZ3R(3?=9BOe)U3vca=$%@*G~!x3$`s!2(n$CaRj4XxH{moGBswBB z2#ofO*{#LL5r@n1n9R46ewlXLf2fjCoA{m2v=9#Bx6UDB&+1IiA953bf4Ex&Ex%f% z)2Sucdki@bs!Kk9m}Dyoe%nJY2EqAyPtD`<+(2CkROUHr&h2hj4Oq@)@|sj8k^WYO zQ|(XHD8kC>*(@g0f5FYr)i=mEYK8&Jv)nH)gVH>Mf0jplkR>=r|Y0|EGn!HXSY%x_HYFfkQd*&cM=jS&5WZP2+Wxk zYmrOacqMYPe-g|zF9p=Io6F_K>nMLRvp;DgczK*CvPTBYxeHnSUWLy*{qV4yX;#jn zc{V2~^QaggoaHaD(<$2~tk`GvO1Nh6?a-;OqII@Rx$=6Q$cw`|jlT7Du+Ew+Gk)?9 z$df#|_wf7@!sA>X+3cD;6e$kwCAN_)C{|+Aw4NYDnz&6q`5w|p4$A&R6nt28uyP)YCVwvaLALub1N338)W#m#=V@pgz z^53J=q%>cYtw9lW8y>N#U&3Hh*xd|#8uDE<_;J6rL1(mp4Xk`|(P(}q&l?@V$%0lG za7WidjG)m3n zbT@3@yGJ)zfNoZY37EkJn3)Vlc_GJku#QjaS_GWO;C=^mX5cc?ic z{fHx_2pL4o%A=X0=7Z+1Pfco{HC!UhmsxEDT+q^+qMjap_l5PPmg$!5HDj&TCz%R@HQu$c9OE(<(yNdW}r$ zIl;F@u13~tU%#ZGIYkw(9-eMKHeLuLGbt(4R8z0QQv%#63mJ;2V|B=Z%*pmXn(G2mC+ zFVr887rl(@r@IK_UWvaa!Yopap7%K&9Z@+waHCm?YEcz6CP%I3yv5$bC5zpk)NrRU zmw-ko(>!lY{N9`NpSb0vt8zsk!F?FWpZ^{3hMe42xyU98}#92L&hjjxnEgH+egeqRd<8M zy&X?-17)34?Dow+!B!ppD2OQG^Q6H^75aaXWu5Y_Oc@>A_JGWXbst=_`}(1*wr8y` zy~fcM{sTN!e|x4I{d3%tk8}snf+jHHb(?1+;n@KEwC}DUX+sU6SnVO?=0Y-F_{<2) z^ykl5gfP8&&axtD?dSS;vfi-Sl6Q@6L`{!vW-ORBytC#$-UoH@!$7O=mVI^EgV|t_ z$y1!kzc^RZh*S=BMNZBh8=PC5%wi~4%WYQ@0?dx4;=dGAB(;KB`AL<+M=1XsgU*uW zkZ!`uNZLLtXw7se!8|^F)H^FA@Tz%e;ppqk-5B5602RUqWIMiJ)f)NSDyo7Go$#c0 z!208pMb7O54dA~mnZXB#Fgz{bIXQYCdgQebO|7Vr)jN9uPVcVj{fUeJvZxk8$@21% z4#%ltReJEFM!avn%%F|*_z&B;So`6Xztuep4*s5#VXtd~`m(r!1PrNdwzau-40l$3 zh?V&ZBQoCRX6AOgb2?%<9ULf6++M&>uP*!sR`x^#k@Q&H*V!C^cuY!u0}f%nHfzO-ph5xz~dj`f+Nez+oJ}mS5xL>}ciKEw`P8GVkCI zHp>r;jqUoLv8Z!TmwJ8BZm;UTlU-m07M>r7s?%z2nWOJy?Ua%Cw!5l1j$>$%eimCY!pN`4C$%JezG~FFp|Y_*4zk)b$V(HK5VAHxz?3KnWR| z#AdpCB_iaJQt zPZ!G#xw!9}z1WDa4t9^n+UzQJpJFq1sbX;GMrb}EFuvQuRh>3B6c}FXYQ5#a#1UY? zMI#FluO)q3nmYnHZ?YygTmxkKtSZz78|ti92BJho6ozfGX7fZ?prc*J?#TxMk?crC=$x0s3F1fTvP!VM3I=3XoPO{l)^tsE^8DMD6roeRG%l62H!hSHmj z;5^+)^TDS3AWzxi`EAj(t6}OWbuLh3{6xU$;KBKsusGCNkdjTN%K@3mDM%3u<%ee; zA>hw>om-=KsaLeX$afW#jugAzM!R0FXa5k1;n$>z);ySYAgCL*(694VDWT*@wz+oJ zqVJXv+d0O6WN&h!V`qk5;Ej|{;KVT+P zH~;se?B|-Ld5#cCC!Uvj_0QL34Z7v&Rb|4&RWlZiHg1#YV9YOm^0jp}lR~HsTQ3`> z0g1JA{?`vdPZ)c@q}GmDB_iPU)kAaT*|G$_gPtcjaD~L;)KFfJ>dB;7;&s9dt*Y64 z#m?Y}+8?JN{j}v0ll9-yiC|V#fJ>v|xsN!o=5Tk9yviu&=F{MBC0cS?Ssu9?=dxP! z(KDHhPu;Bg*qVn8NaWhoeDlnMbOGb>O!qdn))>RakB3M z!2ZEt348708!1}jYj89B7&Fmn5KJe?o?KW(Z~5&;xdbP=WD*qEI;>H zJgpAwxrEM93d?LpsI|EO94!vwO(xR7$sZNxYKQqxs_RY^$lgzFOBF1he-Vhe2BplI zCCR)%vY)P00Lu=1B)o0sg%um?)6js#(7Cff<7jC~2uA1$AH~?Htgs*z-p8w9Lo82v#Hi^-`2p>mc=iK05RTs`q1Y*92}B)RmfR@qZO(6WS5chCxJ(|j*3iDP<$>QFJ!_5fSa z-`<&JtgB?oB*}L=E&NQN8AU>>f^U!%K7MGl#~XSD6NgoW5oS zQwqu#ngX?7zYIo&XfdBJ2K`>fNz()vdcs5xb#->TeqOSgbt{Dp9KY0=l@bduQ+_45 z0lgH#v7&PF)F2!9y**25*n6=XX#lL|N|lzC;-35Ir3rAivbV93rG1)mXeE12)9r$R z%Y5!t*1Ibnrkmf^KSUM|1j9&QAnT(TDrlJd9cQ|D-_O}e0?{q|f(|e_>_F?AqY5YS zcCf)sJ^Ww&x|=|MY{I!L?o06Z;s=Uai#rw(*Iz;szx-!Y%gsPwk^t}x!BGCcLokHs zcNtwWJ*e$g@51M0S3_zScyBIpL6YBF!m*Y={!FsQIO~Gbs&k=FSu$BU>NwVlwDd(C z;G37vOr*o3-p1NBSIf)FCH^GB30Qq%P3>{ypL584WQ)?}+(4|yNw9V=zo*wT^Xb3} z{hsdNc-;G+6{P=Fc<{eavMYX44u4P^vGl+bUhk36jL-4Q6>vcW82KTwVM$l5wzf~{ zntAT)ce+!QX7d84*28=FkIqYHLXfeUMLzMyH~o8eIX?}ZH-fvt)bqiN>N$jWE5?Z_ zFRgZ&$;=JYD%J6+3Crzeu(vTK=_{F{xB;_F%E^^2(;!8fnYTl(!_FK~#*UE@90L_& zv%`Kqar$%nmWQHs!dsKzZaYD7pv%sv-!tuCM$<0ccS3?ahM2TX?ReyKLyB*jK$BfS zy*d*WaFEU6BUSLTqf|P(%&d-)!GqrtEBz6mN0ke^(Pr_fN4?_!&*Krt!p^pyO~~tu z;mGyK*q9Yy;T}z5I4Hbl{QG+CL1v>x-v>1Ne)8Y=hc!<5!0s6U#Vbby_}qu8+dovc zcE+{8o(5VRh{B0UJ8SfX1lT#$B=+AsY;tyJum>viby|2g(Fl1dv8?}SPfNj(|Gc5G zoy2U@h2Ao**Dg&A+mL@1o$ao8ctOO|Cmi0e0FG|#qA2)a+wUt4t#-AaBV?V=b{yj& zamVr`xsOsp#(Q=~#T}aC88U@6iixAjQy0ooJ0G}s3z=oBQY>%u(fZ3IQ1_&L1rDAS zt`v_Wo0iY?<3Im8Oe1Fq(-1kd@he-+8e#U9gJ`TP>;>x}+4jqOkNPs5u&oJdvehM} z!r{gG4P%q`KLH(6-^=?H6$O&!8DQ<1MwE^am9rVz<@`eGX6-zy|m(sZ>S`@Ka&{F8(EVGH0 zR+?X(gskfuh0O?TW2|i!H&jlxsfq7S<2>J)W!JCm@$Lb&Y(~3M&gOOcRPQGDU!*0C zvL7{Ik0(#P%1Q_^P=*e9k!yBu8i9F}?>P|D{s$9t0HcmA&bTfapcW>4Qm%N@^Zq?d zs@!k!HP`YlQMu5TxQ#)>PI7vovKc>8ljxfh?ybecR#M4w4!f#Zf*e;l1^&=`w7N8B zZ(ph_QR}0Pudm1aFKvf8P@Z3$8lH)jI}2+s0c!l}HNxgxz*Oh$6=Hjw#qdktf^iw) zch&kt`tch(sqBb%ik6h?s6Mcd(ASW}7nGx53}=1PCakbnI@6B&nDy?|mG+-@{p2Fi z1M}Ffg;F|WlHsuHU7rHQ%6grD=l2X!|Lc?g_Dq7xp8=9uWA&3wAJ@*QLW}(k{@`BT z5V`*&oAPfgBBRT6g$_GcBK{&`Zj81y zMpo9xWAbpF+pL9I)WCLqDsm)6POmT~;g=iqXWV@%{xFNz<1JwRSt(iTL-Z1wU4m)9)mKISj1XF;Ez48nbv%9Pt z`4i3Q`4QUufohyhj1#s6_)XsOk@ohNt-W$Pn1erTem`(dpBNB#WT(fp!>!dd*gQb~LHQ&Gkq{!+han?$2X`54H6dEv2Wbxjj@$QLunh%-ykU@JLR z;HNzkpLuw&oz3n{KWo|1zW;pF&R}B-_>+rv=b_%vL2aLyp6D2KC?sXw_M1$eT;(P2 z+J>OxVW*$QW+D3X@zIDa!don1*y7AZm4~(jMW7(~`F#Tr)dbEaNG9ji%}yX;BjU&i`ZQ*1;%iR9_ig#5fJC|Q$jirlNqGYmH-he_&O1o=JMRH4 z396dJtBSVj-Alfq*#ib*4d*B@IP_yuyn&~)4Gm_R6q1Z3675h9Fy9PW9lId7bjVP$ zE|cX<wdyawn&y1N=3esb)s+{g0DCs`W)o;x6W~rn1dAm$CcWrcxu2G41;0No;txGQTw(Ad#qV%#RwHX9cTia|VMUD|2i>&n?JK9?< z+qh3JdItUsYaH8!H+P>NOW;2%W?p=7+O(T}IAkb(ZM4tD*if*qM|d&?)x zNp}4vZ8N#h%i-%>Kx5JdMpn#WAWH5N@lg#!fGV9(g>f2+NS#!IR@4P=3pSs;U;2%m)HjDxu{&6jdphuI(e#{SZRcHyPG) z13CKwWY33f?-~}&R8}kKjDI1aE3F;KksHQ1_f8NFf4b45fdDKYHJs1soQ;$j0^QWq zAz4kL<8|c_e(f)W_egJ}53?f*Ay`d-q64Nk(IJOvIC4DNdd@wSzWS~dd+qv{^Nqf4 zwUxreJ?|D|5H>c_bME8_RJJNJ`*dp4gL>_;<`d|50;-c-S0QkxzF-#7+#i#;bhS2U zcwf^aN?;9;7Q&VEP$4Vc5lL#+1t&qO#W?4fe`ma6pnj9KfBWnixBp2MKUcZ^M_v_j zgiLN8Xi&BFig?7CV9(fWe}0n7%VV=~;TbFKd8=zG%zkGn-J4;)%4jC0%$I$XK%}x@ zz7Mrfex9AwtB)}dGpjfGE=TSap~`Gtwqt)NWJPf4Ouu4L54B9_8tgIXXk)PY+br}@ z0TmVoQnD>4+DW3_xMh5fEVrltLg9K<)wT`~#KsbuX}bE@5c8P(MDat8jufM#3o3c~ zKSVlXTbd7!HE{Ab=e%GZEXi_-{IiIq%hS^35;XgrF~vNdT0hG17YHAN_n@JX3%L}W zV2?Dpxu_lXbwPpnGt#L-r(t>In%!^z8|w1n)-fOHgM)I9PXTvtK69tLm$0ha{bj-E zep0s0zu8xrW3f)3q*LUH-|1Dn8)6<`DKjW^HKO?bNj$8Tq#x9ODp}CI!A3Ys{$4ZT zI1wC_K7sfF5|f`Z+Fi|t2}V3Bse`=7tKN9Vsf;zfYc%AGRPz+J^>413yNEaA{jaL- z8qymj@RK8~-1e#1w!Q|Gq#x!|*rUo%l_lMd+GYZ5pP$^ycsQ+r9nS)_eQOMRlfQ1^ zi>C6Hlee_)<`*&OHSFnB?v`L5!^iB6t7`pJ6t!2U4ki$j!^(Tm-5Ao5;_MuWWJ;os+vF;H~ZokliBXawDW=m5$ z7tE)E2A!*>8FsGU?ADml$tmNbZ{=G58h*=FwXYNBd^3(<2pS6C$KY9h^-T|VyRCIu z`$o99ozH4={d6YD2?^#NOKa}ZrP|t2d@&);T>aS`JT!`GY?|0!pc8zP%5HI=i*7Iv z?F_g{3XM!HtdLp$Y&O=4@b&*(L5V?gY`?@enluk_HzJ5@)(I)d7O4`|uxc-qpQdOn zp&I07zWaD8fuU04+SQo9Ei*mzi3%LFe4^0hRl#!O_gFqSae_9}8%e*J#%pcFlpef4 z&&JbWXp2bX;Y7;o`_H`>!V1m7Sb63W3t%9$^!U)L7w%iU(6y!;GyZLJt^hdvM>5Nloh= z%zS?#u+a&yQi9x+l^$pR{bpB>!?bnPEiUt4sBSJu42MAi>!LjH(kC z1(Z4q!ertaNJ4iI3*N2zw_jYd$gcZ|O`R&U;!lyNpYwsl11&rL;G+K86gPMAsXs5k zqU8xBV+VGlk$nq1LZ4icS3w{02Q_vk;CpNZY27NFRkni{+>nSJ*^+P09KT8lW|HiV z-9#-ir`GqyT1`@UHT2Z+!3f} zw0B~B93~lDd1&U{B-!`bpJUwiv~DE@NJ?9!o+H>*lA7<$La)ePKVRk)L-#;GwfOnf zsJ&;qGc0JCD36=8nHnrtNfDE7H)s~^!;@}1_zVqh-S-aB)%FBoicNCFmlGp?swQ+Q zCQml~^@qt{?cUbR1k{)W9<1+O&{;-^$P9ZLUwVpA&s8W2#Tcb+bTOVi6Q5&)nk{{q zY@k1OL=)RO3Azo_Xd#z16uvit_YUs@vnW%#gY2Jnnzi`{g@oDcBKLM(Osu)Tx0WhE zXK377y}jC}HZu4QTAgUb9vjKzoHa<6v3Tfue43r-osj2kl;y6MiLIk&>cied{##)A zo3$*x5+12;$zMBu&PZ*WeO&r}L3}r~=U|0ZbdxFx_U-5N=xVX8^Hld-S%>rYtzAvY z*8b<~vy>u7_sOCfr2==Nw6gn2nsUm?B1z@F;SJf9*^)dp-n=L8FI?Y#9IC* zcs2^3U4N$Mr0k2gPb?`%6{%(*H6uj142V7Og&}xpo@UuZS^(fQRv$YA-BLApeUX<@ zR);cV!5SRXFyC5tnpvTQt{oa2hc&qV_VZTF>@d!GXDm}O(VrD@;K7N&c0JK~dw?hN z(~=3&M?WLzFku6-X?FW-tai)sp*5xy8|h0iH+Kg`edFbFv7+c$l72|l+C`Ki)!hgd zR$6ApvvkTVnXriRPhM`u($%c-iQzXiQS9f*CCrfUCw-@BoD53Mh!ZACoL%J-NvGmOU?5180WO zs+iho{TZb!TS%qy&Ro@mQIEY`(~)KeSnl}DgrjdHV=#Fct$zxpgfBan2{|~9a>AhM z`9}Fe%S9g@m-c6&D< z+fYCGX6I+bC?ID&bj~WzzkaA(8gDVR3LxzCmfY`@{R3BPcP}+|&feOzZ`6Ai|M8xSp-?fl;_qvv79q zC%A+Ti=pmK4>TSLmQYj(eqi%?{-y?aQk1`QB)F$V93Ly#tsTWE3_VVQMpEN?+KD?Y zaH5Ox%L&(k^_j&!I=kzN;n{Vk5hv+KrUBysVMW8V-=-DrB+Q@w#$44Y*31ss+e@a1 z&SK9Bo}OicsDx6ST}=T=UZE>ukM@}N3E$|}owE~fCS2^j_6&N`NDu#Te2sJeEbXWn zq!j?sWiS{h3LL{E37W00%xyyS{o^VTZ++m8ZLd>%$6SJ@oZ@b>R| zpt?iVbk53dtEb&e12739!+1-HG4U)3E(^>P1__Nm9k%gvV4?i7;Db9M?Z(SI&Hm^c+Hvgh-77nwz%$E!U{yf7nitm@@Bt$@F+M zgc@5SN^kx{MC0ev;t+D>M4`ymY0=F4dSIp0Oi-BmnQd%h63zWJQvW*-+obg@Se9l6 z`^5yXloJt-tKWL{`hgLId-3~h;bIji&~$Nt*D=hF;<)&nXnpCx>8l_l2V()b#LF>SvSIW`O0vJ ziHpa0TJ1$Qm{{E!!ioE|9#&PYPMI|=++PzukW#B|FqsHqizO4{O3+NAK?T{pzoXo* zYz0@XI~bG9lTEH#aRm)pVp@^>wt z;oe2Ta01*0$Jrm%GF#4W2ZvY0+qxfXigUER+W%ZQCRr_!Lgc9x`iDpb6flssQE8?s zWuH?=oWP9#;bXG>x*h1v4rRtkAIr=4N~j?-`$_5$hto?!+>ARX?t&SzG~Lsvn5`9| zNEJCwkxudZZjoxWm+NO}*V2nKcQcHWMr7vj|Y7aVB z3}C~HwHyX{Q!*_@=h%%lQC75EzbTjO`t6~H51< z(Y$OPjKegZWMD`46woriA$-*7$$B~WQYsxlTS4Na%=qO5sTE>6geN9RtHqErguEB>d7%%S0z!J-wK|c3}t+a z{4qiCQ4sDi8g|Q?>FvUTObdPcSDE}Fwvb;B*R)wO)eHY{uj%RQGz30<*8jiPg1Yx_ z$y`Y@rSQ`{8+F)>uPxpcfwj;H^ljPj#|8fue0s%17fqthB#1ZJks)2puc9Tkw6OT~ zyyMoCd^vGjKF(trdOW%?yy~7@JOg*2?%Shozn4^7LBPad&HGQYLyxY|oAnQ#nv4U= zPCMq~BTw2ESWsW*m+qrR7BZtfL~Y0pk6%IEe`lMiKgtl37HKtZD0TaXh+ZozL)4^)UM^rX3cRdeA&E=GxBgIx)}B5yg>CK-QHZZ0t7rk~irSrbQmRL^JTz2#p5%;iaX}pQ! zz8l^?{LFV~Pf@3p*F%l5$4j?H&}d1KrD|W0ME8@tY%GJ}!JaOxSAGt8Nuk_}Zfu76 zwebYC>L+q_1OZ6tFiXuj^AmLScv$5xHAPt9iqF2ee1E%lS2F%#=-1PzRVUz-)~&ZZ zCBt%=%gE!hToJw}FDwMA#A#`nC$fsje7)+1m$GxJ#FsA+f`4nOG5bdzK7Ky)49)iB z=akPfC8da=^a6&7YkBD4?UqnXaN+Ip3tNd zP-!X<2tD)~YUnNW-U&@9f;=C4?X~uL_dCWu_{P}ZcQVI3$kEK4_n7m4|9;nHcgW1f zX5^*`unzTd&Pu@SCu(wUKu}x(2;9M`JrH=~ zUaW-uamZ_Pc?IhReJ@_>vl+c#(%XdM?X|&2ilfQ>Noiz%%61v+16W5!h8MH)|B_~U zJ238QNlp4)*>38{CgVGV+(Ez`-B$wu_D|M)BTlq7J#BsjD{|U1wLGH zR|3{ZGw!S8H8IE##%7>?1e@6oBE~ z`i;+UYN{5Tzrd|%)GDQqeKx(-^%{44 zGJ90QJIs1OP-N*FUR_Q^!@!y&VdzrXgPpOF09J^a(>&DnNRumgjK$3!lFEQCZJo63 zUt<-i@T*+Qr8G1x#?e>OU5^$qgd{w#JaX59!pon;^>oPTd;WRg1R*3i94R-dqc;=i z;S-(V4K!sYtt#LEz<4~Zn0(VMM zB=QS5*c^YlyFI<-v;X?B$F2JZckMh)F3Mh})!)L5vGB@#;`e$F;7RD5i}%|;r3Ti% zpJm)Gcvz$s>=fs!C(D;!a9pSGFzCFl+VYwH{p9wQ-EuF-7jxQ%RZ6N(kS zl|E?DvFdTJMAS=2@7nX2hOd+t89YS(_I5I}kx#%s3kRh2bc6FdnRc}0Tx6wGnCzq@ zSzv5)p+jsM-1NC)Pb!S-O$Yx{w_i=Qiij$fVv_idUCpj7l6oNQYlO5hWrJx96Q=RG z6c@j}I+E z#Cam`da}qowTa7QjDf#w65LtiGdS8A@Zq-E)2MC6AT7qygKv8iVp3N7tsY@KQiK!@ zn+&gglVsBIZ8F*NucX%Ilk4NehL-)3s4ZnY&3tK1LftQ*=|hz!w^5luE)BsMoe$KT z2}gnkI-`<@Yr$#s(LZA%4+rl)qgI=}Cw49t94=ZFi3)J-9I2_6OTGqbYdRkG)rmt2 z-(WoK1FYQ;o)TgcGkbOo;BVFZ`%vAxF!)NmT=Jf;ST)<7D^;<9p4RI&mXuNW0aFFj zjMxwTNc`iE*EclqH-OTPp#vkTwy)>tzR>%9zCRXz)yej@n{|u*(!1eHnC)4UE}t6s zL3|tDevo<%J=da`Z?uqp!d*KZCx$8YcF1(Op!fGOU1TNUq={l)toU*La#{a|FAlRB z>gXwExXieL8SQ^mB49m$q%$SWg`xxdaIFeg!q;?w!LxQ5=WvzStw1*zWpVF{eLhJI zryqKSBpx@nU%}k~^sApA;aZROswjOZ5>xLfr2A=L-O=G_ezIYm5}&KyS|XQ0kxB88 zWhaLz$%U<)mOm4-0^QDE1_-p`(_0Q-V6_ILD^317=;EK#fZ-Xmmbch!;?*`nTgZJS zBlr>!O|!?P*JH$i-F0`LXqN37zX;y&sHEz7Md`e>#yab}UY^&srPGM+a$B3Cqzp4X zZKfsf{D^FI#*b_+K!2LjQ)j4AbBmh9K3iTc8@xq{L_}_ORzjDtiw#0 zDjkyYr*bKLuCh;x#m6szfDK<#qkaP77B?`(N*pn^ol!i)Ej(SN%x(<~5Ozo^{3UnbGfWsLJ9A79Flq$C4Dwn--hd2a2IC*nW$ayc_ z)8>!%bI@R*t*zNy`l`?xEn99EY1+Bwv^$yFUfnEH{P!$uz+RY@y-mh(0isNzobFW3 z48%ga+roL%H@AvczB{vSlFEMjko~U$5IX^6*ew*RH8klcjm&j0@Ac`U{{9AXSAuA( zBkXQNZiYAuqe3Nq0+Y{~rD|Du}f}(Xqg*sOTB3-Ok2=@nsb~XHfu| zaioSg(TEy>KmqTUS>{LwK&!dlpV=>v&)0h%yWgey$Y4Fe^|AjQ%=U#QeS>B}IMTwk zem>>Mg7d%(o*rv?51-ts^Siid8lQZ1gE{FpCo5!nFQZNH3;Y?om+5sq4e@6$f9{_A@uR zlsJ|)Sg~c!qaKm7CC_gSXJ{3ERPJLZ*(~W@dAV=YvJ0Mb?l4oFWh9@b)LmJDMu;I- zx3!!M+tqV3RB*qTDu#TGr*XI` zvEum-zF}dY)?sl^lz#QG?XD<@4Pu|W;EjH>_ki#leb@ zdr}uiKSJ{7hdpz^yH&9hAb_(gx8gL8CyxIEefNgi@kMpr^M}-2o$ih_m<7z!m!SwwU{0VKJ~2`gF;8ON!)Tl4NpqG zYXI|1+H}QqLJA4j8%A-tpz9HJYQe>LQ?_TddbB6=ZUOj#ij*a1*U0x0{X!eKE3_4fWqcNy*BsX<0YxeuR?E+dF3oVom@RVCR~^!s zat&ES-T}78j<5Hel7e;>8o~j{+=TLpP#La-@*_jcbj@C=H`1GDwiD^N#>s9)&X*A5 zMbjIDm>;m?ZhxC01GMNDuAPx4cn&&NvYPb&o`^8=^z!>MCmZh#8c{Qn@SK&`Ut$tv zp8aI-AcveHeOOV+n#OIabu4ZVo3S1b59`g+z}bkQ2WqQKc2}9L4H8uqG7aZ9cP8G6 z_lRY5$-nPcIxX?oex4TyRGcu1kxqdY=$sH|c(^`ok<@^yeK9(&a5=F19(4}G8CX0|!EKeH21$R)jy z0rj3h-SpU>%NOx2-xI4gN$h-;D&jpK7E|1AUtiK~(Tiqa?m!4F*ehAPN$G`Y(*UG= zQfh;Lnq;O3nk>sBWrnr=iP0K>C19qUDAD3$!qzn((c3#?`J6ERy|6wU_BRQnPNua) z$m7OmXws@F`SL;1p+w--#`eo#4XTp9XpD5_O%gZA{)zI`q%P^W7mKI+p%kXc)Yy|? z;)h5B3G{$X#D3im#b^6nQ}JwN^2DW5txoAG%m$`Q!4W2HCQKlBgBZ;DfKo3-W&>=q zI}r7tK)L8+MWC_|ix zLRQw=$|p#Gg1kY+i9$2K^hhIzi{MQ70?l|czW@mSH;IFgpjQUlOCek};LF+wXq~um z9s)hG|C>a>AZLYRoF+lAMm@gttwGsIZ&$;VqniVVP=gV0-yukx)`R%QfFb7S&D#^T zB`wS0xe7GMA2|O%!nQzvB zR#AT1D^PZ?;@kX#`53vUlU_RwNmZv3LDlJrL6=#B`Ee_zyP}FK?U2?Ura)|iVFbg` z%{P(zpsv)OKRsrrZtZ`Q)Tp}@I!)MBb{SwuLM{4bUk&lFlA|(sU3SoQa^znVYiAo& zVEhelx;+`~a7N32#ZUQ_ZOE2LsnGj7%}>pP#}JN8K!h-xc2ct;K5}#2B~bm57l0Zh zSi2#8630%@V9T8%&60&E(+7Ig$oc#-X}c8e{R2cXNiFJe=f)<4s9)7_S9p7(oXLP%qJM-k6w4oYC57;0h5= zTGm`dVKp%RCJzVRCW7v|vMze2=?%uzR3TT1YYFY$MOi%VuNvP~=s&RzKfQ0e{qjyN zvGb70*d3UCF4aFlx98=vKRhy~k0_MQoj5#sWHTANB(_B_w1TYMsj7Dv$GkmmM4{f1 zHW<-2eLw)AK143;^WcCY=>mZpwpRci^;rm3v8#Jhq7Z#?vb1WJOVN2nt@nPJsw&*d zGNsoD6Eu-6Cnajbk2_bMF$Lk#pYO8DHwJHT7V$EU#LN_D&X7}?C(5~2p9wtES2JEd zVXQ@Fa@3Kce;3bBq>sn(*$1Ue^?O_u$=M(fl{=NWWxuQWWG-eUNoZZ8fJ)r{QVVOQ z!8g0LQx@57UsRk_5W3n0M#9hz%&i_%#GR@>3}H5FmMOZ5%*rgie6?eox4Wq!1#G+a zx!gVIf)&&|>CJgAerMeKlrO^pEdDb1p>KWS<00oPK5H6 zYU;j5l=@dS&2O21FR+2_2K9jKpK;i)wxlg0Mx<$uNTw(BzkjN@Cs|_06KZtS4N$av0Kl}dhbY5y(lB}VbO8rRa71?Bw;efpcU`)=ShL)Fv4~!ol%W*9<Tcu z;Udk@PgFY(506`UZ6|8MIaEW}HH(}zz(+2BdI~F^$410`$t?+0-kHsJ-#ah&3#i_m z2=eDapap$}P}O1mbJ9O|MuXrxz)Q1~ir1zaV5@uU#Y>Z!ZeDjdeIcES2B{H6(}fX5 zZ;q|*8X+*GR3BTHKGV3Cu=3(35A&P{Ia+0p+mELHuX@futepQ75AQ#~d^Ezo^dKOJ zaB~2_a^E*=+z+k&yjkGt*5-Coi&L!EFGwCNbJ8|p^2bF`mdxKIO*y-8k?gU+oH|i; zhMq;7t`uf{_tIk0y!3Lm@sD`DtU$j-_CBR&&HBuAbje!-+~s~wg?g)BhBT7BkX^R; z6-BP?s0`x9sD`Obp8viVGJH1R2RX)7>_t|0zgc2`3M$-*;;X| z|1B$29h3Nd-#~PwKn_E7>^+zHpAl*DgSu1f}LbwlWOy>MAQmjvh3CeIK_%%Cz0dRJlo+_B;9%ubQ zp6cn<9j~fDujtk5I-zCBkzd2~#AV~m5g%U5rQ5@x)b;rC8FD$?KI&uAO7?DAq;73G zRb%ZVUe#WCLq?~;^;^gH=jorcx#6x*ENKlOxqweMoAS_&xpaSz1z*X_ zdUN`_aVZa_LCFd-C6?j~h(7`U4c8hx@i$55pCv1k!Rdvw<_3W~3StYlmlk|gHy3uC zPyRh+{@>EKQcK-)|H-Cmnk5OHMo(8S*S4FL-94LS8}iQ}W>R{Ry#p-t8sq4C+mYBS?Ks=c!`pS(#{|<#7x=g@>8$XJDat{+ zr}C)6r(6|W0RX`MYti{`soD^x=sr8EA_zxfSD#DCw=RjH#st`x{LTRyC)X~?%&$`D zy1OESahj4dGM&SπGpU2_bz9}C)v#}5pu_);F=8_#H`ydp3kLf&RloF8s2q->ES zFbPb$^xeln(#&AP)$xn!A)X%3SS{LCrzcePoSOhzxoIt@-EzKU*g;=4oU)gO6M?H# z_a}xyjJ^q)+P2eK@ADlKhqYm5KCOOKXA~Zx(536R;Lp8D%h!+Pv3PbZwxPECfiHp6fWQ`6jfu^^N^llNPw(Y+1P$!)@ym}niq{-B;e+q%f1q?Dovr$?&m z3<0gDYLQVeEocz9>+hW-Bu0slXDb^pKL z_a*OPCNQtdyP#RQ1~5iNw}VbZBtD$0ha44mSBUG2&8Ky`i7?XBJN>&L^go>a7l$1??GLx)Pv}!64>$gM)K4zwiB*ULGt`YHgg@h@6c=n!Y9ZIpK{%$-|57Bg zNb6&dxs6t;qilDy(OrwpECN`NhBvKhwms9<5y*Whm|T?DO<20zeVf~p(zGHld=LTq z5-KL3T51K3F`YXmuW`9`V~hn+Ha94>^4-B~gX$tz4uQ567>f9KFuW=+#yDHV;k*$h zQ7lB7Fb(bwd5zB4gsD{ryKk_8`ixt&6&YF%O}b~D`gg34CH5Si2_%qheXc6ytRfNt zqyYe^@LxvltIoP=4w5BiV3CBj_1&YY85*Gl{Yy(eMD#Vyo22Ui;YPEk@Oc3-wNrCJ zxlCL?47$2(oIW@FI<*14FiBs^;+o&He-2dV8OQ~QAKVxB(>0;S9**AY+)=TI~dkPX78>vQA)2(Lla>EGusj@DC{&t8T zz4YNaiy-E{u*e(lTM}02?ua5#yEu-NLc?o>VbD^;>D@ta4DvZrQseMs?!IC5IqTSK zX_1b$A(lcWrbG<_TcJvTSwy+QZYtT~+cWxbm=Qgt?cV0v?Yh}GNMs;4y^o*ZHTH=- z2^~Fe@5hK!%~H0UkzEddGbS}dHCC^}c6egW?%$HgHHE17#9oNAv|S8LE%K1=Tc1(L zT6?@L^J^QXQ`*f(Ua0eFU9gHrHgLzK=ZqG zPC$FnLW(16>w_5;O5;g6-COdNa;1;A7Bb=()^fX-zYH!eb{Y0!hsP87=jsWZ*72hD zCoWVxCBBVbI9$!_%NaBF-HPJ+NFh~7+o4`3!NqxVZ@WUBT25(Juu8{KVX8o7UfHVun4Z$a|`;O{rZ}BskB6t zYfMbNA-K!%c*RQ(E>UnW%pGc2H87gHN+Qu3C+O~>FylDg#P?B;>HzLbF@A96<_Lvd z&LQhVFdBGBX6J?d>2-F?@aI%TDb>cmY1|}#O8zE^xoB&t$f;^R4DmCj3vg(nZ2u>1 z{MuM4O!Lp0;s0K#khPyk$$>&vh`#DRHnp}P?-W~tdy*)5S+udq-j(|us%$z9r+rs= z4`c9M{#D4Yk zXN_H0FYLD=ADs6?A-VN^y6t7ANvLS|IT*=%u`YtDiiEr^d!n;oB>EGK(; zsf?RsdEpES3Tx)dY zY+2wLoxQuf{#)XK4TZ>=&A%fZ+T`aE;%}eJT{0-vSwA5b+ep|! z+YAvY%rPphq-bnl-6_BSgmWva1^LpsRTb$Le#)eJW<+hqOQNyDjGdh%ICF($ne4rS z-o&3wIGM%3w^ekaev9mqBB0#Y<|4QzRf0J}cyc?ecblywkSlM)Ukx4;{X@ zNo)BBvha;_0Ml2+G=^;~I=Ta|-z4jwUQ4&ae2+>BggHP=&spE5ts6zs;jl!1(!hNW z?$dLv38X-~bMVN_lK0c<_H_BXFOFGcX&`1pW>Q~<*z;(P%9|q(G2zF3PXn{hIxMJ# z9kl`d*M5#nB|6^{A9f!JWi-g!Y#(`6a&}41Xx3(L=6MgJ(@1T?hcsqY>9f6!XPf;! z5hc4u1R6E*tb7Bu%4;f;9p~bgF~n*Uo1zu>LkdW{t>d~i=UKbUxj|Z`xs9+0;BOM3 z`EABfJzm_(wt80X7umMkq)b)SSI$V>!G|YHhS?P$X^o^6& zHiNt?k&s6Dk0`EESD7hWnAB^;WDFe`k1p@+?X1oZs@?~y8dr7t?bObcwA(W8APObY z*ucL0(`}|^16HIhu=kwVhZ}!k+vW9>s}dJs3s{ztPfO>bjHdt)*7Sk=%%t*(x;N`z z9hQm;+*x8v6Tm0;0SLcd=Ie;>M$6pp(b=Wfz8awjn^&Wryi`gelUIpAe8di{apkrS zbkFx_BES!96K9b$V_mbFYinGkzP`xrPn|$k*2zTZ{MyqafxTk@w=hV29#RG5; zbzq;Vipg>g^Cq>=1OE`VgP%U-VtAMaxGRNHp~@szfL0d#&P&in_tQsUTBP?p(^#(D zKUQn0x~!Z0u?OoAH+${?gYnGQqHKkwR_pjP@BIA?t$xApB9MYO_%Jz{W6#i5=DO_W zU+MbMWv3$U3q>|2R_Xf)go{(41?(>SD3h?XAm=XQ&!d9AEI@~W|Jglr*Q{De9)$<(zNH{f)T^j+yV@(#@t1Rx&>YjdZ~tGP&ukPgE7u?#9cb zv>X)QIE)qxYWzrVlJ5XNohU6Ew4F$~V@YWhXfdf{%EgmkPc7e$ZqmDoRt@It>Pp1J z&YdWo)u~1%GB7c%rUxqC+R7gOPFrKUPPY;Fg9llxei+s()T7b8X4!Ov&xukvcJDT0 zQ{dMC(h@gtqi&l6=DY^vs%Qe_(F|l+NuhWEcb&jZpM-tA!@2Xq=hkP!*HL!f4$9bK zm-y2G=c;wy6TLKF9QWh{SS#!N$({YpO0)Y}*bZ!LKE`uQd~*(?-Gb6jkqT!FZ${y1 zXr??SrBc7KYT#T@D9v`uh*C}S1m~~IY-SbO0737Vs*QNu5nws@@I2<3HYMc;tA@?AB7iQ$^t#LAMpqN1d~QxJ3-o8G~y#x-5PHV<1T%{q!d((0S3;b@!@wYJQV2O~=xI zKnd#Cm=pnS3)e%*tAlSWmHR1GEeicj5}sON`}>yKmW)5^GMhLB;K)VL9|IS{d>wX> zPO0XV``D>X9mU(LsD?XR_R46%G2Qj1?Up#&J>^@#{7u5v8Bg;it9B(hi3e6p-fGc3 z|1f!}n3j5VW7YNCA6rSG5tH=6Ql@q2we#b{M|>xmpZHDZzCZLnlo(bBHJ(-yLJW|| z-&8kDnwMX)%anQ!Am*KCLnZpb8Qm5KUC-ttq;In7H5B4E?nr7BjZ2S`LRT&*(TY>X z86`H`N0o}k*bB8pQyGojg@uxxf(c{43JURoNJ|JCf8CUKF&qjPlogd2Ad$%h7ij|3 zvv|S}857z0+IufVs{0Yy%?{H_zmG&M%iWT61ZiMDeLe@qr4@S|hSM0|!kBqyije|1 z6GkdKl;UNd&Rh3t%(v{byN3nG81`RDoL^qEur1nN_gsIxv$qn zW9#KC`RXt4Q?e{&0c^Rt_UF?!f*ZH%WAd`!sNt&Rzd&)$VFz4dRDYTzhA0J79;F_u zskOf&btvpk`k*t=CejADXApd|IYc5NObXVqEH_kb_$IL|)`%d)Dof)Vy&kHEYvpPT z>^NkXHcYMT(+(mwDi{)rGC33KJwAvO-#QY91RVLu8QP3G34fe;YkDD*6$E|UXIwso zS(ogaZp`yTPYUb0IaonJgo#h~9*Pfkd8&Kd%bn8dc~4L^!cx4Cur+-vzP>(0{dcop zN7Xx~zH>(k&DBwwvyO~a zp(!rVd@FE8L+Wwmn?psO<1=pG?7q9@`V2FqeU^kcHq2~8so?R=>)Y;|_gOQA(QPCK zBC!Nbi3_JxBPZ9UR9Z*%L*Qz=2o#BA0umlTsm1qkKqS|ui+&{+;~a&1Ydc~KGV7jd zpWYy^^h}8#y0KFh@N_+dnvD_d|Ml(N?3mu4-Y#h>Hp43FEHO|(7k+`r(1@$apxN>|rqFBzMM^n8`NRm;c z>?E`=#{65<+O^|yzN>M6Vq}iy8ElQQI^2sRzS62tEI))GzQoWDB-AJT_Gc^9lif7x z+ISpj^_vPcP)WEYMD>vf;TG7S_IkHQo{EkFt_;vYhrO<8&KA-!!f{3VVjctig9_DU z8Kp=Wz`YTvtVI}u2~rkX+pi245Z8D zccEOYdo#RTp3D!i3EOa__8F;0nGD<&eB30_x<})5I}b{~M!Egpm_z@CRmyvOE*w@8 z!23IzM(_V(6#Z9P-SGW?>3=>~m&RQz^tu%b|I-w8hg;z1)zGsi3&zEi3ePHOdwnM! zi(Suf_~OddT;Ya%(tJ@Y5+#*-U7l&@+L~9QG!4-i{rG8pLoDN)W9IF3dsUc`4uOr` z)MN_SaaefK;T<)yH~N zq%YQr`)u{f@`sRmkRJO$)xpUVeDRZ7Eh={80q*4Ah|m6bn~a=jReI}sNJVD8{JMEz zATfSV$JBw@B;g*5=h>9&UtYFPx}fpHq=k;bgYIkdzjuXlE3>YzM9gh~yTx~4M(G}B zybF35u!CM1C-Z*2ZC>gzaob;}|8AP?raC_^3K)3N7XL2tvw8pWCVpzJ=S_)ZO^^5< zxN2kgG4Iz&d`4IMQc(W8+lH89x=FgBzexfDXzI@;tMB|HS^wV-|C-Qr$|%>Lx`e3n zmPG!80(CoULa2Bkd_e1ynAC!(=JX^jjzWJ@ruCZEgw0NzlRfShO<@HJ=z5TX3%Zrz z`3d>(hSylr9(!y(i|Lj5`Omlcsx9NC`UHx@vCzj{(&fujQ>+ww5$3l8lvK4#QXj zlN8i1F&#S|gp#*)|Ij&FXToOvVHwWCE6R*24fDQWSY{XK8fzV{Pw1AML2H~$9AIqU;7P4vUuP$ zRHC7d*R}?<*=YAC!9%?4SGo9dBTUAvJZ!}da8tv=Vxm@2vA+?0V}{!O_8iGR1@rF! zpHTPfA+Gsrzfi-$Tb^a|l4=noCydfJrN;Aa(srx0o&2|_j(pGU(sG{hcGkbX^}|O0 zoxCoEp0@h=&J71h{hkhezJBiZQ7FLSk4@dbPeUL6Q)u+h87X!6zwF0M!vDn&`j?96 zKYxRWPZ@iy<~U)Onr=K)xZTVf1?o{T;kY;|8(;RnL5mqzb(UQNO=86p9nJ9+j=a_v~u zpirDS+Vf0DO$oQaS$SfIpI-)2jA|Zv(R_4%`~%!bPQ6_KdROjC>`R4Lny1!$%5{aM zvp)=PblAf@JxW~JM|QjRHDnm`F6yj^)_BHOP&ymg+cn>p^OSEF5LN1qu^{#z$umOf zCTV$-eVF@D0IL7U8)852tK*ybe&$(!a44 zWN3aEes!bnQd?w#SF)(x?)XijPAtAk$y5BPx|I}a+C8M{V)DiqO*NViKi@Wb4MqpK z8Wgv36Mc~Q*{0V3|Ce$RX(^}g{+d4~3b{`*l!TvhbdH-u5f>cRubM-jw#yc;5iCds zZI_mr(U$oF>53d-_m^JO%8XW23MKSY4>^hYQ(S7f-8(SUf2JbLe|eKCIhH#uV3(nB z80l4`u)j*)KuxmnEGIvgv_o-bNpO`#%NXy!9#*#DV>?U9!|yybgww@+DmfBz{PG!ld!ow&f#ADwu6%brJXI%C@|LE4dCEW192 ziZU3jgLJkX>MvQDJy zL%%tf|A`K*^)8owF*@pyCXVtx=C|P<7foQSst%TwiMxNG=qAF;Qb!Ip*_&sl@@6}3 z?npqDiokMDn{BsBvQe#$f0CcpVA2M7@t`2GNvsjmn@1FL_-Pfe>--d1+%rGIW`NLG z)O;f0XmdU5b*qWcgOaR9Qq1tGXu?RSWDKphU^GRbh0P&*Uz}J(UhRbIBsuB=bP4%u85Qj@Y^UnwlaPfSaPa9@g1t1Ca9YnH5WAlH zcJ<-s-?%51yxim2TO@u00BdfVh=2v9I<2p!C&~u_Cfc-}&#p+t7$4yGx?*ibZt(iT z)_}67zIi(R(_*peFRF4C6}#oFTV-6i;-0u)SX%PBHfqI28V7!bJj|)~in(~I$!kSp zk+Hus(%J5(J#{6x9Gl-vb^y)YX=3c;$~rmmAY->OCE1pBOm>hX*@T`aJA~J!OwzqV z&gp=SajU%f*5xLED2&3cC@m9iCPUr=b&X%-@)p?7S{h-My`=TC0lddZI4pnd3v>V` zaMcID2Dja^Yp7dz7K6vnO@cs8q-~b1?HTs!t(w?Rm;j+R^=CQ>GdE@+-q%bohu7ss zKUN;&Jui1div`yc)lp<0V#St5D7q#mi?Kw|)Co}wK4|SM|JFofPsORq0iG~8a3@-Y zGJ~~;{P~9XuVM1QDS~O&VF?lNbl{SWMN*Oe8&7JY-}HecmI^-1M543?!TOeIPL%AX zjFyYO*zUjgLDx%w0=6NaRGk9(OhVfD>w07P2^S?p?1D7bLh8NPc!l_eZ`Jc+Hswd) z&MN+#?%%}NisB$MHsVnLvQaL%X6l06H{xJodsWv#Tj=?CbsGCdO`-{kOPQx+o3t{3 zUzQ&(h(g^Z9PbE$y{1NHh=U3`rD2ifj|LP)>t-9@9P)@)dqB$v$X?T(zRf#cG2=ONrj$`uz{V{ z5yfK6i>X0-DqD^A?mJEb!waY&ObY=EfJ>b1pCNfl)1AZ`oS82KEC0kTWcU9h?#$PD z3Z51=&d;1jorKF_=7_tOpb-P)lQzr74!A@zE(0yyQy|)9qb`=7sAjCK0jXA3$^8sp zGT?J(bc(nBnxPY@s`INq3yt*MFHh4Uahv*cwigi`q4#U-6ZAv%*Jv@d3W0`RTw`f! z#hNvz%*-%>)WO1H?_)N#!>3>R(v%u3aG(8mnrpw-* z_NrD-(7+$$_ceeR?LQ>Es<;;)<_w^v$tH`pyf^H5q@i%&#OtztZEKoijEOo6o91UW z#;fbiCa=Gyxl49p*OCn$Emg~oJ}q#LnhAD-ydb_>K9R-K$K@)-273JTOhs2au7$V= zz(7wYg*mCbbQbE|W#_v*XH?o|bwl3ox)bf^`N&mY85DUI%FR7-6XsflyrVkJh2EZ7 z2-tK?P0q4EMxjG8m#!WU+Bo79>~Ex-GN#+hmcSFoF};&jZR*(fEp*2v;ztS1EH=IF zs!@-|rZP5YdvR)fMjxx4;q zq*;69!XPtX4K{O-9aCu~@E%e_3qPt@tWfDxxK<82gm+A}1lonA#@E(oCeO?$0|Ok- zIt~n(c`F?nM5VZRc49AL0!E3y$9kBBZU@(eXl>7s0V7IYApd7c3$SG zHg_Ylh&C_ z*ONjBUaVWzwy4jD>FuFC8W#?s@ComRjs>^%_1bFRKx{}YbmwTGk?ZgszT?$;9@rtN z0}P3bwK@qFnc55>J@OU)bS4~yG2u$ZX-5UE+wL2?IBmqM2nO;LsxFIyxU^XVB-$JN zjb6Vlm-?~dtT?_!xJAv$)bNuaOL~~7G?EZryd9?z>M@B5nd2)#bM#+-5c^Nf#U+n# znG`yUo^}AHo20_nGl(wC=K(v-ArD6n;%=?i2C%c1bAOT8b&ccn>PYJbDShdv;wW)ra-Udc{( zc9r{CII|KSnFrz3FZ6)?8G(>D)#gt<>AOen%(oIZ%kWw_+Ntc++uAnd9#m87Lx|+r z4RUBCic6CueuxMj{Y+W-wWe*f=<*i`zh9%k{G+9}%DVK_b;wQ3yi^ z`{EbeShrD*boW>AE@1{F#UCLp1krTzQf6DKzqPN0F?uH|#8N9N;gO`@V@rs4v^$>> zXMJDHlNjzr!FqBN*v!n(D3tDLr2NJyox^c+%8aGa%hA_G|G*43TXFV6itQ}DLxQ`y zi{<^U3NxFjT3rsae{$oxgWMm)YnCPAc+LA`8cb{z&k0r4BNgUeb-TR`F*-Ttrmc7Js9pAC%}gd&WuT&k#b?s}noCUv z|AXr&A^|I{2D9GJIC|$6G<0vL*2Qq4 zMP83c(RHx0(J9;DCnJ)$-BCi7RrCDyw^J^kQ}h~X^Y@XRXo>n?PJ@O3v2qy~O@dMv zd-=0Z^-95yk}kFMo`nn=M5SfoU>7p8a6pcWD^Fys;jy%z)1Oh^sRCQQI7`TOdq1s| z%BWeBL1nkQd2G1Y$FvK2Lu?EfGVT2WTVW&;u1&{HTIf_*>&yR<#YKDO%+B6uEg2t^ zVli47>ns10E$?ar-LS1y)798C|HmZS=HX1~-tge+;!epjv=SFVyDH+(PgONz5nI=( z$%>QGb$9>`+qFVq>lA&kD^TEW|E&BrSvBTdqf~dMd));Ac~3p6H;7mpGW6E>rSu%% ztUp`GNNtBd@nYy?SY3v{{Xy76{P5XT)NlyQrsSc=etz)>TL0-6FZyD#?~)(pD#p?) z93ESeq3Ca2cDVG4j0Bv)ZlNw0NF97~gWUyKAQfrfvw|D;L3xzQTM|i z>vgi!jZ17RdbiiI8tRV2CY?L=1Kmy0i);6{Y*1t*cPHCre9=~=YsCB8Fr9za=8R&C=NbWQ* zo4ZIzyuf=8;7w9W!@Z1Sg(=@4;W6BZ(nJxihe@ilogI)=aBf5ZzRifU`;dd)yx^F} zMAyAXMqBy82lm4a(*>{q%%WISu@7bO-oqVz*5Wm<#5SkaviXd)39UCO?zn@_@JOEt zmYO4WIYjS#jV`={NLg~(WK$M6uqtQJU--~vPK;Bz{$kou_G>lBXTO{0!`w9icZp}e z@>G)VIM-D0-2n0YaG+hu{iMiT5cTCxAsCp>LUY<&72)aoYoZPvE0;LZbfpDXdnpDy zzVn@X4y6pBvcpItu6xWip4l7JQ19c5lzF|q-L9}zjyHT&%PGt=3~c_hZF|z#LYJ+% zq029>_AM&~<>z_tXA)YE)Oh@&)oJ;2$&^+S25A~ zb|)yCdRxg{s6P2!mFc-nNG&Ao7o|v%+!xqJ$O|gcqb4RBxkK{<3$;2KA=pj5pltg; zA9hTK0VJoVKVPjRUPw2KEZ+YyKqYa05l9nb@i&R-D6n}G)E_L{NaJN$BNQBl5*c{$o9?U8uA_Ao@LJ-zg>17X?Abv#Yt}ZHFPS!DOWHqLev{qiCMcPNRz521^XfIJt74PA}39M?ImNWDzNu^K&h4*86PgA2Pf%4&^i)1Lmu_x5gUk*3X zBY8ct6QpH&+ox*?kfv0VOE$CJrEMsaFZMkR@%3AHNE2cymbO9}KaOjM&}w1HejZV4 zSjeq7>|B}qvcLNeKFdF*$bV5U)I)yDC_wc;0h(cxKRpAc!Zu&rd549`3H-+D;Q7Q? z8?Pam5s}73b1^VZh)c=aT=jWwbgmk{=sv%w%$|Ic10wNwVeQwgd)f3oUCAqjF9943 zGqECl47gY~2I@+5_!pJvT(URNSc9~_3b8~@wUecxQ(u2_%LH9zm3A?yHk#(xup1B& zWa8Mwwn@G`l>mdr7LctZMUBpM11Qrl;{?3Hs$nx|C1$3=&HSYN(EgF#x4cq&#z>B- zaxS*U+zkI4v2^R?D2C-w5~+?iQ=pGceQ?(`<~joV-Q_IcUuOibm;VoO?-kcn^l$ru z1w@*FAdrM2A|hRS35rN>iqb+62tD-BgQy@)NPy6LQ&1tH_g+H_T|)0wLJ1&6(ErUo z4`<)~a31eHZ+XhYT62BooZ~mfcYN*g`k7{gmeHzs1jO%I^rjF&q05Ly)_d~dLpA|A zEba{$lW5m<*Yf8Cw^G@~zrEw&Glwnpqu`ckA#8vY*S!_XSSyzPnbY~O+(g92=E2mT z9h+2+=dMW{9L`1|&#Bl~tG6@kk;N^TkXp*44@QLFUTvR-@U4H)$1IW@nUG(nERosK ztG!yr^$wz~oOHpAn;_Z;fg4k22Z8#(zgl*$!0Xu#Yt`z%do&_pKE$z5rx&GrUtn{5 zw2M>RH6JwOw)ZoqQuQJ8i`0xcpC28km4;}`Mf;GiiiuP#i_bh21W_z&!)L$I_a!^; zgR{0GoZsRlyE3(9Dw@qj`Z@a{o0T%%P=U;bZYA#=%k%bKYd5_?&wox&qlF21m!P5` zly8Xfm=w3l_Le{RF(p0-r|48I`+djG}{#RCgPFM^A4cbi}oprJSXWq9|}eu zGec!_bNdGW_qFu?t#ik1W&t1fQ*NyX8-x!#c_G*Di6_kaXii_ya6dl%-%IX)t-Jr< zMeQ$_qEk5p+MjOPwZg+g*@D+$ojX=jwcYmn6DmAZk**iD-%>oQ>iFZyn2wee;@a&a zLiwZqS()#5qJdq56&#c+-98Gxnm!}-Pww&8fc%vjOIu=_#~L#H!Wx;W#U~whR1$&IN24aBa55_N6Hl2^kheSH)!9*FTI zBhsv5@~{{v!X(V=J|>LN&*DpaTZ)U)`xYIY;;m$)s0~Zc zi5T73Gn;Qa7Js}?S>TtS3shExU6PN;bm61(vxd@UZbqPrxiLffn5uU zr{EfY0?MJP`f{XFwwr;0;v82TG`A`|0`weh9%QyCGZw%*R)UjI5p#<9z8_Bo*p8$=7>EP&euHGI)@eax9l5dUGp5}OZytVxuQHw#lg0K; zX<6>UGj^;Xs+u#?JSS#y%z+YIxMq3Q0eMO}6z52IP3{OwoL<8hSE)b)Pgidutba|GLzWpBC!c^ZH#7l+wY zX*ZPix5pu)29E^nE+~X@6`7J(xMWv?V{I-tjfH^V5lWn1tva<5Tw67F{!m50>I#dE z`7EBfB{wiE;V8E}Ab%SORcr>GdR3jj&UC=bb~u#8=B3Ra$vZ&fj1wj)6mdqCr-nF9 zuQ$i%=VqejH{}{Ev7=Aw7W>)$L$L{s;y(5+`7t(E7>Re}^G*AjamwF;XLwR6llo!V zcMR=+{cQeA;B9wg@5SOb#oJdQ@7aeIrItDe<1%YpjVu-U8ZYG6;c24=v9urj8l8~8 zcTF4^MNkrCJ-f|MCs^L)le@j+H1DSExot9@Iw|UcTpSMoU0R}xtdPx^c{c>**>~x< ziaXBpHkTW_7~c)>qCHqErl&b@2}?D1?p=LpJL>7fX1%N=?XP|=s)qV}zaIiI=PK0RbXknKEbxkn;c4+Mn&6i>WAMo6W6u$_pDe!^S zXX)#QaA!;@Gu+4YzO*g+ZeK-k3$RbC3tlL5xPi-ZLoxU@M1wc{Ce32+&yewBlE`^n zr0|+t2wf_(s>WwN?5{Bm_tN^sJ_)>ELnbZM8UDd*Gv3v^+E>AN#7>qP!R-Z@YfYPD zbl&HQnpe5Fy@bCD?}er#6F!APRw_n`@3jebR8OVmQMc#p6R;m-hOD*NCHjC;4>%W3 z{0ID$8fP;!Gj=j;eBgJDSoLu{k#~4|RFhV&M9vHoBVWj~3#p1YE~GESLnA)G=cl4+ z;C`As^u$MJPF5Al>LnF{T7rY!?1P@tX$`9}9T&FD4VVoQ)$G1QVrw>n0LK}Ifz)sN za&8hnhRV%uHLOd~Q`RvXkdk@5_1;Ra$(>bo;PfQ#`aAm9WEp&}ID%Gst8|^yCCfmM z*aUr#$9IRLyZc*RF|eu?kzy$ZtV$Q`i0>YpW-D$k3dnkX_O`B3ym=q9Tbd3eE% z51hyBQ>duv{qZ|Pj;rg zO{{xIb2chZ3E^K4gpuQSsM9ewa=Wjg$AP07UcVB;>)&So$@EwmSh&kTjYF8>nqPyI zf+s8BCdE=grQ7*vH?7%vW(u*hEL>X?jGO6I@ci4f(_;Ym$}-l@Yi)tmQJ*)7Ls1>| zG7#v`!LZZ%iqK<8&s>&Pyy2Zg+i6vTJD;gK$pxR+yEn^-HCDhu&XrsqmAh(x2$p+h zEodjRHWT(ogAa@gD(zw$6EOrY;=H^gvL*1=1L|v1dm;Re{Y#kGeDK^+luLWpgGxDO z&auTH;Ur>@_ZPO3r#UofGkktgo6Z1gjWRE-oD)aap@dIM@2g)p_VmNlq)sz+V&qKj z{~<`!K=TG(X$d_B=N;o_>Devu1FG}p6PA8IBOi;RB)+@TDO)j8kHp>f{I0aAz1fsL z%s)nTKTv}&NVa8mNq^w;5@5>`pE6t`7kSghb+pgZXliAj(C95MW0gJm1=h_VsUX1@ z|@KzKGoS2oAoPtD9V=|l4M4)I;r*O|(nJhRw;=|jo){kWu3(9J?* zz+E4_FT2I&70mTe$0u9HxQ$h%+sFkH#$)~CzTNzu(y(2D#GLB~Q_d|pkcZ`)C=v%e z>YX6;&ItYXmy-Ch=y+A9C=2ROzd} z>*-~Svyc}o%DeAF+#IQ6Le{^@;Mqx2nT!HqRlx}fNY@<3eq@GCApetvyc zuImnE%&)8eL*cfkKrL>-&DTvvV9Z>fH+^>Ff>Hl9w!UEfh}A18*Zrj65l5b7f!2d< z3Ti_w9ts7tA+$wVe)uHz{xF#MzEd7-4N`1S!up9_=XQIq};(Md9Uj()=BA$#(;dzv+0A` zeHUfrb=hA}@Fw8Vw&1TsIHPLeSUb~XAi6E*cy0KvLqEIshI?9rD1alm*fG*oRBZ^ zQD4Wdlm`zwrpkhPeccIN8_RdRC}gq(4J1+;u$z9NdUtFEQi*Qci)XO@2@Hu%xz;ib z*EqPZRPEhkY{oghzwcP{6?6QXuh^&tk5f-5?>Km`Sl)BZD*9D;SNkI6&0Ap+d z;kx+g(O-T_VJ(&wfwvnhf@Ld?X?_eXI;~TDSlRkj19W4%IXyd0Qk)-%H;>TF;!nuP zQu1>o+A#;#U`~uZSk>0v;C{fG52DZDy|$LoEs8ES?sbtO4(a@IeBXr%i*CMC)-blF zuJXyGxWmba3W5bKGIijS`aO}#e4XnKWyYxIHTm927$laZyc4Js_-Qv&k8G5g0qfjh5oG>uK!$LChk^Qr`Q0WN zEkQ>E#2v}D+~6{2e+@;)9~rZ~t#C${fA{zuVl5t?3wIAN*S9{~Hn4r>VG>VZu=-8l zO-Ov+4^@QD0w*x8T=_5DAu-1W-7;PJYVow9pRQMMDCN&hHT^Rx840?4&-&hHWF|`l zP>2EFSKilp{vp3Joi-%^!7Bq5v!yjz#i$3&K$L&xCs5VokJrllh67sPDTZ9Ad`H$3 z7ayCSyM8*^_EL-1ka?nSY?4tG2JH3DmaO(%Y?R6cK!lnKDvXLQW=6@$XoVv1yaV9Ex#w8y>G@72eDMW2^iE-8s#sVNseQnq2_SU-z zHQFeDBMl@_*I7MqaEm8UD4?=UoYd~Sxic|sxW~vX9Sr|{lWWUaHMXMl%fPrtT?Jh~ zdx)nkSP!cUjFrY-*WKWQ>|i$_HBtRDn1oPmV?dAIFwL)ZbIbWw#yM)K=$bt8$4)EX z{0a7L$^1nt*+#OhFCl>|GoqayWFIg?JxN-7`)b00>)h_0iQ4Q7+bFj$A8U`P{e7li z1r**GYO73^gt1>r10vFB?67Cg=B_zqb1h)4$hPm@)|kSA%zCv3^COQn*)4zClBhw` zm1yGXE(sJOQ5K8-TB~y%)+0F_6O~%!l-^x^L@rZJL#t0Q@ORN2pMRZ~?MmJ4@(91; zl-ualGRbLAz$F^M{0^lFgA*@KEcdM2+&S+$*4Z_yT&If@hUTa~o0)*nJdL_$l-_&U z1~!%r2#Z@7Vri&E%=J&8?z#rp%$1ipI%|~=!BO-NJES^p8RNi?jXy^P^?5?|Ur5V<} z*SIY;KoHZuLqE5Bz$ z#20h08y}k^{|T9oEyO*A0tJe7Qu1?g{S7<0*DlDr>@rrpH8fj((eE z6bK#!9Q}uax#C_)Q`Y+-a=@lpq<1=N8E0ajxH7d%GecG_gm}{;LjU!e$fET;zQM9I zEYDci7<;Av4nV!rtNLx%a(Y$to2!&Hq=z}}bQuuUfT}zn8U4_OR|}asQX@fPv@VZK zgR_--6@;dDnq08@BkuF74b`}s!kk7y|0o zvW{gj-%1E_=vD?!qpD*L?+JB_ZLjL$@~wz%1(myQh*^BXB=Op_88DeBMJi(?d`Fgh zq(l294vJ(P~{k9lfoFdx~qZP%c zl*pY;0-5PqUnBKB4AnD?$8q;ogf_?{D#PeY2ZB#%HTEOA$)cjsdJ$iyNFh}ZYACc+ z@0mL>Dk6O_%FWMg+ydrenvmj^Yxh@&%~{t& z)#H6s$i0t|q;To~m16zB5n#Rho~%N+SpnT(i9eY)e`)YW)%7~R_nKCI$(Zgeuu8Av1JV(E_0!vD*W$-2(58aL&W4A` zOt_}NRdU_aG@^115R(V+$K(lBy*?qtP4sS4?6$5K(<2W4LlLKSEWj-kv^J8M?RAfd z#30M(51>*RMgdzDs{Ji**!L0w`$f_gK=E0Im&Su4KVqmo1{V{SNfMen zd_7~(1-a@inI=j}m}S1g*`FQ!B1Y0J@K2CIhQ2&$mrJWQ5B1X-Hr#&x)do6?h|wIQ zJxA$UjQy=ov)F!wY~b7wL)-vc zI|WtbGwjgT6~H5F@3MZbeoiuCz$8q>?`IlGo~FbBI7_0~w!cK{$5K0{hv^aEb@LeU z^gx4%{db@BP&H)V3U_qV|%_I4f zec`_Gz1(KmVP9Ndd@!}LShzl*px6{7r2db0RX(6Hhhlvrl-iy+Dw}>oU8}%Nf98u@ zc>bQ(#sgW6)TS1nZ-;4V6}r6|u?0gg>A_bu)4cAe-gFE@3nFn=*F@R`VWyFh?}ZHp z<=TiI2=A;OI0hXTaa=m?>j(Z`-}qVI6}8YAK+LK%t+0}@M4e0)gio2x=tJ$pSR!)T zIRnF_{DK(or{}H(qa-D|k)KD=pG0~-9|XHyE3e`slS)S)P!_IvG?B0$xf!fiQF5zY zP%~ig71GU^Vi8`q4J(%T6};8r5OVMC1!m~V{+^CksF0Yrc%vV~X7g?@$Ut>VXG{KL z|GvHJ;TE^o>-6`v=>Be_!zOi^TjqR40RGYqE^8eF6Z$>3CuaJOzsGm2ZVem34jkT^ z)c%;>Oh{g^Jd%2v@vvGGIGo|$;9E|nRR)+u{&=17)q>&S?Zm386!x9oDb11jhCHXZ z_|gi`O=fn%E(QPqD3v{8Ec4Uam0_y3qwMZ)usZso%$AA1bB@`OZYWK99E!$Gi$9{9 zeOC#?0Iw zrB6-xPa$v$=byudTIG3KO6HRdQuAL5Q1C~s-aWN~VfnN*_xr^LQOlo?Gr^D32JaXM zOWcDS2qG_G8e)Ri(-=st|DoVnbd+q^XJ4h24pt5xhI?B7gP9D(BqPf=t$McQ1B z?zzh5?aY_rH%Ykhu@N%+{_wOdN7TM2SS!1G)eH(qLGOw#i>w3WBkscn@46zm79`bB zVpCaUzRH_OFRl>RMeS_5PfxV=L%zg}HSIk#b zv1KL7?8Zy!k85SB`Gn^GPzb&%@$9{ZaWuh%^>kSd5rf*z}HSa4g zdY!Bas5sNQOLceG%#iNfk!#pYUH*m1ixT4&&7uD~6ZMOVAU_s54(1g=I&4Mo4lvUn zEQ2nho&NonD4((4BrZ)-=Hv-j;k(?g1!A@Ci?z6mwju_j^i>A_AB#LpUi9%Uo-p3}CR&M+o@fr;v|xB_`Rz)$k9o0}l!WtdQ#Sgj_(apLXTJeRl zly`GO#LMe1A)#*cUq?4zOWbP^85+OMIdv5>DfiMNbh*XsKuZEbVVO) zVek*1#C)yq0HroI(H1-H!-kt}CQpR!D#?b0CI@-icqL3?crtEr7>oDiC}vH4ENa>S z8IlmBIH6u(LzQz)SyeX1U*>zak_x2zi9u$Ci^9C#9^WeZ7N_v@J?8_F*gGWrXjPKW zuiG;=0bx(mSiQ^bDoD`voSz6*?nh75&4<~iWGkl=zJ{gAuRGOW)C8P?!*|J4Jko;W zVc~x$=!UC8E*8kU?&iT;=*pFp8LjMEH*_XwB2b`wc8FPbNpb;GA0i#M=GGP!XFjq} zw(%}E{WLAE3cV&*7=^K^V4fQq5vsC~fbkqo+s1Wl#;QZMV@w&XMu~Dw^)Q`<;m7^f zf8$EvIZtA9g1kwj<{2X`KDV#1pIjm$2ZuF_#<&Qa{%qBy|-vMgzQem!u_+{qch=S z8;RGA)OxXnd?j^g=0C)TGDMIpG;bP2o$lZjqoU8&>KkH_D$%Q??d-*+mHHKzWhKOo zwr9Gb5Bie7shMNZxo`EshX?789Tdi+3bzvQNQ@04Yk!j*jZmri&}H$i&b)hV%eS{C zMYkqxP0{>7^;F4PDRbYP2>@V^l_(IGSADiqCXx8eHNkk`5H}yKfITta|jFr42%TW&v7tWes9#sAUOS1T!s}s60Pj0{k@?L< z^}A&TvTBnvoj_^8b&jrSzjA3J{oMiz>3>&<#jQY%)Pb*;YEgTz!962}@n-dAu|q%i zM7NbX*)tm10$63a?}zpPh!2J?rK!vat8Rq0bGK|n|qQHwZbG#brc zapw+Wzj9?4eiN|Otof_7m_2%!+NS4&Q}*x06a>4lTDpD8u;{1CLB=r63!#c0Qpl+m zh}_E|=ACTi>vd8M?{Q8;27_(?dA*mF86ou=-qveVCwlnk_N4!+WTSeq%Pge?9-hmx zAOz$$PSqM1FlD_ zo~Bd|AuS@Fw6J>{3qp!*tuQc)Q*o-OhEJI=(9b_Sxesf`nz@)Bc;71A<8y|B24tqP zU9YcGs}}d&eP~@lH!zs!@Houdunw(b+jL{ZBdyJxZ+&)n&{5{scoi`)yWdWDrm*|k zq?snHi?0yDC$353xtE6)mrq-z)(F58Uj5WwZT|X8$)@w}FEfdseWp1=5=Tg~VnR5Hk|c>iguE%{xVH=*)wQR()580WNZPn- zY`%t2gu5c23nX-iR7=YQQjq_+X+u3y8;4uicnv!|Wo!5WTa_5&Eb~bo6q0C(S!i{X z{IAoZo&TP0lYY%M-91>B1G67A{o|ehiPg45SJ&0DO4KPJQ}UC$70T{)s*v8~%ITEM zsVkbpPJ}VuDMG%>w4kf z&ozn!vuwirdo@utdL%d>3Uxty>gw_9jla$SNjK8osU=Easd(tFO9HWpOix3_WM)?T_ah*zJ2rB{7O zK|b5fvNWIFWG3C8MExL~7BE?6eY{+f6|HHxc>UWsEh=po4DA~cQ2v@TvM$M55!bI} zmft|7&@KMLQZ}q_s#0ZWoKmTqD`Q%L(lP)b0F~^R4n~eoIN*)7c%4Q?`Mwhp^yOlm z(E?fTo}5F)d)Kb}^>UqtVUfxr%$)Ad{EIj)v|tuem2AJfH7{d z;lbQ^8O>!Efc)4z4Bl5b5E%ZlBgPc$mScX;?I)uxVn~VRQ^b(_cfK@ec%m|R1cNr^ zT&q}GDjYo-?;y?s51} zmL)kTq6wd#qI<%C2n+$bZKGG{oeAng!K%HG5%H{U? zEH(xTVE-tPzNF-T4_o$unp_9sPd{N>G-+U`=` zySm_wUU1q~wQ{>Dm=^O^ZbErtYb&FGBmoH0MJy4g>+hNHqCqJq0aKSvmotT94xtY2LL zr{=cty~9oV)9+^F=ec_~gTwf>QTU;hKNZNWaGlez^5XiZI=f14wRFpwPasL2;5MJ^ZImzhO-RS{8ut zg!=H-&h=yOjq~>$2zk5MTImaJ8n*yW$Z0LpbXB(q0d@BK=E8yxYht*ZA|PzVrQZ41 z+FryPdmZN6Zq}7kbykX6ic5wn0?nA_dRL3dqFI|Kphe5y<{^^HngOT{8d~wn zHznM5vw^}d>i|?855bz9I48>1!e>4Qi5-kJ(Z}y1{-)dY|l+b-1+Og zo>(T3dL7=ZBjx7NDD)~5C_X=p5Hgv6Eo%<*=K!LXUM?G?cle}u#?e<L9>+1PsMkc|e8M_3u7ng+FPHquH@DP=VDg;+uBs2)}R1Ov(c$!Zl>)zum_^|0HhLGx$nOs4RG*B*x;95hpBk;gJPT2DMWY0Ck2 z-dXmX9tTl}HvMDGw)smu`L8qXW32!5D%RvFD?u$3o#j>eXt^$mMa!&|S}##n=J(BS{dL$!gvdYawMd74SSqN^&g5mly;^+p3TZb#j}~=k}_J%V$@` z?`*@Kkmm@Gm+m^+mGF6`h1`(P0?m+DCjPf&u|4T%m<~vCc5rpCSQi;K0{fq*q+au)xTvMwB%7B>Hn$v6Jk`?WBRVyQt=5@ zB%bzxr-vthUiSOPj)eT+3tw%uy!>uvvxZk?-vN*3gDcukNAM44T2iZ$yyNbk$!%|Q z{ca9*Dh{vg;Ny>8P@P_0e$PbHjZiD>-6*pw2wl((rUpqy3N675k37`ovpxk5H`I}n z)ah0Sj;uSCY~N~cIlxud5}(V^`^4vboCCj)rs}vWN5+q=_>^0fJkXMBsGy!C}qSQ%P4QJ;tY?^pfbzy1$+4%$>`hU+z%<&*$TrdzMP4{&43v(@V~b&RkT)sS%FpxKBNi~xV{Tm zq2$y{5}(uFcDXC}L)`vhUyoTVneFl*$-^DQ=-7CN&FRJ$7Qq>en@4LL1@+voBu#eEM~tmBZITu*_`AISXmi>(#wS&?2bN_jY2Z`^o*@~^j4qM-}UX52{M`I-jhwN zi}!Gl!nD=&J+*$;`4~s+zIEWV&~y9=Sjj=oY5N3HL>N58F>kuP>N3UK9v#)r+}#XM zG|5u$n}{iUlfm$v>74;jVMP^{@9Txr8_FX3dhrD?TXKYfM79^ymda z<%RTCcvdfpRz5b8)^2sP`d8ppc!N-v(sTVzsI|o2W`Of0R14U?os+p;0F`?JNmqR1 zou<2~%~6TAu-}HGHFPVE{qp*$-52CP?IyigULEzH+$}9oXJN_Crs=(U54Ko6quTY5h+OV-90t6`yxynGk?pgWHU)^WoAxtT*BRQRFY8-a&(@XDN5S) zIx@kzR0LzX#ptI7gUFpi*?;a&;>T~ic*(@{e`EN=$so1L(q%e8xM@JUhgH!P*Xl16 zM#h4+&+gqc;w5zUZ9gN!fNE-D@|aJCsQ$|(mc_rw zCThpFA3xdJ@NLOErw87Te@6KJV_(9>+Ot2wepzz*+IMLoLi4YW;8%3#m|L`gF3jpO zh-bG?s9Cj}0dL zk3H9KQnRs}%gR0#Ck57wtnFr+9|f{CmQ|3)Oefc$1in_~cAok^3SSj(-;&;gLlDZ| z3!3}kI4eD2+*8tp+1%OL)qS0wBgcxwPVu@zoK+9roi-mGA*(lM&MWK$%L3k-$YO|E z$_*r1>9ip0P0?C%jopdc8Re@1T(y!Mq6)xTD_wtwZJ0@l>HKW0r^uvZq8){g{NV3( z^I=erYG?{u99!C+sQn9eLb0$cPAo|xaB;Ms33Sm~7nsY2gdWEbJ z1fI(?T4o>g)i#Jy_uAVf%$T`ZFxaQf^1~^SiJ@HWo7IX*)ORQaES0*976nhA1_ujt zbN!s@->8x~#CCTJYtaTy%QsM~R}`u({8jKLmJ)H~J&{4P8ef0ZjIc};BPPITs5Cdq zbx}d*OKz|yfR;j6)gjJh3n(z|CVqMh6vX^x=2RsWkW>?cw>2~8`!QV9=jK;2L#CSO z&JA*!sLWMSVev^<`?Ip z5r(A)gUah?u9Xyy$z##h#Ox7n0X|g~*y;XSWf0K^lKJA#KjkIIyShV;5v^|S{xej% z!VR}mNFsjfU#jP(agMHN#V3BQ2rE;?tXn)E7ZwYCuIckeMg^qMwQ^S3cCX#9e>`5f zvmOX9+6QL_Ml+!@IR8VDhS@a#_-!LE&9dp;X;DV?IE0EujDsPJOj1+P7NSaSAqf&$V^^vdVyP%+mogS zd5#BmYz@V!=}kNy`nzRG`}*~@@XUw(8r@#}L#000iEfVaGd{FwDbD8WGG#4X{Tbn@ zr8CY_wv69zt1{qw+z?*o0l;+L)l8rRRK5&3lP`L5xIkT9r(w?hcwsew86{fN=xOIY z(yKcIV7)gDN`85$+H{{22qqCEv&$Z)U~v@=3O({A1{rv86*T>GLeJ>6ZT8hoh6E?3 zN*Ri!RL36`Fml}ZX++g97`u2#V4CjVl4Q?3;Vbswnzp}W4U@D(yI(Pd_jmKCsu@Od)WsrUYv+;tX(;@6*QE${ ze!K9j4{G=U$aR&aSpUj5?tCkc8|IT_9hyR_V@sq3YMZ73!cTUyu?(=91C=?SApg>z z|DkY)XZLIT#rtxBOAykkAUcU2{@Fvw$J%1zmkfD8rLBh`Y>*?aba03rkH1?t<@o@1a#>sAC2qXxJ#SFWJrgvQnFN=KDpup#bO&{h`-%GR zqwM|jRQk73p2j5Ojhsiqb;;w;mI-w)?x^sf4i!772W~-rOrQB54f|`0MIs_Mu79d8 z5aq2&E$vI|_o}MA*C>cEUEf4jQcKSQIQ^QMbBs(@0rCnVgP|58w>)M}!wTn!Lf9Vl(Fv7Fm;^2{s*x@Q9#6I zkW#n5`_3ULpFz}UlhWU0Bv^GBCv3DLMI6`CFQ5R*b}oL-WuM#PPbHe^wK1C}Rr3sfEIPk@OGUoUir87lu%i|7@Yb);$(xux(M zV578PV0Dn7_rSlvR_>3HhalcjO2o-7SXy8g)|$qz3lUS02vxe(To<@FHf zY3N2$$SW;7CARU**vl1D`}22Btf?!?gG$4G5gH(Kc4WdXb$vYKmhvMWWL$8q>F}NJ z$RWdi;|4@+nM&H0RHL%lstteEzlWzZ$|rS*U$>KUSGFZh%1?`{TxcZx^uE2|&wvjC zM*!)?Py_I+MY67*$(nK%akW51ul~KR#*dn=#XiXmj})K4rRzi zdLM-|6Pi)xY{JUOc{O<66zJ+_7d!Z1>VCymtd)BvwbEEwvKYq)1mdbMZh77a z;ZLuncT0XPPYip=W)ZFvkkZ6&2yr7qt7^4C>54MoGGWm0jXN58{z;CNH{v&3SG-O0 zsr-bPI{SFiPwywkA&+F9o#OCV)1`|3QKY}`tH1H}&wn=G#WBwX()E;7zZHp8X4AYM z;n!BwWmbgprfN*wFb^hEBKpD{%+nuv_jZZM@&VS|qH?C#r08W6Rj{5L8$8FL1B7Nx zRo9UL-7f6i;s$>vi+&rO?oi6JIhb{WVhL3v!*QKPCGHxn<$&U!4@I)bFC4$lGufv) zTqa6!ES7d)ypPD{&6T`}nl3wE0mu%asNE9@Pw(E~?^}0hLNOn;YN)^HRj3AW@7|XlyAEC)Za&}3Ona!*4SKfOE`-Pd>7@?pd8bhlnR#YskHm$ z$)%wfQ^HTI`^1jXkRXF}L5VqgvWWwLLCjT?5V?gJ;Uq3=83*$U>_>1K-$XX0)eOf~ zJMwHW(1UwsANU+2dJ)_&akAI?D&=LnKBZ_0;`jo}P2TR?xgHC%JgpBYFs?+(qv7%r)1Id0Jsx4nwR9&1MM? zc@?qiXA!F6Dae*DU~u=%x>L>35KvH-VfO&6snzFQt zYVM3OluVgNF86Oq$ZNUId=9or>g*eo2ikcEHPnt9=8|BCYY!#~Bcgyy5vdUkZGv;qFtYxjVx9x4>0~yfWsIS>5 zy4Qf=En944u&KhF>WY!Bb|gVPG_x`xg>{5|j^L#mXQYZ#cev@)H??avKL zFJ@wUhJ0C`-}ctC%&t~yulbZB<<_|EM{~Jq2p?ITA>99$Q?ciF5PqJb%x5P#79Ek5 zv{LE7%u?K6HM5)KOW~T6y;gwhvCwd2Ozwn!hpsg^3JI5Y+f~{?fqmHmv8q6yK;Kao zLVam7OK9BnNc2M!RP|J;j<-{qANvKD7_L6trRI?DwaUjeoFlZSD-Ngr#BZfSDqhE~R_+t?sR5~r#&7W%*Q04N zilM+02b^COl`PT5nC9g#v-I1_KLCG$NkuaxS|WFO31o1Pd>bc@k5n8hKF5cDl zNd8+dm%OtL(I{eab4e)1abqHFSanE4(vQX?wJc{R=Ib(R6Q6C*cbZK!g5lZ_VdhYeb*Z{v6)Q{yFF6}TQPo)*m?Ud1tcjr=3Q9Ht&ciGw$X{3`p}#h{K`)xrQmDjnl!FavknbHa9909G9 zS2k^)U~k3bku4uR9?9>189q|ME%*M$^A*MavO{_!`3a0r-9NhE?;4awCidR z-c?;*7rP0M(YbX4w3`Zlcsn1r+EuF#7;=(c0_rts;6 zm{+Py??e0%y!QIk{FyNkD`EP0W44q;=PUc^j#atET+ih9 zA4H&@Sp@ki?9XNtT)xL`z<1Bx4=)Vd_#17IIW%-gl;E6zcxE@l19~5dCd;jF;DM@c zi?@z6y@^xReSom<;i&?O7je$3&Ga7!3C}?E$b+^#V=ksnm98vlRJSob)8r?OiTb7% z-1+c8OvcJ%j@XGOGV2vp`r0h*A7qt0`AaG{&qpxbb(ygji_Tlvsa6 z9Lo^Ae#pUqYoM(E=evxu&o~!G0kULVTc=OCd@>CVI@ztjJ1AY-REWJ(iy$m+b^+Fj zb45}O!|-ZNa@rlNd1b529U%mDHB3LnTB4JvYD^$-ng~hE$APg*Rfx=9wrtz?rNzf9 z8S^g&^wA5m_XKt3j2pD}Tr1>4C$J6;U+!q~gtBJ34uhG+Twn66#(p}>@2R8ft5UB{ zDZ+r;IRF4&s3rok@9r(5^lG9_qhMTi<}s|ZymQ4&DG}<}CtvIC2TqT81x(16_$Th= z7q#uXzSxs+C1Bp=ycBWz9GL9%ma>*mF|Lb?-=aR2sy=UL%9F;>Gkg{bVB}z}FUHtz zgbad2r7J3iM98llz8ILH73jHmhW!^KSu(P?ILLX`QnnvyFmoF-AHh{By%v6>0vNf5jOgs94$Shk6~=n#dhg3p&T*Hwt2WZ7B+M(O(6peomGLp2>qp54{Rw{h zLS71@K}U@efH2C~>gsX|$nr+R{#ns%#qSvJR$m9Pg_zy0@pnISC|BhrjE<_aU)K#l z@asUbZvibGi^MaKG6C87L+y-WN&X_o0lWOR!_3*roaqg8+47hG@GezVz62PFu&iY{ zyCgS7`+LBILvVMkVf-bjLneKd>jd)<`TKien*^r?_CZ?9{MD3%QJ9YV@ZHj(f07a_ zb=fYlEGABf>CFLtxW(C1x5d-mvpdwDoQ7HV)3OtoHbAbEyXnX($t^G!7%gcc=l|h2 zJr;i7x6zkd%GNMY<$Bx3m6g%6elIqi$oi#dNo7KpS4}`jc9U0tjWtYlOH?99b!e5^ zFN2|}^OuEMWJxzW;5;_+X{crRsLo8bVse*;#&C(>-#9_bIN`Z;*Xoh}HIB~}eA&0W zqMQGCPFhn3>>t6_X;@t0KTK#L@K_)87Kq7R9V&k2ExlYt4`T?G2cB|V2U?6y4{ACk ze4X=DzQj-Aa|ew&Y{cxn8AQJ8|3AdNXH=7Kw?2r1q9Pz5J)sxry*Cl*2#QKCLg)!K z^Z-(%6MC;AO@sg;gdUI@O6byi2kAkYpugeFocFxv%v%4o{_|mGzU}8zva+7s_ukjO z3hf9Fp?zABy~=Q%S0fifr+Dh8t!Rz9ZRK%uQeOvYhD2e=;fYN4+Eg8rx|ILVO8Nk2 zvo_u(Db^?Hd0+D$BU7qwVbpNm$JIl#)e3X{yKb?lXFP?_x{)~VailU?J6u;bn`*QdLCoc9Ds#-sv>TMhBt>8Z@!9Xs+;itT+HZ=^uCRr6D(r85H z$HmT)l1P@6JS|SCyo=%W*7^fp8qGF3UvFiM%hNYd=YiV#9*`_AD4aI$S*SvMkegy& z^??mIVlg(+O2=e2dL>p+6ql$5RFjC(>!P({ULj#exTi8+Z{N?yE@F{(ei4&^z8<0b zb#!F_GSfLeEA>YhO^5&jc@zBoijT3hJq0+6!l%NwL&8A+>g|5+go36$t>lCzl;Na- z_^w(z!;JfVH*17-c@K#_CONmmLQ1o5nK~YpyX~-ZzwV0MoIDLw)`WW4Xo%Yf8ekpx9e%Ih4d~MVZK< zv`?z(g68B^Yc)D^rd$Ou=KfTA0q$tenQ-zLL4fzgxe3Uz_|-L2fw7^h4Jt7ek3}Y` z4tpEjDH>8K88NPw-Dt^6w(yiUhiI>KmcE8(SDB#geD8)t+Jo#-mxyfQ=hXSWIr5aM zEb~m`)8x|Zs+!S2bQ~iaCRZ2$T50GskoZu$UyVd$nyu%tJ{JcyktMs=Qz3Nen zMuQU#rO~W%96a9MafsAy*?;4kW1ewu`A)vf0FozcL2@<=6EtoSF03tNP_X1aFl4$0+_C z?sZ%Jw z9rG=!4fD~VQ^HxTJyLKTsW)KKKgcmFO>msm!{V&B+IYrC#vZ!DOs-Y~7A{r;GdKVvSid|Tj_N4U8& zRvIh`t1kmrpmwc2Qw_xo_Uz{UNJ~|#f)po=NOG;+2uPDX-+!cyqW0A+L10#0v3OY? zh}JOxbV$+EP1u%ASmsg3cFHA0#W)w;%k7Z`lvAl)bZq`0N43UW?|Gucw{c`T7uP`h zumfm-w{YgFm+X@$Cknhnym^YK<&$1c*sc(vX&}d{5*Y^ivK2%=+c3`OpA54mju_nZ zak@yH2IhxXaW|6FI!$+3KR8BPeH-MDr+IK@P5To6{HKN+ljXBrJR+N|d|xEztZemn z6K}d*UKN@stt94dr@F&7F~Rp0Vwi6KDZEnj9TE_al+fI)AagwDfFo&TaJPYEj7uv3 z<=wWUB=c18nYj~Jy_^9rrfQ4wxoNv(lp9Mtru*q+g={m`ZDvNfA%iINM59{QSr4d3 zF`C|{jH)$KmvY0ycl^?#&&-)=C$i~CK0M`!9s!GwmDQkI4*yI=uD?b4&Yb*d`GnH5 z_JIz{chk0p6fO`DkOdRm&CM>|I{Eht{O{^t$pbMSVLpd*f8+H`p9DS#dGeG*rqbLv zk2E*!nVoB!O$xe-IgYZd3L1tTZ-6RrHhXO++VYFb8fCBD$G%Doz0zjay7DdZKqn7D zy+zSCEvGx>TCpJ&7G*YkR)!c1+kTv2|^JLABeg((0cN*u=M{eIMeK~Tce@IIa zM^P^<9(s^6-K#Li%oa^c4{}`NG49(SofSB*`m}yh57>qoDUxTI?uL|3X!F%Z%qz(F7x~Y%c<`*D@NIP|D>Lg z+M?3WEF^nxS9C92ma4UleVGsB0LvZUx?h_L)+dsmLbK?RXs-ajaJ{(alM%wg`N?co|>4;>A^a#n4sODk>?n z5d2GUTIkL6*O@pk{=U}k^TFF>g?T2zkke2ME0z1dfBpZc{(o7e?H^$Es|ja4!e3mi zTX{{CHkEv*X}FyNfsFXQLJQ6{eh$fb14yr=oUR|drg?&JFnHF%<)TJ5Y0JdY7GQqU zPO5B3{pP$ziN}!$sTqF+Oqs> z+U{@UZTD`%j*-k2`8#uwb*(9_dfHGXr4t!-3x0fr^Dgi>mq)90+>+Wj}lmKxf! zO>{Zd>Gsx;GGstheE++qlxo#e^=D}zwHkD5<6Q`N0dL`ng%m5?&Hy%Z1!HUlC7|Er z6}uc{o!|cjm?LV6-#HMdvj2piFlq_PmwfH&PWm|HD`ynlm#{=db>ExT;Z5%6DG0y! zrED;i-1=|3K>L2)&J)J6--unZ^o1&!=>pA$pNw($hJ$sM#y*2>8epR{e_wxYkGb>~ zdlm$voOr6{c&CYOBMwNLk6~=fIh-p!E+bnCJ<~Wf;uC6$Mav)Cl4)l@BnrQVvQa`5 zhJs?vzK?o)d(yOr`${^x=vBag}{U)Vb8O zecyxGLq-b^GvhF zHMZJx1F?f<2=mT+@w8GoI@}ggNnqC1e2xSOfeUNFSB%o>{3w%aYFzRQrzrD<08=#zt2iHx58JvA8)$G@(8lX!4!2r zc^Z>_B;&2`9|ZOI$oFze8uwYq&13dHvTb|&f)#Jdo-9U$WdjS!QzK|!F;%GmM36&F zQY!VyGEM$yOn>gG4tj|%Ozgg-GSTcegOYE%YzvSG9){h#b%(|bamV63VII2qgM+Dn z=+5)?qsV8co(nE7()Ck)MAG*b%DD~nEDZXvLEPer@6yy)oU-EhJcMVvwdj51oZJqV z1XKz8p*ExLGaPpYWY3n;RwZBN*vSXJ{GSG(|BIC0f18N@Kdk=WPcr@=O%TQZ?1}#U z`1D_vg=PPxjwT+BqPXL+?`?eMk(K?86&oSUN zxV1*`3m9SK+-@KI`G7~BJtct;tD7&W$lX8(O6P{tEpahAnv=eWq*i-eJWE$0l3{1h zkqkfRDm*;IleM#Dhemfs3Wejgd1qE}spx9kO*C+)^qUn$t1z)N{ii$`-S1XPaEI;H zn@L@oP8B_>RQo6IBD%oKg)S`t)JG`+we{)|{Zw`1>U@8Wo&&!$H}gQ)q+qJ>w_@$f z)c4PYBo3yZK-U*-v&{J4&pSP2hd}b+IayV=%lA8?QmMhK)8!2MZoYD7s5i#>H4x|T zhDAfe6Xn_48DfRYsa}Uy?8jS0JW&F z_c9502k-dF_Z)$>A0(cEbCq4)=ccVrX0^=-yQYvvt--h`gr**QYy%0s(Vz%SWnL_s zBV;}G8vBbNBJ4|Z1USM?X7)>t)Tf?}lIT`s+or}AlsG8FO_s!kfPg^!?vD|FSJ|T< zlTvX%cY5CG@VyYAVZ0MoClbnFUe&yu0_J5)iFs+l&&KxOcyduu|DU@9vavnL0@yyz zVYkytGmVR0QHYG#NA-E$$H>+xVP9Atv5;0_BTbb`;V9+Q%#fG2vtWn zEmWdnyvh8sLM?Z=hJB)#6x?cI*qBsCR;C@$s7T1e`8a{HRz5D}^+y_X79TvVGAV7y zjsU;Y6wS8hKbIbR6^WDb|MLJk_Xh0Vo3pXvZw2py=LJg zO+N)9dW`dX)v1=I`l)P1rTb(l{V!YRZVcE?9hND!MO;mxPG2Ga2#n6@Fw%10bk z+Sus=+qORHl0Mzvb)t0T3?q+x1bnZGJj>d9Tn(uGqyz z*~5Q3GaH(@>`XeINWqQ>_zvpPZMqHZPIQ6 z<9ncW0rdhlHc&bH_Y7c7??@5i9^gh&RkKNE9nR(Em$g$Tci@$2#n2y$Wm_pYr_D!u;UhC zAZ+qzmduAqUKJC~5tDb?%F zvIh_N+t4xbX(7EN1P|G-0C(6wMx}AO4bu&dWR>sxnEdrViT|S$(g^BPlo~KT($1eI zzANdeOnmi}#{GQ`fVnG@uNxk0*)l}=W?8$ATAcLIfgu}8S4Zv-4EJ_*pp?)Ri2<$fT z?>xT+CW}PXFV$gNeo^9QL7oZlYR9BtT5%@CvN!gGy5rW()8;r0oU~P`o|!=S;sm_! ztpm+nvE+cb&g;Ek9$Kpd55Hdy=KL0zgp z7yU6NB1TnvQ`%2$A+$O)mgK&qxYiZFCTE>*;lN#z+>4bLo7tZS2*Fea*%-*;(A(MS zuDT4sE(2Fv)81l$@M=;v+*Br$1<7ylSbj{VnzQ6PobWS>u3Pu2q0FF+CFG~vd zOwsZ6vSUW$3M{ptg#*%nyLqL5jk<$BFsz~RA>jBsuz_aJ{}J&9c_1f`N*TwARtx020?z$q#t<4mm2Ot+(yO`IG z2F)2^QAO5nfbYUyn~X|a1r$l?)nwPSL(w97$+3|~vaPJ&GY0rjY0Xz=5+zCrFr~pF z^Ld<_w_Lp|CmACJl+(FZ7Ne0T9x8b%_aB(-2~l;DZ7#nt@9_wS$21K+)$;=YiC@_B zrKZ-r-^*zpD#?+o9Sg4#%X>iF`eu)DNWJtIe8Y4{^oIfQ`%8YYI{u7PXJeMnW`2O}+i#)f90e$U)y zVzc<1lO}(@+%2YP$XY-!$}&gsH_j(*h)lY40%j_^`!kuupSuycZvJe{t9Z-A+#>O~ zCpSWLaXA4q&%IfRnK`7 zmARccGs`d={ZWp@bBzEvaN(YLeD;ld{O?0p_j^XyKaywOzs$s9p_1$9UP;ifMW~sb zJ&@Xr zN-XmJ(<}Rfs^799tR`vS!PgJkrAeSUg!=5$R-xJ9_eP28IA28|2_=ZUGC}I>2|-H& z4Cd&D{~@SVLb?3%HoHIkD}7|nepYN~sTjr%A4wVlF_7eL-|A0N(|`>mwQMM`E=c&i zvyXH?!Q8_YwuQd{TfD#8R_fNby#$Hq)?4(frda5Ec_kOvVeVL7Hf-867#1Crw7AW$ z&NXb>#<~{lZ}wC{RiZa%rn)@ko3#TL7jETGx9`ozbl#DZgrAXbOw1CVbi5VBLEbwr zHVguiYf&bLol>pH>YxR3!LFA2=Z>=P{&MvVKo{QVaV9pv1H`Q}$gFC1THX#E+Dm*6 zVebz4To>uN$#Bx#y^=qEdj{2R7W3nnn6dqKG&A0xdM2Rp=6lcc4;);B-jxc;tWN+3 zrC)ACygDY|K=Ty_%zIDdMGb46-`NP$pN!6X!5-g#TW(EQJkOqnQ+zJ5T*Mf8dWM&g zqu9XD^0sQe_>&?jO51p9Nama1mS_Uv-*EE<>Sfq#L7H(U&bj6av_yzr4_U6*Vw-6${!+(<+rFtXm2h1ok8#xRE*i$PoR;fokx-w0(B&HGw49bM(Do6PLDMuu z#+?B&B^|}@8pSymZ2m#@XXV)cL*RO$F7-wg8<<>cKwq#<2f~?un*&JBpRI@Pty0X8 zXXs9tNgWy2=SL2Z{g5+LJ$Q99Xg6#VE=Q+DYsv1M_4uu(Sms4tYZBFi&8X-O%#pv#hw&~8vGDc7&BieDUau2BN9Lq%n^`bZpI~cbH(SgbilD0ksu739 z{78FqV%_Kt8ryy=Ok<{ToGozmO@~~+aJLB|!yQGzIl?F=Lu%*n8=0Q<<^J3&`|R(r zQL1Su8a}Bknbz1MM7P-Md=)2S>k2)zgP;hy>-n9kz|hg{C-0`VP2>8k=|9H6mnbnF zehMg?gqUZGvvoa|osZ)M?WJV4wvHwOvh@qH*`RSRudh~gsYIG@s+oqni}W1F%qz^^ zBiZe@7H|Z$f^XUZDaf|f&mW`RcO%^`E@G%kjA>uX&jc8`X}QEIC@+RT+N!T3s2ymLAaNaVng=s_O&t zGqGc_hk6-Yt{+3K6nYstgbYXI4j8+T=Wttgkxth%J5=Id^mCDsao3WuyPTj=wbX~h z&@Ar9+nz1@f;=(EwEh!XQfrh6cj@UT#70T;8ejLRF|zx9uvb~06TKRD+mHc(YL*YLV=QKv{N z_7Iy&sq397H|d&rnCZkLKJj^#1?<=I-A_@~q|hgZWd)F1eco{uM+$<&TGaa>nCknQ zb7WVGvk^_jq2Al~*y-A!6}VghtU^FuxRZJQw87yB0f9Y7cz5U)t#b>WunGfmPfLK_1jeW5wWROD zURi_jzTgcM&}W1K_Qjn8?z2Nt8tJhUT&X4!*sw`8(a+_U<95Ecb?D*-*3NzIz{qj+ z6ym&7D?aN~^U;diXlb=-tWBLf@k+A7%mtWsert%pkUJL`2l-N&q-7)O9BJatk^GUN z>cql@%d}&>KoZA9bI6}PZMNNu9rjZ(ofB1a>?m_~CK^1Y11Xv zGpgT|-K#g_=1Y^j~YX3*X@&Ar+&N1wEN4l*155Y1W8vNfLLiJ~?6!(;p6;TElh;V^d zK|izcSN@9sOr7St`!5~cn^@IuVNSbXZ}v=K#9DUW~wahAyW~%h-kPh#|3Hd`kvvjGC^1kEXw4-|G}uE zUAC)p#_+Lf7%RugYZRx(r+RwEJb=yh#huZQ^d`E%n!Sbe`yP%PaUXl$2E0Mlx)XV% z<0DZoZ!>m3<1eaekZcn_Mmp@AR?!GibOw!K-Fcll&zLNel<*Ak%R0j9R98|LZUm^q z=ILQAA{utz2*yCiF?**N3iV<)ooc@Lyv$a9qU(nxO?%3M`6@-xM3ddC@nD8YN=aqn zgM;61>iV@D)cU2%pKo!nvUp;ebdQ5bi|bw%2cN5t6#Digm8x4qwY-N)&*swh-bTdh z_DrkJD-!F>+JH2t#qzIYJoVhRmitHdnJ5t{moc!I!C_Zu`Vhm&am`c`RnG7hB;J*}td|9{!_>U7l|#!0oVm`!!tOI8|)r*u|kbwAQy` zKtyROCLCa_FGov;$=I@F9c-$Fy?Y$1PtUWyFY8L`tht(k+KNh+g&p6*zE3pvrnPu+ zF+}tlo5!O&cJMQX2|ngQWIeHiL!xecJ3}=0ldidmh({UxdM@qZ>HufED_NfmX@UT& zB`^HZ^N3xVtQ%!>6wsjVCp^wt(Q<`5x+OOClMrqfOot>cE6oX#*NAPF&jSgkVFO-(4ROS zMUl((Eky(6%5$#}J9Wk`{9S837E3r(+YdEgP>G4DYk-Prq9jct-it{R{ln`g(pGxp zUTj)rlgpdNdGUn!%JJFeNrgdCgF+9806m*Pqxr zHpzALc>FgfV%yu*4pbDE#$VrH4=O;f7W zF;8E>>dheB_FMl%5*Snv8Mw@p>3=)Oxa{z%96NN;AZ}y|}2VW%&tZoPTR=ysv9{Hp~(!~`6 ze!K}N`O9;!m;!~YI{38{pyF^V%6VWb)D2eU)Eb|K=8$Dt+<_*rc>m733TS)J{m8EJ zXY_>=+v(FTE1I{=0@D*QXNL3k>fdLxZCpRXe2@zt6U$?>EwCmXFcM>wol(3~y6`+r z7A^a=o%q1RSvmR5?kjSU-AU)bB>WYFMbCvt=58z5MOg3OAoFx6O;}Ztdapu2?AuqR zAgG8`RE5p7#GVk${4N6IA!n>U-2)QE^1!U$-#b0 zRFkoHkI6SH$V3#Y9^}=B=L*S5;G+u+pBq6;5hfb+cHP2lUDwY{*}HTS#^e#itvghO z8~338t?oJAx-HuYj0dpG2s5fLHbRt%giW^OLZd&I5Swz#zT>Eqdg}nyD#j-&CI*WG zJ+J{OjS;HKiih z)M-DqU~zeqyQic++C`dp!nMd&DH38PJ{;4 zUJ7a~crGFc*onL+uUeGXnW`88%?em+oT-I7k}3csXD&c1v&JOf6c-`qg+a9FrH3ez zS+e@(KgY^tG8xU6-CaDT^*ZJM0c|zTD2QQut{PeaNb~E zJDh&kQ{8!kn0nB=V1CCyv{9?cg57fxHTBaXf^Ocr#7CLM6XtFO=(Y?; zw7N-A_4f!%Zm@CvaCYb(+F;$UyA|VHV$JgI0IwDP(4@pPcAisl_rAcG2ua_fQqS^t z7J#f=Z~E}SA7mq?=%W}N6N7?m;nn^2*X5*-+~0p2*02U3oG$FTCHVRk%8Mc%V8u3) zIuH)0$^Q5BS9Mtj%+_+_nFV>2YKu&6MBhiB;Oq5|L2l4IoU(lz!_swV8H~lEZ@y%| zT5Iy_26nt#tR}cS;Kh!OcgNPzvfs2h*#YMp;Zzel_O6^7`BY3Sw&l{Yy+=O2H(PIGLuRiCDq$@{1p~aKIcyN&W6)^hp(7?u29~qk(fE8A zg>Uq5Qv%4jO#0<@3kwT8zK|w11{!uJG-8_1oMU3ToLLD2-b_NGhp+BS7! zlcimh$hKd(BULz@Q}Rye2ID^yCI7Wc`h4IKx{i2K3f)SIGj~=U{BW+(ywAnV>0Kwz zvk;>;=zg}$PoX=jEn_3#y#@}`B=i2x{qomdQ$>$VYq~1RS|n+v3!9t_k8Hw$GJB)emG>4kvPbr(9W8Rixaju8y$Yj zBUEj~nQMs*@{dhLBSrA|Q<^#l*XLxbTl&)?t3gsD=B^LT`&HoHN>Lo?jWKn45Oguv z#@F55kKB?&}OH4KyI%o_}&YL%2-H(lnU}a8~_G9wGpY3f-zODDg zcb68dlXUXZu*Z2&HH00S#1yAWCpt+!#}ZGuhfhdl_1tE-)zu|3PFMF8pKz)3(blRz z206^fFc>(^XB#b3_{bIQf1f2|Zc)NcS0{<%UX>-ViTr5hSEM!V3Lx)fJ#HI8@5^A7 zxXy+Kml40_Hh*D~tEMs0VNxq1WMD6kPzL;h7Hg{~Cf7SZ? z_^m`&<|=%tK9*H*|Eaxrh+W*-Fh(IRd>{!}{jlNWbH2SgB*Q(nk&OLMLM$a>^> zZ}DK+T^l^?RWjdgc6m~znX957T&*@J(C`1OQ`<68KJ-g#f7i<2r-pvSRtM1dIgJyGi7!%!U5y?)XGxPWsG`6+WQ%wEDZ`s} zbJKe($uzI12KPmsPo*;D?^A4e=Ut}OyW81eZVn~aPxKnphSacP*2$8xJim9;aTkiu z3G40vu;zEwWR8DkL@KKe?w%}xxdj;z(hi157!Qn|Bc<~~BA;xQ#Db2A+(NlN!|!)x z>g6@Z0yCK%9#Tlg+tdHmW)tE9jGepa?Pt)wF_76O2*asM|3skHP@m?dKSq0YzZRwu zFztSi3aqp_m8#UUsXfHVav~3J|6l<|kojukF%n6(PXi|aYxRiJ#tU)R)|=RzAuN5Phj;!tNLRM{ zNzbQ%7qsWKlFCfQP8A z^O*Y#<4*!cPUt0S!v55v_vZP4d;LMH^Hs}}gS&tG^MynX8BZ^qwdc6yxQh%hd42(I z^BP2<>b`hEXYE-s$6Zy%ZV_9%7E`95^}PMJga^TMTTrfF&RV{q`0nEOtM>(wVuk|t zr`V(Y9HHvlKg4eR}oF~ zps-BhR~P$UIs1BveVdJ3=!KqA6Qnrmpi^`y)E>@MHZ)-}I;vwQh?30Fn$(|!C7#!9 z$M`nI%B=aqyvd?w@SzX*MArhy^9GJbNZ&T}YXv;Mx~|&{No+NWYg;I+mAF|bCR|#w zIBdc5DF_OzADZ7F}!Au#zsXch;{R4`f0Vp1Z^bS;RWWtwNnqRl_l{ zsSE4!5rzx9-5VdGW`^tGnFU%Jws1^X@%|?KXv=ZSwcsU1Ro+Dq|FC9M5EH_kIowvT zKG#EQd`ry_fM2^q^vj9>;>9YUxRj@xakjHuDd{}*Z71ipX>7KSTBnp3$6%X^0=GBJ zj@WyzzBP$OKY1-xG^8Bo6-ITS)gwK)>dYgGa)@jh!yJuhwY3Yi#b~a?$;K|2RI6qp3TvE~YumH%Iqxp)mwea;kXmn6cJbRU z;p1s&mcf!P!_?BW#~=8@oXbtf$YZN3ApTpGwFO(R)I&R+`^@B!`SQmWQzoKWO`Blb z=#}0faW#tr-xZG|P{y~+qgRbH6%Ve>r;3x3gxd_!aG<99C`pg$llf}EnO(du2LP8W zMJ&-Z{9xz(i;|*@wS4~gLBGNtb)XTu+YZUv0n*+gEN~;+iQbkv zzNc|Z$jT5BZ>HjhI6`E}T;mQJFD`_4m^t8Y6#++ziI`ao3RO{Codf7-VN~$^I;+Tw z12w*lKxk^wi)F#6SH!QSgvI-0@+EK@{0`pzO8$Dfn}~rEnisC1g(C{RmCh-KjiU+y zJDD$P%G`V*M2(jV)mEqR%}m;hIk$`iFFURis`k^d2@1dHHI12*QR%ywXGp2;|ITmw z-y^Rbl7lUS1}#RnGRZm^nf_F~Xt<$2h9^7^YI$uUPt$%x5%4{fu80N8$lw<|e>-jR zSFHcNz~}5e;NbuDcT|aI*{G7_KQYEJi>`9 zYF%6_w}R7|LSx;t9R*$y4#-Z_*SvQ^2u|i*N@e>{kJir~|3Np{kr|b7zM|I3{uMT~lIcp5=Y;+$+C4zD+Vmz^8PnS9=(* zpTr(DY`aG9r9VdesYtHW>sig6>yB6gOUQj+<`LM~IXuV*Ed>1GxK_7cIC}&*J|8uX zu?ObsGd}a1*8THQ3(585$w3b~Q*ssBy#rh5Go>T+3$#c$#^ACYle7r!EzpwxLpykB@Wybeji7>T?)1 z)w*cF>r1~k5|=jd@}kQQCi!6I-Z-(R!|B~4p+r7PHd8wvVGGG{dj`^w^E(WXVF5=* zMxtPiU5uxK0emL5{&2CyPBzUhJLhNAO4|6thlkJwlcfU(Ql``CzDkGUp1J5+q$#rx zt(}ZxTU_lU;SM{QQqO?A{3{zG@qMq4FNl3U`ge4S+vfno>Ma?wC~7a{R9n~7nGD6f zc3{^x-+ExQ34IFj?P`cJVaHMj6Z{w+;`x?X`7;`}XE%V@zc|y zU(SM@yZ?((`oF>#&02uzfo7$4X$&3~?SlGU-TXd+S;%!-iqEowUkcy!yeYDqj7iG^ zoabAL_4fN6;eUrd(tEJ6K5#No22Y4S=%w3?URRTfj+ir4d0p7^CX7c?jTdJ-o7K-z z1asSgwX}v+_mzGwa=Rd!JfG%H8hVf9*cK2N7}NGp|4yu{Nd(vuUi}ejiX*o;s~U8R z(EW#iXxn{17S$kVdqww!Mn~~U?<^%6Q$#jn#`yX0JdY{coabp28c{2-J5F=C4FBWb z6mTOZu=I4TesuL_k-uaCT;6riX4$!!ncn`&QEXsJoR+Ml%O*ch((aZDqVSRT5>%en z6dQOSYa&a}swItZULC2~!X!5}^Akwrdium-1f!zg`FxojX`+H$dg7r>!0v5d{a(q7 zx^z)Y)uVh_Up}Ld+R1D(YxP;CsdTwIIRST;I!~5;)d9D>c2DE0qm@%Xr`Zg3vB2u# zw#qk5J;*ltqJ#B&MJWM8;C?c8a{>h`ns52PaKo;#BEXqCx6Lr1Zx#_n7nL6fUI=%& z_U4_t37muQHXvW`NJk6OO`ay>)C?Au7X+74sK z+NXX{(n<{m@IWV&_Nd4It9(lFZx-(VSwV#Vg-$jF5w3o-NNYp?xRoXRo5y<}B^Qd_ z236uxMdhb5jY3XzE-eAUPXN<3kz}9Z|9(z1s4cjAe8%%_P(Kx^Y2pXzGN3Pcd%>&gm$s?(Wbmnx z8NV&h2Gok%yeNB$aw=>|7hQyLDA{kUonFCFJFv$*O73MtarQ0E>qpb`f68d8-r556 zU_StX7yMn}ZU+qswF6Z)zZm(z7*yYq#q`0a-6gTSC9{LPv$<0t+7(c;WNxbNHbjX0 z12mISWHYMi`;*c2Q3$+J@t}rj_S$l_)>i|)B6w?#k&%?LL&5kT89GsJqa3dNXlGee zlTd@)KrU5Cd3*x%JsDHYM25u>v{>e?x;oa3(&=gY7n{LVU)A-RrHd4UN zWl-B}oWyKg+(elm^Z`qJjMG-DUibM=_l+6TtE@(UOw3sBs}v%a&>!15dDXDFfDbCq zKL~J8{YaJ&s#&XFT;-SeDOB!KSF6eybs(Q3dfKMZ#}6$S!g-o3-t`*g4={{s$*mM| zmdJ_D6*qw`tn=JC;6yNtkvRx(Bq5HzU4Wu?)_A*di%4MRdTggfU;eq;_U>oQkmKWf ziwkqcptS(`CJHq9m%UU{>gkxveG#k?L%Fi8mXLD+yx&4ju>0__bWxO znU5Ee``OEN0at6aeuBeebG-yM!NfaC&+o4O2q{80mgQm>o1G@J#s`4xk~0PUL-co6 zl+21ly%1M7J+J}WowYpUhO&+1L7ZFfl+005M@hE?f+&wbb9X~~o^qa{*UjKwUV(sS z5H?Xp?H_`kSL6Ll8@FbyJiCi{fe8DDDx$vy`?{}yT3yb{gJSC&X`h3Sp29EkBSGI% z;21;hglZVG7-Rf#*DBN68yF&w;&u!z?fV*d)gb!a3SAQ08R*yM8H z$bt08!Va%mt3`g-Eiaf*@-g5ov|LQ|~Me15oc*_NmQ=a!dT>(<>X>UUMdPZzp~EJD7taW5;x~ zD0-E+(rEh3vOk1ljbKQFlPPC;*@{dKDDVj=`$8N_s<3oX=F^ApGegUpnz$%FH=CBJ zjG-OCPr)1UHB^NYRjozCG-dC%6RH%WDQX#VU5P&2l4#$R={xM{VAW@HpfX62#e-N^ zg~Y?1VT)UUXXNqPHB>srU50Zwt-gdjXpV7RTF!-muy9Ab$ar1np7)!ii>ogf<} zFapMO+>^v-35T+$KeG;SGtTCsplJ~sz=q%!N#)GC%zv8svW6C>riAmU9KibnScfo9 z_Uio2U`=R(h-=KKEIG>#azlDK^5lFQ;c;4&+`5mDIrh_LO>BDDw0_XGNGQ`_A`A6S>08+;;;3jF3c@ zgjdYGwpl1M4LI$WkDvD5NE&vZPO<-Tr96zaVibJKvUitmk)wWJZAOp#ZG$)gW8w0s zkIy|s2D~wdSJFj0kz%^CuQ;`hp24I)XzEaZ5#Q1X1P4+t#o^uZX^cu@dW0UtsA)46 zA^A2PAY~o|01XH3p=#kbjNfw=MVzs+h%DZEjZGk8cf(htQ6?;JL~s)v%-S;DSD&eK zt|YnVj2&bzE*ApHdvtIJ!RKb?Ek-?+iGU9ApVMM_x*Oxn%ck1BZUdr3HHq1C*+I*F z)AGS8%C~^5ITrUWPC~F3F!Eqk%wPkRTDnKs|K^wZkbE$mnKIN*1^uHAxECn*Lq;Lt z(!)roOF~;;^yV=Foc|}1K63{JyRe?S zGPCLSOLQjM-H&$&rDkwnk>{ZtVN2Bo^0Na7ESRwXZ_|-uGC2_6&UQ)chkg(v)RB!w_P4)$o_1Aa9 zcK5F9KbIxM?_wGBqEEzJ@2qEqEQv74FD|s{mSg4m;8_cE6DuyvW%^oaySTYziT!~4 z3&U_6EH-z1{14530c|5BehsDENv9$9*7if`<$=x#2i!KANXU08-)d`rus0Gu*p~OR z>aS8Pu(wGXC21E)!X$|3!-i>&K5mL#F>xTMx zBaZOzasgF$4gf>4vTif7vV4{~Yf%m-zyLz_8X=PsiN>ubO}GzMVE-MzB}L^b%&J`_ z`@z9S)ueNBj?z+LAq`HsN>D8QGn&KfFCD9U#2lBigFx z+a_PkJ|rc#i7tI0%=XjRavYsEukn=SEkI(K^>N2-oU^c-CZF|t*MjLStMDNL?*ve+ z3V=alnMZbJZe}YmQMmC&A$Cj=tVU%@SIq)1k8vi%e>t*-6Tvo(9 zZI@B58B!7cmKiiOQ!{XJn?M1Ix4?)t#9ebyGvEyL|KY=!gCwX9th-q%pdHj& zhm0}TNXAIk%9{6>>zdbn{km7u=eMZ{mup=su&VMyIY6Ol!4xZ~jP?w}nRMOff}fOJ zY7Ls6!JY5$L^V7UT_mIKgce-MSxRS&hPDE zR<%i9PwcR$;iHT^{=&WHqL4ZTWvL_>)jqAN?^@mhTgMtA6{};Vo^M$v95*7<)?CDU zWC7^>tAfG0F28W`%A-c80KGnU$C|@ru^(}!elFxr`}@mWCcSjrL+#(50yDd7 z{sYATOP`1r%hhGwB#+#fhPhdD1|LMeajO0e&)rG;v04ixiS>Fv<)F#Y8`3#on1K2j zwLk?2)L%$6nk_}#xzvTs6p9yPVj0c&o%+$i9i)*1P*ZaC+mSUU8-DL^09Z#lkWy-Y zJxN7KG1#2q7db?-!I6Vo;`nAK&HNnoNB3>#x$f!?c+tkju#87k^3=xB7=ya33-?i0 z+Yg=cih3RMi&y-shU~iB2OIh1$A`dsW2igzf;3*{&eqcxvv8;_=^Z* zI80!%fPbwP({Y_q_yZCoMh-BobhCndj%zGfvR3cJx{Kgetc2oWOYXUX?NA4 zP!y7s5*x7BuURafl=#f&Y%Bo^eLDB7BXo<;w?b4cBO+_~AOcw>s}QBDNVjzm zM~>fiS^Mti_JLBquZxL~i*SPYCjAsRu^ut9HSbfR3|eW4?y4|zWlois|GBqT%JyO@ zJoC{`H-&q@Q6H-2=U6Ld;WGGLE^+k9K<$9*4N%fDA>yl;tnVx6Qtac^ z(EfEM*I59WJqx*Z>+v^4QHwuFSC+cfnJ%>}?4`(vfvs~XH@AF~~c5g5;hNJs7J4K$z zHFBra{*kuj^HwDJaInwN-4U}*&MUs z$}#Aud|@=uzi&=JHI3o7|I)g0D7jV|_U*8hyCk{G+HVFA#r45^`KsuZzTmzN?3oQU~GQ)D?R~ zx;%0d@~X?ISXJ-xLpJ1kXrzP2GN2PS-`i>(XhNTi3sR~kI1gr)o|7nzZ zCSz=rQ}(x2{rE~o23PFh)?ib`09!B;mKIl+G}swsqvJhm&_ry!9-_Y?(*XU%9ea%b zB~O71UW~>(6o_KawBaTVIp(Nx)U&Q* zwTO10KS+Ek<7MdK8wn403F&>`r|TIelrSQtrus6viK~AsXJm;+4qba8RidEQ^g%`< zVo3-PIV>wHE_&D}*AI1m$uc0i9#1<|BZ)4wpAAq+VrkU}c&h&zFBEs^=+1!%*GNRH zMb7o#De<{YG~dh{>`E>P7i!Z(IP^~eW+rkH4QzI~MT0u_4M<@^QI9u0UZB+?%Jj_a zqv!mrin}zpp}L8DiE>+Tu<5Yr6ziuN^UT0sF)ityHG04FP&$TF99E~RfYQhn^KWz% zf(%Ydwy_4bpYxCkKC5XJc(C?{+Xz=a+T>B0n>MHSx`&kMMyDbk7jiqJ!W!S1v0LYJ z*0%zKdG2pFQa$d_N9$E+Z>3aY&Xh1M7p6Y4(l{r3*EM;nmXS^0++}*nrS4rWql_9~ zn%h34%y+sk!ZlV&V)nTD3g|NTQ+LJ?Z$T~946YN>zN{}*73xYK2n-s37hsPm=Z=u; zmKc^XK{z%K#9{DwV@I>)yL93;#=AK`6}c37^mdRCxVi|+OYtLvU>$CmTU z{m~p32^DRhSAh-nI!tZKzNdVKJ1D#S!XdUH`3`OWW>zjDE*>e&>4S57+rI*pW*N$Jv{N~Xa>PuP?UgW-teU<1x8x@3`w#q+S|?Go*DC67j?G96f4@3?0Lb{y zi7bRJ=ivE5VsZ&ZQG>e3IFUVIhnDYHODXNo9Usi57vN__tg!yuCoK)HX-%t zd_UT;#UB*Ew)rng?SFgt+WC6cY@}z$GW{Qvkq)+J?9cDq8g!)BwN|+NW(e9XO&Ttk}8q6_A_$KioHpvd$h^)(Hz36!gxLc@lojN&l8;= zOAb?oyI;?@l^#&18@NFrl(%v1C0|hNoa<-OMIqZYj}zBB2133k9Fpifv~mhon~_X! za~`irb0IOwgjXR^Mat6X+ZW~3;c!ZHsGN|kUQAh={=S~%DBUsCb{9FSNgH~p8e7?*u(D^OQX5;Z**iT_pye)zd0Z!ZY*-v z5px%E8FriCG_sT?3S51&^no8vxfL_TuO@5~w^|WYE;?Su`BQT@s$G;^KCcLoShkUR z&(X=NKgwwDrAd~e^_B@ru-JbiyB7ku*c4ykCZj! zNfn-XKwdf6R7 z0bn!RP{;L8bleCG~YYATb;!o-6@6tpf>M(B8jzgE$T0)rJ>4{NAh*LC!dYV1rq z=Ykp_T0TREA-Q3^9j23?3=fyOGU$P7AGF6c0X!X((mC|;6}&=jQGHrYQBu|xA7CBl zGYkEyhG+>nm**!tR^LV^Y-KtU%YMW(L^zjNbD=v7%-N_nwBpZEa!tZL3snm#zksL7 zwxs#60KRF1h z+Mvt)+Eu&AT60zC9q_P0Eqt~A*}1*4T%7E@FOKybaVwzfgf^9_hhR!P)pzs24})>@ z0t!c0gh|(od#dO9)KH8z(aY!6+vBS*>(||yY?vE6Y?|7}^2mBt4B^MT?0mR*8+$k{ zZZP?%6LT`MflarKmBLKo_Wi1iV+VJ{`I=;=bLXZ?roOB1M&8e??>(0Tnf zHa0c6OnXwm;P5_Cg;9K~u6^p@$VgeO2n*B>=H%$DD7C=7>~Crx&9sLyNxZEx*ZH~q zZ&a(&g_M-b>%RzEWfAGvpCe14lGY^=gU37R0d!z)DT|o-$^i;Lt~-s{5?j~mZi~Ww zqjF8?q;h}JuPxSlN-X|zG_92N<@yDC6V8=9Jl8)TpA0ntlN_gj)?h%BTK`Y9NGo#3$1z}E>>`G-OM5S zxR&l7ial^uk6YKqW=sZkup`i6D0ufC&7t*fCM}y9tEdM{_+JF!+2&C(F-G=tP85`5 zmITqB;|H}qo>vxa{(2ow)zcZltI=;$iM`(;AY$!YrnviDN9`f9KHKv=E4Kt-F`t#F;o&PRE&8!Kf8s5#}Z|&jr8uY}06B=Q+J_ zm8#y<+yq5>TBM&Q$qXDSz}N?$?9N9x9#1m?IjNAm$rzB3>jCmCvwz3vfTd4s_j7o| zw<;KqJsxb6t{ILr2x?EmH4S`~OZ33wxS<{T^(!sDsm8;SHHT@P|=co%#12^P-%xpkS&YP)P(5@fF-@AiViv2 z)mPhqsH5d|gSelUfRnL=Tuy$K9v+?}x)sLMaSf8;!%Tt=E|Qu&63!`DDK)cb;;Okc zXov-f9k$a`H8P@}rnU0nTK3tXwDykgsnS5Z*C5?*>7E~@0qU&XJPGN$R^M_ZV%7*s z-pAnNu#Pz*dzM^reA-`y*HIGbv3kiUbaH163DH=C;bhf>mxOwov$iN?;|@6JFbR2O z)XAtBK)E!nHsvoErY5tui=~ijsU#ft7~xmm4R7;T?kMfkE0mJKTuNZ!Wtl#lBK_zj zPzu809@VDQtMHOY(36o4W92<5JtFmSk9K~@I1L%m^XIA;QmXa8Ey&u8mG*<0RA1eZ z)Z!m|1J!K5h_Je~AK2?33Xt+MDyIDkt~nWf$12=zhz3iKaiu#;Mq97unsd(1%p9vS zEckGXZi`G&nP1%W&7L*7E7jLGdfv_?}gso(?;- zZ`X@&g_h#Y6%a`i6p6djkk|ZKHjMK3%w^{sb^MT(H|nVG1=B?Yw>QRk3t-&Cw52__ zVB&rq4Wsde@oT$;$QNM+hy}*QA}Y%}c=rgX>7H+as;2zwk70;yq&RhuYYp<3KgxM+ zy6G+!P~n}aLCnioeol&XtE$e0f)nyhV*NO(ucJLD|G@5?gm~YRi3<)lB0nmYiQs`) z$&EF4#43!X3|_LKF8Q<|zxT-tA5G>}_DM1tR`7gLi;;sTMRzRdC{d2APY+L`1Fpi( z3S_VjL&560hjNP1d1yxCft7UsKe;VLxlT{k@0b9q{KmxncT!H{g1kKZEHWkt6Jo;eWQsE#8?uxv z?LnfF%sT2myaG2Cd8?5`rjG_cjwltk=pJhjaBt<$s!ohygSW|_037Z=r@ z+$(x53ns!Dp~Z#M-7v|tQ%8LAjSr@eE`42zQQk_>-fj4TfQQS4*}eHK>>gT8sH#Oy zNnG3s?Di%CuErKkIAs%EQ6>s4tL?0Dsn}duY|6HW)Si5;jS7LRGTu%`zx62;R1ys3 z5>;R{JgXXTB!x-U%f%YaF)ubnhb`*eL#qRtgp%c$P;{@6)jLQy>DReRWooNaN zlNI&_#Q3WGjeT8p&;1bf4Y^~duP#Z3^6EP!J3>N^x}t}g(LW#0LSX{8-aG;~wzg14 zB`F{A!5UcRwnKi0`xN*-$;91MC{%ppds49r>oB-GooJFo|Df(Ak(*BUtWLj?xbZRD zq$su_tJX2ekl}KMk|`lBs2xA#p|jxxHy|x-_)t}Y(k>Vr4kQ@XZ6$U?4`Qdb@}fd8gdAFu#Kk?&k^vnT zOVx4#g{$RCy6`E3VlwBV7z4m{5L_YBo#TGkx-;5hkn!1cE%<`WbN2I zpNZQ?Dj+G&T#h&1p>yks(;s$-EH=b{mlCa+&Im9p78~FQ(_b`xwzs$(BeR zB}Ka}2Hc7Fz#0I~A|(q)-Hq&MG<$n5GIrjE{2Km@!eapbbfH&+wd+*&*ejCzcB<`N zzOQ~B!n7y0kW*irber*V)7>shG3}C__xkIoVY#|sXKuU1z_pVnh!WC4&Alj&zn{4P z{>9pZqEb~r zK>%WE+FE0pzvzW|TKqxG(k;UP6KS5=3412&_-^PV&;1U7FKVMM+vkc#~BEL)TE zwR&#*N~VVG5S1=W=9c)TLg^VI-RXd=@J><_*(ywbmAWol^%ywN>u>3g+OdvbYM3+7 z%{Qy+YIkRPzf!>9SMbGD!$6cK^G$VZSItuvPkFzjx6ebX5EXg~MbbNiC2+@&fnpVk zZ1F`Q%MQ~_Cn>yW2{z&~3hS@+&fTDNO83nEO-j#&@qE`3Gx~J2jmUdfx4NqQB3r3@kNT!RSUmE+#fiz{?Vd7?;5r|l zlC~wMYX{6eB%0oH|B(+R>D3C)5rnaiLXPWeir4MDqi4XnoF!b@iJQncotwG|$ z^bQl|BQt-?@lgVC)qt)ODK*MZ3>qgp1uB>~f@@KyA3fr#y>9GpFb=gwlx$RoUE=o) z*7Y|ht-Vm!Ngtl%9>z$ld`!Xp^_ZOR z3-zhvM+(jYR0_;A=otk+k@0WobKgf_9Cqz)ZbEi5SU>%K(m(LlXG$U0f|cz2yddbC z9ipGFygPN;nsRLqK%(c^Q!%9}8y=QA_nR}0>rMP##z|XGgKo|9&jof6>YO>ycy78i zryw0QwaUNTivO$Cy*o>N49U-}Y#r1@Y(?-Tu~aNLsVbm|OL`X;Oi$Yn^6KncKR9?- zB3u)@`@LJgJ^jP4{aeg}DCVfuR`8sH(XzfzA`alyo2#=Sl+#IS;X7$8dqF@7Nwnxz6$^ z#*WBXwwx(n*_nZ}${Nl>i`v-zXTDIZ_pj+2VjWgCn9SV2?j4{FV*OKh6wnF4few9QQb)<4siS8XkPT*)`wq3yk-{ zsW!cvhC0Q?KxcFmg8>(a(LhGq7EKPY#QH+o(=TU4lTUr!PZh)~H}3d~NX^rAMh7wa zsS*k}yNHt=N3tGO(onzN)+-3=JfhNuONG>F&&>x$Py%wa0NIiEWiwaSjOM;w{w`_} zF!|0>n5n7X8{3Z^mfrz8USz5zv@4tW(pqWMfS6BLs>5q`L8@MJjn`Qs>uU=&CD$&! zMbbCyb@8R}pfvWd)6e30vHJ5p%$6UyLTuWrHJI$#BTx5t$hl%FBCx3gNt`zv!Hh811&_UTn`T(4^1-_wLfb5;JqLD4Z*j1J618l`ssg>i~l>3 zxDPpPJJD-n|E7Rv=Xgy(?it#BD~(mujfv>T_LGNz_e@ew`h^BDL-uPYa*!na=8y1C z_S{3bwp=NTtT*O7bIt;;pD6+MPIk)kkh}|c&mb0OWm(*1l9C?#0$4*8+|bZ1dllwz z7kJNA%go8$2QXnoqwPLAhj>uK?N8vb8m%-4reaa&X>2(?Fx;&nwK_P|hOu@xfZW&3 zV!}AA+LtMuV)%__ME1V5)>9qWk8I_Q#;AuI&T=kfaJWRCFHRLDX|~hMwXA2T1y^{K zi=$2H(wofMc@hxuazSx&2uHQI2cNk!Gd~2=9Ik7hc=wdOqXBZ-drJ$O@7?xqyxz#K z^gFE@e#0v?kYIoWvUH`SfBM8UDRMX-rfqXRDy7kvhS>G+lg-QsP%$WNn@%*JH5oq@ zS*8wO+cP+OB~%A&-rB#=d#%EooLv1e{^Pry$Id5 zn4Ww((!e#i{5H$3Q*gJMc9HTu^7TFA9s@k5iZqMRIq~>!)NLbS0WK|?Ch3_y7I$}e zu#+9-qPfbiCdb7n(Bjm{;|ohpDI+|0`xl)^l2!L%T22U>lBuE-G_wxxuBWT2XWkiS zJrMhKs7TTmX2Zr7zkJm{>Gm{eAxZYa4%H7%o#y&Fs4jHdJD;7oe;-Bsr-t1B#`e7+sY&!IMA_m{oot<20oaH$TpRYQzK5b@dALSxkbvdn z{D-9czdfio=Tcxa5ywaQKvKYP(k5*ca=+;r=4*CiLp%%6WcByEU46Wr@S>7a;A zR{GcpGC?r-*Jk9WDLITE2Rm)qX80#oMgE|uL~TtXcg++TQB(S;E-l~2N)G9bK5`p~ z@SX|Jhg+O(Yb@<(K>J|*xDy3fe?EHTd%uQauW!Pslo3dl*(H4?9NwuWB4lx(5<;2! z=$8ukp`Zo}x#4t1ACLVhj_;no{$B)qk7VBnTR<>27O|Et3dt)BLb8gtN%t+vX~&BcEI|S_QPFK&9$#DVA()yzufk-b*iGi?oW6E>8rU-1e$=B_Rxh3G zG`GYS3+YUXEHCOZWWZCQrrilGxR{_MpxZHVVfryToJ?CVGfHPBPR399CZWb!4hwOrjiS%pc zw>&>?c{#oYvigf4Omv!PgHvUWT0h+-ea1#%z0Vd{d7Tc5@-~RIl5Uf&1xYa)1r@c1 zLmxLionG;n`@zXvGSXmi2c@3OK1A0^jlPfq!1C6>eLJDj))Ou@2( z3xmHKp7AfFU-t|N!8JM=W(=rU^LS|q(T&RnY^VytdNzuM0J&Z9Gt7;)~L0VQC zAk}d=maFDGY_?1+Ro7UqxMw{uB1H6@Mh~LGb*r}&xvx-lm)-=3vi(! zZ1Qz0c+_=aGxOU-AV`~#Rbf|Husw*{zlNqTb z=^-U7i1uK=Af9qkv2=p$Z}q2JPR~xog)BQ-Gb>Um*|H`4MerG06i2fyRuwY^<=dT; zGa>fgn}i4bZqzLeKowE-fW`TUc%j^v6oE`zSvWNyGO66dSvX2*#mRnzy#yQPQB$Cj z(wy-Z!ST(8(D%?JmW~_rp>Ck4B-3ltsG2%zzZG!m2t&+(og)lE;PI4z^;hR^5FUBO z0!?xE%CVd^KRIW_PN}r%QL+rGtaUh!%(FK2N-O%3=%+e| zHBecYeM2R=2&P9|@7 zT`V#41K7L(HY*pZLW8qML(tuU8m*(g*Ej&GSbQg8ggRT_FI^k{M=-d2;IRw-MpV}6 zMA+CjskV}@3WmHO@2gW9^IUkP;{7}V)F{}U?$X&=c3u=O9^2+R8GKi7XnLM^yt6GGUjwR~vvY?fOg`GP|xDOPP+X@UmGWbVDLv zat?t8_B^*1^z_233czpL1)^?0Jr53cPb||eb#ExDVB?0Df{JOT6xbKcDQuPg4DXCP z!5$cCx`!uAqV^1^o$DKpL_xk2 z3Rfxlf})`M=YG2jTM&mx~eo8;AXf3Mo`;z)(%v7_A zi>ooe$sA0TA`!#`WqCzH&W?-?9i`uBRSQh&M z=?dYatUEqTPjZ#&j~cXEY232YsL<+a1zXPsjB7-EI-sxn@IAN>_^q%U#jI7Ka`6b; zwb1oqdrVhK!wRfLOz%$YF6S}%hkAa2+Vn_z;3P8=NTU{4)5iGdNbzvm$6lR(Z_zQi z?D+KJUIKJWlz8fkewW4G+u}LF5O>eqT%6ZvVokY)y~{K!E2~d1FsU>GI2mL46Lo$A zp0`gk3SRkv*fmlIUq?y%HWb#U?|vu9mjc? zA0nKYXs89Dazg8lGxIXpBym4{JYacqBhC&@ai`)XeA6NMm*^fwzc;^U#S0{t?TnH|vpaNcB>O8qd)|{9ol72Q1wo=Q| z+bWd3la4QijNITILZLIBp@6Xh0({o~`NYV>(6){h;NU_DO9zAIy&gA(X9DsY3sIq> zCvP$;bD^`O_sLAC*EZ+Lh{AnY8w_kZjly~4rIt$>e_+p#onqoM!4EU~>X>=!Ub$gN zz`gX4=lOos%g!c?dmE39VOUGA#f+6ddF_xTHo-zCjj#1PT;aHB|GTDc8m-^|$Ti%L zDGR{MDIv9OrJ$Ad;UuHLpE_c zD%QLYLvQ;xfJxUDpOQI7CD~T}h`*``0RG;ri1>imnONr)lg4zXGBQ7WFkm+wy)mG*xQIQ;a_Tm{|F|1`{HSC>XzF7~<< z3;(~TD%_efY9GgoDFNO7u`TneXi$g!!bR1_MS#+}(F5Hlsh&LVI`!298M$9FNtsa(Rm`%P?K^a^bY9+a)lzI$KI z?KaWXix+%EPBSv+o{6b)iaarun^$(mTa0X$M{|c*4ur-oe)|(>k zMvzW>cuAk->^$M;!Gv}z`jZn6B5e}9F(By6+z$$gY)=Pd+a;|&l(@m5YVZJ^P4Ziy z8=qbIpKpbKWRbp&{|B@waPv92j>{@-02T&9$ognbIsb&zY_N zXGX^SC#(1_&OIf7xNcgxEBq~&HU|BL%+WvU#OEOwNmQQmy050k&JcUAzMThBvV#(y z#&PF3Q8nTCNOjGTCv|<-+VKLmF`|<|Po#@_WyOcqAQmbdRU;7+Vi5JRKyrI!0p;-E2{ntr9OgRGZ4I7+9M#Z_&Wkh#8;b#5JW!LOXDW}+ zj%Vb-QyCt& zzj#TazL7BR28Whi9W}7@K0K0Afel%v?}9&5f5L9463C4@CRuGl{l+RooBU;4r7hBz z>V6w zDN~%^+LTbp9Vcfzv@M>I3^KRo#7tQvO^q7dOP{PmR%JWwbLat!K-;ZsiSM9gd2-@F7F<(0=aX|fR+LGkOZ;&Sc z8Y)r$XRj}DJK^Dk@QX#mZSth0e^?^_m3{DkV)Va(F8g1V=Kmr*|Ht9~G%w^yqT{!n zHR0(vzV>rKzffA$r`4y>A9HTV7Lw|)0b)Zk167-5e=Lkoc7kNH=^Kl&3ZM-kmgStQqEAv;Vof5&9$-g8V*96`b zcW+dpn`|RvPk#6}Wq-+QlaW4d^w?SfWBuHMWGuZ$BC!_L3B%)$+}@e@YQ=_VG$^*N zmCT{aIzHJqStT67+{X;i%ovZ{>6a}Mv>}L3#?uJ_K=9){krz*L+y6!G` z82uo5rZKvu9)E?TW}LV#(^i0;?mV%1(WG~;3wPpiqzjcecoS6gqe$XqY6I3Eew)m+ zD^dCdtN=m>5B6hnU;wP&T4xiRgC6Thtjh7T>DmP@9Tt9V>DmHxf#NGg&fERCUFtkK z)LU&_GB7d_X>Il}qWC!ShSx`2$2n{dLn30Io>Q3ehX>*_1V!((IOP(}X^Vcr$_QhI zG=s+vkBHo3z?8{-8=uA?te5^@bobEDgH9wq?cHCQh~ac9`s!np%Zu{&Su8G;sYveD zUrulISM)5&2~ZnQ8JpUhW&R#3@)tq1mu=DKVISEGUqNd)OMfeu*!!*X=i}Z%{%Q8O ze)|fd6_0?&eDQK5Nd(vt6zX>Mcxsi#HwHm)TGmksP#qA8tR`(;++!es`L(eP0}Ks5EcpH2*rflz}}@P z_Y4n(*Z8o)C-2|Ar4>h73X4)#HK+`^&FQ6d8FpkoN@B5rYX|O?8-IBG;{!~z*v-Ns z_APIF3!omvG_t65CFl4#j@kEQU(@uiu$5PuL-EAw*TzqScB^*YB*}I=CfB|X3&SmB zF#TG4qE%oO!@90>Lyznl^VL}jWc_Q^Cu8Yd5}Qq`A!p2fzc)USGBdNh^Dm_xAbzp? z&Y#X9W|=KM$h2g`l`rY1tN=SchfUX^s3GELjaFfFT;*`EHp;W=q5YLvR8CmuP=fyg zy?YTP`SEyaU%QVIw2sqpj)81SbZeY)&nl+_&zFDD{vr(@MV7@dd-RHNU3fh%{yyx- zsDxj0q=V%I4Ek+j#vn8lj`Q-=fAcq+!T(=Bp8wxhKkgBQpXn;#rEvsQ!0YKm?^@wV zktI&)LX!$YkupH|DT)!@(6e@6V(=^{EJt;q=pnzD%%0reR~3tEr?($uQum+}RtjD* zv(e5*i1yKTMYzyXR+a>PR}RY|)`3L4PwuM_kJV7cEf1f;d`Qe1PBr+9j- z$cav&?k=>apM)!S*N3>KqDc*QkMI3PC`YfYH%Z7=1M|`}`9tUTrDiqYOOxPoAk>2> z-93YKZ}W`*#bb$u%-!|9x9`S8MUk|$v^&^BWquzkIGt#_-v}iEg7+T1dkwRj*2VH# zp=ZKv8Yt1AKAOaX1pFL+%`YaXCx4NWQT&z#6A}q`3tw)mf&h`fi3_-Ei-L?0G~&Bj zA}>dM*_gZh)U?iSB$K|KL-}3=-1Dse9LRQOQeQsjp3N3ubZ}+LoOSQ2=sRaPUMovC zwmCztI||>qJGP%)`jO8i<-jUNTEj|{oX6a2k-6n4RW9QX|Bv3?Oz;0B3>QnTvCpSwvIOJ5D=+}ex6jZSfA7#R>k*1j$bi7yhO>s4?Ke|`1#9*WaLU9>G z30L(b{@>T?xlA;ON`K4^tlRogYyIGYW*#j7h0zW)`?SqQ;%Yo##N;V%NqxcP{smA?pPJpUqSYm$vZf_$6VS|O}D z+f}I~D?CNL_en>#>7#B?dRHFe!T%jQv^yq~B_9vx;;$-|kcoTYA&oLu^1?gi#grv7$pKMGiUawUk{5OLtw$Di;pIDn;EV@aNBL?PhnAM*k(j-B%rgj z)B4{p1J;>nDTLF%!nKqVO-mx4dF4SM;nO3yiO|e+tC)|&=7Wn#0G>=E)4T9G-joCH zJMDelT(_zAnm=eNu_k6!1_Z%95*&iR7mVG&@goS>TG-OIQdaKc9aPCSptmZW^yCZk zxUd@At`6uZ9iVur-F&Y1)l~S;soCMixewcp=STeiRn>Wzg?Aj=nQA_oLi5Q zA#5Og`uf zBP-`dAHn++SJcY~BW3v9JH>xLaH@6WotvV?%wpFj-D^^O@D2GLpG2-B%& zmf{Jkuy$!O_iKR$_cHgZ_1?m5m)M7Jt!U>_AWh;;5SR@Mx1->Ql(>I&^0W`>B_D>M#-4Cr0wCRUj*}}g7$C$#echDs!~i@aS|Ov07f}s((%lv#@+b^ zCZ(<{s}eO<{=bGg8DH5;+X^QNh~0g#Ue&E|8S`!C=G%~{^pPsPtXjQaMMGtUjdINZ z2mT}*KfwsI2?$-P@n1CamR7pIY8K?OXCInwV?-T=dn}I`q*A;tFuUU4^**pn;jwZ; zyPARKm|7er0fX`_5Kl$UuEsuQ-pG;Ot|lPm-yjw~A*K+2U$2p&!;_U2=wAe^AHPN6 z6qbfsSy`@`@14K>f5xnT8^3!6PRaY00(iUX5&hPW%_w;nFYDz)&OWU-o0>m8=8whw z_H`Nm!JqG%RFN}!lP78-{t zdy$0B%|7RSx9)S^J8Gi#WNk|omYoM_UUuDl!K1=ZUE$0;`Bqxg4NXRJtO|WpWEpy=;`bH+_(d7 z_BNUuuK=f^!rxVVnRZ>`gWlW2BYc^a7; zpR|==Oc?l0mL4tYogB*~X@3{<+mx!0=-Lj5^r}DM=NbnubkDf^P;y4++>JDc^y2pl z@ejjx#uvy;K0;5$C#+C`v;5CSAFR&|4L%ibr&&goH<)}(-dGPqYkrP|XH|ZM!kUx= zc2bh}!RbTAZX~n2;)Td_;bE<)+9^@h4eNCU_2-{=Ui+0bkb(Y~qh2pxMM@1pozd~D zjcxRJ^Oi*~LM2)^0DMqb(si}YrO!3FzY~rh^(qU@=}CJghig&xk?l{rE?599c^;3% zt;IXp^uZye&OLL*@vKDLyAsX|gHmTN3YV%sEET;iu!2}j`jf2y=X%ik&P$T+6y%6z znOK23+mj0}yQ}^fzQlzSQ9;`)=pUuvHsZ;`_^ySWUjA@f7)c}qzGsD0<#MuIf1ng) z^yDyBonr25kk${a()lyQ)3YXmZAR^+J?n3l;c0#%ENNdDWn>Ive!(4ob4352CQ1LF z;T(U*ZY5_p@F+n8^J?mv|HKNglKxdV;lE86;d@_)kM&g?^TKLpJ2od=fIenqZ9N(I zIDF@sJJ9PXr2>7$PwOGkq7|PX2Dr}JhhvoG=EQYh}>y}=KD;*GJeRY2Da@VF@Wju9Rg4b8L-LU=bq9vrW#&*&` zca!~JQh?ASX{-D8(0=YQ$fGihj?c;@c+V7n^(e1p#MWMSt=;OKh zh#=Ia0~PJ4K|cA~cl(SI4*2;}s-Q=vp;R&IPl~4Uy&W-C5|Q-I2qPFd#bL4a4jxsQ z{Fl0DhjZ4IONaJ@5$L2Kylf>EHDXVw#Jj$VcxLx_cxkbz81Zaq6HUm-vvDNAC}dpmDrGDQ8PcqHvc2y@V_P4a9c1; zVl4i0)#-d4Y}0BVBSF|Ek9)Rg!K_Dq18^T~!MJ{gVZ`Mr#L9P5aV4G3_?IoA>4axqeR<9$*X~wpXP}}8lO)Xd61`B!^4gYP={|3{;lK@`XENt}Q(Vei7dP1LY-4&d;^@BV_po!j_>BGE%9vJAjx-@ zgsxS9$ok91T@oTseH)b5eah$uhEQ=;PcVGWgfpSnf+5Xeofpbf9G-WYt#dm^9tkep zrM#eB`8=`mxW^TAEMoBd1|UsV_hQ{&aa|Uc42;Vx%Avmhu^}fw|4h1t`si3rbv5KC zh!|FmK%RsR`paLZH&+*Nf1y1^_0X&};e%FTCsTFht52#4up*yA=#fuD1u7mGuD?WU zv#v_KxhsBaYkM;Qm-w7tmD+0Xl2-C(K{l%nmA`24Pi@e}@Hj_GIl)Bv#FP19!6(~j zGgHxY1Rv;ebgzEH4d8?P4ImB~CcjjMUR97pq~uj*`RaDOH9X9^#_PnN0OZDoGVm!X zd)b4KoQZlQWRbd$MDmB(*-rEb25v)fy@C8;&mibi5^tcja^yk`6>O3$`f=|m~c#xL%RKD|G z>kfMNk@6Ldl~i82jlIIlLlvZ&`Ldk=HI#Wq7&X}Pk!m2)sIMKgbDHD7Gi6_}Kr@|Yi2JsGFj zusimdgVy#_cTNeOBEeC+q7MxqLvIJque(VH)fQHTo?#0-69KHh6Kl`>(Q6WP$o!bX zi^Ik|$Ov^@xk7m@?sST*!d>x`d1X@=F)vY)yi4T3p(eUwEaZoTLj$ZEuRQp7g)9Y? zccS%mf39hD>X}bT9Q5+L_FYRrRnzN1cW(d%`TLukh<3>>n~QU$ubg(0*KYIZSBNZAXq({}W0Ci?e2 zJ~hEZ2PLV0^nT+sgSoljW7kW$mm97FZn=+h_q)ZWx?=`-VXWfja}}J5z48P#Tu7uW zsyPmz64?c>J-Py%g3r5?^8T{cpO6tC7k{Qxf5DP51ph_X=vNNZuhReT7JAHn ze9*RGFXcym*IHY5+lj`H0qX4;XmafHH*`>!y(1q#g!b$2Xdw@XZKonU#!r3Hq(1omsRi05h$z!xqRQX<1m-Jquqh+^Y_JY{+!*eQjTUItQyjTrbZv@KNfqzx$IZ5PkJ+4p0~osHRdQ?m=(%0{ zOsmtP>kU=umA%-Xiyz5%O=8dNX}|%i6O*jhkp^b7#fu2ca4>XBuOU z`aj z*Z$tbqT2hT$L62|Stg8K9|}o4D(;vlepq{d8!TfU7nt`kOa%>K$L(j1Q(}z#82ffj zwUgj16F-d^YYEX~i?!>?H!4@vKf2 zQhhjhnfRMv>`YOm=AOo=6h(xXLVv$2-?$K!_6fOI{kH`t`MsGt!i{AiabrGyUIGQ- z=O>%X_8!A^Uph5WKhmvQYYNQ3sQJEEpM&f1or3c{EUWH&GWtTYEw3Ww?=2Q%z6}_^ z&IRW8kbtICXn(|Xj>acz+rNPXD0y(}Z?*xZzpWvs=S!S(5#Hbp=4!yWuea@#UyYj z@R3UKI(>@unZj1p%M5lEUY9=tWB-c)y_c=8x_D|EG{Jbaw`Eh$(!v@$U3p%^HPQ(#LX3#NZ=2fR}jJ8Faf1I`F z(K#dev2*EDnTaT~-~MnBXfYR6J{8I26^trjbH2uf(BY~CjJ}C>lQpJUJc@ZEe-s3{ zczRID{EURES5IBVgtSlcDQ{?20CslB?729>$Bjb4+I!UG@PPQ7OQquLgwNGOstG06 zDPPdbtPkxQ=g|ZAXkT`(m}UC%u00zDpMAI4RdK*E06+$~U8eiC>-Fgz@Mq!;U`f^W zp@d-S6@%nyikT>b+IyuOWTo7AZg`C)*jBg5b7Z)gaMht;=JRUCBrrJ8F5`%3#7bXW zOZ9{Eql3(+nLUWUmuKIHvPa0y;Uk${GYbTVA%j=H9{$zO~fWQ-cQF8pk&(~==W~VdJxY}KJ60$%j8y7_6oDV6Zg3y=#c5pT2aVFKbobH zh)Ci#H9@|-;1}8_$tB^>G>^VP70a3l99;~=zd)J%{RW-98jdu3}YXP*a z8bje_eAhg5SeJS3l7CiilGVg<$Pd+bRj8?+Af7Who3y*>eA7rW5ge}#4+)L(IyCT3 zMt!QWvTy@X8UjJZ;-48Z7?6Y7ccvj9>t@5hmUWQ>=w!HytuX`KAP575toh%xBENW~ zb%L?rOj>BThkl*Q>~^D0|2OrCkMg)Lz?u^h;nx{^Op8ZQ@*>WFKlv3p8D+0S8p zyTj|<(sLR@0SpswrH~&=Im_B2wIL?E9@j`Xq6O zhID+>&6rx#1ITl~`HhYcN!;)4IFuLTkrs1^XCc!?iaCI7$Y^4jjeq;yzx8v$UDmm^ zt73yONoP2CX~xlb*LV7m<|B_KRIHDaV(QYonBSPXO9`s?3J6K?B>Apc-nuY!v1wk8 z@DZHF^>6YR`fdDZWrA?ky~>S!iS5|XIc35NAKuS&q=ADWedS_mpI5fm*@6N{zNDq4 zn;o~dO5PuW!~3{b+Pq1U4apOF4Iw)F+hs)&@v*$&6jNsypylPv#rn`$?;pU2IDM{d83YNnhjezvZ+N8P2?bFk_Si9a%FIv+;I z7v%S4^;VAqwiwTnS7(U%>g*@Ci(`RicfJ6_=RJE>0{1p^5ua~cF3pkVk%EF!r}fks zdb?nc%o*tk09AYM>pxG+tF25N{XT7lT`@_Ae9!NOp{K33QCI>g6S)R)Y-F+o5uSBu z`k(@RLb-yt0+V^dXuN{@IQ5@nDPbmrAbL$wLG_wp;wsNzX1=n}Pby>aUM)pbIf;WF z%qZafKuM2*L}-{O#T|M)?{<|9?4NvvVg~JJ_l;R`Nsbe6nyy%3*JP4@9N)iqc}w~p z)p7kf`V}ENVQr(IS~UGQEEI(O4frnK_mg>?lKDRJ+W09U{iUYh)H0^7er$wT0$-$w zQr5$(@Eg-k+ur^11%0=1eS&#;<7bPfRd=p~s%ON5@&XG^ZE$Ljnk&+MjQZ#Y#%nV{ zgoBvR@!tXR{eG<(zZ#Y?c5!s0DLkAHgr2m1%1HAJ9}?he_BB1pp+9kK!v$6vzB?G1 z8!45}I3o04mM@t4mrvnT*m2X;cd(*IDim-CXkJ0}#~T}SjHVB~QCn#7#0Z-juCSvg z*?6tyJL?R@v!8FL@#{l3xRlFIfgM-N@!cZQE)C;zacf4w8*uzQfya zvMQKkm-utC=HhX-6wp3Ww&FbP#;TrH0#H(JY%XlC9HFS9va?})onMCVef{W1NTX!v zoI67IN!NCz@->3)uy}sTA+xNLe>t>rD7{QhzGWyvn&hC%JW}%FtCo!vrSFhnQk>l2 zc!mEv_U%Sl@gB1j*GRvLjyvHMga3iJ{hu!X$JySrf035_ixA~6%}Hj#fZdXfm6I}+ zKcPW_&xEwMqT^3R5MMywf3PXpj=xw*V@)j3a(59eml4ps_w+FUjRW`|=HAKb*!-lKf z#2M;5)<0UX^NHxDFJZ%!KL(vC(J7vJ=ekzL_qloUR9@B}Z~Rb<3M@3vFDG99f%RI> z#iFyUl#h5CzwX}8cai^R45K}_UAP4E)z{kSyk7X`w?;iA_PY&C1A5=1_@%w{J<*Aj zwrzX+sV~C^mS%=G09>GJGL8L%srN6}xM%t#R4jAYRB8i{2@9{G4R(@5raaobWr%#& z_a!`csvg^&aQC1EeBMdj0McT#ozo&-!qk68hO{>L?1tT6LFf2(9UjX1BZ36vP%qbn zxQ7Hkx(Od;JM#^d`(>=CA4_xz6^L;q!!=`8^OwUWZJII<;0U6sW9Y`&#IVFw-|CsV zGa@5Sa4duKWx=tF#bfh>`bWz4{_e`Pb^|e436+ic%pZ@fD|5edu+Pui<6nJ~myCu@ zrZ@hWV~ng0X^d+-yAzv{Nd1s9-WNOe$*enmf`rLJL{rh)?iW@|D>OkuDdgzPH*U6) zksfCZ;jRD;n1^uenevVI3Q3t_ zYP>ic8+{+mv+Z#2hpbf;#TcswjCZYt3ioYSVZ!-5MIMf_cYQJA(c*nvS4-jESShMj zrE9**v1UV#BXdjg1B(r}J^jC(v_NMF0nb{#F0lp*IYU zo9i&`(q@d~gVEvCXeVhNfOE;S*gX6Z6!BtNK!TIAP2~=elKLnC*_I(CaoXiOh0EsN zMb8SOj1!i9qfX<5dI5;(1>J$b#oQh{j%S0*Kt$&K(uJB@PnM_8?bnWD=BcNt*TLYQ+CQK}$s zdY=g0t4i_B{Kg+8qZ!7%^EWtvcQ2(JYqu?47b4l7WDr z#%o2yuFVY~*?;83%60v>*HWz9$$-sJx=l<+v5qYDQ)k0ZGh8|!>MSI@0;gLZAXIm^{?}~#ba?h z<U_x~%8~F<;8h|ALu|8X>h2>i(i$OZW|}h{Fhj3= z;NoSBzIP70<|qHX=Ogd|M2*wWuhdJbzh7t$cErs(LGBEM-9)7SmG+q_m+bBGFhPrWc;@{tW%&ckS zrr@OpM+gFYhQ&hkK)-c&4IzWYIqu7CY9v_!^sA@n4S-Wg#1Sv-_m1+HUc=$bcENQ~ z%e3um|NF4f{_cXp5u9WawVBatU#dvW^tVr3`Mlyb%|E;r-1p_V-(4yd({)_in%>4l zmO-Rbj~OF$*4-5m6qx>amaTnp_v=JNG^ew8Rh`G7*Nm?vxc8y{iQPh3UfLM`?r+`j z_mQTUw%7aQ&=9r3Houbpt1p-Qdj}pE%j(Vf+t*+f&lYaiD&~k%v;15_d_P`^@xit} zG_ZW>l|BH!Ui*CvjJTla4Bhhzmii?YHjif>(NpNPAIY0=&?g9|F~5us4XFwf3UjCE zxAXJ|WA>1w{N8H3kpYwo`~Br0(~2S+%9tBK*VX@*I&J(|a}iwp#ula%B@|#oo(z3y zK&AMI5NZ-Q;2B&WtZ%6L@s@v-ZLJq%o=mn)m)(F;QAtCu5o-ryCi+&x&?_m=n21$? z`%%KmP~Y2l=7<8@m9o5sss8rAtI)vkUt7#}&C~WmHeUrY_!qVy^SkhOrKJ>BNt zI;eKz2XrO;xFC1VGRV|xKhO&%Ru?u)VZb#ZpT@9`wz)q|LXF8_iiyCeRR-nG~4}9 zP4EVy)e+jkcuL?dT^6RrRC#KcC;ySuh-ZmQu;8L=|4~|9@Z}t|ktLfp zQlO)2Y7k&ewz7x$Y0&VRe5tZ%l{IO-+VfRN%AGW}bn?ghez9IhC~!?BkJU@6B@@mv4gd{rV8Ct+g~%wUNAUH*SZMwnPDXH21ih8|f-k1>91D z@L9^IUg)@q57olaUT(Qi5lkzC!OeSOIt9v}zApNK$n8pSI(8TCGX6Y^n_r8F$b3Mg zT;bbV-Q_H=6D{%xNX9^~9aB^~SYnN2)H1Yv$!%)=rDuB=ion;a^yT(^QnM0DC}dD)ECTe1e*C{DWgw)4bk#8VV z-eN;alVZ^8zQvhX%3YB?I(RaBt3mle#FwZ_?-&WM(BlRapHo8)kKGeB`5(S{un7mH z*D6~_k1dH_QkZ=NC{cpft%GwB-_`#Y0plbD=DM+r*n=*1cIFuaB~5lq@iYWC0NyFk z<9oD|7Ys6!LIOKr`guUs@c88}J+%-$n=SwV7`#^xo5#?)+>XAUy;W8O8)GXqA@EW;IeG1k zh;=|K<`Vg7sS@0t)r=X*OWq7K+>iVG>CSv}WkO@jNw+MC4QnKG6daqlMDZk&DWSv(F>gU0kMb zB*P!!orU(S8wx|co;|S0QVQJ!?XwLuGYjk3l=$i2p+2~p$1#+oP0~BNXR~&IM6p7PX%xSc5cbP776L4CfGIVj`-rVFtEp`bDm{#HzAlevLr4a zZad*5rs9kc74R`S4IiR@s*yt1dO>IK&H1#f-FBM($@4kqI8cvN;9*@2tYLwUNvf|B zk8S|L?vjelS0Xza2iIf7f?xbI-KO|Vj-1}cdWV8_B5S7dJYP){uIQhoBT13ICHM6w z_oqK6=pP9Nye^3fH$9!(V`E6GHwoQ9)+*tZaUR7SzKkv!ya9ym9V%Qpvi)#Y@wrrL z_c)3lb5NjQ+QdR8_jthyNpi;5obLwtHv5`Jvzy=a)&}rnf7RxcdZ?LWn zD>N!$gvK6%`BRayjh+4swuza-Dk762A(?Y%o8rLT4UY*s;2ZSOWeR~*?nab6Bs}QD z?j)Bc8%hE_$9{K_rp-$v8ru>6SkPC01|U3GC3~_BUwNEGh?zO+He%1H`RY zMr6}UD2wP;H`7(Gu%6chSVcRoy4DyVg&boYBD0P=Zvc&y%WiUOgrLP6z#9Ig!@bp3 zLqDf*p{S|CRBsl?ugMwgI*YZ$7%ybqM_ix{*KK;ZW0v5$lZxEunf+D+UvS57}442HF5hww8H1 zPYjncY-GT5?XZb2Bb5pF@@ZqEE2by5swTmDOXj)8Rsc}rOJlaWU%27l zmj0Bk_?WxWgkQOlv&Aed@*kkcKmDEbf8|65O*KjGgIJnMoBknYLGuwZa`F5!|E*am z`_Jn^NtSutPw@YQqW-JBGWy;>4W2rM|CtgQF7f_(_&tGtDr|iJ=VtUS>%X|i93C92 zge}dl8eV$hTt#_j31`w%`KXQdy*wQ~LvU)B`G~vNbAh)9l$uR<2V6p3?Ws&h%dZjU z0lk(iofs;QsoHeS*nP_21T=@J#(D=Q72}jxm=^3*HL!E?RHna->w_v^3<~yRb9P3A zLz_4Fpr#?b_606sjrdVH_Dm`wdViib4FhC8?2|Rh=eNAhszLgV&Rw3l$}<< zxq}ZB$Hok;iRH*l?o+A)hVHsyu}3nm;77;U*kM*rJ&u^J9NoYe@)eVg?^@zMl8%E% zP1@`j=IRh@L%#7%EsT2~SC`uf8^Yp`;YuwyUErgGIY#3rUIg75-eIy6siN{11A+o) zfoEQ&4Hn)8#EB0Z81F87Fg{**YoI>v_xSnk%pu{g6-U($^nbXy)()f;tV{TR9teAI zNYX^uX2Ch#{Tf!PZ>e%E@|9SXcqtJI>wfY8{{`96bvJkjQ%2Cjp?pR*F?g1wpt_^t zkRaFkNUn{(NS{(7Ce}|llK>?3h4gF&r+p4nKT&hNM5QLKD5!FB;X{?YTVsqJ73PG3 z1Y6Z0{yT(hbu4a7G%B;Fh>8LVdt-mpSnNbYHur9Ib3{RK5{&BiAEwhfu@a{^7VbPt z>3!OMs(Esx2eSQsO3s!7qfiqbQF@YC8M3iS--6 z`(*S&gZn^6|L*Q>ug6GAI0hNo-Wd6+`XHO=QrG}%OM0qjCiG562|)y0)NYzaY;oaa z4S#JuNx5x_)wH1Lo-v_5Dvjzwz@m&U>yv;9UFPy2#^_HB-3k-Yc$X@#+OVMc{??o} z?e@M2Dq4>)3yWmZc0i2WOT!_!1s6sg6%Dz>DSm+^Zp0D9#q#dKG<>$z(nu7Tex+a9 zf()OfqckcSUBEYboAi}5YriHtPMTaj*uJXhX%Kv%jG2>j&9Cur%fHES^()j_kYJfh zvsyi~&DR3@BY9@Hs?<$M$;@s}`mq#(?zxlH$ft3eC=H@Iy5YGA4&6DR{XkLAk0`iB zEGE$Pi3CkgcLKagXj>{a&uG{UfO3_g=zncE9>3nBgdJ0>sjp|BtP(0gG4MV^=$a++ zyGK>qWUsI=%w{5i`q}#UO7^8%xKyZP*xL8qnG_t}_dgK`oK?8Sa7b^{g-#F}&Db0xmIg zahDz9Ibr>yVRaS$(y+=)>$s-iL5NDHgf+X~0b)z1ayRD<@`LwNxHAo*zFb3uPROBF zp){sh8JlUvboAQ{H&rHk?BJ-mrFCofA$q&6JZ|f6Ojce=bqd;oRdXoOlMuyglklCT zqX%lq+h2+^(i_v5rS2zx{tk^7WfUWA2y+b*He4EZzdL+pjMNcNh7Gs77$pP0wnYW~ z^q|@OmQb;37iIFq&etieA@}=c@0bCnb2j4)SM<{nez%y2SN3=kkA)MbIb15pObVLO&T4Pk$Y_}LyG)}{xh z$(d16WlA|nJw9wqX0B?m52c$Bk!#|?u_l=KDV@3C6BFzX*xc682y^jZTcRn^iDy@x zQQoEU<06Fg_&bqwcu(&i7niRTm|0=lQ&%=0g1mJ}-TomP($n}~yQbxe$Z^B%=EiH+ zlRjlVq9p#jzH#@Oq2Ki%*!_PGU=Ln0^mCmNPJ)SlhS#d=ujbc;l~$V!|9H$++-=H@ zN?lmfE1_s@@1YpRE#0=fMXutr`A)8aHtjHW=}qB-0Grg!8J{4(x(Lim)GkrW%rm= zu{Wm|+yIO$d?nF-Z$(2_H-N(wlS&Xo+q11IJZjhYDNb9BNq+^XQl?qHq@U2AE#z`r z{z6mfr|+3$3}_T~J#ms!JOP@@p$ekqqMRXddlzu4@(jUnRp$ZJ>c=>}hgdgEL2iy) zZG}vffn9cH4|E6u(5Xcz1KixdUY1RQ=Fud|z5H z4E*}4%q=G!&Amk!>SZ~XmVKcemgG8^zcxZ|6NfubZcP+>p(&cHGbUH;Ca*anFZTMb z`>2RE4)}5mPb0f`3@*$%&k7JHV@VEr;!z-b-DWhjUst~A54?_PuD*Va&R^M=pC2|V zCVB)DkNex|w=DUl&0l04UJ=8xlXqd(dsa|#GOmSwWMZ?u9*AG-x~`|Vr0i7kijo?1 znYZjYfXd(9yoGRKx;E+o4fmehL;uVNpw*fDEB5Bs;C<^GfNk^LpUxZCHHQJ8OzvP2 z%C(xP`Qy$zyp2|pzqR-Y7?q(;fH^5D`aU&gAje)?OAX2I?*Oydl>NNmBRsVhOP9A= z3QDP(KP+@TWQ)>zw%0MoNlV)k{#WIv|Gc63=RS({MeILPN@L0fCt@OY=P8Rui$8Jo_!mC!!<#2z}TN05Jr9wjd&^>YvU!u^>< zM0JsuMXR}~-Ys#(K7)GC{FQaMd=(uEee;s#r#(GCuW(wPl#QNgpQs;#szjU7`BbO{ zaQL0?DOt&dJCKkV`;Osd$FUmZYjcMf=J#fRyNC}chXGNh=Y#-Cg&CIycf7}pJ>6L( z%RaZnq>K6YTqtoH$YFy3)W^9--);3%BZuf@KCOO0_G;6ejy}&7?cJst#&&b_&$H*# zsY-?md=&cbFGbkYcHA$&`pj|Wru7aarRUY{*q3vcRk&$K@7)LbayRsgP^xCu>|_}{ zdH=y|dze)Y6mAfTl*uQlHebo-WYzKH+nlHn?tqU((b`px08?W*tH(?iuj$gSbv|&s zw&>0+VmlSL4`%pE=Esp0p^rg}3;Oyy8f;e}r(YT$K9<%5`vDh&F}X)Mw<+clfu_9igK zFN55G$xLZ(PoC11509F>-W~DCkO)l&T^2;N0vj7*+V#XmQL~mE4FgudH^(xIqxRhG zCETd5_`Ah0{KNX=*Nf?JgT+QqHg(qc5I}EPX^ea5q~t`^sw|zNHkEl(Uubm4&ex5X3Yl zt|x!hIa<2&9?wp}zdZF){IIF0+} zQ0Ib8g`5V9O4(^Q0A28hjR_R@0MKxa$}(O+AMF#L@tN)vA>8ZF;x;0={gCJvMf~4N z_RpyC)PEMAfIrm+U@jIICpA@$qCvaANgw;?AKZLfI&+p38$yc1p2GXC4&s?t=|RWh z?T5jqh?ko8+f4|lR`m6;G=_?KRnAkwdSEvI8X~kA!4cArefs6{Fs!F1JZjWKt6$>u zUdg(l6LNzj`Y%&x&MI4}A=jkuiK1~A_@MW-&U&Zz>aO|ywb5~}ViYQ5mI&>&nC&N# zoW&usoR{gVh|b&|5;aM=YU}{rdwuOZFrGcPBpJdC60+Jh`A&ik?jq|JozTnQ58D$E zq5@IvDO}fFn(JS~f4@qk-w6lL?u0REQeUhn#+jU%4m7 zs%v39_S6pZ2V35kl_7+P$R=3em1^Q_dTt}J3ThRtDd|GpmMEFYdy-#W6T7HL-T&kq zv31+1+LPUjM498;1d)OwW91YN&7>=#Q5m`1LUx0$c5MtW^;)}LM(CE94&OEH!?EGr z9Kw<+8Cf+VhkgeFtLXcEUu9&6+#+kGk)8bkw&?aVa?u@g4cm>%yc~Vs0`{$%e9gl5d8U8C8PIhe_^6X=F_zlc^Qq_4|Gxa z;-QfRjVCpa{(ONk$VBLgQd3v)f#_@9l}^32ov#F|Gh>yFUS1@#L@vmZ$W2j|NOvs_ zB4S4kG;bk3i$u-vWq4GtXUJc6J!5XMO}7fq5ad^Nx6_-_bjSZ#|AkD-Pqq>p^L-_@;va-kI4YGuRBxBLmIDM$)5Gi8gZHD zD`_HlE}MV-TglS>eojMtMWJ%d9J=k7aZgkhpMXAzX;*<_!(`Y%13_3`=GT;6KGwY zWCcNw*ElPmOSwQCIa!N>3m(~)O7}I7g1XJWJ81vU$V&Y)ee^e}DIKnk&eYnBzQN9| zuRsQ|5yQB^mRr93pHKUwfyXB7UAhefSDD+VV}DROXCg zLHOlIq3Xna5IWFC+A1L9xD8V0pBGKkIS}8Dfh#BW!NeOr=EKeMC*=u(yNof(jFT;xWhb{AI!E;+4eyDvGvW<(0x&#=93S4c_ghZ*=E={>8? zhO4?=yv!8*+&_7T-lqyDPO+@znQmogyu!y+>8v6<9>!+`Sok&%NKb}1UGT+mTv_U8Bnli!M`JNY>A)_}_dHv2$|JKksx_JnrgtMii_zVIr4kr#}dY#-I4)7aFe zs|3u`h5Se#D2Uhz>;gpa;(a|Blw_$ckF@gH2Q{--K9JiJlHxS4+}^VTCBD~vDMR^u zvm}o2e*?knxn>M{Hd@-DUkH3y$?^OUoYy)eoYRGs(|cL%@fs-`e)PrxhqS6nq2c^u zt%-#ea056N&p&)u>(1*yuf*TEnJ6P;-2M&hf@;wMi7bPvpBLHwwj7id-2hmOf@nGD zl%Q!5r4%fQJ!1viZkmC^RYa%`pKGbPfaf}bNTTHrV2yX19o{BSZBh@WxCayt_~Qix z_-^^>>H1X87sqzah(0L3AbTesQ)3`)a)CZ(aTsdJ^tlMyGO}Ong)yB{W}n+iZfVTo zs~(7o{~QI47LsvHdF71_xC=|R?4U-}(D{wo8?K_!dLa5*1`>xabt- z4LdFZP1qKatU{!!vP>btn1a_$kG85?o1nJHaN<~vO6vR+jemtg z;U5pa9bCBqU_$1V<*yWz(g14*SnpnzF*XIs)*8BxXssPVxE&PN|F-_B(e->%^e;^U<~Hjl5@g7bKih6akN2%02cMdMEE z*U(zTnow+XE?*va!u8cI)$w2#TPJb)JPV*r0G)viJY42RgU8PubFrySJ>0h1p6aUy2J?xfrWWE7Blf01D5%%O{m4J$I*+Q5#@&n+; zvvbv2BV`An-IGxmC8q9ASEGlV?WDGscBqabW%Oxz!B(?QANsG8T|`n1?I+;MzP0w+ zIs|s%OSJ6bZpNAV;A`*YTRK4qqH65=#1tW-vEI(_*30ax+}bpu)wf5NbUP<-BJ>|q zE0^H;z1?`W#;Uo*hUB8x`?&IAaYtO*BuQDeTMw9?sDkTt_Q^I#VTrZ5U z3{G#y?BZ~?3s!JL$NPuGqt*+7@0Z@*H=?j`;VZV+T@M>eP^GI3X)VSU^oj{?K%p{h z(aQ3NN~VM>bDch~4CqRJU>6S@+JTM;>c~E8fe(ddsXM+>=X^NyKcvF2_=kYvQp~CU84W6xoo)JTI zZyuD5mV|crk-niXi*Cztw1{M@KI)tGK9h+7?s$1d+gRH@UgvY@Be&h{D%P0G=z%XI zx5ruziM3N7LJq<^-;7 z7!l)qoeT4B7spL7V=hM2zQ3`f6DPv5cO?6S7tb)M!9KGEd164EQzDQDoX$P1%5}cR zXXmwVZYwsfx0PauN{)I}{t?rl6k1t~))XQe6sw!0?{je?co}PH$sN&Oo8!f`?9Y#@ zj8yHJEb4X|3}yKNzy4P7p#V|!@&@316}XH?{E6Us+4{G|ZNZw#8T+a0nN;1K=Q+Dq z5k1%amR`;tIMEloR zD_-b7v4uxmE&lvU!`0(;L4T7A@++px3MS#}CEzQdyIXJMQ?eRAd zdXe(Cm4dEM3*c&o_pmu&aGGU<8x8@po7DWzAHCBbj7WYv6rwTe-@pe|G~OU1`}HqC zL0Q3iS@o_*+x#Cmx%$|@9qrLTZV)XE>o4lb7PK%E^MItJzm{uvq-bCaw+yJcZ$3xc zb+E?!gp)?|Aj(VDN@P?euGk64hQu@+X@uf{5(^{Osru$`JEPW3ZU7#owG|I#ef60O zHR1A^?IiFK8MtGLWVhwBb5)oLJ(Bq<_eHViIqD@k>?%bx+TfL(q)KY-rV;fGZ`#F|2+5?8Tz zbFObWmaNzbNDPeR;vDYx=lih)Oq5^S7G8NI8JNf)?1v&9B(3rlzJ5CvoEBXZY}gx_ za!(hhA9=MHy4!a$C%lwVv0i9;iqk6Q7%6vQBB)(s9b0`C8L@W}aX=wDIf@KxEj#ge zXpHgf5NIX0UwwSLNPmz12GCopdaf7n*(gh}O_V`Iq#%+zJPR_`DQ`ETtVS&M`8<&5 zQFo)|-_jt+$!)63#vCAqkdAJ-hJN(E+V>mb9h&H%syS$0ZlLoZcW2)ZoWPx|thQJ3 zRs&U>VGhyi>x2C@dTK=OhD(1Pf&Y21{a-?nKpR#FWi@(cG?cDuY8uP&7v>yM>!J1i0t4WLkB*L5$75lPdYgb)FhTC%C+Qjvoy z?b#TEM?pop?1GhQE0c@ZCU~_RjiE=$Zrh#vqT}^9fX`Z|Uhf>uEEXqL;D`juAopxs z{U*sW9vmprQO7f8Ede^uD=oz^?bt=D5yDMePS-!{-%|;Po}DE7J3U_Ia!$;Az9T1) zCC^{-ezphs3U89YzhoyoI@jpV8t24>?RY2DDuUbFPctv_5v+q~sMOkslqM0N8o~B_ z-4x$u3&$u(gd_8kfU6cv3DfG*Aw6)bf)nfIPrpEgHrBfv?uuh}V~G(yXB)%6n@zYr z)0?494~KBmXWxH(m#8xI8D^be=Z5D?GMv=t46?&{if`gzokF~kO$ns!EVRsdj*A1x zV$0Mk;OonOdpqb(x?hWX%!IiE2J91B3x|e6&84|c?}BjLOkGR^y?Bei|1}w|r6phkAE#R+;z$+O09OJu46FrV^!U6m&3bcr*BcuWL!PdyP|E)w2RCS4(hmc6GM7R_ExC2=?^k zS4ihhrxk2=v-a2Ho+ew9asW-A4V);Je+rX<^{GT5Vd;mR) zxQufAG>*-VruJ#AaM%muTRQE(kCSJiL+2vyQGcL@=5txe3VvZ2Dd6Dw?cnv-^M5gj zMHzkkrzy;a&Q~IE1JLubl2){icyzNJnDlZ?=REn}msRxaj}AZlcECo*xqqSXmI2?5 zqR9TTs@93DW%vI-+`R`>lk3(kj2i)wCL+D5h^TbwU8Dr0sYnkXgdTc_&;+Fy0qGq? zq=g!K6$mYKDWQknAs|hPf81xg#e2>^`yY4Q``z<>gE1K4P2qi?_sO%KHP@VT4Ylat z`y?VtTFC6P5_Sdovd(Fm)}CxQn+Fb=EqO1h6bOEabbo*;Ig7|Z4-QFB?@JxGNN7VK zf#)|ePEl!vp6y$_5_=JXuHHF%tCkwt2XtpeYu>}U=c5njewG+?bH?;!6*gRCeF4rab}oZ;qX&_oeTUO%(JJFGbDsyjgHvDg2%c{WB6eVpcBEU|yx``PVD zc5zcw0m+$k+Gn{+0&bYJw6^3g)slj z&v?74N&H0W3y+kO04hcw9^~D&n}F-7fHQLk5v|r=EY)j+BzCt{&tvsJs!mJ-cCty9 z3D3a#t<=fDD!O-eM!DjEk;p=@sW}I8&Lbk|jM*m5{QU(Q z`ewzysz-kWvtTQZ9VG>t`$#%EL2Y#Ddl?zM--Wk&-`j)cScVL^aQJ%}BS|R}H~!xg ztDAl>0sS8SU2OfahxK#W8b1$L(YdjRyFCRvqf4#0`IYpyn}2FhqOy$sU?<-BlL~n8 z7=?poO_?yQ(1WLl8IMjDFi?VuSp~TKU)eBea(N=&E5;1KUwSMW}Y{jk?SNnA9ki|(~eo5=CY^fvF_;1OWN zMsuSt%Z5PK)MiP=V_91J$3l8kOJd^ z#@hfF`V@X}SjKh_kpzY8#O%(~vgb;l<%4S^wNmhE<_dYy2@KJE&1Hz^g&xJN{>Xp-uQ9?@*SPQ`9RM~ zDaPl^@lyo2{xvBnHtr)G$Q?b?@KRCEh|1_DLoI9B>u8fx*n2VMkgRJG?;^9HpqYg& z>NB}*7mZr}nw|tqJsV@x%Cdi*{Zp24A^KUj0m04~7g{vg$9VB(YTMazbB*In+R4a7 zi(u^$3HDbLn=g57&w`A@$Q^zj$-QvBkp5UqDgDo??d1yt)^7)c-gBlYE(!Qll8OX% zuysG#>p{KU4WntvaDFASf-fvQ(4!`kZOH9-N3D-=ZhKagWHq62Fvs7{WrqRp3u0zf zy%zKF1HTN^d)r>^X(gnb{fqq1l@S#W>R}tyW1B5EGMd{$#RgwN^>4eRmnpoejQylu zsjN)Vjr@i+O=)U0yp-EoIusdwGJq_vZ9&p{CArJQeM41vv;(Hdb1@@*1h z`N&O2BIK5z5LOHt4<^J2qgZk9{Z6Cu*b+>RpK-p8H&2sTA^-~!AgjhY| zxBJuD&1GFp{R5+2@&ZQM4FbLHk1b2nVsfHd(68$xgW=t=zWbVM!2HW4rDh0aNLFM@ zR$@Czsi+_&_O{&2({ehQmITCsNRWLmyp%9yZFqGM-Xnr<`Rs|&WS==3v7JK&4G{a$ zn*2@2eTM9{I7R~)yQD~dhe5o|O}pSnm)M+ZQ!*XX zi8N~5&i8|PkO8o|<8GnxQSQaUmzM50ZSUg_1$4A^>Ns1q2zBMzq!JQS1@29#aaY(Y z1s@lU)d=u>x)m*5URV8Q(BT^I;r{Aq?0vRo^1zo-W(lH`Q}3)@iM{;$?rjUxf7KA( zs>JwzxT*N-$#V3tF+2%@TZlO8M0Xqk7en75t_$_XO7_3S)}HUj&RB?xLnXgS75CQ! z*;JpX_?`_R1W@EHuR%J!OKJfT4=SIT- z_h0uKWhC8ahU*0g-Er(@6tEzC>%2J>uUsvyOw5kwsob!*b1F}XzkFl}y{ga$vxi(W zA_~+Zn2+_Efa9FbNg}lIIPbIL#bXZ@+?J&>HGN!^cxt!5cDv04LH^nVG4S{f#LyrT zRo7?NZ#Lt`QpNW1N>oMPq zjS$j>rg36+E6ZrN{lgxQ=hBxjIxdLH{3U4}38RlmEVxrKt*_K}CdY*P{Y{>DXQXw& zLIyS*b`io~&Dt(zK^G$l4oJ6Xk%RsJRF3&QRsDOY^U;N<9t^+m1`1Ok8yf~j4qh%* z*>q}?NG$Yzz6et5`)ME21ur|zcK)mU51&?&l;Z6#;Pr)EP%D2mCJg+&Qd_-T;-$eH zHOIhj_0?{~NQl3rkC>%AvRH<+e^=lcUrX+ZY3HcwMog>FxTu+#;T~E4M}!C2t_&gP z5@aFaee@k`F@%kME=ImaE^j5T69|<(d44cNF`PT!7*n_;VX(aj2B0~X0#GSg<*LeY z04h-B2CArh<%t+fIYQvKSmu+TD($M1oAL_wY%$A6sPcPLxDNSGfdEU zWNd6^_8P3vW(yn3;M6#saUNJ|eS6Lg6?NY=ac>@?031^P5GZ!#68if%zcR~aE7qmazLf~`WandNcskWTC1{$+5@ey z=I}!Ig2h(Pc~eutbAAVsc^$RL1pB3@=NKC)y#4ynQjv4u*@7{|yj6clmB|8Uxeb)tYbU(&b8P|ge z)Xxb0Ofii9h6OoR+JCeEM>oF9Z&K67@fJna$zT0w$M+9+@_#0C{Zkvyf7!wF_T@)E zdhd}l%XWIug9ju&KmC&=b@ABu50vZ`wCE#ki-9li6y?wBbGoOQRWk1SnqQAA_9UO4b;3kBjmik>Qj21(ItFsxhWQktx*CXq21 zy1l>on|o>sjWBV_4dGLPph9|FDb<*FI;`OEf#tXI+wH2O-W=yL2|-(}vK@iDflO-& z^P-DgWfi+f63{toc1q9U+Ok3W!9mIH=Yo!Tyd+P*3F_;Qw>I~L=B13GV;Z>0_2L)Gfgg4=B^Cl_MVJ(1F8LC z(p?9;x4Ija({t45$jOa(B{Qb5L58P*`(E+dgR`=SG(jGYDoxIXn#(lJdWFYn1>3j zpPcH&u6?)e;zni6Txf0=VYlHQ)j!`|)}`Q5?>JbY&@3qbGM^UtcDX zg>9_}h97PxAbc=Lv}o@djTm|_<}L%qgj%2MvJ)T01Lct<2;eMe5^8;NYD2z7?=oGm z)2He=Id1MiZHb42CYqdB3SoJdWpK22S;?QG9@S$h%`1)IyKt@@qRNRdUe=?*b`Rl8))VEDp}SAjZFoWpLyW)F|yvWRV1gq`Kf)SV^~ z-xB`4G`ZD67V5@;g8k2fcGeC|Qml#o+2OsyrxNTd0Zb}vJq|uD&qgth+Tt>_0S~G3 zlGXgPW4jxf8Fa{*+qsA5!(dCHZ4G_z@a#$Nuk6kV0rd8*8vWY!QxPNU2T__I*B++2 z5Sszz*;m|MQnQM>>IH}-tFLNEGooLldzvx!ZeJI$-xJUom|=2HNlgng3FTW}TW3&E z7cTqqC5^x73}coXa`LIEEbSjsFY^C4&^Wj%j$yKr*x0Vm%z@)YY_&P2y*B&kmps|4 z>-L(8o=bv4vEX(t+uZ_u;veAZ+_CKOY%r4(5A!-=r0x^hocbZg|7MP zTSfLl44b;IQ+NfV(=oy|FWrv#gd{=Jr-3164}K|TV>TQhoALEGjTi+|cK#0x1QS*+ zD7S_&l>DD;wzND-LwF!Y)P8{#b`iP=;ek?6@M4f@(rbT*Zr`rnh(6J^Q(4N_|6GmP zsUcYoYUa{<)Ejf{|G@?fyNGwIT!b2A`+u*}|A`XdTrA^LSbQ}GX@5V-Uj%OI!_i2E zgSI$~h0=OK-uMZlAEtlXz#*oMg`R*V2FUT6e#`z-V^tV@L437!5OI)?DGm@vtVd9; zU6$7W?XYdfNAj{I`Mw8>lxa2Y#MtL@TMimr!D`se9kJ-LD*3>TIhV1D3Q<^9;D z)}FwOm>s<5&OX*xa*}k&0Y3zj5P6TB&=1*hGzjX37nM;L=Mo<(%gmf zgdgpP>?W{&;+hhV-X>ipFp&t(G$yP^bkF`;>fku%SgXzk%>4O8!lr~*AJh({n4Y={D$~`!q!dD8o=5!oE@mAE7MA?%yDCbIgz15ulE! zV`(vFS3LcEXwe-~O~}u>$Lp^+cuX@iEeQ@96VsOcgxncn6-1oT`Foahn^&n1r?wE_ zE^WF6_K)hCMPXT=)V)&Za~75Oa9-tw@k#C5t^6S2KSTi;-@>h|rcjip9YX}A-CC4D>9FW+kq zl;wUfGCIG>Ab))6MbU+nb7CX#utAR@p9-gR=HU~PEo#E>%N})SiMfzF=c7Q???PA( z-Sck7uTBbn3KG1JF^g>7?Dul)g)(hE4HQD_H40?^u5o48`VlZNwJ@kHNt1e`@Iqsy zG6tak3B)7Lp4R>zGcMAF{F@ER)rR z0+dZNE{W)>aS;>MjD=-bRk@EaytePp+Qk~YQUiXX92NAzFd5{yZz4hwo=~SxxJ63p zX5A0-cxLz{S>RnNE@f}mAl29voW4cTbh4?k^okIjq;LhI_Tkq)>y+zU+g;79`Zv${ z`MzPDIch>7<%mLVx3bLnb^e&E!=S^r(ql%mxT&BWF+6@toMf}EgnN;G5SGbq!Xlu`?i0WbfW;P+P-_oR zZwlL3Ep9JeCM^;i4Dg*(U)Xg3SgT%? zv>Y>;(1of-+OWA(8)6_?yTdKA_r8YBV^R2@jVD(2a<1`>0-#^nV-(9N%7h4wEZ?cf zM2Z6hiJ#vgW?vMUnNHcb9BR0L;>Q%)E>YAY&1hRmQQgctal=lycl zv)$^I6%)Ivs?~{V`xQtF*#FWEMRt#QnYrXT_GSpzMq8nq*-ip9EjVfMgi9kv=aX#= zP~h5l=U`+{xLK%SBWG|=mEg*8jf)$3Bo2?niN#L988Ki~*At^$Y8r#{B!$wpY&ZpF zSVbKfJQQc4kY`vnpr=1cVMxMzvJk98V5&Q^#&`*h^MH;is4yDLU7A?ICPfvj9x(Fe z>*lGKsXzi0!|V;I^Qskjz=$(lWD{XoOP*+YQysqTpy(|R;#$t!d&1dB-74(xmb&P+%>r=|rZMDZTejbZ`jUg&6f zOamC(Ik;<54U1qf`;>PoZ)M^2Q+1(W0^kx=kSo!xN$5!P*7m(JQVU4+s5eA;@7Sd* zZzh$Pgqi;{N{baZ1>sQXT7hu0SnQqfjo5`Bix*d)kYiouw*vM4D zx%%6y7Kya+X?+~dP2{$lsIn(GN!N4hG{iKm1+)QSH{cMVmIZINPR0slzk=FEMrmwJ zA!HAxP0fyl!fQt#Nh9evd(Y#T@DpaN)mKne&-~&@e4K7+94Jt-3Llg|{7redfqcRn zEyAHZG~=3FS645jwmddNCKFqBtdA3fWLdOsL2;Np>^L-3?@C=YOc~)X1uJD=6n^ zf>vej7P$WBN`F6@i4MI}aR<1tQ>h-dw~P5EIY^}y9t#CMJ^T48PQ%;dBDRi8HqSA; z;Fw4Z-f_`h;D1*&@EzCqJN4an8q41b^8G0JCL>5b3zSh;wG^Ux?YTqAMd17ha|| zLB2~1UaY48w&^#&?{sCNqNWC`7(uZ3m0N0@)(14r(8(1?uc}DHzz)xlkrlH$VHH<< z2CTxZb*n_hk_-DMLi|qwZ){K?g9!bj5Lj5bk65SRhD0R=AzxkUSQ)$ulc6-&l_>{4uak2gV5{YiO zr>VFOhhM}zBJ^m(%W#II0`tFPKYN_h_K8|Mez@CMN;nYO_|7I-1U|zl6P~KJX|!3l z9xI*vbS;u%GK%Ti^u%xu{N?C)U$D#Ysz~$7%*=+HA_#4*b83rrNKD+u1deT~vceIEK-oDgcR zw^*Ncbu-&ZO9iGrB4)e!N`NA`60A2|xJL9v?k46&2}w;b0Y|yS-aEcs32=EeGg)By zV8W)da%vf=K1t3^6{pwH9hPzT`IU_^?O&o~hIE$q2C8b4sak`Ei z&WxhjUC3+~3u0Z!;^BI^>gVD@nWk@s__ew@Il0Y948W(4uq!8lS45sCwH@`9Lq7HL zFwmhb?%VB$!WbB$aRU8^lUvv*DikszrB{G7XB{IF2}w&siNvRRDee%29jXOwtSI}K zPKpU;B0Nx%V3IUrw1O0+ee0S$#m}HzvtEY}6l%6^k+^!euu!q#;=j53m}WIBNa(=P z@yao7M7#?-#Pzp%X0EnGUpD zdrqPoPtQqlpB#pK!=g3)hQ*&aW92ela~9D!jS=)rHfSGc!)21A3Z5G*4b|+}Oi{lbJj0?C%CnYzs!Sq_BalW<;zJ$LOzAfT4(ZbQ~Fm1p4Q>kM0Ts#r<+MU)E?kO$( z;y)tc!jK$XVs|*K`8!r-Ov>`OH@CsrC$w_M&N~GbX&Z5Dvf(tZYo;dOvxDWP5t1Vk zS;aGG_GgrNw=jOq(qJiVnt^m&AGMhauoe6w$PyW3BexlP+A%9X{ z$L-ijNEp^uk!SX{pSYoCm(z_CRc) z@1anpn)%^w{WIEWG0!9gKE>AC`79+in<12#YRag(_KX9a+7YMsagWMgO!U}jKv}tK zzkZj&a>ldx)nSmD3;b3V7ema;;y=t6MH*vkIqqjXt`Oc{i zG`;Q|DZwjMk`a1WT3VNd>wg{|^@p?|N2wi_UTsjpb!>))Z5>^%emJJ98@0h}iBI^d zu9etYiDlL}IXq!=kqrW*G2vt<9Z^(8LhrTQfVHmVOxZ4*r`wa_>R-CaKodGx2-6fj@RgL?{GPV+VzoWGcu3{X!EBcIvP#O#bk7$Ppma#yKtB1&YO z&$Vic81F?QJj1sJ3fYN3bW35?Sys=Aoxszm6?9Ql-4Wc*@mXuvTXC2JX;=|^@WVs< z*12VRJWR8r$bn9(o;o1}pk{ct?bP$GXUY^MagS{SMJy|lM!Ra|mh)|ArBFiaH+V;) z592iG2d?iS-1r)j##7+&iq5k7Ms;u23Q`x@JXBxAoat)IjySxIWUTXQLiJcaLNe!ngriZyk#m}B*lD8Z@DnG@E z@tl|F{(LA&`UkA{$3_{q<CT3H!%A;EP3!DK&V7{w z$@72Eiut9GoOh95x2E=`JO6hOAG2WnlCDL=-|e~)-{^YLge13*ezYVhz-`0YNVT;a zb}RtvIeTqeXQkk?S_2#r7Ov1nu1R4$wccU zeg@kshcypMxNk^aCY@HTZz{d0E&nAvMej?Nd`JF%$rz#ZfB?b8hTiHV*q$vwQ5xBq zEUejMPFU#Abj8708{>=GKy$NpyWDj>&(Lt-hiHK$q%nX?BICn1tQVpM3Z-`8&-L!+mA_i!DR}nAel5tN?SDq#}#bf&}f$sOZ{(j-}h%njJ>A z5)x&M@S2AiFAW_}-7ddcF3SynSDm<`%NFUMa_Y+xac=CXr(uz_$~-O@0 z{7{ws>qr0X^xXG?BR9WcU7N0twB}$_U{x#s+VTnVcvq(|jWXv2rK6f2ruB{K9KM}f z{Kvf`&etXTZu?L#T?S)|M>4?*TmX{?yGmrH?}P^6%RbBg>+!Fqok|+{KXn}Pg>kqM znbF=!!Mf%tAM5>T=!)&`}&a{PF-xiwydI&SVJVxGC1>+PB% zj5fy{+tkhv!7DC30SYnYp})fky5p$qAV*?SF|px{lRUFv6(mP90_?Io+0-4aS zX%Jhtmy~5`>Lg>@xOicR_g$?Li+43sB~PXAS0y_EmHA;;Ne|S(#Q<7`knn)ylqeN9 zmRzwXvHxIE|3{RUIm{mg?EwK!r9zt}Q4raw;^{8_N7|w-i7fPPow{sqFWvFMb7PIQ zg-i$#zo>vy5Y!z8G65XK2BQFJHmsFnbNh&@@*zL_i+lJOjPUmY()PIMgRblE){Woy z>KFF#YX4dwOi7RO$hkIl;M6n6-bt0CTt*lLOLXjq4c6dBPcZwG+7@P2yw<2Enkc#&*MeuIzDmf5R#}(72z9 z3Jq7iOMQcECV9l4`-9J_KIW$NXZr%5}q`9Rk5VSxwZnLI5 zLjZJ-f^-~fSa@CfkT?fH_q3g7$P}8cN&lMG)M+NQO<@ApPhZYoT)S9+*S_0c5~Q7Q zYaseUKb7II#;-nv873($E!tSNS17qy6@i3P6FGD)Bvsw7ATojH1R1AOX}QiPEG_Gs zQ)}i)+LPM@E!mr->E#H$?V(M&LL#Iv4N`G5WNG6}ev^Xfh$CRn*(-Y{f0jn8vX5?(6n&KmwEr82`3Bi# zzG4j3ONNeH;DMVKt(3L|DnHeSBeu?ynJf#M$6|zVQ%dPFttVASUm(0=QjQDjvfc2P z?q|Onh1{@wr>YcDMlK&p!AP5QBmFncGe?n~`w$G{X4eR(!z@c^~+`94W8_N2$4{Puhip+8vdA=2qA{QpYn$d4qme{AD1r4@qfRg!#w-n5grx2tsgXY zuI?T5?8p#sr0nSY&=slGDPj}y*=2v<%8|S;cgL!>IynJR8+ZLA3K@Q7X2fv5zbbqd zx5j1j@}&M|_;!!P*%52z`<)`9g%Ub7(GlZKH2O2&I%1jFTF-WNP~<>(|Mba*U+O~1 z4RZ*O?b+Ai@rlaZX{Vqd4u@kI=W@&F53eQ%((zezSnNF`OJ6Ig;JpC{cmfh9{-6W9~5Z{b6Y-(~a2zOj$1roOeIikp^u z6ql&zTFXV?V@q3?8Xin7>>({}h)-@+8NHA7xMKigQ=$X*+)lQ45ai2vA6HddtLunG zb9G4db@4FsvTu3eaEoUFzp<|d9*E69=%0u@lJ*snBf4~!CcvtPtutSa=bBX{V`#-KG)mSx11__?%@Xi_ z|725LurF};i@gUxpVlstCdRG^N>2u4@)$7!b*`2YXb)t1r0}|bOejdZ4ro`7+#?^>%~|wk^F8k^6>I)MA<~5mdQq{ zKD9@Ln?s2Fiu|+>ebXgQp%$uwsVerV;BJn`?Xul`>^#-xO!T`# z@34oHkpb{R0_cMZy2=ffHc$ql1rs5Gw7Zt6sWSOxmJZEAq&J>@&sMR1koSHN%&)os z^jjI;ypSV5l>^SO?_~EWVrU^67al*A{#BPFS^TH_+2Rq2f3Caw*U_h#s_DPi^U#P3 zS93`C?p3Vwl$Vg-PazPhc3W5wEu4wA?pLc(n@~?CpFeyHADcMx85}=huI@#3K>dI3SMeR#{Qp52r%OU+eZ%6m zojGHlDbjkbHu1}!8Y8D<=Skb*eJmB2tn zcjloS^a3jpsN{Ts_SYR$l>QCt^c&W2HYUKH=cJ*DUYwo1me!Zs(`6b1%tCLSu46il zz(xVU`CtCj#~yE-8T8)DAJIDxIjiwt>i;W=ovwRBg1sz;h%U?6H~?M6tZG(G>ZU7rwGLLribo#{tMDLZ zLD0zm2H+L?IujiqQ?E}2F9`$TCJv;FHhbzZ{g3RhcJF($5~2Fu2Xn%OUB=ZVJcjp- zJuZEIqeW{la1dt7l>}p7nrI$1%__9CvFOvlkm^b~vTlsDrD`!!MTMd(>=+w2fJ?aD z&@--D(8rbXDhB+b%~%blxI)p`l$gXtxut5R_3*7;<(6bPok%nL5?+yezwH)w5c8Ze zRbxU%EdQzkYpDx|G0l+?BprgC4(%kM=}EDP49BW`x!L-JleblQ@uVLc=^T->7dZXhjgiGSK5QZEnk8~R^mIigjH0oKjJ$GEeSm=RL;^X_h zXx;EbXfo{eW07-Y_oPSL3pk??BVHo|*HFWVgdnHvNFOm&3iZLNRpBdx)D#PgUfP48 zXq1l19N~V_7~Q!t)T~{ERzYwrG{Ck(K+2GR3?xUFdhDwdK)OI%l6LR($YRo}_xPX^ zy1%qcW$?hrY9R6Rhc-Dxg|w4#WN|TI+(E3z94u*L$CA6k@6M^o!TxC(S_9>nSd4$n zCXiYeS)m!!A0;4`F4uFmhffbkG0Q=97v#6wj(o(n=Sw2GH7>A`n8$=|^aWiZYUMVf zv2%uWjSjfu@euI1fu@I*GuW$Vp#Cg?RTAv?3QeCQP^&wisTw*Vob%o-iAkiU+q|7x z(VvrEZjMo1#JNaPQQm^`2^^oX3#dI>oZ|KLX)ztZnx~ZueIw?spqpkpFax`VnK)G9 ztxONoYrpv4`hhAuPBYoqVS-AjORUc_!fKPDlQ9t0p-RUI#}e3VefoH`cK=~^llOz+ zme+m5aMNs5=!;Vkb#EMFpyG24%P=B0{ySlX4jDn)plY3Yu+@@t(70?adi9tEXli** zdBt@IheLRO$ifWt{-E8EmHe*1v(VQC8^Z&9eCH-JBGLAAq~80vQ&HCrPH}}oqg`RD zbY3=Bh3ToiOL#06j+a>4w$`VHpPAYAz$Jq2R<~&7q7StI&1*f0o7P(_b#eImLJJT& z?b*vUBjwI=3|6*%q*~5+tOk!Q!cF-+~1KZYmpPcayDSFCI+v|qNPnlpPi z!o)3H58hRKg8(He(7+k#L_WH5*xr0qXyzlSmRBW$`4R193fR3aL&hOvN_Dc4Iz+e) zIsvhj?p99(IuQ4-r)3DQHy%i2MdcFPTh>zy#A2{>yH7}+yIBsJH1ame)&ZctdP}n; zn&p<1`96Yd9rnhUoE_Nh+rhtsVgGB7FA~L{)}-}iGdQRj7nzlrbqjogq{W6C)vIay z!K*(XUJ3lQe-S^`kB|&({DEL!!2l2cw#eig)4pwlgwlM_rq#&rP414skE?D|*= z9PRJ2J+Yy*?Nwn<%#LbZy!Sj7=Ek}TsS6n;idn0`Xcg>|G%6I{FJASLM;49&!SH9jelKyUQxIZ*I>sI z@29~_w&neEcyUr*29sqch4foqG3wgS3=Ub^W?ok@_N#j5tJr(!9x1#!!x8ujE1FvA z8_We9D58Lk*Nkiril?zWuCtQLO*k~LANb!UP|!R(E7*45zM6f)fOgr|WE>>(ep}0V zVf-M`bK9SQBrEqDhoK_@^E660Vm;&zq$q}+xuTMx_esN)wv*m?Xqk1l2WZJRBpEN& z-_@MfaBu@SCJucgw@FJ=43Gz4`AwP$@}0V04+c58?LHu z<*t6 z`sSw)!nUW71wnI+be^xNpBANanWEOz)T1SR1d%$N)pYhC_HSkZ_^6ha-}DWSW*0Op zmSZrn-1_a?{$==*uiWKzQ-sEYiz5^Fujk9CxZhlivs=P|8hHH^ziVsNmy}rE*zL9r zR5dZlTrfWxw&-_N->bQh)auxg?sk-J?FRj1iJ;@ma|x=X3-`5#^2Scz4M6hjL3b;a zMgy=QG^nTLNq@U5QC20%RneArf@Y+j{TdSaB`(aMK1Rk(#`DhI@NkJSGfV-p*F7&4 z4it$*AxxCns&i2kTS@FzTDnZy)9|}yaeO(D(A4dF8svX5^+%sTkuqvZYRWtjNvXU% zq-G`;c7+s&7p`AJ@X+5ila5|PbB>U$KnMuN*7A>eEi{@=B*Gu@Xbzud?Gi2RSetG> zfSao%GjII~oA|;}?M@u_*Q+-B<`=)&u#qw<1sIv%NGapowYX4v_|^zEIgu~9q$_k#npr< zAGwN(p47z}S-N0cF+d7|SWUQJL{Ul??<@h533D}#B{Mgaz=2$48WH5?y8s3n&B=p6 zpd2xOGL78s$~poP6LDb#oEt|^WH4;0vunug&weSMTnj2D$;JTL9DyLan{3u{bn z6$3e9q<6S65it*K(<<7?%n$Q)=tXrSfU65*SXE#>z5Nsx^US=vx6B6+N{bHCGRw6X zN#wsTo3u`__M`0{ zK7rUjy6~|6_M`6}VaMM@wI4C>D$z_W_^XhZrRqD{M(5El_U>bTE7Dhde)twX_)|ZV z9`!v;`YHF`Lml(5Tth6~N4}hEk<$t7L*RC79QM>Zjl`ntDL!|YqWWI08RT$_1f~de zDX_8oV|8loY72#aknGNwyHvLAvzDWj+Na*OR2tq@y)EQou&UwYdU6GOlm9N_j909! zZfdgW8ZNx|b0%a!`8Y651kw|0I3#*^x}ksVN_B;|KoHwy18WUyHp-0{#=vS zsVi%6PW(>UYbyY-MD-RUDG$Z!W{d970r5+>Wl~J1IqY%*cSl-!6ccr2fKcF}Z&4g| zFPZq@`n~GAm{R)<&chPy2R3sbu*xDXH#NOHa^wSAPbKh8 zI3FycS3JgpT&{2g+BG0n+F0TkZkgS~!6usSnhm8DgS<;9mtIyIxorZ0#_^Uk6D%fd zhTr9_-gfIZqnBstpiGQ?S*=dXcQ@F5h)bTXL{uokoR-*}{t8nUrC2meYU?zNyr6sA zRUOwO8hgF9S>3hU9njUUKw>5=ksSVPo<3%0YL2e=dA)G;s$KDkB+g+cj+s7FjzhA* zJgjF?c#Us?hJC%f2e!O+j+ARx_e$Fekdr}FnTo~Fn{y9H#U>GTzR8VD8NM0RmuTJ% zZ|E9*MGN8S$5P=!`_~}P!gd8pCuwDuv|F8R#D@6gK`%i-9oF$H*j46P`>Ff69}G>H zIm?9FOr&LQ{EnXdN0{V3bqyXqzcp7YNTX3OU!u7Jiuy9li0l(BwXC41a{@^{a!8&M z+$;Z$*ClZ+`1UTwv5wnKKSl!yIw+XuL{xfzbRS~4(hPfFJHdtL=BzjKC3X0@SWJ~F zsYJ=lHA0+qKW2K34tV8#(Y8&q31r9W6&D`2q(V21%g%`=R;Gl%|8|$@sDiwrwh zhsKs<(TT;tcGCWpE_c7I$vnMQAhT>`$@x7y-EP9J>VQNEs}F&)@Td`9zEUhKFul`pWyJqm9Z*;UiS7h8^Z zfQOU(WIfSm>^7%uG}h#(K*mn7B)i5#&K@niuf`gOFRY^vdABrO1u%dVdS>|}M$nj| zq#d^$0fOS?1Nh+A?I@JzXNAPO0-cpL0}PoAz!jsu^Iag5P6;qUdUJD~gox0r*JD|) z)dYyNw{%{g`3Gjxa#iBckkk|ZPj02&*%-r#_|!i~iS7K)v4IL449zhjs`}Qq3vt4) zqiR|S$fg2QQFl$cT~14(-Vw7khy%lJ%_J7bol%&r0_}3Obm}!wiUZtha;m z@?)7k&lUqwq{QIJH!Or^&g>Qgv=@9`Kd8ZFiJjM5G4~`-K>j4pMWD_YbM8lEd!N(J zd#ef+_A04&oU||@!Z#(P-f?!u5ab=lEcz-uoU%F~zAhH;w*#^gO!FcAbd)l6@%fVj zjwjnZJp4fKOtsuiUN}4lGc2+HZdm%Zawk`wqNF@P@!3qDqf$p zl{zh6%LDD9^9Sr31G z;#nJiZRzc0zgIg(zu_wSP0C`vO!?b?ey>6#YO@^6FgHf{ItOHn7JsSpJht-O&=36j zNKaN?Ym~hdP+q1trL}Z9f;&ck>RFA-*^bvE1hsy9}izvc!mc`lc%@Q}Rze$X-RhLMKNjLPG3E0wvCdwKjQMk(hZ+ zW-9%C@}N9Y$IFii><^k51B09?{y+BKJ1VMgTN6c5K_v(%xd1JKNRn7&C4)$ooWvp( zMb1zJ36h}*l9ObSoGGGEK#?<5~ffAxOcH{5^n}%nDQ!-PM>m`V@TmpFW8*3-bLC<|Ff!Og;ornl; zU&arfV+`My10ph^g#S?*`!>n_|DNRFe-b67$}|37eAPx5pcHGPEQwnBv2?}sb8-9Xg_%}-2UC&^ zY-#j(cNg%~%=U))yk~p^BE?2EEXNjPGBpKEb4!sG+7oeiapS2D%Bk}Vt$fO_Nfze2 zkgp@}Fz(;F6ouf;JT>3sy3jl0^-ufexTzRYXq$&UpcM4oxI`r%9!m8ztN>3ySAe9Z zCSGiWxACi$`B@kxz2C@J?4cjN$c*e*%3*) zl8X0tKv1@iW$0Yu0mC#g)pvA9wVq$D8v52f8j7~&MjG9V{83}2Ouo&mnFY)^4z<@` zvg2y<4=qpjE;QOnL>tZe-~V!izf^Yh^K+vJ4Ggq(Gz3uIQgRIR>t+Cf1wZI?j*LkT)d>)&;K`8|CQBmEN$IOdKSOR+j7DD4y|}r3WmsZPl*2B`LCFy zlOMv?uUCosfJyEGSM%#>^A9TJn5Yi4E+{E~J)<6)#$yAHHjZ8vuw1kboD|=pA5!ZT z^z}3A`}5;lo%?v5Ae#-FUpYfiL6@B3-{5B=p-xvxs^=n0ciMP4s|G0 z%`PvO!@us++O!Q%qUF{BF#<`Z^FU5mf&MYEaJ_12NVOo1B$?O>q>Znr=3eKUQ^hB<@)+rf+Xq{OrC z9P*LEch!hI;9f<0aeHHpkAlxYQTaB{$O9_TFMP+eD#w>C;dpB+y95whXO!w4fIIX@ zv1Tk)K=ZY}&DMBb<}bKE4g9=Qycd!n-(HtvSn) z0ea)Wgcr*MP->A4_BheTU#sYdpklGW4h*~^;EObO?+Y8lug9AjBXIAdIRY=MPH$ZW zBLkNXo~oNl6%W>R4^27E&2~_0r^=#T88H|&VkD~3I&|I5`vaSNgRAysR{qy74GTk{ zNG<${?Pcsao)dtx^eK)jt2#9m56KPa z4;yX{cq)SfC=r^69Dz;ox+beGdw#0Y&xr2uSorDU)-ry}yH?Ub5|<1@^0MnJ?|<81 z=?6og@5^M@({u=qA15q67*JwX=dC?u`i<2!cdN+sH}Pdy^Zj8>O|Qh1*DyoeVReyB zQ`?s2Md9GKk6hZMZKj?&hwsOIFal6H{J9vj%+-TWi*af;ug3S5m$%pw6R2HQO|Q3b zqOvgMB_8ge7VWaNe5SEiKdKIj*c?Zsc=|GJwlVF;v1(68Nc`s}9-fCm=lZWaLHxJS zFs9Ll2_)FE^5J(hL!Omw7;mqrbFCB33gE-B!955|aKrls{$X|UwnFMVUSlC3X2aN# zK5?(En1-FhHXZ8Lr4`!=h^T@7S#;5()NOC{uG7q95+n3CR+(S(#*J?=%Z?DQgTbvj zGKQ0|c>6@b{gp+5UK{@vTWIjIwaY6CmsUckq5zJt`hw8%GGv^9O~?VI)^v?^55dX5 z;WRn;{o2Jre57Pu#Z{aXy`U%MOstKswFnVe=FQ0R=8&o}WSFcR!{q)jb?#ZYluoKU zu6o};N>O^=_3gDnL>@i8eZma3yzCKF-t(t(UVcnjfX6^mI(UH>CPQ}1r{8^k>K=FA z$2RBOxr>GG9@9E^F_E6Ua#PMQb*_6&}4fH~r5?ENYzQd4_7F zgPj{$6B*j3!B-h2QvTYe9Ve=f&XJ){5Foc9pLAP&O?dt0B=A=v**in+{YC~I#Q~BJ zGsnNN2!_?O2PMZq4mW~TjPk;-rR6BZAN5v2!Wf#K7&}aM5Q`o=TeI85Tg@p3e&|&c zcp`S3=%=7MZ&$wOD(aMv_+f%G`o zjh19s^D;R1T&nTOQad}{qkP1$X7b~b zbpf2r$x}O3Wi9gcma-t^Ir-(F4*gw&_poqOjW3^%c231Vo_DMuGe-$WAP88V z&pQY-Ci@9nPqIaPB0w~x_Si+XoEtv-#BD&-wc|nheA^8)>D-uZV0DZ!TNRp%dB+=O zLsI?Vk)D*S!esB7;4DWKBz$1n3g5VpuB?fJZvf;*rFPzCrju=JGVLW1kgwGyx|AQe z>SHfQpBe!THR!_gZ%!;GfJ}(YTCr=l3Yf3N2hz8gm8|dw$g76s&{{E64`J;H)XS!w zap4n@Lq-H61-HMTT@4?T5iWc}wF{#k8`766@gZ`eWe|IIgIHGmc7v3F9$mtRWewQi zFRQg@GQ5xiK_o@@JpJkfR?JLqk+=E+bI_PzMPCLA#=r-q_)-0_P2L3nkNb_q$id1E z+BQ=Pk(j9LxQ@Ht04NzG=~6Xl#^xcFO?JVhwdbp@Yy#8+e(ltX=4@PY%k_K1g0{%=j5s-_7H01$}vNrA6~(VSH~oi&66hlLLK^@fR%wF!D&?h@byL z2MhnU1CE&0L!`yJ4Ubk-QC_h_&R+cDIlkkn3z_BmI#Dy-&=J834GnIEcro#NQ60ex zOI=2vYR3G@b{?+!#9j}h9gzk9C;MVHUx}Zl7vO6T_$U@C?@#OA)?`i@EES*q=iTiuA9xlMHA&}FEJsr;7&@R zYCWZ|7wSIx`FCHu_RPThnkD-m zhc93JOALBJH~#VOokaFmILHr;ES}Kse@TY_hpYd7`fcIOQ8Se{TH~hcQ8T-Dgl)k# zyxocMd4-OZ?!K?*m42ju)Y?d<(2)ZnzaAj(8ew}1&s2Xk%eLKSmNgMs)+?NqxPrXU zSxsM3Rrd?3Ri5jlj$mK=JO`6tTnnuVu@wrPD0Tb1PK~=UxQ+Fo*0$K8C#J)jJLanW zK)+In=SLQKiZbb|!Z=0Fg(&RL+uMDtS)T7dRTll)3%4A2 zWajAfwS$mvqwD6=^!@-|1tCujo3_(*O5gU3fEiG$|3-GAqI>2(cEHiE6mJ!-m zX7PPq-f?6Ouqw8h<5_pym?;(8Y_y*EmNt}8AVayb>D-97ajcdVZjs51{KEYWe}&Wk zLdi$=HnBFNIns(4U{s>G>Q_1<8}QC{P90j^_a45KUOzG~-yKz_#12h&FQBA+NS}Bs zn1Si!)6`IZPxw%@SXwyX(-w!We=X*=u&azLm3a+u2=)r5ZacGiYgwRk=0!VUu|3JY z8;K@Bz7r4#hajDMjTa`5h78?&Vi3lRQ|MWhlt60{#%F}`=d zeB%X-nm(Y7{vloDUF=C&pr`E6y(hACzT$--|BAvX&l;5M9YKfhjoEOTfr*Yto+GT0hQmBYNG=Q&BCa&KcXG!=_VbhSg+4U+sbqF!KH|3abx}FS#DBR zqxqMZz~|95grDXUEVO>Kq2Qc&SVc_bpwRG+-rcBnFZNF!DJ{LUH!+iM!gZf|#8OVY z;vQ)vF)iYDqd~6FPJ5|LH^s}zY^w0=1qTjpFz=1<>^U5(d7@0|9I^_MirDOixh=9% zU(%Qfl-!wvfAyw3+0_&%@js~ljg{qn5xUB>0LWw-OETz7ARP@SvjQ z$3Wb#iY~M+V+8UJNNZ`Al$Guq@A6AU^|p4)?1bfL5B1QEA4o>P5Mop~5}xj)QB}_Q zI+RXEMH-9}2Bf%4;eHC>w}V)2m5qi0&TLGkd+OPf$4!9ot?lcL5~%#P&xp@a|45J( zvz~+)lbC<%ozXM5XX@Dk8GUq>;l9EJ*|j)#sqf%q;B`#Gs|kwOd0BZ#lxt4DNX*is%S`|GH~qJ7bLc7qVo$j0 zBiB+2JBRQ$5MJDJjnjVh6%}ttIG2IU(Ac~ZC7T4vBq^0``NwnbnQ4@_*^%6eKnE8C zFKC{1iMK9pE*uQ0X19(fmg_C`uGOZ-^IzyGseTwct^?3+m~Uh`iiS#MT?;a2Ir9I; z!jYk;W)AXoanSNDwC#0i?Cc0=ZZ5v?+0*?}+iUAM(And1-%C&$c`5&F?Q;*wG;DT| zdKg9He?qtAKEWVOed|@r|L#>6*Kb-KkFtN5SGM)Ej$Cmo2+Ac{alU+{^zeMz!p;5E z_3Y?2?bins@Q#m~>(o$dbsOg48t;fXo4pULy3+ryK|}FV%(nzYU#?wa)N;z&Qw+gf ze-J2LN#K)JU*blehC<#@!-$bb#l^~rTrdyChrsfo~BS^C%cv62| zQ!!vZVabnA98S8Zo3DRZr^ecnU2v26d%u#n>Vk!$*fXO^Q|RpguB zSgl@wARK|c+ND7ovC?;PqDISeDLyOuoEqogFCC0MdvjF_7n&6nJN2Ud{E35c9erua zviG*59r+xwNTrSep%nvWgB=n1?Yqt*_ttq-q;#gYf_~c2&a#IE%0>LpWs2$}R$7F& zDGscBS6@04xl`v;H= zFbJC<^00qn1x(OB)^6@*^=f`TYjWb5e%=*qhrF2ddlYH9!2^X|^_i zu81ii#hKbLiOY5zuwe#I&W~`qnH$+!|Af0!5>QQ!X}6=u@a&{Da%7se6A|AKrVj+r zx-zX(Q{^BK3Ogocqe(w%>>cBQ!fBh*jp)&G1^GaDqq|%ED|TK>ur{BD;U_+2NAP`1 zt<#wJGCmI_oJ+$H(@X27Oa1GG*J(?Dj)tcF4 zyBidr2JR|4o*mugX$6;aT{0|ls2iU#gBO#sOEx!$`6^QAgRYlOal&}q3Athw@`L*e zia6ct>t}q;>i7*#r8o4@(d2zrsNP#l{)O!0J~=}XEt9y1P}x%)K?-^KC3+afK6)l% z*B{Qw5^K<=(rUt5L#7?uAoOLY$F6tXvJ*+m8co~mJwdyaT6+ZeT7o=pxUsJk_+e*I z+)rBc-Hdu@8*u~TaCwc9aS2a_Lb#!8ny^b%#*4X`n>8INB){`68C^e3`~+Y$JBNwz zgR{tmI3AZ$jdOd$4Rf+S+$B6~q@mz9R_PkkwnON4z1Khzio>RF5?T|ZH$CIlaJkiV zQkP%Z%WzQb%$Gve?z+p--UlOP1V1lh&<3Ywlz_)QzL5<2QVX6vY21gC3@+Ab>@51v z5*m!gK3>Lp_?UMIBzd0wMEcn4EkY3xf8fdr<-|{b<$oC;p_lL?G%0UZgAOLjz4^sP zqt0kCW;bf7TJ{9)zp!W~5p4}O?e6^;!#Xcz2*Gy(`**@7$%hx!)M;ol~;!=7mCZ>(c#YW$A^(tiN=s59k@jc8TVJl+Iv`ZIG8&PHdaZSpA z>HU67`R$Z$$RUZEG}(Ms)JhF3BQY_z-NgP3^yW}+J2#DZInt%qdVoVP9%;lHs?N#3 zhDp*dg`D=}MV^KWoz1_o_BQm$G3jS)QVX!sJx%9y`T0_UhH_!7wtBCQc@O4dNd6!^ zsAG0k|0%R4Q1nbze!-h|V~c(Czu8ul{O4mnKUaDvwVvO+*w?nA*qZ&WXE9U2WF~wc zskq`|ckfPhEomygUrvK0n%QgV=xyXIUk~Ypz{Uvs4c80oUw2`VeK(ZLw+gu*eq%A~ z6M>Jv&)|8bQhjZvc+~m!>@WKCkkAE8V|JvhUo$Pjtx)+=84 zLMM|I`>ZaX0eyvaZ-DkTKmOgb^w;ZHgEVhRH=czs=Y3#RQxN44e-&K%W1H&Jzk}NHUV^-KO4gT8%5VmQi0=Gd8tFqU)jw36 z{-KxjFFz8giZm}$VanOs+k;Iks?)VxmCOy~ohlZOH+^?Q3Xl^Gg;!$i9Nf=nyzlSF ziI#{~4fJI77Rd^~N69@N?*fpd3}a6eRbF(oP|?18Zf>W?Wnn@Q)w#;$7HZqEzE!vW zB@=7M!M>V&iAoUL`rczmb|D(`am|T-?S_N)BQNhs>iO? z0LSf4o0VdQbFp-a#=+^wDpk`X86IMsvZa1%!6&}rFW{d}-l3tIWfr)F+78M0lp=YD zBl#r6HW#tzw>td{Q`_{qI+2<|T(!9m;t*dGwF0lQdq_Ip1bqRKL=*&fpfkipyNjH0 zmmqd_gGVvta(nbAO!=xqcSv7uw=e`3pm>zho{Z32xw3zBzK{=eosFoO%ZdFrqM83M zTKAs*#xmZ0UDlEi=J`V_cg4z}nLzZ_ZpHjkn!;!K3;mVb4QSEqsYaDwy?fr%{k5u7 zW+TId#7F~DpoW3FCVMs07tcor0}qTN2HXzgG>bO`qVEDt8|CpE@P#}l-?Avl&OFa!W3`N_AngaXz1EA zVKzNI(b+-Ps>n^E%iR2Uc?Sl8i4M#Hpv|5xtB-PU9@x!2`NTh&t5Y>?Wtq#z|9zxB zo(vd))UUA*hz3Fi2eH3l7FAr~zWgI0Wz>dVbA;RguT{o0_98-p5Y6sSUq$xT?(t?d zzfCRRbQ_?Bc?U3K$mXt_FgoR;y`ZwM_E824Dm3n4$BX-<%R1}XRx#hF8HtWB#4OgY z1iaZCo>(3{03;M-^v`G&JvMl``+T8jLez>KKlPNQf4Id?k&5KAmvl};EY(R4C}AQu zxNe8e5$@``JhVc8CX_L`-GquQQeAj3Lj8t=P!vZbQmx8WpN^KCzms}V%uYn0X5@eo zFp;tehwWW5g2MI&Rtgr1-*g)D7w3`*Ao*UGG#luCNM(rYJ}U=d!L)up0A}J+*>NaZ{Y$65@%&_M#N}F)vCf3I_YPTzXgR_R2Xa5AI(hD_j3E|xU!Mu3Elh*UoD1>Vt4B%wlEyRWNk&f?CLw~@ zhA%a$-knOfEIUvb;3WeLtKqasCiOC@Mca!+ox{7?HU)uV6=V7u;0=^_rI*~VS{>oN zH$Fr}lK4xl2kxg8lQ%};?&{^;M!AH z|GvYf2bJ}%8KVRHN?X%%HP{z)l2Cih4y?UXoO&q+BD)0FA1VFD@_MwMFU*2;W0iTP z#G457@NmL+KN3JD$EWb~L|jKSdK1^vtsz*YC=Gh2-*{CySMO}Pq<;D&pK_z3UqnT} z@y&(D{H`3jYbG$n7D1ibt1vAsUBsewVI0-;Oa%>$N#kmLtV?ff&VMpFsntGGcB>1U zL<6m&ptqIll>r*BtWnNJTu{vlr;_HP?i(@sMBx@=pe9KlEsR+;?HmWzVyq*iqaW9; zrxMaV@+J@4(xc#K;RX@ej%w^lP?KKD{TMeUChi|*29Wds=DOt{(kSDq&w*LhshNlm zn%+FqkNqLxTxx7Jv6oLoA2ZfZAFU@xA1y~;pf3t&8!m$-ys;5y24JMy_ulU??6U$z$y$xqQkc?sMCqGEb*34@_!{$@4$ib2P(~8DgJV%rydJCavtSbKKbc>u9{pr#qr&%zbraw;a>I zl#c9Qk!wTt0C%7nA7d8|1oEsL=y6hualYHq!3EoPpCI25Tp5>6Ce^Q6#n5BSqU~6m zx(~S1j0vc}-KECGmdKQLlc0vdp7|GUJ-YexVsd5{( z{*z(h4+?}t?P-vc)Xsdn7xjktO}`paryCkdyZV~yXR9*invzNgmf$VBRM#SJe(}Bu zwI|~owV-w=sBRC9)F4O$$)W=rc+!+tqn!@Fe{cYo>hoxTEiX!k~cX!@l6S|tRZWc_F=cxekG-waABc{H#oI6$G13!^-+|a*Hbxh zP7BRG@AQ^M6Ntf?y)fr|CCG~HR=V#s<)-3i!N9boM`N~b!3P~mgn93vc2d@bWqe}f zd(?qjCU}+p@*cbCPdTd{@VYj_3fy8@s&NhJ zqjzyv&cat6;1RnEgM^W>FW^tOpSL|LP<6CdN%|V+)}x0Btd=gV^eciIKap;sk*wyV z`eitjm@Ku%KSA=RmqBA@9tcgDf&D84dklbfR&ID!ZZ@wWk)TK+NdTG6_oG7f+H?|A znevCLE-U~RJ?J|s7TvutYqrtPmatY>YY67th1!eIc5Y+iRsb$9&<-_!hJTZmeodKl z!-ka6kSEbfBSo>=B4R!y31Ba;w@e^1W;xdhD)Sb(Mh}PH|5A!P<#JXn*D`!OzJr~| zInVD%cLET*|G3f%fTH!A2K{pPK87qz+ntLbz4LA%#?raNi@hRK;*Zdb4q@eQF?EGS z;lr;ZS06r>koSSsVOM13;0Se8zWjI?50$Zyf4Y@?_aS6}C=$Eq^XgsQMl0k;ODmkY ze_ER7uV3Ju9^{>6fCd%ORLTA4M1;{P+Z-I&3RtDYJCWEjYE7T>HTY2v4ajNP5|cJo zom?08<-$5NY7#2S@zw}d4k~3-Mj49r$^K;P(e?8ZLWaG2Cc$BuEkE?b1-8&h_xazh|I9SCR{^8-2IXw*RZ*&8{JKME0tg~vTE~b>2vD)+c6xj+FF(sR{ zrJCcrwwNrYaeFZi5Pc}o?&tuK?D*Tu-3tH-sk#>1-l;plnRA)|EWF@=Xyuv_5 zO5mzS0r(wc#yv|X9P?g<1M`Nz44IL?n&f?GsO>Pi%&402*+N+NhM|T=Suc)Wubnv| z)nSe2{DpYDw@7#fZ;;hEYaDJ?;x-Wi6}#)8-Hj#OhRA*po_ZnZuaWiF8~)4Syal22 zr#~QhXi04H+WBsFhwVO}SYUjLBi-ffBB7lj<6?vGYYFZm+4U88)oMgTw6o=GX!Z(Z z`zM{RVUhVogfHP=eAx9bzFp*Zp7*3j?d>H801kqHjF>(RqmQB{4-YLt#fa)AnRfoX zk!TDA5>?h~!&;deAwzaKI6nPzZLh1F(C&!N=dV7T_D{Q8=%pBb3Hv}$0fJb_v_E}O z*r6$sv}9Di4R{qkI>F-U6E((=T4)}=$Mhbf){UdeTkif@ z+32rM{MRs2MJ*P_`*MQR3+a;FGBZ(+S^=lIL&5D(xW3EFO4nu_XKs2B^>_pIlx2(< zJ*x*$7p8amd0rskSg{&3>&x^9Ta-|E@n@)}sA)t@ms6?FdvN3k(L+sK&Ap$S@m2L@ z6%q5!4Jn$D426&h_U^{+mhq^dGzy31zV}CrqvIXdAEK*eQW!l26HnHI|6~@02(A|K z8#QZfCBO2CZ5DYDEBorm08lY`Q7C^Gqt6y#$N+ftMB8$|T)@niv2n*CX?Iw<@=g!N zEcxx(T``eo)h)Syv`UBB!Gw@aiK1NE>Cuxlji{*R*y-YBSQ6zm?R0dbVgF!;Rn<-% zl6nz+>RVYHoP$yzhy+E|)4N2?KBa{JVSK6uJ~O)5!8VmHFOBTt-s&E2LJeJ&lOFQ> z5+LPvMNKvultOv&eJ?G|qsnlXbGR8CI>+&YN=0V)+_48~ImJr;^tgPAtjKcQ9FqP6 zI=AJJ>|xoTBY9+HMeL?c%H1AT)P{0f?&o^P|zB zymoVwJIr^}3%})LY`^kv41c%HCI1{ETtuUf_0G5XPd*`2|DFyuzOlo35Eo4Oi@UM| zu%~xj(Qb4oypbnUB9T9`W-b_17F9ZniH|Ys>zf;TTqXL?QTH!1 zN{-qR9z(x`LGLbj`~v8f&#g`OamtTcJC7gBLs@R!-I2valkMH~zJ!h)YYc`oYXw5r zCVOWJ9dI_{sbt9ceywwK=>KUJ1Xe~eD0XDT84vX*?hgv5;p)r=n!M;u;n{A{D@ z=Utg{w{&Z5!69F;6l81>NfdfZ{TQMsamUV_LpbRp{gcO)1n|UrHh&n2x>s@Hc#dq* zNK1Z!>2G2Na??u7W!b;6XyWSFS&<6%Ia*et@}yZinc=a^cFU`5?9R!HSo315Xt+f7 z6OTX5TUK^@V&UE6uZNRlWVI(NDt%?!3TF{Z?B;|vDr5BknG{w5f>&+Ijh}MzS4lLX zh61-M!%m&uHIW?%RK{EN?Yc+%J;FadG02XQ4S_8??)vpjImb5rw!}%3;;rW<8z@?R zKd|$n)UnsGJiUjHXX}vPJ3A^eUBv0z*H!+OI(ZhAdhah0|6i6f2Cjag6q@*-c6kpH zDHLY{BrF=r8VA07Uat&@`z$*yNKce=1nl5N5gOFMS3w16lAt1uIJ$#Ufg~DAgPF9` zXu*g^RZb?{hBXlEPr<25SS;LH=_lTy^YypTGUjxaofLT^shI8KrKd(^%iI`IRe(xC;X#f+Ee7iqbpX8>UQOXnA%?Un?kF~Y7qiIG5S$Otf~jj zl$^R6OtGg%jt4UL<#J#Nm z>^Sf|&N!hoG&K6S@`aa1qXkCsQno{Y>Oy1L>i`{sGeizS77$V1yp#oo*+fxpB60^A12G zJ+PLsq#7X{VNn#!BmlA22+@re#S*larCZ|+B*-S7{pf!7J+9V}1@y?IKSuJc7E1^3FfvmQcJbhj zL+G96?9>~!wOM=iOsyB$jJAeaX9~xQ!W^yo3u+BxRc~$&a)H7KeesBD#qctB+tr-jN!j9uPrNvnnYj=(A5$xCL~ey z?$G$bXwA9CJi}h|d_pDKYhCiP_NYX6P#;k5z>s=+6n)=hr#>@v7mpvnS&qjQ=bF=4 zB=yA9V^;F2qI?B;GD5V0(Up;1dIC99Q3E2*ytx6DHQ$Apa>-q%?lZ7dr<%6m=XFg< znSR|@6zEUsz0?oQeQn=3L;Hnn{pB0Dp-kV7$`tRP*vbQSWvHK~%Ckn|HZHbon;5<>=SYGOHbjqv|=+T5IM|X%8!8pQm z_KZ&fxGPv`{+yR@7@Nb*S?nC6)LXUt&{^=37#ktu{l(R`*`CU&(5#PhF_%}tl)0i8 zhjNwg-|r065kB0v>ZKoF!xn#_?)RKZTi2s3ahppEDQ3QouJoH(-qUe`(VCcD9aFo#F%O3zw1Qb(&cfhj|a@4biZw<%)$DBLZ&ZI*$WW zn?Ra261kV7h1Sv$s?9cG2%Tl>_Qo7HU7)j2M+*?DzY*Nzd=ggzX>!SruNAyXWC1Nk zGj-b2Jfmhq@LId}nJ?HnMn#y`k`nV!P1y$ek|g#mw7teEgAiO z0yMy+c?PK6x)nbkN|6eSc!hc%iTaR)Oc~GetfmC+tZ`)TT0DJU%`c3lID9 zFo*yO&CYX*8)x>`>dcdE7bd*XlIxTv63nd{V5RPSxPz)|$*( z)3K?+?aMY+^U1XU{IVQcS(xxGO-^oG&n+}<_3m0!akfWby(uQ3LqUckt%@a!bFREA z6Q9YbD1d(eq} z?D%J(6y0|i`sjobw(UO)b)j3!gn)u|^pa{3PIEK*9Y*|q&wZ?LP`b1R!m*+6OV^rf z?pGUoczXTqYa!0OSk-f@J@T2v&ZBd2j58} z{)S0Te8=2&1I}~B0SlV-ONyi$+hOdWq0KvVw4BeiT2I+vJjAv$C@`&_oMFmpa?~6r z#+3CT@62AMP<_qHVF3e^j-%54`h0}q(1I_;;}+8aW_q7Y>1rXL!?crwiAM^V)^#&> z@9`6Ri?&Z1VLMyRnc)Q9gd!BX$T(D&B6hx+NgVl-h@imK;2z+{35dJ9W(;Fp0?l2d zJI4f`TdWE_|B~j$g7})RQ$hz0W*F|Fnv_O4C%DXD^tZ^K?a#_HLj@s}o)JofJFbr9 zh~kL1CUomOJ9(8GbJxO9=t-N*-$oX?DDi>YsSa zZlh6oxRn41-;+1H#Z4b{Y@;&*C<+DY@e#Z=Plhu3=bv$@~424kWTgZJjB6e_z`G&Q!H>aOZ ztAyuiXKF=aRqO7kzP3PJd2-16X`j_od$WrSd>>&Fpe^qMzGULTeLFO6B(%EKc(-C| zVR~1B7tCQ&+sVo&m_}$IupE`M-v#W1^-vA9D%_=X^a#|qe!a}BB!T=^WAIKd=jKV5 z4tNEymH@cj(Ha?8=B~F$*V7sPE#8_Uiav3U)rwQYtO8Z5aRtM z04C)|{x~h;%k!57a>`OP_H>o+l+wCB2&O$^v(^mYs^PLg*~HBGKKul~q(0&OT#J#Ovpv1VweJ ze&+!o)+{|A4vHw*i98*z{ZYPOEiKf0+C*)o#Y4p!Rt91Su(v;y`{0YRdFv2=1ic`A zHO-M2qO~nEA3LRLUZ{>r@kc*j!-p9zHGb}RZ1@db`B{gcE*8>fX1-W!J!>zQ85i!p zTT0VK)dPGxzT$lBGDz4#%>Du?HxHiDxho$|;1kdzkaKUVo$8?OcwK7+rGl`E>{!s;jiQL?3VI@(?PSU-GC`;A30XCe{|!IrR^mVwc&!xzhE7V&t=I(iTuPA;wJ zO<&p(7*e7K9=xJhsM2XuF7JgyF9meY9Av@wEb+0(VfDY(rSp*!9@dh^8`b`&^}5Nv zx`g4WDZ_kkC6Lr^J|8Jpwkj~0rG0JW<`xnbmQ8|!6?Z=+qP58P3E(U>S(!5SU243c z^(JE*%lTTR=!bx(YGu24f(Jo3?wf*F8L~)C8ux251CK`aus68V-+Bx5McnXvjh)2q zBUO+3&r+dTpw}3;K@rjsX1@xX0K0_sIo!0u(QH_X67!QDs+r>CO>(D^zUk^GYx^T( z(Y*qe9zOyWwU#u}@lUAOB640aRdS(prb|qGmr7wcJY2wXIX3#VrDn#8 zkXi{~a?f~@*1C@WcnPnz<#>z}6;;9-Z)5 zs#8--D?9Y@?~cISp+%;RFo9Rf{B6=|NdcsKEmoLW`wI{0?@>eLX?ur3&Jm&(Y;|20RA{DIxJwPJ8RP+~$)%glT=s#L~F5+x?O76FH!A4D`$CTsfb%ET>fq<*@im?N9$Od9>Z zLcDc6Wj1M$!%b-DK&vJz*iL!a*CS1yN^ZD=ekFR@eP}kw`6n=A>+so&8*f5*Y?NUS zc(eQTs47Mq{5W|HD=lk9MH3w&ulv_KXQ6UlIG_YX3cF5y59+d zsVWyAQqSrWG=dV{U26S&jf+@SK6Fn1m~~A#)F$b?SCgzu2MFJ=vRPgS7w~n6@7&^# z-da|4qj_Sc?=Z=;I&aCn*Ntdn|DiqkFlk>)x9G^h1#0l2i98zx+bvvkwO;v+<=7Y& zJGGN){yZ?X%|6b-COJ=R2_Fj8j$XhEzX66^Cwy1@*om^Ha?p+%mLie8q-r3xQybDay7i;|vYa%;{ zpXar`!1hRz5;K+c$>7PZ7Q_~<7$DAC_3qe1q$IYagk8L?2u6d(uXCyDX9pxZE#4_k ze>HCAig^ZaYjKXfGHDS>*lbfkbGGHksN6y2ijqj!}IPHerPq+L+St60V^W1gQ#p*vrN zJQ}jR_>P^5ub&n9g>qJ(hv4Zy{;*RumRH6)I00>A1WC_o-f6U{6j_GGU$8=v@WyMC zg5IUn)aN;&UpRV%HCQE7A5NGSM$C%0qXeR44P=DBK^rzQ8ZYTjHu$_K5lp10ua>b_ z0K|hSHg@4@->e06RnZYY&COnX=1hAVIRqlQtnJ~j$+-wpoML4q5b0x|tr>OHwlZ1Q zK-Ryyq~jrThfij!RE$wBiKT zLZZNySwWyd&X08Acp;xcL#fgwxi^wq7R+T@}u{X z9RjT#ZgUd~3p5mn&>EGqtk35Fuzt*?k!er%+{gMe!X)_W0}b)d2t0M(?Uj!cU8m5dY_ zcN}?-Rzm6%*G6bdXy^s(O_>}M$i=W~OJC?l%HpExYkN~_TK6b5bEI#!tFvHM8EeB- zYtO1%dbY2Dc)va^HPqwgtg}q4bwZk}%>XCVgye}i>fG{BjMZm{4JKxGCF-Z>hh10i z=_bZ!O}6+*ST4e|*K(+fiZPn(fh=adY^BlBqi*~HS9{C!KqD1WRqkl&q^#fr2oNg6 z{&=*0-iT`MRd47h=Vx|47vgoH5+<1}e*9y$1kZjl4)uvKJRA(utVvw5atH>3lO^0$ z9jLjJ`o)(V!37HV51HRSPd(%lKa-YEd2hvrCCP&Wa@!ixp}6%wW>;f34~P7tQ_tFR zBrRV}PhX>rzSF9K@uD#G2XH;38D+OBe_0#=;tQ&^`6%R0Z+?71%s7&=!$e*KUmMa5 z^tY0Tk34Mu6naQQL!S&Zvt+o=u!H7udwCT?zJv#?!2ibe z0nV$0Z<*O6fCm$iF?`MBIr&JMG<-%-%+twcps?Udq(uPF!TenP$B6v>#OXEPy~2>-l?; zuHcub4-K`a9(_eZV914Y95CZ< z^X+z78D!b_z>tic#80eQEtHK;7Zw{gyznk zNgOG1R@kALtoH2UtGtJTN=aJ^Y`*#aB}XBnukC7SH8VXE_~Uhn&c@s! z-qf7ERTy@bY2|<6?mdH={N8nc5CoAfN=rg7BE5H{NLNsl7K%b>l28Mo1Z+r$(0fsu zNDVDOXwpM3QbUyzdJReu^!w-R+50zVpS@rF-<N{;p;;6dgVvJYTIgVI4sHGbmv#wbwH-myrj zZg6gd#miw{44E(G_?_7cp$@%eEe-mQMm+eF(s@_JAw zKxEoC{{%)!K?_az%;AkN_Gc&Gt0;=;mBO5F;rk|{_A=MeeETreG9uGxWZLbM!p`GI zo1KBO3;d`)yQ*23P1t0R>}xrpA_vsQzSRH;)G)9P3{boHB3VqVw2M zEI6%>`_E?&t@W+Sty^WoYc!9uZI=LH`bGWTdS|JR4hE!pt9yzzd&DMVR0%7G@RV*0nMykcRF53OLU z>ruAR0t#$xGi0XAlmNRi$kmMJm=<$4*=Q2tm?-$H!4-?j6sqqXEWcm3VAK8L+=^1p z;ZffMcd_JkkJPo9_!G)vqJ-QB@z=Zk z%B}$$>@_n9_3kW@;xetVT<@~BeJ2Tb*X^}iIJ38b(djjKp~rVeI$r)? zhrVk71erE_=cBlXc~_BO`HI9_w=q`KF!>LUrZSDB9-hZyi);KsQfYm6*FZjV4Lmw5 zYwqSdDbJCfS3Yoyb1SZ0Z$SEt@r&9AAb^N0$}@ApTZdg^%Mh%0!Z>3v-f`@(#?o4g z8szbbl>p>~e^l`PwJDL#b_&aa zM05OHOAenb4E^x_S)Gt+F&}atlBxEct*l8fL?sR)=n$@*tDx+(n7=Z%ix~O?{j$Wu zO{;6;E5xx4?c9*XWlAkM!Bb;D1Qq%S9)qUJp1y!^I+;D1>6^{8_H9rIt9v7*N4*b~ zw1f_ciP0bn5OX@&6ZuLd)mhGJw_nnD{+V0Q09_`}-}U&gLMsM|p@81d;M7_kT&{>c zEZM5_X@n>mgN%e$`uh4k#~>SMBZE&6q~b;9iG-TaCdk6rYkQ%ow99+)?#h0JL0|qR zR59B}G5g1K+!`PK+LN4x{hi4tU!32l<*);nb`WAZMw?iy8wyw`MRu*!~AbX9ASa&WHS;^d_+tDgF(6;$}adgHK1aQTg_zqUur$aQdm zj#E~*&f%!3vt(p7G}6XfX+KDdN!6^+5W$C>)6yCF{eF3I+{LM5^Yzo7OH=dO%r<5v z55^`jr-yYBEfNaEv@i^z@VuGK(TAEz7u-@iatxG|X+89VgDLujnU(ew*`HZe%$-W1 zoDfxQf{Yj9q}R2Z==rI+4nz~n4^r6jt8j=M7tOF;sMI^hpX#q;^3&e*%JCs1?>gX)o zZ2dsh>_*OErd&fpmwUu}t|iP^!ueH})m_x{^KELSt82LG(>1WfS}RIqtc=>`roVld zLL5AEMux<1^ZSg07akVJ(2?cD5qLBq;hsbY$21Ji`LYMRBJ7iTNQd>QE>dmX%0l*j zt<9e8Tn~*7&^yjhs08Oach4nFOPND5m<;(GP~0JXe#YA?YtuC4;9jpj-9w)FvH91{c0|<%zDS}1Go~iF%a+ zUm)6NVI%uq^H^Hu6Un=(&V$_Z#_k~7;}yD-do#~0vYxET$yq7RuQ6q%L=Z^Ye^O1FU!0ah%MDE+tng1y1V&GYh&dQK#v%i_6Q`FS%v)OxF^LZ+68JIli zD$4A%333sVyGpOgS=|<0nx7B`&x$IXy{b5b1cj+hinyEfC^OVZ_AOP?(OlbK$?B|z z2UiaRx9ZNk0+4s%zE+VnoEc#PIB7!2Uz&*Sz_%#lC=LJhjk6_9bO1HxcXs=Z;T|)LF}R`MF#s>~BjwgdUkpW*OP-eettfjpb-w zfBS5r_^qj@6F7QlnwoagGa2)dVe?kb0Fe~Hb?0HSkne_L1aBtqFff+>_4su%v5!!Y zfOIKj$#8DuWtZo2m!t4R8kOv2wdWN3gznw=19{5F)cVMFQ9j>s)y1B`xR13MVRJ&z zXkwNmVWjdrdXE*7fTr0;EPgm`gukN;f3~C))#W`TQ!?s0c)|4^m8k2IYxcgzN9l~b zv~0;4mpzJoD6{FCT+VT0FGxd|+rHv+2Y-o!Z~rTwSU8>u9AHmcvCP{f|Hd1k_mpo% zq`XBs5~soBYRn-M>U2vdOy&uz^B-|Br%3jMhq*HFv7TNK`R;}QeRQsdf2=>19 z7{z@EwXLr7yV==8-oS0l^*UDeae1OZu5;Pl%&f63`IOUOSSf=|RFe-Xm?uJ;%JoBz z+KC(WngCe(slH8MFMlG^!%6LKNXcyQIOZwKsc~eL=0@){>k<&bdP@vg@wQD>xxokH zdOHTPH_(@P0bnvj)o(IP$Mx!y23I%x6_|t>ML9{=Mpn+p#SCNjH}Gmtc3*cX02+U*Fy2kEqQaT@$YsvR?|Ded zK=XDR$QHjTHRU&S;yeH7_ZY6STk6$XHs6&@TyipG0qHFQtE+T*X_o#W$!Xs@Ezr(e z#H}wV`Nv?We}>WoH%5Q5nI0s3Fi7<7?Nwc8nyXiz-twkp8ZhcmYE_$LoR2CaKwt(q zHHH42jRx!PH)2lzkleRSj=g9v+nC9DwwHH=rdul9%0Xm-Pwg^{9f!Q`AmYS4v^GR{ z7fK{;Pcu@FE)vgq7@j%yS&G7?m----wfh2)<~hoUnUI2kfJibd&+{<93{+7I9B%wZTXx_yyb^zDV3*x^##4><9YGGI92QJaa&z# zK=vv2kc$nW&&RmN)YckU<}Gk8*L?t|i3xDw{dzemW=zsQ>FZzF)DWuLm5nVL&()-i z9y4h%%7u#YMl;`6cR`gX& z=WHq^u{u#nkU3|2*b5rIM$UMp|4o@kHTL=22DB+4q|yDal2Yfs`b^J9M=@suJdlq* zKy(8ynCJ2G!E7__8)FFUjp+E!)jiipZg_9*)&+2Duqh6Z+z`BIioMs?vY&A#cUD`Y znyl=f{l!@9>J#SVIb?rKjaDXsC9LyIk(jHi*g8sPb)E8MHNu5<%P2Q8s!;U6nbzw3 z9#@k4=0Q7wQ_ftfgmYFI0bzZ_UmV)w!i#99 z=fFGgMxFj9vV5FJa>#;TexWcc{A`#15QXpwmro1#Sl^Qx9#EugoXdA`*8zV_)fNii zjz`(Z2d^h0-)+get_6u;4>*GjMxijAj9lKP67}eF^iMp3RG=pMWskXbINh*KG!a{A$Gn{;7@j@JiFL1zomo_cyq0V3fz4wfYLD7vEQ$`6*tgNz zQYnb-`lh=Mj+6`lw8*|>_oOXzighVq(ieGT*mu6LRUpboRMDNuN@{He{ceO1Cu@MY zl#)p~Xn2q=ZP>$v#kIbbBA(%ND8iOe7_#rcD4l+}$?-$YRN+vsAu$0$n=o+9$7bo3 zXo_C(wceTLWXdLgoaLQsuGmVHpuH1hx_WTu%2Fh!Z_DBFXe(bzF>aJ3C83EG9Hbw| z4b5RASFUW|o`HVm@fbPyxF)GMKQ)r^VF3Gmt{>tUTse}I?p3P|SWSsZsPwVhOe{v1>SF5z~F3Hoq#+a4$+~#?zrn^Yl-8YfiCn$fM9EbK#$@W|C=t*>KD<4`k8vve%dd}e5Y*1(~58Ir8IKC40_f?{q6!>au|1&czeTLCSug3~t8VieWm=2fcM%hX_tB8k zt97O(y_kw)+i+`2>+}~1dnj(w^EH6y$dJ2TwYbKo{!vAA-tNFe1u92*`df?_+d{p` zq>z(aA?BQzAXtSw|hzzz?TI7+vJ|7+Nh{)}A88vTW zgzZB!H;|}z*Ac{t`L+LnpZs^a?*9b$Cnf*r^6nq1e@NIj{fCkju+k<=4K$L?qRK{V zUSi6CG0L}NK25F__WqhV!2N|nw48IU^B}->nVADpn@4yko!2rcgvk?9~aJwSU5r{u4d1uAgb9Fuvj=1uLCp~+1uI5liG=&@ommM`o^Li{-gA} zmuhod-N204BbfS%%FiG1VSUGWYw3%O%H#a=Gl8BQ&iA?%Hyt-ufuaiwQ_TvJbz(UA z_vW+k%m97y0F{=}F%UZmiQ6I-GdlL9;UOW*4z{TbJd1)6q;mUMe=8VRT<&$YyW>%W zh8dUCr(3JXS->proRV>}WwLLoO2hSLE9?q%{4V8tEDYvoyA-E{eMfV+8V?8i4D+o8 zcALC$)GWvxuASw2Ld^Qh?A3;_BCf(ax>8@QemzGDMt2{<=d~-V0bnmwJ)eLM!;EYw z%GthO<_Eo8XiNCENOyGaN*jT%0rg$I%jFV^NG zKZrh>R~-Fd$`b8t=6uz75yBR|@uHg6?Dh^W7&}MPCu2wq}cMr&j@9F6$lqGCd%}`197v=9d>(t@)>W;aJI#KJkID7k zKmcp|_V*7xlt(3%$5qXpAI1DbLi~GRcxY+y%nlW&7E#u-N>PauJBY0TOLfLF z7JW5BHsI$>3nThe9uhmGGfC)Vm#xxlKO)U471z=1tG=%IJ$tmAnO{G@=e~@;I5!<# z9NI-+c|JM})s!j32s`)LFj73;9RQ+YOHcQYCgTYr;L$b!eKTp}1&F4%`BYhIm52M1 zgk&l?q{EJ5RWvu1&9*AJAVo(CO#1b*uGcAXd z4D65>M_)Y7dhep{Wx;3U&+qEoWo13~Y(<$0Ut)=xKl;HlH!N03NxS-dGfNk>T*4|#aB>E9WQumv=2m*$M=5j0fVdlhX%tyQ?tfGKw$fX=ex8$p{_b<; z&}UL2V3F&`k;<;Da8@wH`s5v;pt3(lIeE+5W1Mlr?)3eMxm&+0VY41u7b$m01qWLW zJyL(SU*~%qP~X%k`gk>K5)E;F5u7W8P@1TiRFuRIZ4WJUb$q@g>U%Ero|;0)M2&BT z(KvzohlkT@X(sUjmoTwfLB+j*x-r&(kS>i`P~fi}zrzWfx?mp|ZFc9hmoHbnH%(!? z0|%rzjYOe8whzlOj+x}_y*c6_#$(F((cKU>h#&zj#KpnBm?v1BOdKA9mT zLJ#F-#f7_7WFkQQV$CR=bHVz23&FfLz{+Bw(zNj^CIeX5c84np@SP2sJ3In?`DEUi zH$W#>MqhL*AW~HrWblP2OV|{YtkQi>WWkPj;rU1Su-&;t{RfXta1vfoIx4a~C{eEM zt`^Q@{zLiEI})QwDUOxBx^`Uw&Gla2^)+f|5eBHXk^GO z&xEOWp9$fSGQshiJA4U3RRVvE|H#g+0KP}jFAvyEhQ{HYWmV1BdGmtPck90TKQ>O? z8`|{n(V)y=;4KanXaZyA4>Uxa-viW9dA^ilu2!b1!#~nu5&wD#)x(x zhOoMKt;-bpYlV_CRhqqJGr(z~*!N>6-C(O*Dria^e@}7cZ22f{$t@e)+3Coz=}#W!JzR@dy?%sH{p%7+9v+ z*8ES?CvH(Z*Eh}&*4@iNbvOIV2dP6eJfK4dddrjNu`V;uYHsX{BQ2Jk8)aa$NtMN) zZMk#VhCZ-C;=BKjsN=}`_Q?i(7kqS$Q=FE?{gm<_KJ*rt@@u>t+Uzi01<;|p(v7}% z?W`?}h&0v7_()iB6ne$e9%=R0gPp>blTVM`tm2t9J3!2!_>=dM>+w& zu@On#&cCEC*HoeeP`weU>Y6kPzRIf%cEXMuj;OnMj;sFWwe>B@8u~YJ-(Hjv@J-da z$+{=aABYeS`wh|l{1ZFJ@C zyBUJ-oYkvu>rME!eN2<@nUNp~omi`;Nz$i*>ueX$ibJfaGwFWn5j}KYMqV#*v2Z`8 z)VH^b;^TNTP$No}#%$68W$`)VoglGKr+$6bKNOLtB&zKU2FuzBv1e8saB=%ercQ4X z3;AUdDLJ!(F)iEZvtp+m%T024;kGM9D?)+u%6IMl}k)z&;(DX!P#(@tYTM0)^=XFw7KH_A$&K0K#$|NAD2_etchS#KZ^&(AhCH)^a!UJF2>QO- zqi@4O8)jZH2dF3(&XAYU)hxn;yy7u#r<1R1&hdO}HIlW-3Y2&JCUgUdyM?)oSaR&n zv0V*(y+kRbuxZ;}4d?h!T9I@^Frk}GZXAtVkU`%{kQ$|CY|!<)WQ_7mE1EBSrqbbXHloK@SFg4_%C z+>TW@-$qY4uL5KBPE_<)E(TM?G{7ulXwVD}OXGmjdw)Zh#T#V;Y74Om{5gXyhh(2o zU+mbdK znxHKgR4ua)N;zehuV$OaWtDK4mKNJJZYUMXll!2oxWt-9oLoEQPe&v$-|3vC(^)|N zE#0(9gx#}B(>%*l4}2H~0nM~5q;bKI;Z>CfhGr=16bls#VGM{{hAXhWEuFh({>w9^ zc`t=zjUkcZ3!l2I`wAzkhEBt!v%{1zqLdm<3z<8lo^!=(-u0Q6zxFoP(r$}R?eD*6 zmY%m|kurKEqI!Ucp6yM$F(wx%vsc+3<(6S+2ix%$acq)Ym(sw#&giOj>dQQ8IUX+c zB+ATgY!JlQm*q`J@Jn%-VzBdV*Y8+YS7y~l?x8B86Y21}pM;@E>z4w2GYl zo#|A?e7<+*3qUH$N3ehh-t)9^8TX8_dL1^4u}ujDIEi1rZu-_gB-|!n549Vul67mn zOuq=m{t3kL10x)0qf*T}I_twkMwP`{S6LTDR?VptBRyJa#mJf8FDdHO-z!9DsT#zp zRBBo?5v6L_>{_{{B}_Nq$fpO+GWu;Ti??`#Wv;^&r%X4T->%>mJ9!kmy@pWsZ(WyB ziqd910Uq_#eo{@MRF+m5hk*b<0zoEVi6{D2BeQ^7c z^#+XYmC8IXdZ0+bN?Cx}E9>~IIdL>_==_4W*Qp&X@{{$Z(;Wt23LoqbRPPy zaV={{_%7ihg@>Ol7`d(!$C2g+d0%f{=s{Y7vGZP=f?7dW?YpJhTNOt&j~`>h0*4p0 z7jhn?mMgROZ#Q+P5~XUZWA~D>PjMOJb=ZUtI`8lYcwvx8l8t@2Wy?JG%Ic+nu!N%k z%|1I1A!bemm1LEUD)brW!}toSR*2Gh-o!m2zb0nd3XjsJf5a4_Tq3#DIt;eSt z$CYH4LTYWVR`1Vlh2db^)Y*!0ZDP}->MYKWVzaLW3Q;DuDQC%*?37&fVC=#00 zK-x-sowhJzSOI0g$Ml0;HeSKgZ2%$f;QG)oTrz_OhbBwh4fK9oIO6TGO`6^4Q1HfC zg`Z!Eo3)bR^9E&W#3S*T|xaL%cTKXWm!oB#) zvxTNDcW~eEUAyvl*>Vr3(R%(v*r!o!pf1lJJ^f_W4(%Y}6YRXs>o%#MD zd9dY@Jc`Kj_qNPhj-=tlu3cHxt~T z{&%zNrNrt~_of-e5g2+?mal6WvHB-_(f;e!x+k-{aQOd8RJ=)XxQO`!%&iw1_>Zi1jh{T^>d$IkQi6XP z|GM`tZtYY3|8DGmaT+y-B^RE(WnHK*Gfv6HF%8C|nfAORG9h#xCUogC2ue@Bcg9*` zxSE@pQy|(Fn+F%Ejub-2PZ9`U@if6@*3NsU>>CV}v!~Wtz62{)1?id^$D=?0kR0Ni zd}lA#ME3ri4Yqk~*@_;e9Lohheq->?-F__BUwo}afJJv!iWiW{XNR~TU25OWrw>eC z5{llf^NxCZ=`<+U^5>81mDAS9$g#5j36o9_&@Cva5f7V-SrQY0zhr>F-wPz3Fi^|^ zf%*iln`GgwDaZKoB{_`WDyK2Oa9+i%qortX{ET8{W|hb6oR%?sL1(^7&bI+~^!ym^ zd5l}Pnq9m94J2bGoI!rnI^;#my}#GD*UoWH;#(R0kj8WA~ z`{Eu+qO$4;+@E6^Sp}0}XNTZ|(dpm{kJeLZsUiHaQw!o4= zIhMJQm+f4z9e1+Xztge{4{yfwy!J|$=0cm@2F1C~bf5R9EEZr3j~j-@#`F>MHMf45 zzxRGR)djKXX3Q4jSde?5KBtskBj}c3DCnZ~&i$Pz#8}7S*B! z?>d9R!m@o!qcen0Z-P@|@+HoQ*lz4^l?o_$^A4#Jh?I}aWc5H~q@y#)${Mn>b{+!I z>db^RIYd_@UzS`SXQ<$Y9E#SxzsM?4Q?*f5V#B&R;QC>aRJC@D3C@WR`f^}{;S>g! z9ENAAb8Y?P`UQ@*20;0vR+$}%@xv)YCP$Q(AM&E^^N_COC9nYl*??;eIZ5jzH${<) zO-?*tvv;?LTUWqI&sKi>?#u4yl+Sme4Sn_!-z?sz_j{X6>qqxawB`g5z&d>xNFr`9 zwB){)FCY|=zpm~a@QA9(vfYjFQV4z~%^j^Z^f}3B-)Hlmt=;^Rwzuios zbPgQ!>#XYSNHsEg`{cWu-D`vr%DK9V_eJn2pzl`ULT0pv%9qNT6yc>zcZ~WM|}C7pT9qvedy} zcN^&22R|1$_{-nN_KeY>#oeg25w6$Z;`xb!wr5jL_J3_8JrI7s;shT?M>c){nnKaR zA)uX@q`HP5@2jwfF^KqjK^7&sNT9LWuYtwfV&nlkTJ)s@@TZv+)3QI#cnTj;p2#%!&fF)6{*|rm zbe1wZ&wlkKXNghA61ET!Taeov_QS*P$;mFnNeMVRZfMfSJBlJkA6&e{QNYfjEwE+~vix+&V#JhIKqpcZ$9@@7#cZ8nYk*M_3CshKDZDZ;v`>4P;*vyrWJv<_3`k(Z%?WW zfuuo3jecBLdeRSAduN8{9Q9on8q`)kw*JXJrl{h0Lb|3LOm!Uh)JInzH&z^5F1%K1 zUP8j(sldzF;M}rBEArx)bxxYC1hPGooR)b03JNsr6IUiXj0PKj(h45fZ}a8NZix2( z0OT4$FfRkA_6(qwvQ|vMzNL+NE*zrLQ)HZO*$A?_ZFvqpHJ%sZ>hFI&^~t7I=W?`^ zZ)0Pez@S`4*Bc3-9PpcXO95cBy=r)cGl|-pzhmJ*3yZr>StqS+rMu2$=%S=sfp+l8 z9-VLHCF~^}*VMAlC>Cc_a?uqs+)~qwHxf0jk<}Al_4*~+jfajVRsdr>XW@_EF_cWh z<-LF!s*3BI19nHJU_1eEM|0b5Z&U5fyOXy=I5D&e!z>L`4=^w8j*M#FLM5eLS8zAy zgJXWBn%SdBVaf4Ls25)SGR{LMPOcQF(I`-`IxAxDF_Pb^Z zw>YNGO1ASsKRKr*@2J`M=XLcgoz>`SSM@qb$rHQoE__T*zhqg0bKwUvK=LF2-!uFd{2{xNVNgC7^d^|j<4p3{-;b=ND9dW~hrs+;MNl|_@Z*{0N~ z$+gvS1i6?m+g4nv1H733zU-Urbwqe;^(Pq`aJ5$M5DJjCWUbdO$ve{9ndN^!67)s) z1XH-*#!cd#`*&r0_Oyk=gbH+vGEmKtqL+!uDR9D~_gSiIyBuX(GjQvjGHGEP%Og5M z5-7GJE_DxsFA+0>J>DlB`fJt@Oqo%^*U;WHc-H$!%!VZNPXMP>Su#zw_3+rAgMawTCga43psr2A_K^itxbwV|A zI&&#=y#~(EN3SUQ^u7Gq&$<)+mTSx&qzAbvOV1Xgify|u_lu$^s=vebP8j5_rROhK zd#+xk7c+hec{n!Z3znL?;nX%w54v6ooyE*&l#!H!Lt9yEe($fLMbvyijc&KzseZN! zcH#>*Dl3N<$C)#dqcGaY9zqey|F~W^at67qQxTK*Vp_Ju^y73y2cK3gS;f@`zk)T-;qMI*Vem)7+c)Svm3Ki;h#L^@|r%F(070eHq z7_3I|q`GGmIL97a7}lHz`aLVRt6*(f%J8f?u4o4n0+{{2(kfqsFOs(#@*> z4@qQ9@QEJr@H{-})fR6OZ`A1__e-INd;wdpeBpPk6EPdNmTUt88j9~I8me>o*xVVu zo%%J?tV~N)bF7P|oZgPCks*-dM~gh024<1$wJC$*eta*FhY-?tU>|A)tQaSH$4Na` z#kcw5G`%7s@oE!OqbRsIT-GKYU$-FlBo%)*Nr~<)<44S~&g>~*qV+%!ZT4$BW2(rb zifK(ArnpIBbKR*mL}}Od5?KK)M*JcyoBCFK=r*n2HLZTZU3bRPXAM2RaI_EAqL5b^~}0k^nOoW z;JQ^DLkDk7_hzPiPA|3QOS8K>R5cRpT01>LZ${1LL-Lpo8)W*Hc;8#z0f}iP z<&6|}7-4I#(OY+7V#f2hK!6W)s7ZE6l(uf*wZXS6d3nO*q;wSzpH!=@et@BHFUP52SeV=`7wQrVvyx5OLCC4uov{!Qm z_n$eYau1v=@43b?+8wFWgkEky`r?-STV;!V)g-seQfz^pFHm~dp@LO7UPF%Ao$9<{ zrCBbzg3o`|i-t9`7r(igwd694;jwF!b!3go#ZTpYzbQ4uNMjM27(cuB4ad1U|OjSb!QSG~Dd1d4B?G{1l^O*Sy;emsFpNY(+ z{eYIz4KLP-q|@%T?aNmbEyC3v&pizR+2ccR4ewRVWdHRl{zE*=zY|IQ%^$5N*RK%AaU(A<&8McbXI?aB3Ec$ z|K3d42X`Go_&;Tc zp{6v7*E4TqD6ttGbp<3thJ=wfArtCJ1$A+9Td5DK6i+ ze07!Vp8;oaL{(~mq+zF?ln44iYS7nB7>zOE@`*~dV`F#yl7VxQT1wCklgceRq*Shm06SI$r#gIRj zjf0inkx-D`SB8dNMyP5KMfMsvUE&=6+8lrav6aG+@BkLGFPDF^R&#+*b|tl0Me3Bb zgxfv2?y#}CaK+V}aYXsB0TP=7s1m!_fZ}9J#1A0u*W9h`KbM~& z(pcg$Thj#!%=AQ>bNv^o4ITZAD%mIEG$t=TT$;0KX1#JVFnt9h9sa1z1m>kX>u3VW z=e7Ek1=r@h{3L}xxeB=7){8ZSKLG(~6LT!CgnrHb=vw(`>5pufe7D%3m8UX)@Ektl zJ+ca`P%({IK?*-Zk}%5YQW`!~w%DxuusTgk3G+LHrFb~r_KG0)6>g}4e_|1Ng>Xk) zLOa-Z zo4(nYYyP^NaFthOwsc@F#o66}Pf1nqN?VCyj|~&kl^j>!lwGfD-0M47y|o;$UFLHo zA5z85v{GFY0ok+PEX8?CKk{yX6!;ixOq8bY1q(_cl#09u>F6`Q2<+&Rkce$hzMGEy zByl2YD>*W2`J=*XF~E7?D9+J4aBd*yR3rTD(FteU>l4N+=k+N#l^=g-*NU5VAd~^z zXZ?N>dB00h_oJSop2tJEHBbO%@}~{YpW6YqvhMv(+1HtgWba?2C+)(vGcKuY&E1{t zW4+#y&2l(K+?pfzP@d14$Ws~FdFT_gt1MzG@HJ!6!?yJ8O+u}_+gf4Eu6Ws-gsLE+ zDKOcUrZOW_bhOMWyJ%>$-P=v8?A&Y|x9UTN~HSLSv!LqYIfGMp36kcZz zXFc_n!UV9bHMWirQsv*RuIlqzFWGEC?R3K=esO)qJwrQzMQA=C86}BTWYxvcp4|nC zcSMpxqpMxla@UO5(lQUiIxJ0OJ^Oqy*NwHzU&Xrmksx7d&@lQ}b?X`Q%kK(v8pwXr z^o{!`EUaf1gG}x3ilRV&(~atK{CMzFiL#U2W04$OBy zKV1#%n5q$YofpQ$SNWlf2h!ri9Fng;HuHSUXR?xJ$kEMuWyZmqzHXrnbhaMR17N!w zE3uoDk~eoLv@Gf8Ih`^h;(Ol#vQi9|uTrgm+q;O;`$@MeU2c*XXUC?T-oCq=xp6yX zRd!XP(~{N0k!^Z*kuLVwYRqR#W$>9K`w6-{Ky2_qwr*~-rkE^JRE`nVThn8*(eSLL z=6PEI+N>dOb}PR>eeh|&(&D9@Q~p`1xg(!T*_R{KLd#S3K2Y4zb zuzJhOw6gV-#;;V$==<^qiq6e8$vMs0Z0oH#J};Ha8DT_V&S9f#3nn-oK5o%x9J~fH z+^emZYw7|FD|&KCxY6W7wkWlGUonRM*oHD0mwH9EDhGsRJf-ev&N5i&;0VWLeWtep(9(dT7S_;0e2GDYCG7bMnvbBz-{ygAjEvg< zNo{9jxPJRTOLG6bG9rv~CgO+OB&9`H_HLiD39E0+Zk=xB)NOY;+kgYbEyM)#vWA_G zlOOe3KPnFk08;X1{|J-`sm=dR*S@3Dx|?NaKT0=0=%uDqSMSIMO~-$ZD@nq@kz{DG zF9eT1ey?Xz>4WDqy3AueMkgw=$8P|R)oQv*@~Q{5;=H%KW~CIu;(d%FlpZ{n&wr3J zzA5)f7<&BUanRzDG=XI^*k~1V0jp!hdL7R?RLYmv7Y4lpsbEIt>vf;VoO$#$P6*AE zb!A+tNal1}_xAsQeR~;NqSW%VV?H=jolWMGqa-|Ju-Loq?LdR2lFGgKiVqc?BHhXW zF>lh>jfox93*5=X=GtR@UX8Ankb<$YdKG(>4W$+Sql^`myy{U&a_sP)S)j=Aq=s66fVN%ULB=WKlcmVf)vODbEu!nzyEl8R$P zM%#PAQ6bURK;PA7eb%`J#k04KWrP`A%5UY+`8(zgI_ybv;tAz&JCU33a?vUW9#P68 z^+D|=)F@Llg|e{6y|^%?*_|Mp7v^ZD0LVwRT8<4&L4mGxwvQ4lg`F%iM7D?F`{^fX z_nyZw-yMZH&v$|^(JsC!1qBxRAXZ92Bgc%=$>Eo8|4OP|Oin%z%>P7h8Rrbs##IM_ z-mx9fyq_I7{BX+rT3qM!MDAo(n5cxvO^ml)r9RbACf+RUlfU=!qbI8WNof5LyhdWG z!|Bthazaf91VD}YCCxrI?T=RS6AL?)f8pmek@Xl?d)GLS$FF%+1Ys3c<>@i!P|pM9mqUbJnsYopmFs{1%c0+^mUaVl$O-y%_u4^DtnT3ByNkW7YJG}B$f6I=e zyFQGADAJ2j<$I1lwm+f>H!A%CM2+BZ{=znlBvR?;~{Oev$?tnp3jaBI1CvY~bPC5w-DANVHzY zp`Y}%Ce9KxkKfK)+ z|K=C|tFMF3=>Pb_hW`jGHGA)IF1-Tac=reKxgH&Nxjf)eE?&0w_l%gedy7xx=ijl1 zLdzEA)T$5b=m-3!?@Ryh^XA6C_mBK{WB&>)HG1m)Xa41Q|E@iK9s|#f*u-{n+id|P*W}pF_uBjF4SHf zmn@1qrO!x@hB2anp&!Z z=8j3Z%(TuED=VxwiMvwt5nP8B+?b81oCHj+HB9)Yg0IOJF>GTc*gu;y0@9p72zL^Q zv14)*a|u^ehUP5>+og`B7s*{BBh#jYCLune!AhLkz38oBL8g5#`vDUE+|}Vf&3~aw zc<-be@mgPDec$Qkx;p8>`LyIGy!U7q$mn7A-LU!Zq955m4=Q31_~uvLg!Z}_Y^Dw` zcA2S$xn{rrvw{EjjNyN5UDPiV`fcg{aXJ4S&;Dy zkvg8x`dmicwPO1m_i8KdW|?P8rS)ae+&k`&fbtDC6_>lnvg7tK8)rjQ5kr`4`Xiel zUT8moL~6%w)ogARIB{BFf{|I+!(~19(!BhL=G4P2Rja{ykC8~)(c*tA4<*tj0)4c0 zSg(b3xU-hDnMNJ`xD&aD9q)=ZPpk_JFf+0~COJ$3f}3XufKMvAN8S06M$rYSUejaN zFrrU`;QsW}xK)Ue@0Ifbn}ni^<1?|*`TjL0%SfX1Jh>lC52sn^c|^=rQydm|TdsU^ zZ^y}bYl$|_SCuOwd`Opmv(9B@?mS;I3(7LYGY~YdFr!S^`!C&}su$;p=6gronE3lN z2Hz%sl401uMO_-l{Pa#S2)i;7C!EL{06xIyeQtZHHJx-ONY|@qaOE6vlKW84wHe#T zVp*5Gz&vFsMf)vYg52G`$$jlRR;{3f{wj^VDKfFsVxeY_mQxgF?_h)}_wzo3@3$HY zAT)%>e|en{Nd(o|t!Q}?`G|G}&U)pd!wQbHG$(D$WF)jM-iTBhDz`m!Hh1&g@?W6aDB}{am1``c;lfezoL@pJ^ukTd zMa)qAEpq^C-M)S_k>M4r#pOF5>@Wh(u&g7wGL0^2QwcHhJTiIweYAVtA#EuI z3wvF-M6o0~<%GycHF*LeOxiMxU{6mH#*;6nu`YA|)$;Cd`e#2Pk7qa+44ZK;6pW^B z;ax0_4ySm2o!ervN)@WhIoT;cW(bydWfJ7tUtq8&qJbdF*K%8@b}aqEXUfr8XvE~& z>Gf88{tFJ7S0n?{vim?(LJI`gX|!{$fG&Q|sJN!1@~VYR__>++YfUIb+Vk4?2WXu& zS*x75)b7bK1$LH%o{4J?@x$K82p}}Ra2LJDMU*{C5=E+*MjMju<{{CxlnxLOxVr-| z^13LbvG%740yu5A=?s>A=puulMl>&3br903g0M1%^@Hk`9F8mDj&LlelzQjMBwPvI-K^+ z_*1Xb-$e*uw$Oh}S&WbHYbC>|=lfuply6t&4DnB~+y1fj{}U%5e>)l~VtD;EUqDm; z3m3oV;*xj2A3;B8sr7$9-{qXc{^fWWfQIGq(VRH`yi^tPFV8~^+@Eul6^q7-567^T zX7Ve_!AM*Dhjuk6q+LFJ*DixjlGN&ptXG4U2!A{(UG9434u%37gW ziU6lFCk&w!zI^8@&8Kquucz=;RH*<=6#LPDc1KHiN#RAfO z0<`XB^HzHFUcg`yuFi8&m$d(|W`O(UOQ{r(|IKCo#?fQJ|3DPRH`llwsqP^2=*{nE z>a5s*ukPn^z&}}gYmLGW;Z>E}e!SoCsvz7PFBbNMjfc5lblEW!S-|&XY%Dm*a5ZJ? zg%NA9ET)Ez!U&(Dk7sIV=!AbP`NYCnmWN+glMVI^Qt^A&V#p zLQh=(&;*&2f00o?FN~`z#|;dm#J8?_iZL?=W4DijF%_vN`w02z&Z_U;Gelgd8(A~w zA<27nQgw&Sze6ZIETDGskl625-Etne%+yW1d}h)pXX!L38`k~ar1@ZkHg@oJn9X^? zwNUzwt)Ao^wHhrOLt99p?BD^6k9IJfv4KH-TU$|v30JP%(L`%N28EKIA7(WreC)Xgy+i%D|n($it@Ks%aa7)`3 zFX>Y^p>~-MMienYlqbQ$8D~>rD zY0-*Ejur{+wA}1vO>mbIm@g6O=bB}QL=J66xc$L5>vLZD(--b38bgtF5nfYj$nIc5sQ{7dK<^V6LBew9h=f}`mHwzL|og) zwGOk_m~h+(wa}$uac~~8j0h&n1K1Fqlm!SsVi>tuEbyeUo8oWDfbrg)%wyt@)2+>C zjHyYBr}2I0kUEf&_{49WoMUHEB5-=$cn(X1O&Fg%A80j;@0^~1dxDcgLS(^9 z#U&2)yqZ1n81~47H)V!nbfF+*>)?`IN7!g3OCiyxZ<{fPg%$lh>Dr{8RiJM}hC9mT)6yK_VS3VC$r|}6)g{)lZN{{K42NL2{r%N8KExcp z&C4DsKXsc!6RHkQ6sS`!z!-H}{9geWIu zeJ9LCQX`F;vMc5etP{e<$lCleL>r(7DDa2rUnT_zqU66P0#CDc`0n-{x1ph#*Qe$VnzYOhlNH8*@P6%~bizm4-u z5RgSPR-rdM^ygL9sxkRl(=_NGq7!3~ydo)7Xiu zpLAKKx#rzHG$B&T6}@^)>VBTpv-^@8!s~*Ynbq*p!Ig{GDtA*|xja?7$~3CRxIlY= zS&{@xY=L7tba0ei5`W8ZHIqYzg}U>WUtc31oAn7>lz$j-cS z6lRR{lSl?R^y*K*UfX>#ypIdwQwvV`jOW>mE9O#ES*y(g-VHT__bohQM4VF)wM-n( z>L_qhRwmJHmfGs!BB7aUBpB@sN-n?U8eFU=pEgPFaCeb#KR=sq=5X_~UN*tJs>?}) z7gi@&MlzP0ZV@CrV_-aBQ^(^ubjDCJsrfpphrh40G9;Zm^gu+!->!Xk&N$H4=bmpc z9|n)NaWPp_ImH`vSB7yo@(Nd_>gmcPX&B9+L6+EBem*fBB{(?W>OQwm8(SP!K6pO7 zD0+yG<4WtY;u-u-Rcme|Th2;#*IH%M!gI~bmrj+LPce-OgHMbv0WS>~17V<_Xw9~= zSr~r{5!(M;{5kktUHsAh8~Zadn4E8_O>Nu_i=DJFS(zw_eVSKmuUmYXp}M47s)Jfx zkxbNttmu2z=Mib;RzkcJuBfhFRljn2ffE9-rdVk$rmO}w+w!_kw+KxeWnGTjpP#AvaNl(=JrT`5=vi%6-en+< zjxL^D+a;ZQy^0y@QYTajQB*+GMKF@M9v~L8+Ep)|SwvT4SY2~w#j#n7#p#!z>(PGh z9-V)G2Jm=g%#2XykE4SR$jc-7=HMFZnF_$0 zyoAp)H@DR}CTsP%{#$O`Trc-8;2}gH;9UqD$1ec$@7#XB(ZYYt_s4d!6-gY& z;(#z_n4XA4&lcD?^3q`)IqtPQuVZ10O>&|2xWM`#z2M#g-Xm9e0?ZV$ppQB*2%p0J zIe&vwqzBw=9d!16r-Oj1Eq$i|zI$pT{cxc*K(9mB@J}AqPo^^y6qmF zHgy`k>!n;JU>vrATaVGc5A05pFMoxIu}_X=o@J$u+-sEKK94Wbpn_UA+{ADMvxNyJ zg{DOT@RfYRmeRe zD#Xf-qTvNcJ(jFX-kh$nzw%5UX0ETEv)Y8!t-$r(eYsc_d(%hl;Lw>#x$zmX*b;VC zR|KJp7W}g!2&_@v$|;JI$1R-j-u=~!gc){p?B@m@OzV}|lR;q^<2pofwwOicjk+D) zc+{bNthg+>q@nxjrjvSsMEtOm8bT@zZS34)Xd5{|BnG8xG~K-A0sp_~=+&rrL+!LQ{R z>G{7D|Nn39EadL|wMY>Dg`{Tf9Q^a11pLR&Oqhim=;J!`e|Sp_-trf2`qVCX z{*ku5(ogsoY}!#X{rv8w(A)4YWSflSjmp<#4{fFNHhHXP>n2^AEPG&3+b@V{XJ%l~ zTaY>mQsHGQ^DBW%(3luEL6#97ilPVjsSb9CGsk0A;36)QTX0-LO(8_S7QDmqR3e+QE2?3reSwc8JEwL@4XM&nBwv+_UTQb!H0%yq7O#Q0X`L&_-HUkW(?#1P{05 z@Mf+O9L3#_D^^uXgBpvl~0QAHwC zzWKXMM)sNUMZcDV7m-+7M|gd5rjY6v;Fs(cAjA`Z&NjpC`tszOds5odMKq>=N@VES zNj}sN5(a$1DF!S2SJuiuOz}UxR{eU{IF_eIKkrYN*syVhqep4^aWnc)ca7QqJ){X#egO>AGHI@|-GYhzwdplEQTSo6p z%O%iYWU8JQ*nwI<)DP`nh&s6{MBe0;-G#%00`pa2N57GU(9|=u5)Xd~O{ZFt&~?p4 zZjjP0h8;b!K6W48pnRf6u2;aEUgKi^2pAM2Q~&O>8|jEW35uq8Yp7b_rAmnixM3BY zLG5yccPc=h+8W9~d~Ivr9Qcm5)j$JqGA&(J;Hp*Ql1I12?mIZ`9yhs%KU&LZc~*?x5a6H3jHmmOx0+w%E!Sv6S==P%Icf3HzcLH{#rlwW!j6}V)X zYJr{$X9|v9QaLW0Kx2W_SQ5 zj{r#avG10LMo#X4I8oeAXsei0rFhPd5gIf*O)B?@nh`~mc{-oWVp)x2)D2Kb_XofI zv_9i>g?ZF^so}>B4E!-Mz@1ECi)0m?hPsB$vsqMN3m1g>IeKoWYp|(UcN)F)7VQ)kB@$8l3vn*kNmt0T@B$96r?+ik}PZ)WSa@#WiYurRnR*mk}jiccZB> zu2Z}Ud>-eP{G8mK>ctTKaZMAy?hl>#`a5aIUZ9Tsf z3XDFvt}JR#vRfg3vC5uKn`3V6_7RUvd9eyBgH)beZzm3aZlax@R#A|xYcLOKBA*Jz(QiOp1$2RSE~s?$e} zVTrewpU5?@%l(>ssOWovKbJvFD&)Addnr4gkwDNV%b;7+?Jh~y41a)FyZTrFUsCSW z!RFh9-XZRft6_jPvux+;y03Ms_Kru{yh8by^-4K0p}@q;ptfiRPp}x8-?v)gaFR8! zOrQYM1A<sAG_Szbna|{w;LDWs>Z4|Mx*yJIe#w^%; zP31@y5E;251GRkEp3H!#YnI*oDhw=#IvdsgwjQ`rma#d%wN3Jrj(zvg-6K4#Wx~$M z+5Xu+_1cvn;#h!7{?gK=j|HX>kBB!+WOzU#nXk zRC5W$siFCrHb<|3>+>5zJf2LdZwW8nKR{q#Ov$+2D+Q$rs1`XKKar+c?8x-DIl zDvz!-V$~#ZrpaME)hAJTtWto%ygDXhEO*j4sRTj&{JhG=*RA*zb{vssK2%1!O0fpS zV)@#$EO{ z(qMgqBTSB>C8Dylu0^nk6%Vc#9~^@JqPdrz=wEiTFc~aJwD-01OX562N8iSt6^ltS zqdv}F-RA7LF+v{mN^b?~BM9|ieXxIq2EyDGn00t{%r1l60yi$*LPtj*>z6AwHa5g0 zqI4-r&%uk}V1=!tSHi*5N9fe5b%P>YY*)^RbIzUf`JwpBF^uQ?(DAfqjj$ge7^XU@ zBS!Yxf|&b8xEN81o~!;Q$Of+D?D$cFa@z&BN1H3HLVa<2nbM4Cece79z^HritF@NZ zFnJ3RdG*``x-xnboSGIC_qN~{X(EC+4$6(6vwDRrc_v*FX>gZLMTNYm(|!5S#K(C( z>(|oFdOHCIrOH)ZxHJw|V&e2pr7g)w;rd#+Fw#Woymrb=Ik(~#k5e;MCI&1g@ zX{sGwt{V{b@Waba%Z28Q*srjAO<6S0cVejq<^FT_m=4JQ~)+>FY*W9cy&mL6th9^_g!@lh5E=-iTi%dxI zgs?+mBsb`AYvXZ_NV?-`uxNU^0mRG}rbKcqdd0U`iB?nfO)dj`H3|@GD>HGQfI;GN zbtx{!yfT0QJB(X@SXTEolp@ba8I4J5U#krinGD$_aYHO;4&FL%(sG)N7L%^$mq|x* zP9&*lKRei%Ph~DQ2=A`0{h*`Pct1_0MsI4*H@E|D;EUte4$HKr_f)iE_BPR8uJDXe zm8s%7a@9C#2Mu}}(_5RYjGipuZOtc{TO%n#-PSedc7xSD(DN*M8ufBwsgqBV5MG~~ z>4od{6{m@G&8>G6@OGi8*EpV_^<$`gRfvVzD#9^Ls*O&1%h#jJJ^~9!5fM+KAKkO5 zx4$k~GW1z!JPa4rGxk(EM<@0ogb#9zHGOa_kkf33-*kXYM*YZ);IItKLnz}UIWO!y zucf4_Dn3wh=THlDrLofF^~IY|mLtE{kzh4wWE@)M%cLW7`t~Xx%?7r;UXN9q$;lb# zO_Zt!8NQ6Uneo7c^xVuO{sTlyl|aS)jnR>6hU;P?PBQ-9$M)zTUu_q8rLy~k&LbQ4MAETDzw>TJ7JhEl{CkOI?gkk7ckP zJ;kxkziHu#3dLE}iXs*O+op=XTe3j~|Mw4JFSYqL-F6-oP`nW**XF4Iay$+B+w+tr z=xvTVUWPUs~O)owd>P&-3=$t$RRR>f5EN(03811 zpWu&06I)||lcR}&4IHp!XJ`q>%uMo@>;m6Jq30Pe5L-O_e-wCpGw5Pj||xW*w_N}eHU;M{7Cz2&OjJ+D!L!sUrg z{XmZA0mO|ZkBf{An6;S`RGd!YT1YDFP{(c^gy&Qcvwe?CYkS#lC|u7R!psEMc`I_K zusBRCDER$UKIx;EQ0S;d#A`8|mg| zukWX!G_RdJA~ru-Uo>cM51EoGet3lWt9I^x84fN-6xYMAC9=Mw;;pIoYON0-rL-&4 z#P@Mh61mZpK%Wl^zyq_a%mNiuB_bY1Ug|aXR>y0OeW0g8VY&=N`VTh^xxbPs8zY-eQ{yBw%n4b!d z^UJft$DE4w%+InSd4mW^=Fr4W2%*;R@roo;9v;|%SXi^3MLKQVZGwXYl)e~5ck(=D4v^iFgW%F$FkbYp0|Ja_zY>p}Ew z^`_==iv4?nF9H8#YaJTVBYH|ESG#A?<5Nv@A3NxZ_XnS zW^#3rt79!Ft+}!wWzbbDY?*t-Xav|?T*#}Y7y7Q^d8_%%YgatM+-n+jRx>!v-R4R1 z{3E5_Pfe%S5BCYY9$ROHRhdoSmy(Doni}COzVyp(*G&bdW|GRHq0B!IuLmBR6qJAW zI(xITR!9mR=h!`>GVpAfa5|YGLK?$l}_} zMD+I0{)2tZa4_#_0_tJjKqsl$x9FEH2m2Xw33j5VOTC*StZpaY?m!7qiWXSF++n~2 z;Rk6F!eK`0T@0N%**^5H z?AC;m6sOcFfk4u%K=4D~5n)$-3yJqVihgd8fuL`oii9V-K3O6N%z!revC?D$`1})w z8faDX)KsUB8rvqyTzx*~@JxYH=OuUL`3gD1l`d`IRKu&CZl#jEON%h25qsR~SrLn` zDZm2HVWcnChOaPeiVlB3Pr{RQJSY1kOuNknUs+@gF3s z!it)!rNM2Cw%TELKXDgwx6n&+BSSO|MMZTMxM;7XTzWh?KZH)#KDJQch5YJ`pGbWwqcEa;n?b2l(nkvwy#v!m7s$G#+uFXpeq@3 zfkrKf3ty^-?x7KdMnqQ#Ok`J0(QrPWz_7Fb^gz9bL?UufKz@2BhqO;^3I@9fCvMOiH5mJV zzSKcNJt7x2r|BwUq&(w0Go!u>Slx2ld-@0`v-jCUlvU0miuilYY!77-{Wjg#)N*RF z-S7vNmg*t(yrNpW&>~vF7kI~a@sI}rMlnka_39t85_|h(KbvK7Vr=3fka4R9K$7V> zd`!Qtnr)0bgyY)p3>fiXLot6q;{Q_T$3Tk}Y$9~xnUuQ&u3nX*7Dsn)BI5eKW9wkx z9V<%I7Vnsp76UK+4s^O(gaoz~Geo8pd1(zaUcteFj>0*P#Z!GMI>n3vl$uBwAZIQe zG*)Qs?RuF+`;O3h0o5I#{5Cv4+-S5^$AdK>nUFPRZvrxM9iz{)H(Cv8HS>FEa@zD8 zRc>F?qoHKux|ZRCRr7%&Qoo>m5U7tm9EFp3cs4`oq?DytN<#6&+Y{=~l~8sBU$68jx6bGk5G^dB=UPzK9UR@qdZ(kZEYn0g!%mJ`k3ig| zA!;YN_#)qt+UmO>S@DpVZb@m^WN!?pOwWSi1~MM^^~RHfm$b9+3{zBUH&@cR7eSA0 zvK@Oxn*|xZDEUt#Ww+Ks*S?KL94YfUv5&lwFSbp}4glD(a``1Ca5@Z()Sm{mG^J<2MnL=62n3KKokJygf; zr&}T<>vuS`dQe+8nWlm&W0ZC-$h5JA6I03t=a8B#7+3*X>XWrx((bPS9fA__0q_EhZkgN@SW zPWEc##Ch7_Znor?>KVkMq4Ay@&xH)A_n)`jaLr2D$TOfULDrg#1LHIlq9mVVd9{!_ zjK{`8u;&t>-c$utJL;>$K=5K_38B~tp?F_W7`c1wYCUQ!J@ht}F<2q{$(uR&)xKSi zfF2ri_a%;maWG~dam@oRzwa5=D>@$!zQ``1^NTe3WqUoM`qFeqb2A3#P8Y{9Sqw(= z9qyO6Sv6*7x}It<=)~j<`usrR9*ZG96t~DUKk#UB@70g@GH-fBBd&_x-iV}b-XxrS zKRg}#U3zv8%Ww>wXjDnUj#n5rZ%e&Nh5s^lA$yV8WeN5660JpDVcW<4!dFLV+DR$U zs39#DUPR95gf=_RW3+}VFO4eZT`^&JsoQaJ3xjc)^;_7J??4J%-f?CEHF}LskWQ`* zA6aX)7b*iz%(SNn#m3L4M^nz8idVvwr$~3N81m9W)vrl(oe2mSzd@>doXd(AR`AQ< zOehf!kbW)wpghZ*9TeJWPi8V(Ej6?zG*(5I-{8f>2_eky2bhV;1&t(~KaS8{;M(+- z-&p$;=y|r)$(>?A$M5WaS~hNo{XOA@Qt*);3&9ORSiFHU`U4$rkl?4nF}%0U+qBs4 z68q3NjIwwya1M%hisz3^{05{YOzPo|DwE+qy*&suIdC@<+(e14W7;s-<=gLk#ORpT zRQO(xZ{&1l5o2Yz{5-^wj#7AP7GJ%oQj-9K+8E7YX?s$1WQxEhi_=V2=?St|(;B+B zCefD}75PQz%%xXn@SgKoEW)K=mfQY&!N5yj?_fuv z`vf^(#Qugwh8?G=@Q27AO|?KBRGLng54s149#B=wNKkGljUJh=L%EwAoeSn*>(qr` zF2ejH*I{(ndA*66x7^TnTL33WVarDM*Ly-v;Dg(bIO@-RcA=_SNyvV&t4ffZ;}mzY zMnB$)AXLuo*lfyc$XZx-OO+ftYq#>?evwU_rKn?*-vWQ53w@E0jPGJ8-l~0Nu#IT`f&!resD3^QP@``1D2$EQ83I3i-~}TQ}NQAnY4M* zK?5lx7;klaB#6<8M@@H|LU72so-S?q~Q29V2$hwYCu1u{TL3!L`nmM}?qXaae)jqwHy}ODt zJHN*9?tdeD)tnuD+RCYxF5&>Kd+nPjXyplAqr=a4G%YV(4>wyqsdG^qrc#Ocju+xw zcqxw2{kFh|>#S@D(x%UW$Eo*<&>n^9obYXH5(Vi#g$w%-=a8Y*Ft@rIel8RMuX{n= zX#CTH^x<3PJPe;Y6-~T?TUQak5D|>ao3y1nS~9_9CGs%_kFy5t z-LkML77~=#ox461y)~fYpef)x_qD zQDfi97M{BXFrkt7P7}+z61|)*UXWUF(VC7+X~RbnenrhI+;2}`Gq>||qh8D2lEXD$ zt3#I2_!!6tTEXftd77w0AY})~^TnG9-O{T_1fe&bOy)r1dWhVh^53)S8$p9emCz%U}O~ zCE+@xPN2nId@H9i;rR~k>KWl%x^xa#a?Ojl1#GE)@BJ<%FTA_u1H}vk$8qO_I4;4o zg2AMMN@IzCq~?>#c3MFvrHKk=_p$o&cTyhd)=y%ZWx3xD%UD)Zbx7$tHjc>a)n9rK zNeT60A41gT1&pz87&(JHnPv!Cz0&tUcJGDPwVLpWDr3AdUoXL#TFP%!BvUR^@&R(| zriJA4umSi|*_gL6G#L$~n`=EuQHR5N-?aPmULS4;p}ea)gTq4$v>zW2lL{SU7lVoz z=NRXGaw>j+DqO&p|69a?4SkIuq6rS&uU;pMH7IXv!js-faE7~A?D^Jq2vTaE&e!?U?pMW{-c?I z01(Xp_VGuxI5P_gJLjL5i!-y5uyg$?vjIE$tIWzy!ttvNtp2MvaSjsppL_#57Uw2m z|C8CD#6LLN87Y}K0oi5xASzD6q-^5mM8YIxV_;??{MWb0U*A$crBIhQv@|jLlOJ_f z4iX?6KdAyq{iLSkYLaW-`NgMmNf`NhpI5t!60jDG?{Z-IpV0*P3d*?;8`sc5D-Xk5C~w; z&`6LFVBkQhz`%jhprWCZGDBmq2*piKVPavEDX6F##3zt*2rD@NVry9yl?`jAw<&=oGaKXwfY1_2HU1w8i#6$A_v92^1+ z6!KRCV8^Hm5NPO-7|cSX1`aH&Y{H6Um_L?45Wqo!@_?g)2!b5Nz-|WAKJb>1T<4TY zBSU|VY4u}nvXhS?yJ{ildrBapi;+qkcVtT@=kumwZ`6NfJj$Fhx;YIMW#MT@E)#7u zPGQ9vA8T47aucX)9qE_ex0X(OA$yG@CU*?$3-A9y5PLW1rnYB3Z44=d0cwmTy+LM zu|mF}FYb-iRbHK3yo`GvVm~ebxamo0Y)Qr+J|6WMyU4$V@ta!NTKrZqgEY<8qP{tj z%HU|x!F-9_t>jZaTqVgHo{9b9*0JcZ8)K1)#_si3}4~t~X zv|_{RYEaj<;$Nimcr0UJ%T%2mB1Sl@$d+;`-1cC5pAe-yaj>E5tDD$No7H&V0nLIg zPTVRVP)n+0iRp1_7z*E9_$G1I1`*?7#B!1yVz8+J`ZL9_glYF6AbPl~PPm3_%b>-D zUuq$fnC;}$mYiy0NGcg`aBQRI1zH09V_cp(=v*u z-)5;w_DDw0O<$f^S+}Ti&YRlNI?I(gE@O8V`V5&&TUSK9+fJQgkywY_l*AsM9Gv1l z{RzhpbEQ+Ys6_DGEQ9VW!kiLdQ-emowekmfW*~V~D%+G}<)xlp6-^He@NmLlEk9|_ z#ABP}MpH{NGIG{icUR;RkDX?#>0G7cWIUEpC6r>TFgc4j@wL)BqR3TNhE(nWxr2>x z8H$R|+p;}V9yMO0ap?N`^#@ih&KJ7$?0kJSv(?fWY(?uYp@gu$*P;>V+@cbF2vI&9 zG1&JZm^@WlA%&>ygJp~hLW)z@A#>;zJg=c#?kwcp6j6i{eq$m|_R@-jCP1@kxW{ev zwo1?XEr#lGOF5#Jb0BWom&$xLOBrq(bJ4lDQZ}q^evOQ&CrP#nz1jE~ss?FopWVxO zP17tJtWTu$q*8J!l{a%%%SW9yh?5<))v~{DZ)E7XhU71Y+NUA2Z!XID-IPdY*cl|^ z6Hy$DSPRTGzf37q$RMGbIS$j3EqIEh(8ufLlnr{4z5X1npX!A6Kh_!y!%3u<@i}Su?thDAui}(0?$K8zniQ zPCimq?{@$7=pb(^Uc*i5OpiQVZ~ON6w(|8%1RH@$YU8ZPl7)=icx3!I71!ot)O^KU z_j42C^AR1ojeAV~yO<9Ky&MVR`WN|$Rf0QO<1^ySsgOvsv`f7|Kzc|WztPC6Kc3|a zj-hyG^Bc^&*Z1ef+Zk9D5$-J$MJlufmS^W?9*lPcw9OPgb|KeNmlFOe@SJlj+&D&@ zFgSy^E*nh_aaPvN5c~iU`D84!D*Z8y`_2J`<% zO+Eij(|@0824oDUGwrK=;B?`iO2?MVsNDapYNX#91$;+>jOA7EiBJqW;R$F&?sKZ# zmWfWIZ*yZ#{~E!NII*mxpI<*EDgEI|G9=sqYvR0h*$g;dRqQiBBALKp>6!lk_>S6n zNA-*QDBi!A{Am&6e_8}(6aBHBn*3y4_Q3a*K52T@3G>S)nZ>&5eMbBJCjxlVUy3HW zSIpdp(c$sv&iKvaKaRRQ*g#Tq{CDEbpKUst)48SC7_(sphM_W8($}{Le{1&BnoZTg zF8+e%5z@!(j%x8@%WOr1cxkJfgUOIl<7K--T-L$l(t&lRqr-rxE;L;4Sr!;T8rC{GVDNHkVg-{>=*X?>mJ1HDp5r?GIT6CNe5re+m7! zsmtosFx2r&OxES_hYqrGRcT3aZdLRFOlRC>hK&F3SlJ(@^OK)HB>s;X3;sjH{~hN0 zUr$(p$5;wG|4Yc#c6)W&V~Unjys@^zI?0#&U-yyUA;&0Mrf!y0;qnM8X%mv+dn(0A z=0t7!rW~>OOll$}QjNLhy3h!G-j%!ghwmQWtp!`6E!@#98;sG5ifHjh|?Ud{Z%HSO{nyg>|3PCT_>Bx!k}IaWUkV&)`n zsQ0U`c;+_E#RSBH3I&!vG zTu;98%iaod2X89rc*o~}n%u`x_T*FNt;sXQa-gDSNZvuMFn22-hYh;CcbP?O=O~8v zN!ywGkZXf8ZNbI5++z0x!ibQV$fB_(5X}7n6);KbG>Bp`3o~UjCC9Vs5Xb|}Of)Rj z!{PPRT9nG4r@r8Zrr31GkA+F)Efof~D=ng8tQ0EW?kD4=$8dC^q0y)@$-n@%8Vxxj z&Cnu4*$S1jpv7XCP;aVT*(jR0(^#FFqf8}E%i>laUgEY{FGZPa!LcTJWo#CxUoCR> z6iB)JBt1kiJDcW{;$${49VQ}6IWpKaZ0KTfXogG9umPV(-U3^vxICAWm9wpI_wI$t zxv1|W>*Y62(v7G!Cdx1^I^|>snMg6YG~uCfrmR}|Yk7yK&mCx>AuGZd@w66{bPl+u z%<&(*=%!cBhjQ7A9QS3l6sCy?zA$phA7p%VkTSpO`i9T<>2HZYywJit?J zsqnBA7T?RxSU*h>H&c|(XFP7OFL~w8fYvdWrB(r#F3m>yK5>QmD~wrW4lT_sHL5Lc z?93GWMV?qXvg!onL>(G>0xqiidp9{!DRSwMfqVV?Fw=d<8hd+q(4!vL)e^23K(v3D zsi{RDqp7H?#E0Pbbu$BxawvyUs4)mnX0MVG=Q>!0;CPYIDV(UOUFivReH); zYb46PB6ip0*ypuyM(vFq7IjL%@n3H_#?IN`*Jvn@NioB9zU6ZlI_}3XWSA!QnuM{59csTIBS7v$d zq9J3&HAL-DJCkF#OXM_CsSbBkQ!ZUc6nRU#c?l@_UNZYt4=vjuf-` z3g)hoFr}#yvP$15(RLPB>(N4vN9s@espi*pekc9vuZZJa+o;L3|9-MmH>}cAQCyux z>PiN31SAC_q1r|z477|LTtONjy8Q|hrMSQO1B7+(@dotaL#!QFNkrndk7o zAMzp7I7Q=6BVUG0RE|Us7_)bzZMwzLEGbH(@Q2}6cgaB1^)YX$UTY5H7ouY{weoTE z*h?LP#69re-+x)V$TWN0XiHxk05b)JzsNRoKO_Wii@DwuAGu!Qf%YUdg{_1j z?@CYJuYbb!B)KRyNxItBfp3dm8fd0^1o3}Ru5Nwl7u`2@-_#tuekMdukKr}(<0P{O zg}SIRei9;d6PSQ3>!$~QPH*Nm76 z-bEreg__p8b>VYk`c8JJeMHdVYeWbT00GXq%ROH(^YRdLMi* z^4%tERS$M;bdL*V-siEBY!~+&lXDr%S@+Fi!3<}*Sto*}=~X|0_}6strxPYFiRr-z zOHqC~w`KaC%k-ZX%}wR6!6U*f%@nMfAW+@0bsZ+h={@Be=P_1-&c%}UOmeUg)%otz~5muwo$rC8Z(JoGlC3%7 zjpbu=QwGI~84MQ-aRyWlVaE9}@be_&xz&ImHtQ`olg_)*LHv$WznNYLl|>p}yG4Vg z!Mjnr@%AUcAje=qeK5xV7C^e2EmeCQ5~@NKkY zjmOt{0+5zRA4)#~_GopmAxi7xX`(b2Z*MN6s^jw}gbJ|-RBq(CQU>3bDDmFNS}gQy zP)lvTuaJ!%G)|AHP@=OgjF>PPc=cHgC5z)Uh&8Y>(m{HNnj4>kIGXoBWg@`Uwu|?IuoJ$aBX-!^D-jNMh{-AV}-`Q#btzC=p=7lgya7c zIxye=&HS?p)k@kmv|gm}39z_;Q4q@da=Evl?t$m=Xr)M522^nDvrPATp&m!oo5QbB&_=i>W z)4W!$tlPv3g8lX(>e_rlI6QGlhJjjIGz*PF3%`^NA1E4Yuj?0m7e`#*RQI#vC^R{EH!WqiOHwqp?##noin{Yb z`F|2gNngm8PH>DCsAMIM2>trJs`bESc!|g#q^r(fPO;Un4Yh;|W3KqUd4Z_2ePmU* z`J}K3iH9e0>G+mxQ7V13lmxUK1>{6HDQYZXQ8TT>lz0$YI%Ne7_m}-yP8uy14_#Xv zC;faL(FV27TKMIP@XJ73#s@9q~8Iu-SE>M!a_?abQrDU0MmmtzwG+!OP%a?NU z&r`zS(!hgDgXG!QBSguvRK7M&f)OPWQMi(69sk_UB^FJ5ZoY~mD%USxGeLo9v67$*jo%`fGqW7Id{DpV@t;dBip zm^cI3`iA@N5RedsNfReZ_+r$>n=jw5erpZ!61)Y`wq(O~$>9#JONIM5;v4fbh%E+t zB&=U|cA6t4{nN%KJ^4QFaJf3!4*4&RTp*lUUi54O=PU1CWQdoG*1AOhw?J_IBM{-0 z=`=O}uE6R|V1kMtEF&hqiGe*;b~L=HD&nS3C1A?69LI_Rg93|cBM_YCQ@vhL;&85e z%9#EH;Ed30P%G7Zg9%rgH?hC~xY~?W{Ign<{QA@1-$e5Fq>lUlF?W9WD|`VD0Uo(-vnyWTR8Qs{7+6jQ+#k^zB4GKQ;iEGWtK^0HwD&y&L zqH~nE$c3dpuxu0G2OP}m@c^*8n=PI0(p9X!ksc*xVU4*&8|4j-ov!!*Tj(%#0uH=d zX$jL$dL#1rfr`y~V_EdUW)oPE*i_$I%vY%Msi|aSr9)2P>0svWXBSsHa%ltole%9I zOw3bmbrkH83b@T56v{4^c8Qq`(Ag6-8@)O?vbhhtK=jK0OyR!mE)wrqVs9UkQ4}}V z`W04<6$1KCr~9+4}wV`6uJ{akb7sM#haw4zSz z3{NT*Bf`+EgjgtJOvaZ#S`ZRCDei}wH=qf^@KtQK`pRU!W85UxvB+AzN?eY(zaZ~h zjyQ!mK%?MGJeMELiWGd|5Uwe_HOE|zSdx16a*pT>Os?3oZECPkdwJ)Ie7W!);hfUc z3H8S4)wFBq;{DS7?J>X!Zj&b~9r~;w(<~$`eq4|Sif^KiQlv_yeI5DIqAJnbvFZ{d z`D9)-;?#`#TDQA{uCOd|Zz?I=sXlWwyB;mkh~Ut;m*;qgx=enVN|P{MTK=SbY8`f0(1N*W%?J-kLgVSgytJZvLFr2{i!)mlC5>O% z0+zPNX8LPs-cd=<_!AI=Bb{O5Vo9gXsoBqGjisp}!SNF%v0S0yeNeDV#GuH?6F_QO z(0ntueS>?hL_&A7$|;$uQL5asp{%uFA9X%zQlS8?7N#7(gMld>61Q>tO3W4^61&Lw z1H5KvLS2;$u6T2&jN2BOg=w>QNjszxayGx;M7rsn#=TyjTIE;EQ1|E!mcAIF^-|kp ze={+G6+ulMIm`T`3sl|vHX}{WeC4_35$yRK2&zd=WG}m>0Lfs2A0nQ{I&y)o<*JO) z%UMzt9zIBC|8e4kR48EX(H+7+#d96HoUyPD*AX9yL&419Er1-&Ubcjt*T&Q;XULm^ z6j{A|-C&$1##4f5vqg2@5(su`88AUnmN-$)C0sedN#V2nhJPLiF+E4X{a^`A6`fB^ zN#N2RK<~7X%efIRpdOOY8>L1{>Cg&Pat^*}|F*kOtKOQ}QiP}N$a_Ppt;72S5O^Td zwk`!O;|Rzh)+7_w+IQOZ9@)QpM#-b_Ml2^+NRy*9xoe|O+1Pcv^DP=Mu1w(xyzuDZ(>f~FlF7fQZF20XKc9oeQCBHqhVcJ!k;RsTY!wz4780;nwYC3I*EgENI-t1ZdFh<{%R@_?@e5wCN0+)*BmkJ zTzU8d-t~fdlSpN=B;4m6RTifB4aN)z^eWskA17hUo z!kd;b*Ol>q*MID>B;6U>)qu>>$MH)utM-cZJe&lgy6MI-Q{G)6Yeh`0CtIm94Gq$q zka^%C{Yt679Hu?x1K%zN;nYIrPIa<^$WSmBS}4WUJOS`aD`Z{GB@gZUi-vURdy0DB zgE_1b;yQmZ8+&9eP+e${Ihh}deCKFZ=rLgtPumK|4)sio(mrg`a4 z$r3^99AItQzLgK!x(C2rbw#nCBnNdb0YB+!L(&E_^t*8dzY~YtdvzI4%Hj!WIl9&vWP}R7z0&C|K$5 z@$Zz7aZ|y-lvDX=5ZT+f26zGd76*mT_q~;_!j})(+LTnTxJf7PQG5@{g;Lupq&yD2 z(Fs;r<`t}DXLLMOhJ!-c1~VvSkSClOIHtA{!4!|7ku$t%wK}$n0&1ULGwiuYIkRQgk;Wbh z!E>U}^zr_h+T*bAydfPl34B2S9Bng{LP&|V z#IwPls4KhL>ZkhiSTf;VhypvkRcva3@_7V&zMuO!K5&eErl5zB&{AVFa`X?t8>tV! z73uHmIoUspb@DHT|7Vc%`~R1C!$Pjs!rz{~5A*DgsJ||5E1uqH;vbDW5BqqhxSI6} z)FxLdf)q@@Z@G!Zwx@`tcs;yZolA?Eco_@nKWlWM17N!vrukH8C^Z%6NKCHx+Lstx z5l7fE<Mv_yiPl4d?kw5gB0faK zN0^0#TrIu>Xe`jEmYlLOAOKTHOCSegDJ0vV$UZ#?*WR(Vw>kPNiug3u{rpK&OwcL5 z7(H}n0`}#Eda8PUB(1!@jbhwcQih{5@vJla7=SDa`?y{l+6!wW&9@v{i_KM}*?Pa) zsIR@jYjskU{);QtW&q#VPK71R{dFA8u$5dqy(_I&$;S!NoF5-^>EC=9(q0b9Uq|qt zk8EK+2GEBOe$^LfE_}d@qnqKn9)Tpv4-^ee?W`(+kJn`tg@NuEiQ}a#%-4rP4k;$p zDUNEIKQlQq5aV0Z4PHzPtW!tCelUCoP&?3b=j0#EmyFd`vzdBV4t8@&m+4cpq}QR; zoEUKP&uraTfCtYD>bpqA3B@hH|(2;@8~!G#QH2(MaIdi z_V%bj#@wSTKCp2)yrzeI>^neJ>!tTr*@ea}rxg^@Oi`}tVgd{!3{we7w;7XF!Axug zyV3_C2ml)-YWFI-UTlG;3A0Kk49|xysX{)9gwH%?^DtA ztVg1>x;{QA!#N66Tknw*o5>X*;k95h#xY=NSs~u^!%s{b?5_+hYgc^IjFNBdYOGb+|^fQ!Wxl?3SCRW?+Qzuzb zxYGI>Ks#wNBTKSvCX-}TU|9!n-x%P$2g~$ct?1#+=U9`A@7v+RdDOTIG_Y)~jU2)* z+B{JnBBL%=5UE?Ft_);gG5NwVKDR8?a=PRRfG~U;ECMrCYmO7bgKvGT_~vH|v>r37 z4L9GKbS%iPQPe!6q}V1}v~oK&jwEb2wt+3(5@> z0+aMSCa`pLy1l!qMedbIi^eo^oq#9xc(MDj&*C!nz6ccV;RqCY>wat_Qf&W_k1?|e zwHUK{KU-g+;1e>!xda-d7|8Fh_9S9s;Lr^wshij0PO~LP46|y8{Y1f6e00idWqZtz zad)={pF$N>PQ0^&=ah#h(vmQZy~wI=v#0n2WjG4`#iWflqpLG7Fmy}e-gh9|7Jcx30CGuAOMWe4yq8Txlu_h%@6`1j%^ z(wBdaOa6=L|9A6t&aOhT2BBvtlucG9r2~TyjkQ-;#u>2_C*4}34lv#9m6YwNA=nPM zH{I~P>v)Hl{h!p#{!UfSvDsECFpq}QW;n{94{3rrw$YIOv*QuvX|Og@1*NX(k1>7h z$0JighgLOaV;iZntnmHs@WJ{2y2vj(`u1n`jUN^QzW=*a|BLNEx0YP00q1?>HQXF= zL`;ocBr=WB1$+Ylp6~Gg(fFqDm0frb8pqFX__tLf9tg-3;O?j~bv^4KV~QB$66PSG zzH|5x=?}p7`QhdwOxQ%8=nf?0)Xe+%^B=Dq(D3$@^9D_*LStdztCcxND0t=^=&gQlswpwc5D#BvYTGq|ZkR({IJETkc&`j?sa zs#X8l+3##d68wcXhGD*6wQlk6A}o4fFdx4sfT-3arrUyB5oDp#G}nh&`_b0|zT7XY z(X|*6u&MRprR0dMz#Q>S+-b6rL&_B?aJl;l;P^U8L58PspD@XdU#n{>di&Drx1FlN z^yUG+2e5yeivYfWO=@;uNbWNA7)gX87qEAMe5-(*WxZ&DTe?VtDQm4eSB|WLafV?Q zjm)b1$>boEq%l?}YQ)u+oViH))Ptha7VdYtJWlwoyscv}7A{Kh@(>f~_-f*Of>mEO z0P*>MDb2qZINsjSoNXn2&$~NZLgNyP#(Vn0D$ls7pT)LX|~SBYzq-`V7)yX z&!({}o0g`awAJHY>DJ8>Zvk=M>U0$<2HA|SNyI=3@=xBUL=gon-SgWLFiO-!q~NZO z0|2Th8tdJ&>qX89WQrCz>J%)(-fBH{8y{N;hv9R#d>-axcQ}X!3DZIP>?2C>1^p91jL4J;8KLVZ{-J=)jLW<&lAxI8Dfz|1UI+2or|;#C zZD@8yp_!RMjs?*)Qm^-lb$lm;G6|D$HAVc)AH?7bA-$FzZMu(NeY58vdRM4nK8dOI z)-XHHd|YEj&zSxm`)wmv{)*xBHcouCm|O8*@?;iyMg=;|2^yd_lWy%p5``-t6@ZF#M9<0=b|O%XA|M)dd$G z&=e1eJS}#Dph8((VLT)u*CI-0-5w&Dy0SFyCJMD=>3C-4T8!GF=-T!w-6k=T3L`dA zfq^sANvftFAU}%IFTkRp;6UtDGOwB`Z?5Ag4XV#j1F027)B`QIFK%Ako&L(Qo7_32 z=rv{@;g}hX55^&-!2(ZP_5ZAmIF_mkw*wX*D@aHR^{Z@8i$FFWC?aA4lUau_{50V{*;&RIOpFG?wuq&<7d{s&j=DheBwLzmFljp<`k~nWJ zQ6t5-^0PhDCW5p54M=^7W|K5WJs^N{UNK$grM~UU)kfVOQ-SI7V)ofQ<{v5}ND#Xc z&Gcq)%$=&v7+1qu!6xQRgA{{c><6et#Rxkx$WV+W+A@GHKNE1#BFKsb9kJ`cZPb;0qjZzfB zTZ~x$qobRy(7{nO;GA|6Ni4RiL^q zt*&l=@RBwGvBurQM$jwL@QInidY$jw$MS5eF>%`?~8 z7pS0em`O5aYFkpp>bN1n?^e29H_{Za<~3!QiBmk;E#Tfn5!H)bL6vRF#a2awc0OUA zg5Zxc7&-pz;}gBgQ4X)lMFRR?7wwmnwI&S=n6Hsn7lkGge6qDp@1qu`y}fxMGczoN zN!#le3eE}(M$M9NO*eH(o!%RsV6u=RTi+y9<>km{u4^FuD9hc(P6#eElpzi>bXnAs z8&on^FEd5-7s~VGCVo{Ur>|4TTU9_9e<&0@z(fe0hD=3RxN%z$PzVAzw3E!N>%+SH z2RIy3%r@v-_rQB;)bzECo0i*%8@Ccwl1z8P8J7EcdF7XR&GQPy{_In`+>s>4xZHI8 z;llc6GOBC11(>)%OF_Djt55GxzyICH0KYdKVQ;4?%*lM%7!+HGemBvDb)uv52n5!pfJ^>c6@`XS=5OTT{0`KK%3Q{~h`F zNBQ5w!2fW)eZZ2LV=^UHDFHM_kgc;YO6?l1PW>w zi8O44&nHGPl)88~g5O@I5P1snWSdL27>DXj97*ewE|#zP?4^4~|0pW&?`|*++#nwxzLBtTPL<)4H$-aJo^f`J@N-Nd?DpC+GCSEb z0VxL*ltS~p4X<$I3T++h_fa4%Brd0Jz9iwrbd#=GerD%t@s`^|jrE6^A07PdXb9oZ zr}TmgAUAIsY)uzaPOH;$cLi*gCusTt*wCdxh~*fTx>jN*c%Y6^m0$%{f`ro8?WDdw z_L7JbOn#2q#iH&IQu@t>Z*dfkm`tt%kyhobYmTS!4oatsHf2$Mop})ta-3o=DHj%J z5)dtvQZ=%ikYktYUgJJXl%w8%^Q-->n$=88W+D6&)p?}ORbz2tVi@+VR4S~TI*Sdx z(T7S6dKOaL=Z4{^t;7kY^qkQLC!}yDjseYc$AsnpnzbH0)I1Ty*RJN{wmF>Im;|Eb z*0uu#>H(V!v+{8u4l06QeWmmRt!kxZC0rV?2sj7rb;>^WxVmoVhcO)vvxqCRAv=Wp zC&25(ZjW#I71;%B5qoNPYCjL|m~MNnKr39Py4QX&0`UsasoqRxn;ZIfoM zeo;RZCYz8irz&F(1cI3df$Ku|Kb*4oxkMZELyo?o39d7+Q!?=e5~}O@1OZ>-w9TtE zCAZ|)J#*zAMi-Z45nywTEg%Q#8!PeUdT=IYUQpsw!iXFsr9Q_=mN+hPJ5fy)G+M{| ziB0QHVQMr+oPsGwE6u;J%{I%nXjgPH_&|bXJ?kSjaKkbtfQhlR$*q|xfsfE9W&Gw0m1bam=ZLGggdA z&sZw511MDE3qC@Nz)}rq2#tX2XDIb`1T$334ryHd5z31~)E)OrUU}*hBJW#+ODk3AdvnFByGa!b)D-R6J6`awg!Z^CFzS z(T5$_U6|=Sd-c`*Skck*=E#i;X4KSyG|)HV`~W~)0RS*ywMFz?ntPb}DXNQN6lru( z)|`TN9u*4= zL@}N-AP<%P0-NN5_QwDVd?*8(^m^AUwFjz>@`$=nI`CEJZ$f@r#w0`L0MR_MLbV*UuXbyso6^!>tdc z_XvlYS=F8L=6$WCl&kZ2b}Ftn(ihY#4phy=*Hv@Me-fHW8u~{ku7#GzH z2V9N(y*??rq=w3UDz21MuXcdhP5|SiLDFNo3N+d_(IB%36K{Vyui@7TeB;@?2Ri88 zAZAO!gHI$l7E;t1FE_#wYLdlgy4t3*Hel$|SIX1wK}0#kB^p=-E(Vs?9ed7+^$|Y( zH7t4@Ej%w=JuX1%H-a+CaKzJOKz*dH)U^aDI=PC2hFo+{deLwOt~{rN1W(9B&4=}Z zZmQ#4c1@Wnv*^1j#`V?etuqCp0vB(xpzuuj9Y+kAT<}Kwf=f#CE(YPW45i2klBIPI zAMZUy%pSiNc%Hzg`bT7EzBXYIeL_h(5)cMUqK>oIp^M*b&g0;`E-I`$RD3V>wN1Q= z{#-70a8XzysIdL+mn?fP74_r*cUzhaZEf`DCji|=->+_`SnG#k5^48=xy9T%c2P!AHl~QJK{}LQfG?Cv!sw4a5UjRTC&jYhed6`dpsQ8y zVhIP;148=SNIt{1@j(;VohpA&Gu*M6!1|x+)LihVI;BoPiLVF!U!)v+*8C?6JbD$A63%8sk+6+Lcf$@+0sK8-L3tytMeB4lrX9Lh*l zMv9I&S{rnPfG5*h;YCeD!jHzkEN>Z+6BMp}MNaLvMUd3x3d`EKMTBQjJ)!YAtWAOI zpr!tc3gfEDzS8MO#C&gvwZIRYvB>ihayS<5PYuA}lQR$#HZ$r)ed8faWTy^C*^M`A z0j=y&lO)ByDHYYp=1~uTRY(|kNV?FZTDx=!Pr~D(yBOc(6ctnB4{c^3e%*RWJ%G2y zCtcSLjmjx&E;x-moJV{DG)pND=FU~`WUY8Hy3YJ^aZ0ZL%}}v9=|!19xB!=L;1ofJ zgng8pp!k$QUefD8z+U)KG}e@(f|IMR?$32`zUg(lBj7l`(^>2I6M)tZ-b`LwmzEU; zP_$|&HQYf|cq=%U#z#j!@sqNSG0&@a!sTWm5iQBmj!|*$l&$wbxhVepTCqj#KI@NN z(mmWmhLuw2q~TEG^H3ucJYAkiO9MEE#u9!-fK#!$IQ*4&n#X2*p_|OEuWw>~;U5xq zzLjg6CZBQOt^pCJAwElLxmSjqRg#2woFTNp+Hv9Cu|?by;2GA~On`m?S%9SQpHdk- z%y{;vRCr*2OJ!5_MvA`06y8S>#OrY_;=cNhyf}Dq6s3kc1B-=nX}KDPYyumE;qq|> z+K&^*GV#an)b6khWsyTp;{5a~7)9v^)O)q(URq&~2j z}9=y=0**(hdRFv|MR>anZ(rT zUHTs!1CV3ApYRUnf-JXn%y8)1i{AA((mgNX`7&H!g!`+)hf)k#qW=+I-f!WxevOr= z3=A(PF8NcTtU7*>j7{HaynO;3HM7)mEr+CUR^*Vev;I0IP2LW4m~$?=p@^JlCedDC znqiztV2Bn@Q?wGC;svgw=oAw&m-{>c#xc|+4b$BRMP91y5qedtL{!X){A0m?gZuan zDqPRAM1=4+BKiqUuT6_N1wZRC$=@uM7SE1K=fL(_H{o-+2&$Qt}ymSbW&v zKkKilVsPN90VUWt9G)#p>;PNgD_Ot3M`axjuyGok#FR3?20!P1&j&eakm%C4uhjHT zRpd4j;WX$W>tt{C5gPrN>zegV;A;;q8Z(Ys&`hW{LUBd z3E|VdnQq5S$MrN;*^`FhyOrd6HWTMiqqr%!sgl*dU|C&ulqL+ zHmzjTbg}_in(V&IguLws@#+D3+$5q$Ekv4->le44I$M`BJEmKGuH~G*Mg99eDF222AfR18!q#ZuW5@b z6P)_4><=XypV{p-+|tiab}CkW-SfNA5+FT%fXujm)d^pA&Rblr#ZyK|tgb$xQPt&V z{iyJDPV(YG)HL4MFop)3NhSXXl^aJwn%#5T)_K&wYwEeiFo=yZV6LB3 zWC)@L8s;xft)suYNV)$BTF;Vt#{3afULE&Md&g3=7>K+x?iA}%`KeR>O6IZAx?-tm zrhT=@yV$4#OLPn9${pz@f#z1w-LCQD#iGUSc%Ylcr@tle;}t^FM{ov7hGdR4n|T52>hlQ-F9XG(KC|}c!tsaSYeRL zAGmTqe1+h^rEBiX~ z`-5VrVV*kZ3RR2L#F&&UG*?0w875hFlCU@TD0b2NTp*0E-z)0VC_OA?*I9aTd~Jzh z2<};Mgwt05O5rs)srQlAq-t!eh?=g4CU8G*k#ST1ucpv^IG>_-MCjg}aJ$Og50^vQ zpK?%KH)2>Tx+{AE=yJ`N&CCoouKan!r2IiIQHQabmyv50r{}an`tyOvG`yx^RqYxB zSc_S8xQWlc+Wp-c@TQXBt>F&d8lGR%{L>nm|ED#uex2Lj)GveUg1^DY%PqJrP~H{8 zn}haW%|T^nr-MzB&<8_ z6*hJuc$om%x-j{&?L4Sis1`nx)=aF$#H;IZqcajDA|lvDTUxGS8=oeY85GFV%qA1g z{I|vS6<)rJT!UHd0TRwj!qkV6u5-;+-nap>Qi8{Ilz;5HukiAu-R2VKzikhQ7I*mh zd3Zcx2y{RnbO?qezWg)W`A_6E%QR7(9OzBgXGiS*L7HU4u#kas0#;#(b?KpgAXxyO zv!xq&T!7TYZ{0B(h_PyBc8VJBo2)l=lm7v68DcUR9P1c+0ZTMkB|Lb~=0%j+Rl~7&jVAW8T zohY2ig&)?|HO)Gh(b%{B_A})brL=w+c?IWotreZFBKd*MIq^yQqy5l6J3#t>B?SNk zHIXz}Yu=Rx-20oWg^3TBB1M`12+XhErW}c$$VCtMTo1$3X@E9PW^l?mOw^0K8sF1r zruF4X?%mTS)U)PX`>p8Vq`<)`ZRFB8G%q1dxg!_hq(jT4&tFUy%#e^!G=g?Z3UP>m3AQ+%mt zi`;A3vJl;JePbslTjo)o+dX@sg;?K^iZ$5+BSZXs08GI7(Ph9a)-m#lJ{j!MQc3Zb zrKv`sjU^l@fd`N4nIc3z(`EQk-#Tp|(3TUZ$=tA2y}NjbNQ994OrxZ){(km1JyKJ$ zX>0Tzp47>EJp!C%)CQ(`^W{JmCi?1BCu@a9h|*q$?0LvoMqhl{kf!e>(#SwV>*5n2 zV`;2=GuDoo*DWytSNdG@tf;m=X$Y%(;G6Xiy`iv)O>TvLRZKhXxB+r)n1s(Aa9Ua| ztOjm5;&}pW!^>7OlKolq&tx)jne=bonFTVG3J2aigBL1@qm~Zz1ON-e^Kf7@IU0CD zMYpNTDbgSPTPP)yxt}FI!QWh{1(#LdpCU-36nG!{yC}zQbwsCJE=QOC^`5w_RmcdM zZDgmC-g0d@p_=ds&7U!$vT7z5{0iO=O@;GpJ9gbg!-)%_hYfd;52}$1oAonQr-6qu zPk_sJh_wRjoK-^(?g!}_Xzx*TiJ=`Ps+G;v%gzM4BjH%VB?2eQs!^k4_-*VdAIG+}Z~hap6hPXrX1%sYxhEI9|fZ=oy$bdw&S@oVi-ST*y%=M9@XY_70JejPGHZ=nroKV@#TV4 z4Vg%lWels}?g4HciS9jrKOK8)IH=MUSq7BU#XCU?l zxBd5^U1W*qHIuh_gX}8%FNnGo7HNTCVPmSkP}E1hfh^&&R|Q;cv8IGU zf8X`uCPW@m*RcA zMoK}t4hrTZwpqnyXr%RP*)*McZv#;Rv-(8oV+waP)glldkr*G1Sl*zw=J{1Yb|lRl z?j-wZ=1-d~T+`}RMye(@`3~$wCGGA5h}>p>SY-i-P3!bEn(R@D+DC=24Dv6B%vHF9 z8=Jtm{-$~nL;(_>b8$kgJ+XDBGh`({OB5h{+2fso@8&6m^mJ^7wJ8^3Ur=Tw`B4O|u`1BtxNgA|LLg0GefEk)XC% zUCWe1TrUE7K9k(Q`Q?lA`ed`;vvDF%608!xP7kYVoR0I3&%LJ&^Y-i)ZnT=*jkO%+ z-_ZUwWEc<{MRmbMz!NtIUh+nvAySN%PE6i7T_>b$R;&lRfFs!#%q2Vooz2X>B)Ia& z_D|ydmlDcpfJ{lVJL0je;dKu5^&U?c3%pW!##O>+f1gg3A@*#7*oOPl23fEr4=JhN!QiQrPqu1fK zI#dBtEd?d1p>Ku@_YG;#CF|8KaO~*%C$=M_?3`J(Cm7o+DFlw#x~12}y=3EU3HG_{ zQXRB4eT^92xPF_SO2%+0|0q1W9&^kT@wK;3NUE82;JnE${4NC48 z-N!rD?Gh5aakC?Itk?_xrt4?{h&d~Jw`t1UJ8i+=MNN8u%l@&$G9&FoSngZHc6`P< zW=3GH<+S9O=J_ry;(6%&19glvkaaZ@eW>gwn^ssfs6a;sZ|k5}FbFqyGZ}I; z1@lRjSU180oo|GqQPucyP%u>r*We=JM`VP2=EGY9_nYWdP5G&PiwJjeiL27HpCymq z4~0S_2NcOM3WUd*7F2fFrSKC12r@^BO+sCv)C}C=j%tO{LO2IAAiDl@)TxXB{cdlG zVY?ry4n>p%7GNvs8?|xHkc-&r$iWH4(QJhR_)cP+Cae;r-uT8XvjkolvuX*6GN)7D z6*C|eG8y4csOm6_6s6D}HSTi04}&s#Q$iXx(MM z?oQKXPNaB`wCOhN&Wx=D&OI|vE)au&9=C~}@E28BgFHIJ`Y@*c99;>_2Jt9@-AuQS zqx*lVgj3%dyqH3@A}zjbcde`yC(N?m*1eNpZhm(mzAD}$JTp>v8RA29=(8t0jGfU)W9CiwFTe-KLyf$9scRpN{ENF;gOt{K zS8m}TEl-s06>!X0(eFTw+z37A7BZ9)8Ie&FzUH7Jp2LxV8VlcuIHjV z$8d@l00~aQhcoqj`TM`$6!?WHjkNqMt2cq@@d>b#2tbBDFqQsa{m1-13ia=^NdLe7 zW7zrl{&g~_NBW|HPBazVe@vFq_~^5G&8ufGpKZ;a)c8LGU-5Fk{f=~S@B2=DhT3(C zM^ba@JZk-H1=|A^0?O^sDfDgUj)>p%69YoM9R?E z6dg+xFJm~@^u6tkLW-ro$`>kEH8dHQGu(Sj`Zk90%&OzNP?V9vDh2&kVoO+ZW61K#c~HjXVI(bi#-Ks{c!SeK z7;LA&MmiiO7b&{7R0Iw8mNfY=fn4HK{`3+1Xj-iUB4RI2dh3nckVidu$39!DIUmeE zx3O;0C^9i>k4}|oxvKA!WGO)Ak{?PSrB3mX!U`-N*)?-1_#=2?)}7Nwd~&BKF$5%l z*_*#39sNf4Y=0SK-EnWRbAgJms3WOHgRsl-X=vBbnMF7q=PJSQMl7p*Y=Bp5C%*UN zRL*;cxzLZ5*etAF~`bz~PJTKD-P~KnT78zb?Oc&$m=fg5`2-L+)n6h<<$Z(r!-DrsRrvvh) z5)*SV{qo9&7}6jXB>Xac^THn@WdwoZoZ~e+gx!?JcnSVPHPad0d1#3NXG~uaPqeAZ zoe~bR^4Dz z4$lvWzDZ9uT^csZ7e4QH?0zO{0SjQ%ZH@`sxVpW?aGO2KBdt)tkp!m8SH)o?S`HK; z6z!Z%8Q+;JeVy9Rlfzba%g%q2+bMm67U|g-{ zy96t&+A6b#D1chf8_^uLO>WRq2*Z;5#(TpV3ZHWnjWAN|9cVeQ)@@qv`ZqQ22D}x_ zLzZgR4J(w~rLwP!b>0=NG+BzS?U80_4le-Z0m3#0G7~9^p-Sn7ELOB&muTDyYf|4oPDN!`< z*M|pEyS#Yt12wyuHUneJO)XbbWf+-rZ@egf?8PBS7CKI&#v}5)?6!I9Z~)+XyTpxI zqIxJj_ycX(@|ZBE>eBUvffbusxC&F(blr9E&ewv-`F0&-4Dxs&ZD{Pp8_5Coj<-xU zc$!fYIR|)YZ}2y#r04Qmg*$=t@3{OfsH_F1U~9cB%V6daqP_SPH^yu?`aln5die`x z%-J|i18hOrsSp%Z+h-b6-Mit0G9}Dmee&apkYe&_Tx#YY9$Ka4O#apg)Wtyt+KBdZxx659dAY(vABFz< zO?b_g04Gkh&OS@_*-Iz%=&5MFw>odWCRV=jTuMa?97jE+?G)Z4Jna2ClF25R=oB{c zUfRhWRp*L)xy}J0hh+FgY}q0cVu`^0du2m%Ux2+Yl>L=AUJq4g9_CQ8==X2scM|$X zw=o|BNdo?;Ks1>vJK!Zf4sv+D>t$2^TuOjjGqy|8Alwlte&94a*h7A;v-jj$zKZ+q zeWk%|?-xJ!S8`)7KE;s2yC~80rB|qe_SQl|O%z~%%*+J+QXDq^-sfyzL`Ba%aw#`C zqwfv$ag~P$9qR=PyZro^g5OFUc>5Wd3@^;`a0eVNodB`)@GoS!*ur$jwMhs(j2#AN zSz^|>2zG*B=}i?8TC$W1!g_<=@-MAQKP2yznTmQ|?xFXh(@q!^U!(2UneYnVh{*AijqPN>->XHuC*ji>F>?OBi(vNDzT?CzeH+XDt|95dFqQ;%Nh?B zSLh6ZzQxw1|39^zV{j%>-=||^Vq14?+s?$!B)MbTwrv{|+jb_N*q+#!IGd;H#Xj$| zwfk;uZCCZD?k}hN)aldzbDiI1e}$GnBynrJekLWWUz@-d-Lxi!CJ~7*G|lVWH;@=( z;Hrd6F&u^z3(0<9t4IfiB7d5$BL(W7hbImj#~S$^#Xy=0$qoO`o^|sK*g(XKlEHc6(wwar=Meh!crRHqujATE7k3rGqwlXuNN;XjbEeTS$5VY-jM1q;`a z9eY$haP*77wNSfdU>tUqIr`)k0XxJ`23#wWGJGTQPS!^OwwGxMU;qy5K12{^5~=8* z>+!*b)_`{^zMPg}^HTVo+{cS1n`(n-e8^?Kg!%;VUc2iw%KDqaEB0agJlEt7`w{+= zWIcvZait4YeNNeZO2PukjHAribHU$T?*o?h<$CGQ@=*~KTK!Kn?K536#TmRsyd+#- z(`5@<8-EImy!tOV%lye-bVZo`A~!WBXXD-@b$k|k9(xom9ixvT|rj= zJ8QfXnfb9l#+#`v;WNKKMp#Ow=4T%-%kTBrP)rF(spSm5je_o#7?%n)&^^x03{^1hj&(5rOKrI>)?xxqrFX`#Q~Lw1t1%qF_p; z($=HtoyXF zcFTRsif)iQG@p!A?!LffkYK&?Ur|Y&-wka-c<`2uqhix$vuna!|3Tu4Xr0>Ef^>U9 zbKXh9aIQ0{ayvWw_;8SA5*YMg-rpL7ka!}kl@E#Z*pEW0M864fbyW(rX7d__sSGhy zq~z4aJCC<4o_go+BXl}YnlFeJ8A9OJ+giKSMHabC7vxJQ!?;mv-KKuot*Ko2Q`qKJ zC=cF>w{nCc3Hq)DQ zx)jD9*brH~@L`8|tQPT7suM$QK6X+>0)-Vghk}`yv$xhc#vJDEGx)gJY84W*f?iR8 z5AQ)ZzL~)g?Vl5Zd^DFH7cyYPYh6CyRNMZpHl~9|TZ=8+-WuM~4u_&#L6meFf*;23 zb!ha#Qn$JS7Wf8(6=F%yS+B263dzys)VX5^hEiN8xx=5r=7=QX?TuIIv^zoB8Hk1D z=onFdx`wu9n?eha%#ow`9YEV=@-@+C3YA*>gj+M$!=B$wr6;6&lR|$D6i}_fJ7z%y zB5+qFzg`Rx6FDSf-!P*>cOAVQ;jFq{XBPE~AgHt!pW*w!xLfNR0I&S7PHTd>V999f zlZbes3eW|RL5+w3Zlu?B4)RsqE1Le)HYC3vvbPZ6{u=&bQ%4>f4W}1DXHAx&4EH$b z-(h{v@*!Gt{RN3JfRc=;7Xhp#AmuSNXsdIir(6EXy8+1ZsIpsrQ-MmY=R8)_i~-*P z8TfOt4|PbmHs-QiWsYSE6O0F;>CN*3YwXXW(7yi?9a?!Fxf|lh8H@2{u5XFO zj`R*Z$rP~+)$g3u@gUPOz_Zh~K6V&PB1e8;r$~kdd$pA`cFPje@twwTqCP5Qn*{Ig;EHI+ z>e;7INa~wVReYrzO0)9dq`}z7osI}&yL9kKf2g@wYd%de{sCxCdPAurZ?{lIb<*eM9f_Uq5Twh+1@A9e5Y$^ zj%m&FFfo*?nKj7GyL6UN%BsxFlUBcuLWiB`hPHCqF!IaCB(#UCuKiT)gdrPFHmbpc zShT3_Rmrt$z}$vrq`|$zmr7AgB83kF)KzRzSPpveS4TJ>jHV1lD&`*CyHD_6p9C;S zubEdaO{uzjwfN8>V~pvaN;B^Bc>`W8befOnRN)d#cQxod?PHuWh!k?UVw+8LLa2P> zzx(#%xiR==OZS-F3|c6M)A_xD?p316`x8hc@D*JmDd3x>H$@3Bv&`=pcC#9kJ1F+Y zX0(BVajiA|V{SDAUvUWFWrdx5(+MKosufm~TJDS(aflp!^pMQanh<0RpU zf=rAJ#N`?xM2R_(pqAAg=Z5%FkoBEJIht4K44pc{IMWiUpREd_~>=#=E{1mJ}wtL*fI#=PVkLQ}b79+MD#Zm=a&R?5#9%kW76SDff5$ zwbGRc>NvP??&PCHbV+`9kIaK3hWx4n?j5VB43miFb#Tfm(~PjH<>g(DQQNRz0tnJ! zHVykn(uaTDzc(T8PABM4e@m-tn7mW4H!>2da>yS?F@a$k;=^yQ_d?5jBzBZJSK}>g z`g4F7j6fn#36fLKOT3is!sdmKX2DE;&p{SW(Hf|YrY{~kG!*~MBVyXiS1m4#J&5v* zJqV-$Nc=S@;r*<49QQ|jH2{@OXQNI(EM=z?l82oVx01p~L<;Vn`KHg=!J9f6f-oVv z*r=oWnc({Y-H9-g_^*Z=_Wxc@gqfI?mE}L>Da>pvEdNv?xlq@)R#C(LobGOIz3^;p zc1o-tXiH;R(d={q4~vG3Yzk^0JLPCf+i6)7)Jb!pZ^9D=UwR5F5uU{&ViGf2z-IT< zM&lfl!(>H!fMmP%-yI$r;>Uh4q%@sfy4pxJLG@-IvA3PRdiB|M>$%Eyn&CepBnDB0 zrQRXgTHpSiik|lgLf=}C1YP*irpx)s0Ky&G!ON7AoVB&AFY->m06lP%a6B#;CMf2E zbM_W4hyo4H2hyi%JgJaOC?Z21H`%NA9(x zXnk!@6&N6CHZO7%bzCHQIBS~O?@bKw;p%>9wIC?s$9X+4aiBJ9_=)AhJKOLR@N7pi zX$VsJmjT+#3pudTUp$ykNYgI?9A_+RlFIoQK*Q2A2ZW=nM2Vn;ET22qz7-L!JXWH6 zh!mH;iI;UqPt@I*vHuM3A)B-sKzx9~ysmicj4oroWNSd@CA+ zBy-+y9??dPBr?!ltzxbdG zf#!+mRk);tsx}V_L#9g1{w`LNV^Wu4Dy8Uq$__d`=Vv)=_4J8*HUDK(!q94|hQ2C_ z)g{oe(?U~mn!k67t&|(PfG%0ts^ZWYgd2(T;ijJY!Av z^$pl|TMaoQN@wkx^uJVi%4ZS(i!fy?jKF5r|NCl4a5 zYD^_`cx;ZaH~6I_p<6x*xo+pa*0^=O>P2Z{#sGV0VWOj_=#&H^y}^ZFxAy5U1^f*63Ewk75F^0IjqJ0NP) z&LJkRMI)RH0xKyPs_M_KpG$|toAeD^@j*1~1kXi;p zQLz$0?}ZU^M>2;_h3Fy3m|*wa3G@2A6k(V_QSRk;aw-us3ou;!7*>ib6c$`JF&a6x zL8~0NLY81!u*KJN%GHgb=kP>dxke^tWB4JMdJ$gdSYjLO*l7wZNRqAjRKkVLFA0v_ zAVjH1RSNRS@Ux^^ICyiW$pv4I; zE}ELieXflg5pawkm`QYXTH&xFxv#ZaYW*q=^;+X(G4&*IZ~IyYOOS`=!J)0?kd;WN zkDP9?!J~^->u}|;T>wsPy$>r1xBMy|g+;%zUiE1vPaptZ;|HZ`Cr&=HgfqAG?Vx=* zr?M=^$)nPZQIhC@EIL}FK%BP`0~LZ6JfM9*zuMRJ*V8(OXyK^n3n0uB!zUOTG&=vk z4)PrTy+NLsg@xmvMs>Y-dCMUtgpj`iA*u7j1~YUWZ~-UDBAD+57RWm*GM47XMO;xE zJq>uJiUEkXUU}}RJ+5&CAqX!qMQ6-|*kh~=4klqtJLpn5(>p;EUK)0s>%~od?=8mJ zytqY{xFGKgr->dxgTtesSINF6arowegGbG2J^CsyBRJGSN;dF>k{S2b^HDh{&cm@e zSo{YUfXTdlBUzZEwLIVXwqpnBB@&%XMRtjbkU!EDYEmD0Jt~98J#<)q2fN#1+S55_tB03Hi?Itj_OEh7GptuAzLBSfb9=7XH zj_E)jimrzM;Cm#t%$Dx0Sz-V(`pwKoK6EIxB@G&jta0CD~=;t}2KbO^ZMv>fVCMLC|Y~w?j8UmxI5UJnN>4 z8MC5p27OO)zcGx^n>|zSt>UBoIinF=M_oLOB-I9`N8sEJLNl6I$1ZCFhM%H_wzf{V z*LjeLNRbDjeR~D^`V|Mze2o+b9*}Y0BxEdD4W6tMyJ_K{3|S7^qb))~CO>O2&a)sa ztEkh?l7||6y4PmAl$rcS8`#ZZ>jo(b6cyB}T)@xw_E#rL* z?%jxcSrVe$_{UaTc2xWma0Yo={pAC*HFk1#G%>XK4?EvK?1BG{-oQ-E%+AU6<@ozI zcODl93;RFyjtehXZ`H-_Pj0s?p9z*EgI4!d)U1|86BOd7BwrM2UttJAGAx5KmFuQx2`sv(8yY3)<0i+ z|9*OJb>DSvJNcTm_ikUkO@GY*VPVLE@5`a<0blCj=^R3BJg{kKq5eOIF*$433Wy@^ zmT9&V# z+F(fLU4v2mdW)Ou54-)MJn(Lddn0813$jWtMF3u)2cvXaTiuU4B8OP`_^?OHyp-!#$ z*oD-9QH)tr#5xs)o<+HbQwJjFB0m3cM&^mlmFN{CG6bcPF1lyb&zDD+dNzD|97{`( z`$)Rx-GvOKAib`Y0IndL<7`JY?Y4{^Uc2lrlV)r*?n0uFH;J>55itwY)og&bf{KG) zIrYSB4-g&tfqR-HasrS((14J$q-=QS_K2E5&=*4OaBbqnSe`kVx*^;q%xclIIjAPs z>UgeYnRX2S^|=Rgsp+3VeM`O zMENSSxA6;)Fg%g51<-S_)IJCyih|Lvw8!%Kd4*VWqsORE!MKvU5a=Tc*GNRUF-Q4G zbrUq>NLj*P>W3I&&}Q&OmpHm-U-v6vXU6+a@c8?%>r|Z{%0sjWxq3?yPJH6ze$;mI&=J)uXu-#!F zU~AowPoTJBhHQY7BM8KOdp?Ceb?FJM{9RA%5+Aa=9lzSo<>dgohIGm|+CO&%bwKjS zo9;LIb5*E4l>@T$ZunXGUE;cCk=T4;+z&<}wD`pEVU#>8xJr&yzSN^Rlv$QE=})fnWIpa=8anGg(~yITycv zNy!qBm9Fdoiqxqwz-H!MudjIb#+TE8_==|Vpg%ooIgN|kFVAqwT}@e4vU$t141eK_-V z$p$PklsWz;@{&LBfGV=MIwC1*0g1rMK_$i;#e@Y7DF7UGanJdMn5lEnyVz1j4<7Va z+3~1^1zz!pDbKgwhP4LWg8h^J#=%zJdD-HA+@Q0fmRQme2zf>E@@z z+}2+lu-)M(BJZ4efK#yQzF9bVL?oGM;O#CO*s74o?9484!v@oisC+!^gDRx?R%~X~ z``D4|Qdl}MH8-m+s*a?LXQ-@1CbT#-PDNt=n@%G+!@|6=n@PY!i9XF+{stj%K2Uy2 z7KWqcF=BXl!L~Udz7`v`M?z~k5sllnd>b2rVl4uv0*s_}1t$^(-U8BKWgNR{8KRid zu~AV@3FcADu`<51IDO27@dO@I1youesJ4TCE5?wGZP%K6pMO)HJBGr7)t_ISau2D! zCeo7M4EfeLMOeop(&Ndb-;HfvO;tpWE1H3|PU3{nD{;VV3$$v%?bzuLSFw@bW6SXuuJn#I~u35OaeJzvODS+pfwYlkvU zGci;anq2h40%jye`I6g9i(4%Z@vAAV2{R7$+A8O^!Qau&Pe&niMZ}l6NilJk4vzt- zV)zFvF2I%?6K9yq3r1}X99XHz7>N8#11`o1w6gG?uo}9p2=!-MYe-x0rI(zzll*m{ z8cq!cP3NK=HmdsTd)h78Y6ESg83NY&YckM7p6Em?nODp;T_C?co}=g{^sW4ciBXoM zDWW8%aMPUe!U{k9p6wy$`uT-NHc*x_L0|}n-Ulde5UGY*%~Lntk=sQBm$HAmCZd4} zkvo7!x~1hZ5azbT2BCms7}nLuF*{>W3eeQ_10p0tDy)*!$?AsM!zLM@lZ$#*ghne@ z*zq5VmP^tcl7}YS5%x*#QHOb8vm-Mlo8kXq?%M~Rt& z_89PIXpJH4!wBLJ?6IK(hb*X=$jn78>zkR1QQ%>F{jcEx`o!Ll?vNZtOh~X&KEf!0 z=Ne;bNBHu}(`-4@lRIkV1$7@xNdL^1kQ5K9r)tfq8kBm$fI11)PEv zSChI(IPpK7+lMKdNJv5Qmmd)3OZ;32eIjME46P3UOu%#HWOLHM2yg z*msWD;w!3?=i$C86jmuxHYxBZN>TEQ#|tEC9L_L;iA%;w&tqjYr)NK}m2JaxLAPuW zEQAW1luDEF{9W?Rp%Nf{d4*J!A1r>$FM?EHE{kZAh{*C&=W`Xy=FGt)ko|Q?b;J;JqF>38uODuvr^u3B_mB6+4 zu~WGGjl3hKlXYS+o7eUZ&^cML2dX@J#dqw4>7w4siI;8ge{J4NYz%|NC42TC#5hTM zFye;&=(}wEQ9EvQdoV!uYtN-~vlLK!L)t!iXmH-gwbl=0o=M`3$zT%5LXl?4h=`eE zEhJs0Gn82izM03q)O5GEr~H%Nawods;jIznRM5I%Awyk0<0M|fxWa5_{j2KXVY##e z@#e8ywN+rNU#HhStkw7AD6@wzJSgaKLmJj1iFJw|hvLquF%Tnnh98*BuaK98dJ#U4 z1`ka})>-)Zo6iCtuA#-|NEc-BmL4m$TxPlSlg4{r)FOg34 z9vrGE^7|#QfA(sJ$pNL4G54Wk^srC$brd&t;^%h^hzgdYadsVa3kzUmZWKF@Bh{aJ z-qTCOE+6h{;DryPb*`UXfu_CD;J~`ZvbWXBcb{{VDEFSfGYX8b3I6t`*UGd zG$CM5#it0{_`yJu0Xm30ToacMb42M|o&N+Bvoe)u#q!4M*$>kMr^}`GFyH!`lUS=G z1^Fno>m`ltUz&|hpYr^|jO&Wc4U^2+S$g%wUN)no*ddf!3W%qL$}h?gDVZ8~(YUm1 zaEMVM-xJ{C4Ap2R{eC?y<~?7B%PIZfY!-YXc*-2TV76Uzc%)Mze(B5#kU)ZV1>jeS zolh2U(J@`EhvwFrT1$)CaWJ8|p9Nh12H&+9(q9w$%}xB%az)?!CzFei7sW&c1ms;r z)+p50h{knQz@Z1^$7I<(Jlw51vInEjnN0i=_!f)I!c(O!9I6pbbvQw`*KtaFw1C6$ z{-tDnphwa=Cek^PCj$9UIo&NuYm6HS77t*eqM1CsdZ`VfO>Yf!<8VIGH0hAc%28xs z#dsNHNf0Qs(*m<0`aHHGGLO}B09^gCpkYXvWMO+NvxNjF<`I@7e)s)RY+>A#A}mhY zR7!Cy%JqO-lZB!oW0$)h^5WyfNw+$TlQBIpUA^7nhx0U7+D{H|Zj`)B8+V@B)!u?x z?%E?NMjNEJ{P-+MJj zl(A=^#_#@&b3|$wK$@!IVuO*J@BGcE2$#%z!4^yrY~2AexOS3s;LPC6WQ3!TKYW^s zTU6Z|U;T1oLflQWuG+%%J>foYK=e(F*3-QT01ujL-9~DV1JSyK)><%6ql;3iHqZ>f za1X9m9(Ftp7?XUrOaT%*_Lpfe;m^Pa{+ek>PwE=l54ArBZS=g8-|{}6=Fd8FL7sum zrX8ky5S#_iz$1m?ysKLYS}kG>`D=GJIxy}ccabHkH(66)<#9HvM9I_yU%VaqtrNNU zXwF#6lYkC@5QihkX}M+imz(n;U9wk6c|2@SM zNxNI4!p{Y`uKs-Hdh2BEpakk)Gk8MvzH0ca-=5i{i*p(qWfc0$5#Xwc&7HM-wN-*q za+K}Sh??B*#v!@otX)#+bhg}EN71Mzo@aa4YK=#Z%^@*Yol`Yg*Ak(%d)&R#%BbJg zLfe^3;_!zlW@eaYMq%yhZO?s-kDa_v(sosXjy$Zm{*0l#fv#}k(#=9+&K8I_Kvv0M zp2cg!pS6BhYz&;PeW1ngH55a=4JsJA40g)hXte!a!>5M0t*nKrE?V5wK#^Hx?0DZXHKPU@OKv7Mta5 z$?@UjVt9Hap!&8*<8*Tv^&9RkhSXh9umOF5Wl*|uQe!S)@F>^Ib}|~Hm2c_Usqe5g zcYwA!=@h=Ag)uk7sNNIrKy`tavAO3~*Y7IZ&2RnX7vqbUS>suh#-!`CuGa<51e$hf@jIbWUu|$s z7rV>!ONT|4dh;Z96eBwuV)xltmOxpcW-stO>`tuPzUA+l$(c#F(TV zPbn?Y1J*5Kab+(o*2i*T3eMl6xrEa&O=*oEtA?(13RpfYzzg-UrMr~CEFQHYe9+u+y^bBN zLoE@Terlq0(vmsVe=t%4lF&)7g+VT24Jt^w0(YK7d!3N3NRHJPJ#o1|>i;BOnX;aE zm^OB`V~la6p4%SE6JV2R5kvLsjI?qtuHw%V^|Uw~ZQN&b(_hd#IhJl;zqv}9Jm7a0 zowqc2WL2^{()jBqsQBJlDalkTxzNoCm}=W7xGMGVx+#0e?pAG1ya!6uc8yv)$W_?4 zM^b|Mxmw+9yidS~$NYfYh6%6(C>EUKP_D=~;!jY!Bh+hLHx4quS*>vwzS>*&6eip9 ze9dQ^UI((aypFrCDe+#hTQ4T$H(J&cy4Kskw@iemZaWk2X{w=gfNadTjlcFdUyW2X zXKrx)7N`V#-3LUo=>YK;3VUcViluhd3c+)Y#$NGQ7lx3iSd@tiVSk=G2~Rcs?0l@{ z%0JrzCSa#KP6CSescvS{_}dD`T3_sG*O}eTUH@iB558(OTsiyu{76zy@zwfD38gk? zgX@mnFN_al#ubL>Tr2b8yA@yo#WC1y!e)m`QlFxB<_VEmD`aj1z&(c*^`d`<4GL0X zx(tfQrJ@%S($VJc4t@e#haimkmHN`%HN|tA%Eg>&L$16!RrR0e)<7T#2ACLToU~NReUjt zua&iz6yFc$12VBkQcJFpu?bgO0c)pQlOqyPBV$STTT5IS&-iYXRZ52m=qr;;Cxn{D z271vm{Bu;>nfya3Mz0fra3}b_-ruWCVl2zyRXjS8xYN~OW%ANEXoCK=H^W2G($e3( zClsqO;!7hd<^0ydXiTG?Sb9ZQ7AFs$PqH!0UW| zL}~Og8#On-hV0VY>ME=7KBDCnmaEH*uGvIa)pUr6sZUD{?jaroKG0Ev{H1pvgusO%~YxL1zuK zAMK37;cJD~L0mQ%D$X23AW)t(46mvND3%)CEz>|)Fu1tk_SHEoEcu~jIQeW6!mi*3 z58y*aUotJZf!syRJ6Qhi6b)|;W|ODckB|5xNvXsBq~dKkyk*l$Bhc(J)_Z_@mC-=$ z2X)?LQsR9oXxHyF;^R+noH~-zu2R%)L#YM|W=am~T|^=sdP!r)K7Xf?a?y6ikBaqZ zfjCzEoKayr)(F<~z_sqfnRfyAgBW>}*SD4~y!UFiA4#pW&!A=(Hf`N325RB0q(UF94GWf%?cz9Qaa)z zWbFGE*zqzRLXL)FQXs{a(t~4p{8j^GhV$MoQznw3eK&f!x=Yz=UT;4uT}-Nxh;ZmS zY4mNuqhOe_Y{p`E4sO(bMjM1+Ad_%r{X*I)=`k;QWyU1gT%j_eU7RToUiRrYO<>np zvy@WK=vE)Gsj@!dU6Q({Y0$1TY8^ct58(*?mrh zc5Fd%t9bc!=AEo)qyb&O&)hKP=$Q9^r!>w=bE8Us92ydIXt?@aBrCkz)o_Ws{IGOR z{W!G2*4S9s2yrif`yB6+y$$Vk$mh(t_?pm#uMx|x#qa<338-l4=-9(~k+YDz%v#Mo z&cHAah8Js`H@Cy8{R<%@h570r#&6T?S%!YaEq=yYLMp4UZuz}2jS+X8{1c}>>?7|y zLerv2txu52C8_H;Vu&jcI4~4Ztl1C8Y&xDEJ(VpWz)dX~oocTu!`9Pz@OoDY%`f6y zk2zXQg>J`4XlM?cm&!H!@-0o)17p=j7{M*3qdj=>97^0=UGIaNcz-uYao}gN!8iOg zp6bAV4&Fp;q{dwwccNCWRu;Fy(1xt6QCiG*D~iGD$y~uY6#e$RBN8XLt#0T3IN=2! za5{2{B(T_Ee}PVUdGu~pr1}R|iTN9w zX}sYuuKuXJ+2W_e5_zSb)o;9WN=oh-_#Oi$xlFn)fdZCCm%K(6MyeR*)n;`QM@w@whhBQdYMaAjCnB9FXawp?f8u_RXEQ&s&b&}Nla6Js?) zh6DG}nmmL0llzRS2ZHIl;z8gfdQVg|;4p4&)s&O7|BQzpDXU0hM!q~!8emlLIoLEB zV^ZIWo`&Ot%jW61lylll+|b`_$!xpORuq~N5G@&Qrm~iWaWLyFF(RKMK@N1^x=jIR zNl;vV@s!g?KDpDzITR{I+J}mT`0t~|NRZUiJj&;}CX4-sX0c0l#*Zpo$EXN7;mwK$ zh)90V<`S^cOu#Y`A380)OeC^(&1KT0Do(x7h0P3>kTCLf7=fQjg`myf0hD|5o)~;>7(BV2kpdF{AnD>zrheCWTPy*apoY?3M1uhEJHoH zDjD{3nQtS10X71(>>M4^GGz~(X98FaYQR67X z_Oa{8n-idnbM`3bD65&xqNdTpyL3cK^B+}78Rz86e1_g~t4l7X8q_LwBK20eM{w-F zF1KxE8T%gD;fB}plsUq+t*3n$E?hspRYhQUii+Pu=5=V7;}S|WthP#pCUjxtF2s#r zkNyys^%O~5eq^IgoJ%24jzeZVnsG(!bWCZiiKP3}7`yK@IPo`QH2p#}Yq@$Xy~ljG zIq3!p)dM+5kXM)y+>&U^uj1N8AmzKoY@=)R$|xz;H*m62LmcaJWwjkRRgy45#%x^! z1wN-;fGzRo%sgDIbAe5V3-<@tG?|<>}$f7+647amOy zV#-P_&=$lfZ!3&lCe{e!7iCL`MFNz_OZrL5V_f@hNia?b?31sQSML>wgD$tl?r&G< zC2_DT{hm8-qYg*iR8)wY21dRS?4vVbz1W`k$Oe*6e6HSkHu!u)*|?bu?_Pj+FN+#_ zWlXKUO2dlHn`S`_K5&XW1hcl5S~y|?2vq+}|26+##=bwJ(fnJ4UyneG0!qOS$4!QX zJo7aqo1LOhb)IwlmY?*U`#Z6|W%#>JoV3vXii?%ehk9oK^{g!ywf;?7b_7mPYQd89 z@%N>+C!F(B|CEaEeX0*5q}Ib2wROCJ&iwQ%l=L{g^t8038d%pKREC)+e0s`G%2 zGjfKE&}_`&mT}MFz>n0PW~@0s?0}8F!~*I?kfu!QKvedCEi7?Pj(e(zZfvdzggQ7< z5n4N-W2YU#_)3I<4ctZ4630?<%ACPcywZQ2VX>hIpG(|p*FrMHoZUVxfXJR|*Hm`qE11EuNqC&R{N}<&Dn{2`2kG2L;?9q6U?yyV)2sBoMkMEVBMO)5hc)0v zlxPB^GN2XPdV#*X8TbA?(OD0C1~XYtNoU{ELT|CBw0U@OslL!l*HuA5VKcI3dg;$& z%i|IzpmpNVRhc0MEYdslTNR2|!W$NO5#y(7DRMJYry>EuGlM2VZJ2Ita*!TjBckIv ziCGX;YHpN25@-(e-Yldk?e*WjyKmou2#s5WdXo4u2+h3+nyQ1%kiR_<^7#nm2+=kE z>>bwU?6bc7D{1|KZwjuu4YP=NO$Txc0xTGexvq#&-jA6*;VEw>w4@I_@*swK57C0J z-Js>?EiOyo%V1Z$(=N9NF3V|OY7Nl}d2c~lveW5*vbm_CaEa2hajC9J?J(6)(KE8G zn4CW`zoJMf?Z&;GC!zl*&sK=j}3(6y*ydMJPhx z;G6)HrA$#G+#wn0riy?frW_IK5LCEf^hf}@^0msf5+I)`W>xS~S*FrzBMxFGi9Om?8oe^5i=Y z;m-w!31D$JECFb82%yIdwSzKn(!Epu5B2L$+)yBK0Zu zp~o|VNdXYpAlP8;DQ2;RKB14D@_WJbpk5G;QD@79@<4nL?onp-{RJR8VD3R@^8!8~ z?umr1z`LRE8D@Ef{2)B>j_LC`0~kPZpgqBlw*!7azktv7f_6jQd*y2c_(I>q%}xk0 zg1)Qk+q@#q)&~55@B}@^pRM)Jg?^!!W%SPlf8m~;@%M#u#yr-_H}lVhx+M?*pDFXt zh42JBcF3O>iUZ97!v*m~KSs$93m^pJfn7j$1_MCc6VE#2L;J^`WLgW(sjXc?zcJ4$ z=vqQH8!uY_zl%HR8ep6 ztq(TZu7WCTYArtEEf0ixyVHx4MvexVVi7{*FZp6xm?!ZrSp*EZ;%``1nkqL}m)cA1 z3ujjs7elk<)61F~+k>|}uieiU&LFi8b2zz0MAtz49ZNGe&K-N4p9|s)?ygC?oKuN# zded#65PS#oXa53%e4)MnEf9p5^}iBgiR-pOObDS@cj)cs3^davN#9UQDu;%VaM!_S zhny26!bLp$*BMuuQZQkQXCCc7cOYDxteYJ9p6ZIs`)__&SQz5gs)Gm)id$d~>(cD1 z542+Q41zJ*9*y_U9{U=L_j_EQG$u8&ZmT--4WhwsRYO$XhuHO;Ejf9y|G|QqjSZ`! zYY*OX^Sqe+a$zDXw%8QU&~n;!|53VO$J=3c6tf)y7_#Jmte?Qwk=~ab43Fm}2Bd!8 zdMW34-OOuMDR{eJj~GU%l9kLzDTO2-ib)p2QOhT|s%9vqdB>?%r%)9S!ct4m&G2yd zKD)1M8EQ$f&IiAB5sNPf@W&@a2YO|q8Zl|*gc;M4COuy`F=?vAZ@rkx3LkEiK#IYCo>b1DLXeS zyAh``2Pdb%|M?ERf5L-|fFPih5+kx+*y-5V*oeZ#)`0MX3@}V3@B}$_p+tvwiEld3 z6yfNVAQPqF(y0baI{7eFzhT8f@BO8OQNE2oqE{)y%LzbK?t&+L;iaxb;2;LE2&VtQ inEn}PBL06p;GCQd9i81BO-$ifm^t7mD8#=@!2J(=z*g@7 literal 0 HcmV?d00001 From 916a6dca34144d44a3a9c7f09f2c5100f0ad4183 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:02:48 +0000 Subject: [PATCH 47/69] Changed requirement of X11 to not required We do not use X11 in this tutorial thus far, and removing it makes it work in docker containers as they have no X11 session. --- tutorial_3/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial_3/CMakeLists.txt b/tutorial_3/CMakeLists.txt index ed304bd..793efe5 100644 --- a/tutorial_3/CMakeLists.txt +++ b/tutorial_3/CMakeLists.txt @@ -36,7 +36,7 @@ set(CPP_FILES tut3.cpp utils.h CImg.h) # build our project executable, and link opencl lib find_package(OpenCL 2 REQUIRED) # find x11 for linux desktop -find_package(X11 REQUIRED) +find_package(X11) # get thread library used in the tutorials find_package(Threads REQUIRED) From 50acb289b40020f78038c66ef3bde2a8f68a2ff9 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:03:35 +0000 Subject: [PATCH 48/69] Making docker nicer to look at when interactive I have added fish inside the container to help people especially new people to get around --- tutorial_3/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial_3/Dockerfile b/tutorial_3/Dockerfile index 75b2ad6..5d6ec54 100644 --- a/tutorial_3/Dockerfile +++ b/tutorial_3/Dockerfile @@ -27,10 +27,10 @@ FROM nvidia/opencl # pesky CL/cl2.hpp RUN mkdir -p /opencl_tutorials && \ apt update -y && \ - apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev && \ + apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ git clone https://github.com/KhronosGroup/opencl-clhpp && \ cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ rm -r /opencl-clhpp && \ - echo "cd /opencl_tutorials" >> /root/.bashrc + echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc COPY . /opencl_tutorials/ From 2d85e8cd236a8f169fb8c0648d0800b69bafd022 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:06:15 +0000 Subject: [PATCH 49/69] Propogated fish niceness to other dockers --- tutorial_1/Dockerfile | 4 ++-- tutorial_2/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorial_1/Dockerfile b/tutorial_1/Dockerfile index 75b2ad6..5d6ec54 100644 --- a/tutorial_1/Dockerfile +++ b/tutorial_1/Dockerfile @@ -27,10 +27,10 @@ FROM nvidia/opencl # pesky CL/cl2.hpp RUN mkdir -p /opencl_tutorials && \ apt update -y && \ - apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev && \ + apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ git clone https://github.com/KhronosGroup/opencl-clhpp && \ cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ rm -r /opencl-clhpp && \ - echo "cd /opencl_tutorials" >> /root/.bashrc + echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc COPY . /opencl_tutorials/ diff --git a/tutorial_2/Dockerfile b/tutorial_2/Dockerfile index 75b2ad6..5d6ec54 100644 --- a/tutorial_2/Dockerfile +++ b/tutorial_2/Dockerfile @@ -27,10 +27,10 @@ FROM nvidia/opencl # pesky CL/cl2.hpp RUN mkdir -p /opencl_tutorials && \ apt update -y && \ - apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev && \ + apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ git clone https://github.com/KhronosGroup/opencl-clhpp && \ cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ rm -r /opencl-clhpp && \ - echo "cd /opencl_tutorials" >> /root/.bashrc + echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc COPY . /opencl_tutorials/ From 83a67f4cf1de3cee3dcac4ed4e936c5b4eceb0e8 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:07:30 +0000 Subject: [PATCH 50/69] Removed defunkt sln file from origin --- OpenCL Tutorials.sln | 61 -------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 OpenCL Tutorials.sln diff --git a/OpenCL Tutorials.sln b/OpenCL Tutorials.sln deleted file mode 100644 index 4fdf357..0000000 --- a/OpenCL Tutorials.sln +++ /dev/null @@ -1,61 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tutorial 1", "Tutorial 1\Tutorial 1.vcxproj", "{E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tutorial 2", "Tutorial 2\Tutorial 2.vcxproj", "{9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tutorial 3", "Tutorial 3\Tutorial 3.vcxproj", "{8CB4B79A-8170-44DE-88DC-C73EACB44CB2}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tutorial 4", "Tutorial 4\Tutorial 4.vcxproj", "{E95D4B5A-1F3F-4A31-931F-7A99CE219124}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Debug|x64.ActiveCfg = Debug|x64 - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Debug|x64.Build.0 = Debug|x64 - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Debug|x86.ActiveCfg = Debug|Win32 - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Debug|x86.Build.0 = Debug|Win32 - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Release|x64.ActiveCfg = Release|x64 - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Release|x64.Build.0 = Release|x64 - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Release|x86.ActiveCfg = Release|Win32 - {E99F5DFC-113A-4BC3-8253-90A6AC0C9A9D}.Release|x86.Build.0 = Release|Win32 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Debug|x64.ActiveCfg = Debug|x64 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Debug|x64.Build.0 = Debug|x64 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Debug|x86.ActiveCfg = Debug|Win32 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Debug|x86.Build.0 = Debug|Win32 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Release|x64.ActiveCfg = Release|x64 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Release|x64.Build.0 = Release|x64 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Release|x86.ActiveCfg = Release|Win32 - {9167FEE5-0E64-4275-B2B2-A3F87F3A5C8F}.Release|x86.Build.0 = Release|Win32 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Debug|x64.ActiveCfg = Debug|x64 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Debug|x64.Build.0 = Debug|x64 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Debug|x86.ActiveCfg = Debug|Win32 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Debug|x86.Build.0 = Debug|Win32 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Release|x64.ActiveCfg = Release|x64 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Release|x64.Build.0 = Release|x64 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Release|x86.ActiveCfg = Release|Win32 - {8CB4B79A-8170-44DE-88DC-C73EACB44CB2}.Release|x86.Build.0 = Release|Win32 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Debug|x64.ActiveCfg = Debug|x64 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Debug|x64.Build.0 = Debug|x64 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Debug|x86.ActiveCfg = Debug|Win32 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Debug|x86.Build.0 = Debug|Win32 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Release|x64.ActiveCfg = Release|x64 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Release|x64.Build.0 = Release|x64 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Release|x86.ActiveCfg = Release|Win32 - {E95D4B5A-1F3F-4A31-931F-7A99CE219124}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {609364E1-9537-43CB-85C9-0D79409A1E2D} - EndGlobalSection -EndGlobal From efd7dea8f7db9fa6cbdcfb61ed2cf19dc875e280 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:11:56 +0000 Subject: [PATCH 51/69] Added README for tut 2 and 3 --- tutorial_2/README.md | 30 ++++++++++++++++++++++++++++++ tutorial_3/README.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tutorial_2/README.md create mode 100644 tutorial_3/README.md diff --git a/tutorial_2/README.md b/tutorial_2/README.md new file mode 100644 index 0000000..59aff22 --- /dev/null +++ b/tutorial_2/README.md @@ -0,0 +1,30 @@ +# Tutorial 2 + +This directory "tutorial_2" is a completely self contained OpenCL example. It has CMake to build itself and Docker to automate all the dependencies and building for you. To build this project without docker simply run the build.sh file. + +Note: All following instructions are to be followed from the command line or terminal from inside this directory locally. + +## Universal Docker + +Docker allows us to install everything consistently between all operating systems, platforms, etc, in a light weight virtual machine, so that we dont have to worry about installing anything other than docker itself. + +To use docker for opencl you will need only two dependancies [docker](https://wiki.archlinux.org/index.php/Docker) and the docker [NVIDIA container toolkit](https://wiki.archlinux.org/index.php/Docker#With_NVIDIA_Container_Toolkit_(recommended)). + +Docker version must be >= 19.03 + +This docker can then be run on Linux by: + ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactively by connecting to it ```sudo docker run --gpus all -it archer/opencl bash```. When you log in you will be in your current directory again but this time inside a container where you can build/ test all you like, and then it gets wiped when you close. Enjoy the consistent sandbox! + +## Linux + +On Linux simply: ```./build.sh``` to build the project in a folder called "build", and run it automatically for you for convenience. + +(it has a shebang which means it knows what program to run as its specified at the top of the file) + + + +If you would rather not have to install cmake or any other dependancies, or if you want to ensure it works consistently, or if you are forced to use windows but would like an actual useful OS in your hands, then simply use the Dockerfile. + +## Windows + +On windows in theory run ```cmake``` then ```make``` to build the project then run the file called ```tut2```. CMake can also build visual studio solutions for you if you still desire them. I believe the incantation will be ```cmake -G "Visual Studio 10 Win64"``` diff --git a/tutorial_3/README.md b/tutorial_3/README.md new file mode 100644 index 0000000..fb944b4 --- /dev/null +++ b/tutorial_3/README.md @@ -0,0 +1,30 @@ +# Tutorial 2 + +This directory "tutorial_3" is a completely self contained OpenCL example. It has CMake to build itself and Docker to automate all the dependencies and building for you. To build this project without docker simply run the build.sh file. + +Note: All following instructions are to be followed from the command line or terminal from inside this directory locally. + +## Universal Docker + +Docker allows us to install everything consistently between all operating systems, platforms, etc, in a light weight virtual machine, so that we dont have to worry about installing anything other than docker itself. + +To use docker for opencl you will need only two dependancies [docker](https://wiki.archlinux.org/index.php/Docker) and the docker [NVIDIA container toolkit](https://wiki.archlinux.org/index.php/Docker#With_NVIDIA_Container_Toolkit_(recommended)). + +Docker version must be >= 19.03 + +This docker can then be run on Linux by: + ```sudo docker build -t archer/opencl .``` which will download, install, build everything for you. Then you can use it interactively by connecting to it ```sudo docker run --gpus all -it archer/opencl bash```. When you log in you will be in your current directory again but this time inside a container where you can build/ test all you like, and then it gets wiped when you close. Enjoy the consistent sandbox! + +## Linux + +On Linux simply: ```./build.sh``` to build the project in a folder called "build", and run it automatically for you for convenience. + +(it has a shebang which means it knows what program to run as its specified at the top of the file) + + + +If you would rather not have to install cmake or any other dependancies, or if you want to ensure it works consistently, or if you are forced to use windows but would like an actual useful OS in your hands, then simply use the Dockerfile. + +## Windows + +On windows in theory run ```cmake``` then ```make``` to build the project then run the file called ```tut3```. CMake can also build visual studio solutions for you if you still desire them. I believe the incantation will be ```cmake -G "Visual Studio 10 Win64"``` From f00b3c46368665c065385b551beed48ef564fcbf Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:13:29 +0000 Subject: [PATCH 52/69] ensuring necessary build files are included --- tutorial_2/.dockerignore | 5 +++++ tutorial_2/build.sh | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tutorial_2/.dockerignore create mode 100755 tutorial_2/build.sh diff --git a/tutorial_2/.dockerignore b/tutorial_2/.dockerignore new file mode 100644 index 0000000..9eca48d --- /dev/null +++ b/tutorial_2/.dockerignore @@ -0,0 +1,5 @@ +# ignore everything using whitelist strategy +# * +build/ +# you can selectiveley allow files using the ! at the beginning of the line +#!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/tutorial_2/build.sh b/tutorial_2/build.sh new file mode 100755 index 0000000..3713097 --- /dev/null +++ b/tutorial_2/build.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +rm -r build/ +mkdir -p build +cd build/ +cmake .. +make +./tut2 +cd .. From ef96d8147bf704c4f627034f6732b815b7f5fc85 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:15:05 +0000 Subject: [PATCH 53/69] Added multi tutorial dockerfile --- .dockerignore | 5 +++++ Dockerfile | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9eca48d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +# ignore everything using whitelist strategy +# * +build/ +# you can selectiveley allow files using the ! at the beginning of the line +#!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5d6ec54 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +FROM nvidia/opencl + +# setting up the place, making sure everything is installed, and getting the +# pesky CL/cl2.hpp +RUN mkdir -p /opencl_tutorials && \ + apt update -y && \ + apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ + git clone https://github.com/KhronosGroup/opencl-clhpp && \ + cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ + rm -r /opencl-clhpp && \ + echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc + +COPY . /opencl_tutorials/ From d2825dfb3486ca7facc276010eae21d52d790b08 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:22:15 +0000 Subject: [PATCH 54/69] Overhauled main README --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3166787..d8df947 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ # OpenCL Tutorials -## Requirements - -The presented tutorials were developed and tested on Windows 10, Visual Studio 2019 and [Intel SDK for OpenCL](https://software.intel.com/en-us/intel-opencl) so that can be run on Windows PCs in the computing labs. Tutorial 4 also depends on the Boost library. If you would like to develop OpenCL programs on your computer you have two options: - - replicate the [Windows setup](#windows-setup) from the computing labs; - - use the [multi_os](https://github.com/gcielniak/OpenCL-Tutorials/tree/multi_os) branch, which should allow for running the tutorials on different operating systems, programming environments and OpenCL SDKs. There is limited documentation for this option, however, so you should only choose that option if you are comfortable with installing custom libraries on your specific OS. - -## Windows Setup - - OS + IDE: Windows 10, Visual Studio 2019 - - OpenCL SDK: the SDK enables you to develop and compile the OpenCL code. In our case, we use [Intel SDK for OpenCL Applications](https://software.intel.com/en-us/intel-opencl). You are not tied to that choice, however, and can use SDKs by NVidia or AMD - just remember to make modifications in the project include paths. Each SDK comes with a range of additional tools which make development of OpenCL programs easier. - - OpenCL runtime: the runtime drivers are necessary to run the OpenCL code on your hardware. Both NVidia and AMD GPUs have OpenCL runtime included with their card drivers. For CPUs, you will need to install a dedicated driver by [Intel](https://software.intel.com/en-us/articles/opencl-drivers) or APP SDK for older AMD processors. It seems that AMD’s OpenCL support for newer CPU models was dropped unfortunately. You can check the existing OpenCL support on your PC using [GPU Caps Viewer](http://www.ozone3d.net/gpu_caps_viewer/). - - Boost library: install the recent [Boost library Windows binaries](https://sourceforge.net/projects/boost/files/boost-binaries/) (e.g. [boost_1_72_0](https://sourceforge.net/projects/boost/files/boost-binaries/1.72.0/boost_1_72_0-msvc-14.2-64.exe/download) for VS2019). Then, add two environmental variables in the command line specifying the location of the include and lib Boost directories. For example, with boost_1_72_0 the commands would look as follows: `setx BOOST_INCLUDEDIR "C:\local\boost_1_72_0"` and `setx BOOST_LIBRARYDIR "C:\local\boost_1_72_0\lib64-msvc-14.2"`. - - A useful reference if you are struggling to get going: [OpenCL on Windows](http://streamcomputing.eu/blog/2015-03-16/how-to-install-opencl-on-windows/). +Hey guys, each sub-directory containing tutorials is a wholistic self inclusive directory, with all the neccessary source code needed to run, but not necessarily to build. These tutorials require CL2.hpp but use depreciated features from opencl version 1.2. As such setting up your own system may be a tad strange. + +I have included CMake files in each directory that can be run using the build.sh file, or manually. These files are to help you with the requirements, and they are quite strict. However if you have better things to do than worry about dependancies and all their nuances, then simply use docker instead, which will create some lightweight virtualmachines for you to work with instead automatically. + +You need only 2 things, docker itself, and nvidia-container-toolkit, the latter is for GPU containers so you can use nvidia GPUs from inside any given container. For further information please see the following two sources: + +https://nemesyst.readthedocs.io/en/latest/docker/ while not specifically for these tutorials it gives a good summary of how to use Dockerfiles. + +https://wiki.archlinux.org/index.php/Docker the holy bible of linux, which shall endow you with super powers, including usage of docker, and installation of things like nvidia-container-toolkit. + +May the force be with you. From 92e31e79f913bd79278593793b0d6ad39e9f7cd6 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:36:23 +0000 Subject: [PATCH 55/69] Changed to single dockerfile strategy --- Dockerfile | 3 ++- README.md | 15 +++++++++++++-- tutorial_1/.dockerignore | 5 ----- tutorial_1/Dockerfile | 36 ------------------------------------ tutorial_2/.dockerignore | 5 ----- tutorial_2/Dockerfile | 36 ------------------------------------ tutorial_3/.dockerignore | 5 ----- tutorial_3/Dockerfile | 36 ------------------------------------ 8 files changed, 15 insertions(+), 126 deletions(-) delete mode 100644 tutorial_1/.dockerignore delete mode 100644 tutorial_1/Dockerfile delete mode 100644 tutorial_2/.dockerignore delete mode 100644 tutorial_2/Dockerfile delete mode 100644 tutorial_3/.dockerignore delete mode 100644 tutorial_3/Dockerfile diff --git a/Dockerfile b/Dockerfile index 5d6ec54..551aa98 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,8 @@ FROM nvidia/opencl # pesky CL/cl2.hpp RUN mkdir -p /opencl_tutorials && \ apt update -y && \ - apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ + apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev \ + fish neovim && \ git clone https://github.com/KhronosGroup/opencl-clhpp && \ cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ rm -r /opencl-clhpp && \ diff --git a/README.md b/README.md index d8df947..ac1e22f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # OpenCL Tutorials -Hey guys, each sub-directory containing tutorials is a wholistic self inclusive directory, with all the neccessary source code needed to run, but not necessarily to build. These tutorials require CL2.hpp but use depreciated features from opencl version 1.2. As such setting up your own system may be a tad strange. +Hey guys, each sub-directory containing tutorials is a holistic self inclusive directory, with all the necessary source code needed to run, but not necessarily to build. These tutorials require CL2.hpp but use depreciated features from opencl version 1.2. As such setting up your own system may be a tad strange. -I have included CMake files in each directory that can be run using the build.sh file, or manually. These files are to help you with the requirements, and they are quite strict. However if you have better things to do than worry about dependancies and all their nuances, then simply use docker instead, which will create some lightweight virtualmachines for you to work with instead automatically. +I have included CMake files in each directory that can be run using the build.sh file, or manually. These files are to help you with the requirements, and they are quite strict. However if you have better things to do than worry about dependencies and all their nuances, then simply use docker instead, which will create some lightweight virtualmachines for you to work with instead automatically. You need only 2 things, docker itself, and nvidia-container-toolkit, the latter is for GPU containers so you can use nvidia GPUs from inside any given container. For further information please see the following two sources: @@ -10,4 +10,15 @@ https://nemesyst.readthedocs.io/en/latest/docker/ while not specifically for the https://wiki.archlinux.org/index.php/Docker the holy bible of linux, which shall endow you with super powers, including usage of docker, and installation of things like nvidia-container-toolkit. +quick-start: + +# create a docker image with all our sources/ everything ready +sudo docker build -t archer/opencl-tuts . + +# run our docker image with all the GPUs available +sudo docker run --gpus all -it archer/opencl-tuts bash + +# OR if you dont have nvidia gpus/ you dont want to use GPUs/ don't have nvidia-container-toolkit installed +sudo docker run -it archer/opencl-tuts bash + May the force be with you. diff --git a/tutorial_1/.dockerignore b/tutorial_1/.dockerignore deleted file mode 100644 index 9eca48d..0000000 --- a/tutorial_1/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -# ignore everything using whitelist strategy -# * -build/ -# you can selectiveley allow files using the ! at the beginning of the line -#!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/tutorial_1/Dockerfile b/tutorial_1/Dockerfile deleted file mode 100644 index 5d6ec54..0000000 --- a/tutorial_1/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# MIT License -# =========== -# -# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -FROM nvidia/opencl - -# setting up the place, making sure everything is installed, and getting the -# pesky CL/cl2.hpp -RUN mkdir -p /opencl_tutorials && \ - apt update -y && \ - apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ - git clone https://github.com/KhronosGroup/opencl-clhpp && \ - cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ - rm -r /opencl-clhpp && \ - echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc - -COPY . /opencl_tutorials/ diff --git a/tutorial_2/.dockerignore b/tutorial_2/.dockerignore deleted file mode 100644 index 9eca48d..0000000 --- a/tutorial_2/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -# ignore everything using whitelist strategy -# * -build/ -# you can selectiveley allow files using the ! at the beginning of the line -#!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/tutorial_2/Dockerfile b/tutorial_2/Dockerfile deleted file mode 100644 index 5d6ec54..0000000 --- a/tutorial_2/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# MIT License -# =========== -# -# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -FROM nvidia/opencl - -# setting up the place, making sure everything is installed, and getting the -# pesky CL/cl2.hpp -RUN mkdir -p /opencl_tutorials && \ - apt update -y && \ - apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ - git clone https://github.com/KhronosGroup/opencl-clhpp && \ - cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ - rm -r /opencl-clhpp && \ - echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc - -COPY . /opencl_tutorials/ diff --git a/tutorial_3/.dockerignore b/tutorial_3/.dockerignore deleted file mode 100644 index 9eca48d..0000000 --- a/tutorial_3/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -# ignore everything using whitelist strategy -# * -build/ -# you can selectiveley allow files using the ! at the beginning of the line -#!lib/**/*.py # this would allow all python files in subdirectories of lib if enabled diff --git a/tutorial_3/Dockerfile b/tutorial_3/Dockerfile deleted file mode 100644 index 5d6ec54..0000000 --- a/tutorial_3/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# MIT License -# =========== -# -# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -FROM nvidia/opencl - -# setting up the place, making sure everything is installed, and getting the -# pesky CL/cl2.hpp -RUN mkdir -p /opencl_tutorials && \ - apt update -y && \ - apt install -y cmake fish make gcc git build-essential ocl-icd-opencl-dev && \ - git clone https://github.com/KhronosGroup/opencl-clhpp && \ - cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ - rm -r /opencl-clhpp && \ - echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc - -COPY . /opencl_tutorials/ From 332be581c3fc082eb49d7702faaeb464b510a14a Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:39:46 +0000 Subject: [PATCH 56/69] Fixed formatting of README being huge --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ac1e22f..9243308 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ https://nemesyst.readthedocs.io/en/latest/docker/ while not specifically for the https://wiki.archlinux.org/index.php/Docker the holy bible of linux, which shall endow you with super powers, including usage of docker, and installation of things like nvidia-container-toolkit. -quick-start: +## quick-start: -# create a docker image with all our sources/ everything ready -sudo docker build -t archer/opencl-tuts . +### create a docker image with all our sources/ everything ready +```sudo docker build -t archer/opencl-tuts .``` -# run our docker image with all the GPUs available -sudo docker run --gpus all -it archer/opencl-tuts bash +### run our docker image with all the GPUs available +```sudo docker run --gpus all -it archer/opencl-tuts bash``` -# OR if you dont have nvidia gpus/ you dont want to use GPUs/ don't have nvidia-container-toolkit installed -sudo docker run -it archer/opencl-tuts bash +### OR if you dont have nvidia gpus/ you dont want to use GPUs/ don't have nvidia-container-toolkit installed +```sudo docker run -it archer/opencl-tuts bash``` May the force be with you. From 6de8fbd927f64c30879eee88837fb10d32881ef0 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:49:36 +0000 Subject: [PATCH 57/69] Working tutorial 4 implementation Now to get boost into the docker --- tutorial_4/CImg.h | 62163 ++++++++++++++++++++++ tutorial_4/CMakeLists.txt | 51 + tutorial_4/Tutorial 4.vcxproj | 169 - tutorial_4/Tutorial 4.vcxproj.filters | 16 - tutorial_4/build.sh | 38 + tutorial_4/{Tutorial 4.cpp => tut4.cpp} | 12 +- tutorial_4/utils.h | 225 + 7 files changed, 62488 insertions(+), 186 deletions(-) create mode 100644 tutorial_4/CImg.h create mode 100644 tutorial_4/CMakeLists.txt delete mode 100644 tutorial_4/Tutorial 4.vcxproj delete mode 100644 tutorial_4/Tutorial 4.vcxproj.filters create mode 100755 tutorial_4/build.sh rename tutorial_4/{Tutorial 4.cpp => tut4.cpp} (80%) create mode 100644 tutorial_4/utils.h diff --git a/tutorial_4/CImg.h b/tutorial_4/CImg.h new file mode 100644 index 0000000..d9d2a21 --- /dev/null +++ b/tutorial_4/CImg.h @@ -0,0 +1,62163 @@ +/* + # + # File : CImg.h + # ( C++ header file ) + # + # Description : The C++ Template Image Processing Toolkit. + # This file is the main component of the CImg Library project. + # ( http://cimg.eu ) + # + # Project manager : David Tschumperle. + # ( http://tschumperle.users.greyc.fr/ ) + # + # A complete list of contributors is available in file 'README.txt' + # distributed within the CImg package. + # + # Licenses : This file is 'dual-licensed', you have to choose one + # of the two licenses below to apply. + # + # CeCILL-C + # The CeCILL-C license is close to the GNU LGPL. + # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html ) + # + # or CeCILL v2.1 + # The CeCILL license is compatible with the GNU GPL. + # ( http://www.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". + # + # As a counterpart to the access to the source code and rights to copy, + # modify and redistribute granted by the license, users are provided only + # with a limited warranty and the software's author, the holder of the + # economic rights, and the successive licensors have only limited + # liability. + # + # In this respect, the user's attention is drawn to the risks associated + # with loading, using, modifying and/or developing or reproducing the + # software by the user in light of its specific status of free software, + # that may mean that it is complicated to manipulate, and that also + # therefore means that it is reserved for developers and experienced + # professionals having in-depth computer knowledge. Users are therefore + # encouraged to load and test the software's suitability as regards their + # requirements in conditions enabling the security of their systems and/or + # data to be ensured and, more generally, to use and operate it in the + # same conditions as regards security. + # + # The fact that you are presently reading this means that you have had + # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms. + # +*/ + +// Set version number of the library. +#ifndef cimg_version +#define cimg_version 250 + +/*----------------------------------------------------------- + # + # Test and possibly auto-set CImg configuration variables + # and include required headers. + # + # If you find that the default configuration variables are + # not adapted to your system, you can override their values + # before including the header file "CImg.h" + # (use the #define directive). + # + ------------------------------------------------------------*/ + +// Include standard C++ headers. +// This is the minimal set of required headers to make CImg-based codes compile. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Detect/configure OS variables. +// +// Define 'cimg_OS' to: '0' for an unknown OS (will try to minize library dependencies). +// '1' for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...). +// '2' for Microsoft Windows. +// (auto-detection is performed if 'cimg_OS' is not set by the user). +#ifndef cimg_OS +#if defined(unix) || defined(__unix) || defined(__unix__) \ + || defined(linux) || defined(__linux) || defined(__linux__) \ + || defined(sun) || defined(__sun) \ + || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__FreeBSD__) || defined (__DragonFly__) \ + || defined(sgi) || defined(__sgi) \ + || defined(__MACOSX__) || defined(__APPLE__) \ + || defined(__CYGWIN__) +#define cimg_OS 1 +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \ + || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) +#define cimg_OS 2 +#else +#define cimg_OS 0 +#endif +#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2) +#error CImg Library: Invalid configuration variable 'cimg_OS'. +#error (correct values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows'). +#endif +#ifndef cimg_date +#define cimg_date __DATE__ +#endif +#ifndef cimg_time +#define cimg_time __TIME__ +#endif + +// Disable silly warnings on some Microsoft VC++ compilers. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) +#pragma warning(disable:4244) +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4319) +#pragma warning(disable:4512) +#pragma warning(disable:4571) +#pragma warning(disable:4640) +#pragma warning(disable:4706) +#pragma warning(disable:4710) +#pragma warning(disable:4800) +#pragma warning(disable:4804) +#pragma warning(disable:4820) +#pragma warning(disable:4996) + +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#endif +#endif + +// Define correct string functions for each compiler and OS. +#if cimg_OS==2 && defined(_MSC_VER) +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#include +#if defined(__MACOSX__) || defined(__APPLE__) +#define cimg_sscanf cimg::_sscanf +#define cimg_sprintf cimg::_sprintf +#define cimg_snprintf cimg::_snprintf +#define cimg_vsnprintf cimg::_vsnprintf +#else +#define cimg_sscanf std::sscanf +#define cimg_sprintf std::sprintf +#define cimg_snprintf snprintf +#define cimg_vsnprintf vsnprintf +#endif +#endif + +// Include OS-specific headers. +#if cimg_OS==1 +#include +#include +#include +#include +#include +#include +#elif cimg_OS==2 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#ifndef _WIN32_IE +#define _WIN32_IE 0x0400 +#endif +#include +#include +#include +#endif + +// Look for C++11 features. +#ifndef cimg_use_cpp11 +#if __cplusplus>201100 +#define cimg_use_cpp11 1 +#else +#define cimg_use_cpp11 0 +#endif +#endif +#if cimg_use_cpp11==1 +#include +#include +#endif + +// Convenient macro to define pragma +#ifdef _MSC_VER +#define cimg_pragma(x) __pragma(x) +#else +#define cimg_pragma(x) _Pragma(#x) +#endif + +// Define own types 'cimg_long/ulong' and 'cimg_int64/uint64' to ensure portability. +// ( constrained to 'sizeof(cimg_ulong/cimg_long) = sizeof(void*)' and 'sizeof(cimg_int64/cimg_uint64)=8' ). +#if cimg_OS==2 + +#define cimg_uint64 unsigned __int64 +#define cimg_int64 __int64 +#define cimg_ulong UINT_PTR +#define cimg_long INT_PTR +#ifdef _MSC_VER +#define cimg_fuint64 "%I64u" +#define cimg_fint64 "%I64d" +#else +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#endif + +#else + +#if UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX)) +#define cimg_uint64 unsigned long long +#define cimg_int64 long long +#define cimg_fuint64 "%llu" +#define cimg_fint64 "%lld" +#else +#define cimg_uint64 unsigned long +#define cimg_int64 long +#define cimg_fuint64 "%lu" +#define cimg_fint64 "%ld" +#endif + +#if defined(__arm__) || defined(_M_ARM) +#define cimg_ulong unsigned long long +#define cimg_long long long +#else +#define cimg_ulong unsigned long +#define cimg_long long +#endif + +#endif + +// Configure filename separator. +// +// Filename separator is set by default to '/', except for Windows where it is '\'. +#ifndef cimg_file_separator +#if cimg_OS==2 +#define cimg_file_separator '\\' +#else +#define cimg_file_separator '/' +#endif +#endif + +// Configure verbosity of output messages. +// +// Define 'cimg_verbosity' to: '0' to hide library messages (quiet mode). +// '1' to output library messages on the console. +// '2' to output library messages on a basic dialog window (default behavior). +// '3' to do as '1' + add extra warnings (may slow down the code!). +// '4' to do as '2' + add extra warnings (may slow down the code!). +// +// Define 'cimg_strict_warnings' to replace warning messages by exception throwns. +// +// Define 'cimg_use_vt100' to allow output of color messages on VT100-compatible terminals. +#ifndef cimg_verbosity +#if cimg_OS==2 +#define cimg_verbosity 2 +#else +#define cimg_verbosity 1 +#endif +#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4) +#error CImg Library: Configuration variable 'cimg_verbosity' is badly defined. +#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }). +#endif + +// Configure display framework. +// +// Define 'cimg_display' to: '0' to disable display capabilities. +// '1' to use the X-Window framework (X11). +// '2' to use the Microsoft GDI32 framework. +#ifndef cimg_display +#if cimg_OS==0 +#define cimg_display 0 +#elif cimg_OS==1 +#define cimg_display 1 +#elif cimg_OS==2 +#define cimg_display 2 +#endif +#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2) +#error CImg Library: Configuration variable 'cimg_display' is badly defined. +#error (should be { 0=none | 1=X-Window (X11) | 2=Microsoft GDI32 }). +#endif + +// Configure the 'abort' signal handler (does nothing by default). +// A typical signal handler can be defined in your own source like this: +// #define cimg_abort_test if (is_abort) throw CImgAbortException("") +// +// where 'is_abort' is a boolean variable defined somewhere in your code and reachable in the method. +// 'cimg_abort_test2' does the same but is called more often (in inner loops). +#if defined(cimg_abort_test) && defined(cimg_use_openmp) + +// Define abort macros to be used with OpenMP. +#ifndef _cimg_abort_init_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 + +// Include display-specific headers. +#if cimg_display==1 +#include +#include +#include +#include +#ifdef cimg_use_xshm +#include +#include +#include +#endif +#ifdef cimg_use_xrandr +#include +#endif +#endif +#ifndef cimg_appname +#define cimg_appname "CImg" +#endif + +// Configure OpenMP support. +// (http://www.openmp.org) +// +// Define 'cimg_use_openmp' to enable OpenMP support (requires OpenMP 3.0+). +// +// OpenMP directives are used in many 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/) +// +// Define 'cimg_use_opencv' to enable OpenCV support. +// +// OpenCV library may be used to access images from cameras +// (see method 'CImg::load_camera()'). +#ifdef cimg_use_opencv +#ifdef True +#undef True +#define _cimg_redefine_True +#endif +#ifdef False +#undef False +#define _cimg_redefine_False +#endif +#include +#include "cv.h" +#include "highgui.h" +#endif + +// Configure LibPNG support. +// (http://www.libpng.org) +// +// Define 'cimg_use_png' to enable LibPNG support. +// +// PNG library may be used to get a native support of '.png' files. +// (see methods 'CImg::{load,save}_png()'. +#ifdef cimg_use_png +extern "C" { +#include "png.h" +} +#endif + +// Configure LibJPEG support. +// (http://en.wikipedia.org/wiki/Libjpeg) +// +// Define 'cimg_use_jpeg' to enable LibJPEG support. +// +// JPEG library may be used to get a native support of '.jpg' files. +// (see methods 'CImg::{load,save}_jpeg()'). +#ifdef cimg_use_jpeg +extern "C" { +#include "jpeglib.h" +#include "setjmp.h" +} +#endif + +// Configure LibTIFF support. +// (http://www.libtiff.org) +// +// Define 'cimg_use_tiff' to enable LibTIFF support. +// +// TIFF library may be used to get a native support of '.tif' files. +// (see methods 'CImg[List]::{load,save}_tiff()'). +#ifdef cimg_use_tiff +extern "C" { +#define uint64 uint64_hack_ +#define int64 int64_hack_ +#include "tiffio.h" +#undef uint64 +#undef int64 +} +#endif + +// Configure LibMINC2 support. +// (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference) +// +// Define 'cimg_use_minc2' to enable LibMINC2 support. +// +// MINC2 library may be used to get a native support of '.mnc' files. +// (see methods 'CImg::{load,save}_minc2()'). +#ifdef cimg_use_minc2 +#include "minc_io_simple_volume.h" +#include "minc_1_simple.h" +#include "minc_1_simple_rw.h" +#endif + +// Configure Zlib support. +// (http://www.zlib.net) +// +// Define 'cimg_use_zlib' to enable Zlib support. +// +// Zlib library may be used to allow compressed data in '.cimgz' files +// (see methods 'CImg[List]::{load,save}_cimg()'). +#ifdef cimg_use_zlib +extern "C" { +#include "zlib.h" +} +#endif + +// Configure libcurl support. +// (http://curl.haxx.se/libcurl/) +// +// Define 'cimg_use_curl' to enable libcurl support. +// +// Libcurl may be used to get a native support of file downloading from the network. +// (see method 'cimg::load_network()'.) +#ifdef cimg_use_curl +#include "curl/curl.h" +#endif + +// Configure Magick++ support. +// (http://www.imagemagick.org/Magick++) +// +// Define 'cimg_use_magick' to enable Magick++ support. +// +// Magick++ library may be used to get a native support of various image file formats. +// (see methods 'CImg::{load,save}()'). +#ifdef cimg_use_magick +#include "Magick++.h" +#endif + +// Configure FFTW3 support. +// (http://www.fftw.org) +// +// Define 'cimg_use_fftw3' to enable libFFTW3 support. +// +// FFTW3 library may be used to efficiently compute the Fast Fourier Transform +// of image data, without restriction on the image size. +// (see method 'CImg[List]::FFT()'). +#ifdef cimg_use_fftw3 +extern "C" { +#include "fftw3.h" +} +#endif + +// Configure LibBoard support. +// (http://libboard.sourceforge.net/) +// +// Define 'cimg_use_board' to enable Board support. +// +// Board library may be used to draw 3D objects in vector-graphics canvas +// that can be saved as '.ps' or '.svg' files afterwards. +// (see method 'CImg::draw_object3d()'). +#ifdef cimg_use_board +#include "Board.h" +#endif + +// Configure OpenEXR support. +// (http://www.openexr.com/) +// +// Define 'cimg_use_openexr' to enable OpenEXR support. +// +// OpenEXR library may be used to get a native support of '.exr' files. +// (see methods 'CImg::{load,save}_exr()'). +#ifdef cimg_use_openexr +#include "ImfRgbaFile.h" +#include "ImfInputFile.h" +#include "ImfChannelList.h" +#include "ImfMatrixAttribute.h" +#include "ImfArray.h" +#endif + +// Configure TinyEXR support. +// (https://github.com/syoyo/tinyexr) +// +// Define 'cimg_use_tinyexr' to enable TinyEXR support. +// +// TinyEXR is a small, single header-only library to load and save OpenEXR(.exr) images. +#ifdef cimg_use_tinyexr +#ifndef TINYEXR_IMPLEMENTATION +#define TINYEXR_IMPLEMENTATION +#endif +#include "tinyexr.h" +#endif + +// Lapack configuration. +// (http://www.netlib.org/lapack) +// +// Define 'cimg_use_lapack' to enable LAPACK support. +// +// Lapack library may be used in several CImg methods to speed up +// matrix computations (eigenvalues, inverse, ...). +#ifdef cimg_use_lapack +extern "C" { + extern void sgetrf_(int*, int*, float*, int*, int*, int*); + extern void sgetri_(int*, float*, int*, int*, float*, int*, int*); + extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*); + extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*); + extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*); + extern void dgetrf_(int*, int*, double*, int*, int*, int*); + extern void dgetri_(int*, double*, int*, int*, double*, int*, int*); + extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*); + extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, + int*, double*, int*, double*, int*, int*); + extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*); + extern void dgels_(char*, int*,int*,int*,double*,int*,double*,int*,double*,int*,int*); + extern void sgels_(char*, int*,int*,int*,float*,int*,float*,int*,float*,int*,int*); +} +#endif + +// Check if min/max/PI macros are defined. +// +// CImg does not compile if macros 'min', 'max' or 'PI' are defined, +// because it redefines functions min(), max() and const variable PI in the cimg:: namespace. +// so it '#undef' these macros if necessary, and restore them to reasonable +// values at the end of this file. +#ifdef min +#undef min +#define _cimg_redefine_min +#endif +#ifdef max +#undef max +#define _cimg_redefine_max +#endif +#ifdef PI +#undef PI +#define _cimg_redefine_PI +#endif + +// Define 'cimg_library' namespace suffix. +// +// You may want to add a suffix to the 'cimg_library' namespace, for instance if you need to work +// with several versions of the library at the same time. +#ifdef cimg_namespace_suffix +#define __cimg_library_suffixed(s) cimg_library_##s +#define _cimg_library_suffixed(s) __cimg_library_suffixed(s) +#define cimg_library_suffixed _cimg_library_suffixed(cimg_namespace_suffix) +#else +#define cimg_library_suffixed cimg_library +#endif + +/*------------------------------------------------------------------------------ + # + # Define user-friendly macros. + # + # These CImg macros are prefixed by 'cimg_' and can be used safely in your own + # code. They are useful to parse command line options, or to write image loops. + # + ------------------------------------------------------------------------------*/ + +// Macros to define program usage, and retrieve command line arguments. +#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false) +#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0) +#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage) + +// Macros to define and manipulate local neighborhoods. +#define CImg_2x2(I,T) T I[4]; \ + T& I##cc = I[0]; T& I##nc = I[1]; \ + T& I##cn = I[2]; T& I##nn = I[3]; \ + I##cc = I##nc = \ + I##cn = I##nn = 0 + +#define CImg_3x3(I,T) T I[9]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \ + T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \ + T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \ + I##pp = I##cp = I##np = \ + I##pc = I##cc = I##nc = \ + I##pn = I##cn = I##nn = 0 + +#define CImg_4x4(I,T) T I[16]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \ + T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \ + T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \ + T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \ + I##pp = I##cp = I##np = I##ap = \ + I##pc = I##cc = I##nc = I##ac = \ + I##pn = I##cn = I##nn = I##an = \ + I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_5x5(I,T) T I[25]; \ + T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \ + T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \ + T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \ + T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \ + T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \ + I##bb = I##pb = I##cb = I##nb = I##ab = \ + I##bp = I##pp = I##cp = I##np = I##ap = \ + I##bc = I##pc = I##cc = I##nc = I##ac = \ + I##bn = I##pn = I##cn = I##nn = I##an = \ + I##ba = I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_2x2x2(I,T) T I[8]; \ + T& I##ccc = I[0]; T& I##ncc = I[1]; \ + T& I##cnc = I[2]; T& I##nnc = I[3]; \ + T& I##ccn = I[4]; T& I##ncn = I[5]; \ + T& I##cnn = I[6]; T& I##nnn = I[7]; \ + I##ccc = I##ncc = \ + I##cnc = I##nnc = \ + I##ccn = I##ncn = \ + I##cnn = I##nnn = 0 + +#define CImg_3x3x3(I,T) T I[27]; \ + T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \ + T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \ + T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \ + T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \ + T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \ + T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \ + T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \ + T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \ + T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \ + I##ppp = I##cpp = I##npp = \ + I##pcp = I##ccp = I##ncp = \ + I##pnp = I##cnp = I##nnp = \ + I##ppc = I##cpc = I##npc = \ + I##pcc = I##ccc = I##ncc = \ + I##pnc = I##cnc = I##nnc = \ + I##ppn = I##cpn = I##npn = \ + I##pcn = I##ccn = I##ncn = \ + I##pnn = I##cnn = I##nnn = 0 + +#define cimg_get2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_p1##x,y,z,c), I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), \ + I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), I[8] = (T)(img)(_n1##x,_n1##y,z,c) + +#define cimg_get4x4(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[3] = (T)(img)(_n2##x,_p1##y,z,c), I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), \ + I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), I[8] = (T)(img)(_p1##x,_n1##y,z,c), \ + I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \ + I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[15] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get5x5(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), I[8] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \ + I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), \ + I[15] = (T)(img)(_p2##x,_n1##y,z,c), I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), I[20] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_n2##y,z,c) + +#define cimg_get6x6(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ + I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), I[8] = (T)(img)(x,_p1##y,z,c), \ + I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), \ + I[15] = (T)(img)(_n1##x,y,z,c), I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), \ + I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), I[20] = (T)(img)(x,_n1##y,z,c), \ + I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), \ + I[27] = (T)(img)(_n1##x,_n2##y,z,c), I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), I[32] = (T)(img)(x,_n3##y,z,c), \ + I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get7x7(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), I[8] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), \ + I[15] = (T)(img)(_p2##x,_p1##y,z,c), I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), \ + I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), I[20] = (T)(img)(_n3##x,_p1##y,z,c), \ + I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \ + I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), \ + I[27] = (T)(img)(_n3##x,y,z,c), I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), I[32] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \ + I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), \ + I[39] = (T)(img)(_n1##x,_n2##y,z,c), I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), I[44] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[48] = (T)(img)(_n3##x,_n3##y,z,c) + +#define cimg_get8x8(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ + I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ + I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), I[8] = (T)(img)(_p3##x,_p2##y,z,c), \ + I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \ + I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), \ + I[15] = (T)(img)(_n4##x,_p2##y,z,c), I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), I[20] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), \ + I[27] = (T)(img)(x,y,z,c), I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), \ + I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), I[32] = (T)(img)(_p3##x,_n1##y,z,c), \ + I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \ + I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), \ + I[39] = (T)(img)(_n4##x,_n1##y,z,c), I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), I[44] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), \ + I[51] = (T)(img)(x,_n3##y,z,c), I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), \ + I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), I[56] = (T)(img)(_p3##x,_n4##y,z,c), \ + I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \ + I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), \ + I[63] = (T)(img)(_n4##x,_n4##y,z,c); + +#define cimg_get9x9(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), \ + I[3] = (T)(img)(_p1##x,_p4##y,z,c), I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), \ + I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), I[8] = (T)(img)(_n4##x,_p4##y,z,c), \ + I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \ + I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), \ + I[15] = (T)(img)(_n2##x,_p3##y,z,c), I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), \ + I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), I[20] = (T)(img)(_p2##x,_p2##y,z,c), \ + I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \ + I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), \ + I[27] = (T)(img)(_p4##x,_p1##y,z,c), I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), \ + I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), I[32] = (T)(img)(_n1##x,_p1##y,z,c), \ + I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \ + I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), \ + I[39] = (T)(img)(_p1##x,y,z,c), I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), \ + I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), I[44] = (T)(img)(_n4##x,y,z,c), \ + I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \ + I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), \ + I[51] = (T)(img)(_n2##x,_n1##y,z,c), I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), \ + I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), I[56] = (T)(img)(_p2##x,_n2##y,z,c), \ + I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \ + I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), \ + I[63] = (T)(img)(_p4##x,_n3##y,z,c), I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), \ + I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), I[68] = (T)(img)(_n1##x,_n3##y,z,c), \ + I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \ + I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), \ + I[75] = (T)(img)(_p1##x,_n4##y,z,c), I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), \ + I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), I[80] = (T)(img)(_n4##x,_n4##y,z,c) + +#define cimg_get2x2x2(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ + I[3] = (T)(img)(_n1##x,_n1##y,z,c), I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), \ + I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +#define cimg_get3x3x3(img,x,y,z,c,I,T) \ + I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), \ + I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), \ + I[5] = (T)(img)(_n1##x,y,_p1##z,c), I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), \ + I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), \ + I[11] = (T)(img)(_n1##x,_p1##y,z,c), I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), \ + I[14] = (T)(img)(_n1##x,y,z,c), I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), \ + I[17] = (T)(img)(_n1##x,_n1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), \ + I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), \ + I[23] = (T)(img)(_n1##x,y,_n1##z,c), I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), \ + I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c) + +// Macros to perform various image loops. +// +// These macros are simpler to use than loops with C++ iterators. +#define cimg_for(img,ptrs,T_ptrs) \ + for (T_ptrs *ptrs = (img)._data, *_max##ptrs = (img)._data + (img).size(); ptrs<_max##ptrs; ++ptrs) +#define cimg_rof(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size() - 1; ptrs>=(img)._data; --ptrs) +#define cimg_foroff(img,off) for (cimg_ulong off = 0, _max##off = (img).size(); off<_max##off; ++off) +#define cimg_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) +#define cimg_forY(img,y) cimg_for1((img)._height,y) +#define cimg_forZ(img,z) cimg_for1((img)._depth,z) +#define cimg_forC(img,c) cimg_for1((img)._spectrum,c) +#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x) +#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x) +#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y) +#define cimg_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x) +#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y) +#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z) +#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y) +#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y) +#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z) +#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z) +#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) cimg_forXYZ(img,x,y,z) + +#define cimg_rof1(bound,i) for (int i = (int)(bound) - 1; i>=0; --i) +#define cimg_rofX(img,x) cimg_rof1((img)._width,x) +#define cimg_rofY(img,y) cimg_rof1((img)._height,y) +#define cimg_rofZ(img,z) cimg_rof1((img)._depth,z) +#define cimg_rofC(img,c) cimg_rof1((img)._spectrum,c) +#define cimg_rofXY(img,x,y) cimg_rofY(img,y) cimg_rofX(img,x) +#define cimg_rofXZ(img,x,z) cimg_rofZ(img,z) cimg_rofX(img,x) +#define cimg_rofYZ(img,y,z) cimg_rofZ(img,z) cimg_rofY(img,y) +#define cimg_rofXC(img,x,c) cimg_rofC(img,c) cimg_rofX(img,x) +#define cimg_rofYC(img,y,c) cimg_rofC(img,c) cimg_rofY(img,y) +#define cimg_rofZC(img,z,c) cimg_rofC(img,c) cimg_rofZ(img,z) +#define cimg_rofXYZ(img,x,y,z) cimg_rofZ(img,z) cimg_rofXY(img,x,y) +#define cimg_rofXYC(img,x,y,c) cimg_rofC(img,c) cimg_rofXY(img,x,y) +#define cimg_rofXZC(img,x,z,c) cimg_rofC(img,c) cimg_rofXZ(img,x,z) +#define cimg_rofYZC(img,y,z,c) cimg_rofC(img,c) cimg_rofYZ(img,y,z) +#define cimg_rofXYZC(img,x,y,z,c) cimg_rofC(img,c) cimg_rofXYZ(img,x,y,z) + +#define cimg_for_in1(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound) - 1; i<=_max##i; ++i) +#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img)._width,x0,x1,x) +#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img)._height,y0,y1,y) +#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img)._depth,z0,z1,z) +#define cimg_for_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c) +#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inZ(img,z0,z1,z) +#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z) +#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_inC(img,c0,c1,c) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_insideXYZ(img,x,y,z,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_insideXYZC(img,x,y,z,c,n) \ + cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) + +#define cimg_for_out1(boundi,i0,i1,i) \ + for (int i = (int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1) + 1:i) +#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \ + for (int j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \ + for (int k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \ + ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \ + for (int l = 0; l<(int)(boundl); ++l) \ + for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1) + 1; \ + i<(int)(boundi); ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1) + 1:i)) +#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img)._width,x0,x1,x) +#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img)._height,y0,y1,y) +#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img)._depth,z0,z1,z) +#define cimg_for_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c) +#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img)._width,(img)._height,x0,y0,x1,y1,x,y) +#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img)._width,(img)._depth,x0,z0,x1,z1,x,z) +#define cimg_for_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c) +#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img)._height,(img)._depth,y0,z0,y1,z1,y,z) +#define cimg_for_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c) +#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c) +#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) \ + cimg_for_out3((img)._width,(img)._height,(img)._depth,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) \ + cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c) +#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) \ + cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c) +#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) \ + cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c) +#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) +#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img)._width - 1 - (n),x) +#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img)._height - 1 - (n),y) +#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img)._depth - 1 - (n),z) +#define cimg_for_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum - 1 - (n),c) +#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y) +#define cimg_for_borderXYZ(img,x,y,z,n) \ + cimg_for_outXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z) +#define cimg_for_borderXYZC(img,x,y,z,c,n) \ + cimg_for_outXYZC(img,n,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n), \ + (img)._depth - 1 - (n),(img)._spectrum - 1 - (n),x,y,z,c) + +#define cimg_for_spiralXY(img,x,y) \ + for (int x = 0, y = 0, _n1##x = 1, _n1##y = (img).width()*(img).height(); _n1##y; \ + --_n1##y, _n1##x+=(_n1##x>>2) - ((!(_n1##x&3)?--y:((_n1##x&3)==1?(img)._width - 1 - ++x:\ + ((_n1##x&3)==2?(img)._height - 1 - ++y:--x))))?0:1) + +#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \ + for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \ + _dx=(x1)>(x0)?(int)(x1) - (int)(x0):(_sx=-1,(int)(x0) - (int)(x1)), \ + _dy=(y1)>(y0)?(int)(y1) - (int)(y0):(_sy=-1,(int)(y0) - (int)(y1)), \ + _counter = _dx, \ + _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \ + _counter>=0; \ + --_counter, x+=_steep? \ + (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \ + (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx)) + +#define cimg_for2(bound,i) \ + for (int i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + ++i, ++_n1##i) +#define cimg_for2X(img,x) cimg_for2((img)._width,x) +#define cimg_for2Y(img,y) cimg_for2((img)._height,y) +#define cimg_for2Z(img,z) cimg_for2((img)._depth,z) +#define cimg_for2C(img,c) cimg_for2((img)._spectrum,c) +#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x) +#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x) +#define cimg_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x) +#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y) +#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y) +#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z) +#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y) +#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z) +#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z) +#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) cimg_for2XYZ(img,x,y,z) + +#define cimg_for_in2(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + ++i, ++_n1##i) +#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img)._width,x0,x1,x) +#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img)._height,y0,y1,y) +#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img)._depth,z0,z1,z) +#define cimg_for_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c) +#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Z(img,z0,z1,z) +#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in2C(img,c0,c1,c) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i) +#define cimg_for3X(img,x) cimg_for3((img)._width,x) +#define cimg_for3Y(img,y) cimg_for3((img)._height,y) +#define cimg_for3Z(img,z) cimg_for3((img)._depth,z) +#define cimg_for3C(img,c) cimg_for3((img)._spectrum,c) +#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x) +#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x) +#define cimg_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x) +#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y) +#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y) +#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z) +#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y) +#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z) +#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z) +#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) cimg_for3XYZ(img,x,y,z) + +#define cimg_for_in3(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + _p1##i = i++, ++_n1##i) +#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img)._width,x0,x1,x) +#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img)._height,y0,y1,y) +#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img)._depth,z0,z1,z) +#define cimg_for_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c) +#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Z(img,z0,z1,z) +#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in3C(img,c0,c1,c) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for4(bound,i) \ + for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for4X(img,x) cimg_for4((img)._width,x) +#define cimg_for4Y(img,y) cimg_for4((img)._height,y) +#define cimg_for4Z(img,z) cimg_for4((img)._depth,z) +#define cimg_for4C(img,c) cimg_for4((img)._spectrum,c) +#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x) +#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x) +#define cimg_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x) +#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y) +#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y) +#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z) +#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y) +#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z) +#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z) +#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) cimg_for4XYZ(img,x,y,z) + +#define cimg_for_in4(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img)._width,x0,x1,x) +#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img)._height,y0,y1,y) +#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img)._depth,z0,z1,z) +#define cimg_for_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c) +#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Z(img,z0,z1,z) +#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in4C(img,c0,c1,c) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for5(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for5X(img,x) cimg_for5((img)._width,x) +#define cimg_for5Y(img,y) cimg_for5((img)._height,y) +#define cimg_for5Z(img,z) cimg_for5((img)._depth,z) +#define cimg_for5C(img,c) cimg_for5((img)._spectrum,c) +#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x) +#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x) +#define cimg_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x) +#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y) +#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y) +#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z) +#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y) +#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z) +#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z) +#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) cimg_for5XYZ(img,x,y,z) + +#define cimg_for_in5(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img)._width,x0,x1,x) +#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img)._height,y0,y1,y) +#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img)._depth,z0,z1,z) +#define cimg_for_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c) +#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Z(img,z0,z1,z) +#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in5C(img,c0,c1,c) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for6(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for6X(img,x) cimg_for6((img)._width,x) +#define cimg_for6Y(img,y) cimg_for6((img)._height,y) +#define cimg_for6Z(img,z) cimg_for6((img)._depth,z) +#define cimg_for6C(img,c) cimg_for6((img)._spectrum,c) +#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x) +#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x) +#define cimg_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x) +#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y) +#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y) +#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z) +#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y) +#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z) +#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z) +#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) cimg_for6XYZ(img,x,y,z) + +#define cimg_for_in6(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img)._width,x0,x1,x) +#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img)._height,y0,y1,y) +#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img)._depth,z0,z1,z) +#define cimg_for_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c) +#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Z(img,z0,z1,z) +#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in6C(img,c0,c1,c) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for7(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for7X(img,x) cimg_for7((img)._width,x) +#define cimg_for7Y(img,y) cimg_for7((img)._height,y) +#define cimg_for7Z(img,z) cimg_for7((img)._depth,z) +#define cimg_for7C(img,c) cimg_for7((img)._spectrum,c) +#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x) +#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x) +#define cimg_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x) +#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y) +#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y) +#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z) +#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y) +#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z) +#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z) +#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) cimg_for7XYZ(img,x,y,z) + +#define cimg_for_in7(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \ + i<=(int)(i1) && \ + (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img)._width,x0,x1,x) +#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img)._height,y0,y1,y) +#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img)._depth,z0,z1,z) +#define cimg_for_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c) +#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Z(img,z0,z1,z) +#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in7C(img,c0,c1,c) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for8(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for8X(img,x) cimg_for8((img)._width,x) +#define cimg_for8Y(img,y) cimg_for8((img)._height,y) +#define cimg_for8Z(img,z) cimg_for8((img)._depth,z) +#define cimg_for8C(img,c) cimg_for8((img)._spectrum,c) +#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x) +#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x) +#define cimg_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x) +#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y) +#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y) +#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z) +#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y) +#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z) +#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z) +#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) cimg_for8XYZ(img,x,y,z) + +#define cimg_for_in8(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img)._width,x0,x1,x) +#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img)._height,y0,y1,y) +#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img)._depth,z0,z1,z) +#define cimg_for_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c) +#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Z(img,z0,z1,z) +#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in8C(img,c0,c1,c) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for9(bound,i) \ + for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(int)(bound)?(int)(bound) - 1:1, \ + _n2##i = 2>=(int)(bound)?(int)(bound) - 1:2, \ + _n3##i = 3>=(int)(bound)?(int)(bound) - 1:3, \ + _n4##i = 4>=(int)(bound)?(int)(bound) - 1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for9X(img,x) cimg_for9((img)._width,x) +#define cimg_for9Y(img,y) cimg_for9((img)._height,y) +#define cimg_for9Z(img,z) cimg_for9((img)._depth,z) +#define cimg_for9C(img,c) cimg_for9((img)._spectrum,c) +#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x) +#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x) +#define cimg_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x) +#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y) +#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y) +#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z) +#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y) +#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z) +#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z) +#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) cimg_for9XYZ(img,x,y,z) + +#define cimg_for_in9(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p4##i = i - 4<0?0:i - 4, \ + _p3##i = i - 3<0?0:i - 3, \ + _p2##i = i - 2<0?0:i - 2, \ + _p1##i = i - 1<0?0:i - 1, \ + _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \ + _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \ + _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \ + _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img)._width,x0,x1,x) +#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img)._height,y0,y1,y) +#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img)._depth,z0,z1,z) +#define cimg_for_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c) +#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Z(img,z0,z1,z) +#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ + cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[4] = (T)(img)(x,y,z,c)), \ + (I[7] = (T)(img)(x,_n1##y,z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for4x4(img,x,y,z,c,I,T) \ + cimg_for4((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = I[5] = (T)(img)(0,y,z,c)), \ + (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in4((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[4] = (T)(img)(_p1##x,y,z,c)), \ + (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(x,_p1##y,z,c)), \ + (I[5] = (T)(img)(x,y,z,c)), \ + (I[9] = (T)(img)(x,_n1##y,z,c)), \ + (I[13] = (T)(img)(x,_n2##y,z,c)), \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[6] = (T)(img)(_n1##x,y,z,c)), \ + (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[7] = (T)(img)(_n2##x,y,z,c)), \ + (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for5x5(img,x,y,z,c,I,T) \ + cimg_for5((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \ + (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \ + (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + 2>=(img)._width?(img).width() - 1:2); \ + (_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in5((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[10] = (T)(img)(_p2##x,y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[11] = (T)(img)(_p1##x,y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[7] = (T)(img)(x,_p1##y,z,c)), \ + (I[12] = (T)(img)(x,y,z,c)), \ + (I[17] = (T)(img)(x,_n1##y,z,c)), \ + (I[22] = (T)(img)(x,_n2##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_n1##x,y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ + x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ + x<=(int)(x1) && ((_n2##x<(img).width() && ( \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n2##x,y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for6x6(img,x,y,z,c,I,T) \ + cimg_for6((img)._height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \ + (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \ + (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \ + (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in6((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p2##x,y,z,c)), \ + (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[13] = (T)(img)(_p1##x,y,z,c)), \ + (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(x,_p2##y,z,c)), \ + (I[8] = (T)(img)(x,_p1##y,z,c)), \ + (I[14] = (T)(img)(x,y,z,c)), \ + (I[20] = (T)(img)(x,_n1##y,z,c)), \ + (I[26] = (T)(img)(x,_n2##y,z,c)), \ + (I[32] = (T)(img)(x,_n3##y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[15] = (T)(img)(_n1##x,y,z,c)), \ + (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[16] = (T)(img)(_n2##x,y,z,c)), \ + (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[17] = (T)(img)(_n3##x,y,z,c)), \ + (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for7x7(img,x,y,z,c,I,T) \ + cimg_for7((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img)._width?(img).width() - 1:1, \ + _n2##x = 2>=(img)._width?(img).width() - 1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \ + (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \ + (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \ + (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \ + (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \ + (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + 3>=(img)._width?(img).width() - 1:3); \ + (_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in7((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ + _n3##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[21] = (T)(img)(_p3##x,y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[22] = (T)(img)(_p2##x,y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[23] = (T)(img)(_p1##x,y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[10] = (T)(img)(x,_p2##y,z,c)), \ + (I[17] = (T)(img)(x,_p1##y,z,c)), \ + (I[24] = (T)(img)(x,y,z,c)), \ + (I[31] = (T)(img)(x,_n1##y,z,c)), \ + (I[38] = (T)(img)(x,_n2##y,z,c)), \ + (I[45] = (T)(img)(x,_n3##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_n1##x,y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_n2##x,y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ + x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ + x<=(int)(x1) && ((_n3##x<(img).width() && ( \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[27] = (T)(img)(_n3##x,y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for8x8(img,x,y,z,c,I,T) \ + cimg_for8((img)._height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \ + (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \ + (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \ + (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \ + (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \ + (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \ + (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in8((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[24] = (T)(img)(_p3##x,y,z,c)), \ + (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[25] = (T)(img)(_p2##x,y,z,c)), \ + (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[26] = (T)(img)(_p1##x,y,z,c)), \ + (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(x,_p3##y,z,c)), \ + (I[11] = (T)(img)(x,_p2##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,z,c)), \ + (I[27] = (T)(img)(x,y,z,c)), \ + (I[35] = (T)(img)(x,_n1##y,z,c)), \ + (I[43] = (T)(img)(x,_n2##y,z,c)), \ + (I[51] = (T)(img)(x,_n3##y,z,c)), \ + (I[59] = (T)(img)(x,_n4##y,z,c)), \ + (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[28] = (T)(img)(_n1##x,y,z,c)), \ + (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[29] = (T)(img)(_n2##x,y,z,c)), \ + (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[30] = (T)(img)(_n3##x,y,z,c)), \ + (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[31] = (T)(img)(_n4##x,y,z,c)), \ + (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for9x9(img,x,y,z,c,I,T) \ + cimg_for9((img)._height,y) for (int x = 0, \ + _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ + _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ + _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \ + (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \ + (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \ + (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \ + (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \ + (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \ + (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \ + (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + 4>=((img)._width)?(img).width() - 1:4); \ + (_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,c,I,T) \ + cimg_for_in9((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p4##x = x - 4<0?0:x - 4, \ + _p3##x = x - 3<0?0:x - 3, \ + _p2##x = x - 2<0?0:x - 2, \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \ + _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ + _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ + _n4##x = (int)( \ + (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \ + (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \ + (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \ + (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \ + (I[36] = (T)(img)(_p4##x,y,z,c)), \ + (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \ + (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \ + (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \ + (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \ + (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \ + (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \ + (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \ + (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \ + (I[37] = (T)(img)(_p3##x,y,z,c)), \ + (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \ + (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \ + (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \ + (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \ + (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \ + (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \ + (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \ + (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \ + (I[38] = (T)(img)(_p2##x,y,z,c)), \ + (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \ + (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \ + (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \ + (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \ + (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \ + (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \ + (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[39] = (T)(img)(_p1##x,y,z,c)), \ + (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \ + (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \ + (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \ + (I[4] = (T)(img)(x,_p4##y,z,c)), \ + (I[13] = (T)(img)(x,_p3##y,z,c)), \ + (I[22] = (T)(img)(x,_p2##y,z,c)), \ + (I[31] = (T)(img)(x,_p1##y,z,c)), \ + (I[40] = (T)(img)(x,y,z,c)), \ + (I[49] = (T)(img)(x,_n1##y,z,c)), \ + (I[58] = (T)(img)(x,_n2##y,z,c)), \ + (I[67] = (T)(img)(x,_n3##y,z,c)), \ + (I[76] = (T)(img)(x,_n4##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ + (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ + (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[41] = (T)(img)(_n1##x,y,z,c)), \ + (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ + (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ + (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ + (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ + (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ + (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ + (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ + (I[42] = (T)(img)(_n2##x,y,z,c)), \ + (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ + (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ + (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ + (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ + (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ + (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ + (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ + (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ + (I[43] = (T)(img)(_n3##x,y,z,c)), \ + (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ + (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ + (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ + (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ + x + 4>=(img).width()?(img).width() - 1:x + 4); \ + x<=(int)(x1) && ((_n4##x<(img).width() && ( \ + (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ + (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ + (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ + (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ + (I[44] = (T)(img)(_n4##x,y,z,c)), \ + (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ + (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ + (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ + (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \ + I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \ + I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \ + I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for2x2x2(img,x,y,z,c,I,T) \ + cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(0,y,z,c)), \ + (I[2] = (T)(img)(0,_n1##y,z,c)), \ + (I[4] = (T)(img)(0,y,_n1##z,c)), \ + (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in2((img)._depth,z0,z1,z) cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (T)(img)(x,y,z,c)), \ + (I[2] = (T)(img)(x,_n1##y,z,c)), \ + (I[4] = (T)(img)(x,y,_n1##z,c)), \ + (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[1] = (T)(img)(_n1##x,y,z,c)), \ + (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for3x3x3(img,x,y,z,c,I,T) \ + cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \ + (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \ + (I[12] = I[13] = (T)(img)(0,y,z,c)), \ + (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \ + (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \ + (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \ + (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \ + cimg_for_in3((img)._depth,z0,z1,z) cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x - 1<0?0:x - 1, \ + _n1##x = (int)( \ + (I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ + (I[3] = (T)(img)(_p1##x,y,_p1##z,c)), \ + (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \ + (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[12] = (T)(img)(_p1##x,y,z,c)), \ + (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \ + (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \ + (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \ + (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \ + (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \ + (I[4] = (T)(img)(x,y,_p1##z,c)), \ + (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \ + (I[10] = (T)(img)(x,_p1##y,z,c)), \ + (I[13] = (T)(img)(x,y,z,c)), \ + (I[16] = (T)(img)(x,_n1##y,z,c)), \ + (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \ + (I[22] = (T)(img)(x,y,_n1##z,c)), \ + (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \ + x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ + x<=(int)(x1) && ((_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ + (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ + (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[14] = (T)(img)(_n1##x,y,z,c)), \ + (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ + (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ + (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ + (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l) +#define cimglist_for_in(list,l0,l1,l) \ + for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width - 1; \ + l<=_max##l; ++l) + +#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn + +// Macros used to display error messages when exceptions are thrown. +// You should not use these macros is your own code. +#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::" +#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']' +#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::" +#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type() +#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::" +#define cimglist_instance _width,_allocated_width,_data,pixel_type() + +/*------------------------------------------------ + # + # + # Define cimg_library:: namespace + # + # + -------------------------------------------------*/ +//! Contains all classes and functions of the \CImg library. +/** + This namespace is defined to avoid functions and class names collisions + that could happen with the inclusion of other C++ header files. + Anyway, it should not happen often and you should reasonnably start most of your + \CImg-based programs with + \code + #include "CImg.h" + using namespace cimg_library; + \endcode + to simplify the declaration of \CImg Library objects afterwards. +**/ +namespace cimg_library_suffixed { + + // Declare the four classes of the CImg Library. + template struct CImg; + template struct CImgList; + struct CImgDisplay; + struct CImgException; + + // Declare cimg:: namespace. + // This is an uncomplete namespace definition here. It only contains some + // necessary stuff to ensure a correct declaration order of the classes and functions + // defined afterwards. + namespace cimg { + + // Define Ascii sequences for colored terminal output. +#ifdef cimg_use_vt100 + static const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 }; + static const char t_black[] = { 0x1b, '[', '0', ';', '3', '0', ';', '5', '9', 'm', 0 }; + static const char t_red[] = { 0x1b, '[', '0', ';', '3', '1', ';', '5', '9', 'm', 0 }; + static const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 }; + static const char t_yellow[] = { 0x1b, '[', '0', ';', '3', '3', ';', '5', '9', 'm', 0 }; + static const char t_blue[] = { 0x1b, '[', '0', ';', '3', '4', ';', '5', '9', 'm', 0 }; + static const char t_magenta[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 }; + static const char t_cyan[] = { 0x1b, '[', '0', ';', '3', '6', ';', '5', '9', 'm', 0 }; + static const char t_white[] = { 0x1b, '[', '0', ';', '3', '7', ';', '5', '9', 'm', 0 }; + static const char t_bold[] = { 0x1b, '[', '1', 'm', 0 }; + static const char t_underscore[] = { 0x1b, '[', '4', 'm', 0 }; +#else + static const char t_normal[] = { 0 }; + static const char *const t_black = cimg::t_normal, + *const t_red = cimg::t_normal, + *const t_green = cimg::t_normal, + *const t_yellow = cimg::t_normal, + *const t_blue = cimg::t_normal, + *const t_magenta = cimg::t_normal, + *const t_cyan = cimg::t_normal, + *const t_white = cimg::t_normal, + *const t_bold = cimg::t_normal, + *const t_underscore = cimg::t_normal; +#endif + + inline std::FILE* output(std::FILE *file=0); + inline void info(); + + //! Avoid warning messages due to unused parameters. Do nothing actually. + template + inline void unused(const T&, ...) {} + + // [internal] Lock/unlock a mutex for managing concurrent threads. + // 'lock_mode' can be { 0=unlock | 1=lock | 2=trylock }. + // 'n' can be in [0,31] but mutex range [0,15] is reserved by CImg. + inline int mutex(const unsigned int n, const int lock_mode=1); + + inline unsigned int& exception_mode(const unsigned int value, const bool is_set) { + static unsigned int mode = cimg_verbosity; + if (is_set) { cimg::mutex(0); mode = value<4?value:4; cimg::mutex(0,0); } + return mode; + } + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + inline FILE* _stdin(const bool throw_exception=true); + inline FILE* _stdout(const bool throw_exception=true); + inline FILE* _stderr(const bool throw_exception=true); + + // Mandatory because Microsoft's _snprintf() and _vsnprintf() do not add the '\0' character + // at the end of the string. +#if cimg_OS==2 && defined(_MSC_VER) + inline int _snprintf(char *const s, const size_t size, const char *const format, ...) { + va_list ap; + va_start(ap,format); + const int result = _vsnprintf(s,size,format,ap); + va_end(ap); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char *const format, va_list ap) { + int result = -1; + cimg::mutex(6); + if (size) result = _vsnprintf_s(s,size,_TRUNCATE,format,ap); + if (result==-1) result = _vscprintf(format,ap); + cimg::mutex(6,0); + return result; + } + + // Mutex-protected version of sscanf, sprintf and snprintf. + // Used only MacOSX, as it seems those functions are not re-entrant on MacOSX. +#elif defined(__MACOSX__) || defined(__APPLE__) + inline int _sscanf(const char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsscanf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _sprintf(char *const s, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsprintf(s,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _snprintf(char *const s, const size_t n, const char *const format, ...) { + cimg::mutex(6); + va_list args; + va_start(args,format); + const int result = std::vsnprintf(s,n,format,args); + va_end(args); + cimg::mutex(6,0); + return result; + } + + inline int _vsnprintf(char *const s, const size_t size, const char* format, va_list ap) { + cimg::mutex(6); + const int result = std::vsnprintf(s,size,format,ap); + cimg::mutex(6,0); + return result; + } +#endif + + //! Set current \CImg exception mode. + /** + The way error messages are handled by \CImg can be changed dynamically, using this function. + \param mode Desired exception mode. Possible values are: + - \c 0: Hide library messages (quiet mode). + - \c 1: Print library messages on the console. + - \c 2: Display library messages on a dialog window. + - \c 3: Do as \c 1 + add extra debug warnings (slow down the code!). + - \c 4: Do as \c 2 + add extra debug warnings (slow down the code!). + **/ + inline unsigned int& exception_mode(const unsigned int mode) { + return exception_mode(mode,true); + } + + //! Return current \CImg exception mode. + /** + \note By default, return the value of configuration macro \c cimg_verbosity + **/ + inline unsigned int& exception_mode() { + return exception_mode(0,false); + } + + 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; + } + + //! Set current \CImg openmp mode. + /** + The way openmp-based methods are handled by \CImg can be changed dynamically, using this function. + \param mode Desired openmp mode. Possible values are: + - \c 0: Never parallelize. + - \c 1: Always parallelize. + - \c 2: Adaptive parallelization mode (default behavior). + **/ + inline unsigned int openmp_mode(const unsigned int mode) { + return openmp_mode(mode,true); + } + + //! Return current \CImg openmp mode. + inline unsigned int openmp_mode() { + return openmp_mode(0,false); + } + +#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); + + // Evaluate math expression. + inline double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0); + + } + + /*--------------------------------------- + # + # Define the CImgException structures + # + --------------------------------------*/ + //! Instances of \c CImgException are thrown when errors are encountered in a \CImg function call. + /** + \par Overview + + CImgException is the base class of all exceptions thrown by \CImg (except \b CImgAbortException). + CImgException is never thrown itself. Derived classes that specify the type of errord are thrown instead. + These classes can be: + + - \b CImgAbortException: Thrown when a computationally-intensive function is aborted by an external signal. + This is the only \c non-derived exception class. + + - \b CImgArgumentException: Thrown when one argument of a called \CImg function is invalid. + This is probably one of the most thrown exception by \CImg. + For instance, the following example throws a \c CImgArgumentException: + \code + CImg img(100,100,1,3); // Define a 100x100 color image with float-valued pixels + img.mirror('e'); // Try to mirror image along the (non-existing) 'e'-axis + \endcode + + - \b CImgDisplayException: Thrown when something went wrong during the display of images in CImgDisplay instances. + + - \b CImgInstanceException: Thrown when an instance associated to a called \CImg method does not fit + the function requirements. For instance, the following example throws a \c CImgInstanceException: + \code + const CImg img; // Define an empty image + const float value = img.at(0); // Try to read first pixel value (does not exist) + \endcode + + - \b CImgIOException: Thrown when an error occured when trying to load or save image files. + This happens when trying to read files that do not exist or with invalid formats. + For instance, the following example throws a \c CImgIOException: + \code + const CImg img("missing_file.jpg"); // Try to load a file that does not exist + \endcode + + - \b CImgWarningException: Thrown only if configuration macro \c cimg_strict_warnings is set, and + when a \CImg function has to display a warning message (see cimg::warn()). + + It is not recommended to throw CImgException instances by yourself, + since they are expected to be thrown only by \CImg. + When an error occurs in a library function call, \CImg may display error messages on the screen or on the + standard output, depending on the current \CImg exception mode. + The \CImg exception mode can be get and set by functions cimg::exception_mode() and + cimg::exception_mode(unsigned int). + + \par Exceptions handling + + In all cases, when an error occurs in \CImg, an instance of the corresponding exception class is thrown. + This may lead the program to break (this is the default behavior), but you can bypass this behavior by + handling the exceptions by yourself, + using a usual try { ... } catch () { ... } bloc, as in the following example: + \code + #define "CImg.h" + using namespace cimg_library; + int main() { + cimg::exception_mode(0); // Enable quiet exception mode + try { + ... // Here, do what you want to stress CImg + } catch (CImgException& e) { // You succeeded: something went wrong! + std::fprintf(stderr,"CImg Library Error: %s",e.what()); // Display your custom error message + ... // Do what you want now to save the ship! + } + } + \endcode + **/ + struct CImgException : public std::exception { +#define _cimg_exception_err(etype,disp_flag) \ + std::va_list ap, ap2; \ + va_start(ap,format); va_start(ap2,format); \ + int size = cimg_vsnprintf(0,0,format,ap2); \ + if (size++>=0) { \ + delete[] _message; \ + _message = new char[size]; \ + cimg_vsnprintf(_message,size,format,ap); \ + if (cimg::exception_mode()) { \ + std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \ + if (cimg_display && disp_flag && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } \ + catch (CImgException&) {} \ + if (cimg::exception_mode()>=3) cimg_library_suffixed::cimg::info(); \ + } \ + } \ + va_end(ap); va_end(ap2); \ + + char *_message; + CImgException() { _message = new char[1]; *_message = 0; } + CImgException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgException",true); } + CImgException(const CImgException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgException() throw() { delete[] _message; } + CImgException& operator=(const CImgException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgAbortException class is used to throw an exception when + // a computationally-intensive function has been aborted by an external signal. + struct CImgAbortException : public std::exception { + char *_message; + CImgAbortException() { _message = new char[1]; *_message = 0; } + CImgAbortException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgAbortException",true); } + CImgAbortException(const CImgAbortException& e):std::exception(e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + } + ~CImgAbortException() throw() { delete[] _message; } + CImgAbortException& operator=(const CImgAbortException& e) { + const size_t size = std::strlen(e._message); + _message = new char[size + 1]; + std::strncpy(_message,e._message,size); + _message[size] = 0; + return *this; + } + //! Return a C-string containing the error message associated to the thrown exception. + const char *what() const throw() { return _message; } + }; + + // The CImgArgumentException class is used to throw an exception related + // to invalid arguments encountered in a library function call. + struct CImgArgumentException : public CImgException { + CImgArgumentException(const char *const format, ...) { _cimg_exception_err("CImgArgumentException",true); } + }; + + // The CImgDisplayException class is used to throw an exception related + // to display problems encountered in a library function call. + struct CImgDisplayException : public CImgException { + CImgDisplayException(const char *const format, ...) { _cimg_exception_err("CImgDisplayException",false); } + }; + + // The CImgInstanceException class is used to throw an exception related + // to an invalid instance encountered in a library function call. + struct CImgInstanceException : public CImgException { + CImgInstanceException(const char *const format, ...) { _cimg_exception_err("CImgInstanceException",true); } + }; + + // The CImgIOException class is used to throw an exception related + // to input/output file problems encountered in a library function call. + struct CImgIOException : public CImgException { + CImgIOException(const char *const format, ...) { _cimg_exception_err("CImgIOException",true); } + }; + + // The CImgWarningException class is used to throw an exception for warnings + // encountered in a library function call. + struct CImgWarningException : public CImgException { + CImgWarningException(const char *const format, ...) { _cimg_exception_err("CImgWarningException",false); } + }; + + /*------------------------------------- + # + # Define cimg:: namespace + # + -----------------------------------*/ + //! Contains \a low-level functions and variables of the \CImg Library. + /** + Most of the functions and variables within this namespace are used by the \CImg library for low-level operations. + You may use them to access specific const values or environment variables internally used by \CImg. + \warning Never write using namespace cimg_library::cimg; in your source code. Lot of functions in the + cimg:: namespace have the same names as standard C functions that may be defined in the global + namespace ::. + **/ + namespace cimg { + + // Define traits that will be used to determine the best data type to work in CImg functions. + // + template struct type { + static const char* string() { + static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24", + "unknown32", "unknown40", "unknown48", "unknown56", + "unknown64", "unknown72", "unknown80", "unknown88", + "unknown96", "unknown104", "unknown112", "unknown120", + "unknown128" }; + return s[(sizeof(T)<17)?sizeof(T):0]; + } + static bool is_float() { return false; } + static bool is_inf(const T) { return false; } + static bool is_nan(const T) { return false; } + static T min() { return ~max(); } + static T max() { return (T)1<<(8*sizeof(T) - 1); } + static T inf() { return max(); } + static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const T& val) { static const char *const s = "unknown"; cimg::unused(val); return s; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "bool"; return s; } + static bool is_float() { return false; } + static bool is_inf(const bool) { return false; } + static bool is_nan(const bool) { return false; } + static bool min() { return false; } + static bool max() { return true; } + static bool inf() { return max(); } + static bool is_inf() { return false; } + static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; } + static const char* format() { return "%s"; } + static const char* format_s() { return "%s"; } + static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned char) { return false; } + static bool is_nan(const unsigned char) { return false; } + static unsigned char min() { return 0; } + static unsigned char max() { return (unsigned char)-1; } + static unsigned char inf() { return max(); } + static unsigned char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned char val) { return (unsigned int)val; } + }; + +#if defined(CHAR_MAX) && CHAR_MAX==255 + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return 0; } + static char max() { return (char)-1; } + static char inf() { return max(); } + static char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const char val) { return (unsigned int)val; } + }; +#else + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const char) { return false; } + static bool is_nan(const char) { return false; } + static char min() { return ~max(); } + static char max() { return (char)((unsigned char)-1>>1); } + static char inf() { return max(); } + static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const char val) { return (int)val; } + }; +#endif + + template<> struct type { + static const char* string() { static const char *const s = "signed char"; return s; } + static bool is_float() { return false; } + static bool is_inf(const signed char) { return false; } + static bool is_nan(const signed char) { return false; } + static signed char min() { return ~max(); } + static signed char max() { return (signed char)((unsigned char)-1>>1); } + static signed char inf() { return max(); } + static signed char cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(signed char)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const signed char val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned short) { return false; } + static bool is_nan(const unsigned short) { return false; } + static unsigned short min() { return 0; } + static unsigned short max() { return (unsigned short)-1; } + static unsigned short inf() { return max(); } + static unsigned short cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned short val) { return (unsigned int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "short"; return s; } + static bool is_float() { return false; } + static bool is_inf(const short) { return false; } + static bool is_nan(const short) { return false; } + static short min() { return ~max(); } + static short max() { return (short)((unsigned short)-1>>1); } + static short inf() { return max(); } + static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const short val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const unsigned int) { return false; } + static bool is_nan(const unsigned int) { return false; } + static unsigned int min() { return 0; } + static unsigned int max() { return (unsigned int)-1; } + static unsigned int inf() { return max(); } + static unsigned int cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; } + static const char* format() { return "%u"; } + static const char* format_s() { return "%u"; } + static unsigned int format(const unsigned int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int"; return s; } + static bool is_float() { return false; } + static bool is_inf(const int) { return false; } + static bool is_nan(const int) { return false; } + static int min() { return ~max(); } + static int max() { return (int)((unsigned int)-1>>1); } + static int inf() { return max(); } + static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; } + static const char* format() { return "%d"; } + static const char* format_s() { return "%d"; } + static int format(const int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_uint64) { return false; } + static bool is_nan(const cimg_uint64) { return false; } + static cimg_uint64 min() { return 0; } + static cimg_uint64 max() { return (cimg_uint64)-1; } + static cimg_uint64 inf() { return max(); } + static cimg_uint64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; } + static const char* format() { return cimg_fuint64; } + static const char* format_s() { return cimg_fuint64; } + static unsigned long format(const cimg_uint64 val) { return (unsigned long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_int64) { return false; } + static bool is_nan(const cimg_int64) { return false; } + static cimg_int64 min() { return ~max(); } + static cimg_int64 max() { return (cimg_int64)((cimg_uint64)-1>>1); } + static cimg_int64 inf() { return max(); } + static cimg_int64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; + } + static const char* format() { return cimg_fint64; } + static const char* format_s() { return cimg_fint64; } + static long format(const long val) { return (long)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const double val) { // Custom version that works with '-ffast-math' + if (sizeof(double)==8) { + cimg_uint64 u; + std::memcpy(&u,&val,sizeof(double)); + return ((unsigned int)(u>>32)&0x7fffffff) + ((unsigned int)u!=0)>0x7ff00000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static double min() { return -DBL_MAX; } + static double max() { return DBL_MAX; } + static double inf() { +#ifdef INFINITY + return (double)INFINITY; +#else + return max()*max(); +#endif + } + static double nan() { +#ifdef NAN + return (double)NAN; +#else + const double val_nan = -std::sqrt(-1.); return val_nan; +#endif + } + static double cut(const double val) { return val; } + static const char* format() { return "%.17g"; } + static const char* format_s() { return "%g"; } + static double format(const double val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "float"; return s; } + static bool is_float() { return true; } + static bool is_inf(const float val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const float val) { // Custom version that works with '-ffast-math' + if (sizeof(float)==4) { + unsigned int u; + std::memcpy(&u,&val,sizeof(float)); + return (u&0x7fffffff)>0x7f800000; + } +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static float min() { return -FLT_MAX; } + static float max() { return FLT_MAX; } + static float inf() { return (float)cimg::type::inf(); } + static float nan() { return (float)cimg::type::nan(); } + static float cut(const double val) { return (float)val; } + static float cut(const float val) { return (float)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const float val) { return (double)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "long double"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const long double val) { +#ifdef isnan + return (bool)isnan(val); +#else + return !(val==val); +#endif + } + static long double min() { return -LDBL_MAX; } + static long double max() { return LDBL_MAX; } + static long double inf() { return max()*max(); } + static long double nan() { const long double val_nan = -std::sqrt(-1.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"; } + static double format(const long double val) { return (double)val; } + }; + +#ifdef cimg_use_half + template<> struct type { + static const char* string() { static const char *const s = "half"; return s; } + static bool is_float() { return true; } + static bool is_inf(const long double val) { +#ifdef isinf + return (bool)isinf(val); +#else + return !is_nan(val) && (val::min() || val>cimg::type::max()); +#endif + } + static bool is_nan(const half val) { // Custom version that works with '-ffast-math' + if (sizeof(half)==2) { + short u; + std::memcpy(&u,&val,sizeof(short)); + return (bool)((u&0x7fff)>0x7c00); + } + return cimg::type::is_nan((float)val); + } + 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.); return val_nan; } + static half cut(const double val) { return (half)val; } + static const char* format() { return "%.9g"; } + static const char* format_s() { return "%g"; } + static double format(const half val) { return (double)val; } + }; +#endif + + template struct superset { typedef T type; }; + template<> struct superset { typedef unsigned char type; }; + template<> struct superset { typedef char type; }; + template<> struct superset { typedef signed char type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 cimg_uint64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double 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 float type; }; + template<> struct superset { typedef double 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 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; }; + template<> struct superset { typedef double type; }; +#ifdef cimg_use_half + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; +#endif + + template struct superset2 { + typedef typename superset::type>::type type; + }; + + template struct superset3 { + typedef typename superset::type>::type type; + }; + + template struct last { typedef t2 type; }; + +#define _cimg_Tt 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 { + unsigned int nb_wins; + pthread_t *events_thread; + pthread_cond_t wait_event; + pthread_mutex_t wait_event_mutex; + CImgDisplay **wins; + Display *display; + unsigned int nb_bits; + bool is_blue_first; + bool is_shm_enabled; + bool byte_order; +#ifdef cimg_use_xrandr + XRRScreenSize *resolutions; + Rotation curr_rotation; + unsigned int curr_resolution; + unsigned int nb_resolutions; +#endif + X11_info():nb_wins(0),events_thread(0),display(0), + nb_bits(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) { +#ifdef __FreeBSD__ + XInitThreads(); +#endif + wins = new CImgDisplay*[1024]; + pthread_mutex_init(&wait_event_mutex,0); + pthread_cond_init(&wait_event,0); +#ifdef cimg_use_xrandr + resolutions = 0; + curr_rotation = 0; + curr_resolution = nb_resolutions = 0; +#endif + } + + ~X11_info() { + delete[] wins; + /* + if (events_thread) { + pthread_cancel(*events_thread); + delete events_thread; + } + if (display) { } // XCloseDisplay(display); } + pthread_cond_destroy(&wait_event); + pthread_mutex_unlock(&wait_event_mutex); + pthread_mutex_destroy(&wait_event_mutex); + */ + } + }; +#if defined(cimg_module) + X11_info& X11_attr(); +#elif defined(cimg_main) + X11_info& X11_attr() { static X11_info val; return val; } +#else + inline X11_info& X11_attr() { static X11_info val; return val; } +#endif + +#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 +#define cimg_lock_display() cimg::mutex(15) +#define cimg_unlock_display() cimg::mutex(15,0) + + struct Mutex_info { +#ifdef _PTHREAD_H + pthread_mutex_t mutex[32]; + Mutex_info() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); } + void lock(const unsigned int n) { pthread_mutex_lock(&mutex[n]); } + void unlock(const unsigned int n) { pthread_mutex_unlock(&mutex[n]); } + int trylock(const unsigned int n) { return pthread_mutex_trylock(&mutex[n]); } +#elif 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; } +#else + Mutex_info() {} + void lock(const unsigned int) {} + void unlock(const unsigned int) {} + int trylock(const unsigned int) { return 0; } +#endif + }; +#if defined(cimg_module) + Mutex_info& Mutex_attr(); +#elif defined(cimg_main) + Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#else + inline Mutex_info& Mutex_attr() { static Mutex_info val; return val; } +#endif + +#if defined(cimg_use_magick) + static struct Magick_info { + Magick_info() { + Magick::InitializeMagick(""); + } + } _Magick_info; +#endif + +#if cimg_display==1 + // Define keycodes for X11-based graphical systems. + const unsigned int keyESC = XK_Escape; + const unsigned int keyF1 = XK_F1; + const unsigned int keyF2 = XK_F2; + const unsigned int keyF3 = XK_F3; + const unsigned int keyF4 = XK_F4; + const unsigned int keyF5 = XK_F5; + const unsigned int keyF6 = XK_F6; + const unsigned int keyF7 = XK_F7; + const unsigned int keyF8 = XK_F8; + const unsigned int keyF9 = XK_F9; + const unsigned int keyF10 = XK_F10; + const unsigned int keyF11 = XK_F11; + const unsigned int keyF12 = XK_F12; + const unsigned int keyPAUSE = XK_Pause; + const unsigned int key1 = XK_1; + const unsigned int key2 = XK_2; + const unsigned int key3 = XK_3; + const unsigned int key4 = XK_4; + const unsigned int key5 = XK_5; + const unsigned int key6 = XK_6; + const unsigned int key7 = XK_7; + const unsigned int key8 = XK_8; + const unsigned int key9 = XK_9; + const unsigned int key0 = XK_0; + const unsigned int keyBACKSPACE = XK_BackSpace; + const unsigned int keyINSERT = XK_Insert; + const unsigned int keyHOME = XK_Home; + const unsigned int keyPAGEUP = XK_Page_Up; + const unsigned int keyTAB = XK_Tab; + const unsigned int keyQ = XK_q; + const unsigned int keyW = XK_w; + const unsigned int keyE = XK_e; + const unsigned int keyR = XK_r; + const unsigned int keyT = XK_t; + const unsigned int keyY = XK_y; + const unsigned int keyU = XK_u; + const unsigned int keyI = XK_i; + const unsigned int keyO = XK_o; + const unsigned int keyP = XK_p; + const unsigned int keyDELETE = XK_Delete; + const unsigned int keyEND = XK_End; + const unsigned int keyPAGEDOWN = XK_Page_Down; + const unsigned int keyCAPSLOCK = XK_Caps_Lock; + const unsigned int keyA = XK_a; + const unsigned int keyS = XK_s; + const unsigned int keyD = XK_d; + const unsigned int keyF = XK_f; + const unsigned int keyG = XK_g; + const unsigned int keyH = XK_h; + const unsigned int keyJ = XK_j; + const unsigned int keyK = XK_k; + const unsigned int keyL = XK_l; + const unsigned int keyENTER = XK_Return; + const unsigned int keySHIFTLEFT = XK_Shift_L; + const unsigned int keyZ = XK_z; + const unsigned int keyX = XK_x; + const unsigned int keyC = XK_c; + const unsigned int keyV = XK_v; + const unsigned int keyB = XK_b; + const unsigned int keyN = XK_n; + const unsigned int keyM = XK_m; + const unsigned int keySHIFTRIGHT = XK_Shift_R; + const unsigned int keyARROWUP = XK_Up; + const unsigned int keyCTRLLEFT = XK_Control_L; + const unsigned int keyAPPLEFT = XK_Super_L; + const unsigned int keyALT = XK_Alt_L; + const unsigned int keySPACE = XK_space; + const unsigned int keyALTGR = XK_Alt_R; + const unsigned int keyAPPRIGHT = XK_Super_R; + const unsigned int keyMENU = XK_Menu; + const unsigned int keyCTRLRIGHT = XK_Control_R; + const unsigned int keyARROWLEFT = XK_Left; + const unsigned int keyARROWDOWN = XK_Down; + const unsigned int keyARROWRIGHT = XK_Right; + const unsigned int keyPAD0 = XK_KP_0; + const unsigned int keyPAD1 = XK_KP_1; + const unsigned int keyPAD2 = XK_KP_2; + const unsigned int keyPAD3 = XK_KP_3; + const unsigned int keyPAD4 = XK_KP_4; + const unsigned int keyPAD5 = XK_KP_5; + const unsigned int keyPAD6 = XK_KP_6; + const unsigned int keyPAD7 = XK_KP_7; + const unsigned int keyPAD8 = XK_KP_8; + const unsigned int keyPAD9 = XK_KP_9; + const unsigned int keyPADADD = XK_KP_Add; + const unsigned int keyPADSUB = XK_KP_Subtract; + const unsigned int keyPADMUL = XK_KP_Multiply; + const unsigned int keyPADDIV = XK_KP_Divide; + +#elif cimg_display==2 + // Define keycodes for Windows. + const unsigned int keyESC = VK_ESCAPE; + const unsigned int keyF1 = VK_F1; + const unsigned int keyF2 = VK_F2; + const unsigned int keyF3 = VK_F3; + const unsigned int keyF4 = VK_F4; + const unsigned int keyF5 = VK_F5; + const unsigned int keyF6 = VK_F6; + const unsigned int keyF7 = VK_F7; + const unsigned int keyF8 = VK_F8; + const unsigned int keyF9 = VK_F9; + const unsigned int keyF10 = VK_F10; + const unsigned int keyF11 = VK_F11; + const unsigned int keyF12 = VK_F12; + const unsigned int keyPAUSE = VK_PAUSE; + const unsigned int key1 = '1'; + const unsigned int key2 = '2'; + const unsigned int key3 = '3'; + const unsigned int key4 = '4'; + const unsigned int key5 = '5'; + const unsigned int key6 = '6'; + const unsigned int key7 = '7'; + const unsigned int key8 = '8'; + const unsigned int key9 = '9'; + const unsigned int key0 = '0'; + const unsigned int keyBACKSPACE = VK_BACK; + const unsigned int keyINSERT = VK_INSERT; + const unsigned int keyHOME = VK_HOME; + const unsigned int keyPAGEUP = VK_PRIOR; + const unsigned int keyTAB = VK_TAB; + const unsigned int keyQ = 'Q'; + const unsigned int keyW = 'W'; + const unsigned int keyE = 'E'; + const unsigned int keyR = 'R'; + const unsigned int keyT = 'T'; + const unsigned int keyY = 'Y'; + const unsigned int keyU = 'U'; + const unsigned int keyI = 'I'; + const unsigned int keyO = 'O'; + const unsigned int keyP = 'P'; + const unsigned int keyDELETE = VK_DELETE; + const unsigned int keyEND = VK_END; + const unsigned int keyPAGEDOWN = VK_NEXT; + const unsigned int keyCAPSLOCK = VK_CAPITAL; + const unsigned int keyA = 'A'; + const unsigned int keyS = 'S'; + const unsigned int keyD = 'D'; + const unsigned int keyF = 'F'; + const unsigned int keyG = 'G'; + const unsigned int keyH = 'H'; + const unsigned int keyJ = 'J'; + const unsigned int keyK = 'K'; + const unsigned int keyL = 'L'; + const unsigned int keyENTER = VK_RETURN; + const unsigned int keySHIFTLEFT = VK_SHIFT; + const unsigned int keyZ = 'Z'; + const unsigned int keyX = 'X'; + const unsigned int keyC = 'C'; + const unsigned int keyV = 'V'; + const unsigned int keyB = 'B'; + const unsigned int keyN = 'N'; + const unsigned int keyM = 'M'; + const unsigned int keySHIFTRIGHT = VK_SHIFT; + const unsigned int keyARROWUP = VK_UP; + const unsigned int keyCTRLLEFT = VK_CONTROL; + const unsigned int keyAPPLEFT = VK_LWIN; + const unsigned int keyALT = VK_LMENU; + const unsigned int keySPACE = VK_SPACE; + const unsigned int keyALTGR = VK_CONTROL; + const unsigned int keyAPPRIGHT = VK_RWIN; + const unsigned int keyMENU = VK_APPS; + const unsigned int keyCTRLRIGHT = VK_CONTROL; + const unsigned int keyARROWLEFT = VK_LEFT; + const unsigned int keyARROWDOWN = VK_DOWN; + const unsigned int keyARROWRIGHT = VK_RIGHT; + const unsigned int keyPAD0 = 0x60; + const unsigned int keyPAD1 = 0x61; + const unsigned int keyPAD2 = 0x62; + const unsigned int keyPAD3 = 0x63; + const unsigned int keyPAD4 = 0x64; + const unsigned int keyPAD5 = 0x65; + const unsigned int keyPAD6 = 0x66; + const unsigned int keyPAD7 = 0x67; + const unsigned int keyPAD8 = 0x68; + const unsigned int keyPAD9 = 0x69; + const unsigned int keyPADADD = VK_ADD; + const unsigned int keyPADSUB = VK_SUBTRACT; + const unsigned int keyPADMUL = VK_MULTIPLY; + const unsigned int keyPADDIV = VK_DIVIDE; + +#else + // Define random keycodes when no display is available. + // (should rarely be used then!). + const unsigned int keyESC = 1U; //!< Keycode for the \c ESC key (architecture-dependent) + const unsigned int keyF1 = 2U; //!< Keycode for the \c F1 key (architecture-dependent) + const unsigned int keyF2 = 3U; //!< Keycode for the \c F2 key (architecture-dependent) + const unsigned int keyF3 = 4U; //!< Keycode for the \c F3 key (architecture-dependent) + const unsigned int keyF4 = 5U; //!< Keycode for the \c F4 key (architecture-dependent) + const unsigned int keyF5 = 6U; //!< Keycode for the \c F5 key (architecture-dependent) + const unsigned int keyF6 = 7U; //!< Keycode for the \c F6 key (architecture-dependent) + const unsigned int keyF7 = 8U; //!< Keycode for the \c F7 key (architecture-dependent) + const unsigned int keyF8 = 9U; //!< Keycode for the \c F8 key (architecture-dependent) + const unsigned int keyF9 = 10U; //!< Keycode for the \c F9 key (architecture-dependent) + const unsigned int keyF10 = 11U; //!< Keycode for the \c F10 key (architecture-dependent) + const unsigned int keyF11 = 12U; //!< Keycode for the \c F11 key (architecture-dependent) + const unsigned int keyF12 = 13U; //!< Keycode for the \c F12 key (architecture-dependent) + const unsigned int keyPAUSE = 14U; //!< Keycode for the \c PAUSE key (architecture-dependent) + const unsigned int key1 = 15U; //!< Keycode for the \c 1 key (architecture-dependent) + const unsigned int key2 = 16U; //!< Keycode for the \c 2 key (architecture-dependent) + const unsigned int key3 = 17U; //!< Keycode for the \c 3 key (architecture-dependent) + const unsigned int key4 = 18U; //!< Keycode for the \c 4 key (architecture-dependent) + const unsigned int key5 = 19U; //!< Keycode for the \c 5 key (architecture-dependent) + const unsigned int key6 = 20U; //!< Keycode for the \c 6 key (architecture-dependent) + const unsigned int key7 = 21U; //!< Keycode for the \c 7 key (architecture-dependent) + const unsigned int key8 = 22U; //!< Keycode for the \c 8 key (architecture-dependent) + const unsigned int key9 = 23U; //!< Keycode for the \c 9 key (architecture-dependent) + const unsigned int key0 = 24U; //!< Keycode for the \c 0 key (architecture-dependent) + const unsigned int keyBACKSPACE = 25U; //!< Keycode for the \c BACKSPACE key (architecture-dependent) + const unsigned int keyINSERT = 26U; //!< Keycode for the \c INSERT key (architecture-dependent) + const unsigned int keyHOME = 27U; //!< Keycode for the \c HOME key (architecture-dependent) + const unsigned int keyPAGEUP = 28U; //!< Keycode for the \c PAGEUP key (architecture-dependent) + const unsigned int keyTAB = 29U; //!< Keycode for the \c TAB key (architecture-dependent) + const unsigned int keyQ = 30U; //!< Keycode for the \c Q key (architecture-dependent) + const unsigned int keyW = 31U; //!< Keycode for the \c W key (architecture-dependent) + const unsigned int keyE = 32U; //!< Keycode for the \c E key (architecture-dependent) + const unsigned int keyR = 33U; //!< Keycode for the \c R key (architecture-dependent) + const unsigned int keyT = 34U; //!< Keycode for the \c T key (architecture-dependent) + const unsigned int keyY = 35U; //!< Keycode for the \c Y key (architecture-dependent) + const unsigned int keyU = 36U; //!< Keycode for the \c U key (architecture-dependent) + const unsigned int keyI = 37U; //!< Keycode for the \c I key (architecture-dependent) + const unsigned int keyO = 38U; //!< Keycode for the \c O key (architecture-dependent) + const unsigned int keyP = 39U; //!< Keycode for the \c P key (architecture-dependent) + const unsigned int keyDELETE = 40U; //!< Keycode for the \c DELETE key (architecture-dependent) + const unsigned int keyEND = 41U; //!< Keycode for the \c END key (architecture-dependent) + const unsigned int keyPAGEDOWN = 42U; //!< Keycode for the \c PAGEDOWN key (architecture-dependent) + const unsigned int keyCAPSLOCK = 43U; //!< Keycode for the \c CAPSLOCK key (architecture-dependent) + const unsigned int keyA = 44U; //!< Keycode for the \c A key (architecture-dependent) + const unsigned int keyS = 45U; //!< Keycode for the \c S key (architecture-dependent) + const unsigned int keyD = 46U; //!< Keycode for the \c D key (architecture-dependent) + const unsigned int keyF = 47U; //!< Keycode for the \c F key (architecture-dependent) + const unsigned int keyG = 48U; //!< Keycode for the \c G key (architecture-dependent) + const unsigned int keyH = 49U; //!< Keycode for the \c H key (architecture-dependent) + const unsigned int keyJ = 50U; //!< Keycode for the \c J key (architecture-dependent) + const unsigned int keyK = 51U; //!< Keycode for the \c K key (architecture-dependent) + const unsigned int keyL = 52U; //!< Keycode for the \c L key (architecture-dependent) + const unsigned int keyENTER = 53U; //!< Keycode for the \c ENTER key (architecture-dependent) + const unsigned int keySHIFTLEFT = 54U; //!< Keycode for the \c SHIFTLEFT key (architecture-dependent) + const unsigned int keyZ = 55U; //!< Keycode for the \c Z key (architecture-dependent) + const unsigned int keyX = 56U; //!< Keycode for the \c X key (architecture-dependent) + const unsigned int keyC = 57U; //!< Keycode for the \c C key (architecture-dependent) + const unsigned int keyV = 58U; //!< Keycode for the \c V key (architecture-dependent) + const unsigned int keyB = 59U; //!< Keycode for the \c B key (architecture-dependent) + const unsigned int keyN = 60U; //!< Keycode for the \c N key (architecture-dependent) + const unsigned int keyM = 61U; //!< Keycode for the \c M key (architecture-dependent) + const unsigned int keySHIFTRIGHT = 62U; //!< Keycode for the \c SHIFTRIGHT key (architecture-dependent) + const unsigned int keyARROWUP = 63U; //!< Keycode for the \c ARROWUP key (architecture-dependent) + const unsigned int keyCTRLLEFT = 64U; //!< Keycode for the \c CTRLLEFT key (architecture-dependent) + const unsigned int keyAPPLEFT = 65U; //!< Keycode for the \c APPLEFT key (architecture-dependent) + const unsigned int keyALT = 66U; //!< Keycode for the \c ALT key (architecture-dependent) + const unsigned int keySPACE = 67U; //!< Keycode for the \c SPACE key (architecture-dependent) + const unsigned int keyALTGR = 68U; //!< Keycode for the \c ALTGR key (architecture-dependent) + const unsigned int keyAPPRIGHT = 69U; //!< Keycode for the \c APPRIGHT key (architecture-dependent) + const unsigned int keyMENU = 70U; //!< Keycode for the \c MENU key (architecture-dependent) + const unsigned int keyCTRLRIGHT = 71U; //!< Keycode for the \c CTRLRIGHT key (architecture-dependent) + const unsigned int keyARROWLEFT = 72U; //!< Keycode for the \c ARROWLEFT key (architecture-dependent) + const unsigned int keyARROWDOWN = 73U; //!< Keycode for the \c ARROWDOWN key (architecture-dependent) + const unsigned int keyARROWRIGHT = 74U; //!< Keycode for the \c ARROWRIGHT key (architecture-dependent) + const unsigned int keyPAD0 = 75U; //!< Keycode for the \c PAD0 key (architecture-dependent) + const unsigned int keyPAD1 = 76U; //!< Keycode for the \c PAD1 key (architecture-dependent) + const unsigned int keyPAD2 = 77U; //!< Keycode for the \c PAD2 key (architecture-dependent) + const unsigned int keyPAD3 = 78U; //!< Keycode for the \c PAD3 key (architecture-dependent) + const unsigned int keyPAD4 = 79U; //!< Keycode for the \c PAD4 key (architecture-dependent) + const unsigned int keyPAD5 = 80U; //!< Keycode for the \c PAD5 key (architecture-dependent) + const unsigned int keyPAD6 = 81U; //!< Keycode for the \c PAD6 key (architecture-dependent) + const unsigned int keyPAD7 = 82U; //!< Keycode for the \c PAD7 key (architecture-dependent) + const unsigned int keyPAD8 = 83U; //!< Keycode for the \c PAD8 key (architecture-dependent) + const unsigned int keyPAD9 = 84U; //!< Keycode for the \c PAD9 key (architecture-dependent) + const unsigned int keyPADADD = 85U; //!< Keycode for the \c PADADD key (architecture-dependent) + const unsigned int keyPADSUB = 86U; //!< Keycode for the \c PADSUB key (architecture-dependent) + const unsigned int keyPADMUL = 87U; //!< Keycode for the \c PADMUL key (architecture-dependent) + const unsigned int keyPADDIV = 88U; //!< Keycode for the \c PADDDIV key (architecture-dependent) +#endif + + const double PI = 3.14159265358979323846; //!< Value of the mathematical constant PI + + // Define a 10x13 binary font (small sans). + static const char *const data_font_small[] = { + " UwlwnwoyuwHwlwmwcwlwnw[xuwowlwmwoyuwRwlwnxcw Mw (wnwnwuwpwuypwuwoy" + "ZwnwmwuwowuwmwnwnwuwowuwfwuxnwnwmwuwpwuypwuwZwnwnwtwpwtwow'y Hw cwnw >{ 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 " + "rwswewnwuwowuwozswtwuwqwtwmwnwlwowuwuwowOxpxuxqwuwowswqwswoxpwlwjwqwswqwswy~}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 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

    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

    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 std::printf(). + \note If configuration macro \c cimg_strict_warnings is set, this function throws a + \c CImgWarningException instead. + \warning As the first argument is a format string, it is highly recommended to write + \code + cimg::warn("%s",warning_message); + \endcode + instead of + \code + cimg::warn(warning_message); + \endcode + if \c warning_message can be arbitrary, to prevent nasty memory access. + **/ + inline void warn(const char *const format, ...) { + if (cimg::exception_mode()>=1) { + char *const message = new char[16384]; + std::va_list ap; + va_start(ap,format); + cimg_vsnprintf(message,16384,format,ap); + va_end(ap); +#ifdef cimg_strict_warnings + throw CImgWarningException(message); +#else + std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s\n",cimg::t_red,cimg::t_normal,message); +#endif + delete[] message; + } + } + + // Execute an external system command. + /** + \param command C-string containing the command line to execute. + \param module_name Module name. + \return Status value of the executed command, whose meaning is OS-dependent. + \note This function is similar to std::system() + but it does not open an extra console windows + on Windows-based systems. + **/ + inline int system(const char *const command, const char *const module_name=0, const bool is_verbose=false) { + cimg::unused(module_name); +#ifdef cimg_no_system_calls + return -1; +#else + if (is_verbose) return std::system(command); +#if cimg_OS==1 + const unsigned int l = (unsigned int)std::strlen(command); + if (l) { + char *const ncommand = new char[l + 24]; + std::memcpy(ncommand,command,l); + 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; + std::memset(&pi,0,sizeof(PROCESS_INFORMATION)); + std::memset(&si,0,sizeof(STARTUPINFO)); + GetStartupInfo(&si); + si.cb = sizeof(si); + si.wShowWindow = SW_HIDE; + si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW; + const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi); + if (res) { + WaitForSingleObject(pi.hProcess,INFINITE); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; + } else return std::system(command); +#else + return std::system(command); +#endif +#endif + } + + //! Return a reference to a temporary variable of type T. + template + inline T& temporary(const T&) { + static T temp; + return temp; + } + + //! Exchange values of variables \c a and \c b. + template + inline void swap(T& a, T& b) { T t = a; a = b; b = t; } + + //! Exchange values of variables (\c a1,\c a2) and (\c b1,\c b2). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) { + cimg::swap(a1,b1); cimg::swap(a2,b2); + } + + //! Exchange values of variables (\c a1,\c a2,\c a3) and (\c b1,\c b2,\c b3). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) { + cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a4) and (\c b1,\c b2,...,\c b4). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) { + cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a5) and (\c b1,\c b2,...,\c b5). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a6) and (\c b1,\c b2,...,\c b6). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a7) and (\c b1,\c b2,...,\c b7). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7); + } + + //! Exchange values of variables (\c a1,\c a2,...,\c a8) and (\c b1,\c b2,...,\c b8). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7, T8& a8, T8& b8) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8); + } + + //! Return the endianness of the current architecture. + /** + \return \c false for Little Endian or \c true for Big Endian. + **/ + inline bool endianness() { + const int x = 1; + return ((unsigned char*)&x)[0]?false:true; + } + + //! Reverse endianness of all elements in a memory buffer. + /** + \param[in,out] buffer Memory buffer whose endianness must be reversed. + \param size Number of buffer elements to reverse. + **/ + template + inline void invert_endianness(T* const buffer, const cimg_ulong size) { + if (size) switch (sizeof(T)) { + case 1 : break; + case 2 : { + for (unsigned short *ptr = (unsigned short*)buffer + size; ptr>(unsigned short*)buffer; ) { + const unsigned short val = *(--ptr); + *ptr = (unsigned short)((val>>8) | ((val<<8))); + } + } break; + case 4 : { + for (unsigned int *ptr = (unsigned int*)buffer + size; ptr>(unsigned int*)buffer; ) { + const unsigned int val = *(--ptr); + *ptr = (val>>24) | ((val>>8)&0xff00) | ((val<<8)&0xff0000) | (val<<24); + } + } break; + case 8 : { + const cimg_uint64 + m0 = (cimg_uint64)0xff, m1 = m0<<8, m2 = m0<<16, m3 = m0<<24, + m4 = m0<<32, m5 = m0<<40, m6 = m0<<48, m7 = m0<<56; + for (cimg_uint64 *ptr = (cimg_uint64*)buffer + size; ptr>(cimg_uint64*)buffer; ) { + const cimg_uint64 val = *(--ptr); + *ptr = (((val&m7)>>56) | ((val&m6)>>40) | ((val&m5)>>24) | ((val&m4)>>8) | + ((val&m3)<<8) |((val&m2)<<24) | ((val&m1)<<40) | ((val&m0)<<56)); + } + } break; + default : { + for (T* ptr = buffer + size; ptr>buffer; ) { + unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T); + for (int i = 0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe)); + } + } + } + } + + //! Reverse endianness of a single variable. + /** + \param[in,out] a Variable to reverse. + \return Reference to reversed variable. + **/ + template + inline T& invert_endianness(T& a) { + invert_endianness(&a,1); + return a; + } + + // Conversion functions to get more precision when trying to store unsigned ints values as floats. + inline unsigned int float2uint(const float f) { + int tmp = 0; + std::memcpy(&tmp,&f,sizeof(float)); + if (tmp>=0) return (unsigned int)f; + unsigned int u; + // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&u,&f,sizeof(float)); + return ((u)<<1)>>1; // set sign bit to 0 + } + + inline float uint2float(const unsigned int u) { + if (u<(1U<<19)) return (float)u; // Consider safe storage of unsigned int as floats until 19bits (i.e 524287) + float f; + const unsigned int v = u|(1U<<(8*sizeof(unsigned int)-1)); // set sign bit to 1 + // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler. + std::memcpy(&f,&v,sizeof(float)); + return f; + } + + //! Return the value of a system timer, with a millisecond precision. + /** + \note The timer does not necessarily starts from \c 0. + **/ + inline cimg_ulong time() { +#if cimg_OS==1 + struct timeval st_time; + gettimeofday(&st_time,0); + return (cimg_ulong)(st_time.tv_usec/1000 + st_time.tv_sec*1000); +#elif cimg_OS==2 + SYSTEMTIME st_time; + GetLocalTime(&st_time); + return (cimg_ulong)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour))); +#else + return 0; +#endif + } + + // Implement a tic/toc mechanism to display elapsed time of algorithms. + inline cimg_ulong tictoc(const bool is_tic); + + //! Start tic/toc timer for time measurement between code instructions. + /** + \return Current value of the timer (same value as time()). + **/ + inline cimg_ulong tic() { + return cimg::tictoc(true); + } + + //! End tic/toc timer and displays elapsed time from last call to tic(). + /** + \return Time elapsed (in ms) since last call to tic(). + **/ + inline cimg_ulong toc() { + return cimg::tictoc(false); + } + + //! Sleep for a given numbers of milliseconds. + /** + \param milliseconds Number of milliseconds to wait for. + \note This function frees the CPU ressources during the sleeping time. + It can be used to temporize your program properly, without wasting CPU time. + **/ + inline void sleep(const unsigned int milliseconds) { +#if cimg_OS==1 + struct timespec tv; + tv.tv_sec = milliseconds/1000; + tv.tv_nsec = (milliseconds%1000)*1000000; + nanosleep(&tv,0); +#elif cimg_OS==2 + Sleep(milliseconds); +#else + cimg::unused(milliseconds); +#endif + } + + inline unsigned int wait(const unsigned int milliseconds, cimg_ulong *const p_timer) { + if (!*p_timer) *p_timer = cimg::time(); + const cimg_ulong current_time = cimg::time(); + if (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; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \return Number of milliseconds elapsed since the last call to wait(). + \note Same as sleep() with a waiting time computed with regard to the last call + of wait(). It may be used to temporize your program properly, without wasting CPU time. + **/ + inline cimg_long wait(const unsigned int milliseconds) { + cimg::mutex(3); + static cimg_ulong timer = cimg::time(); + cimg::mutex(3,0); + return cimg::wait(milliseconds,&timer); + } + + // Custom random number generator (allow re-entrance). + inline cimg_ulong& rng() { // Used as a shared global number for rng + static cimg_ulong rng = 0xB16B00B5U; + return rng; + } + + inline unsigned int _rand(cimg_ulong *const p_rng) { + *p_rng = *p_rng*1103515245 + 12345U; + return (unsigned int)*p_rng; + } + + inline unsigned int _rand() { + cimg::mutex(4); + const unsigned int res = cimg::_rand(&cimg::rng()); + cimg::mutex(4,0); + return res; + } + + inline void srand(cimg_ulong *const p_rng) { +#if cimg_OS==1 + *p_rng = cimg::time() + (cimg_ulong)getpid(); +#elif cimg_OS==2 + *p_rng = cimg::time() + (cimg_ulong)_getpid(); +#endif + } + + inline void srand() { + cimg::mutex(4); + cimg::srand(&cimg::rng()); + cimg::mutex(4,0); + } + + inline void srand(const cimg_ulong seed) { + cimg::mutex(4); + cimg::rng() = seed; + cimg::mutex(4,0); + } + + inline double rand(const double val_min, const double val_max, cimg_ulong *const p_rng) { + const double val = cimg::_rand(p_rng)/(double)~0U; + return val_min + (val_max - val_min)*val; + } + + 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; + } + + inline double rand(const double val_max, cimg_ulong *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_ulong *const p_rng) { + double x1, w; + do { + 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.); + return x1*std::sqrt((-2*std::log(w))/w); + } + + 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_ulong *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.; 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; + } + + //! Bitwise-rotate value on the left. + template + inline T rol(const T& a, const unsigned int n=1) { + return n?(T)((a<>((sizeof(T)<<3) - n))):a; + } + + inline float rol(const float a, const unsigned int n=1) { + return (float)rol((int)a,n); + } + + inline double rol(const double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + + inline double rol(const long double a, const unsigned int n=1) { + return (double)rol((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half rol(const half a, const unsigned int n=1) { + return (half)rol((int)a,n); + } +#endif + + //! Bitwise-rotate value on the right. + template + inline T ror(const T& a, const unsigned int n=1) { + return n?(T)((a>>n)|(a<<((sizeof(T)<<3) - n))):a; + } + + inline float ror(const float a, const unsigned int n=1) { + return (float)ror((int)a,n); + } + + inline double ror(const double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + + inline double ror(const long double a, const unsigned int n=1) { + return (double)ror((cimg_long)a,n); + } + +#ifdef cimg_use_half + inline half ror(const half a, const unsigned int n=1) { + return (half)ror((int)a,n); + } +#endif + + //! Return absolute value of a value. + template + inline T abs(const T& a) { + return a>=0?a:-a; + } + inline bool abs(const bool a) { + return a; + } + inline int abs(const unsigned char a) { + return (int)a; + } + inline int abs(const unsigned short a) { + return (int)a; + } + inline int abs(const unsigned int a) { + return (int)a; + } + inline int abs(const int a) { + return std::abs(a); + } + inline cimg_int64 abs(const cimg_uint64 a) { + return (cimg_int64)a; + } + inline double abs(const double a) { + return std::fabs(a); + } + inline float abs(const float a) { + return (float)std::fabs((double)a); + } + + //! Return hyperbolic arcosine of a value. + inline double acosh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::acosh(x); +#else + return std::log(x + std::sqrt(x*x - 1)); +#endif + } + + //! Return hyperbolic arcsine of a value. + inline double asinh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::asinh(x); +#else + return std::log(x + std::sqrt(x*x + 1)); +#endif + } + + //! Return hyperbolic arctangent of a value. + inline double atanh(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::atanh(x); +#else + return 0.5*std::log((1. + x)/(1. - x)); +#endif + } + + //! Return the sinc of a given value. + inline double sinc(const double x) { + return x?std::sin(x)/x:1; + } + + //! Return base-2 logarithm of a value. + inline double log2(const double x) { +#if cimg_use_cpp11==1 && !defined(_MSC_VER) + return std::log2(x); +#else + const double base2 = std::log(2.); + return std::log(x)/base2; +#endif + } + + //! Return square of a value. + template + inline T sqr(const T& val) { + return val*val; + } + + //! 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./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) { + return std::min(std::min(a,b),c); + } + + //! Return the minimum between four values. + template + inline t min(const t& a, const t& b, const t& c, const t& d) { + return std::min(std::min(a,b),std::min(c,d)); + } + + //! Return the maximum between three values. + template + inline t max(const t& a, const t& b, const t& c) { + return std::max(std::max(a,b),c); + } + + //! Return the maximum between four values. + template + inline t max(const t& a, const t& b, const t& c, const t& d) { + return std::max(std::max(a,b),std::max(c,d)); + } + + //! Return the sign of a value. + template + inline T sign(const T& x) { + return (T)(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; + while (x>i) i<<=1; + return i; + } + + //! Return the modulo of a value. + /** + \param x Input value. + \param m Modulo value. + \note This modulo function accepts negative and floating-points modulo numbers, as well as variables of any type. + **/ + template + inline T mod(const T& x, const T& m) { + const double dx = (double)x, dm = (double)m; + return (T)(dx - dm * std::floor(dx / dm)); + } + inline int mod(const bool x, const bool m) { + return m?(x?1:0):0; + } + inline int mod(const unsigned char x, const unsigned char m) { + return x%m; + } + inline int mod(const char x, const char m) { +#if defined(CHAR_MAX) && CHAR_MAX==255 + return x%m; +#else + return x>=0?x%m:(x%m?m + x%m:0); +#endif + } + inline int mod(const unsigned short x, const unsigned short m) { + return x%m; + } + inline int mod(const short x, const short m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline int mod(const unsigned int x, const unsigned int m) { + return (int)(x%m); + } + inline int mod(const int x, const int m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + inline cimg_int64 mod(const cimg_uint64 x, const cimg_uint64 m) { + return x%m; + } + inline cimg_int64 mod(const cimg_int64 x, const cimg_int64 m) { + return x>=0?x%m:(x%m?m + x%m:0); + } + + //! Return the min-mod of two values. + /** + \note minmod(\p a,\p b) is defined to be: + - minmod(\p a,\p b) = min(\p a,\p b), if \p a and \p b have the same sign. + - minmod(\p a,\p b) = 0, if \p a and \p b have different signs. + **/ + template + inline T minmod(const T& a, const T& b) { + return a*b<=0?0:(a>0?(a + inline T round(const T& x) { + return (T)std::floor((_cimg_Tfloat)x + 0.5f); + } + + //! Return rounded value. + /** + \param x Value to be rounded. + \param y Rounding precision. + \param rounding_type Type of rounding operation (\c 0 = nearest, \c -1 = backward, \c 1 = forward). + \return Rounded value, having the same type as input value \c x. + **/ + template + inline T round(const T& x, const double y, const int rounding_type=0) { + if (y<=0) return x; + if (y==1) switch (rounding_type) { + case 0 : return cimg::round(x); + case 1 : return (T)std::ceil((_cimg_Tfloat)x); + default : return (T)std::floor((_cimg_Tfloat)x); + } + const double sx = (double)x/y, floor = std::floor(sx), delta = sx - floor; + return (T)(y*(rounding_type<0?floor:rounding_type>0?std::ceil(sx):delta<0.5?floor:std::ceil(sx))); + } + + // Code to compute fast median from 2,3,5,7,9,13,25 and 49 values. + // (contribution by RawTherapee: http://rawtherapee.com/). + template + inline T median(T val0, T val1) { + return (val0 + val1)/2; + } + + template + inline T median(T val0, T val1, T val2) { + return std::max(std::min(val0,val1),std::min(val2,std::max(val0,val1))); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = std::max(val0,tmp); val1 = std::min(val1,val4); tmp = std::min(val1,val2); val2 = std::max(val1,val2); + val1 = tmp; tmp = std::min(val2,val3); + return std::max(val1,tmp); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6) { + T tmp = std::min(val0,val5); + val5 = std::max(val0,val5); val0 = tmp; tmp = std::min(val0,val3); val3 = std::max(val0,val3); val0 = tmp; + tmp = std::min(val1,val6); val6 = std::max(val1,val6); val1 = tmp; tmp = std::min(val2,val4); + val4 = std::max(val2,val4); val2 = tmp; val1 = std::max(val0,val1); tmp = std::min(val3,val5); + val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); + val3 = std::max(tmp,val3); val3 = std::min(val3,val6); tmp = std::min(val4,val5); val4 = std::max(val1,tmp); + tmp = std::min(val1,tmp); val3 = std::max(tmp,val3); + return std::min(val3,val4); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8) { + T tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val7 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); + val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val6,val7); + val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val1,val2); + val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); + val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); + val8 = std::max(val7,val8); val3 = std::max(val0,val3); val5 = std::min(val5,val8); + val7 = std::max(val4,tmp); tmp = std::min(val4,tmp); val6 = std::max(val3,val6); + val4 = std::max(val1,tmp); val2 = std::min(val2,val5); val4 = std::min(val4,val7); + tmp = std::min(val4,val2); val2 = std::max(val4,val2); val4 = std::max(val6,tmp); + return std::min(val4,val2); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8, T val9, T val10, T val11, + T val12) { + T tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = tmp; tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; + tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val5,val8); + val8 = std::max(val5,val8); val5 = tmp; tmp = std::min(val0,val12); val12 = std::max(val0,val12); + val0 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val2,val3); val3 = std::max(val2,val3); val2 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val8,val11); + val11 = std::max(val8,val11); val8 = tmp; tmp = std::min(val7,val12); val12 = std::max(val7,val12); val7 = tmp; + tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val10,val11); val11 = std::max(val10,val11); val10 = tmp; tmp = std::min(val1,val4); + val4 = std::max(val1,val4); val1 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val8); val8 = std::max(val7,val8); val7 = tmp; val11 = std::min(val11,val12); + tmp = std::min(val4,val9); val9 = std::max(val4,val9); val4 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; + tmp = std::min(val5,val6); val6 = std::max(val5,val6); val5 = tmp; val8 = std::min(val8,val9); + val10 = std::min(val10,val11); tmp = std::min(val1,val7); val7 = std::max(val1,val7); val1 = tmp; + tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; val3 = std::max(val1,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; val8 = std::min(val8,val10); + val5 = std::max(val0,val5); val5 = std::max(val2,val5); tmp = std::min(val6,val8); val8 = std::max(val6,val8); + val5 = std::max(val3,val5); val7 = std::min(val7,val8); val6 = std::max(val4,tmp); tmp = std::min(val4,tmp); + val5 = std::max(tmp,val5); val6 = std::min(val6,val7); + return std::max(val5,val6); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, + T val5, T val6, T val7, T val8, T val9, + T val10, T val11, T val12, T val13, T val14, + T val15, T val16, T val17, T val18, T val19, + T val20, T val21, T val22, T val23, T val24) { + T tmp = std::min(val0,val1); + val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); + val3 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); val2 = std::min(tmp,val3); + val3 = std::max(tmp,val3); tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; + tmp = std::min(val5,val7); val7 = std::max(val5,val7); val5 = std::min(tmp,val6); val6 = std::max(tmp,val6); + tmp = std::min(val9,val10); val10 = std::max(val9,val10); val9 = tmp; tmp = std::min(val8,val10); + val10 = std::max(val8,val10); val8 = std::min(tmp,val9); val9 = std::max(tmp,val9); + tmp = std::min(val12,val13); val13 = std::max(val12,val13); val12 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val15,val16); val16 = std::max(val15,val16); val15 = tmp; tmp = std::min(val14,val16); + val16 = std::max(val14,val16); val14 = std::min(tmp,val15); val15 = std::max(tmp,val15); + tmp = std::min(val18,val19); val19 = std::max(val18,val19); val18 = tmp; tmp = std::min(val17,val19); + val19 = std::max(val17,val19); val17 = std::min(tmp,val18); val18 = std::max(tmp,val18); + tmp = std::min(val21,val22); val22 = std::max(val21,val22); val21 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = std::min(tmp,val21); val21 = std::max(tmp,val21); + tmp = std::min(val23,val24); val24 = std::max(val23,val24); val23 = tmp; tmp = std::min(val2,val5); + val5 = std::max(val2,val5); val2 = tmp; tmp = std::min(val3,val6); val6 = std::max(val3,val6); val3 = tmp; + tmp = std::min(val0,val6); val6 = std::max(val0,val6); val0 = std::min(tmp,val3); val3 = std::max(tmp,val3); + tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; tmp = std::min(val1,val7); + val7 = std::max(val1,val7); val1 = std::min(tmp,val4); val4 = std::max(tmp,val4); tmp = std::min(val11,val14); + val14 = std::max(val11,val14); val11 = tmp; tmp = std::min(val8,val14); val14 = std::max(val8,val14); + val8 = std::min(tmp,val11); val11 = std::max(tmp,val11); tmp = std::min(val12,val15); + val15 = std::max(val12,val15); val12 = tmp; tmp = std::min(val9,val15); val15 = std::max(val9,val15); + val9 = std::min(tmp,val12); val12 = std::max(tmp,val12); tmp = std::min(val13,val16); + val16 = std::max(val13,val16); val13 = tmp; tmp = std::min(val10,val16); val16 = std::max(val10,val16); + val10 = std::min(tmp,val13); val13 = std::max(tmp,val13); tmp = std::min(val20,val23); + val23 = std::max(val20,val23); val20 = tmp; tmp = std::min(val17,val23); val23 = std::max(val17,val23); + val17 = std::min(tmp,val20); val20 = std::max(tmp,val20); tmp = std::min(val21,val24); + val24 = std::max(val21,val24); val21 = tmp; tmp = std::min(val18,val24); val24 = std::max(val18,val24); + val18 = std::min(tmp,val21); val21 = std::max(tmp,val21); tmp = std::min(val19,val22); + val22 = std::max(val19,val22); val19 = tmp; val17 = std::max(val8,val17); tmp = std::min(val9,val18); + val18 = std::max(val9,val18); val9 = tmp; tmp = std::min(val0,val18); val18 = std::max(val0,val18); + val9 = std::max(tmp,val9); tmp = std::min(val10,val19); val19 = std::max(val10,val19); val10 = tmp; + tmp = std::min(val1,val19); val19 = std::max(val1,val19); val1 = std::min(tmp,val10); + val10 = std::max(tmp,val10); tmp = std::min(val11,val20); val20 = std::max(val11,val20); val11 = tmp; + tmp = std::min(val2,val20); val20 = std::max(val2,val20); val11 = std::max(tmp,val11); + tmp = std::min(val12,val21); val21 = std::max(val12,val21); val12 = tmp; tmp = std::min(val3,val21); + val21 = std::max(val3,val21); val3 = std::min(tmp,val12); val12 = std::max(tmp,val12); + tmp = std::min(val13,val22); val22 = std::max(val13,val22); val4 = std::min(val4,val22); + val13 = std::max(val4,tmp); tmp = std::min(val4,tmp); val4 = tmp; tmp = std::min(val14,val23); + val23 = std::max(val14,val23); val14 = tmp; tmp = std::min(val5,val23); val23 = std::max(val5,val23); + val5 = std::min(tmp,val14); val14 = std::max(tmp,val14); tmp = std::min(val15,val24); + val24 = std::max(val15,val24); val15 = tmp; val6 = std::min(val6,val24); tmp = std::min(val6,val15); + val15 = std::max(val6,val15); val6 = tmp; tmp = std::min(val7,val16); val7 = std::min(tmp,val19); + tmp = std::min(val13,val21); val15 = std::min(val15,val23); tmp = std::min(val7,tmp); + val7 = std::min(tmp,val15); val9 = std::max(val1,val9); val11 = std::max(val3,val11); + val17 = std::max(val5,val17); val17 = std::max(val11,val17); val17 = std::max(val9,val17); + tmp = std::min(val4,val10); val10 = std::max(val4,val10); val4 = tmp; tmp = std::min(val6,val12); + val12 = std::max(val6,val12); val6 = tmp; tmp = std::min(val7,val14); val14 = std::max(val7,val14); + val7 = tmp; tmp = std::min(val4,val6); val6 = std::max(val4,val6); val7 = std::max(tmp,val7); + tmp = std::min(val12,val14); val14 = std::max(val12,val14); val12 = tmp; val10 = std::min(val10,val14); + tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val10,val12); + val12 = std::max(val10,val12); val10 = std::max(val6,tmp); tmp = std::min(val6,tmp); + val17 = std::max(tmp,val17); tmp = std::min(val12,val17); val17 = std::max(val12,val17); val12 = tmp; + val7 = std::min(val7,val17); tmp = std::min(val7,val10); val10 = std::max(val7,val10); val7 = tmp; + tmp = std::min(val12,val18); val18 = std::max(val12,val18); val12 = std::max(val7,tmp); + val10 = std::min(val10,val18); tmp = std::min(val12,val20); val20 = std::max(val12,val20); val12 = tmp; + tmp = std::min(val10,val20); + return std::max(tmp,val12); + } + + template + inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, + T val7, T val8, T val9, T val10, T val11, T val12, T val13, + T val14, T val15, T val16, T val17, T val18, T val19, T val20, + T val21, T val22, T val23, T val24, T val25, T val26, T val27, + T val28, T val29, T val30, T val31, T val32, T val33, T val34, + T val35, T val36, T val37, T val38, T val39, T val40, T val41, + T val42, T val43, T val44, T val45, T val46, T val47, T val48) { + T tmp = std::min(val0,val32); + val32 = std::max(val0,val32); val0 = tmp; tmp = std::min(val1,val33); val33 = std::max(val1,val33); val1 = tmp; + tmp = std::min(val2,val34); val34 = std::max(val2,val34); val2 = tmp; tmp = std::min(val3,val35); + val35 = std::max(val3,val35); val3 = tmp; tmp = std::min(val4,val36); val36 = std::max(val4,val36); val4 = tmp; + tmp = std::min(val5,val37); val37 = std::max(val5,val37); val5 = tmp; tmp = std::min(val6,val38); + val38 = std::max(val6,val38); val6 = tmp; tmp = std::min(val7,val39); val39 = std::max(val7,val39); val7 = tmp; + tmp = std::min(val8,val40); val40 = std::max(val8,val40); val8 = tmp; tmp = std::min(val9,val41); + val41 = std::max(val9,val41); val9 = tmp; tmp = std::min(val10,val42); val42 = std::max(val10,val42); + val10 = tmp; tmp = std::min(val11,val43); val43 = std::max(val11,val43); val11 = tmp; + tmp = std::min(val12,val44); val44 = std::max(val12,val44); val12 = tmp; tmp = std::min(val13,val45); + val45 = std::max(val13,val45); val13 = tmp; tmp = std::min(val14,val46); val46 = std::max(val14,val46); + val14 = tmp; tmp = std::min(val15,val47); val47 = std::max(val15,val47); val15 = tmp; + tmp = std::min(val16,val48); val48 = std::max(val16,val48); val16 = tmp; tmp = std::min(val0,val16); + val16 = std::max(val0,val16); val0 = tmp; tmp = std::min(val1,val17); val17 = std::max(val1,val17); + val1 = tmp; tmp = std::min(val2,val18); val18 = std::max(val2,val18); val2 = tmp; tmp = std::min(val3,val19); + val19 = std::max(val3,val19); val3 = tmp; tmp = std::min(val4,val20); val20 = std::max(val4,val20); val4 = tmp; + tmp = std::min(val5,val21); val21 = std::max(val5,val21); val5 = tmp; tmp = std::min(val6,val22); + val22 = std::max(val6,val22); val6 = tmp; tmp = std::min(val7,val23); val23 = std::max(val7,val23); val7 = tmp; + tmp = std::min(val8,val24); val24 = std::max(val8,val24); val8 = tmp; tmp = std::min(val9,val25); + val25 = std::max(val9,val25); val9 = tmp; tmp = std::min(val10,val26); val26 = std::max(val10,val26); + val10 = tmp; tmp = std::min(val11,val27); val27 = std::max(val11,val27); val11 = tmp; + tmp = std::min(val12,val28); val28 = std::max(val12,val28); val12 = tmp; tmp = std::min(val13,val29); + val29 = std::max(val13,val29); val13 = tmp; tmp = std::min(val14,val30); val30 = std::max(val14,val30); + val14 = tmp; tmp = std::min(val15,val31); val31 = std::max(val15,val31); val15 = tmp; + tmp = std::min(val32,val48); val48 = std::max(val32,val48); val32 = tmp; tmp = std::min(val16,val32); + val32 = std::max(val16,val32); val16 = tmp; tmp = std::min(val17,val33); val33 = std::max(val17,val33); + val17 = tmp; tmp = std::min(val18,val34); val34 = std::max(val18,val34); val18 = tmp; + tmp = std::min(val19,val35); val35 = std::max(val19,val35); val19 = tmp; tmp = std::min(val20,val36); + val36 = std::max(val20,val36); val20 = tmp; tmp = std::min(val21,val37); val37 = std::max(val21,val37); + val21 = tmp; tmp = std::min(val22,val38); val38 = std::max(val22,val38); val22 = tmp; + tmp = std::min(val23,val39); val39 = std::max(val23,val39); val23 = tmp; tmp = std::min(val24,val40); + val40 = std::max(val24,val40); val24 = tmp; tmp = std::min(val25,val41); val41 = std::max(val25,val41); + val25 = tmp; tmp = std::min(val26,val42); val42 = std::max(val26,val42); val26 = tmp; + tmp = std::min(val27,val43); val43 = std::max(val27,val43); val27 = tmp; tmp = std::min(val28,val44); + val44 = std::max(val28,val44); val28 = tmp; tmp = std::min(val29,val45); val45 = std::max(val29,val45); + val29 = tmp; tmp = std::min(val30,val46); val46 = std::max(val30,val46); val30 = tmp; + tmp = std::min(val31,val47); val47 = std::max(val31,val47); val31 = tmp; tmp = std::min(val0,val8); + val8 = std::max(val0,val8); val0 = tmp; tmp = std::min(val1,val9); val9 = std::max(val1,val9); val1 = tmp; + tmp = std::min(val2,val10); val10 = std::max(val2,val10); val2 = tmp; tmp = std::min(val3,val11); + val11 = std::max(val3,val11); val3 = tmp; tmp = std::min(val4,val12); val12 = std::max(val4,val12); val4 = tmp; + tmp = std::min(val5,val13); val13 = std::max(val5,val13); val5 = tmp; tmp = std::min(val6,val14); + val14 = std::max(val6,val14); val6 = tmp; tmp = std::min(val7,val15); val15 = std::max(val7,val15); val7 = tmp; + tmp = std::min(val16,val24); val24 = std::max(val16,val24); val16 = tmp; tmp = std::min(val17,val25); + val25 = std::max(val17,val25); val17 = tmp; tmp = std::min(val18,val26); val26 = std::max(val18,val26); + val18 = tmp; tmp = std::min(val19,val27); val27 = std::max(val19,val27); val19 = tmp; + tmp = std::min(val20,val28); val28 = std::max(val20,val28); val20 = tmp; tmp = std::min(val21,val29); + val29 = std::max(val21,val29); val21 = tmp; tmp = std::min(val22,val30); val30 = std::max(val22,val30); + val22 = tmp; tmp = std::min(val23,val31); val31 = std::max(val23,val31); val23 = tmp; + tmp = std::min(val32,val40); val40 = std::max(val32,val40); val32 = tmp; tmp = std::min(val33,val41); + val41 = std::max(val33,val41); val33 = tmp; tmp = std::min(val34,val42); val42 = std::max(val34,val42); + val34 = tmp; tmp = std::min(val35,val43); val43 = std::max(val35,val43); val35 = tmp; + tmp = std::min(val36,val44); val44 = std::max(val36,val44); val36 = tmp; tmp = std::min(val37,val45); + val45 = std::max(val37,val45); val37 = tmp; tmp = std::min(val38,val46); val46 = std::max(val38,val46); + val38 = tmp; tmp = std::min(val39,val47); val47 = std::max(val39,val47); val39 = tmp; + tmp = std::min(val8,val32); val32 = std::max(val8,val32); val8 = tmp; tmp = std::min(val9,val33); + val33 = std::max(val9,val33); val9 = tmp; tmp = std::min(val10,val34); val34 = std::max(val10,val34); + val10 = tmp; tmp = std::min(val11,val35); val35 = std::max(val11,val35); val11 = tmp; + tmp = std::min(val12,val36); val36 = std::max(val12,val36); val12 = tmp; tmp = std::min(val13,val37); + val37 = std::max(val13,val37); val13 = tmp; tmp = std::min(val14,val38); val38 = std::max(val14,val38); + val14 = tmp; tmp = std::min(val15,val39); val39 = std::max(val15,val39); val15 = tmp; + tmp = std::min(val24,val48); val48 = std::max(val24,val48); val24 = tmp; tmp = std::min(val8,val16); + val16 = std::max(val8,val16); val8 = tmp; tmp = std::min(val9,val17); val17 = std::max(val9,val17); + val9 = tmp; tmp = std::min(val10,val18); val18 = std::max(val10,val18); val10 = tmp; + tmp = std::min(val11,val19); val19 = std::max(val11,val19); val11 = tmp; tmp = std::min(val12,val20); + val20 = std::max(val12,val20); val12 = tmp; tmp = std::min(val13,val21); val21 = std::max(val13,val21); + val13 = tmp; tmp = std::min(val14,val22); val22 = std::max(val14,val22); val14 = tmp; + tmp = std::min(val15,val23); val23 = std::max(val15,val23); val15 = tmp; tmp = std::min(val24,val32); + val32 = std::max(val24,val32); val24 = tmp; tmp = std::min(val25,val33); val33 = std::max(val25,val33); + val25 = tmp; tmp = std::min(val26,val34); val34 = std::max(val26,val34); val26 = tmp; + tmp = std::min(val27,val35); val35 = std::max(val27,val35); val27 = tmp; tmp = std::min(val28,val36); + val36 = std::max(val28,val36); val28 = tmp; tmp = std::min(val29,val37); val37 = std::max(val29,val37); + val29 = tmp; tmp = std::min(val30,val38); val38 = std::max(val30,val38); val30 = tmp; + tmp = std::min(val31,val39); val39 = std::max(val31,val39); val31 = tmp; tmp = std::min(val40,val48); + val48 = std::max(val40,val48); val40 = tmp; tmp = std::min(val0,val4); val4 = std::max(val0,val4); + val0 = tmp; tmp = std::min(val1,val5); val5 = std::max(val1,val5); val1 = tmp; tmp = std::min(val2,val6); + val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; + tmp = std::min(val8,val12); val12 = std::max(val8,val12); val8 = tmp; tmp = std::min(val9,val13); + val13 = std::max(val9,val13); val9 = tmp; tmp = std::min(val10,val14); val14 = std::max(val10,val14); + val10 = tmp; tmp = std::min(val11,val15); val15 = std::max(val11,val15); val11 = tmp; + tmp = std::min(val16,val20); val20 = std::max(val16,val20); val16 = tmp; tmp = std::min(val17,val21); + val21 = std::max(val17,val21); val17 = tmp; tmp = std::min(val18,val22); val22 = std::max(val18,val22); + val18 = tmp; tmp = std::min(val19,val23); val23 = std::max(val19,val23); val19 = tmp; + tmp = std::min(val24,val28); val28 = std::max(val24,val28); val24 = tmp; tmp = std::min(val25,val29); + val29 = std::max(val25,val29); val25 = tmp; tmp = std::min(val26,val30); val30 = std::max(val26,val30); + val26 = tmp; tmp = std::min(val27,val31); val31 = std::max(val27,val31); val27 = tmp; + tmp = std::min(val32,val36); val36 = std::max(val32,val36); val32 = tmp; tmp = std::min(val33,val37); + val37 = std::max(val33,val37); val33 = tmp; tmp = std::min(val34,val38); val38 = std::max(val34,val38); + val34 = tmp; tmp = std::min(val35,val39); val39 = std::max(val35,val39); val35 = tmp; + tmp = std::min(val40,val44); val44 = std::max(val40,val44); val40 = tmp; tmp = std::min(val41,val45); + val45 = std::max(val41,val45); val41 = tmp; tmp = std::min(val42,val46); val46 = std::max(val42,val46); + val42 = tmp; tmp = std::min(val43,val47); val47 = std::max(val43,val47); val43 = tmp; + tmp = std::min(val4,val32); val32 = std::max(val4,val32); val4 = tmp; tmp = std::min(val5,val33); + val33 = std::max(val5,val33); val5 = tmp; tmp = std::min(val6,val34); val34 = std::max(val6,val34); + val6 = tmp; tmp = std::min(val7,val35); val35 = std::max(val7,val35); val7 = tmp; + tmp = std::min(val12,val40); val40 = std::max(val12,val40); val12 = tmp; tmp = std::min(val13,val41); + val41 = std::max(val13,val41); val13 = tmp; tmp = std::min(val14,val42); val42 = std::max(val14,val42); + val14 = tmp; tmp = std::min(val15,val43); val43 = std::max(val15,val43); val15 = tmp; + tmp = std::min(val20,val48); val48 = std::max(val20,val48); val20 = tmp; tmp = std::min(val4,val16); + val16 = std::max(val4,val16); val4 = tmp; tmp = std::min(val5,val17); val17 = std::max(val5,val17); + val5 = tmp; tmp = std::min(val6,val18); val18 = std::max(val6,val18); val6 = tmp; + tmp = std::min(val7,val19); val19 = std::max(val7,val19); val7 = tmp; tmp = std::min(val12,val24); + val24 = std::max(val12,val24); val12 = tmp; tmp = std::min(val13,val25); val25 = std::max(val13,val25); + val13 = tmp; tmp = std::min(val14,val26); val26 = std::max(val14,val26); val14 = tmp; + tmp = std::min(val15,val27); val27 = std::max(val15,val27); val15 = tmp; tmp = std::min(val20,val32); + val32 = std::max(val20,val32); val20 = tmp; tmp = std::min(val21,val33); val33 = std::max(val21,val33); + val21 = tmp; tmp = std::min(val22,val34); val34 = std::max(val22,val34); val22 = tmp; + tmp = std::min(val23,val35); val35 = std::max(val23,val35); val23 = tmp; tmp = std::min(val28,val40); + val40 = std::max(val28,val40); val28 = tmp; tmp = std::min(val29,val41); val41 = std::max(val29,val41); + val29 = tmp; tmp = std::min(val30,val42); val42 = std::max(val30,val42); val30 = tmp; + tmp = std::min(val31,val43); val43 = std::max(val31,val43); val31 = tmp; tmp = std::min(val36,val48); + val48 = std::max(val36,val48); val36 = tmp; tmp = std::min(val4,val8); val8 = std::max(val4,val8); + val4 = tmp; tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val6,val10); + val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val7,val11); val11 = std::max(val7,val11); val7 = tmp; + tmp = std::min(val12,val16); val16 = std::max(val12,val16); val12 = tmp; tmp = std::min(val13,val17); + val17 = std::max(val13,val17); val13 = tmp; tmp = std::min(val14,val18); val18 = std::max(val14,val18); + val14 = tmp; tmp = std::min(val15,val19); val19 = std::max(val15,val19); val15 = tmp; + tmp = std::min(val20,val24); val24 = std::max(val20,val24); val20 = tmp; tmp = std::min(val21,val25); + val25 = std::max(val21,val25); val21 = tmp; tmp = std::min(val22,val26); val26 = std::max(val22,val26); + val22 = tmp; tmp = std::min(val23,val27); val27 = std::max(val23,val27); val23 = tmp; + tmp = std::min(val28,val32); val32 = std::max(val28,val32); val28 = tmp; tmp = std::min(val29,val33); + val33 = std::max(val29,val33); val29 = tmp; tmp = std::min(val30,val34); val34 = std::max(val30,val34); + val30 = tmp; tmp = std::min(val31,val35); val35 = std::max(val31,val35); val31 = tmp; + tmp = std::min(val36,val40); val40 = std::max(val36,val40); val36 = tmp; tmp = std::min(val37,val41); + val41 = std::max(val37,val41); val37 = tmp; tmp = std::min(val38,val42); val42 = std::max(val38,val42); + val38 = tmp; tmp = std::min(val39,val43); val43 = std::max(val39,val43); val39 = tmp; + tmp = std::min(val44,val48); val48 = std::max(val44,val48); val44 = tmp; tmp = std::min(val0,val2); + val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val1,val3); val3 = std::max(val1,val3); val1 = tmp; + tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val5,val7); + val7 = std::max(val5,val7); val5 = tmp; tmp = std::min(val8,val10); val10 = std::max(val8,val10); val8 = tmp; + tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; tmp = std::min(val12,val14); + val14 = std::max(val12,val14); val12 = tmp; tmp = std::min(val13,val15); val15 = std::max(val13,val15); + val13 = tmp; tmp = std::min(val16,val18); val18 = std::max(val16,val18); val16 = tmp; + tmp = std::min(val17,val19); val19 = std::max(val17,val19); val17 = tmp; tmp = std::min(val20,val22); + val22 = std::max(val20,val22); val20 = tmp; tmp = std::min(val21,val23); val23 = std::max(val21,val23); + val21 = tmp; tmp = std::min(val24,val26); val26 = std::max(val24,val26); val24 = tmp; + tmp = std::min(val25,val27); val27 = std::max(val25,val27); val25 = tmp; tmp = std::min(val28,val30); + val30 = std::max(val28,val30); val28 = tmp; tmp = std::min(val29,val31); val31 = std::max(val29,val31); + val29 = tmp; tmp = std::min(val32,val34); val34 = std::max(val32,val34); val32 = tmp; + tmp = std::min(val33,val35); val35 = std::max(val33,val35); val33 = tmp; tmp = std::min(val36,val38); + val38 = std::max(val36,val38); val36 = tmp; tmp = std::min(val37,val39); val39 = std::max(val37,val39); + val37 = tmp; tmp = std::min(val40,val42); val42 = std::max(val40,val42); val40 = tmp; + tmp = std::min(val41,val43); val43 = std::max(val41,val43); val41 = tmp; tmp = std::min(val44,val46); + val46 = std::max(val44,val46); val44 = tmp; tmp = std::min(val45,val47); val47 = std::max(val45,val47); + val45 = tmp; tmp = std::min(val2,val32); val32 = std::max(val2,val32); val2 = tmp; tmp = std::min(val3,val33); + val33 = std::max(val3,val33); val3 = tmp; tmp = std::min(val6,val36); val36 = std::max(val6,val36); val6 = tmp; + tmp = std::min(val7,val37); val37 = std::max(val7,val37); val7 = tmp; tmp = std::min(val10,val40); + val40 = std::max(val10,val40); val10 = tmp; tmp = std::min(val11,val41); val41 = std::max(val11,val41); + val11 = tmp; tmp = std::min(val14,val44); val44 = std::max(val14,val44); val14 = tmp; + tmp = std::min(val15,val45); val45 = std::max(val15,val45); val15 = tmp; tmp = std::min(val18,val48); + val48 = std::max(val18,val48); val18 = tmp; tmp = std::min(val2,val16); val16 = std::max(val2,val16); + val2 = tmp; tmp = std::min(val3,val17); val17 = std::max(val3,val17); val3 = tmp; + tmp = std::min(val6,val20); val20 = std::max(val6,val20); val6 = tmp; tmp = std::min(val7,val21); + val21 = std::max(val7,val21); val7 = tmp; tmp = std::min(val10,val24); val24 = std::max(val10,val24); + val10 = tmp; tmp = std::min(val11,val25); val25 = std::max(val11,val25); val11 = tmp; + tmp = std::min(val14,val28); val28 = std::max(val14,val28); val14 = tmp; tmp = std::min(val15,val29); + val29 = std::max(val15,val29); val15 = tmp; tmp = std::min(val18,val32); val32 = std::max(val18,val32); + val18 = tmp; tmp = std::min(val19,val33); val33 = std::max(val19,val33); val19 = tmp; + tmp = std::min(val22,val36); val36 = std::max(val22,val36); val22 = tmp; tmp = std::min(val23,val37); + val37 = std::max(val23,val37); val23 = tmp; tmp = std::min(val26,val40); val40 = std::max(val26,val40); + val26 = tmp; tmp = std::min(val27,val41); val41 = std::max(val27,val41); val27 = tmp; + tmp = std::min(val30,val44); val44 = std::max(val30,val44); val30 = tmp; tmp = std::min(val31,val45); + val45 = std::max(val31,val45); val31 = tmp; tmp = std::min(val34,val48); val48 = std::max(val34,val48); + val34 = tmp; tmp = std::min(val2,val8); val8 = std::max(val2,val8); val2 = tmp; tmp = std::min(val3,val9); + val9 = std::max(val3,val9); val3 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; + tmp = std::min(val7,val13); val13 = std::max(val7,val13); val7 = tmp; tmp = std::min(val10,val16); + val16 = std::max(val10,val16); val10 = tmp; tmp = std::min(val11,val17); val17 = std::max(val11,val17); + val11 = tmp; tmp = std::min(val14,val20); val20 = std::max(val14,val20); val14 = tmp; + tmp = std::min(val15,val21); val21 = std::max(val15,val21); val15 = tmp; tmp = std::min(val18,val24); + val24 = std::max(val18,val24); val18 = tmp; tmp = std::min(val19,val25); val25 = std::max(val19,val25); + val19 = tmp; tmp = std::min(val22,val28); val28 = std::max(val22,val28); val22 = tmp; + tmp = std::min(val23,val29); val29 = std::max(val23,val29); val23 = tmp; tmp = std::min(val26,val32); + val32 = std::max(val26,val32); val26 = tmp; tmp = std::min(val27,val33); val33 = std::max(val27,val33); + val27 = tmp; tmp = std::min(val30,val36); val36 = std::max(val30,val36); val30 = tmp; + tmp = std::min(val31,val37); val37 = std::max(val31,val37); val31 = tmp; tmp = std::min(val34,val40); + val40 = std::max(val34,val40); val34 = tmp; tmp = std::min(val35,val41); val41 = std::max(val35,val41); + val35 = tmp; tmp = std::min(val38,val44); val44 = std::max(val38,val44); val38 = tmp; + tmp = std::min(val39,val45); val45 = std::max(val39,val45); val39 = tmp; tmp = std::min(val42,val48); + val48 = std::max(val42,val48); val42 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); + val2 = tmp; tmp = std::min(val3,val5); val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val6,val8); + val8 = std::max(val6,val8); val6 = tmp; tmp = std::min(val7,val9); val9 = std::max(val7,val9); val7 = tmp; + tmp = std::min(val10,val12); val12 = std::max(val10,val12); val10 = tmp; tmp = std::min(val11,val13); + val13 = std::max(val11,val13); val11 = tmp; tmp = std::min(val14,val16); val16 = std::max(val14,val16); + val14 = tmp; tmp = std::min(val15,val17); val17 = std::max(val15,val17); val15 = tmp; + tmp = std::min(val18,val20); val20 = std::max(val18,val20); val18 = tmp; tmp = std::min(val19,val21); + val21 = std::max(val19,val21); val19 = tmp; tmp = std::min(val22,val24); val24 = std::max(val22,val24); + val22 = tmp; tmp = std::min(val23,val25); val25 = std::max(val23,val25); val23 = tmp; + tmp = std::min(val26,val28); val28 = std::max(val26,val28); val26 = tmp; tmp = std::min(val27,val29); + val29 = std::max(val27,val29); val27 = tmp; tmp = std::min(val30,val32); val32 = std::max(val30,val32); + val30 = tmp; tmp = std::min(val31,val33); val33 = std::max(val31,val33); val31 = tmp; + tmp = std::min(val34,val36); val36 = std::max(val34,val36); val34 = tmp; tmp = std::min(val35,val37); + val37 = std::max(val35,val37); val35 = tmp; tmp = std::min(val38,val40); val40 = std::max(val38,val40); + val38 = tmp; tmp = std::min(val39,val41); val41 = std::max(val39,val41); val39 = tmp; + tmp = std::min(val42,val44); val44 = std::max(val42,val44); val42 = tmp; tmp = std::min(val43,val45); + val45 = std::max(val43,val45); val43 = tmp; tmp = std::min(val46,val48); val48 = std::max(val46,val48); + val46 = tmp; val1 = std::max(val0,val1); val3 = std::max(val2,val3); val5 = std::max(val4,val5); + val7 = std::max(val6,val7); val9 = std::max(val8,val9); val11 = std::max(val10,val11); + val13 = std::max(val12,val13); val15 = std::max(val14,val15); val17 = std::max(val16,val17); + val19 = std::max(val18,val19); val21 = std::max(val20,val21); val23 = std::max(val22,val23); + val24 = std::min(val24,val25); val26 = std::min(val26,val27); val28 = std::min(val28,val29); + val30 = std::min(val30,val31); val32 = std::min(val32,val33); val34 = std::min(val34,val35); + val36 = std::min(val36,val37); val38 = std::min(val38,val39); val40 = std::min(val40,val41); + val42 = std::min(val42,val43); val44 = std::min(val44,val45); val46 = std::min(val46,val47); + val32 = std::max(val1,val32); val34 = std::max(val3,val34); val36 = std::max(val5,val36); + val38 = std::max(val7,val38); val9 = std::min(val9,val40); val11 = std::min(val11,val42); + val13 = std::min(val13,val44); val15 = std::min(val15,val46); val17 = std::min(val17,val48); + val24 = std::max(val9,val24); val26 = std::max(val11,val26); val28 = std::max(val13,val28); + val30 = std::max(val15,val30); val17 = std::min(val17,val32); val19 = std::min(val19,val34); + val21 = std::min(val21,val36); val23 = std::min(val23,val38); val24 = std::max(val17,val24); + val26 = std::max(val19,val26); val21 = std::min(val21,val28); val23 = std::min(val23,val30); + val24 = std::max(val21,val24); val23 = std::min(val23,val26); + return std::max(val23,val24); + } + + //! Return sqrt(x^2 + y^2). + template + inline T hypot(const T x, const T y) { + return std::sqrt(x*x + y*y); + } + + template + inline T hypot(const T x, const T y, const T z) { + return std::sqrt(x*x + y*y + z*z); + } + + template + inline T _hypot(const T x, const T y) { // Slower but more precise version + T nx = cimg::abs(x), ny = cimg::abs(y), t; + if (nx0) { t/=nx; return nx*std::sqrt(1 + t*t); } + return 0; + } + + //! Return the factorial of n + inline double factorial(const int n) { + if (n<0) return cimg::type::nan(); + if (n<2) return 1; + double res = 2; + for (int i = 3; i<=n; ++i) res*=i; + return res; + } + + //! Return the number of permutations of k objects in a set of n objects. + inline double permutations(const int k, const int n, const bool with_order) { + if (n<0 || k<0) return cimg::type::nan(); + if (k>n) return 0; + double res = 1; + for (int i = n; i>=n - k + 1; --i) res*=i; + return with_order?res:res/cimg::factorial(k); + } + + inline double _fibonacci(int exp) { + double + base = (1 + std::sqrt(5.))/2, + result = 1/std::sqrt(5.); + while (exp) { + if (exp&1) result*=base; + exp>>=1; + base*=base; + } + return result; + } + + //! Calculate fibonacci number. + // (Precise up to n = 78, less precise for n>78). + inline double fibonacci(const int n) { + if (n<0) return cimg::type::nan(); + if (n<3) return 1; + if (n<11) { + cimg_uint64 fn1 = 1, fn2 = 1, fn = 0; + for (int i = 3; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + if (n<75) // precise up to n = 74, faster than the integer calculation above for n>10 + return (double)((cimg_uint64)(_fibonacci(n) + 0.5)); + + if (n<94) { // precise up to n = 78, less precise for n>78 up to n = 93, overflows for n>93 + cimg_uint64 + fn1 = (cimg_uint64)1304969544928657ULL, + fn2 = (cimg_uint64)806515533049393ULL, + fn = 0; + for (int i = 75; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } + return (double)fn; + } + return _fibonacci(n); // Not precise, but better than the wrong overflowing calculation + } + + //! Calculate greatest common divisor. + inline long gcd(long a, long b) { + while (a) { const long c = a; a = b%a; b = c; } + return b; + } + + //! Convert Ascii character to lower case. + inline char lowercase(const char x) { + return (char)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + inline double lowercase(const double x) { + return (double)((x<'A'||x>'Z')?x:x - 'A' + 'a'); + } + + //! Convert C-string to lower case. + inline void lowercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = lowercase(*ptr); + } + + //! Convert Ascii character to upper case. + inline char uppercase(const char x) { + return (char)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + inline double uppercase(const double x) { + return (double)((x<'a'||x>'z')?x:x - 'a' + 'A'); + } + + //! Convert C-string to upper case. + inline void uppercase(char *const str) { + if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uppercase(*ptr); + } + + //! Return \c true if input character is blank (space, tab, or non-printable character). + inline bool is_blank(const char c) { + return c>=0 && c<=' '; + } + + //! Read value in a C-string. + /** + \param str C-string containing the float value to read. + \return Read value. + \note Same as std::atof() extended to manage the retrieval of fractions from C-strings, + as in "1/2". + **/ + inline double atof(const char *const str) { + double x = 0, y = 1; + return str && cimg_sscanf(str,"%lf/%lf",&x,&y)>0?x/y:0; + } + + //! Compare the first \p l characters of two C-strings, ignoring the case. + /** + \param str1 C-string. + \param str2 C-string. + \param l Number of characters to compare. + \return \c 0 if the two strings are equal, something else otherwise. + \note This function has to be defined since it is not provided by all C++-compilers (not ANSI). + **/ + inline int strncasecmp(const char *const str1, const char *const str2, const int l) { + if (!l) return 0; + if (!str1) return str2?-1:0; + const char *nstr1 = str1, *nstr2 = str2; + int k, diff = 0; for (k = 0; kp && str[q]==delimiter; ) { --q; if (!is_iterative) break; } + } + const int n = q - p + 1; + if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; } + return false; + } + + //! Remove white spaces on the start and/or end of a C-string. + inline bool strpare(char *const str, const bool is_symmetric, const bool is_iterative) { + if (!str) return false; + const int l = (int)std::strlen(str); + int p, q; + if (is_symmetric) for (p = 0, q = l - 1; 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; } + return false; + } + + //! Replace reserved characters (for Windows filename) by another character. + /** + \param[in,out] str C-string to work with (modified at output). + \param[in] c Replacement character. + **/ + inline void strwindows_reserved(char *const str, const char c='_') { + for (char *s = str; *s; ++s) { + const char i = *s; + if (i=='<' || i=='>' || i==':' || i=='\"' || i=='/' || i=='\\' || i=='|' || i=='?' || i=='*') *s = c; + } + } + + //! Replace escape sequences in C-strings by their binary Ascii values. + /** + \param[in,out] str C-string to work with (modified at output). + **/ + inline void strunescape(char *const str) { +#define cimg_strunescape(ci,co) case ci : *nd = co; ++ns; break; + unsigned int val = 0; + for (char *ns = str, *nd = str; *ns || (bool)(*nd=0); ++nd) if (*ns=='\\') switch (*(++ns)) { + cimg_strunescape('a','\a'); + cimg_strunescape('b','\b'); + cimg_strunescape('e',0x1B); + cimg_strunescape('f','\f'); + cimg_strunescape('n','\n'); + cimg_strunescape('r','\r'); + cimg_strunescape('t','\t'); + cimg_strunescape('v','\v'); + cimg_strunescape('\\','\\'); + cimg_strunescape('\'','\''); + cimg_strunescape('\"','\"'); + cimg_strunescape('\?','\?'); + case 0 : *nd = 0; break; + case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : + cimg_sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns; + *nd = (char)val; break; + case 'x' : + cimg_sscanf(++ns,"%x",&val); + while ((*ns>='0' && *ns<='9') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns; + *nd = (char)val; break; + default : *nd = *(ns++); + } else *nd = *(ns++); + } + + // Return a temporary string describing the size of a memory buffer. + inline const char *strbuffersize(const cimg_ulong size); + + // Return string that identifies the running OS. + inline const char *stros() { +#if defined(linux) || defined(__linux) || defined(__linux__) + static const char *const str = "Linux"; +#elif defined(sun) || defined(__sun) + static const char *const str = "Sun OS"; +#elif defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined (__DragonFly__) + static const char *const str = "BSD"; +#elif defined(sgi) || defined(__sgi) + static const char *const str = "Irix"; +#elif defined(__MACOSX__) || defined(__APPLE__) + static const char *const str = "Mac OS"; +#elif defined(unix) || defined(__unix) || defined(__unix__) + static const char *const str = "Generic Unix"; +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \ + defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + static const char *const str = "Windows"; +#else + const char + *const _str1 = std::getenv("OSTYPE"), + *const _str2 = _str1?_str1:std::getenv("OS"), + *const str = _str2?_str2:"Unknown OS"; +#endif + return str; + } + + //! Return the basename of a filename. + inline const char* basename(const char *const s, const char separator=cimg_file_separator) { + const char *p = 0, *np = s; + while (np>=s && (p=np)) np = std::strchr(np,separator) + 1; + return p; + } + + // Return a random filename. + inline const char* filenamerand() { + cimg::mutex(6); + static char randomid[9]; + for (unsigned int k = 0; k<8; ++k) { + const int v = (int)cimg::rand(65535)%3; + randomid[k] = (char)(v==0?('0' + ((int)cimg::rand(65535)%10)): + (v==1?('a' + ((int)cimg::rand(65535)%26)): + ('A' + ((int)cimg::rand(65535)%26)))); + } + cimg::mutex(6,0); + return randomid; + } + + // Convert filename as a Windows-style filename (short path name). + inline void winformat_string(char *const str) { + if (str && *str) { +#if cimg_OS==2 + char *const nstr = new char[MAX_PATH]; + if (GetShortPathNameA(str,nstr,MAX_PATH)) std::strcpy(str,nstr); + delete[] nstr; +#endif + } + } + + // Open a file (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. + /** + \param path Path of the filename to open. + \param mode C-string describing the opening mode. + \return Opened file. + \note Same as std::fopen() but throw a \c CImgIOException when + the specified file cannot be opened, instead of returning \c 0. + **/ + inline std::FILE *fopen(const char *const path, const char *const mode) { + if (!path) + throw CImgArgumentException("cimg::fopen(): Specified file path is (null)."); + if (!mode) + throw CImgArgumentException("cimg::fopen(): File '%s', specified mode is (null).", + path); + std::FILE *res = 0; + if (*path=='-' && (!path[1] || path[1]=='.')) { + res = (*mode=='r')?cimg::_stdin():cimg::_stdout(); +#if cimg_OS==2 + if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode +#ifdef __BORLANDC__ + if (setmode(_fileno(res),0x8000)==-1) res = 0; +#else + if (_setmode(_fileno(res),0x8000)==-1) res = 0; +#endif + } +#endif + } else res = cimg::std_fopen(path,mode); + if (!res) throw CImgIOException("cimg::fopen(): Failed to open file '%s' with mode '%s'.", + path,mode); + return res; + } + + //! Close a file. + /** + \param file File to close. + \return \c 0 if file has been closed properly, something else otherwise. + \note Same as std::fclose() but display a warning message if + the file has not been closed properly. + **/ + inline int fclose(std::FILE *file) { + if (!file) { warn("cimg::fclose(): Specified file is (null)."); return 0; } + if (file==cimg::_stdin(false) || file==cimg::_stdout(false)) return 0; + const int errn = std::fclose(file); + if (errn!=0) warn("cimg::fclose(): Error code %d returned during file closing.", + errn); + return errn; + } + + //! Version of 'fseek()' that supports >=64bits offsets everywhere (for Windows). + inline int fseek(FILE *stream, cimg_long offset, int origin) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return _fseeki64(stream,(__int64)offset,origin); +#else + return std::fseek(stream,offset,origin); +#endif + } + + //! Version of 'ftell()' that supports >=64bits offsets everywhere (for Windows). + inline cimg_long ftell(FILE *stream) { +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) + return (cimg_long)_ftelli64(stream); +#else + return (cimg_long)std::ftell(stream); +#endif + } + + //! Check if a path is a directory. + /** + \param path Specified path to test. + **/ + inline bool is_directory(const char *const path) { + if (!path || !*path) return false; +#if cimg_OS==1 + struct stat st_buf; + return (!stat(path,&st_buf) && S_ISDIR(st_buf.st_mode)); +#elif cimg_OS==2 + const unsigned int res = (unsigned int)GetFileAttributesA(path); + return res==INVALID_FILE_ATTRIBUTES?false:(res&16); +#else + return false; +#endif + } + + //! Check if a path is a file. + /** + \param path Specified path to test. + **/ + inline bool is_file(const char *const path) { + if (!path || !*path) return false; + std::FILE *const file = cimg::std_fopen(path,"rb"); + if (!file) return false; + cimg::fclose(file); + return !is_directory(path); + } + + //! Get file size. + /** + \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 = cimg::std_fopen(filename,"rb"); + if (!file) return (cimg_int64)-1; + std::fseek(file,0,SEEK_END); + const cimg_int64 siz = (cimg_int64)std::ftell(file); + cimg::fclose(file); + return siz; + } + + //! Get last write time of a given file or directory (multiple-attributes version). + /** + \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). + \param nb_attr Number of attributes to read/write. + \return Latest read attribute. + **/ + template + inline int fdate(const char *const path, T *attr, const unsigned int nb_attr) { +#define _cimg_fdate_err() for (unsigned int i = 0; i + inline int date(T *attr, const unsigned int nb_attr) { + int res = -1; + cimg::mutex(6); +#if cimg_OS==2 + 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); + attr[i] = (T)res; + } +#endif + cimg::mutex(6,0); + return res; + } + + //! 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. + **/ + 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 dcraw binary. + inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c wget binary. + inline const char *wget_path(const char *const user_path=0, const bool reinit_path=false); + + // Get/set path to the \c curl binary. + inline const char *curl_path(const char *const user_path=0, const bool reinit_path=false); + + //! Split filename into two C-strings \c body and \c extension. + /** + filename and body must not overlap! + **/ + inline const char *split_filename(const char *const filename, char *const body=0) { + if (!filename) { if (body) *body = 0; return 0; } + const char *p = 0; for (const char *np = filename; np>=filename && (p=np); np = std::strchr(np,'.') + 1) {} + if (p==filename) { + if (body) std::strcpy(body,filename); + return filename + std::strlen(filename); + } + const unsigned int l = (unsigned int)(p - filename - 1); + if (body) { if (l) std::memcpy(body,filename,l); body[l] = 0; } + return p; + } + + //! Generate a numbered version of a filename. + inline char* number_filename(const char *const filename, const int number, + const unsigned int digits, char *const str) { + if (!filename) { if (str) *str = 0; return 0; } + char *const format = new char[1024], *const body = new char[1024]; + const char *const ext = cimg::split_filename(filename,body); + if (*ext) cimg_snprintf(format,1024,"%%s_%%.%ud.%%s",digits); + else cimg_snprintf(format,1024,"%%s_%%.%ud",digits); + cimg_sprintf(str,format,body,number,ext); + delete[] format; delete[] body; + return str; + } + + //! Read data from file. + /** + \param[out] ptr Pointer to memory buffer that will contain the binary data read from file. + \param nmemb Number of elements to read. + \param stream File to read data from. + \return Number of read elements. + \note Same as std::fread() but may display warning message if all elements could not be read. + **/ + template + inline size_t fread(T *const ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fread(): Invalid reading request of %u %s%s from file %p to buffer %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",stream,ptr); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_read = nmemb, al_read = 0, l_to_read = 0, l_al_read = 0; + do { + l_to_read = (to_read*sizeof(T))0); + if (to_read>0) + warn("cimg::fread(): Only %lu/%lu elements could be read from file.", + (unsigned long)al_read,(unsigned long)nmemb); + return al_read; + } + + //! Write data to file. + /** + \param ptr Pointer to memory buffer containing the binary data to write on file. + \param nmemb Number of elements to write. + \param[out] stream File to write data on. + \return Number of written elements. + \note Similar to std::fwrite but may display warning messages if all elements could not be written. + **/ + template + inline size_t fwrite(const T *ptr, const size_t nmemb, std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fwrite(): Invalid writing request of %u %s%s from buffer %p to file %p.", + nmemb,cimg::type::string(),nmemb>1?"s":"",ptr,stream); + if (!nmemb) return 0; + const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + size_t to_write = nmemb, al_write = 0, l_to_write = 0, l_al_write = 0; + do { + l_to_write = (to_write*sizeof(T))0); + if (to_write>0) + warn("cimg::fwrite(): Only %lu/%lu elements could be written in file.", + (unsigned long)al_write,(unsigned long)nmemb); + return al_write; + } + + //! Create an empty file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + **/ + inline void fempty(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::fempty(): Specified filename is (null)."); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!file) cimg::fclose(nfile); + } + + // Try to guess format from an image file. + inline const char *ftype(std::FILE *const file, const char *const filename); + + // Load file from network as a local temporary file. + inline char *load_network(const char *const url, char *const filename_local, + const unsigned int timeout=0, const bool try_fallback=false, + const char *const referer=0); + + //! Return options specified on the command line. + inline const char* option(const char *const name, const int argc, const char *const *const argv, + const char *const defaut, const char *const usage, const bool reset_static) { + static bool first = true, visu = false; + if (reset_static) { first = true; return 0; } + const char *res = 0; + if (first) { + first = false; + visu = cimg::option("-h",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("-help",argc,argv,(char*)0,(char*)0,false)!=0; + visu |= cimg::option("--help",argc,argv,(char*)0,(char*)0,false)!=0; + } + if (!name && visu) { + if (usage) { + std::fprintf(cimg::output(),"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal); + std::fprintf(cimg::output(),": %s",usage); + std::fprintf(cimg::output()," (%s, %s)\n\n",cimg_date,cimg_time); + } + if (defaut) std::fprintf(cimg::output(),"%s\n",defaut); + } + if (name) { + if (argc>0) { + int k = 0; + while (k Operating System: %s%-13s%s %s('cimg_OS'=%d)%s\n", + cimg::t_bold, + cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"), + cimg::t_normal,cimg::t_green, + cimg_OS, + cimg::t_normal); + + std::fprintf(cimg::output()," > CPU endianness: %s%s Endian%s\n", + cimg::t_bold, + cimg::endianness()?"Big":"Little", + cimg::t_normal); + + std::fprintf(cimg::output()," > Verbosity mode: %s%-13s%s %s('cimg_verbosity'=%d)%s\n", + cimg::t_bold, + cimg_verbosity==0?"Quiet": + cimg_verbosity==1?"Console": + cimg_verbosity==2?"Dialog": + cimg_verbosity==3?"Console+Warnings":"Dialog+Warnings", + cimg::t_normal,cimg::t_green, + cimg_verbosity, + cimg::t_normal); + + std::fprintf(cimg::output()," > Stricts warnings: %s%-13s%s %s('cimg_strict_warnings' %s)%s\n", + cimg::t_bold, +#ifdef cimg_strict_warnings + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Support for C++11: %s%-13s%s %s('cimg_use_cpp11'=%d)%s\n", + cimg::t_bold, + cimg_use_cpp11?"Yes":"No", + cimg::t_normal,cimg::t_green, + (int)cimg_use_cpp11, + cimg::t_normal); + + std::fprintf(cimg::output()," > Using VT100 messages: %s%-13s%s %s('cimg_use_vt100' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_vt100 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Display type: %s%-13s%s %s('cimg_display'=%d)%s\n", + cimg::t_bold, + cimg_display==0?"No display":cimg_display==1?"X11":cimg_display==2?"Windows GDI":"Unknown", + cimg::t_normal,cimg::t_green, + (int)cimg_display, + cimg::t_normal); + +#if cimg_display==1 + std::fprintf(cimg::output()," > Using XShm for X11: %s%-13s%s %s('cimg_use_xshm' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xshm + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using XRand for X11: %s%-13s%s %s('cimg_use_xrandr' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xrandr + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); +#endif + std::fprintf(cimg::output()," > Using OpenMP: %s%-13s%s %s('cimg_use_openmp' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_openmp + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using PNG library: %s%-13s%s %s('cimg_use_png' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_png + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + std::fprintf(cimg::output()," > Using JPEG library: %s%-13s%s %s('cimg_use_jpeg' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_jpeg + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using TIFF library: %s%-13s%s %s('cimg_use_tiff' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_tiff + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using Magick++ library: %s%-13s%s %s('cimg_use_magick' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_magick + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using FFTW3 library: %s%-13s%s %s('cimg_use_fftw3' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_fftw3 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + std::fprintf(cimg::output()," > Using LAPACK library: %s%-13s%s %s('cimg_use_lapack' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_lapack + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + char *const tmp = new char[1024]; + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::imagemagick_path()); + std::fprintf(cimg::output()," > Path of ImageMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::graphicsmagick_path()); + std::fprintf(cimg::output()," > Path of GraphicsMagick: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::medcon_path()); + std::fprintf(cimg::output()," > Path of 'medcon': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::temporary_path()); + std::fprintf(cimg::output()," > Temporary path: %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + std::fprintf(cimg::output(),"\n"); + delete[] tmp; + } + + // Declare LAPACK function signatures if LAPACK support is enabled. +#ifdef cimg_use_lapack + template + inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) { + dgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) { + sgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + template + inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) { + dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) { + sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + template + inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN, + T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) { + dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN, + float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) { + sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + template + inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) { + int one = 1; + dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) { + int one = 1; + sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + template + inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) { + dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) { + ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + template + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, T* lapA, int &LDA, + T* lapB, int &LDB, T* WORK, int &LWORK, int &INFO){ + dgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + + inline void sgels(char & TRANS, int &M, int &N, int &NRHS, float* lapA, int &LDA, + float* lapB, int &LDB, float* WORK, int &LWORK, int &INFO){ + sgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); + } + +#endif + + // End of the 'cimg' namespace + } + + /*------------------------------------------------ + # + # + # Definition of mathematical operators and + # external functions. + # + # + -------------------------------------------------*/ + +#define _cimg_create_ext_operators(typ) \ + template \ + inline CImg::type> operator+(const typ val, const CImg& img) { \ + return img + val; \ + } \ + template \ + inline CImg::type> operator-(const typ val, const CImg& img) { \ + typedef typename cimg::superset::type Tt; \ + return CImg(img._width,img._height,img._depth,img._spectrum,val)-=img; \ + } \ + template \ + inline CImg::type> operator*(const typ val, const CImg& img) { \ + return img*val; \ + } \ + template \ + inline CImg::type> operator/(const typ val, const CImg& img) { \ + return val*img.get_invert(); \ + } \ + template \ + inline CImg::type> operator&(const typ val, const CImg& img) { \ + return img & val; \ + } \ + template \ + inline CImg::type> operator|(const typ val, const CImg& img) { \ + return img | val; \ + } \ + template \ + inline CImg::type> operator^(const typ val, const CImg& img) { \ + return img ^ val; \ + } \ + template \ + inline bool operator==(const typ val, const CImg& img) { \ + return img == val; \ + } \ + template \ + inline bool operator!=(const typ val, const CImg& img) { \ + return img != val; \ + } + + _cimg_create_ext_operators(bool) + _cimg_create_ext_operators(unsigned char) + _cimg_create_ext_operators(char) + _cimg_create_ext_operators(signed char) + _cimg_create_ext_operators(unsigned short) + _cimg_create_ext_operators(short) + _cimg_create_ext_operators(unsigned int) + _cimg_create_ext_operators(int) + _cimg_create_ext_operators(cimg_uint64) + _cimg_create_ext_operators(cimg_int64) + _cimg_create_ext_operators(float) + _cimg_create_ext_operators(double) + _cimg_create_ext_operators(long double) + + template + inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg& img) { + return img + expression; + } + + template + inline CImg<_cimg_Tfloat> operator-(const char *const expression, const CImg& img) { + return CImg<_cimg_Tfloat>(img,false).fill(expression,true)-=img; + } + + template + inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg& img) { + return img*expression; + } + + template + inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg& img) { + return expression*img.get_invert(); + } + + template + inline CImg operator&(const char *const expression, const CImg& img) { + return img & expression; + } + + template + inline CImg operator|(const char *const expression, const CImg& img) { + return img | expression; + } + + template + inline CImg operator^(const char *const expression, const CImg& img) { + return img ^ expression; + } + + template + inline bool operator==(const char *const expression, const CImg& img) { + return img==expression; + } + + template + inline bool operator!=(const char *const expression, const CImg& img) { + return img!=expression; + } + + template + inline CImg transpose(const CImg& instance) { + return instance.get_transpose(); + } + + template + inline CImg<_cimg_Tfloat> invert(const CImg& instance) { + return instance.get_invert(); + } + + template + inline CImg<_cimg_Tfloat> pseudoinvert(const CImg& instance) { + return instance.get_pseudoinvert(); + } + +#define _cimg_create_ext_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) + + /*----------------------------------- + # + # Define the CImgDisplay structure + # + ----------------------------------*/ + //! Allow the creation of windows, display images on them and manage user events (keyboard, mouse and windows events). + /** + CImgDisplay methods rely on a low-level graphic library to perform: it can be either \b X-Window + (X11, for Unix-based systems) or \b GDI32 (for Windows-based systems). + If both libraries are missing, CImgDisplay will not be able to display images on screen, and will enter + a minimal mode where warning messages will be outputed each time the program is trying to call one of the + CImgDisplay method. + + The configuration variable \c cimg_display tells about the graphic library used. + It is set automatically by \CImg when one of these graphic libraries has been detected. + But, you can override its value if necessary. Valid choices are: + - 0: Disable display capabilities. + - 1: Use \b X-Window (X11) library. + - 2: Use \b GDI32 library. + + Remember to link your program against \b X11 or \b GDI32 libraries if you use CImgDisplay. + **/ + struct CImgDisplay { + cimg_ulong _timer, _fps_frames, _fps_timer; + unsigned int _width, _height, _normalization; + float _fps_fps, _min, _max; + bool _is_fullscreen; + char *_title; + unsigned int _window_width, _window_height, _button, *_keys, *_released_keys; + int _window_x, _window_y, _mouse_x, _mouse_y, _wheel; + bool _is_closed, _is_resized, _is_moved, _is_event, + _is_keyESC, _is_keyF1, _is_keyF2, _is_keyF3, _is_keyF4, _is_keyF5, _is_keyF6, _is_keyF7, + _is_keyF8, _is_keyF9, _is_keyF10, _is_keyF11, _is_keyF12, _is_keyPAUSE, _is_key1, _is_key2, + _is_key3, _is_key4, _is_key5, _is_key6, _is_key7, _is_key8, _is_key9, _is_key0, + _is_keyBACKSPACE, _is_keyINSERT, _is_keyHOME, _is_keyPAGEUP, _is_keyTAB, _is_keyQ, _is_keyW, _is_keyE, + _is_keyR, _is_keyT, _is_keyY, _is_keyU, _is_keyI, _is_keyO, _is_keyP, _is_keyDELETE, + _is_keyEND, _is_keyPAGEDOWN, _is_keyCAPSLOCK, _is_keyA, _is_keyS, _is_keyD, _is_keyF, _is_keyG, + _is_keyH, _is_keyJ, _is_keyK, _is_keyL, _is_keyENTER, _is_keySHIFTLEFT, _is_keyZ, _is_keyX, + _is_keyC, _is_keyV, _is_keyB, _is_keyN, _is_keyM, _is_keySHIFTRIGHT, _is_keyARROWUP, _is_keyCTRLLEFT, + _is_keyAPPLEFT, _is_keyALT, _is_keySPACE, _is_keyALTGR, _is_keyAPPRIGHT, _is_keyMENU, _is_keyCTRLRIGHT, + _is_keyARROWLEFT, _is_keyARROWDOWN, _is_keyARROWRIGHT, _is_keyPAD0, _is_keyPAD1, _is_keyPAD2, _is_keyPAD3, + _is_keyPAD4, _is_keyPAD5, _is_keyPAD6, _is_keyPAD7, _is_keyPAD8, _is_keyPAD9, _is_keyPADADD, _is_keyPADSUB, + _is_keyPADMUL, _is_keyPADDIV; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- + +#ifdef cimgdisplay_plugin +#include cimgdisplay_plugin +#endif +#ifdef cimgdisplay_plugin1 +#include cimgdisplay_plugin1 +#endif +#ifdef cimgdisplay_plugin2 +#include cimgdisplay_plugin2 +#endif +#ifdef cimgdisplay_plugin3 +#include cimgdisplay_plugin3 +#endif +#ifdef cimgdisplay_plugin4 +#include cimgdisplay_plugin4 +#endif +#ifdef cimgdisplay_plugin5 +#include cimgdisplay_plugin5 +#endif +#ifdef cimgdisplay_plugin6 +#include cimgdisplay_plugin6 +#endif +#ifdef cimgdisplay_plugin7 +#include cimgdisplay_plugin7 +#endif +#ifdef cimgdisplay_plugin8 +#include cimgdisplay_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + \note If the associated window is visible on the screen, it is closed by the call to the destructor. + **/ + ~CImgDisplay() { + assign(); + delete[] _keys; + delete[] _released_keys; + } + + //! Construct an empty display. + /** + \note Constructing an empty CImgDisplay instance does not make a window appearing on the screen, until + display of valid data is performed. + \par Example + \code + CImgDisplay disp; // Does actually nothing + ... + disp.display(img); // Construct new window and display image in it + \endcode + **/ + CImgDisplay(): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(); + } + + //! Construct a display with specified dimensions. + /** \param width Window width. + \param height Window height. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note A black background is initially displayed on the associated window. + **/ + CImgDisplay(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(width,height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image. + /** \param img Image used as a model to create the window. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note The pixels of the input image are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(img,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list. + /** \param list The images list to display. + \param title Window title. + \param normalization Normalization type + (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). + \param is_fullscreen Tells if fullscreen mode is enabled. + \param is_closed Tells if associated window is initially visible or not. + \note All images of the list, appended along the X-axis, are initially displayed on the associated window. + **/ + template + explicit CImgDisplay(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(list,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of an existing one. + /** + \param disp Display instance to copy. + \note The pixel buffer of the input window is initially displayed on the associated window. + **/ + CImgDisplay(const CImgDisplay& disp): + _width(0),_height(0),_normalization(0), + _min(0),_max(0), + _is_fullscreen(false), + _title(0), + _window_width(0),_window_height(0),_button(0), + _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), + _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), + _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { + assign(disp); + } + + //! Take a screenshot. + /** + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(CImg& img) { + return screenshot(0,0,cimg::type::max(),cimg::type::max(),img); + } + +#if cimg_display==0 + + static void _no_display_exception() { + throw CImgDisplayException("CImgDisplay(): No display available."); + } + + //! Destructor - Empty constructor \inplace. + /** + \note Replace the current instance by an empty display. + **/ + CImgDisplay& assign() { + return flush(); + } + + //! Construct a display with specified dimensions \inplace. + /** + **/ + CImgDisplay& assign(const unsigned int width, const unsigned int height, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + cimg::unused(width,height,title,normalization,is_fullscreen,is_closed); + _no_display_exception(); + return assign(); + } + + //! Construct a display from an image \inplace. + /** + **/ + template + CImgDisplay& assign(const CImg& img, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display from an image list \inplace. + /** + **/ + template + CImgDisplay& assign(const CImgList& list, + const char *const title=0, const unsigned int normalization=3, + const bool is_fullscreen=false, const bool is_closed=false) { + _no_display_exception(); + return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed); + } + + //! Construct a display as a copy of another one \inplace. + /** + **/ + CImgDisplay& assign(const CImgDisplay &disp) { + _no_display_exception(); + return assign(disp._width,disp._height); + } + +#endif + + //! Return a reference to an empty display. + /** + \note Can be useful for writing function prototypes where one of the argument (of type CImgDisplay&) + must have a default value. + \par Example + \code + void foo(CImgDisplay& disp=CImgDisplay::empty()); + \endcode + **/ + static CImgDisplay& empty() { + static CImgDisplay _empty; + return _empty.assign(); + } + + //! Return a reference to an empty display \const. + static const CImgDisplay& const_empty() { + static const CImgDisplay _empty; + return _empty; + } + +#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,256,-85,false), \ + CImgDisplay::_fitscreen(dx,dy,dz,256,-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 int + u = CImgDisplay::screen_width(), + v = CImgDisplay::screen_height(); + const float + mw = dmin<0?cimg::round(u*-dmin/100.f):(float)dmin, + mh = dmin<0?cimg::round(v*-dmin/100.f):(float)dmin, + Mw = dmax<0?cimg::round(u*-dmax/100.f):(float)dmax, + Mh = dmax<0?cimg::round(v*-dmax/100.f):(float)dmax; + float + w = (float)std::max(1U,dx), + h = (float)std::max(1U,dy); + if (dz>1) { w+=dz; h+=dz; } + if (wMw) { h = h*Mw/w; w = Mw; } + if (h>Mh) { w = w*Mh/h; h = Mh; } + if (wdisp = img is equivalent to disp.display(img). + **/ + template + CImgDisplay& operator=(const CImg& img) { + return display(img); + } + + //! Display list of images on associated window. + /** + \note disp = list is equivalent to disp.display(list). + **/ + template + CImgDisplay& operator=(const CImgList& list) { + return display(list); + } + + //! Construct a display as a copy of another one \inplace. + /** + \note Equivalent to assign(const CImgDisplay&). + **/ + CImgDisplay& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return \c false if display is empty, \c true otherwise. + /** + \note if (disp) { ... } is equivalent to if (!disp.is_empty()) { ... }. + **/ + operator bool() const { + return !is_empty(); + } + + //@} + //------------------------------------------ + // + //! \name Instance Checking + //@{ + //------------------------------------------ + + //! Return \c true if display is empty, \c false otherwise. + /** + **/ + bool is_empty() const { + return !(_width && _height); + } + + //! Return \c true if display is closed (i.e. not visible on the screen), \c false otherwise. + /** + \note + - When a user physically closes the associated window, the display is set to closed. + - A closed display is not destroyed. Its associated window can be show again on the screen using show(). + **/ + bool is_closed() const { + return _is_closed; + } + + //! Return \c true if associated window has been resized on the screen, \c false otherwise. + /** + **/ + bool is_resized() const { + return _is_resized; + } + + //! Return \c true if associated window has been moved on the screen, \c false otherwise. + /** + **/ + bool is_moved() const { + return _is_moved; + } + + //! Return \c true if any event has occured on the associated window, \c false otherwise. + /** + **/ + bool is_event() const { + return _is_event; + } + + //! Return \c true if current display is in fullscreen mode, \c false otherwise. + /** + **/ + bool is_fullscreen() const { + return _is_fullscreen; + } + + //! Return \c true if any key is being pressed on the associated window, \c false otherwise. + /** + \note The methods below do the same only for specific keys. + **/ + bool is_key() const { + return _is_keyESC || _is_keyF1 || _is_keyF2 || _is_keyF3 || + _is_keyF4 || _is_keyF5 || _is_keyF6 || _is_keyF7 || + _is_keyF8 || _is_keyF9 || _is_keyF10 || _is_keyF11 || + _is_keyF12 || _is_keyPAUSE || _is_key1 || _is_key2 || + _is_key3 || _is_key4 || _is_key5 || _is_key6 || + _is_key7 || _is_key8 || _is_key9 || _is_key0 || + _is_keyBACKSPACE || _is_keyINSERT || _is_keyHOME || + _is_keyPAGEUP || _is_keyTAB || _is_keyQ || _is_keyW || + _is_keyE || _is_keyR || _is_keyT || _is_keyY || + _is_keyU || _is_keyI || _is_keyO || _is_keyP || + _is_keyDELETE || _is_keyEND || _is_keyPAGEDOWN || + _is_keyCAPSLOCK || _is_keyA || _is_keyS || _is_keyD || + _is_keyF || _is_keyG || _is_keyH || _is_keyJ || + _is_keyK || _is_keyL || _is_keyENTER || + _is_keySHIFTLEFT || _is_keyZ || _is_keyX || _is_keyC || + _is_keyV || _is_keyB || _is_keyN || _is_keyM || + _is_keySHIFTRIGHT || _is_keyARROWUP || _is_keyCTRLLEFT || + _is_keyAPPLEFT || _is_keyALT || _is_keySPACE || _is_keyALTGR || + _is_keyAPPRIGHT || _is_keyMENU || _is_keyCTRLRIGHT || + _is_keyARROWLEFT || _is_keyARROWDOWN || _is_keyARROWRIGHT || + _is_keyPAD0 || _is_keyPAD1 || _is_keyPAD2 || + _is_keyPAD3 || _is_keyPAD4 || _is_keyPAD5 || + _is_keyPAD6 || _is_keyPAD7 || _is_keyPAD8 || + _is_keyPAD9 || _is_keyPADADD || _is_keyPADSUB || + _is_keyPADMUL || _is_keyPADDIV; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode Keycode to test. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.key(cimg::keyTAB)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool is_key(const unsigned int keycode) const { +#define _cimg_iskey_test(k) if (keycode==cimg::key##k) return _is_key##k; + _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3); + _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7); + _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11); + _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2); + _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6); + _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0); + _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME); + _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W); + _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y); + _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P); + _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN); + _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D); + _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J); + _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER); + _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C); + _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M); + _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT); + _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR); + _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT); + _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT); + _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2); + _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5); + _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8); + _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB); + _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV); + return false; + } + + //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. + /** + \param keycode C-string containing the keycode label of the key to test. + \note Use it when the key you want to test can be dynamically set by the user. + \par Example + \code + CImgDisplay disp(400,400); + const char *const keycode = "TAB"; + while (!disp.is_closed()) { + if (disp.is_key(keycode)) { ... } // Equivalent to 'if (disp.is_keyTAB())' + disp.wait(); + } + \endcode + **/ + bool& is_key(const char *const keycode) { + static bool f = false; + f = false; +#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(keycode,#k)) return _is_key##k; + _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3); + _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7); + _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11); + _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2); + _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6); + _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0); + _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME); + _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W); + _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y); + _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P); + _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN); + _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D); + _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J); + _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER); + _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C); + _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M); + _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT); + _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR); + _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT); + _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT); + _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2); + _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5); + _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8); + _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB); + _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV); + return f; + } + + //! Return \c true if specified key sequence has been typed on the associated window, \c false otherwise. + /** + \param keycodes_sequence Buffer of keycodes to test. + \param length Number of keys in the \c keycodes_sequence buffer. + \param remove_sequence Tells if the key sequence must be removed from the key history, if found. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + CImgDisplay disp(400,400); + const unsigned int key_seq[] = { cimg::keyCTRLLEFT, cimg::keyD }; + while (!disp.is_closed()) { + if (disp.is_key_sequence(key_seq,2)) { ... } // Test for the 'CTRL+D' keyboard event + disp.wait(); + } + \endcode + **/ + bool is_key_sequence(const unsigned int *const keycodes_sequence, const unsigned int length, + const bool remove_sequence=false) { + if (keycodes_sequence && length) { + const unsigned int + *const ps_end = keycodes_sequence + length - 1, + *const pk_end = (unsigned int*)_keys + 1 + 128 - length, + k = *ps_end; + for (unsigned int *pk = (unsigned int*)_keys; pk[0,255]. + If the range of values of the data to display is different, a normalization may be required for displaying + the data in a correct way. The normalization type can be one of: + - \c 0: Value normalization is disabled. It is then assumed that all input data to be displayed by the + CImgDisplay instance have values in range [0,255]. + - \c 1: Value normalization is always performed (this is the default behavior). + Before displaying an input image, its values will be (virtually) stretched + in range [0,255], so that the contrast of the displayed pixels will be maximum. + Use this mode for images whose minimum and maximum values are not prescribed to known values + (e.g. float-valued images). + Note that when normalized versions of images are computed for display purposes, the actual values of these + images are not modified. + - \c 2: Value normalization is performed once (on the first image display), then the same normalization + coefficients are kept for next displayed frames. + - \c 3: Value normalization depends on the pixel type of the data to display. For integer pixel types, + the normalization is done regarding the minimum/maximum values of the type (no normalization occurs then + for unsigned char). + For float-valued pixel types, the normalization is done regarding the minimum/maximum value of the image + data instead. + **/ + unsigned int normalization() const { + return _normalization; + } + + //! Return title of the associated window as a C-string. + /** + \note Window title may be not visible, depending on the used window manager or if the current display is + in fullscreen mode. + **/ + const char *title() const { + return _title?_title:""; + } + + //! Return width of the associated window. + /** + \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual width of the associated window. + **/ + int window_width() const { + return (int)_window_width; + } + + //! Return height of the associated window. + /** + \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance) + may be different from the actual height of the associated window. + **/ + int window_height() const { + return (int)_window_height; + } + + //! Return X-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_x() const { + return _window_x; + } + + //! Return Y-coordinate of the associated window. + /** + \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. + **/ + int window_y() const { + return _window_y; + } + + //! Return X-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,width()-1]. + **/ + int mouse_x() const { + return _mouse_x; + } + + //! Return Y-coordinate of the mouse pointer. + /** + \note + - If the mouse pointer is outside window area, \c -1 is returned. + - Otherwise, the returned value is in the range [0,height()-1]. + **/ + int mouse_y() const { + return _mouse_y; + } + + //! Return current state of the mouse buttons. + /** + \note Three mouse buttons can be managed. If one button is pressed, its corresponding bit in the returned + value is set: + - bit \c 0 (value \c 0x1): State of the left mouse button. + - bit \c 1 (value \c 0x2): State of the right mouse button. + - bit \c 2 (value \c 0x4): State of the middle mouse button. + + Several bits can be activated if more than one button are pressed at the same time. + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.button()&1) { // Left button clicked + ... + } + if (disp.button()&2) { // Right button clicked + ... + } + if (disp.button()&4) { // Middle button clicked + ... + } + disp.wait(); + } + \endcode + **/ + unsigned int button() const { + return _button; + } + + //! Return current state of the mouse wheel. + /** + \note + - The returned value can be positive or negative depending on whether the mouse wheel has been scrolled + forward or backward. + - Scrolling the wheel forward add \c 1 to the wheel value. + - Scrolling the wheel backward substract \c 1 to the wheel value. + - The returned value cumulates the number of forward of backward scrolls since the creation of the display, + or since the last reset of the wheel value (using set_wheel()). It is strongly recommended to quickly reset + the wheel counter when an action has been performed regarding the current wheel value. + Otherwise, the returned wheel value may be for instance \c 0 despite the fact that many scrolls have been done + (as many in forward as in backward directions). + \par Example + \code + CImgDisplay disp(400,400); + while (!disp.is_closed()) { + if (disp.wheel()) { + int counter = disp.wheel(); // Read the state of the mouse wheel + ... // Do what you want with 'counter' + disp.set_wheel(); // Reset the wheel value to 0 + } + disp.wait(); + } + \endcode + **/ + int wheel() const { + return _wheel; + } + + //! Return one entry from the pressed keys history. + /** + \param pos Indice to read from the pressed keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a pressed key or \c 0 for a released key. + \note + - Each CImgDisplay stores a history of the pressed keys in a buffer of size \c 128. When a new key is pressed, + its keycode is stored in the pressed keys history. When a key is released, \c 0 is put instead. + This means that up to the 64 last pressed keys may be read from the pressed keys history. + When a new value is stored, the pressed keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int key(const unsigned int pos=0) const { + return pos<128?_keys[pos]:0; + } + + //! Return one entry from the released keys history. + /** + \param pos Indice to read from the released keys history (indice \c 0 corresponds to latest entry). + \return Keycode of a released key or \c 0 for a pressed key. + \note + - Each CImgDisplay stores a history of the released keys in a buffer of size \c 128. When a new key is released, + its keycode is stored in the pressed keys history. When a key is pressed, \c 0 is put instead. + This means that up to the 64 last released keys may be read from the released keys history. + When a new value is stored, the released keys history is shifted so that the latest entry is always + stored at position \c 0. + - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + unsigned int released_key(const unsigned int pos=0) const { + return pos<128?_released_keys[pos]:0; + } + + //! Return keycode corresponding to the specified string. + /** + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + \par Example + \code + const unsigned int keyTAB = CImgDisplay::keycode("TAB"); // Return cimg::keyTAB + \endcode + **/ + static unsigned int keycode(const char *const keycode) { +#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k; + _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3); + _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7); + _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11); + _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2); + _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6); + _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0); + _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME); + _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W); + _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y); + _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P); + _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN); + _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D); + _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J); + _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER); + _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C); + _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M); + _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT); + _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR); + _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT); + _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT); + _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2); + _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5); + _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8); + _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB); + _cimg_keycode(PADMUL); _cimg_keycode(PADDIV); + return 0; + } + + //! Return the current refresh rate, in frames per second. + /** + \note Returns a significant value when the current instance is used to display successive frames. + It measures the delay between successive calls to frames_per_second(). + **/ + float frames_per_second() { + if (!_fps_timer) _fps_timer = cimg::time(); + const float delta = (cimg::time() - _fps_timer)/1000.f; + ++_fps_frames; + if (delta>=1) { + _fps_fps = _fps_frames/delta; + _fps_frames = 0; + _fps_timer = cimg::time(); + } + 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; + } + + //@} + //--------------------------------------- + // + //! \name Window Manipulation + //@{ + //--------------------------------------- + +#if cimg_display==0 + + //! Display image on associated window. + /** + \param img Input image to display. + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImg& img) { + return assign(img); + } + +#endif + + //! Display list of images on associated window. + /** + \param list List of images to display. + \param axis Axis used to append the images along, for the visualization (can be \c x, \c y, \c z or \c c). + \param align Relative position of aligned images when displaying lists with images of different sizes + (\c 0 for upper-left, \c 0.5 for centering and \c 1 for lower-right). + \note This method returns immediately. + **/ + template + CImgDisplay& display(const CImgList& list, const char axis='x', const float align=0) { + if (list._width==1) { + const CImg& img = list[0]; + if (img._depth==1 && (img._spectrum==1 || img._spectrum>=3) && _normalization!=1) return display(img); + } + CImgList::ucharT> visu(list._width); + 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]); + dims = std::max(dims,visu[l]._spectrum); + } + cimglist_for(list,l) if (visu[l]._spectrumimg.width() become equal, as well as height() and + img.height(). + - The associated window is also resized to specified dimensions. + **/ + template + CImgDisplay& resize(const CImg& img, const bool force_redraw=true) { + return resize(img._width,img._height,force_redraw); + } + + //! Resize display to the size of another CImgDisplay instance. + /** + \param disp Input display to take size from. + \param force_redraw Tells if the previous window content must be resized and updated as well. + \note + - Calling this method ensures that width() and disp.width() become equal, as well as height() and + disp.height(). + - The associated window is also resized to specified dimensions. + **/ + CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) { + return resize(disp.width(),disp.height(),force_redraw); + } + + // [internal] Render pixel buffer with size (wd,hd) from source buffer of size (ws,hs). + template + 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) { + typedef typename cimg::last::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; xstd::printf(). + \warning As the first argument is a format string, it is highly recommended to write + \code + disp.set_title("%s",window_title); + \endcode + instead of + \code + disp.set_title(window_title); + \endcode + if \c window_title can be arbitrary, to prevent nasty memory access. + **/ + CImgDisplay& set_title(const char *const format, ...) { + return assign(0,0,format); + } + +#endif + + //! Enable or disable fullscreen mode. + /** + \param is_fullscreen Tells is the fullscreen mode must be activated or not. + \param force_redraw Tells if the previous window content must be displayed as well. + \note + - When the fullscreen mode is enabled, the associated window fills the entire screen but the size of the + current display is not modified. + - The screen resolution may be switched to fit the associated window size and ensure it appears the largest + as possible. + For X-Window (X11) users, the configuration flag \c cimg_use_xrandr has to be set to allow the screen + resolution change (requires the X11 extensions to be enabled). + **/ + CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool force_redraw=true) { + if (is_empty() || _is_fullscreen==is_fullscreen) return *this; + return toggle_fullscreen(force_redraw); + } + +#if cimg_display==0 + + //! Toggle fullscreen mode. + /** + \param force_redraw Tells if the previous window content must be displayed as well. + \note Enable fullscreen mode if it was not enabled, and disable it otherwise. + **/ + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + return assign(_width,_height,0,3,force_redraw); + } + + //! Show mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& show_mouse() { + return assign(); + } + + //! Hide mouse pointer. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& hide_mouse() { + return assign(); + } + + //! Move mouse pointer to a specified location. + /** + \note Depending on the window manager behavior, this method may not succeed + (no exceptions are thrown nevertheless). + **/ + CImgDisplay& set_mouse(const int pos_x, const int pos_y) { + return assign(pos_x,pos_y); + } + +#endif + + //! Simulate a mouse button release event. + /** + \note All mouse buttons are considered released at the same time. + **/ + CImgDisplay& set_button() { + _button = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a mouse button press or release event. + /** + \param button Buttons event code, where each button is associated to a single bit. + \param is_pressed Tells if the mouse button is considered as pressed or released. + **/ + CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) { + const unsigned int buttoncode = button==1U?1U:button==2U?2U:button==3U?4U:0U; + if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode; + _is_event = buttoncode?true:false; + if (buttoncode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all mouse wheel events. + /** + \note Make wheel() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_wheel() { + _wheel = 0; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a wheel event. + /** + \param amplitude Amplitude of the wheel scrolling to simulate. + \note Make wheel() to return \c amplitude, if called afterwards. + **/ + CImgDisplay& set_wheel(const int amplitude) { + _wheel+=amplitude; + _is_event = amplitude?true:false; + if (amplitude) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all key events. + /** + \note Make key() to return \c 0, if called afterwards. + **/ + CImgDisplay& set_key() { + std::memset((void*)_keys,0,128*sizeof(unsigned int)); + std::memset((void*)_released_keys,0,128*sizeof(unsigned int)); + _is_keyESC = _is_keyF1 = _is_keyF2 = _is_keyF3 = _is_keyF4 = _is_keyF5 = _is_keyF6 = _is_keyF7 = _is_keyF8 = + _is_keyF9 = _is_keyF10 = _is_keyF11 = _is_keyF12 = _is_keyPAUSE = _is_key1 = _is_key2 = _is_key3 = _is_key4 = + _is_key5 = _is_key6 = _is_key7 = _is_key8 = _is_key9 = _is_key0 = _is_keyBACKSPACE = _is_keyINSERT = + _is_keyHOME = _is_keyPAGEUP = _is_keyTAB = _is_keyQ = _is_keyW = _is_keyE = _is_keyR = _is_keyT = _is_keyY = + _is_keyU = _is_keyI = _is_keyO = _is_keyP = _is_keyDELETE = _is_keyEND = _is_keyPAGEDOWN = _is_keyCAPSLOCK = + _is_keyA = _is_keyS = _is_keyD = _is_keyF = _is_keyG = _is_keyH = _is_keyJ = _is_keyK = _is_keyL = + _is_keyENTER = _is_keySHIFTLEFT = _is_keyZ = _is_keyX = _is_keyC = _is_keyV = _is_keyB = _is_keyN = + _is_keyM = _is_keySHIFTRIGHT = _is_keyARROWUP = _is_keyCTRLLEFT = _is_keyAPPLEFT = _is_keyALT = _is_keySPACE = + _is_keyALTGR = _is_keyAPPRIGHT = _is_keyMENU = _is_keyCTRLRIGHT = _is_keyARROWLEFT = _is_keyARROWDOWN = + _is_keyARROWRIGHT = _is_keyPAD0 = _is_keyPAD1 = _is_keyPAD2 = _is_keyPAD3 = _is_keyPAD4 = _is_keyPAD5 = + _is_keyPAD6 = _is_keyPAD7 = _is_keyPAD8 = _is_keyPAD9 = _is_keyPADADD = _is_keyPADSUB = _is_keyPADMUL = + _is_keyPADDIV = false; + _is_event = true; +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + return *this; + } + + //! Simulate a keyboard press/release event. + /** + \param keycode Keycode of the associated key. + \param is_pressed Tells if the key is considered as pressed or released. + \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure + your code stay portable (see cimg::keyESC). + **/ + CImgDisplay& set_key(const unsigned int keycode, const bool is_pressed=true) { +#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = is_pressed; + _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3); + _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7); + _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11); + _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2); + _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6); + _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0); + _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME); + _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W); + _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y); + _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P); + _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN); + _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D); + _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J); + _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER); + _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C); + _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M); + _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT); + _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR); + _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT); + _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT); + _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2); + _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5); + _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8); + _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB); + _cimg_set_key(PADMUL); _cimg_set_key(PADDIV); + if (is_pressed) { + if (*_keys) + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = keycode; + if (*_released_keys) { + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = 0; + } + } else { + if (*_keys) { + std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); + *_keys = 0; + } + if (*_released_keys) + std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); + *_released_keys = keycode; + } + _is_event = keycode?true:false; + if (keycode) { +#if cimg_display==1 + pthread_cond_broadcast(&cimg::X11_attr().wait_event); +#elif cimg_display==2 + SetEvent(cimg::Win32_attr().wait_event); +#endif + } + return *this; + } + + //! Flush all display events. + /** + \note Remove all passed events from the current display. + **/ + CImgDisplay& flush() { + set_key().set_button().set_wheel(); + _is_resized = _is_moved = _is_event = false; + _fps_timer = _fps_frames = _timer = 0; + _fps_fps = 0; + return *this; + } + + //! Wait for any user event occuring on the current display. + CImgDisplay& wait() { + wait(*this); + return *this; + } + + //! Wait for a given number of milliseconds since the last call to wait(). + /** + \param milliseconds Number of milliseconds to wait for. + \note Similar to cimg::wait(). + **/ + CImgDisplay& wait(const unsigned int milliseconds) { + cimg::wait(milliseconds,&_timer); + return *this; + } + + //! Wait for any event occuring on the display \c disp1. + static void wait(CImgDisplay& disp1) { + disp1._is_event = false; + while (!disp1._is_closed && !disp1._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1 or \c disp2. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2) { + disp1._is_event = disp2._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed) && + !disp1._is_event && !disp2._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) { + disp1._is_event = disp2._is_event = disp3._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4 or \c disp5. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, + CImgDisplay& disp5) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event) + wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp6. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp7. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp8. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp9. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp10. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, + CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9, + CImgDisplay& disp10) { + disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = + disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = disp10._is_event = false; + while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || + !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) && + !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && + !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event) + wait_all(); + } + +#if cimg_display==0 + + //! Wait for any window event occuring in any opened CImgDisplay. + static void wait_all() { + return _no_display_exception(); + } + + //! Render image into internal display buffer. + /** + \param img Input image data to render. + \note + - Convert image data representation into the internal display buffer (architecture-dependent structure). + - The content of the associated window is not modified, until paint() is called. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + template + CImgDisplay& render(const CImg& img) { + return assign(img); + } + + //! Paint internal display buffer on associated window. + /** + \note + - Update the content of the associated window with the internal display buffer, e.g. after a render() call. + - Should not be used for common CImgDisplay uses, since display() is more useful. + **/ + CImgDisplay& paint() { + return assign(); + } + + + //! Take a snapshot of the current screen content. + /** + \param x0 X-coordinate of the upper left corner. + \param y0 Y-coordinate of the upper left corner. + \param x1 X-coordinate of the lower right corner. + \param y1 Y-coordinate of the lower right corner. + \param[out] img Output screenshot. Can be empty on input + **/ + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + cimg::unused(x0,y0,x1,y1,&img); + _no_display_exception(); + } + + //! Take a snapshot of the associated window content. + /** + \param[out] img Output snapshot. Can be empty on input. + **/ + template + const CImgDisplay& snapshot(CImg& img) const { + cimg::unused(img); + _no_display_exception(); + return *this; + } +#endif + + // X11-based implementation + //-------------------------- +#if cimg_display==1 + + Atom _wm_window_atom, _wm_protocol_atom; + Window _window, _background_window; + Colormap _colormap; + XImage *_image; + void *_data; +#ifdef cimg_use_xshm + XShmSegmentInfo *_shminfo; +#endif + + static int screen_width() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_width(): Failed to open X11 display."); + res = DisplayWidth(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width; + else res = DisplayWidth(dpy,DefaultScreen(dpy)); +#else + res = DisplayWidth(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static int screen_height() { + Display *const dpy = cimg::X11_attr().display; + int res = 0; + if (!dpy) { + Display *const _dpy = XOpenDisplay(0); + if (!_dpy) + throw CImgDisplayException("CImgDisplay::screen_height(): Failed to open X11 display."); + res = DisplayHeight(_dpy,DefaultScreen(_dpy)); + XCloseDisplay(_dpy); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) + res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height; + else res = DisplayHeight(dpy,DefaultScreen(dpy)); +#else + res = DisplayHeight(dpy,DefaultScreen(dpy)); +#endif + } + return res; + } + + static void wait_all() { + if (!cimg::X11_attr().display) return; + pthread_mutex_lock(&cimg::X11_attr().wait_event_mutex); + pthread_cond_wait(&cimg::X11_attr().wait_event,&cimg::X11_attr().wait_event_mutex); + pthread_mutex_unlock(&cimg::X11_attr().wait_event_mutex); + } + + void _handle_events(const XEvent *const pevent) { + Display *const dpy = cimg::X11_attr().display; + XEvent event = *pevent; + switch (event.type) { + case ClientMessage : { + if ((int)event.xclient.message_type==(int)_wm_protocol_atom && + (int)event.xclient.data.l[0]==(int)_wm_window_atom) { + XUnmapWindow(cimg::X11_attr().display,_window); + _is_closed = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case ConfigureNotify : { + while (XCheckWindowEvent(dpy,_window,StructureNotifyMask,&event)) {} + const unsigned int nw = event.xconfigure.width, nh = event.xconfigure.height; + const int nx = event.xconfigure.x, ny = event.xconfigure.y; + if (nw && nh && (nw!=_window_width || nh!=_window_height)) { + _window_width = nw; _window_height = nh; _mouse_x = _mouse_y = -1; + XResizeWindow(dpy,_window,_window_width,_window_height); + _is_resized = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + if (nx!=_window_x || ny!=_window_y) { + _window_x = nx; _window_y = ny; _is_moved = _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } + } break; + case Expose : { + while (XCheckWindowEvent(dpy,_window,ExposureMask,&event)) {} + _paint(false); + if (_is_fullscreen) { + XWindowAttributes attr; + XGetWindowAttributes(dpy,_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XSetInputFocus(dpy,_window,RevertToParent,CurrentTime); + } + } break; + case ButtonPress : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1); break; + case 3 : set_button(2); break; + case 2 : set_button(3); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonPressMask,&event)); + } break; + case ButtonRelease : { + do { + _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + switch (event.xbutton.button) { + case 1 : set_button(1,false); break; + case 3 : set_button(2,false); break; + case 2 : set_button(3,false); break; + case 4 : set_wheel(1); break; + case 5 : set_wheel(-1); break; + } + } while (XCheckWindowEvent(dpy,_window,ButtonReleaseMask,&event)); + } break; + case KeyPress : { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,true); + } break; + case KeyRelease : { + char keys_return[32]; // Check that the key has been physically unpressed + XQueryKeymap(dpy,keys_return); + const unsigned int kc = event.xkey.keycode, kc1 = kc/8, kc2 = kc%8; + const bool is_key_pressed = kc1>=32?false:(keys_return[kc1]>>kc2)&1; + if (!is_key_pressed) { + char tmp = 0; KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + set_key((unsigned int)ksym,false); + } + } break; + case EnterNotify: { + while (XCheckWindowEvent(dpy,_window,EnterWindowMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + } break; + case LeaveNotify : { + while (XCheckWindowEvent(dpy,_window,LeaveWindowMask,&event)) {} + _mouse_x = _mouse_y = -1; _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + case MotionNotify : { + while (XCheckWindowEvent(dpy,_window,PointerMotionMask,&event)) {} + _mouse_x = event.xmotion.x; + _mouse_y = event.xmotion.y; + if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; + _is_event = true; + pthread_cond_broadcast(&cimg::X11_attr().wait_event); + } break; + } + } + + static void* _events_thread(void *arg) { // Thread to manage events for all opened display windows + Display *const dpy = cimg::X11_attr().display; + XEvent event; + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0); + if (!arg) for ( ; ; ) { + cimg_lock_display(); + bool event_flag = XCheckTypedEvent(dpy,ClientMessage,&event); + if (!event_flag) event_flag = XCheckMaskEvent(dpy, + ExposureMask | StructureNotifyMask | ButtonPressMask | + KeyPressMask | PointerMotionMask | EnterWindowMask | + LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask,&event); + if (event_flag) + for (unsigned int i = 0; i_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window) + cimg::X11_attr().wins[i]->_handle_events(&event); + cimg_unlock_display(); + pthread_testcancel(); + cimg::sleep(8); + } + return 0; + } + + void _set_colormap(Colormap& _colormap, const unsigned int dim) { + XColor *const colormap = new XColor[256]; + switch (dim) { + case 1 : { // colormap for greyscale images + for (unsigned int index = 0; index<256; ++index) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].green = colormap[index].blue = (unsigned short)(index<<8); + colormap[index].flags = DoRed | DoGreen | DoBlue; + } + } break; + case 2 : { // colormap for RG images + for (unsigned int index = 0, r = 8; r<256; r+=16) + for (unsigned int g = 8; g<256; g+=16) { + colormap[index].pixel = index; + colormap[index].red = colormap[index].blue = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } break; + default : { // colormap for RGB images + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap[index].pixel = index; + colormap[index].red = (unsigned short)(r<<8); + colormap[index].green = (unsigned short)(g<<8); + colormap[index].blue = (unsigned short)(b<<8); + colormap[index++].flags = DoRed | DoGreen | DoBlue; + } + } + } + XStoreColors(cimg::X11_attr().display,_colormap,colormap,256); + delete[] colormap; + } + + void _map_window() { + Display *const dpy = cimg::X11_attr().display; + bool is_exposed = false, is_mapped = false; + XWindowAttributes attr; + XEvent event; + XMapRaised(dpy,_window); + do { // Wait for the window to be mapped + XWindowEvent(dpy,_window,StructureNotifyMask | ExposureMask,&event); + switch (event.type) { + case MapNotify : is_mapped = true; break; + case Expose : is_exposed = true; break; + } + } while (!is_exposed || !is_mapped); + do { // Wait for the window to be visible + XGetWindowAttributes(dpy,_window,&attr); + if (attr.map_state!=IsViewable) { XSync(dpy,0); cimg::sleep(10); } + } while (attr.map_state!=IsViewable); + _window_x = attr.x; + _window_y = attr.y; + } + + void _paint(const bool wait_expose=true) { + if (_is_closed || !_image) return; + Display *const dpy = cimg::X11_attr().display; + if (wait_expose) { // Send an expose event sticked to display window to force repaint + XEvent event; + event.xexpose.type = Expose; + event.xexpose.serial = 0; + event.xexpose.send_event = 1; + event.xexpose.display = dpy; + event.xexpose.window = _window; + event.xexpose.x = 0; + event.xexpose.y = 0; + event.xexpose.width = width(); + event.xexpose.height = height(); + event.xexpose.count = 0; + XSendEvent(dpy,_window,0,0,&event); + } else { // Repaint directly (may be called from the expose event) + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height,1); + else XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#else + XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); +#endif + } + } + + template + void _resize(T pixel_type, const unsigned int ndimx, const unsigned int ndimy, const bool force_redraw) { + Display *const dpy = cimg::X11_attr().display; + cimg::unused(pixel_type); + +#ifdef cimg_use_xshm + if (_shminfo) { + XShmSegmentInfo *const nshminfo = new XShmSegmentInfo; + XImage *const nimage = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy); + if (!nimage) { delete nshminfo; return; } + else { + nshminfo->shmid = shmget(IPC_PRIVATE,ndimx*ndimy*sizeof(T),IPC_CREAT | 0777); + if (nshminfo->shmid==-1) { XDestroyImage(nimage); delete nshminfo; return; } + else { + nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0); + if (nshminfo->shmaddr==(char*)-1) { + shmctl(nshminfo->shmid,IPC_RMID,0); XDestroyImage(nimage); delete nshminfo; return; + } else { + nshminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,nshminfo); + XFlush(dpy); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(nshminfo->shmaddr); + shmctl(nshminfo->shmid,IPC_RMID,0); + XDestroyImage(nimage); + delete nshminfo; + return; + } else { + T *const ndata = (T*)nimage->data; + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + XShmDetach(dpy,_shminfo); + XDestroyImage(_image); + shmdt(_shminfo->shmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = nshminfo; + _image = nimage; + _data = (void*)ndata; + } + } + } + } + } else +#endif + { + T *ndata = (T*)std::malloc(ndimx*ndimy*sizeof(T)); + if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); + else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + _data = (void*)ndata; + XDestroyImage(_image); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), + cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,ndimx,ndimy,8,0); + } + } + + void _init_fullscreen() { + if (!_is_fullscreen || _is_closed) return; + Display *const dpy = cimg::X11_attr().display; + _background_window = 0; + +#ifdef cimg_use_xrandr + int foo; + if (XRRQueryExtension(dpy,&foo,&foo)) { + XRRRotations(dpy,DefaultScreen(dpy),&cimg::X11_attr().curr_rotation); + if (!cimg::X11_attr().resolutions) { + cimg::X11_attr().resolutions = XRRSizes(dpy,DefaultScreen(dpy),&foo); + cimg::X11_attr().nb_resolutions = (unsigned int)foo; + } + if (cimg::X11_attr().resolutions) { + cimg::X11_attr().curr_resolution = 0; + for (unsigned int i = 0; i=_width && nh>=_height && + nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) && + nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height)) + cimg::X11_attr().curr_resolution = i; + } + if (cimg::X11_attr().curr_resolution>0) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy), + cimg::X11_attr().curr_resolution,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + } + } + } + if (!cimg::X11_attr().resolutions) + cimg::warn(_cimgdisplay_instance + "init_fullscreen(): Xrandr extension not supported by the X server.", + cimgdisplay_instance); +#endif + + const unsigned int sx = screen_width(), sy = screen_height(); + if (sx==_width && sy==_height) return; + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _background_window = XCreateWindow(dpy,DefaultRootWindow(dpy),0,0,sx,sy,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + const cimg_ulong buf_size = (cimg_ulong)sx*sy*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + void *background_data = std::malloc(buf_size); + std::memset(background_data,0,buf_size); + XImage *background_image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)background_data,sx,sy,8,0); + XEvent event; + XSelectInput(dpy,_background_window,StructureNotifyMask); + XMapRaised(dpy,_background_window); + do XWindowEvent(dpy,_background_window,StructureNotifyMask,&event); + while (event.type!=MapNotify); + GC gc = DefaultGC(dpy,DefaultScreen(dpy)); +#ifdef cimg_use_xshm + if (_shminfo) XShmPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy,0); + else XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#else + XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); +#endif + XWindowAttributes attr; + XGetWindowAttributes(dpy,_background_window,&attr); + while (attr.map_state!=IsViewable) XSync(dpy,0); + XDestroyImage(background_image); + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + Display *const dpy = cimg::X11_attr().display; + XUngrabKeyboard(dpy,CurrentTime); +#ifdef cimg_use_xrandr + if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) { + XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); + XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),0,cimg::X11_attr().curr_rotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(dpy,0); + cimg::X11_attr().curr_resolution = 0; + } +#endif + if (_background_window) XDestroyWindow(dpy,_background_window); + _background_window = 0; + _is_fullscreen = false; + } + + static int _assign_xshm(Display *dpy, XErrorEvent *error) { + cimg::unused(dpy,error); + cimg::X11_attr().is_shm_enabled = false; + return 0; + } + + void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + cimg::mutex(14); + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous display window if existing + if (!is_empty()) assign(); + + // Open X11 display and retrieve graphical properties. + Display* &dpy = cimg::X11_attr().display; + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Failed to open X11 display.", + cimgdisplay_instance); + + cimg::X11_attr().nb_bits = DefaultDepth(dpy,DefaultScreen(dpy)); + if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 && + cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32) + throw CImgDisplayException(_cimgdisplay_instance + "assign(): Invalid %u bits screen mode detected " + "(only 8, 16, 24 and 32 bits modes are managed).", + cimgdisplay_instance, + cimg::X11_attr().nb_bits); + XVisualInfo vtemplate; + vtemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,DefaultScreen(dpy))); + int nb_visuals; + XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals); + if (vinfo && vinfo->red_maskblue_mask) cimg::X11_attr().is_blue_first = true; + cimg::X11_attr().byte_order = ImageByteOrder(dpy); + XFree(vinfo); + + cimg_lock_display(); + cimg::X11_attr().events_thread = new pthread_t; + pthread_create(cimg::X11_attr().events_thread,0,_events_thread,0); + } else cimg_lock_display(); + + // Set display variables. + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _title = tmp_title; + flush(); + + // Create X11 window (and LUT, if 8bits display) + if (_is_fullscreen) { + if (!_is_closed) _init_fullscreen(); + const unsigned int sx = screen_width(), sy = screen_height(); + XSetWindowAttributes winattr; + winattr.override_redirect = 1; + _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx - _width)/2,(sy - _height)/2,_width,_height,0,0, + InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + } else + _window = XCreateSimpleWindow(dpy,DefaultRootWindow(dpy),0,0,_width,_height,0,0L,0L); + + XSelectInput(dpy,_window, + ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask | + EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask); + + XStoreName(dpy,_window,_title?_title:" "); + if (cimg::X11_attr().nb_bits==8) { + _colormap = XCreateColormap(dpy,_window,DefaultVisual(dpy,DefaultScreen(dpy)),AllocAll); + _set_colormap(_colormap,3); + XSetWindowColormap(dpy,_window,_colormap); + } + + static const char *const _window_class = cimg_appname; + XClassHint *const window_class = XAllocClassHint(); + window_class->res_name = (char*)_window_class; + window_class->res_class = (char*)_window_class; + XSetClassHint(dpy,_window,window_class); + XFree(window_class); + + _window_width = _width; + _window_height = _height; + + // Create XImage +#ifdef cimg_use_xshm + _shminfo = 0; + if (XShmQueryExtension(dpy)) { + _shminfo = new XShmSegmentInfo; + _image = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,_shminfo,_width,_height); + if (!_image) { delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmid = shmget(IPC_PRIVATE,_image->bytes_per_line*_image->height,IPC_CREAT|0777); + if (_shminfo->shmid==-1) { XDestroyImage(_image); delete _shminfo; _shminfo = 0; } + else { + _shminfo->shmaddr = _image->data = (char*)(_data = shmat(_shminfo->shmid,0,0)); + if (_shminfo->shmaddr==(char*)-1) { + shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); delete _shminfo; _shminfo = 0; + } else { + _shminfo->readOnly = 0; + cimg::X11_attr().is_shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(dpy,_shminfo); + XSync(dpy,0); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11_attr().is_shm_enabled) { + shmdt(_shminfo->shmaddr); shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); + delete _shminfo; _shminfo = 0; + } + } + } + } + } + if (!_shminfo) +#endif + { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*(cimg::X11_attr().nb_bits==8?1: + (cimg::X11_attr().nb_bits==16?2:4)); + _data = std::malloc(buf_size); + _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, + ZPixmap,0,(char*)_data,_width,_height,8,0); + } + + _wm_window_atom = XInternAtom(dpy,"WM_DELETE_WINDOW",0); + _wm_protocol_atom = XInternAtom(dpy,"WM_PROTOCOLS",0); + XSetWMProtocols(dpy,_window,&_wm_window_atom,1); + + if (_is_fullscreen) XGrabKeyboard(dpy,_window,1,GrabModeAsync,GrabModeAsync,CurrentTime); + cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this; + if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type::min(); } + cimg_unlock_display(); + cimg::mutex(14,0); + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + + // Remove display window from event thread list. + unsigned int i; + for (i = 0; ishmaddr); + shmctl(_shminfo->shmid,IPC_RMID,0); + delete _shminfo; + _shminfo = 0; + } else +#endif + XDestroyImage(_image); + _data = 0; _image = 0; + if (cimg::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap); + _colormap = 0; + XSync(dpy,0); + + // Reset display variables. + delete[] _title; + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + + cimg_unlock_display(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + (cimg::X11_attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))* + (size_t)_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return render(nimg).paint(); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): + cimg::X11_attr().nb_bits==16?sizeof(unsigned short): + sizeof(unsigned int))*(size_t)_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + Display *const dpy = cimg::X11_attr().display; + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*width()/100), + tmpdimy = (nheight>0)?nheight:(-nheight*height()/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + show(); + cimg_lock_display(); + if (_window_width!=dimx || _window_height!=dimy) { + XWindowAttributes attr; + for (unsigned int i = 0; i<10; ++i) { + XResizeWindow(dpy,_window,dimx,dimy); + XGetWindowAttributes(dpy,_window,&attr); + if (attr.width==(int)dimx && attr.height==(int)dimy) break; + cimg::wait(5,&_timer); + } + } + if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) { + case 8 : { unsigned char pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + case 16 : { unsigned short pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; + default : { unsigned int pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } + } + _window_width = _width = dimx; _window_height = _height = dimy; + cimg_unlock_display(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - _width)/2,(screen_height() - _height)/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height* + (cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4)); + void *image_data = std::malloc(buf_size); + std::memcpy(image_data,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,image_data,buf_size); + std::free(image_data); + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + cimg_lock_display(); + if (_is_fullscreen) _init_fullscreen(); + _map_window(); + _is_closed = false; + cimg_unlock_display(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (_is_fullscreen) _desinit_fullscreen(); + XUnmapWindow(dpy,_window); + _window_x = _window_y = -1; + _is_closed = true; + cimg_unlock_display(); + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + show(); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XMoveWindow(dpy,_window,posx,posy); + _window_x = posx; _window_y = posy; + cimg_unlock_display(); + } + _is_moved = false; + return paint(); + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XUndefineCursor(dpy,_window); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + static const char pix_data[8] = { 0 }; + XColor col; + col.red = col.green = col.blue = 0; + Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8); + Cursor cur = XCreatePixmapCursor(dpy,pix,pix,&col,&col,0,0); + XFreePixmap(dpy,pix); + XDefineCursor(dpy,_window,cur); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed) return *this; + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XWarpPointer(dpy,0L,_window,0,0,0,0,posx,posy); + _mouse_x = posx; _mouse_y = posy; + _is_moved = false; + XSync(dpy,0); + cimg_unlock_display(); + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + Display *const dpy = cimg::X11_attr().display; + cimg_lock_display(); + XStoreName(dpy,_window,tmp); + cimg_unlock_display(); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(false); + } + + CImgDisplay& paint(const bool wait_expose=true) { + if (is_empty()) return *this; + cimg_lock_display(); + _paint(wait_expose); + cimg_unlock_display(); + return *this; + } + + template + CImgDisplay& render(const CImg& img, const bool flag8=false) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + if (cimg::X11_attr().nb_bits==8 && (img._width!=_width || img._height!=_height)) + return render(img.get_resize(_width,_height,1,-100,1)); + if (cimg::X11_attr().nb_bits==8 && !flag8 && img._spectrum==3) { + static const CImg::ucharT> default_colormap = CImg::ucharT>::default_LUT256(); + return render(img.get_index(default_colormap,1,false)); + } + + const T + *data1 = img._data, + *data2 = (img._spectrum>1)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>2)?img.data(0,0,0,2):data1; + + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + cimg_lock_display(); + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, no normalization + _set_colormap(_colormap,img._spectrum); + unsigned char + *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height], + *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + (*ptrd++) = (unsigned char)*(data1++); + break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, no normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (G>>1); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + ptrd[0] = (G<<5) | (G>>1); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + ptrd[0] = (G<<5) | ((unsigned char)*(data3++)>>3); + ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, no normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | + (unsigned char)*(data3++); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | + ((unsigned char)*(data1++)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = 0; + ptrd[3] = 0; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = 0; + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)*(data1++); + ptrd[2] = (unsigned char)*(data2++); + ptrd[3] = (unsigned char)*(data3++); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)*(data3++); + ptrd[1] = (unsigned char)*(data2++); + ptrd[2] = (unsigned char)*(data1++); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (cimg::X11_attr().nb_bits) { + case 8 : { // 256 colormap, with normalization + _set_colormap(_colormap,img._spectrum); + unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: + new unsigned char[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char R = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = R; + } break; + case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); + delete[] ndata; + } + } break; + case 16 : { // 16 bits colors, with normalization + unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data: + new unsigned short[(size_t)img._width*img._height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (val&M) | (G>>3); + ptrd[1] = (G<<5) | (val>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; + ptrd[0] = (G<<5) | (val>>3); + ptrd[1] = (val&M) | (G>>3); + ptrd+=2; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd[1] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd+=2; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; + ptrd[0] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); + ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); + ptrd+=2; + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); + delete[] ndata; + } + } break; + default : { // 24 bits colors, with normalization + unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data: + new unsigned int[(size_t)img._width*img._height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (val<<24) | (val<<16) | (val<<8); + } + break; + case 2 : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + break; + default : + if (cimg::X11_attr().byte_order==cimg::endianness()) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data1++) - _min)*mm)<<16) | + ((unsigned char)((*(data2++) - _min)*mm)<<8) | + (unsigned char)((*(data3++) - _min)*mm); + else + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)((*(data3++) - _min)*mm)<<24) | + ((unsigned char)((*(data2++) - _min)*mm)<<16) | + ((unsigned char)((*(data1++) - _min)*mm)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img._spectrum) { + case 1 : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = 0; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = val; + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + ptrd[0] = val; + ptrd[1] = val; + ptrd[2] = val; + ptrd[3] = 0; + ptrd+=4; + } + break; + case 2 : + if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + break; + default : + if (cimg::X11_attr().byte_order) + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = 0; + ptrd[1] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[3] = (unsigned char)((*(data3++) - _min)*mm); + ptrd+=4; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ptrd[0] = (unsigned char)((*(data3++) - _min)*mm); + ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); + ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); + ptrd[3] = 0; + ptrd+=4; + } + } + } + if (ndata!=_data) { + _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); + delete[] ndata; + } + } + } + } + cimg_unlock_display(); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + Display *dpy = cimg::X11_attr().display; + cimg_lock_display(); + if (!dpy) { + dpy = XOpenDisplay(0); + if (!dpy) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to open X11 display."); + } + Window root = DefaultRootWindow(dpy); + XWindowAttributes gwa; + XGetWindowAttributes(dpy,root,&gwa); + const int width = gwa.width, height = gwa.height; + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + + XImage *image = 0; + if (_x1>=0 && _x0=0 && _y0red_mask, + green_mask = image->green_mask, + blue_mask = image->blue_mask; + img.assign(image->width,image->height,1,3); + T *pR = img.data(0,0,0,0), *pG = img.data(0,0,0,1), *pB = img.data(0,0,0,2); + cimg_forXY(img,x,y) { + const unsigned long pixel = XGetPixel(image,x,y); + *(pR++) = (T)((pixel & red_mask)>>16); + *(pG++) = (T)((pixel & green_mask)>>8); + *(pB++) = (T)(pixel & blue_mask); + } + XDestroyImage(image); + } + } + if (!cimg::X11_attr().display) XCloseDisplay(dpy); + cimg_unlock_display(); + if (img.is_empty()) + throw CImgDisplayException("CImgDisplay::screenshot(): Failed to take screenshot " + "with coordinates (%d,%d)-(%d,%d).", + x0,y0,x1,y1); + } + + template + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned char *ptrs = (unsigned char*)_data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); + switch (cimg::X11_attr().nb_bits) { + case 8 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = *(ptrs++); + *(data1++) = (T)(val&0xe0); + *(data2++) = (T)((val&0x1c)<<3); + *(data3++) = (T)(val<<6); + } + } break; + case 16 : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val0&0xf8); + *(data2++) = (T)((val0<<5) | ((val1&0xe0)>>5)); + *(data3++) = (T)(val1<<3); + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned short + val0 = ptrs[0], + val1 = ptrs[1]; + ptrs+=2; + *(data1++) = (T)(val1&0xf8); + *(data2++) = (T)((val1<<5) | ((val0&0xe0)>>5)); + *(data3++) = (T)(val0<<3); + } + } break; + default : { + if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + ++ptrs; + *(data1++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data3++) = (T)ptrs[2]; + ptrs+=3; + } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + *(data3++) = (T)ptrs[0]; + *(data2++) = (T)ptrs[1]; + *(data1++) = (T)ptrs[2]; + ptrs+=3; + ++ptrs; + } + } + } + return *this; + } + + // Windows-based implementation. + //------------------------------- +#elif cimg_display==2 + + bool _is_mouse_tracked, _is_cursor_visible; + HANDLE _thread, _is_created, _mutex; + HWND _window, _background_window; + CLIENTCREATESTRUCT _ccs; + unsigned int *_data; + DEVMODE _curr_mode; + BITMAPINFO _bmi; + HDC _hdc; + + static int screen_width() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsWidth; + } + + static int screen_height() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return (int)mode.dmPelsHeight; + } + + static void wait_all() { + WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE); + } + + static LRESULT APIENTRY _handle_events(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) { +#ifdef _WIN64 + CImgDisplay *const disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA); +#else + CImgDisplay *const disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA); +#endif + MSG st_msg; + switch (msg) { + case WM_CLOSE : + disp->_mouse_x = disp->_mouse_y = -1; + disp->_window_x = disp->_window_y = 0; + disp->set_button().set_key(0).set_key(0,false)._is_closed = true; + ReleaseMutex(disp->_mutex); + ShowWindow(disp->_window,SW_HIDE); + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + return 0; + case WM_SIZE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam); + if (nw && nh && (nw!=disp->_width || nh!=disp->_height)) { + disp->_window_width = nw; + disp->_window_height = nh; + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_resized = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_MOVE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->_mutex,INFINITE); + const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam)); + if (nx!=disp->_window_x || ny!=disp->_window_y) { + disp->_window_x = nx; + disp->_window_y = ny; + disp->_is_moved = disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + } + ReleaseMutex(disp->_mutex); + } break; + case WM_PAINT : + disp->paint(); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + break; + case WM_ERASEBKGND : + // return 0; + break; + case WM_KEYDOWN : + disp->set_key((unsigned int)wParam); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_KEYUP : + disp->set_key((unsigned int)wParam,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MOUSEMOVE : { + while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {} + disp->_mouse_x = LOWORD(lParam); + disp->_mouse_y = HIWORD(lParam); +#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT) + if (!disp->_is_mouse_tracked) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = disp->_window; + if (TrackMouseEvent(&tme)) disp->_is_mouse_tracked = true; + } +#endif + if (disp->_mouse_x<0 || disp->_mouse_y<0 || disp->_mouse_x>=disp->width() || disp->_mouse_y>=disp->height()) + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_event = true; + SetEvent(cimg::Win32_attr().wait_event); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); + cimg_unlock_display(); + } break; + case WM_MOUSELEAVE : { + disp->_mouse_x = disp->_mouse_y = -1; + disp->_is_mouse_tracked = false; + cimg_lock_display(); + while (ShowCursor(TRUE)<0) {} + cimg_unlock_display(); + } break; + case WM_LBUTTONDOWN : + disp->set_button(1); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONDOWN : + disp->set_button(2); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONDOWN : + disp->set_button(3); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_LBUTTONUP : + disp->set_button(1,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_RBUTTONUP : + disp->set_button(2,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case WM_MBUTTONUP : + disp->set_button(3,false); + SetEvent(cimg::Win32_attr().wait_event); + break; + case 0x020A : // WM_MOUSEWHEEL: + disp->set_wheel((int)((short)HIWORD(wParam))/120); + SetEvent(cimg::Win32_attr().wait_event); + } + return DefWindowProc(window,msg,wParam,lParam); + } + + static DWORD WINAPI _events_thread(void* arg) { + CImgDisplay *const disp = (CImgDisplay*)(((void**)arg)[0]); + const char *const title = (const char*)(((void**)arg)[1]); + MSG msg; + delete[] (void**)arg; + disp->_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + disp->_bmi.bmiHeader.biWidth = disp->width(); + disp->_bmi.bmiHeader.biHeight = -disp->height(); + disp->_bmi.bmiHeader.biPlanes = 1; + disp->_bmi.bmiHeader.biBitCount = 32; + disp->_bmi.bmiHeader.biCompression = BI_RGB; + disp->_bmi.bmiHeader.biSizeImage = 0; + disp->_bmi.bmiHeader.biXPelsPerMeter = 1; + disp->_bmi.bmiHeader.biYPelsPerMeter = 1; + disp->_bmi.bmiHeader.biClrUsed = 0; + disp->_bmi.bmiHeader.biClrImportant = 0; + disp->_data = new unsigned int[(size_t)disp->_width*disp->_height]; + if (!disp->_is_fullscreen) { // Normal window + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)disp->_width - 1; rect.bottom = (LONG)disp->_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - disp->_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - disp->_height - border1); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_OVERLAPPEDWINDOW | (disp->_is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT, + disp->_width + 2*border1, disp->_height + border1 + border2, + 0,0,0,&(disp->_ccs)); + if (!disp->_is_closed) { + GetWindowRect(disp->_window,&rect); + disp->_window_x = rect.left + border1; + disp->_window_y = rect.top + border2; + } else disp->_window_x = disp->_window_y = 0; + } else { // Fullscreen window + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + disp->_window = CreateWindowA("MDICLIENT",title?title:" ", + WS_POPUP | (disp->_is_closed?0:WS_VISIBLE), + (sx - disp->_width)/2, + (sy - disp->_height)/2, + disp->_width,disp->_height,0,0,0,&(disp->_ccs)); + disp->_window_x = disp->_window_y = 0; + } + SetForegroundWindow(disp->_window); + disp->_hdc = GetDC(disp->_window); + disp->_window_width = disp->_width; + disp->_window_height = disp->_height; + disp->flush(); +#ifdef _WIN64 + SetWindowLongPtr(disp->_window,GWLP_USERDATA,(LONG_PTR)disp); + SetWindowLongPtr(disp->_window,GWLP_WNDPROC,(LONG_PTR)_handle_events); +#else + SetWindowLong(disp->_window,GWL_USERDATA,(LONG)disp); + SetWindowLong(disp->_window,GWL_WNDPROC,(LONG)_handle_events); +#endif + SetEvent(disp->_is_created); + while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg); + return 0; + } + + CImgDisplay& _update_window_pos() { + if (_is_closed) _window_x = _window_y = -1; + else { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_width - 1; rect.bottom = (LONG)_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 - _width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + GetWindowRect(_window,&rect); + _window_x = rect.left + border1; + _window_y = rect.top + border2; + } + return *this; + } + + void _init_fullscreen() { + _background_window = 0; + if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0; + else { + DEVMODE mode; + unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U; + for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) { + const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight; + if (nw>=_width && nh>=_height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) { + bestbpp = mode.dmBitsPerPel; + ibest = imode; + bw = nw; bh = nh; + } + } + if (bestbpp) { + _curr_mode.dmSize = sizeof(DEVMODE); _curr_mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&_curr_mode); + EnumDisplaySettings(0,ibest,&mode); + ChangeDisplaySettings(&mode,0); + } else _curr_mode.dmSize = 0; + + const unsigned int + sx = (unsigned int)screen_width(), + sy = (unsigned int)screen_height(); + if (sx!=_width || sy!=_height) { + CLIENTCREATESTRUCT background_ccs; + _background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs); + SetForegroundWindow(_background_window); + } + } + } + + void _desinit_fullscreen() { + if (!_is_fullscreen) return; + if (_background_window) DestroyWindow(_background_window); + _background_window = 0; + if (_curr_mode.dmSize) ChangeDisplaySettings(&_curr_mode,0); + _is_fullscreen = false; + } + + CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + + // Allocate space for window title + const char *const nptitle = ptitle?ptitle:""; + const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; + char *const tmp_title = s?new char[s]:0; + if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); + + // Destroy previous window if existing + if (!is_empty()) assign(); + + // Set display variables + _width = std::min(dimw,(unsigned int)screen_width()); + _height = std::min(dimh,(unsigned int)screen_height()); + _normalization = normalization_type<4?normalization_type:3; + _is_fullscreen = fullscreen_flag; + _window_x = _window_y = 0; + _is_closed = closed_flag; + _is_cursor_visible = true; + _is_mouse_tracked = false; + _title = tmp_title; + flush(); + if (_is_fullscreen) _init_fullscreen(); + + // Create event thread + void *const arg = (void*)(new void*[2]); + ((void**)arg)[0] = (void*)this; + ((void**)arg)[1] = (void*)_title; + _mutex = CreateMutex(0,FALSE,0); + _is_created = CreateEvent(0,FALSE,FALSE,0); + _thread = CreateThread(0,0,_events_thread,arg,0,0); + WaitForSingleObject(_is_created,INFINITE); + return *this; + } + + CImgDisplay& assign() { + if (is_empty()) return flush(); + DestroyWindow(_window); + TerminateThread(_thread,0); + delete[] _data; + delete[] _title; + _data = 0; + _title = 0; + if (_is_fullscreen) _desinit_fullscreen(); + _width = _height = _normalization = _window_width = _window_height = 0; + _window_x = _window_y = 0; + _is_fullscreen = false; + _is_closed = true; + _min = _max = 0; + _title = 0; + flush(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + _min = _max = 0; + std::memset(_data,0,sizeof(unsigned int)*_width*_height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + template + CImgDisplay& assign(const CImgList& list, const char *const title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, + (img._height - 1)/2, + (img._depth - 1)/2)); + _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); + if (_normalization==2) _min = (float)nimg.min_max(_max); + return display(nimg); + } + + CImgDisplay& assign(const CImgDisplay& disp) { + if (!disp) return assign(); + _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); + std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100), + tmpdimy = (nheight>0)?nheight:(-nheight*_height/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { + if (_window_width!=dimx || _window_height!=dimy) { + RECT rect; rect.left = rect.top = 0; rect.right = (LONG)dimx - 1; rect.bottom = (LONG)dimy - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int cwidth = rect.right - rect.left + 1, cheight = rect.bottom - rect.top + 1; + SetWindowPos(_window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS); + } + if (_width!=dimx || _height!=dimy) { + unsigned int *const ndata = new unsigned int[dimx*dimy]; + if (force_redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy); + else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy); + delete[] _data; + _data = ndata; + _bmi.bmiHeader.biWidth = (LONG)dimx; + _bmi.bmiHeader.biHeight = -(int)dimy; + _width = dimx; + _height = dimy; + } + _window_width = dimx; _window_height = dimy; + show(); + } + _is_resized = false; + if (_is_fullscreen) move((screen_width() - width())/2,(screen_height() - height())/2); + if (force_redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { + if (is_empty()) return *this; + if (force_redraw) { + const cimg_ulong buf_size = (cimg_ulong)_width*_height*4; + void *odata = std::malloc(buf_size); + if (odata) { + std::memcpy(odata,_data,buf_size); + assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + std::memcpy(_data,odata,buf_size); + std::free(odata); + } + return paint(); + } + return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty() || !_is_closed) return *this; + _is_closed = false; + if (_is_fullscreen) _init_fullscreen(); + ShowWindow(_window,SW_SHOW); + _update_window_pos(); + return paint(); + } + + CImgDisplay& close() { + if (is_empty() || _is_closed) return *this; + _is_closed = true; + if (_is_fullscreen) _desinit_fullscreen(); + ShowWindow(_window,SW_HIDE); + _window_x = _window_y = 0; + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (_window_x!=posx || _window_y!=posy) { + if (!_is_fullscreen) { + RECT rect; + rect.left = rect.top = 0; rect.right = (LONG)_window_width - 1; rect.bottom = (LONG)_window_height - 1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int + border1 = (int)((rect.right - rect.left + 1 -_width)/2), + border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); + SetWindowPos(_window,0,posx - border1,posy - border2,0,0,SWP_NOSIZE | SWP_NOZORDER); + } else SetWindowPos(_window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER); + _window_x = posx; + _window_y = posy; + show(); + } + _is_moved = false; + return *this; + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = true; + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + _is_cursor_visible = false; + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || _is_closed || posx<0 || posy<0) return *this; + _update_window_pos(); + const int res = (int)SetCursorPos(_window_x + posx,_window_y + posy); + if (res) { _mouse_x = posx; _mouse_y = posy; } + return *this; + } + + CImgDisplay& set_title(const char *const format, ...) { + if (is_empty()) return *this; + char *const tmp = new char[1024]; + va_list ap; + va_start(ap, format); + cimg_vsnprintf(tmp,1024,format,ap); + va_end(ap); + if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } + delete[] _title; + const unsigned int s = (unsigned int)std::strlen(tmp) + 1; + _title = new char[s]; + std::memcpy(_title,tmp,s*sizeof(char)); + SetWindowTextA(_window, tmp); + delete[] tmp; + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "display(): Empty specified image.", + cimgdisplay_instance); + if (is_empty()) return assign(img); + return render(img).paint(); + } + + CImgDisplay& paint() { + if (_is_closed) return *this; + WaitForSingleObject(_mutex,INFINITE); + SetDIBitsToDevice(_hdc,0,0,_width,_height,0,0,0,_height,_data,&_bmi,DIB_RGB_COLORS); + ReleaseMutex(_mutex); + return *this; + } + + template + CImgDisplay& render(const CImg& img) { + if (!img) + throw CImgArgumentException(_cimgdisplay_instance + "render(): Empty specified image.", + cimgdisplay_instance); + + if (is_empty()) return *this; + if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, + (img._depth - 1)/2)); + + const T + *data1 = img._data, + *data2 = (img._spectrum>=2)?img.data(0,0,0,1):data1, + *data3 = (img._spectrum>=3)?img.data(0,0,0,2):data1; + + WaitForSingleObject(_mutex,INFINITE); + unsigned int + *const ndata = (img._width==_width && img._height==_height)?_data: + new unsigned int[(size_t)img._width*img._height], + *ptrd = ndata; + + if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { + _min = _max = 0; + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)*(data1++), + G = (unsigned char)*(data2++), + B = (unsigned char)*(data3++); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } else { + if (_normalization==3) { + if (cimg::type::is_float()) _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.f); + switch (img._spectrum) { + case 1 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); + *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); + } + } break; + case 2 : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); + } + } break; + default : { + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned char + R = (unsigned char)((*(data1++) - _min)*mm), + G = (unsigned char)((*(data2++) - _min)*mm), + B = (unsigned char)((*(data3++) - _min)*mm); + *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); + } + } + } + } + if (ndata!=_data) { _render_resize(ndata,img._width,img._height,_data,_width,_height); delete[] ndata; } + ReleaseMutex(_mutex); + return *this; + } + + template + static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { + img.assign(); + HDC hScreen = GetDC(GetDesktopWindow()); + if (hScreen) { + const int + width = GetDeviceCaps(hScreen,HORZRES), + height = GetDeviceCaps(hScreen,VERTRES); + int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; + if (_x0>_x1) cimg::swap(_x0,_x1); + if (_y0>_y1) cimg::swap(_y0,_y1); + if (_x1>=0 && _x0=0 && _y0 + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) { img.assign(); return *this; } + const unsigned int *ptrs = _data; + img.assign(_width,_height,1,3); + T + *data1 = img.data(0,0,0,0), + *data2 = img.data(0,0,0,1), + *data3 = img.data(0,0,0,2); + for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { + const unsigned int val = *(ptrs++); + *(data1++) = (T)(unsigned char)(val>>16); + *(data2++) = (T)(unsigned char)((val>>8)&0xFF); + *(data3++) = (T)(unsigned char)(val&0xFF); + } + return *this; + } +#endif + + //@} + }; + + /* + #-------------------------------------- + # + # + # + # Definition of the CImg structure + # + # + # + #-------------------------------------- + */ + + //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T. + /** + This is the main class of the %CImg Library. It declares and constructs + an image, allows access to its pixel values, and is able to perform various image operations. + + \par Image representation + + A %CImg image is defined as an instance of the container \c CImg, which contains a regular grid of pixels, + each pixel value being of type \c T. The image grid can have up to 4 dimensions: width, height, depth + and number of channels. + Usually, the three first dimensions are used to describe spatial coordinates (x,y,z), + while the number of channels is rather used as a vector-valued dimension + (it may describe the R,G,B color channels for instance). + If you need a fifth dimension, you can use image lists \c CImgList 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, ...). + Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions. + + Concerning the pixel value type \c T: + fully supported template types are the basic C++ types: unsigned char, char, short, unsigned int, int, + unsigned long, long, float, double, ... . + Typically, fast image display can be done using CImg images, + while complex image processing algorithms may be rather coded using CImg or CImg + images that have floating-point pixel values. The default value for the template T is \c float. + Using your own template types may be possible. However, you will certainly have to define the complete set + of arithmetic and logical operators for your class. + + \par Image structure + + The \c CImg structure contains \e six fields: + - \c _width defines the number of \a columns of the image (size along the X-axis). + - \c _height defines the number of \a rows of the image (size along the Y-axis). + - \c _depth defines the number of \a slices of the image (size along the Z-axis). + - \c _spectrum defines the number of \a channels of the image (size along the C-axis). + - \c _data defines a \a pointer to the \a pixel \a data (of type \c T). + - \c _is_shared is a boolean that tells if the memory buffer \c data is shared with + another image. + + You can access these fields publicly although it is recommended to use the dedicated functions + width(), height(), depth(), spectrum() and ptr() to do so. + Image dimensions are not limited to a specific range (as long as you got enough available memory). + A value of \e 1 usually means that the corresponding dimension is \a flat. + If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty. + Empty images should not contain any pixel data and thus, will not be processed by CImg member functions + (a CImgInstanceException will be thrown instead). + Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage). + + \par Image declaration and construction + + Declaring an image can be done by using one of the several available constructors. + Here is a list of the most used: + + - Construct images from arbitrary dimensions: + - CImg img; declares an empty image. + - CImg img(128,128); declares a 128x128 greyscale image with + \c unsigned \c char pixel values. + - CImg img(3,3); declares a 3x3 matrix with \c double coefficients. + - CImg img(256,256,1,3); declares a 256x256x1x3 (color) image + (colors are stored as an image with three channels). + - CImg img(128,128,128); declares a 128x128x128 volumetric and greyscale image + (with \c double pixel values). + - CImg<> img(128,128,128,3); declares a 128x128x128 volumetric color image + (with \c float pixels, which is the default value of the template parameter \c T). + - \b Note: images pixels are not automatically initialized to 0. You may use the function \c fill() to + do it, or use the specific constructor taking 5 parameters like this: + CImg<> img(128,128,128,3,0); declares a 128x128x128 volumetric color image with all pixel values to 0. + + - Construct images from filenames: + - CImg img("image.jpg"); reads a JPEG color image from the file "image.jpg". + - CImg img("analyze.hdr"); reads a volumetric image (ANALYZE7.5 format) from the + file "analyze.hdr". + - \b Note: You need to install ImageMagick + to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io). + + - Construct images from C-style arrays: + - CImg img(data_buffer,256,256); constructs a 256x256 greyscale image from a \c int* buffer + \c data_buffer (of size 256x256=65536). + - CImg img(data_buffer,256,256,1,3); constructs a 256x256 color image + from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others). + + The complete list of constructors can be found here. + + \par Most useful functions + + The \c CImg class contains a lot of functions that operates on images. + Some of the most useful are: + + - operator()(): Read or write pixel values. + - display(): displays the image in a new window. + **/ + template + struct CImg { + + unsigned int _width, _height, _depth, _spectrum; + bool _is_shared; + T *_data; + + //! Simple iterator type, to loop through each pixel value of an image instance. + /** + \note + - The \c CImg::iterator type is defined to be a T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + CImg img("reference.jpg"); // Load image from file + // Set all pixels to '0', with a CImg iterator. + for (CImg::iterator it = img.begin(), it::const_iterator type is defined to be a \c const \c T*. + - You will seldom have to use iterators in %CImg, most classical operations + being achieved (often in a faster way) using methods of \c CImg. + \par Example + \code + 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::value_type type of a \c CImg is defined to be a \c T. + - \c CImg::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimg_plugin +#include cimg_plugin +#endif +#ifdef cimg_plugin1 +#include cimg_plugin1 +#endif +#ifdef cimg_plugin2 +#include cimg_plugin2 +#endif +#ifdef cimg_plugin3 +#include cimg_plugin3 +#endif +#ifdef cimg_plugin4 +#include cimg_plugin4 +#endif +#ifdef cimg_plugin5 +#include cimg_plugin5 +#endif +#ifdef cimg_plugin6 +#include cimg_plugin6 +#endif +#ifdef cimg_plugin7 +#include cimg_plugin7 +#endif +#ifdef cimg_plugin8 +#include cimg_plugin8 +#endif + + //@} + //--------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //--------------------------------------------------------- + + //! Destroy image. + /** + \note + - The pixel buffer data() is deallocated if necessary, e.g. for non-empty and non-shared image instances. + - Destroying an empty or shared image does nothing actually. + \warning + - When destroying a non-shared image, make sure that you will \e not operate on a remaining shared image + that shares its buffer with the destroyed instance, in order to avoid further invalid memory access + (to a deallocated buffer). + **/ + ~CImg() { + if (!_is_shared) delete[] _data; + } + + //! Construct empty image. + /** + \note + - An empty image has no pixel data and all of its dimensions width(), height(), depth(), spectrum() + are set to \c 0, as well as its pixel buffer pointer data(). + - An empty image may be re-assigned afterwards, e.g. with the family of + assign(unsigned int,unsigned int,unsigned int,unsigned int) methods, + or by operator=(const CImg&). In all cases, the type of pixels stays \c T. + - 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 + \endcode + **/ + CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {} + + //! Construct image with specified size. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \note + - It is able to create only \e non-shared images, and allocates thus a pixel buffer data() + for each constructed image instance. + - Setting one dimension \c size_x,\c size_y,\c size_z or \c size_c to \c 0 leads to the construction of + an \e empty image. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values during construction (e.g. with \c 0), use constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead. + \par Example + \code + CImg 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; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values. + /** + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value Initialization value. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), + but it also fills the pixel buffer with the specified \c value. + \warning + - It cannot be used to construct a vector-valued image and initialize it with \e vector-valued pixels + (e.g. RGB vector, for color images). + For this task, you may use fillC() after construction. + **/ + CImg(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value): + _is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(value); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified sequence of integers \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be an \e integer). + \param value1 Second value of the initialization sequence (must be an \e integer). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \warning + - You must specify \e exactly \c size_x*\c size_y*\c size_z*\c size_c integers in the initialization sequence. + Otherwise, the constructor may crash or fill your image pixels with garbage. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _CImg_stdarg(img,a0,a1,N,t) { \ + size_t _siz = (size_t)N; \ + if (_siz--) { \ + va_list ap; \ + va_start(ap,a1); \ + T *ptrd = (img)._data; \ + *(ptrd++) = (T)a0; \ + if (_siz--) { \ + *(ptrd++) = (T)a1; \ + for ( ; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \ + } \ + va_end(ap); \ + } \ + } + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + } + +#if cimg_use_cpp11==1 + //! Construct image with specified size and initialize pixel values from an initializer list of integers. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... } + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param { value0, value1, ... } Initialization list + \param repeat_values Tells if the value filling process is repeated over the image. + + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image + { 0,255,0,255, // Set the 4 values for the red component + 0,0,255,255, // Set the 4 values for the green component + 64,64,64,64 }); // Set the 4 values for the blue component + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { +#define _cimg_constructor_cpp11(repeat_values) \ + auto it = values.begin(); \ + size_t siz = size(); \ + if (repeat_values) for (T *ptrd = _data; siz--; ) { \ + *(ptrd++) = (T)(*(it++)); if (it==values.end()) it = values.begin(); } \ + else { siz = std::min(siz,values.size()); for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); } + assign(size_x,size_y,size_z,size_c); + _cimg_constructor_cpp11(repeat_values); + } + + template + 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): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, const unsigned int size_y, + std::initializer_list values, + const bool repeat_values=true): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y); + _cimg_constructor_cpp11(repeat_values); + } + + template + CImg(const unsigned int size_x, + std::initializer_list values, + const bool repeat_values=true):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x); + _cimg_constructor_cpp11(repeat_values); + } + + //! Construct single channel 1D image with pixel values and width obtained from an initializer list of integers. + /** + Construct a new image instance of size \c width x \c 1 x \c 1 x \c 1, + with pixels of type \c T, and initialize pixel + values from the specified initializer list of integers { \c value0,\c value1,\c ... }. Image width is + given by the size of the initializer list. + \param { value0, value1, ... } Initialization list + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int) with height=1, depth=1, and spectrum=1, + but it also fills the pixel buffer with a sequence of specified integer values. + \par Example + \code + const CImg img = {10,20,30,20,10 }; // Construct a 5x1 image with one channel, and set its pixel values + img.resize(150,150).display(); + \endcode + \image html ref_constructor1.jpg + **/ + template + CImg(const std::initializer_list values): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(values.size(),1,1,1); + auto it = values.begin(); + unsigned int siz = _width; + for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); + } + + template + CImg & operator=(std::initializer_list values) { + _cimg_constructor_cpp11(siz>values.size()); + return *this; + } +#endif + + //! Construct image with specified size and initialize pixel values from a sequence of doubles. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initialize pixel values from the specified sequence of doubles \c value0,\c value1,\c ... + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param value0 First value of the initialization sequence (must be a \e double). + \param value1 Second value of the initialization sequence (must be a \e double). + \param ... + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...), but + takes a sequence of double values instead of integers. + \warning + - You must specify \e exactly \c dx*\c dy*\c dz*\c dc doubles in the initialization sequence. + Otherwise, the constructor may crash or fill your image with garbage. + For instance, the code below will probably crash on most platforms: + \code + const CImg img(2,2,1,1, 0.5,0.5,255,255); // FAIL: The two last arguments are 'int', not 'double'! + \endcode + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + } + + //! Construct image with specified size and initialize pixel values from a value string. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified string \c values. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param values Value string describing the way pixel values are set. + \param repeat_values Tells if the value filling process is repeated over the image. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills + the pixel buffer with values described in the value string \c values. + - Value string \c values may describe two different filling processes: + - Either \c values is a sequences of values assigned to the image pixels, as in "1,2,3,7,8,2". + In this case, set \c repeat_values to \c true to periodically fill the image with the value sequence. + - Either, \c values is a formula, as in "cos(x/10)*sin(y/20)". + In this case, parameter \c repeat_values is pointless. + - For both cases, specifying \c repeat_values is mandatory. + It disambiguates the possible overloading of constructor + CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) with \c T being a const char*. + - 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 + (img1,img2).display(); + \endcode + \image html ref_constructor2.jpg + **/ + CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values):_is_shared(false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + fill(values,repeat_values); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer. + /** + Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, + and initializes pixel values from the specified \c t* memory buffer. + \param values Pointer to the input memory buffer. + \param size_x Image width(). + \param size_y Image height(). + \param size_z Image depth(). + \param size_c Image spectrum() (number of channels). + \param is_shared Tells if input memory buffer must be shared by the current instance. + \note + - If \c is_shared is \c false, the image instance allocates its own pixel buffer, + and values from the specified input buffer are copied to the instance buffer. + If buffer types \c T and \c t are different, a regular static cast is performed during buffer copy. + - Otherwise, the image instance does \e not allocate a new buffer, and uses the input memory buffer as its + own pixel buffer. This case requires that types \c T and \c t are the same. Later, destroying such a shared + image will not deallocate the pixel buffer, this task being obviously charged to the initial buffer allocator. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. when requested size is too big for available memory). + \warning + - You must take care when operating on a shared image, since it may have an invalid pixel buffer pointer data() + (e.g. already deallocated). + \par Example + \code + unsigned char tab[256*256] = { 0 }; + CImg 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 + CImg(const t *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a (%u,%u,%u,%u) shared instance " + "from a (%s*) buffer (pixel types are different).", + cimg_instance, + size_x,size_y,size_z,size_c,CImg::pixel_type()); + } + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + + } + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (values && siz) { + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = is_shared; + if (_is_shared) _data = const_cast(values); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy(_data,values,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image from reading an image file. + /** + Construct a new image instance with pixels of type \c T, and initialize pixel values with the data read from + an image file. + \param filename Filename, as a C-string. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it reads the image + dimensions and pixel values from the specified image file. + - The recognition of the image file format by %CImg higly depends on the tools installed on your system + and on the external libraries you used to link your code against. + - Considered pixel type \c T should better fit the file format specification, or data loss may occur during + file load (e.g. constructing a \c CImg from a float-valued image file). + - A \c CImgIOException is thrown when the specified \c filename cannot be read, or if the file format is not + recognized. + \par Example + \code + const CImg img("reference.jpg"); + img.display(); + \endcode + \image html ref_image.jpg + **/ + explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(filename); + } + + //! Construct image copy. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance. + \param img Input image to copy. + \note + - Constructed copy has the same size width() x height() x depth() x spectrum() and pixel values as the + input image \c img. + - If input image \c img is \e shared and if types \c T and \c t are the same, the constructed copy is also + \e shared, and shares its pixel buffer with \c img. + Modifying a pixel value in the constructed copy will thus also modifies it in the input image \c img. + This behavior is needful to allow functions to return shared images. + - Otherwise, the constructed copy allocates its own pixel buffer, and copies pixel values from the input + image \c img into its buffer. The copied pixel values may be eventually statically casted if types \c T and + \c t are different. + - Constructing a copy from an image \c img when types \c t and \c T are the same is significantly faster than + with different types. + - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated + (e.g. not enough available memory). + **/ + template + CImg(const CImg& img):_is_shared(false) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Construct image copy \specialization. + CImg(const CImg& img) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = img._is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Advanced copy constructor. + /** + Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance, + while forcing the shared state of the constructed copy. + \param img Input image to copy. + \param is_shared Tells about the shared state of the constructed copy. + \note + - Similar to CImg(const CImg&), except that it allows to decide the shared state of + the constructed image, which does not depend anymore on the shared state of the input image \c img: + - If \c is_shared is \c true, the constructed copy will share its pixel buffer with the input image \c img. + For that case, the pixel types \c T and \c t \e must be the same. + - If \c is_shared is \c false, the constructed copy will allocate its own pixel buffer, whether the input + image \c img is shared or not. + - A \c CImgArgumentException is thrown when a shared copy is requested with different pixel types \c T and \c t. + **/ + template + CImg(const CImg& img, const bool is_shared):_is_shared(false) { + if (is_shared) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid construction request of a shared instance from a " + "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).", + cimg_instance, + CImg::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data); + } + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + } else { _width = _height = _depth = _spectrum = 0; _data = 0; } + } + + //! Advanced copy constructor \specialization. + CImg(const CImg& img, const bool is_shared) { + const size_t siz = (size_t)img.size(); + if (img._data && siz) { + _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; + _is_shared = is_shared; + if (_is_shared) _data = const_cast(img._data); + else { + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), + img._width,img._height,img._depth,img._spectrum); + } + std::memcpy(_data,img._data,siz*sizeof(T)); + } + } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } + } + + //! Construct image with dimensions borrowed from another image. + /** + Construct a new image instance with pixels of type \c T, and size get from some dimensions of an existing + \c CImg instance. + \param img Input image from which dimensions are borrowed. + \param dimensions C-string describing the image size along the X,Y,Z and C-dimensions. + \note + - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it takes the image dimensions + (\e not its pixel values) from an existing \c CImg instance. + - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. + In order to initialize pixel values (e.g. with \c 0), use constructor CImg(const CImg&,const char*,T) + 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') + \endcode + **/ + template + CImg(const CImg& img, const char *const dimensions): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values. + /** + Construct a new image instance with pixels of type \c T, and size get from the dimensions of an existing + \c CImg instance, and set all pixel values to specified \c value. + \param img Input image from which dimensions are borrowed. + \param dimensions String describing the image size along the X,Y,Z and V-dimensions. + \param value Value used for initialization. + \note + - Similar to CImg(const CImg&,const char*), but it also fills the pixel buffer with the specified \c value. + **/ + template + CImg(const CImg& img, const char *const dimensions, const T& value): + _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + assign(img,dimensions).fill(value); + } + + //! Construct image from a display window. + /** + Construct a new image instance with pixels of type \c T, as a snapshot of an existing \c CImgDisplay instance. + \param disp Input display window. + \note + - The width() and height() of the constructed image instance are the same as the specified \c CImgDisplay. + - The depth() and spectrum() of the constructed image instance are respectively set to \c 1 and \c 3 + (i.e. a 2D color image). + - The image pixels are read as 8-bits RGB values. + **/ + explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { + disp.snapshot(*this); + } + + // Constructor and assignment operator for rvalue references (c++11). + // This avoids an additional image copy for methods returning new images. Can save RAM for big images ! +#if cimg_use_cpp11==1 + CImg(CImg&& 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); + } +#endif + + //! Construct empty image \inplace. + /** + In-place version of the default constructor CImg(). It simply resets the instance to an empty image. + **/ + CImg& assign() { + if (!_is_shared) delete[] _data; + _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; + return *this; + } + + //! Construct image with specified size \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (siz!=curr_siz) { + if (_is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignement request of shared instance from specified " + "image (%u,%u,%u,%u).", + cimg_instance, + size_x,size_y,size_z,size_c); + else { + delete[] _data; + try { _data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + } + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + return *this; + } + + //! Construct image with specified size and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,T). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const T& value) { + return assign(size_x,size_y,size_z,size_c).fill(value); + } + + //! Construct image with specified size and initialize pixel values from a sequence of integers \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const int value0, const int value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a sequence of doubles \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const double value0, const double value1, ...) { + assign(size_x,size_y,size_z,size_c); + _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a value string \inplace. + /** + In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool). + **/ + CImg& assign(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, + const char *const values, const bool repeat_values) { + return assign(size_x,size_y,size_z,size_c).fill(values,repeat_values); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \inplace. + /** + In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int). + **/ + template + 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; + if (!values || !siz) return assign(); + assign(size_x,size_y,size_z,size_c); + const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + const size_t curr_siz = (size_t)size(); + if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c); + if (_is_shared || values + siz<_data || values>=_data + size()) { + assign(size_x,size_y,size_z,size_c); + if (_is_shared) std::memmove((void*)_data,(void*)values,siz*sizeof(T)); + else std::memcpy((void*)_data,(void*)values,siz*sizeof(T)); + } else { + T *new_data = 0; + try { new_data = new T[siz]; } catch (...) { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgInstanceException(_cimg_instance + "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", + cimg_instance, + cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), + size_x,size_y,size_z,size_c); + } + std::memcpy((void*)new_data,(void*)values,siz*sizeof(T)); + delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; + } + return *this; + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + template + 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) { + if (is_shared) + throw CImgArgumentException(_cimg_instance + "assign(): Invalid assignment request of shared instance from (%s*) buffer" + "(pixel types are different).", + cimg_instance, + CImg::pixel_type()); + return assign(values,size_x,size_y,size_z,size_c); + } + + //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. + CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c, const bool is_shared) { + const size_t siz = (size_t)size_x*size_y*size_z*size_c; + if (!values || !siz) return assign(); + if (!is_shared) { if (_is_shared) assign(); assign(values,size_x,size_y,size_z,size_c); } + else { + if (!_is_shared) { + if (values + siz<_data || values>=_data + size()) assign(); + else cimg::warn(_cimg_instance + "assign(): Shared image instance has overlapping memory.", + cimg_instance); + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true; + _data = const_cast(values); + } + return *this; + } + + //! Construct image from reading an image file \inplace. + /** + In-place version of the constructor CImg(const char*). + **/ + CImg& assign(const char *const filename) { + return load(filename); + } + + //! Construct image copy \inplace. + /** + In-place version of the constructor CImg(const CImg&). + **/ + template + CImg& assign(const CImg& img) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum); + } + + //! In-place version of the advanced copy constructor. + /** + In-place version of the constructor CImg(const CImg&,bool). + **/ + template + CImg& assign(const CImg& img, const bool is_shared) { + return assign(img._data,img._width,img._height,img._depth,img._spectrum,is_shared); + } + + //! Construct image with dimensions borrowed from another image \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions) { + if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum); + unsigned int siz[4] = { 0,1,1,1 }, k = 0; + CImg item(256); + for (const char *s = dimensions; *s && k<4; ++k) { + if (cimg_sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item._data)>0) s+=std::strlen(item); + if (*s) { + unsigned int val = 0; char sep = 0; + if (cimg_sscanf(s,"%u%c",&val,&sep)>0) { + if (sep=='%') siz[k] = val*(k==0?_width:k==1?_height:k==2?_depth:_spectrum)/100; + else siz[k] = val; + while (*s>='0' && *s<='9') ++s; + if (sep=='%') ++s; + } else switch (cimg::lowercase(*s)) { + case 'x' : case 'w' : siz[k] = img._width; ++s; break; + case 'y' : case 'h' : siz[k] = img._height; ++s; break; + case 'z' : case 'd' : siz[k] = img._depth; ++s; break; + case 'c' : case 's' : siz[k] = img._spectrum; ++s; break; + default : + throw CImgArgumentException(_cimg_instance + "assign(): Invalid character '%c' detected in specified dimension string '%s'.", + cimg_instance, + *s,dimensions); + } + } + } + return assign(siz[0],siz[1],siz[2],siz[3]); + } + + //! Construct image with dimensions borrowed from another image and initialize pixel values \inplace. + /** + In-place version of the constructor CImg(const CImg&,const char*,T). + **/ + template + CImg& assign(const CImg& img, const char *const dimensions, const T& value) { + return assign(img,dimensions).fill(value); + } + + //! Construct image from a display window \inplace. + /** + In-place version of the constructor CImg(const CImgDisplay&). + **/ + CImg& assign(const CImgDisplay &disp) { + disp.snapshot(*this); + return *this; + } + + //! Construct empty image \inplace. + /** + Equivalent to assign(). + \note + - It has been defined for compatibility with STL naming conventions. + **/ + CImg& clear() { + return assign(); + } + + //! Transfer content of an image instance into another one. + /** + Transfer the dimensions and the pixel buffer content of an image instance into another one, + and replace instance by an empty image. It avoids the copy of the pixel buffer + when possible. + \param img Destination image. + \note + - Pixel types \c T and \c t of source and destination images can be different, though the process is + designed to be instantaneous when \c T and \c t are the same. + \par Example + \code + CImg 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 + CImg& move_to(CImg& img) { + img.assign(*this); + assign(); + return img; + } + + //! Transfer content of an image instance into another one \specialization. + CImg& move_to(CImg& img) { + if (_is_shared || img._is_shared) img.assign(*this); + else swap(img); + assign(); + return img; + } + + //! Transfer content of an image instance into a new image in an image list. + /** + Transfer the dimensions and the pixel buffer content of an image instance + into a newly inserted image at position \c pos in specified \c CImgList instance. + \param list Destination list. + \param pos Position of the newly inserted image in the list. + \note + - When optional parameter \c pos is ommited, the image instance is transfered as a new + image at the end of the specified \c list. + - It is convenient to sequentially insert new images into image lists, with no + additional copies of memory buffer. + \par Example + \code + CImgList 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 + CImgList& move_to(CImgList& list, const unsigned int pos=~0U) { + const unsigned int npos = pos>list._width?list._width:pos; + move_to(list.insert(1,npos)[npos]); + return list; + } + + //! Swap fields of two image instances. + /** + \param img Image to swap fields with. + \note + - It can be used to interchange the content of two images in a very fast way. Can be convenient when dealing + with algorithms requiring two swapping buffers. + \par Example + \code + CImg img1("lena.jpg"), + img2("milla.jpg"); + img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena' + \endcode + **/ + CImg& swap(CImg& img) { + cimg::swap(_width,img._width,_height,img._height,_depth,img._depth,_spectrum,img._spectrum); + cimg::swap(_data,img._data); + cimg::swap(_is_shared,img._is_shared); + return img; + } + + //! Return a reference to an empty image. + /** + \note + This function is useful mainly to declare optional parameters having type \c CImg in functions prototypes, + e.g. + \code + void f(const int x=0, const int y=0, const CImg& img=CImg::empty()); + \endcode + **/ + static CImg& empty() { + static CImg _empty; + return _empty.assign(); + } + + //! Return a reference to an empty image \const. + static const CImg& const_empty() { + static const CImg _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Access to a pixel value. + /** + Return a reference to a located pixel value of the image instance, + being possibly \e const, whether the image instance is \e const or not. + This is the standard method to get/set pixel values in \c CImg images. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Range of pixel coordinates start from (0,0,0,0) to + (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 + 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 + 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' + const float + valR = img(10,10,0,0), // Read red value at coordinates (10,10) + valG = img(10,10,0,1), // Read green value at coordinates (10,10) + valB = img(10,10,2), // Read blue value at coordinates (10,10) (Z-coordinate can be omitted) + avg = (valR + valG + valB)/3; // Compute average pixel value + img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the color pixel (10,10) by the average grey value + \endcode + **/ +#if cimg_verbosity>=3 + T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (!_data || off>=size()) { + cimg::warn(_cimg_instance + "operator(): Invalid pixel request, at coordinates (%d,%d,%d,%d) [offset=%u].", + cimg_instance, + (int)x,(int)y,(int)z,(int)c,off); + return *_data; + } + else return _data[off]; + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->operator()(x,y,z,c); + } + + //! Access to a pixel value. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param wh Precomputed offset, must be equal to width()*\ref height(). + \param whd Precomputed offset, must be equal to width()*\ref height()*\ref depth(). + \note + - Similar to (but faster than) operator()(). + It uses precomputed offsets to optimize memory access. You may use it to optimize + the reading/writing of several pixel values in the same image (e.g. in a loop). + **/ + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } + + //! Access to a pixel value \const. + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd=0) const { + cimg::unused(wh,whd); + return (*this)(x,y,z,c); + } +#else + T& operator()(const unsigned int x) { + return _data[x]; + } + + const T& operator()(const unsigned int x) const { + return _data[x]; + } + + T& operator()(const unsigned int x, const unsigned int y) { + return _data[x + y*_width]; + } + + const T& operator()(const unsigned int x, const unsigned int y) const { + return _data[x + y*_width]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) const { + return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) { + return _data[x + y*_width + z*wh]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, + const ulongT wh) const { + return _data[x + y*_width + z*wh]; + } + + T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) { + return _data[x + y*_width + z*wh + c*whd]; + } + + const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, + const ulongT wh, const ulongT whd) const { + return _data[x + y*_width + z*wh + c*whd]; + } +#endif + + //! Implicitely cast an image into a \c T*. + /** + Implicitely cast a \c CImg 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 + std::printf("'img1' is not empty, 'img2' is empty."); + } + } + \endcode + - It also allows to use brackets to access pixel values, without need for a \c CImg::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) + \endcode + **/ + operator T*() { + return _data; + } + + //! Implicitely cast an image into a \c T* \const. + operator const T*() const { + return _data; + } + + //! Assign a value to all image pixels. + /** + Assign specified \c value to each pixel value of the image instance. + \param value Value that will be assigned to image pixels. + \note + - The image size is never modified. + - The \c value may be casted to pixel type \c T if necessary. + \par Example + \code + CImg 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) { + return fill(value); + } + + //! Assign pixels values from a specified expression. + /** + Initialize all pixel values from the specified string \c expression. + \param expression Value string describing the way pixel values are set. + \note + - String parameter \c expression may describe different things: + - If \c expression is a list of values (as in \c "1,2,3,8,3,2"), or a formula (as in \c "(x*y)%255"), + the pixel values are set from specified \c expression and the image size is not modified. + - If \c expression is a filename (as in \c "reference.jpg"), the corresponding image file is loaded and + replace the image instance. The image size is modified if necessary. + \par Example + \code + CImg img1(100,100), img2(img1), img3(img1); // Declare 3 scalar images 100x100 with unitialized values + img1 = "0,50,100,150,200,250,200,150,100,50"; // Set pixel values of 'img1' from a value sequence + img2 = "10*((x*y)%25)"; // Set pixel values of 'img2' from a formula + img3 = "reference.jpg"; // Set pixel values of 'img3' from a file (image size is modified) + (img1,img2,img3).display(); + \endcode + \image html ref_operator_eq.jpg + **/ + CImg& operator=(const char *const expression) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { + _fill(expression,true,1,0,0,"operator=",0); + } catch (CImgException&) { + cimg::exception_mode(omode); + load(expression); + } + cimg::exception_mode(omode); + return *this; + } + + //! Copy an image into the current image instance. + /** + Similar to the in-place copy constructor assign(const CImg&). + **/ + template + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy an image into the current image instance \specialization. + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Copy the content of a display window to the current image instance. + /** + Similar to assign(const CImgDisplay&). + **/ + CImg& operator=(const CImgDisplay& disp) { + disp.snapshot(*this); + return *this; + } + + //! In-place addition operator. + /** + Add specified \c value to all pixels of an image instance. + \param value Value to add. + \note + - Resulting pixel values are casted to fit the pixel type \c T. + For instance, adding \c 0.2 to a \c CImg 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 + // 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 + (img1,img2,img3).display(); + \endcode + \image html ref_operator_plus.jpg + **/ + template + CImg& operator+=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + value,524288); + return *this; + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the specified string \c expression. + \param expression Value string describing the way pixel values are added. + \note + - Similar to operator=(const char*), except that it adds values to the pixels of the current image instance, + instead of assigning them. + **/ + CImg& operator+=(const char *const expression) { + return *this+=(+*this)._fill(expression,true,1,0,0,"operator+=",this); + } + + //! In-place addition operator. + /** + Add values to image pixels, according to the values of the input image \c img. + \param img Input image to add. + \note + - The size of the image instance is never modified. + - It is not mandatory that input image \c img has the same size as the image instance. + If less values are available in \c img, then the values are added periodically. For instance, adding one + WxH scalar image (spectrum() equal to \c 1) to one WxH color image (spectrum() equal to \c 3) + means each color channel will be incremented with the same values at the same locations. + \par Example + \code + CImg 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 + (img2,img1).display(); + \endcode + \image html ref_operator_plus1.jpg + **/ + template + CImg& operator+=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this+=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator++() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr + 1,524288); + return *this; + } + + //! In-place increment operator (postfix). + /** + Add \c 1 to all image pixels, and return a new copy of the initial (pre-incremented) image instance. + \note + - Use the prefixed version operator++() if you don't need a copy of the initial + (pre-incremented) image instance, since a useless image copy may be expensive in terms of memory usage. + **/ + CImg operator++(int) { + const CImg copy(*this,false); + ++*this; + return copy; + } + + //! Return a non-shared copy of the image instance. + /** + \note + - Use this operator to ensure you get a non-shared copy of an image instance with same pixel type \c T. + Indeed, the usual copy constructor CImg(const CImg&) returns a shared copy of a shared input image, + and it may be not desirable to work on a regular copy (e.g. for a resize operation) if you have no + information about the shared state of the input image. + - Writing \c (+img) is equivalent to \c CImg(img,false). + **/ + CImg operator+() const { + return CImg(*this,false); + } + + //! Addition operator. + /** + Similar to operator+=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator+(const t value) const { + return CImg<_cimg_Tt>(*this,false)+=value; + } + + //! Addition operator. + /** + Similar to operator+=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator+(const char *const expression) const { + return CImg(*this,false)+=expression; + } + + //! Addition 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. + **/ + template + CImg<_cimg_Tt> operator+(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)+=img; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const t), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - value,524288); + return *this; + } + + //! In-place substraction operator. + /** + Similar to operator+=(const char*), except that it performs a substraction instead of an addition. + **/ + CImg& operator-=(const char *const expression) { + return *this-=(+*this)._fill(expression,true,1,0,0,"operator-=",this); + } + + //! In-place substraction operator. + /** + Similar to operator+=(const CImg&), except that it performs a substraction instead of an addition. + **/ + template + CImg& operator-=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this-=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator--() { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr - 1,524288); + return *this; + } + + //! In-place decrement operator (postfix). + /** + Similar to operator++(int), except that it performs a decrement instead of an increment. + **/ + CImg operator--(int) { + const CImg copy(*this,false); + --*this; + return copy; + } + + //! Replace each pixel by its opposite value. + /** + \note + - If the computed opposite values are out-of-range, they are treated as with standard C++ numeric types. + For instance, the \c unsigned \c char opposite of \c 1 is \c 255. + \par Example + \code + const CImg + img1("reference.jpg"), // Load a RGB color image + img2 = -img1; // Compute its opposite (in 'unsigned char') + (img1,img2).display(); + \endcode + \image html ref_operator_minus.jpg + **/ + CImg operator-() const { + return CImg(_width,_height,_depth,_spectrum,(T)0)-=*this; + } + + //! Substraction operator. + /** + Similar to operator-=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator-(const t value) const { + return CImg<_cimg_Tt>(*this,false)-=value; + } + + //! Substraction operator. + /** + Similar to operator-=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator-(const char *const expression) const { + return CImg(*this,false)-=expression; + } + + //! Substraction 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. + **/ + template + CImg<_cimg_Tt> operator-(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)-=img; + } + + //! In-place multiplication operator. + /** + Similar to operator+=(const t), except that it performs a multiplication instead of an addition. + **/ + template + CImg& operator*=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr * value,262144); + return *this; + } + + //! In-place multiplication operator. + /** + 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,1,0,0,"operator*=",this)); + } + + //! In-place multiplication operator. + /** + Replace the image instance by the matrix multiplication between the image instance and the specified matrix + \c img. + \param img Second operand of the matrix multiplication. + \note + - It does \e not compute a pointwise multiplication between two images. For this purpose, use + mul(const CImg&) instead. + - 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' + // 'A' is now a 1x2 vector whose values are [5;11]. + \endcode + **/ + template + CImg& operator*=(const CImg& img) { + return ((*this)*img).move_to(*this); + } + + //! Multiplication operator. + /** + Similar to operator*=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator*(const t value) const { + return CImg<_cimg_Tt>(*this,false)*=value; + } + + //! Multiplication operator. + /** + Similar to operator*=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator*(const char *const expression) const { + return CImg(*this,false)*=expression; + } + + //! Multiplication 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. + **/ + 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)", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + 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_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 *ps0 = img.data(), *ps1 = img.data(0,1); + Tt *pd0 = res.data(), *pd1 = res.data(0,1); + const Ttdouble + a0 = (Ttdouble)_data[0], a1 = (Ttdouble)_data[1], + a2 = (Ttdouble)_data[2], a3 = (Ttdouble)_data[3]; + cimg_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++); + *(pd0++) = (Tt)(a0*x + a1*y); + *(pd1++) = (Tt)(a2*x + a3*y); + } + return res; + } + case 3 : { // 3x3_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *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_forX(img,i) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z); + *(pd1++) = (Tt)(a3*x + a4*y + a5*z); + *(pd2++) = (Tt)(a6*x + a7*y + a8*z); + } + return res; + } + case 4 : { // 4x4_matrix * Matrix + const t *ps0 = img.data(), *ps1 = img.data(0,1), *ps2 = img.data(0,2), *ps3 = img.data(0,3); + Tt *pd0 = res.data(), *pd1 = res.data(0,1), *pd2 = res.data(0,2), *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_forX(img,col) { + const Ttdouble x = (Ttdouble)*(ps0++), y = (Ttdouble)*(ps1++), z = (Ttdouble)*(ps2++), + c = (Ttdouble)*(ps3++); + *(pd0++) = (Tt)(a0*x + a1*y + a2*z + a3*c); + *(pd1++) = (Tt)(a4*x + a5*y + a6*z + a7*c); + *(pd2++) = (Tt)(a8*x + a9*y + a10*z + a11*c); + *(pd3++) = (Tt)(a12*x + a13*y + a14*z + a15*c); + } + return res; + } + } + } + + // Fallback to generic version. +#ifdef cimg_use_openmp + 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 + Tt *ptrd = res._data; + cimg_forXY(res,i,j) { + Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); *(ptrd++) = (Tt)value; + } +#endif + return res; + } + + //! In-place division operator. + /** + Similar to operator+=(const t), except that it performs a division instead of an addition. + **/ + template + CImg& operator/=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,*ptr / value,32768); + return *this; + } + + //! In-place division operator. + /** + 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,1,0,0,"operator/=",this)); + } + + //! In-place division operator. + /** + Replace the image instance by the (right) matrix division between the image instance and the specified + matrix \c img. + \param img Second operand of the matrix division. + \note + - It does \e not compute a pointwise division between two images. For this purpose, use + div(const CImg&) instead. + - It returns the matrix operation \c A*inverse(img). + - The size of the image instance can be modified by this operator. + **/ + template + CImg& operator/=(const CImg& img) { + return (*this*img.get_invert()).move_to(*this); + } + + //! Division operator. + /** + Similar to operator/=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + template + CImg<_cimg_Tt> operator/(const t value) const { + return CImg<_cimg_Tt>(*this,false)/=value; + } + + //! Division operator. + /** + Similar to operator/=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator/(const char *const expression) const { + return CImg(*this,false)/=expression; + } + + //! Division 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. + **/ + template + CImg<_cimg_Tt> operator/(const CImg& img) const { + return (*this)*img.get_invert(); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const t), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::mod(*ptr,(T)value),16384); + return *this; + } + + //! In-place modulo operator. + /** + 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,1,0,0,"operator%=",this); + } + + //! In-place modulo operator. + /** + Similar to operator+=(const CImg&), except that it performs a modulo operation instead of an addition. + **/ + template + CImg& operator%=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this%=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> operator%(const t value) const { + return CImg<_cimg_Tt>(*this,false)%=value; + } + + //! Modulo operator. + /** + Similar to operator%=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. + **/ + CImg operator%(const char *const expression) const { + return CImg(*this,false)%=expression; + } + + //! Modulo 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. + **/ + template + CImg<_cimg_Tt> operator%(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false)%=img; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const t), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr & (ulongT)value,32768); + return *this; + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition. + **/ + CImg& operator&=(const char *const expression) { + return *this&=(+*this)._fill(expression,true,1,0,0,"operator&=",this); + } + + //! In-place bitwise AND operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise AND operation instead of an addition. + **/ + template + CImg& operator&=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this&=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator&(const t value) const { + return (+*this)&=value; + } + + //! Bitwise AND operator. + /** + Similar to operator&=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator&(const char *const expression) const { + return (+*this)&=expression; + } + + //! Bitwise AND 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 is \c T. + **/ + template + CImg operator&(const CImg& img) const { + return (+*this)&=img; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr | (ulongT)value,32768); + return *this; + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition. + **/ + CImg& operator|=(const char *const expression) { + return *this|=(+*this)._fill(expression,true,1,0,0,"operator|=",this); + } + + //! In-place bitwise OR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise OR operation instead of an addition. + **/ + template + CImg& operator|=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this|=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator|(const t value) const { + return (+*this)|=value; + } + + //! Bitwise OR operator. + /** + Similar to operator|=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator|(const char *const expression) const { + return (+*this)|=expression; + } + + //! Bitwise OR 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 is \c T. + **/ + template + CImg operator|(const CImg& img) const { + return (+*this)|=img; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const t), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const t) instead. + **/ + template + CImg& operator^=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,(ulongT)*ptr ^ (ulongT)value,32768); + return *this; + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead. + **/ + CImg& operator^=(const char *const expression) { + return *this^=(+*this)._fill(expression,true,1,0,0,"operator^=",this); + } + + //! In-place bitwise XOR operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise XOR operation instead of an addition. + \warning + - It does \e not compute the \e power of pixel values. For this purpose, use pow(const CImg&) instead. + **/ + template + CImg& operator^=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator^(const t value) const { + return (+*this)^=value; + } + + //! Bitwise XOR operator. + /** + Similar to operator^=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator^(const char *const expression) const { + return (+*this)^=expression; + } + + //! Bitwise XOR 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 is \c T. + **/ + template + CImg operator^(const CImg& img) const { + return (+*this)^=img; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) << (int)value,65536); + return *this; + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition. + **/ + CImg& operator<<=(const char *const expression) { + return *this<<=(+*this)._fill(expression,true,1,0,0,"operator<<=",this); + } + + //! In-place bitwise left shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise left shift instead of an addition. + **/ + template + CImg& operator<<=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg operator<<(const t value) const { + return (+*this)<<=value; + } + + //! Bitwise left shift operator. + /** + Similar to operator<<=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator<<(const char *const expression) const { + return (+*this)<<=expression; + } + + //! Bitwise left shift 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 is \c T. + **/ + template + CImg operator<<(const CImg& img) const { + return (+*this)<<=img; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const t), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const t value) { + if (is_empty()) return *this; + cimg_openmp_for(*this,((longT)*ptr) >> (int)value,65536); + return *this; + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition. + **/ + CImg& operator>>=(const char *const expression) { + return *this>>=(+*this)._fill(expression,true,1,0,0,"operator>>=",this); + } + + //! In-place bitwise right shift operator. + /** + Similar to operator+=(const CImg&), except that it performs a bitwise right shift instead of an addition. + **/ + template + CImg& operator>>=(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return *this^=+img; + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs> (int)*(ptrs++)); + for (const t *ptrs = img._data; ptrd> (int)*(ptrs++)); + } + return *this; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const t), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + template + CImg operator>>(const t value) const { + return (+*this)>>=value; + } + + //! Bitwise right shift operator. + /** + Similar to operator>>=(const char*), except that it returns a new image instance instead of operating in-place. + The pixel type of the returned image is \c T. + **/ + CImg operator>>(const char *const expression) const { + return (+*this)>>=expression; + } + + //! Bitwise right shift 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 is \c T. + **/ + template + CImg operator>>(const CImg& img) const { + return (+*this)>>=img; + } + + //! Bitwise inversion operator. + /** + Similar to operator-(), except that it compute the bitwise inverse instead of the opposite value. + **/ + CImg operator~() const { + CImg res(_width,_height,_depth,_spectrum); + const T *ptrs = _data; + cimg_for(res,ptrd,T) { const ulongT value = (ulongT)*(ptrs++); *ptrd = (T)~value; } + return res; + } + + //! Test if all pixels of an image have the same value. + /** + Return \c true is all pixels of the image instance are equal to the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator==(const t value) const { + if (is_empty()) return false; + typedef _cimg_Tt Tt; + bool is_equal = true; + for (T *ptrd = _data + size(); is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)value)) {} + return is_equal; + } + + //! Test if all pixel values of an image follow a specified expression. + /** + Return \c true is all pixels of the image instance are equal to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator==(const char *const expression) const { + return *this==(+*this)._fill(expression,true,1,0,0,"operator==",this); + } + + //! Test if two images have the same size and values. + /** + Return \c true if the image instance and the input image \c img have the same dimensions and pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - The pixel buffer pointers data() of the two compared images do not have to be the same for operator==() + to return \c true. + Only the dimensions and the pixel values matter. Thus, the comparison can be \c true even for different + pixel types \c T and \c t. + \par Example + \code + const CImg 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 + **/ + template + bool operator==(const CImg& img) const { + typedef _cimg_Tt Tt; + const ulongT siz = size(); + bool is_equal = true; + if (siz!=img.size()) return false; + t *ptrs = img._data + siz; + for (T *ptrd = _data + siz; is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)*(--ptrs))) {} + return is_equal; + } + + //! Test if pixels of an image are all different from a value. + /** + Return \c true is all pixels of the image instance are different than the specified \c value. + \param value Reference value to compare with. + **/ + template + bool operator!=(const t value) const { + return !((*this)==value); + } + + //! Test if all pixel values of an image are different from a specified expression. + /** + Return \c true is all pixels of the image instance are different to the specified \c expression. + \param expression Value string describing the way pixel values are compared. + **/ + bool operator!=(const char *const expression) const { + return !((*this)==expression); + } + + //! Test if two images have different sizes or values. + /** + Return \c true if the image instance and the input image \c img have different dimensions or pixel values, + and \c false otherwise. + \param img Input image to compare with. + \note + - Writing \c img1!=img2 is equivalent to \c !(img1==img2). + **/ + template + bool operator!=(const CImg& img) const { + return !((*this)==img); + } + + //! Construct an image list from two images. + /** + Return a new list of image (\c CImgList instance) containing exactly two elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image \c img, at position [\c 1]. + + \param img Input image that will be the second image of the resulting list. + \note + - The family of operator,() is convenient to easily create list of images, but it is also \e quite \e slow + in practice (see warning below). + - Constructed lists contain no shared images. If image instance or input image \c img are shared, they are + inserted as new non-shared copies in the resulting list. + - The pixel type of the returned list may be a superset of the initial pixel type \c T, if necessary. + \warning + - Pipelining operator,() \c N times will perform \c N copies of the entire content of a (growing) image list. + This may become very expensive in terms of speed and used memory. You should avoid using this technique to + build a new CImgList instance from several images, if you are seeking for performance. + Fast insertions of images in an image list are possible with + CImgList::insert(const CImg&,unsigned int,bool) or move_to(CImgList&,unsigned int). + \par Example + \code + const CImg + 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' + \endcode + \image html ref_operator_comma.jpg + **/ + template + CImgList<_cimg_Tt> operator,(const CImg& img) const { + return CImgList<_cimg_Tt>(*this,img); + } + + //! Construct an image list from image instance and an input image list. + /** + Return a new list of images (\c CImgList instance) containing exactly \c list.size() \c + \c 1 elements: + - A copy of the image instance, at position [\c 0]. + - A copy of the specified image list \c list, from positions [\c 1] to [\c list.size()]. + + \param list Input image list that will be appended to the image instance. + \note + - Similar to operator,(const CImg&) const, except that it takes an image list as an argument. + **/ + template + CImgList<_cimg_Tt> operator,(const CImgList& list) const { + return CImgList<_cimg_Tt>(list,false).insert(*this,0); + } + + //! Split image along specified axis. + /** + Return a new list of images (\c CImgList instance) containing the splitted components + of the instance image along the specified axis. + \param axis Splitting axis (can be '\c x','\c y','\c z' or '\c c') + \note + - Similar to get_split(char,int) const, with default second argument. + \par Example + \code + const CImg 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 + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the number of image columns. + /** + Return the image width, i.e. the image dimension along the X-axis. + \note + - The width() of an empty image is equal to \c 0. + - width() is typically equal to \c 1 when considering images as \e vectors for matrix calculations. + - width() returns an \c int, although the image width is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._width. + **/ + int width() const { + return (int)_width; + } + + //! Return the number of image rows. + /** + Return the image height, i.e. the image dimension along the Y-axis. + \note + - The height() of an empty image is equal to \c 0. + - height() returns an \c int, although the image height is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._height. + **/ + int height() const { + return (int)_height; + } + + //! Return the number of image slices. + /** + Return the image depth, i.e. the image dimension along the Z-axis. + \note + - The depth() of an empty image is equal to \c 0. + - depth() is typically equal to \c 1 when considering usual 2D images. When depth()\c > \c 1, the image + is said to be \e volumetric. + - depth() returns an \c int, although the image depth is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._depth. + **/ + int depth() const { + return (int)_depth; + } + + //! Return the number of image channels. + /** + Return the number of image channels, i.e. the image dimension along the C-axis. + \note + - The spectrum() of an empty image is equal to \c 0. + - spectrum() is typically equal to \c 1 when considering scalar-valued images, to \c 3 + for RGB-coded color images, and to \c 4 for RGBA-coded color images (with alpha-channel). + The number of channels of an image instance is not limited. The meaning of the pixel values is not linked + up to the number of channels (e.g. a 4-channel image may indifferently stands for a RGBA or CMYK color image). + - spectrum() returns an \c int, although the image spectrum is internally stored as an \c unsigned \c int. + Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving + \c unsigned \c int variables. + Access to the initial \c unsigned \c int variable is possible (though not recommended) by + (*this)._spectrum. + **/ + int spectrum() const { + return (int)_spectrum; + } + + //! Return the total number of pixel values. + /** + Return width()*\ref height()*\ref depth()*\ref spectrum(), + i.e. the total number of values of type \c T in the pixel buffer of the image instance. + \note + - The size() of an empty image is equal to \c 0. + - The allocated memory size for a pixel buffer of a non-shared \c CImg instance is equal to + size()*sizeof(T). + \par Example + \code + 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 + **/ + ulongT size() const { + return (ulongT)_width*_height*_depth*_spectrum; + } + + //! Return a pointer to the first pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the first value in the pixel buffer of the image instance, + whether the instance is \c const or not. + \note + - The data() of an empty image is equal to \c 0 (null pointer). + - The allocated pixel buffer for the image instance starts from \c data() + and goes to data()+\ref size() - 1 (included). + - To get the pointer to one particular location of the pixel buffer, use + data(unsigned int,unsigned int,unsigned int,unsigned int) instead. + **/ + T* data() { + return _data; + } + + //! Return a pointer to the first pixel value \const. + const T* data() const { + return _data; + } + + //! Return a pointer to a located pixel value. + /** + Return a \c T*, or a \c const \c T* pointer to the value located at (\c x,\c y,\c z,\c c) in the pixel buffer + of the image instance, + whether the instance is \c const or not. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)). Thus, this method has the same + properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int). + **/ +#if cimg_verbosity>=3 + T *data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + const ulongT off = (ulongT)offset(x,y,z,c); + if (off>=size()) + cimg::warn(_cimg_instance + "data(): Invalid pointer request, at coordinates (%u,%u,%u,%u) [offset=%u].", + cimg_instance, + x,y,z,c,off); + return _data + off; + } + + //! Return a pointer to a located pixel value \const. + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return const_cast*>(this)->data(x,y,z,c); + } +#else + T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } + + const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { + return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; + } +#endif + + //! Return the offset to a located pixel value, with respect to the beginning of the pixel buffer. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)) - img.data(). + 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 + \endcode + **/ + longT offset(const int x, const int y=0, const int z=0, const int c=0) const { + return x + (longT)y*_width + (longT)z*_width*_height + (longT)c*_width*_height*_depth; + } + + //! Return a CImg::iterator pointing to the first pixel value. + /** + \note + - Equivalent to data(). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + iterator begin() { + return _data; + } + + //! Return a CImg::iterator pointing to the first value of the pixel buffer \const. + const_iterator begin() const { + return _data; + } + + //! Return a CImg::iterator pointing next to the last pixel value. + /** + \note + - Writing \c img.end() is equivalent to img.data() + img.size(). + - It has been mainly defined for compatibility with STL naming conventions. + \warning + - The returned iterator actually points to a value located \e outside the acceptable bounds of the pixel buffer. + Trying to read or write the content of the returned iterator will probably result in a crash. + Use it mainly as a strict upper bound for a CImg::iterator. + \par Example + \code + 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::iterator pointing next to the last pixel value \const. + const_iterator end() const { + return _data + size(); + } + + //! Return a reference to the first pixel value. + /** + \note + - Writing \c img.front() is equivalent to img[0], or img(0,0,0,0). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& front() { + return *_data; + } + + //! Return a reference to the first pixel value \const. + const T& front() const { + return *_data; + } + + //! Return a reference to the last pixel value. + /** + \note + - Writing \c img.back() is equivalent to img[img.size() - 1], or + img(img.width() - 1,img.height() - 1,img.depth() - 1,img.spectrum() - 1). + - It has been mainly defined for compatibility with STL naming conventions. + **/ + T& back() { + return *(_data + size() - 1); + } + + //! Return a reference to the last pixel value \const. + const T& back() const { + return *(_data + size() - 1); + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to a specified default value in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note + - Writing \c img.at(offset,out_value) is similar to img[offset], except that if \c offset + is outside bounds (e.g. \c offset<0 or \c offset>=img.size()), a reference to a value \c out_value + is safely returned instead. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + **/ + T& at(const int offset, const T& out_value) { + return (offset<0 || offset>=(int)size())?(cimg::temporary(out_value)=out_value):(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions \const. + T at(const int offset, const T& out_value) const { + return (offset<0 || offset>=(int)size())?out_value:(*this)[offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions. + /** + Return a reference to the pixel value of the image instance located at a specified \c offset, + or to the nearest pixel location in the image instance in case of out-of-bounds access. + \param offset Offset to the desired pixel value. + \note + - Similar to at(int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified offset, i.e. + - If \c offset<0, then \c img[0] is returned. + - If \c offset>=img.size(), then \c img[img.size() - 1] is returned. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel offset. + - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int). + **/ + T& at(const int offset) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + T& _at(const int offset) { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value at a specified offset, using Neumann boundary conditions \const. + const T& at(const int offset) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "at(): Empty instance.", + cimg_instance); + return _at(offset); + } + + const T& _at(const int offset) const { + const unsigned int siz = (unsigned int)size(); + return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to a specified default value in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c x,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to operator()(), except that an out-of-bounds access along the X-axis returns the specified value + \c out_value. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || x>=width())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate \const. + T atX(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || x>=width())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate. + /** + Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), + or to the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to at(int,int,int,int,const T), except that an out-of-bounds access returns the value of the + nearest pixel in the image instance, regarding the specified X-coordinate. + - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when + you are \e not sure about the validity of the specified pixel coordinates. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _at(int,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + T& atX(const int x, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + T& _atX(const int x, const int y=0, const int z=0, const int c=0) { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate \const. + const T& atX(const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atX(): Empty instance.", + cimg_instance); + return _atX(x,y,z,c); + } + + const T& _atX(const int x, const int y=0, const int z=0, const int c=0) const { + return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X and Y-coordinates. + **/ + T& atXY(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y coordinates \const. + T atXY(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || x>=width() || y>=height())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXY(int,int,int,int). + **/ + T& atXY(const int x, const int y, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + T& _atXY(const int x, const int y, const int z=0, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates \const. + const T& atXY(const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXY(): Empty instance.", + cimg_instance); + return _atXY(x,y,z,c); + } + + const T& _atXY(const int x, const int y, const int z=0, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1),z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on + X,Y and Z-coordinates. + **/ + T& atXYZ(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates \const. + T atXYZ(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_value:(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed both on X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZ(int,int,int,int). + **/ + T& atXYZ(const int x, const int y, const int z, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + T& _atXYZ(const int x, const int y, const int z, const int c=0) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates \const. + const T& atXYZ(const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZ(): Empty instance.", + cimg_instance); + return _atXYZ(x,y,z,c); + } + + const T& _atXYZ(const int x, const int y, const int z, const int c=0) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1),c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions. + /** + Similar to atX(int,int,int,int,const T), except that boundary checking is performed on all + X,Y,Z and C-coordinates. + **/ + T& atXYZC(const int x, const int y, const int z, const int c, const T& out_value) { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())? + (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); + } + + //! Access to a pixel value, using Dirichlet boundary conditions \const. + T atXYZC(const int x, const int y, const int z, const int c, const T& out_value) const { + return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_value: + (*this)(x,y,z,c); + } + + //! Access to a pixel value, using Neumann boundary conditions. + /** + Similar to atX(int,int,int,int), except that boundary checking is performed on all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _atXYZC(int,int,int,int). + **/ + T& atXYZC(const int x, const int y, const int z, const int c) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + T& _atXYZC(const int x, const int y, const int z, const int c) { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Access to a pixel value, using Neumann boundary conditions \const. + const T& atXYZC(const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "atXYZC(): Empty instance.", + cimg_instance); + return _atXYZC(x,y,z,c); + } + + const T& _atXYZC(const int x, const int y, const int z, const int c) const { + return (*this)(cimg::cut(x,0,width() - 1), + cimg::cut(y,0,height() - 1), + cimg::cut(z,0,depth() - 1), + cimg::cut(c,0,spectrum() - 1)); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to atX(int,int,int,int,const T), except that the returned pixel value is approximated by + a linear interpolation along the X-axis, if corresponding coordinates are not integers. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + const Tfloat + Ic = (Tfloat)atX(x,y,z,c,out_value), In = (Tfloat)atXY(nx,y,z,c,out_value); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access along + the X-axis. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that an out-of-bounds access returns + the value of the nearest pixel in the image instance, regarding the specified X-coordinate. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atX(): Empty instance.", + cimg_instance); + + return _linear_atX(fx,y,z,c); + } + + Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1); + const unsigned int + x = (unsigned int)nfx; + const float + dx = nfx - x; + const unsigned int + nx = dx>0?x + 1:x; + const Tfloat + Ic = (Tfloat)(*this)(x,y,z,c), In = (Tfloat)(*this)(nx,y,z,c); + return Ic + dx*(In - Ic); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X and Y-coordinates. + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + const Tfloat + Icc = (Tfloat)atXY(x,y,z,c,out_value), Inc = (Tfloat)atXY(nx,y,z,c,out_value), + Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXY(float,float,int,int). + **/ + Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXY(): Empty instance.", + cimg_instance); + + return _linear_atXY(fx,fy,z,c); + } + + Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy; + const float + dx = nfx - x, + dy = nfy - y; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y; + const Tfloat + Icc = (Tfloat)(*this)(x,y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c); + return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved both for X,Y and Z-coordinates. + **/ + Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + const Tfloat + Iccc = (Tfloat)atXYZ(x,y,z,c,out_value), Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), + Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), + Iccn = (Tfloat)atXYZ(x,y,nz,c,out_value), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), + Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZ(float,float,float,int). + **/ + Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZ(): Empty instance.", + cimg_instance); + + return _linear_atXYZ(fx,fy,fz,c); + } + + Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z; + const Tfloat + Iccc = (Tfloat)(*this)(x,y,z,c), Incc = (Tfloat)(*this)(nx,y,z,c), + Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c), + Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c), + Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c); + return Iccc + + dx*(Incc - Iccc + + dy*(Iccc + Innc - Icnc - Incc + + dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) + + dz*(Iccc + Incn - Iccn - Incc)) + + dy*(Icnc - Iccc + + dz*(Iccc + Icnn - Iccn - Icnc)) + + dz*(Iccn - Iccc); + } + + //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z,C-coordinates. + /** + Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the + boundary checking are achieved for all X,Y,Z and C-coordinates. + **/ + Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1, + c = (int)fc - (fc>=0?0:1), nc = c + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z, + dc = fc - c; + const Tfloat + Icccc = (Tfloat)atXYZC(x,y,z,c,out_value), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_value), + Icncc = (Tfloat)atXYZC(x,ny,z,c,out_value), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_value), + Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_value), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_value), + Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_value), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_value), + Icccn = (Tfloat)atXYZC(x,y,z,nc,out_value), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_value), + Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_value), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_value), + Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_value), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_value), + Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_value), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_value); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn -Icccc); + } + + //! Return pixel value, using linear interpolation and Neumann boundary conditions for all X,Y,Z and C-coordinates. + /** + Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking + are achieved for all X,Y,Z and C-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _linear_atXYZC(float,float,float,float). + **/ + Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXYZC(): Empty instance.", + cimg_instance); + + return _linear_atXYZC(fx,fy,fz,fc); + } + + Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + const float + nfx = cimg::cut(fx,0,width() - 1), + nfy = cimg::cut(fy,0,height() - 1), + nfz = cimg::cut(fz,0,depth() - 1), + nfc = cimg::cut(fc,0,spectrum() - 1); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz, + c = (unsigned int)nfc; + const float + dx = nfx - x, + dy = nfy - y, + dz = nfz - z, + dc = nfc - c; + const unsigned int + nx = dx>0?x + 1:x, + ny = dy>0?y + 1:y, + nz = dz>0?z + 1:z, + nc = dc>0?c + 1:c; + const Tfloat + Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c), + Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c), + Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c), + Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c), + Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc), + Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc), + Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc), + Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc); + return Icccc + + dx*(Inccc - Icccc + + dy*(Icccc + Inncc - Icncc - Inccc + + dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + + dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - + Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + + dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + + dz*(Icccc + Incnc - Iccnc - Inccc + + dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + + dc*(Icccc + Inccn - Inccc - Icccn)) + + dy*(Icncc - Icccc + + dz*(Icccc + Icnnc - Iccnc - Icncc + + dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + + dc*(Icccc + Icncn - Icncc - Icccn)) + + dz*(Iccnc - Icccc + + dc*(Icccc + Iccnn - Iccnc - Icccn)) + + dc*(Icccn - Icccc); + } + + //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or a specified default value in case of out-of-bounds access along the X-axis. + The cubic interpolation uses Hermite splines. + \param fx d X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. + \note + - Similar to linear_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a \e cubic interpolation along the X-axis. + - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2; + const float + dx = fx - x; + const Tfloat + Ip = (Tfloat)atX(px,y,z,c,out_value), Ic = (Tfloat)atX(x,y,z,c,out_value), + In = (Tfloat)atX(nx,y,z,c,out_value), Ia = (Tfloat)atX(ax,y,z,c,out_value); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. + /** + Similar to cubic_atX(float,int,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atX(fx,y,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), + or the value of the nearest pixel location in the image instance in case of out-of-bounds access + along the X-axis. The cubic interpolation uses Hermite splines. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Similar to cubic_atX(float,int,int,int,const T) const, except that the returned pixel value is + approximated by a cubic interpolation along the X-axis. + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atX(float,int,int,int). + \warning + - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. + **/ + Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atX(): Empty instance.", + cimg_instance); + return _cubic_atX(fx,y,z,c); + } + + Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::type::is_nan(fx)?0:cimg::cut(fx,0,width() - 1); + const int + x = (int)nfx; + const float + dx = nfx - x; + const int + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2; + const Tfloat + Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c), + In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c); + return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atX(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 + are achieved both for X and Y-coordinates. + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2; + const float dx = fx - x, dy = fy - y; + const Tfloat + Ipp = (Tfloat)atXY(px,py,z,c,out_value), Icp = (Tfloat)atXY(x,py,z,c,out_value), + Inp = (Tfloat)atXY(nx,py,z,c,out_value), Iap = (Tfloat)atXY(ax,py,z,c,out_value), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)atXY(px,y,z,c,out_value), Icc = (Tfloat)atXY(x, y,z,c,out_value), + Inc = (Tfloat)atXY(nx,y,z,c,out_value), Iac = (Tfloat)atXY(ax,y,z,c,out_value), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)atXY(px,ny,z,c,out_value), Icn = (Tfloat)atXY(x,ny,z,c,out_value), + Inn = (Tfloat)atXY(nx,ny,z,c,out_value), Ian = (Tfloat)atXY(ax,ny,z,c,out_value), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)atXY(px,ay,z,c,out_value), Ica = (Tfloat)atXY(x,ay,z,c,out_value), + Ina = (Tfloat)atXY(nx,ay,z,c,out_value), Iaa = (Tfloat)atXY(ax,ay,z,c,out_value), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXY(fx,fy,z,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved for both X and Y-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXY(float,float,int,int). + **/ + Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXY(): Empty instance.", + cimg_instance); + return _cubic_atXY(fx,fy,z,c); + } + + Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::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: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), + Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ipc = (Tfloat)(*this)(px,y,z,c), Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), + Iac = (Tfloat)(*this)(ax,y,z,c), + Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c), + Ian = (Tfloat)(*this)(ax,ny,z,c), + In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c), + Iaa = (Tfloat)(*this)(ax,ay,z,c), + Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y-coordinates. + /** + Similar to cubic_atXY(float,float,int,int) const, except that 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 { + 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 { + return cimg::type::cut(_cubic_atXY(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 + are achieved both for X,Y and Z-coordinates. + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + const int + x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2, + y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2, + z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2; + const float dx = fx - x, dy = fy - y, dz = fz - z; + const Tfloat + Ippp = (Tfloat)atXYZ(px,py,pz,c,out_value), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_value), + Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_value), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_value), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_value), Iccp = (Tfloat)atXYZ(x, y,pz,c,out_value), + Incp = (Tfloat)atXYZ(nx,y,pz,c,out_value), Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_value), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_value), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_value), + Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_value), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_value), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_value), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_value), + Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_value), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_value), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)atXYZ(px,py,z,c,out_value), Icpc = (Tfloat)atXYZ(x,py,z,c,out_value), + Inpc = (Tfloat)atXYZ(nx,py,z,c,out_value), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_value), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)atXYZ(px,y,z,c,out_value), Iccc = (Tfloat)atXYZ(x, y,z,c,out_value), + Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), Iacc = (Tfloat)atXYZ(ax,y,z,c,out_value), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_value), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), + Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_value), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)atXYZ(px,ay,z,c,out_value), Icac = (Tfloat)atXYZ(x,ay,z,c,out_value), + Inac = (Tfloat)atXYZ(nx,ay,z,c,out_value), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_value), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)atXYZ(px,py,nz,c,out_value), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_value), + Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_value), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_value), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_value), Iccn = (Tfloat)atXYZ(x, y,nz,c,out_value), + Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_value), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_value), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), + Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_value), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_value), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_value), + Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_value), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_value), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)atXYZ(px,py,az,c,out_value), Icpa = (Tfloat)atXYZ(x,py,az,c,out_value), + Inpa = (Tfloat)atXYZ(nx,py,az,c,out_value), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_value), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)atXYZ(px,y,az,c,out_value), Icca = (Tfloat)atXYZ(x, y,az,c,out_value), + Inca = (Tfloat)atXYZ(nx,y,az,c,out_value), Iaca = (Tfloat)atXYZ(ax,y,az,c,out_value), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)atXYZ(px,ny,az,c,out_value), Icna = (Tfloat)atXYZ(x,ny,az,c,out_value), + Inna = (Tfloat)atXYZ(nx,ny,az,c,out_value), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_value), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_value), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_value), + Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_value), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_value), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the XYZ-coordinates. + /** + Similar to cubic_atXYZ(float,float,float,int,const T) const, except that 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 { + return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c,out_value)); + } + + //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. + /** + Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking + are achieved both for X,Y and Z-coordinates. + \note + - If you know your image instance is \e not empty, you may rather use the slightly faster method + \c _cubic_atXYZ(float,float,float,int). + **/ + Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXYZ(): Empty instance.", + cimg_instance); + return _cubic_atXYZ(fx,fy,fz,c); + } + + Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { + const float + nfx = cimg::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 + px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2, + py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2, + pz = z - 1<0?0:z - 1, nz = dz>0?z + 1:z, az = z + 2>=depth()?depth() - 1:z + 2; + const Tfloat + Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c), + Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c), + Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + + dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), + Ipcp = (Tfloat)(*this)(px,y,pz,c), Iccp = (Tfloat)(*this)(x, y,pz,c), + Incp = (Tfloat)(*this)(nx,y,pz,c), Iacp = (Tfloat)(*this)(ax,y,pz,c), + Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + + dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), + Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c), + Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c), + Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + + dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), + Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c), + Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c), + Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + + dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), + Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + + dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), + Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c), + Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c), + Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + + dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), + Ipcc = (Tfloat)(*this)(px,y,z,c), Iccc = (Tfloat)(*this)(x, y,z,c), + Incc = (Tfloat)(*this)(nx,y,z,c), Iacc = (Tfloat)(*this)(ax,y,z,c), + Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + + dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), + Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c), + Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c), + Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + + dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), + Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c), + Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c), + Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + + dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), + Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + + dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), + Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c), + Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c), + Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + + dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), + Ipcn = (Tfloat)(*this)(px,y,nz,c), Iccn = (Tfloat)(*this)(x, y,nz,c), + Incn = (Tfloat)(*this)(nx,y,nz,c), Iacn = (Tfloat)(*this)(ax,y,nz,c), + Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + + dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), + Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c), + Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c), + Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + + dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), + Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c), + Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c), + Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + + dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), + In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + + dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), + Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c), + Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c), + Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + + dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), + Ipca = (Tfloat)(*this)(px,y,az,c), Icca = (Tfloat)(*this)(x, y,az,c), + Inca = (Tfloat)(*this)(nx,y,az,c), Iaca = (Tfloat)(*this)(ax,y,az,c), + Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + + dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), + Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c), + Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c), + Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + + dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), + Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c), + Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c), + Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + + dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), + Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + + dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); + return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); + } + + //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the XYZ-coordinates. + /** + 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 { + 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 { + return cimg::type::cut(_cubic_atXYZ(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 + the value is spread amongst several neighbors if the pixel coordinates are float-valued. + \param value Pixel value to set. + \param fx X-coordinate of the pixel value (float-valued). + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param is_added Tells if the pixel value is added to (\c true), or simply replace (\c false) the current image + pixel(s). + \return A reference to the current image instance. + \note + - Calling this method with out-of-bounds coordinates does nothing. + **/ + CImg& set_linear_atX(const T& value, const float fx, const int y=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1; + const float + dx = fx - x; + if (y>=0 && y=0 && z=0 && c=0 && x=0 && nx& set_linear_atXY(const T& value, const float fx, const float fy=0, const int z=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1; + const float + dx = fx - x, + dy = fy - y; + if (z>=0 && z=0 && c=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx& set_linear_atXYZ(const T& value, const float fx, const float fy=0, const float fz=0, const int c=0, + const bool is_added=false) { + const int + x = (int)fx - (fx>=0?0:1), nx = x + 1, + y = (int)fy - (fy>=0?0:1), ny = y + 1, + z = (int)fz - (fz>=0?0:1), nz = z + 1; + const float + dx = fx - x, + dy = fy - y, + dz = fz - z; + if (c>=0 && c=0 && z=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx=0 && nz=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx image whose buffer data() is a \c char* string describing the list of all pixel values + of the image instance (written in base 10), separated by specified \c separator character. + \param separator A \c char character which specifies the separator between values in the returned C-string. + \param max_size Maximum size of the returned image (or \c 0 if no limits are set). + \param format For float/double-values, tell the printf format used to generate the Ascii representation + of the numbers (or \c 0 for default representation). + \note + - The returned image is never empty. + - For an empty image instance, the returned string is "". + - If \c max_size is equal to \c 0, there are no limits on the size of the returned string. + - Otherwise, if the maximum number of string characters is exceeded, the value string is cut off + and terminated by character \c '\0'. In that case, the returned image size is max_size + 1. + **/ + CImg value_string(const char separator=',', const unsigned int max_size=0, + const char *const format=0) const { + if (is_empty() || max_size==1) return CImg(1,1,1,1,0); + CImgList items; + CImg s_item(256); *s_item = 0; + const T *ptrs = _data; + unsigned int string_size = 0; + const char *const _format = format?format:cimg::type::format(); + for (ulongT off = 0, siz = size(); off::format(*(ptrs++))); + CImg item(s_item._data,printed_size); + item[printed_size - 1] = separator; + item.move_to(items); + if (max_size) string_size+=printed_size; + } + CImg res; + (items>'x').move_to(res); + if (max_size && res._width>=max_size) res.crop(0,max_size - 1); + res.back() = 0; + return res; + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Test shared state of the pixel buffer. + /** + Return \c true if image instance has a shared memory buffer, and \c false otherwise. + \note + - A shared image do not own his pixel buffer data() and will not deallocate it on destruction. + - Most of the time, a \c CImg image instance will \e not be shared. + - A shared image can only be obtained by a limited set of constructors and methods (see list below). + **/ + bool is_shared() const { + return _is_shared; + } + + //! Test if image instance is empty. + /** + Return \c true, if image instance is empty, i.e. does \e not contain any pixel values, has dimensions + \c 0 x \c 0 x \c 0 x \c 0 and a pixel buffer pointer set to \c 0 (null pointer), and \c false otherwise. + **/ + bool is_empty() const { + return !(_data && _width && _height && _depth && _spectrum); + } + + //! Test if image instance contains a 'inf' value. + /** + Return \c true, if image instance contains a 'inf' value, and \c false otherwise. + **/ + bool is_inf() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_inf((float)*p)) return true; + return false; + } + + //! Test if image instance contains a NaN value. + /** + Return \c true, if image instance contains a NaN value, and \c false otherwise. + **/ + bool is_nan() const { + if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_nan((float)*p)) return true; + return false; + } + + //! Test if image width is equal to specified value. + bool is_sameX(const unsigned int size_x) const { + return _width==size_x; + } + + //! Test if image width is equal to specified value. + template + bool is_sameX(const CImg& img) const { + return is_sameX(img._width); + } + + //! Test if image width is equal to specified value. + bool is_sameX(const CImgDisplay& disp) const { + return is_sameX(disp._width); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const unsigned int size_y) const { + return _height==size_y; + } + + //! Test if image height is equal to specified value. + template + bool is_sameY(const CImg& img) const { + return is_sameY(img._height); + } + + //! Test if image height is equal to specified value. + bool is_sameY(const CImgDisplay& disp) const { + return is_sameY(disp._height); + } + + //! Test if image depth is equal to specified value. + bool is_sameZ(const unsigned int size_z) const { + return _depth==size_z; + } + + //! Test if image depth is equal to specified value. + template + bool is_sameZ(const CImg& img) const { + return is_sameZ(img._depth); + } + + //! Test if image spectrum is equal to specified value. + bool is_sameC(const unsigned int size_c) const { + return _spectrum==size_c; + } + + //! Test if image spectrum is equal to specified value. + template + bool is_sameC(const CImg& img) const { + return is_sameC(img._spectrum); + } + + //! Test if image width and height are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameY(unsigned int) const are both verified. + **/ + bool is_sameXY(const unsigned int size_x, const unsigned int size_y) const { + return _width==size_x && _height==size_y; + } + + //! Test if image width and height are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameY(const CImg&) const are both verified. + **/ + template + bool is_sameXY(const CImg& img) const { + return is_sameXY(img._width,img._height); + } + + //! Test if image width and height are the same as that of an existing display window. + /** + Test if is_sameX(const CImgDisplay&) const and is_sameY(const CImgDisplay&) const are both verified. + **/ + bool is_sameXY(const CImgDisplay& disp) const { + return is_sameXY(disp._width,disp._height); + } + + //! Test if image width and depth are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXZ(const unsigned int size_x, const unsigned int size_z) const { + return _width==size_x && _depth==size_z; + } + + //! Test if image width and depth are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXZ(const CImg& img) const { + return is_sameXZ(img._width,img._depth); + } + + //! Test if image width and spectrum are equal to specified values. + /** + Test if is_sameX(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXC(const unsigned int size_x, const unsigned int size_c) const { + return _width==size_x && _spectrum==size_c; + } + + //! Test if image width and spectrum are the same as that of another image. + /** + Test if is_sameX(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXC(const CImg& img) const { + return is_sameXC(img._width,img._spectrum); + } + + //! Test if image height and depth are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameYZ(const unsigned int size_y, const unsigned int size_z) const { + return _height==size_y && _depth==size_z; + } + + //! Test if image height and depth are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameYZ(const CImg& img) const { + return is_sameYZ(img._height,img._depth); + } + + //! Test if image height and spectrum are equal to specified values. + /** + Test if is_sameY(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYC(const unsigned int size_y, const unsigned int size_c) const { + return _height==size_y && _spectrum==size_c; + } + + //! Test if image height and spectrum are the same as that of another image. + /** + Test if is_sameY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYC(const CImg& img) const { + return is_sameYC(img._height,img._spectrum); + } + + //! Test if image depth and spectrum are equal to specified values. + /** + Test if is_sameZ(unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameZC(const unsigned int size_z, const unsigned int size_c) const { + return _depth==size_z && _spectrum==size_c; + } + + //! Test if image depth and spectrum are the same as that of another image. + /** + Test if is_sameZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameZC(const CImg& img) const { + return is_sameZC(img._depth,img._spectrum); + } + + //! Test if image width, height and depth are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameZ(unsigned int) const are both verified. + **/ + bool is_sameXYZ(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z) const { + return is_sameXY(size_x,size_y) && _depth==size_z; + } + + //! Test if image width, height and depth are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameZ(const CImg&) const are both verified. + **/ + template + bool is_sameXYZ(const CImg& img) const { + return is_sameXYZ(img._width,img._height,img._depth); + } + + //! Test if image width, height and spectrum are equal to specified values. + /** + Test if is_sameXY(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXYC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_c) const { + return is_sameXY(size_x,size_y) && _spectrum==size_c; + } + + //! Test if image width, height and spectrum are the same as that of another image. + /** + Test if is_sameXY(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYC(const CImg& img) const { + return is_sameXYC(img._width,img._height,img._spectrum); + } + + //! Test if image width, depth and spectrum are equal to specified values. + /** + Test if is_sameXZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameXZC(const unsigned int size_x, const unsigned int size_z, const unsigned int size_c) const { + return is_sameXZ(size_x,size_z) && _spectrum==size_c; + } + + //! Test if image width, depth and spectrum are the same as that of another image. + /** + Test if is_sameXZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXZC(const CImg& img) const { + return is_sameXZC(img._width,img._depth,img._spectrum); + } + + //! Test if image height, depth and spectrum are equal to specified values. + /** + Test if is_sameYZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. + **/ + bool is_sameYZC(const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const { + return is_sameYZ(size_y,size_z) && _spectrum==size_c; + } + + //! Test if image height, depth and spectrum are the same as that of another image. + /** + Test if is_sameYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameYZC(const CImg& img) const { + return is_sameYZC(img._height,img._depth,img._spectrum); + } + + //! Test if image width, height, depth and spectrum are equal to specified values. + /** + Test if is_sameXYZ(unsigned int,unsigned int,unsigned int) const and is_sameC(unsigned int) const are both + verified. + **/ + bool is_sameXYZC(const unsigned int size_x, const unsigned int size_y, + const unsigned int size_z, const unsigned int size_c) const { + return is_sameXYZ(size_x,size_y,size_z) && _spectrum==size_c; + } + + //! Test if image width, height, depth and spectrum are the same as that of another image. + /** + Test if is_sameXYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. + **/ + template + bool is_sameXYZC(const CImg& img) const { + return is_sameXYZC(img._width,img._height,img._depth,img._spectrum); + } + + //! Test if specified coordinates are inside image bounds. + /** + Return \c true if pixel located at (\c x,\c y,\c z,\c c) is inside bounds of the image instance, + and \c false otherwise. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note + - Return \c true only if all these conditions are verified: + - The image instance is \e not empty. + - 0<=x<=\ref width() - 1. + - 0<=y<=\ref height() - 1. + - 0<=z<=\ref depth() - 1. + - 0<=c<=\ref spectrum() - 1. + **/ + bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const { + return !is_empty() && x>=0 && x=0 && y=0 && z=0 && c img(100,100,1,3); // Construct a 100x100 RGB color image + const unsigned long offset = 1249; // Offset to the pixel (49,12,0,0) + unsigned int x,y,z,c; + if (img.contains(img[offset],x,y,z,c)) { // Convert offset to (x,y,z,c) coordinates + std::printf("Offset %u refers to pixel located at (%u,%u,%u,%u).\n", + offset,x,y,z,c); + } + \endcode + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z, t& c) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = (ulongT)(ppixel - _data); + const ulongT nc = off/whd; + off%=whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc; + return true; + } + + //! Test if pixel value is inside image bounds and get its X,Y and Z-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X,Y and Z-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y, t& z) const { + const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((ulongT)(ppixel - _data))%whd; + const ulongT nz = off/wh; + off%=wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; z = (t)nz; + return true; + } + + //! Test if pixel value is inside image bounds and get its X and Y-coordinates. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X and Y-coordinates are set. + **/ + template + bool contains(const T& pixel, t& x, t& y) const { + const ulongT wh = (ulongT)_width*_height, siz = wh*_depth*_spectrum; + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; + ulongT off = ((unsigned int)(ppixel - _data))%wh; + const ulongT ny = off/_width, nx = off%_width; + x = (t)nx; y = (t)ny; + return true; + } + + //! Test if pixel value is inside image bounds and get its X-coordinate. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X-coordinate is set. + **/ + template + bool contains(const T& pixel, t& x) const { + const T *const ppixel = &pixel; + if (is_empty() || ppixel<_data || ppixel>=_data + size()) return false; + x = (t)(((ulongT)(ppixel - _data))%_width); + return true; + } + + //! Test if pixel value is inside image bounds. + /** + Similar to contains(const T&,t&,t&,t&,t&) const, except that no pixel coordinates are set. + **/ + bool contains(const T& pixel) const { + const T *const ppixel = &pixel; + return !is_empty() && ppixel>=_data && ppixel<_data + size(); + } + + //! Test if pixel buffers of instance and input images overlap. + /** + Return \c true, if pixel buffers attached to image instance and input image \c img overlap, + and \c false otherwise. + \param img Input image to compare with. + \note + - Buffer overlapping may happen when manipulating \e shared images. + - If two image buffers overlap, operating on one of the image will probably modify the other one. + - Most of the time, \c CImg instances are \e non-shared and do not overlap between each others. + \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 + std::printf("Buffers overlap!\n"); + } + \endcode + **/ + template + bool is_overlapped(const CImg& img) const { + const ulongT csiz = size(), isiz = img.size(); + return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz)); + } + + //! Test if the set {\c *this,\c primitives,\c colors,\c opacities} defines a valid 3D object. + /** + Return \c true is the 3D object represented by the set {\c *this,\c primitives,\c colors,\c opacities} defines a + valid 3D object, and \c false otherwise. The vertex coordinates are defined by the instance image. + \param primitives List of primitives of the 3D object. + \param colors List of colors of the 3D object. + \param opacities List (or image) of opacities of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_checking to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + template + bool is_object3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true, + char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check consistency for the particular case of an empty 3D object. + if (is_empty()) { + if (primitives || colors || opacities) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no vertices but %u primitives, " + "%u colors and %lu opacities", + _width,primitives._width,primitives._width, + colors._width,(unsigned long)opacities.size()); + return false; + } + return true; + } + + // Check consistency of vertices. + if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) has invalid vertex dimensions (%u,%u,%u,%u)", + _width,primitives._width,_width,_height,_depth,_spectrum); + return false; + } + if (colors._width>primitives._width + 1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %u colors", + _width,primitives._width,colors._width); + return false; + } + if (opacities.size()>primitives._width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines %lu opacities", + _width,primitives._width,(unsigned long)opacities.size()); + return false; + } + if (!full_check) return true; + + // Check consistency of primitives. + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + const unsigned int psiz = (unsigned int)primitive.size(); + switch (psiz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)primitive(0); + if (i0>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indice %u in " + "point primitive [%u]", + _width,primitives._width,i0,l); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + if (i0>=_width || i1>=_width) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + case 2 : 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 " + "segment primitive [%u]", + _width,primitives._width,i0,i1,l); + return false; + } + } break; + 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 " + "triangle primitive [%u]", + _width,primitives._width,i0,i1,i2,l); + return false; + } + } break; + 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 " + "quadrangle primitive [%u]", + _width,primitives._width,i0,i1,i2,i3,l); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid primitive [%u] of size %u", + _width,primitives._width,l,(unsigned int)psiz); + return false; + } + } + + // Check consistency of colors. + cimglist_for(colors,c) { + const CImg& color = colors[c]; + if (!color) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines no color for primitive [%u]", + _width,primitives._width,c); + return false; + } + } + + // Check consistency of light texture. + if (colors._width>primitives._width) { + const CImg &light = colors.back(); + if (!light || light._depth>1) { + if (error_message) cimg_sprintf(error_message, + "3D object (%u,%u) defines an invalid light texture (%u,%u,%u,%u)", + _width,primitives._width,light._width, + light._height,light._depth,light._spectrum); + return false; + } + } + + return true; + } + + //! Test if image instance represents a valid serialization of a 3D object. + /** + Return \c true if the image instance represents a valid serialization of a 3D object, and \c false otherwise. + \param full_check Tells if full checking of the instance must be performed. + \param[out] error_message C-string to contain the error message, if the test does not succeed. + \note + - Set \c full_check to \c false to speed-up the 3D object checking. In this case, only the size of + each 3D object component is checked. + - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. + **/ + bool is_CImg3d(const bool full_check=true, char *const error_message=0) const { + if (error_message) *error_message = 0; + + // Check instance dimension and header. + if (_width!=1 || _height<8 || _depth!=1 || _spectrum!=1) { + if (error_message) cimg_sprintf(error_message, + "CImg3d has invalid dimensions (%u,%u,%u,%u)", + _width,_height,_depth,_spectrum); + return false; + } + const T *ptrs = _data, *const ptre = end(); + if (!_is_CImg3d(*(ptrs++),'C') || !_is_CImg3d(*(ptrs++),'I') || !_is_CImg3d(*(ptrs++),'m') || + !_is_CImg3d(*(ptrs++),'g') || !_is_CImg3d(*(ptrs++),'3') || !_is_CImg3d(*(ptrs++),'d')) { + if (error_message) cimg_sprintf(error_message, + "CImg3d header not found"); + return false; + } + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + + // Check consistency of number of vertices / primitives. + if (!full_check) { + const ulongT minimal_size = 8UL + 3*nb_points + 6*nb_primitives; + if (_data + minimal_size>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has only %lu values, while at least %lu values were expected", + nb_points,nb_primitives,(unsigned long)size(),(unsigned long)minimal_size); + return false; + } + } + + // Check consistency of vertex data. + if (!nb_points) { + if (nb_primitives) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no vertices but %u primitives", + nb_points,nb_primitives,nb_primitives); + return false; + } + if (ptrs!=ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) is an empty object but contains %u value%s " + "more than expected", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs),(ptre - ptrs)>1?"s":""); + return false; + } + return true; + } + if (ptrs + 3*nb_points>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines only %u vertices data", + nb_points,nb_primitives,(unsigned int)(ptre - ptrs)/3); + return false; + } + ptrs+=3*nb_points; + + // Check consistency of primitive data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines %u vertices but no primitive", + nb_points,nb_primitives,nb_points); + return false; + } + + if (!full_check) return true; + + for (unsigned int p = 0; p=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive [%u]", + nb_points,nb_primitives,i0,p); + return false; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + ptrs+=3; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "sphere primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==6) ptrs+=4; + if (i0>=nb_points || i1>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " + "segment primitive [%u]", + nb_points,nb_primitives,i0,i1,p); + return false; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==9) ptrs+=6; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in " + "triangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,p); + return false; + } + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = cimg::float2uint((float)*(ptrs++)), + i1 = cimg::float2uint((float)*(ptrs++)), + i2 = cimg::float2uint((float)*(ptrs++)), + i3 = cimg::float2uint((float)*(ptrs++)); + if (nb_inds==12) ptrs+=8; + if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in " + "quadrangle primitive [%u]", + nb_points,nb_primitives,i0,i1,i2,i3,p); + return false; + } + } break; + default : + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines an invalid primitive [%u] of size %u", + nb_points,nb_primitives,p,nb_inds); + return false; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete primitive data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,p,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of color data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no color/texture data", + nb_points,nb_primitives); + return false; + } + for (unsigned int c = 0; c=c) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared sprite/texture indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,c); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete color/texture data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,c,(unsigned int)(ptrs - ptre)); + return false; + } + } + + // Check consistency of opacity data. + if (ptrs==ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) defines no opacity data", + nb_points,nb_primitives); + return false; + } + for (unsigned int o = 0; o=o) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) refers to invalid shared opacity indice %u " + "for primitive [%u]", + nb_points,nb_primitives,w,o); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete opacity data for primitive [%u]", + nb_points,nb_primitives,o); + return false; + } + } + + // Check end of data. + if (ptrs1?"s":""); + return false; + } + return true; + } + + static bool _is_CImg3d(const T val, const char c) { + return val>=(T)c && val<(T)(c + 1); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + // Define the math formula parser/compiler and expression evaluator. + struct _cimg_math_parser { + CImg mem; + CImg memtype; + CImgList _code, &code, code_begin, code_end; + 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; + + CImg _img_stats, &img_stats, constcache_vals; + CImgList _list_stats, &list_stats, _list_median, &list_median; + 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; + ulongT 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_vector(arg) (memtype[arg]>1) // Is vector? +#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_op(s) s_op = s; ss_op = ss +#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char) +#define _cimg_mp_check_constant(arg,n_arg,mode) check_constant(arg,n_arg,mode,ss,se,saved_char) +#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char) +#define _cimg_mp_check_list(is_out) check_list(is_out,ss,se,saved_char) +#define _cimg_mp_defunc(mp) (*(mp_func)(*(mp).opcode))(mp) +#define _cimg_mp_return(x) { *se = saved_char; s_op = previous_s_op; ss_op = previous_ss_op; return x; } +#define _cimg_mp_return_nan() _cimg_mp_return(_cimg_mp_slot_nan) +#define _cimg_mp_constant(val) _cimg_mp_return(constant((double)(val))) +#define _cimg_mp_scalar0(op) _cimg_mp_return(scalar0(op)) +#define _cimg_mp_scalar1(op,i1) _cimg_mp_return(scalar1(op,i1)) +#define _cimg_mp_scalar2(op,i1,i2) _cimg_mp_return(scalar2(op,i1,i2)) +#define _cimg_mp_scalar3(op,i1,i2,i3) _cimg_mp_return(scalar3(op,i1,i2,i3)) +#define _cimg_mp_scalar4(op,i1,i2,i3,i4) _cimg_mp_return(scalar4(op,i1,i2,i3,i4)) +#define _cimg_mp_scalar5(op,i1,i2,i3,i4,i5) _cimg_mp_return(scalar5(op,i1,i2,i3,i4,i5)) +#define _cimg_mp_scalar6(op,i1,i2,i3,i4,i5,i6) _cimg_mp_return(scalar6(op,i1,i2,i3,i4,i5,i6)) +#define _cimg_mp_scalar7(op,i1,i2,i3,i4,i5,i6,i7) _cimg_mp_return(scalar7(op,i1,i2,i3,i4,i5,i6,i7)) +#define _cimg_mp_vector1_v(op,i1) _cimg_mp_return(vector1_v(op,i1)) +#define _cimg_mp_vector2_sv(op,i1,i2) _cimg_mp_return(vector2_sv(op,i1,i2)) +#define _cimg_mp_vector2_vs(op,i1,i2) _cimg_mp_return(vector2_vs(op,i1,i2)) +#define _cimg_mp_vector2_vv(op,i1,i2) _cimg_mp_return(vector2_vv(op,i1,i2)) +#define _cimg_mp_vector3_vss(op,i1,i2,i3) _cimg_mp_return(vector3_vss(op,i1,i2,i3)) + + // Constructors / Destructors. + ~_cimg_math_parser() { + cimg::srand(rng); + } + + _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), + rng((cimg::_rand(),cimg::rng())),calling_function(funcname?funcname:"cimg_math_parser") { +#ifdef cimg_use_openmp + 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 && (cimg::is_blank(*_expression) || *_expression==';')) ++_expression; + CImg::string(_expression).move_to(expr); + char *ps = &expr.back() - 1; + 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 (!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_slot_nan 29 +#define _cimg_mp_slot_x 30 +#define _cimg_mp_slot_y 31 +#define _cimg_mp_slot_z 32 +#define _cimg_mp_slot_c 33 + + mem.assign(96); + for (unsigned int i = 0; i<=10; ++i) mem[i] = (double)i; // mem[0-10] = 0...10 + for (unsigned int i = 1; i<=5; ++i) mem[i + 10] = -(double)i; // mem[11-15] = -1...-5 + mem[16] = 0.5; + mem[17] = 0; // thread_id + mem[18] = (double)imgin._width; // w + mem[19] = (double)imgin._height; // h + mem[20] = (double)imgin._depth; // d + mem[21] = (double)imgin._spectrum; // s + mem[22] = (double)imgin._is_shared; // r + mem[23] = (double)imgin._width*imgin._height; // wh + mem[24] = (double)imgin._width*imgin._height*imgin._depth; // whd + mem[25] = (double)imgin._width*imgin._height*imgin._depth*imgin._spectrum; // whds + mem[26] = (double)listin._width; // l + mem[27] = std::exp(1.); // 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 = compile-time constant | N>1 = constant ptr to vector[N-1] }. + memtype.assign(mem._width,1,1,1,0); + for (unsigned int i = 0; i<_cimg_mp_slot_x; ++i) memtype[i] = 1; + memtype[17] = 0; + memtype[_cimg_mp_slot_x] = memtype[_cimg_mp_slot_y] = memtype[_cimg_mp_slot_z] = memtype[_cimg_mp_slot_c] = -2; + mempos = _cimg_mp_slot_c + 1; + variable_pos.assign(8); + + reserved_label.assign(128,1,1,1,~0U); + // reserved_label[4-28] are used to store these two-char variables: + // [0] = wh, [1] = whd, [2] = whds, [3] = pi, [4] = im, [5] = iM, [6] = ia, [7] = iv, + // [8] = is, [9] = ip, [10] = ic, [11] = xm, [12] = ym, [13] = zm, [14] = cm, [15] = xM, + // [16] = yM, [17] = zM, [18]=cM, [19]=i0...[28]=i9, [29] = interpolation, [30] = boundary + + // Compile expression into a serie of opcodes. + s_op = ""; ss_op = expr._data; + const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0,false); + if (!_cimg_mp_is_constant(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(); + } + + // 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(); + level.assign(); + variable_pos.assign(); + reserved_label.assign(); + expr.assign(); + pexpr.assign(); + opcode.assign(); + opcode._is_shared = true; + + // Execute begin() bloc 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_begin.end(); + for (p_code = code_begin; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + p_code_end = code.end(); + } + + _cimg_math_parser(): + code(_code),p_code_end(0),p_break((CImg*)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), + 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)), + rng((cimg::_rand(),cimg::rng())),calling_function(0) { +#ifdef cimg_use_openmp + mem[17] = 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) { + 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'.", + pixel_type(),_cimg_mp_calling_function, + (ss - 4)>expr._data?"...":"", + (ss - 4)>expr._data?ss - 4:expr._data, + se<&expr.back()?"...":""); + } + char c1, c2, c3, c4; + + // Simplify expression when possible. + do { + c2 = 0; + if (ssss && (cimg::is_blank(c1 = *(se - 1)) || c1==';')) --se; + } + while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) { + ++ss; --se; c2 = 1; + } + } while (c2 && ss::%s: %s%s Missing %s, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", + *s_op=='F'?"argument":"item", + (ss_op - 4)>expr._data?"...":"", + (ss_op - 4)>expr._data?ss_op - 4:expr._data, + ss_op + std::strlen(ss_op)<&expr.back()?"...":""); + } + + const char *const previous_s_op = s_op, *const previous_ss_op = ss_op; + const unsigned int depth1 = depth + 1; + unsigned int pos, p1, p2, p3, arg1, arg2, arg3, arg4, arg5, arg6; + char + *const se1 = se - 1, *const se2 = se - 2, *const se3 = se - 3, + *const ss1 = ss + 1, *const ss2 = ss + 2, *const ss3 = ss + 3, *const ss4 = ss + 4, + *const ss5 = ss + 5, *const ss6 = ss + 6, *const ss7 = ss + 7, *const ss8 = ss + 8, + *s, *ps, *ns, *s0, *s1, *s2, *s3, sep = 0, end = 0; + double val = 0, val1, val2; + mp_func op; + + // 'p_ref' is a 'unsigned int[7]' used to return a reference to an image or vector value + // linked to the returned memory slot (reference that cannot be determined at compile time). + // p_ref[0] can be { 0 = scalar (unlinked) | 1 = vector value | 2 = image value (offset) | + // 3 = image value (coordinates) | 4 = image value as a vector (offsets) | + // 5 = image value as a vector (coordinates) }. + // Depending on p_ref[0], the remaining p_ref[k] have the following meaning: + // When p_ref[0]==0, p_ref is actually unlinked. + // When p_ref[0]==1, p_ref = [ 1, vector_ind, offset ]. + // When p_ref[0]==2, p_ref = [ 2, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==3, p_ref = [ 3, image_ind (or ~0U), is_relative, x, y, z, c ]. + // When p_ref[0]==4, p_ref = [ 4, image_ind (or ~0U), is_relative, offset ]. + // When p_ref[0]==5, p_ref = [ 5, image_ind (or ~0U), is_relative, x, y, z ]. + if (p_ref) { *p_ref = 0; p_ref[1] = p_ref[2] = p_ref[3] = p_ref[4] = p_ref[5] = p_ref[6] = ~0U; } + + const char saved_char = *se; *se = 0; + const unsigned int clevel = level[ss - expr._data], clevel1 = clevel + 1; + bool is_sth, is_relative; + CImg ref; + CImg variable_name; + CImgList l_opcode; + + // Look for a single value or a pre-defined variable. + 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=='-'); + 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) 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 (ss1==se) switch (*ss) { // One-char reserved variable + case 'c' : _cimg_mp_return(reserved_label['c']!=~0U?reserved_label['c']:_cimg_mp_slot_c); + case 'd' : _cimg_mp_return(reserved_label['d']!=~0U?reserved_label['d']:20); + case 'e' : _cimg_mp_return(reserved_label['e']!=~0U?reserved_label['e']:27); + case 'h' : _cimg_mp_return(reserved_label['h']!=~0U?reserved_label['h']:19); + case 'l' : _cimg_mp_return(reserved_label['l']!=~0U?reserved_label['l']:26); + case 'r' : _cimg_mp_return(reserved_label['r']!=~0U?reserved_label['r']:22); + case 's' : _cimg_mp_return(reserved_label['s']!=~0U?reserved_label['s']:21); + case 't' : _cimg_mp_return(reserved_label['t']!=~0U?reserved_label['t']:17); + case 'w' : _cimg_mp_return(reserved_label['w']!=~0U?reserved_label['w']:18); + case 'x' : _cimg_mp_return(reserved_label['x']!=~0U?reserved_label['x']:_cimg_mp_slot_x); + case 'y' : _cimg_mp_return(reserved_label['y']!=~0U?reserved_label['y']:_cimg_mp_slot_y); + case 'z' : _cimg_mp_return(reserved_label['z']!=~0U?reserved_label['z']:_cimg_mp_slot_z); + case 'u' : + if (reserved_label['u']!=~0U) _cimg_mp_return(reserved_label['u']); + _cimg_mp_scalar2(mp_u,0,1); + case 'g' : + if (reserved_label['g']!=~0U) _cimg_mp_return(reserved_label['g']); + _cimg_mp_scalar0(mp_g); + case 'i' : + if (reserved_label['i']!=~0U) _cimg_mp_return(reserved_label['i']); + _cimg_mp_scalar0(mp_i); + case 'I' : + _cimg_mp_op("Variable 'I'"); + if (reserved_label['I']!=~0U) _cimg_mp_return(reserved_label['I']); + 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); + _cimg_mp_return(pos); + case 'R' : + if (reserved_label['R']!=~0U) _cimg_mp_return(reserved_label['R']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,0,0); + case 'G' : + if (reserved_label['G']!=~0U) _cimg_mp_return(reserved_label['G']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,0,0); + case 'B' : + if (reserved_label['B']!=~0U) _cimg_mp_return(reserved_label['B']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,0,0); + case 'A' : + if (reserved_label['A']!=~0U) _cimg_mp_return(reserved_label['A']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,0,0); + } + else if (ss2==se) { // Two-chars reserved variable + arg1 = arg2 = ~0U; + if (*ss=='w' && *ss1=='h') // wh + _cimg_mp_return(reserved_label[0]!=~0U?reserved_label[0]:23); + if (*ss=='p' && *ss1=='i') // pi + _cimg_mp_return(reserved_label[3]!=~0U?reserved_label[3]:28); + if (*ss=='i') { + if (*ss1>='0' && *ss1<='9') { // i0...i9 + pos = 19 + *ss1 - '0'; + if (reserved_label[pos]!=~0U) _cimg_mp_return(reserved_label[pos]); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 19,0,0); + } + switch (*ss1) { + case 'm' : arg1 = 4; arg2 = 0; break; // im + case 'M' : arg1 = 5; arg2 = 1; break; // iM + case 'a' : arg1 = 6; arg2 = 2; break; // ia + case 'v' : arg1 = 7; arg2 = 3; break; // iv + case 's' : arg1 = 8; arg2 = 12; break; // is + case 'p' : arg1 = 9; arg2 = 13; break; // 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; + _cimg_mp_return(mem_img_median); + break; + } + } + else if (*ss1=='m') switch (*ss) { + case 'x' : arg1 = 11; arg2 = 4; break; // xm + case 'y' : arg1 = 12; arg2 = 5; break; // ym + case 'z' : arg1 = 13; arg2 = 6; break; // zm + case 'c' : arg1 = 14; arg2 = 7; break; // cm + } + else if (*ss1=='M') switch (*ss) { + case 'x' : arg1 = 15; arg2 = 8; break; // xM + case 'y' : arg1 = 16; arg2 = 9; break; // yM + case 'z' : arg1 = 17; arg2 = 10; break; // zM + case 'c' : arg1 = 18; arg2 = 11; break; // cM + } + if (arg1!=~0U) { + if (reserved_label[arg1]!=~0U) _cimg_mp_return(reserved_label[arg1]); + if (!img_stats) { + img_stats.assign(1,14,1,1,0).fill(imgin.get_stats(),false); + mem_img_stats.assign(1,14,1,1,~0U); + } + if (mem_img_stats[arg2]==~0U) mem_img_stats[arg2] = constant(img_stats[arg2]); + _cimg_mp_return(mem_img_stats[arg2]); + } + } else if (ss3==se) { // Three-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d') // whd + _cimg_mp_return(reserved_label[1]!=~0U?reserved_label[1]:24); + } else if (ss4==se) { // Four-chars reserved variable + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s') // whds + _cimg_mp_return(reserved_label[2]!=~0U?reserved_label[2]:25); + } + + pos = ~0U; + is_sth = false; + for (s0 = ss, s = ss1; s='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg2)?4:2; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + if (_cimg_mp_is_vector(arg2)) + set_variable_vector(arg2); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + } + + + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), + arg2,p1,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), + arg2,p1,arg1,_cimg_mp_size(arg2)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg1).move_to(code); + else if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg1).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg1,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ss1=='(' && *ve1==')') { // i/j/I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) is_parallelizable = false; + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + } else if (s1='i'?1:3,p2); + + if (p_ref) { + *p_ref = _cimg_mp_is_vector(arg5)?5:3; + p_ref[1] = p1; + p_ref[2] = (unsigned int)is_relative; + p_ref[3] = arg1; + p_ref[4] = arg2; + p_ref[5] = arg3; + p_ref[6] = arg4; + if (_cimg_mp_is_vector(arg5)) + set_variable_vector(arg5); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg5)) memtype[arg5] = -2; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; + if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2; + if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -2; + } + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg5,p1,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), + arg5,p1,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), + arg5,p1,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg5); + if (*ss>='i') + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg5,arg1,arg2,arg3,arg4).move_to(code); + else if (_cimg_mp_is_scalar(arg5)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg5,arg1,arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg5,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); + } + _cimg_mp_return(arg5); + } + } + + // Assign vector value (direct). + if (l_variable_name>3 && *ve1==']' && *ss!='[') { + s0 = ve1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + is_sth = true; // is_valid_variable_name? + if (*ss>='0' && *ss<='9') is_sth = false; + else for (ns = ss; nsss) { + 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 + _cimg_mp_check_type(arg3,2,1,0); + + if (variable_name[1]) { // Multi-char variable + cimglist_for(variable_def,i) if (!std::strcmp(variable_name,variable_def[i])) { + arg1 = variable_pos[i]; break; + } + } else arg1 = reserved_label[*variable_name]; // Single-char variable + if (arg1==~0U) compile(ss,s0 - 1,depth1,0,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); + } + } + } + + // 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 + 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 && cimg::is_blank(*s)) ++s; + CImg(s,(unsigned int)(se - s + 1)).move_to(macro_body,0); + + p1 = 1; // Indice of current parsed argument + for (s = s0 + 1; s<=s1; ++p1, s = ns + 1) { // Parse function arguments + if (p1>24) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Too much specified arguments (>24) in macro " + "definition '%s()', in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + while (*s && 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; nsexpr._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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + is_sth?"Empty":"Invalid",p1, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (ns==s1 || *ns==',') { // New argument found + *s3 = 0; + p2 = (unsigned int)(s3 - s2); // Argument length + for (ps = std::strstr(macro_body[0],s2); ps; ps = std::strstr(ps,s2)) { // Replace by arg number + if (!((ps>macro_body[0]._data && is_varchar(*(ps - 1))) || + (ps + 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 (cimg::is_blank(*s0)) ++s0; + variable_name.resize(variable_name.end() - s0,1,1,1,0,0,1); + } + + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + // Assign variable (direct). + if (is_sth) { + arg3 = variable_name[1]?~0U:*variable_name; // One-char variable + if (variable_name[1] && !variable_name[2]) { // Two-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + if (c1=='w' && c2=='h') arg3 = 0; // wh + else if (c1=='p' && c2=='i') arg3 = 3; // pi + else if (c1=='i') { + if (c2>='0' && c2<='9') arg3 = 19 + c2 - '0'; // i0...i9 + else if (c2=='m') arg3 = 4; // im + else if (c2=='M') arg3 = 5; // iM + else if (c2=='a') arg3 = 6; // ia + else if (c2=='v') arg3 = 7; // iv + else if (c2=='s') arg3 = 8; // is + else if (c2=='p') arg3 = 9; // ip + else if (c2=='c') arg3 = 10; // ic + } else if (c2=='m') { + if (c1=='x') arg3 = 11; // xm + else if (c1=='y') arg3 = 12; // ym + else if (c1=='z') arg3 = 13; // zm + else if (c1=='c') arg3 = 14; // cm + } else if (c2=='M') { + if (c1=='x') arg3 = 15; // xM + else if (c1=='y') arg3 = 16; // yM + else if (c1=='z') arg3 = 17; // zM + else if (c1=='c') arg3 = 18; // cM + } + } else if (variable_name[1] && variable_name[2] && !variable_name[3]) { // Three-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + if (c1=='w' && c2=='h' && c3=='d') arg3 = 1; // whd + } else if (variable_name[1] && variable_name[2] && variable_name[3] && + !variable_name[4]) { // Four-chars variable + c1 = variable_name[0]; + c2 = variable_name[1]; + c3 = variable_name[2]; + c4 = variable_name[3]; + if (c1=='w' && c2=='h' && c3=='d' && c4=='s') arg3 = 2; // whds + } else if (!std::strcmp(variable_name,"interpolation")) arg3 = 29; // interpolation + else if (!std::strcmp(variable_name,"boundary")) arg3 = 30; // boundary + + arg1 = ~0U; + arg2 = compile(s + 1,se,depth1,0,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; } + + if (arg1==~0U) { // Create new variable + if (_cimg_mp_is_vector(arg2)) { // Vector variable + arg1 = is_comp_vector(arg2)?arg2:vector_copy(arg2); + set_variable_vector(arg1); + } else { // Scalar variable + if (is_const) arg1 = arg2; + else { + arg1 = _cimg_mp_is_comp(arg2)?arg2:scalar1(mp_copy,arg2); + memtype[arg1] = -1; + } + } + + if (arg3!=~0U) reserved_label[arg3] = arg1; + else { + if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); + variable_pos[variable_def._width] = arg1; + variable_name.move_to(variable_def); + } + + } else { // Variable already exists -> assign a new value + if (is_const || _cimg_mp_is_constant(arg1)) { + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid assignment of %sconst variable '%s'%s, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"already-defined ":"non-", + variable_name._data, + !_cimg_mp_is_constant(arg1) && is_const?" as a new const variable":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + _cimg_mp_check_type(arg2,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_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)). + move_to(code); + else // From scalar + CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg2). + move_to(code); + } else // Scalar + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + } + _cimg_mp_return(arg1); + } + + // Assign lvalue (variable name was not valid for a direct assignment). + arg1 = ~0U; + is_sth = (bool)std::strchr(variable_name,'?'); // Contains_ternary_operator? + if (is_sth) break; // Do nothing and make ternary operator prioritary over assignment + + if (l_variable_name>2 && (std::strchr(variable_name,'(') || std::strchr(variable_name,'['))) { + ref.assign(7); + arg1 = compile(ss,s,depth1,ref,is_single); // Lvalue slot + arg2 = compile(s + 1,se,depth1,0,is_single); // 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). + move_to(code); + _cimg_mp_return(arg2); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg2,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg2,arg3).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) = scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), + arg2,p1,arg3,arg4,arg5,arg6).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg2); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg2,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg2,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg2,arg3,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) = value + if (!is_single) 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 (p1!=~0U) { + if (!listout) _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 + 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); + } else { + if (!imgout) _cimg_mp_return(arg2); + if (_cimg_mp_is_scalar(arg2)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg2,arg3,arg4,arg5).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg2,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); + } + _cimg_mp_return(arg2); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V = value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) // From vector + CImg::vector((ulongT)mp_vector_copy,arg1,arg2,(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). + move_to(code); + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s = scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + } + + // No assignment expressions match -> error + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + arg1!=~0U && _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Apply unary/binary/ternary operators. The operator precedences should be the same as in C++. + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='*' || *ps=='/' || *ps=='^') && *ns==*ps && + level[s - expr._data]==clevel) { // Self-operators for complex numbers only (**=,//=,^^=) + _cimg_mp_op(*ps=='*'?"Operator '**='":*ps=='/'?"Operator '//='":"Operator '^^='"); + + ref.assign(7); + arg1 = compile(ss,ns,depth1,ref,is_single); // Vector slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Right operand + _cimg_mp_check_type(arg1,1,2,2); + _cimg_mp_check_type(arg2,2,3,2); + if (_cimg_mp_is_vector(arg2)) { // Complex **= complex + if (*ps=='*') + CImg::vector((ulongT)mp_complex_mul,arg1,arg1,arg2).move_to(code); + else if (*ps=='/') + CImg::vector((ulongT)mp_complex_div_vv,arg1,arg1,arg2).move_to(code); + else + CImg::vector((ulongT)mp_complex_pow_vv,arg1,arg1,arg2).move_to(code); + } else { // Complex **= scalar + if (*ps=='*') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_mul,arg2); + } else if (*ps=='/') { + if (arg2==1) _cimg_mp_return(arg1); + self_vector_s(arg1,mp_self_div,arg2); + } else { + if (arg2==1) _cimg_mp_return(arg1); + CImg::vector((ulongT)mp_complex_pow_vs,arg1,arg1,arg2).move_to(code); + } + } + + // Write computed value back in image if necessary. + if (*ref==4) { // Image value (vector): I/J[_#ind,off] **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + + } else if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) **= value + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + } + + _cimg_mp_return(arg1); + } + + for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 + if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || *ps=='%' || + *ps=='&' || *ps=='^' || *ps=='|' || + (*ps=='>' && *ns=='>') || (*ps=='<' && *ns=='<')) && + level[s - expr._data]==clevel) { // Self-operators (+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=) + switch (*ps) { + case '+' : op = mp_self_add; _cimg_mp_op("Operator '+='"); break; + case '-' : op = mp_self_sub; _cimg_mp_op("Operator '-='"); break; + case '*' : op = mp_self_mul; _cimg_mp_op("Operator '*='"); break; + case '/' : op = mp_self_div; _cimg_mp_op("Operator '/='"); break; + case '%' : op = mp_self_modulo; _cimg_mp_op("Operator '%='"); break; + case '<' : op = mp_self_bitwise_left_shift; _cimg_mp_op("Operator '<<='"); break; + case '>' : op = mp_self_bitwise_right_shift; _cimg_mp_op("Operator '>>='"); break; + case '&' : op = mp_self_bitwise_and; _cimg_mp_op("Operator '&='"); break; + case '|' : op = mp_self_bitwise_or; _cimg_mp_op("Operator '|='"); break; + default : op = mp_self_pow; _cimg_mp_op("Operator '^='"); break; + } + s1 = *ps=='>' || *ps=='<'?ns:ps; + + ref.assign(7); + arg1 = compile(ss,s1,depth1,ref,is_single); // Variable slot + arg2 = compile(s + 1,se,depth1,0,is_single); // Value to apply + + // Check for particular case to be simplified. + if ((op==mp_self_add || op==mp_self_sub) && !arg2) _cimg_mp_return(arg1); + if ((op==mp_self_mul || op==mp_self_div) && arg2==1) _cimg_mp_return(arg1); + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k] += scalar + _cimg_mp_check_type(arg2,2,1,0); + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(arg1); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off] += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) += scalar + if (!is_single) is_parallelizable = false; + _cimg_mp_check_type(arg2,2,1,0); + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off] += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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); + } + _cimg_mp_return(arg1); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) += value + if (!is_single) 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 (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(arg1); + CImg::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) _cimg_mp_return(arg1); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V += value + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (_cimg_mp_is_vector(arg2)) self_vector_v(arg1,op,arg2); // Vector += vector + else self_vector_s(arg1,op,arg2); // Vector += scalar + _cimg_mp_return(arg1); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s += scalar + _cimg_mp_check_type(arg2,2,1,0); + CImg::vector((ulongT)op,arg1,arg2).move_to(code); + _cimg_mp_return(arg1); + } + + variable_name.assign(ss,(unsigned int)(s - ss)).back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + for (s = ss1; s::vector((ulongT)mp_if,pos,arg1,arg2,arg3, + p3 - p2,code._width - p3,arg4).move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='|' && *ns=='|' && level[s - expr._data]==clevel) { // Logical or ('||') + _cimg_mp_op("Operator '||'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] || mem[arg2]); + if (!arg1) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_or,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='&' && *ns=='&' && level[s - expr._data]==clevel) { // Logical and ('&&') + _cimg_mp_op("Operator '&&'"); + arg1 = compile(ss,s,depth1,0,is_single); + _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); + _cimg_mp_check_type(arg2,2,1,0); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant(mem[arg1] && mem[arg2]); + if (arg1>0 && arg1<=16) _cimg_mp_return(arg2); + pos = scalar(); + CImg::vector((ulongT)mp_logical_and,pos,arg1,arg2,code._width - p2). + move_to(code,p2); + _cimg_mp_return(pos); + } + + for (s = se2; s>ss; --s) + if (*s=='|' && level[s - expr._data]==clevel) { // Bitwise or ('|') + _cimg_mp_op("Operator '|'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_vector2_sv(mp_bitwise_or,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1] | (longT)mem[arg2]); + if (!arg2) _cimg_mp_return(arg1); + if (!arg1) _cimg_mp_return(arg2); + _cimg_mp_scalar2(mp_bitwise_or,arg1,arg2); + } + + for (s = se2; s>ss; --s) + if (*s=='&' && level[s - expr._data]==clevel) { // Bitwise and ('&') + _cimg_mp_op("Operator '&'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_bitwise_and,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='!' && *ns=='=' && level[s - expr._data]==clevel) { // Not equal to ('!=') + _cimg_mp_op("Operator '!='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]!=mem[arg2]); + _cimg_mp_scalar2(mp_neq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='=' && *ns=='=' && level[s - expr._data]==clevel) { // Equal to ('==') + _cimg_mp_op("Operator '=='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + 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); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]==mem[arg2]); + _cimg_mp_scalar2(mp_eq,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='=' && level[s - expr._data]==clevel) { // Less or equal than ('<=') + _cimg_mp_op("Operator '<='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_lte,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='=' && level[s - expr._data]==clevel) { // Greater or equal than ('>=') + _cimg_mp_op("Operator '>='"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(1); + _cimg_mp_scalar2(mp_gte,arg1,arg2); + } + + for (s = se2, ns = se1, ps = se3; s>ss; --s, --ns, --ps) + if (*s=='<' && *ns!='<' && *ps!='<' && level[s - expr._data]==clevel) { // Less than ('<') + _cimg_mp_op("Operator '<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 ('>') + _cimg_mp_op("Operator '>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (arg1==arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_gt,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='<' && *ns=='<' && level[s - expr._data]==clevel) { // Left bit shift ('<<') + _cimg_mp_op("Operator '<<'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_left_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_left_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]<<(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_left_shift,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='>' && *ns=='>' && level[s - expr._data]==clevel) { // Right bit shift ('>>') + _cimg_mp_op("Operator '>>'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_vector2_vs(mp_bitwise_right_shift,arg1,arg2); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) + _cimg_mp_vector2_sv(mp_bitwise_right_shift,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) + _cimg_mp_constant((longT)mem[arg1]>>(unsigned int)mem[arg2]); + if (!arg1) _cimg_mp_return(0); + if (!arg2) _cimg_mp_return(arg1); + _cimg_mp_scalar2(mp_bitwise_right_shift,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='+' && (*ns!='+' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Addition ('+') + _cimg_mp_op("Operator '+'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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' + 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]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_linear_add,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_increment,arg1); + if (arg1==1) _cimg_mp_scalar1(mp_increment,arg2); + _cimg_mp_scalar2(mp_add,arg1,arg2); + } + + for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) + if (*s=='-' && (*ns!='-' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && + *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && + (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && + *(ps - 1)<='9')))) && + level[s - expr._data]==clevel) { // Subtraction ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_sub,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + if (!arg1) _cimg_mp_vector1_v(mp_minus,arg2); + _cimg_mp_vector2_sv(mp_sub,arg1,arg2); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] - mem[arg2]); + if (!arg1) _cimg_mp_scalar1(mp_minus,arg2); + if (code) { // Try to spot linear cases 'a*b - c' and 'c - a*b' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)(arg3==arg1?mp_linear_sub_left:mp_linear_sub_right), + arg3,arg4,arg5,arg3==arg1?arg2:arg1).move_to(code); + _cimg_mp_return(arg3); + } + } + if (arg2==1) _cimg_mp_scalar1(mp_decrement,arg1); + _cimg_mp_scalar2(mp_sub,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='*' && *ns=='*' && level[s - expr._data]==clevel) { // Complex multiplication ('**') + _cimg_mp_op("Operator '**'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + pos = vector(2); + CImg::vector((ulongT)mp_complex_mul,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='/' && *ns=='/' && level[s - expr._data]==clevel) { // Complex division ('//') + _cimg_mp_op("Operator '//'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _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); + _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); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]); + if (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='*' && level[s - expr._data]==clevel) { // Multiplication ('*') + _cimg_mp_op("Operator '*'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + p2 = _cimg_mp_size(arg2); + if (p2>0 && _cimg_mp_size(arg1)==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); + _cimg_mp_return(pos); + } + _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); + if (arg2==1) _cimg_mp_return(arg1); + if (arg1==1) _cimg_mp_return(arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_mul,arg1,arg2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); + if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); + + if (code) { // Try to spot double multiplication 'a*b*c' + CImg &pop = code.back(); + if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { + arg3 = (unsigned int)pop[1]; + arg4 = (unsigned int)pop[2]; + arg5 = (unsigned int)pop[3]; + code.remove(); + CImg::vector((ulongT)mp_mul2,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); + _cimg_mp_return(arg3); + } + } + if (!arg1 || !arg2) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_mul,arg1,arg2); + } + + for (s = se2; s>ss; --s) if (*s=='/' && level[s - expr._data]==clevel) { // Division ('/') + _cimg_mp_op("Operator '/'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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 (!arg1) _cimg_mp_return(0); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + for (s = se2, ns = se1; s>ss; --s, --ns) + if (*s=='%' && *ns!='^' && level[s - expr._data]==clevel) { // Modulo ('%') + _cimg_mp_op("Operator '%'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 1,se,depth1,0,is_single); + _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])); + _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)); + } + + if (*ss=='-' && (*ss1!='-' || (ss2='0' && *ss2<='9'))) { // Unary minus ('-') + _cimg_mp_op("Operator '-'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_minus,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(-mem[arg1]); + _cimg_mp_scalar1(mp_minus,arg1); + } + + if (*ss=='!') { // Logical not ('!') + _cimg_mp_op("Operator '!'"); + if (*ss1=='!') { // '!!expr' optimized as 'bool(expr)' + arg1 = compile(ss2,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_logical_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(!mem[arg1]); + _cimg_mp_scalar1(mp_logical_not,arg1); + } + + if (*ss=='~') { // Bitwise not ('~') + _cimg_mp_op("Operator '~'"); + arg1 = compile(ss1,se,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bitwise_not,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(~(unsigned int)mem[arg1]); + _cimg_mp_scalar1(mp_bitwise_not,arg1); + } + } + + for (s = se3, ns = se2; s>ss; --s, --ns) + if (*s=='^' && *ns=='^' && level[s - expr._data]==clevel) { // Complex power ('^^') + _cimg_mp_op("Operator '^^'"); + arg1 = compile(ss,s,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,3,2); + _cimg_mp_check_type(arg2,2,3,2); + if (arg2==1) _cimg_mp_return(arg1); + pos = vector(2); + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vv,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { + CImg::vector((ulongT)mp_complex_pow_vs,pos,arg1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { + CImg::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); + _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); + _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])); + switch (arg2) { + case 0 : _cimg_mp_return(1); + case 2 : _cimg_mp_scalar1(mp_sqr,arg1); + case 3 : _cimg_mp_scalar1(mp_pow3,arg1); + case 4 : _cimg_mp_scalar1(mp_pow4,arg1); + default : + if (_cimg_mp_is_constant(arg2)) { + if (mem[arg2]==0.5) { _cimg_mp_scalar1(mp_sqrt,arg1); } + else if (mem[arg2]==0.25) { _cimg_mp_scalar1(mp_pow0_25,arg1); } + } + _cimg_mp_scalar2(mp_pow,arg1,arg2); + } + } + + // Percentage computation. + if (*se1=='%') { + arg1 = compile(ss,se1,depth1,0,is_single); + arg2 = _cimg_mp_is_constant(arg1)?0:constant(100); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(mp_div,arg1,arg2); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(mem[arg1]/100); + _cimg_mp_scalar2(mp_div,arg1,arg2); + } + + is_sth = ss1ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { // Pre/post-decrement and increment + if ((is_sth && *ss=='+') || (!is_sth && *se1=='+')) { + _cimg_mp_op("Operator '++'"); + op = mp_self_increment; + } else { + _cimg_mp_op("Operator '--'"); + op = mp_self_decrement; + } + ref.assign(7); + arg1 = is_sth?compile(ss2,se,depth1,ref,is_single): + compile(ss,se2,depth1,ref,is_single); // Variable slot + + // Apply operator on a copy to prevent modifying a constant or a variable. + if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { + if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); + else arg1 = scalar1(mp_copy,arg1); + } + + if (is_sth) pos = arg1; // Determine return indice, depending on pre/post action + else { + if (_cimg_mp_is_vector(arg1)) pos = vector_copy(arg1); + else pos = scalar1(mp_copy,arg1); + } + + if (*ref==1) { // Vector value (scalar): V[k]++ + arg3 = ref[1]; // Vector slot + arg4 = ref[2]; // Index + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1,1).move_to(code); + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + move_to(code); + _cimg_mp_return(pos); + } + + if (*ref==2) { // Image value (scalar): i/j[_#ind,off]++ + if (!is_single) 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); + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + arg6 = ref[6]; // C + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + CImg::vector((ulongT)op,arg1).move_to(code); + if (p1!=~0U) { + if (!listout) _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 { + if (!imgout) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==4) { // Image value (vector): I/J[_#ind,off]++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // Offset + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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(pos); + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg1,arg3,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c)++ + if (!is_single) is_parallelizable = false; + p1 = ref[1]; // Index + is_relative = (bool)ref[2]; + arg3 = ref[3]; // X + arg4 = ref[4]; // Y + arg5 = ref[5]; // Z + if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + if (p1!=~0U) { + if (!listout) _cimg_mp_return(pos); + CImg::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) _cimg_mp_return(pos); + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_vector(arg1)) { // Vector variable: V++ + self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); + _cimg_mp_return(pos); + } + + if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s++ + CImg::vector((ulongT)op,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (is_sth) variable_name.assign(ss2,(unsigned int)(se - ss1)); + else variable_name.assign(ss,(unsigned int)(se1 - ss)); + variable_name.back() = 0; + cimg::strpare(variable_name,false,true); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid %slvalue '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_constant(arg1)?"const ":"", + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'. + if (*se1==']' && *ss!='[') { + _cimg_mp_op("Value accessor '[]'"); + is_relative = *ss=='j' || *ss=='J'; + s0 = s1 = std::strchr(ss,'['); if (s0) { do { --s1; } while (cimg::is_blank(*s1)); cimg::swap(*s0,*++s1); } + + if ((*ss=='I' || *ss=='J') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a vector + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff), + pos,p1,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Joff:mp_Ioff), + pos,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + if ((*ss=='i' || *ss=='j') && *ss1=='[' && + (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + if (s0>ss) { // Vector value + arg1 = compile(ss,s0,depth1,0,is_single); + if (_cimg_mp_is_scalar(arg1)) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Array brackets used on non-vector variable '%s', " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + + } + s1 = s0 + 1; while (s1 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); + arg3 = (unsigned int)mem[arg3]; + pos = vector(arg3); + CImg::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + // One argument -> vector value reference + arg2 = compile(++s0,se1,depth1,0,is_single); + if (_cimg_mp_is_constant(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::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'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data,nb, + variable_name._data,_cimg_mp_size(arg1), + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + if (p_ref) { + *p_ref = 1; + p_ref[1] = arg1; + p_ref[2] = arg2; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; // Prevent from being used in further optimization + } + pos = scalar3(mp_vector_off,arg1,_cimg_mp_size(arg1),arg2); + memtype[pos] = -2; // Prevent from being used in further optimization + _cimg_mp_return(pos); + } + } + + // Look for a function call, an access to image value, or a parenthesis. + if (*se1==')') { + if (*ss=='(') _cimg_mp_return(compile(ss1,se1,depth1,p_ref,is_single)); // Simple parentheses + _cimg_mp_op("Value accessor '()'"); + is_relative = *ss=='j' || *ss=='J'; + 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 (s01) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + if (s1::vector((ulongT)(is_relative?mp_list_Jxyz:mp_list_Ixyz), + pos,p1,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + else { + need_input_copy = true; + CImg::vector((ulongT)(is_relative?mp_Jxyz:mp_Ixyz), + pos,arg1,arg2,arg3, + arg4==~0U?_cimg_mp_interpolation:arg4, + arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); + } + _cimg_mp_return(pos); + } + + // i/j(_#ind,_x,_y,_z,_c,_interpolation,_boundary_conditions) + if ((*ss=='i' || *ss=='j') && *ss1=='(') { // Image value as scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s01) { + arg2 = arg1 + 1; + if (p2>2) { + arg3 = arg2 + 1; + if (p2>3) arg4 = arg3 + 1; + } + } + if (s1::vector((ulongT)mp_arg,0,0,p2,arg1,arg2).move_to(l_opcode); + for (s = ++s2; s::vector(arg3).move_to(l_opcode); + ++p3; + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (_cimg_mp_is_constant(arg1)) { + p3-=1; // Number of args + arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]); + if (arg1::vector((ulongT)mp_break,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"breakpoint(",11)) { // Break point (for abort test) + _cimg_mp_op("Function 'breakpoint()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_breakpoint,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"bool(",5)) { // Boolean cast + _cimg_mp_op("Function 'bool()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + + if (!std::strncmp(ss,"begin(",6)) { // Begin + _cimg_mp_op("Function 'begin()'"); + code.swap(code_begin); + arg1 = compile(ss6,se1,depth1,p_ref,true); + code.swap(code_begin); + _cimg_mp_return(arg1); + } + break; + + case 'c' : + if (!std::strncmp(ss,"cabs(",5)) { // Complex absolute value + _cimg_mp_op("Function 'cabs()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_complex_abs,arg1 + 1,arg1 + 2); + } + + if (!std::strncmp(ss,"carg(",5)) { // Complex argument + _cimg_mp_op("Function 'carg()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + _cimg_mp_scalar2(mp_atan2,arg1 + 2,arg1 + 1); + } + + if (!std::strncmp(ss,"cats(",5)) { // Concatenate strings + _cimg_mp_op("Function 'cats()'"); + CImg::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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cbrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::cbrt(mem[arg1])); + _cimg_mp_scalar1(mp_cbrt,arg1); + } + + if (!std::strncmp(ss,"cconj(",6)) { // Complex conjugate + _cimg_mp_op("Function 'cconj()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_conj,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ceil(",5)) { // Ceil + _cimg_mp_op("Function 'ceil()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + pos = vector(2); + CImg::vector((ulongT)mp_complex_exp,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"clog(",5)) { // Complex logarithm + _cimg_mp_op("Function 'clog()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,0,2,2); + pos = vector(2); + CImg::vector((ulongT)mp_complex_log,pos,arg1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"continue(",9)) { // Complex absolute value + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_continue,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"copy(",5)) { // Memory copy + _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 = constant(_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); + _cimg_mp_check_type(arg6,5,1,0); + 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); + } + + if (!std::strncmp(ss,"cos(",4)) { // Cosine + _cimg_mp_op("Function 'cos()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cos,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cos(mem[arg1])); + _cimg_mp_scalar1(mp_cos,arg1); + } + + if (!std::strncmp(ss,"cosh(",5)) { // Hyperbolic cosine + _cimg_mp_op("Function 'cosh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cosh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cosh(mem[arg1])); + _cimg_mp_scalar1(mp_cosh,arg1); + } + + if (!std::strncmp(ss,"critical(",9)) { // Critical section (single thread at a time) + _cimg_mp_op("Function 'critical()'"); + p1 = code._width; + arg1 = compile(ss + 9,se1,depth1,p_ref,true); + CImg::vector((ulongT)mp_critical,arg1,code._width - p1).move_to(code,p1); + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"crop(",5)) { // Image crop + _cimg_mp_op("Function 'crop()'"); + 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); + } + 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 + 2; + break; + case 3 : + CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode); + arg1 = arg2 + 2; + break; + case 4 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,_cimg_mp_boundary). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + break; + case 5 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,opcode[4]). + move_to(opcode); + arg1 = arg2 + (is_sth?2:3); + 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 = arg2 + (is_sth?2:4); + break; + case 7 : + CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, + opcode[6]).move_to(opcode); + arg1 = arg2 + (is_sth?2:4); + 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 = arg2 + (is_sth?2:5); + break; + case 9 : + arg1 = arg2 + (is_sth?2:5); + 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); + 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()?"...":""); + } + 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); + _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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"cut(",4)) { // Cut + _cimg_mp_op("Function 'cut()'"); + s1 = ss4; while (s1val2?val2:val); + } + _cimg_mp_scalar3(mp_cut,arg1,arg2,arg3); + } + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_d,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"date(",5)) { // Current date or file date + _cimg_mp_op("Function 'date()'"); + s1 = ss5; while (s1::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_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); + *se1 = 0; + variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + ((CImg::vector((ulongT)mp_debug,arg1,0,code._width - p1), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code,p1); + *se1 = ')'; + _cimg_mp_return(arg1); + } + + if (!std::strncmp(ss,"display(",8)) { // Display memory, vector or image + _cimg_mp_op("Function 'display()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg::vector((ulongT)mp_display_memory,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + if (*ss8!='#') { // Vector + s1 = ss8; while (s1::string(ss8,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + if (_cimg_mp_is_vector(arg1)) + ((CImg::vector((ulongT)mp_vector_print,arg1,0,(ulongT)_cimg_mp_size(arg1),0), + variable_name)>'y').move_to(opcode); + else + ((CImg::vector((ulongT)mp_print,arg1,0,0), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + + ((CImg::vector((ulongT)mp_display,arg1,0,(ulongT)_cimg_mp_size(arg1), + arg2,arg3,arg4,arg5), + variable_name)>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + *s1 = c1; + _cimg_mp_return(arg1); + + } else { // Image + p1 = compile(ss8 + 1,se1,depth1,0,is_single); + _cimg_mp_check_list(true); + CImg::vector((ulongT)mp_image_display,_cimg_mp_slot_nan,p1).move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"det(",4)) { // Matrix determinant + _cimg_mp_op("Function 'det()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + } + + if (!std::strncmp(ss,"diag(",5)) { // Diagonal matrix + _cimg_mp_op("Function 'diag()'"); + 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); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"dot(",4)) { // Dot product + _cimg_mp_op("Function 'dot()'"); + s1 = ss4; while (s1::vector((ulongT)mp_do,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_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; + } else { + if (s0::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; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"eig(",4)) { // Matrix eigenvalues/eigenvector + _cimg_mp_op("Function 'eig()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + _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); + _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); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"ellipse(",8)) { // Ellipse/circle drawing + if (!is_single) 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 (!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,"fact(",5)) { // Factorial + _cimg_mp_op("Function 'fact()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + 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])); + _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); + 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])); + _cimg_mp_scalar1(mp_fibonacci,arg1); + } + + if (!std::strncmp(ss,"find(",5)) { // Find + _cimg_mp_op("Function 'find()'"); + + // First argument: data to look at. + s0 = ss5; while (s0::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); + _cimg_mp_return(p3); + } + + if (!std::strncmp(ss,"floor(",6)) { // Floor + _cimg_mp_op("Function 'floor()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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_mp_return(pos); + } + break; + + case 'g' : + if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function + _cimg_mp_op("Function 'gauss()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_h,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'i' : + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_median,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (*ss1=='f' && *ss2=='(') { // If..then[..else.] + _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); + _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); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_int,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((longT)mem[arg1]); + _cimg_mp_scalar1(mp_int,arg1); + } + + if (!std::strncmp(ss,"inv(",4)) { // Matrix/scalar inversion + _cimg_mp_op("Function 'inv()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) { + _cimg_mp_check_matrix_square(arg1,1); + p1 = (unsigned int)cimg::round(std::sqrt((float)_cimg_mp_size(arg1))); + pos = vector(p1*p1); + CImg::vector((ulongT)mp_matrix_inv,pos,arg1,p1).move_to(code); + _cimg_mp_return(pos); + } + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(1/mem[arg1]); + _cimg_mp_scalar2(mp_div,1,arg1); + } + + if (*ss1=='s') { // Family of 'is_?()' functions + + if (!std::strncmp(ss,"isbool(",7)) { // Is boolean? + _cimg_mp_op("Function 'isbool()'"); + if (ss7==se1) _cimg_mp_return(0); + arg1 = compile(ss7,se1,depth1,0,is_single); + 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. || 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); + } + + if (!std::strncmp(ss,"isfile(",7)) { // Is file? + _cimg_mp_op("Function 'isfile()'"); + *se1 = 0; + is_sth = cimg::is_file(ss7); + *se1 = ')'; + _cimg_mp_return(is_sth?1U:0U); + } + + if (!std::strncmp(ss,"isin(",5)) { // Is in sequence/vector? + if (ss5>=se1) _cimg_mp_return(0); + _cimg_mp_op("Function 'isin()'"); + pos = scalar(); + CImg::vector((ulongT)mp_isin,pos,0).move_to(l_opcode); + 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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"isinf(",6)) { // Is infinite? + _cimg_mp_op("Function 'isinf()'"); + if (ss6==se1) _cimg_mp_return(0); + arg1 = compile(ss6,se1,depth1,0,is_single); + 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])); + _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); + 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)); + _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); + 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])); + _cimg_mp_scalar1(mp_isnan,arg1); + } + + if (!std::strncmp(ss,"isval(",6)) { // Is value? + _cimg_mp_op("Function 'isval()'"); + val = 0; + if (cimg_sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1); + _cimg_mp_return(0); + } + + } + break; + + case 'l' : + if (*ss1=='(') { // Size of image list + _cimg_mp_op("Function 'l()'"); + if (ss2!=se1) break; + _cimg_mp_scalar0(mp_list_l); + } + + if (!std::strncmp(ss,"log(",4)) { // Natural logarithm + _cimg_mp_op("Function 'log()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log(mem[arg1])); + _cimg_mp_scalar1(mp_log,arg1); + } + + if (!std::strncmp(ss,"log2(",5)) { // Base-2 logarithm + _cimg_mp_op("Function 'log2()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log2,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::log2(mem[arg1])); + _cimg_mp_scalar1(mp_log2,arg1); + } + + if (!std::strncmp(ss,"log10(",6)) { // Base-10 logarithm + _cimg_mp_op("Function 'log10()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log10,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log10(mem[arg1])); + _cimg_mp_scalar1(mp_log10,arg1); + } + + if (!std::strncmp(ss,"lowercase(",10)) { // Lower case + _cimg_mp_op("Function 'lowercase()'"); + arg1 = compile(ss + 10,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_lowercase,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::lowercase(mem[arg1])); + _cimg_mp_scalar1(mp_lowercase,arg1); + } + break; + + case 'm' : + if (!std::strncmp(ss,"mul(",4)) { // Matrix multiplication + _cimg_mp_op("Function 'mul()'"); + s1 = ss4; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'n' : + if (!std::strncmp(ss,"narg(",5)) { // Number of arguments + _cimg_mp_op("Function 'narg()'"); + if (ss5>=se1) _cimg_mp_return(0); + arg1 = 0; + for (s = ss5; s::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::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); + if (arg1>0 && opcode._height==4) // Special case with one argument and p>=1 + _cimg_mp_scalar1(mp_abs,opcode[3]); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'p' : + if (!std::strncmp(ss,"permut(",7)) { // Number of permutations + _cimg_mp_op("Function 'permut()'"); + s1 = ss7; while (s1::vector((ulongT)mp_polygon,_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 (!std::strncmp(ss,"print(",6) || !std::strncmp(ss,"prints(",7)) { // Print expressions + is_sth = ss[5]=='s'; // is prints() + _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); + 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()?"...":""); + } + 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,"resize(",7)) { // Vector or image resize + _cimg_mp_op("Function 'resize()'"); + if (*ss7!='#') { // Vector + s1 = ss7; while (s1::vector((ulongT)mp_vector_resize,pos,arg2,arg1,(ulongT)_cimg_mp_size(arg1), + arg3,arg4).move_to(code); + _cimg_mp_return(pos); + + } else { // Image + if (!is_single) 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); + pos = 0; + for (s = s0; s10) { + *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 arguments, in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + pos<1?"Missing":"Too much", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + l_opcode[0].move_to(code); + _cimg_mp_return_nan(); + } + } + + if (!std::strncmp(ss,"reverse(",8)) { // Vector reverse + _cimg_mp_op("Function 'reverse()'"); + arg1 = compile(ss8,se1,depth1,0,is_single); + 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); + _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 (s11) { + arg2 = arg1 + 1; + if (p2>2) arg3 = arg2 + 1; + } + arg4 = compile(++s1,se1,depth1,0,is_single); + } else { + s2 = s1 + 1; while (s2::vector((ulongT)mp_rot3d,pos,arg1,arg2,arg3,arg4).move_to(code); + } else { // 2D rotation + _cimg_mp_check_type(arg1,1,1,0); + pos = vector(4); + CImg::vector((ulongT)mp_rot2d,pos,arg1).move_to(code); + } + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"round(",6)) { // Value rounding + _cimg_mp_op("Function 'round()'"); + s1 = ss6; while (s1::vector((ulongT)mp_image_s,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"same(",5)) { // Test if operands have the same values + _cimg_mp_op("Function 'same()'"); + s1 = ss5; while (s1::vector((ulongT)mp_shift,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sign(",5)) { // Sign + _cimg_mp_op("Function 'sign()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sign,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sign(mem[arg1])); + _cimg_mp_scalar1(mp_sign,arg1); + } + + if (!std::strncmp(ss,"sin(",4)) { // Sine + _cimg_mp_op("Function 'sin()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sin,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sin(mem[arg1])); + _cimg_mp_scalar1(mp_sin,arg1); + } + + if (!std::strncmp(ss,"sinc(",5)) { // Sine cardinal + _cimg_mp_op("Function 'sinc()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinc,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sinc(mem[arg1])); + _cimg_mp_scalar1(mp_sinc,arg1); + } + + if (!std::strncmp(ss,"sinh(",5)) { // Hyperbolic sine + _cimg_mp_op("Function 'sinh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sinh(mem[arg1])); + _cimg_mp_scalar1(mp_sinh,arg1); + } + + if (!std::strncmp(ss,"size(",5)) { // Vector size + _cimg_mp_op("Function 'size()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + _cimg_mp_constant(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_size(arg1)); + } + + if (!std::strncmp(ss,"solve(",6)) { // Solve linear system + _cimg_mp_op("Function 'solve()'"); + s1 = ss6; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg2)._data,p3, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg4*p3); + CImg::vector((ulongT)mp_solve,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"sort(",5)) { // Sort vector + _cimg_mp_op("Function 'sort()'"); + 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(); + } + } + + if (!std::strncmp(ss,"sqr(",4)) { // Square + _cimg_mp_op("Function 'sqr()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqr,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sqr(mem[arg1])); + _cimg_mp_scalar1(mp_sqr,arg1); + } + + if (!std::strncmp(ss,"sqrt(",5)) { // Square root + _cimg_mp_op("Function 'sqrt()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqrt,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sqrt(mem[arg1])); + _cimg_mp_scalar1(mp_sqrt,arg1); + } + + if (!std::strncmp(ss,"srand(",6)) { // Set RNG seed + _cimg_mp_op("Function 'srand()'"); + arg1 = ss6::vector((ulongT)mp_image_stats,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"stov(",5)) { // String to double + _cimg_mp_op("Function 'stov()'"); + s1 = ss5; 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()?"...":""); + } + pos = vector(p1 + p2 + p2*p2); + CImg::vector((ulongT)mp_matrix_svd,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 't' : + if (!std::strncmp(ss,"tan(",4)) { // Tangent + _cimg_mp_op("Function 'tan()'"); + arg1 = compile(ss4,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tan,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tan(mem[arg1])); + _cimg_mp_scalar1(mp_tan,arg1); + } + + if (!std::strncmp(ss,"tanh(",5)) { // Hyperbolic tangent + _cimg_mp_op("Function 'tanh()'"); + arg1 = compile(ss5,se1,depth1,0,is_single); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tanh,arg1); + if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tanh(mem[arg1])); + _cimg_mp_scalar1(mp_tanh,arg1); + } + + if (!std::strncmp(ss,"trace(",6)) { // Matrix trace + _cimg_mp_op("Function 'trace()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _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); + 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'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,p2, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(p3*p2); + CImg::vector((ulongT)mp_transp,pos,arg1,p2,p3).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'u' : + if (*ss1=='(') { // Random value with uniform distribution + _cimg_mp_op("Function 'u()'"); + if (*ss2==')') _cimg_mp_scalar2(mp_u,0,1); + s1 = ss2; while (s1ss6 && *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 (arg20) || + !std::strncmp(ss,"vector(",7) || + (!std::strncmp(ss,"vector",6) && ss7::sequence(arg4,arg3 + 1,arg3 + arg4).move_to(l_opcode); + arg2+=arg4; + } else { CImg::vector(arg3).move_to(l_opcode); ++arg2; } + s = ns; + } + if (arg1==~0U) arg1 = arg2; + 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); + _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)mp_vtos,pos,p1,arg1,_cimg_mp_size(arg1),arg2).move_to(code); + _cimg_mp_return(pos); + } + break; + + case 'w' : + 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); + } else { if (ss2!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_w,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_wh,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss4!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whd,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + 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); + } else { if (ss5!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_whds,pos,p1).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"while(",6)) { // While...do + _cimg_mp_op("Function 'while()'"); + s0 = *ss5=='('?ss6:ss8; + s1 = s0; while (s1::vector((ulongT)mp_while,pos,arg1,p2 - p1,code._width - p2,arg2, + pos>=arg6 && !_cimg_mp_is_constant(pos), + arg1>=arg6 && !_cimg_mp_is_constant(arg1)).move_to(code,p1); + _cimg_mp_return(pos); + } + break; + + case 'x' : + if (!std::strncmp(ss,"xor(",4)) { // Xor + _cimg_mp_op("Function 'xor()'"); + s1 = ss4; while (s1::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); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + if (is_sth) _cimg_mp_constant(op(*this)); + opcode.move_to(code); + _cimg_mp_return(pos); + } + + // No corresponding built-in function -> Look for a user-defined macro call. + s0 = strchr(ss,'('); + if (s0) { + variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; + + // Count number of specified arguments. + p1 = 0; + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { + while (*s && 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 + for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { // Parse function arguments + while (*s && cimg::is_blank(*s)) ++s; + if (*s==')' && p1==1) break; // Function has no arguments + if (p1>p2) { ++p1; break; } + ns = s; while (ns _pexpr(_expr._width); + ns = _pexpr._data; + for (ps = _expr._data, c1 = ' '; *ps; ++ps) { + if (!cimg::is_blank(*ps)) c1 = *ps; + *(ns++) = c1; + } + *ns = 0; + + CImg _level = get_level(_expr); + expr.swap(_expr); + pexpr.swap(_pexpr); + level.swap(_level); + s0 = user_macro; + user_macro = macro_def[l]; + pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref,is_single); + user_macro = s0; + level.swap(_level); + pexpr.swap(_pexpr); + expr.swap(_expr); + _cimg_mp_return(pos); + } + + if (arg3) { // Macro name matched but number of arguments does not + CImg sig_nargs(arg3); + arg1 = 0; + cimglist_for(macro_def,l) if (!std::strcmp(macro_def[l],variable_name)) + sig_nargs[arg1++] = (unsigned int)macro_def[l].back(); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (sig_nargs._width>1) { + sig_nargs.sort(); + arg1 = sig_nargs.back(); + --sig_nargs._width; + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,sig_nargs.value_string()._data,arg1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } else + throw CImgArgumentException("[" cimg_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'.", + pixel_type(),_cimg_mp_calling_function,variable_name._data, + p1,*sig_nargs,*sig_nargs!=1?"s":"", + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + } + } // if (se1==')') + + // Char / string initializer. + if (*se1=='\'' && + ((se1>ss && *ss=='\'') || + (se1>ss1 && *ss=='_' && *ss1=='\''))) { + if (*ss=='_') { _cimg_mp_op("Char initializer"); s1 = ss2; } + else { _cimg_mp_op("String initializer"); s1 = ss1; } + arg1 = (unsigned int)(se1 - s1); // Original string length + if (arg1) { + CImg(s1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + if (*ss=='_') { + if (arg1==1) _cimg_mp_constant(*variable_name); + *se = saved_char; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Literal %s contains more than one character, " + "in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + ss1, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + pos = vector(arg1); + CImg::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); + _cimg_mp_return(pos); + } + + // Vector initializer [ ... ]. + if (*ss=='[' && *se1==']') { + _cimg_mp_op("Vector initializer"); + 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 + if (arg1) { + CImg(s1 + 1,arg1 + 1).move_to(variable_name).back() = 0; + cimg::strunescape(variable_name); + arg1 = (unsigned int)std::strlen(variable_name); + } + if (!arg1) _cimg_mp_return(0); // Empty string -> 0 + pos = vector(arg1); + CImg::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); + } else { // Vector values provided as list of items + arg1 = 0; // Number of specified values + if (*ss1!=']') for (s = ss1; s::sequence(arg3,arg2 + 1,arg2 + arg3).move_to(l_opcode); + arg1+=arg3; + } else { CImg::vector(arg2).move_to(l_opcode); ++arg1; } + s = ns; + } + 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); + } + _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); + _cimg_mp_return(pos); + case 'R' : // R#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0, + 0,_cimg_mp_boundary); + case 'G' : // G#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1, + 0,_cimg_mp_boundary); + case 'B' : // B#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2, + 0,_cimg_mp_boundary); + case 'A' : // A#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3, + 0,_cimg_mp_boundary); + } + } + + if (*ss1 && *ss2=='#' && ss3::vector(listin[p1].median()).move_to(list_median[p1]); + _cimg_mp_constant(*list_median[p1]); + } + _cimg_mp_scalar1(mp_list_median,arg1); + } + if (*ss1>='0' && *ss1<='9') { // i0#ind...i9#ind + if (!listin) _cimg_mp_return(0); + _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,*ss1 - '0', + 0,_cimg_mp_boundary); + } + switch (*ss1) { + case 'm' : arg2 = 0; break; // im#ind + case 'M' : arg2 = 1; break; // iM#ind + case 'a' : arg2 = 2; break; // ia#ind + case 'v' : arg2 = 3; break; // iv#ind + case 's' : arg2 = 12; break; // is#ind + case 'p' : arg2 = 13; break; // ip#ind + } + } else if (*ss1=='m') switch (*ss) { + case 'x' : arg2 = 4; break; // xm#ind + case 'y' : arg2 = 5; break; // ym#ind + case 'z' : arg2 = 6; break; // zm#ind + case 'c' : arg2 = 7; break; // cm#ind + } else if (*ss1=='M') switch (*ss) { + case 'x' : arg2 = 8; break; // xM#ind + case 'y' : arg2 = 9; break; // yM#ind + case 'z' : arg2 = 10; break; // zM#ind + case 'c' : arg2 = 11; break; // cM#ind + } + if (arg2!=~0U) { + if (!listin) _cimg_mp_return(0); + if (_cimg_mp_is_constant(arg1)) { + if (!list_stats) list_stats.assign(listin._width); + if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(listin[p1].get_stats(),false); + _cimg_mp_constant(list_stats(p1,arg2)); + } + _cimg_mp_scalar2(mp_list_stats,arg1,arg2); + } + } + + if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4 error. + is_sth = true; // is_valid_variable_name + if (*variable_name>='0' && *variable_name<='9') is_sth = false; + else for (ns = variable_name._data; *ns; ++ns) + if (!is_varchar(*ns)) { is_sth = false; break; } + + *se = saved_char; + c1 = *se1; + cimg::strellipsize(variable_name,64); + s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + if (is_sth) + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Undefined variable '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + s1 = std::strchr(ss,'('); + s_op = s1 && c1==')'?"function call":"item"; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s%s%s'.", + pixel_type(),_cimg_mp_calling_function, + s_op,variable_name._data, + s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + } + + // Evaluation procedure. + double operator()(const double x, const double y, const double z, const double c) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + return *result; + } + + // Evaluation procedure (return output values in vector 'output'). + template + void operator()(const double x, const double y, const double z, const double c, t *const output) { + mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; + for (p_code = code; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + if (result_dim) { + const double *ptrs = result + 1; + t *ptrd = output; + for (unsigned int k = 0; k_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + + // Return type of a memory element 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); + cimg_sprintf(res._data + 6,"%u",_cimg_mp_size(arg)); + } else CImg::string("scalar").move_to(res); + return res; + } + + // Insert constant value in memory. + unsigned int constant(const double val) { + + // Search for built-in constant. + if (cimg::type::is_nan(val)) return _cimg_mp_slot_nan; + if (val==(double)(int)val) { + if (val>=0 && val<=10) return (unsigned int)val; + if (val<0 && val>=-5) return (unsigned int)(10 - val); + } + if (val==0.5) return 16; + + // Search for constant already requested before (in const cache). + unsigned int ind = ~0U; + if (constcache_size<1024) { + if (!constcache_size) { + constcache_vals.assign(16,1,1,1,0); + constcache_inds.assign(16,1,1,1,0); + *constcache_vals = val; + constcache_size = 1; + ind = 0; + } else { // Dichotomic search + const double val_beg = *constcache_vals, val_end = constcache_vals[constcache_size - 1]; + if (val_beg>=val) ind = 0; + else if (val_end==val) ind = constcache_size - 1; + else if (val_end=constcache_size || constcache_vals[ind]!=val) { + ++constcache_size; + if (constcache_size>constcache_vals._width) { + constcache_vals.resize(-200,1,1,1,0); + constcache_inds.resize(-200,1,1,1,0); + } + const int l = constcache_size - (int)ind - 1; + if (l>0) { + std::memmove(&constcache_vals[ind + 1],&constcache_vals[ind],l*sizeof(double)); + std::memmove(&constcache_inds[ind + 1],&constcache_inds[ind],l*sizeof(unsigned int)); + } + constcache_vals[ind] = val; + constcache_inds[ind] = 0; + } + } + if (constcache_inds[ind]) return constcache_inds[ind]; + } + + // Insert new constant in memory if necessary. + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(-200,1,1,1,0); } + const unsigned int pos = mempos++; + mem[pos] = val; + memtype[pos] = 1; // Set constant property + if (ind!=~0U) constcache_inds[ind] = pos; + return pos; + } + + // Insert code instructions for processing scalars. + unsigned int scalar() { // Insert new scalar in memory + if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(mem._width,1,1,1,0); } + return mempos++; + } + + unsigned int scalar0(const mp_func op) { + const unsigned int pos = scalar(); + CImg::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!=~0U && 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!=~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: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: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: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: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: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: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 + if (mempos + siz>=mem._width) { + mem.resize(2*mem._width + siz,1,1,1,0); + memtype.resize(mem._width,1,1,1,0); + } + const unsigned int pos = mempos++; + mem[pos] = cimg::type::nan(); + memtype[pos] = siz + 1; + mempos+=siz; + return pos; + } + + unsigned int vector(const unsigned int siz, const double value) { // Insert new initialized vector + const unsigned int pos = vector(siz); + double *ptr = &mem[pos] + 1; + for (unsigned int i = 0; i::vector((ulongT)mp_vector_copy,pos,arg,siz).move_to(code); + return pos; + } + + void self_vector_s(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_s,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1).move_to(code[code._width - 1 - siz + k]); + } + } + + void self_vector_v(const unsigned int pos, const mp_func op, const unsigned int arg1) { + const unsigned int siz = _cimg_mp_size(pos); + if (siz>24) CImg::vector((ulongT)mp_self_map_vector_v,pos,siz,(ulongT)op,arg1).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + } + + unsigned int vector1_v(const mp_func op, const unsigned int arg1) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_vs(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector2_sv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1,arg2 + k).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + unsigned int vector3_vss(const mp_func op, const unsigned int arg1, const unsigned int arg2, + const unsigned int arg3) { + const unsigned int + siz = _cimg_mp_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); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2,arg3).move_to(code[code._width - 1 - siz + k]); + } + return pos; + } + + // Check if a memory slot is a positive integer constant scalar value. + // 'mode' can be: + // { 0=constant | 1=integer constant | 2=positive integer constant | 3=strictly-positive integer constant } + void check_constant(const unsigned int arg, const unsigned int n_arg, + const unsigned int mode, + char *const ss, char *const se, const char saved_char) { + _cimg_mp_check_type(arg,n_arg,1,0); + if (!(_cimg_mp_is_constant(arg) && + (!mode || (double)(int)mem[arg]==mem[arg]) && + (mode<2 || mem[arg]>=(mode==3)))) { + const char *s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ": + n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth ": + n_arg==9?"Ninth ":"One of the "; + *se = saved_char; + char *const s0 = ss - 4>expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + throw CImgArgumentException("[" cimg_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); + 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // 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()?"...":""); + } + } + + // Evaluation functions, known by the parser. + // Defining these functions 'static' ensures that sizeof(mp_func)==sizeof(ulongT), + // so we can store pointers to them directly in the opcode vectors. +#ifdef _mp_arg +#undef _mp_arg +#endif +#define _mp_arg(x) mp.mem[mp.opcode[x]] + + static double mp_abs(_cimg_math_parser& mp) { + return cimg::abs(_mp_arg(2)); + } + + static double mp_add(_cimg_math_parser& mp) { + return _mp_arg(2) + _mp_arg(3); + } + + static double mp_acos(_cimg_math_parser& mp) { + return std::acos(_mp_arg(2)); + } + + static double mp_acosh(_cimg_math_parser& mp) { + return cimg::acosh(_mp_arg(2)); + } + + static double mp_asinh(_cimg_math_parser& mp) { + return cimg::asinh(_mp_arg(2)); + } + + static double mp_atanh(_cimg_math_parser& mp) { + return cimg::atanh(_mp_arg(2)); + } + + static double mp_arg(_cimg_math_parser& mp) { + const int _ind = (int)_mp_arg(4); + const unsigned int + nb_args = (unsigned int)mp.opcode[2] - 4, + ind = _ind<0?_ind + nb_args:(unsigned int)_ind, + siz = (unsigned int)mp.opcode[3]; + if (siz>0) { + if (ind>=nb_args) std::memset(&_mp_arg(1) + 1,0,siz*sizeof(double)); + else std::memcpy(&_mp_arg(1) + 1,&_mp_arg(ind + 4) + 1,siz*sizeof(double)); + return cimg::type::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; ival) { val = _val; argval = i - 3; } + } + return (double)argval; + } + + static double mp_asin(_cimg_math_parser& mp) { + return std::asin(_mp_arg(2)); + } + + static double mp_atan(_cimg_math_parser& mp) { + return std::atan(_mp_arg(2)); + } + + static double mp_atan2(_cimg_math_parser& mp) { + return std::atan2(_mp_arg(2),_mp_arg(3)); + } + + static double mp_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; i>(unsigned int)_mp_arg(3)); + } + + static double mp_bitwise_xor(_cimg_math_parser& mp) { + return (double)((longT)_mp_arg(2) ^ (longT)_mp_arg(3)); + } + + static double mp_bool(_cimg_math_parser& mp) { + return (double)(bool)_mp_arg(2); + } + + static double mp_break(_cimg_math_parser& mp) { + mp.break_type = 1; + mp.p_code = mp.p_break - 1; + return cimg::type::nan(); + } + + static double mp_breakpoint(_cimg_math_parser& mp) { + cimg_abort_init; + cimg_abort_test; + cimg::unused(mp); + return cimg::type::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_cbrt(_cimg_math_parser& mp) { + return cimg::cbrt(_mp_arg(2)); + } + + static double mp_ceil(_cimg_math_parser& mp) { + return std::ceil(_mp_arg(2)); + } + + static double mp_complex_abs(_cimg_math_parser& mp) { + return cimg::_hypot(_mp_arg(2),_mp_arg(3)); + } + + static double mp_complex_conj(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = *(ptrs++); + *ptrd = -*(ptrs); + return cimg::type::nan(); + } + + static double mp_complex_div_sv(_cimg_math_parser& mp) { + const double + *ptr2 = &_mp_arg(3) + 1, + r1 = _mp_arg(2), + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = r1*r2/denom; + *ptrd = -r1*i2/denom; + return cimg::type::nan(); + } + + static double mp_complex_div_vv(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + const double denom = r2*r2 + i2*i2; + *(ptrd++) = (r1*r2 + i1*i2)/denom; + *ptrd = (r2*i1 - r1*i2)/denom; + return cimg::type::nan(); + } + + static double mp_complex_exp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs), er = std::exp(r); + *(ptrd++) = er*std::cos(i); + *(ptrd++) = er*std::sin(i); + return cimg::type::nan(); + } + + static double mp_complex_log(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs); + *(ptrd++) = 0.5*std::log(r*r + i*i); + *(ptrd++) = std::atan2(i,r); + return cimg::type::nan(); + } + + static double mp_complex_mul(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, + r1 = *(ptr1++), i1 = *ptr1, + r2 = *(ptr2++), i2 = *ptr2; + double *ptrd = &_mp_arg(1) + 1; + *(ptrd++) = r1*r2 - i1*i2; + *(ptrd++) = r1*i2 + r2*i1; + return cimg::type::nan(); + } + + static void _mp_complex_pow(const double r1, const double i1, + const double r2, const double i2, + double *ptrd) { + double ro, io; + if (cimg::abs(i2)<1e-15) { // Exponent is real + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) { + if (cimg::abs(r2)<1e-15) { ro = 1; io = 0; } + else ro = io = 0; + } else { + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2), + phio = r2*phi1; + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + } else { // Exponent is complex + if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) ro = io = 0; + const double + mod1_2 = r1*r1 + i1*i1, + phi1 = std::atan2(i1,r1), + modo = std::pow(mod1_2,0.5*r2)*std::exp(-i2*phi1), + phio = r2*phi1 + 0.5*i2*std::log(mod1_2); + ro = modo*std::cos(phio); + io = modo*std::sin(phio); + } + *(ptrd++) = ro; + *ptrd = io; + } + + static double mp_complex_pow_ss(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_sv(_cimg_math_parser& mp) { + const double val1 = _mp_arg(2), *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(val1,0,ptr2[0],ptr2[1],ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vs(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, val2 = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],val2,0,ptrd); + return cimg::type::nan(); + } + + static double mp_complex_pow_vv(_cimg_math_parser& mp) { + const double *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1; + double *ptrd = &_mp_arg(1) + 1; + _mp_complex_pow(ptr1[0],ptr1[1],ptr2[0],ptr2[1],ptrd); + return cimg::type::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_cos(_cimg_math_parser& mp) { + return std::cos(_mp_arg(2)); + } + + static double mp_cosh(_cimg_math_parser& mp) { + return std::cosh(_mp_arg(2)); + } + + static double mp_critical(_cimg_math_parser& mp) { + const double res = _mp_arg(1); + cimg_pragma_openmp(critical(mp_critical)) + { + for (const CImg *const p_end = ++mp.p_code + mp.opcode[2]; + mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + } + --mp.p_code; + return res; + } + + static double mp_crop(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const unsigned int + dx = (unsigned int)mp.opcode[7], + dy = (unsigned int)mp.opcode[8], + dz = (unsigned int)mp.opcode[9], + dc = (unsigned int)mp.opcode[10]; + const 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 (!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, + z + dz - 1,c + dc - 1, + boundary_conditions); + return cimg::type::nan(); + } + + static double mp_cross(_cimg_math_parser& mp) { + CImg + vout(&_mp_arg(1) + 1,1,3,1,1,true), + v1(&_mp_arg(2) + 1,1,3,1,1,true), + v2(&_mp_arg(3) + 1,1,3,1,1,true); + (vout = v1).cross(v2); + return cimg::type::nan(); + } + + static double mp_cut(_cimg_math_parser& mp) { + double val = _mp_arg(2), cmin = _mp_arg(3), cmax = _mp_arg(4); + return valcmax?cmax:val; + } + + 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; + } + + 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++); + cimg::strellipsize(expr); + const ulongT g_target = mp.opcode[1]; + +#ifndef cimg_use_openmp + const unsigned int n_thread = 0; +#else + const unsigned int n_thread = omp_get_thread_num(); +#endif + cimg_pragma_openmp(critical(mp_debug)) + { + std::fprintf(cimg::output(), + "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" + "Start debugging expression '%s', code length %u -> mem[%u] (memsize: %u)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)mp.opcode[3],(unsigned int)g_target,mp.mem._width); + std::fflush(cimg::output()); + mp.debug_indent+=3; + } + const CImg *const p_end = (++mp.p_code) + mp.opcode[3]; + CImg _op; + for ( ; mp.p_code &op = *mp.p_code; + mp.opcode._data = op._data; + + _op.assign(1,op._height - 1); + const ulongT *ptrs = op._data + 1; + for (ulongT *ptrd = _op._data, *const ptrde = _op._data + _op._height; ptrd mem[%u] = %g", + (void*)&mp,n_thread,mp.debug_indent,' ', + (void*)mp.opcode._data,(void*)*mp.opcode,_op.value_string().data(), + (unsigned int)target,mp.mem[target]); + std::fflush(cimg::output()); + } + } + cimg_pragma_openmp(critical(mp_debug)) + { + 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)", + (void*)&mp,n_thread,mp.debug_indent,' ', + expr._data,(unsigned int)g_target,mp.mem[g_target],mp.mem._width); + std::fflush(cimg::output()); + } + --mp.p_code; + return mp.mem[g_target]; + } + + static double mp_decrement(_cimg_math_parser& mp) { + return _mp_arg(2) - 1; + } + + static double mp_det(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + return CImg(ptrs,k,k,1,1,true).det(); + } + + 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; + 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"); + return cimg::type::nan(); + } + + static double mp_display(_cimg_math_parser& mp) { + const unsigned int + _siz = (unsigned int)mp.opcode[3], + siz = _siz?_siz:1; + const double *const ptr = &_mp_arg(1) + (_siz?1:0); + const int + w = (int)_mp_arg(4), + h = (int)_mp_arg(5), + d = (int)_mp_arg(6), + s = (int)_mp_arg(7); + CImg img; + if (w>0 && h>0 && d>0 && s>0) { + if ((unsigned int)w*h*d*s<=siz) img.assign(ptr,w,h,d,s,true); + else img.assign(ptr,siz).resize(w,h,d,s,-1); + } else img.assign(ptr,1,siz,1,1,true); + + CImg expr(mp.opcode[2] - 8); + const ulongT *ptrs = mp.opcode._data + 8; + cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); + ((CImg::string("[" cimg_appname "_math_parser] ",false,true),expr)>'x').move_to(expr); + cimg::strellipsize(expr); + std::fputc('\n',cimg::output()); + img.display(expr._data); + return cimg::type::nan(); + } + + static double mp_div(_cimg_math_parser& mp) { + return _mp_arg(2)/_mp_arg(3); + } + + static double mp_dot(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[4]; + return CImg(&_mp_arg(2) + 1,1,siz,1,1,true). + dot(CImg(&_mp_arg(3) + 1,1,siz,1,1,true)); + } + + static double mp_do(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_body = ++mp.p_code, + *const p_cond = p_body + mp.opcode[3], + *const p_end = p_cond + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (mp.mem[mem_cond]); + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_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; + 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); + const CImg str = _str>'x'; + std::fprintf(cimg::output(),"\n%s",str._data); + return cimg::type::nan(); + } + + static double mp_ellipse(_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]; + CImg color(img._spectrum,1,1,1,0); + bool is_invalid_arguments = false; + unsigned int i = 4; + float r1 = 0, r2 = 0, angle = 0, opacity = 1; + int x0 = 0, y0 = 0; + if (i>=i_end) is_invalid_arguments = true; + else { + x0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + y0 = (int)cimg::round(_mp_arg(i++)); + if (i>=i_end) is_invalid_arguments = true; + else { + r1 = (float)_mp_arg(i++); + if (i>=i_end) r2 = r1; + 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) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_eq(_cimg_math_parser& mp) { + 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); +#endif + return cimg::type::nan(); + } + + static double mp_exp(_cimg_math_parser& mp) { + return std::exp(_mp_arg(2)); + } + + static double mp_eye(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int k = (unsigned int)mp.opcode[2]; + CImg(ptrd,k,k,1,1,true).identity_matrix(); + return cimg::type::nan(); + } + + static double mp_factorial(_cimg_math_parser& mp) { + return cimg::factorial((int)_mp_arg(2)); + } + + static double mp_fibonacci(_cimg_math_parser& mp) { + return cimg::fibonacci((int)_mp_arg(2)); + } + + static double mp_find(_cimg_math_parser& mp) { + const bool is_forward = (bool)_mp_arg(5); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const double + *const ptrb = &_mp_arg(2) + 1, + *const ptre = ptrb + siz, + val = _mp_arg(4), + *ptr = ptrb + ind; + + // Forward search + if (is_forward) { + while (ptr=ptrb && *ptr!=val) --ptr; + return ptr=(longT)siz1) return -1.; + const double + *const ptr1b = &_mp_arg(2) + 1, + *const ptr1e = ptr1b + siz1, + *const ptr2b = &_mp_arg(4) + 1, + *const ptr2e = ptr2b + siz2, + *ptr1 = ptr1b + ind, + *p1 = 0, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 + *const p_init = ++mp.p_code, + *const p_cond = p_init + mp.opcode[4], + *const p_body = p_cond + mp.opcode[5], + *const p_post = p_body + mp.opcode[6], + *const p_end = p_post + mp.opcode[7]; + const unsigned int vsiz = (unsigned int)mp.opcode[2]; + bool is_cond = false; + if (mp.opcode[8]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[9]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + + for (mp.p_code = p_init; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + + if (!mp.break_type) do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) { + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + + for (mp.p_code = p_post; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_fsize(_cimg_math_parser& mp) { + const CImg filename(mp.opcode._data + 3,mp.opcode[2] - 3); + return (double)cimg::fsize(filename); + } + + static double mp_g(_cimg_math_parser& mp) { + cimg::unused(mp); + return cimg::grand(&mp.rng); + } + + 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); + } + + static double mp_gcd(_cimg_math_parser& mp) { + return cimg::gcd((long)_mp_arg(2),(long)_mp_arg(3)); + } + + static double mp_gt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>_mp_arg(3)); + } + + static double mp_gte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)>=_mp_arg(3)); + } + + static double mp_i(_cimg_math_parser& mp) { + return (double)mp.imgin.atXYZC((int)mp.mem[_cimg_mp_slot_x],(int)mp.mem[_cimg_mp_slot_y], + (int)mp.mem[_cimg_mp_slot_z],(int)mp.mem[_cimg_mp_slot_c],(T)0); + } + + static double mp_if(_cimg_math_parser& mp) { + const bool is_cond = (bool)_mp_arg(2); + const ulongT + mem_left = mp.opcode[3], + mem_right = mp.opcode[4]; + const CImg + *const p_right = ++mp.p_code + mp.opcode[5], + *const p_end = p_right + mp.opcode[6]; + const unsigned int vtarget = (unsigned int)mp.opcode[1], vsiz = (unsigned int)mp.opcode[7]; + if (is_cond) for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + else for (mp.p_code = p_right; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.p_code==mp.p_break) --mp.p_code; + else mp.p_code = p_end - 1; + if (vsiz) std::memcpy(&mp.mem[vtarget] + 1,&mp.mem[is_cond?mem_left:mem_right] + 1,sizeof(double)*vsiz); + return mp.mem[is_cond?mem_left:mem_right]; + } + + static double mp_image_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]; + 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()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.display(title); + cimg::mutex(6,0); + 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]; + 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]; + return (double)img.median(); + } + + static double mp_image_print(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + CImg title(256); + std::fputc('\n',cimg::output()); + cimg_snprintf(title,title._width,"[ Image #%u ]",ind); + img.print(title); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_resize(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + const double + _w = mp.opcode[3]==~0U?-100:_mp_arg(3), + _h = mp.opcode[4]==~0U?-100:_mp_arg(4), + _d = mp.opcode[5]==~0U?-100:_mp_arg(5), + _s = mp.opcode[6]==~0U?-100:_mp_arg(6); + const unsigned int + w = (unsigned int)(_w>=0?_w:-_w*img.width()/100), + h = (unsigned int)(_h>=0?_h:-_h*img.height()/100), + d = (unsigned int)(_d>=0?_d:-_d*img.depth()/100), + s = (unsigned int)(_s>=0?_s:-_s*img.spectrum()/100), + interp = (int)_mp_arg(7); + if (mp.is_fill && img._data==mp.imgout._data) { + cimg::mutex(6,0); + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'resize()': " + "Cannot both fill and resize image (%u,%u,%u,%u) " + "to new dimensions (%u,%u,%u,%u).", + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,w,h,d,s); + } + const unsigned int + boundary = (int)_mp_arg(8); + const float + cx = (float)_mp_arg(9), + cy = (float)_mp_arg(10), + cz = (float)_mp_arg(11), + cc = (float)_mp_arg(12); + img.resize(w,h,d,s,interp,boundary,cx,cy,cz,cc); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + static double mp_image_s(_cimg_math_parser& mp) { + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; + return (double)img.spectrum(); + } + + static double mp_image_sort(_cimg_math_parser& mp) { + const bool is_increasing = (bool)_mp_arg(3); + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()), + axis = (unsigned int)_mp_arg(4); + cimg::mutex(6); + CImg &img = mp.listout[ind]; + img.sort(is_increasing, + axis==0 || axis=='x'?'x': + axis==1 || axis=='y'?'y': + axis==2 || axis=='z'?'z': + axis==3 || axis=='c'?'c':0); + cimg::mutex(6,0); + return cimg::type::nan(); + } + + 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(); + 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(); + } + 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]; + 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]; + 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]; + 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]; + return (double)img.width()*img.height()*img.depth()*img.spectrum(); + } + + static double mp_increment(_cimg_math_parser& mp) { + return _mp_arg(2) + 1; + } + + static double mp_int(_cimg_math_parser& mp) { + return (double)(longT)_mp_arg(2); + } + + static double mp_ioff(_cimg_math_parser& mp) { + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3); + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off::is_inf(_mp_arg(2)); + } + + static double mp_isint(_cimg_math_parser& mp) { + return (double)(cimg::mod(_mp_arg(2),1.)==0); + } + + static double mp_isnan(_cimg_math_parser& mp) { + return (double)cimg::type::is_nan(_mp_arg(2)); + } + + static double mp_ixyzc(_cimg_math_parser& mp) { + const unsigned int + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7); + const CImg &img = mp.imgin; + const double + x = _mp_arg(2), y = _mp_arg(3), + z = _mp_arg(4), c = _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.imgin; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), + z = oz + _mp_arg(4), c = oc + _mp_arg(5); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx vals(i_end - 4); + double *p = vals.data(); + for (unsigned int i = 4; i &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(4); + const ulongT siz = (ulongT)img.size(); + longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):is_forward?0:siz - 1); + if (ind<0 || ind>=(longT)siz) return -1.; + const T + *const ptrb = img.data(), + *const ptre = img.end(), + *ptr = ptrb + ind; + const double val = _mp_arg(3); + + // Forward search + if (is_forward) { + while (ptr=ptrb && (double)*ptr!=val) --ptr; + return ptr &img = mp.listin[indi]; + const bool is_forward = (bool)_mp_arg(5); + const ulongT + siz1 = (ulongT)img.size(), + siz2 = (ulongT)mp.opcode[4]; + longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz1 - 1); + if (ind<0 || ind>=(longT)siz1) return -1.; + const T + *const ptr1b = img.data(), + *const ptr1e = ptr1b + siz1, + *ptr1 = ptr1b + ind, + *p1 = 0; + const double + *const ptr2b = &_mp_arg(3) + 1, + *const ptr2e = ptr2b + siz2, + *p2 = 0; + + // Forward search. + if (is_forward) { + do { + while (ptr1=ptr1e) return -1.; + p1 = ptr1 + 1; + p2 = ptr2b + 1; + while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + if (ptr1=ptr1b); + return p2 &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + x = _mp_arg(3), y = _mp_arg(4), + z = _mp_arg(5), c = _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + if (off>=0 && off &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), + z = oz + _mp_arg(5), c = oc + _mp_arg(6); + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), + mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); + return (double)img(mx::vector(mp.listin[ind].median()).move_to(mp.list_median[ind]); + return *mp.list_median[ind]; + } + + static double mp_list_set_ioff(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), y = (int)_mp_arg(4), + z = (int)_mp_arg(5), c = (int)_mp_arg(6); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(3)), y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)), c = (int)(oc + _mp_arg(6)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Ixyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_set_Joff_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.listout[ind]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_list_set_Jxyz_s(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + CImg &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(3)), + y = (int)(oy + _mp_arg(4)), + z = (int)(oz + _mp_arg(5)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_list_spectrum(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._spectrum; + } + + static double mp_list_stats(_cimg_math_parser& mp) { + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + k = (unsigned int)mp.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); + return mp.list_stats(ind,k); + } + + static double mp_list_wh(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height; + } + + static double mp_list_whd(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth; + } + + static double mp_list_whds(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth*mp.listin[ind]._spectrum; + } + + static double mp_list_width(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); + return (double)mp.listin[ind]._width; + } + + static double mp_list_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const CImg &img = mp.listin[ind]; + const longT + off = (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double x = _mp_arg(3), y = _mp_arg(4), z = _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_list_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + boundary_conditions = (unsigned int)_mp_arg(4), + vsiz = (unsigned int)mp.opcode[5]; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], oz = (int)mp.mem[_cimg_mp_slot_z]; + const CImg &img = mp.listin[ind]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(3), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_list_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + interpolation = (unsigned int)_mp_arg(6), + boundary_conditions = (unsigned int)_mp_arg(7), + vsiz = (unsigned int)mp.opcode[8]; + const CImg &img = mp.listin[ind]; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(3), y = oy + _mp_arg(4), z = oz + _mp_arg(5); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_log(_cimg_math_parser& mp) { + return std::log(_mp_arg(2)); + } + + static double mp_log10(_cimg_math_parser& mp) { + return std::log10(_mp_arg(2)); + } + + static double mp_log2(_cimg_math_parser& mp) { + return cimg::log2(_mp_arg(2)); + } + + static double mp_logical_and(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (!val_left) { mp.p_code = p_end - 1; return 0; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_logical_not(_cimg_math_parser& mp) { + return (double)!_mp_arg(2); + } + + static double mp_logical_or(_cimg_math_parser& mp) { + const bool val_left = (bool)_mp_arg(2); + const CImg *const p_end = ++mp.p_code + mp.opcode[4]; + if (val_left) { mp.p_code = p_end - 1; return 1; } + const ulongT mem_right = mp.opcode[3]; + for ( ; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + --mp.p_code; + return (double)(bool)mp.mem[mem_right]; + } + + static double mp_lowercase(_cimg_math_parser& mp) { + return cimg::lowercase(_mp_arg(2)); + } + + static double mp_lt(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<_mp_arg(3)); + } + + static double mp_lte(_cimg_math_parser& mp) { + return (double)(_mp_arg(2)<=_mp_arg(3)); + } + + static double mp_matrix_eig(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptr1 = &_mp_arg(2) + 1; + const unsigned int k = (unsigned int)mp.opcode[3]; + CImg val, vec; + CImg(ptr1,k,k,1,1,true).symmetric_eigen(val,vec); + CImg(ptrd,1,k,1,1,true) = val; + CImg(ptrd + k,k,k,1,1,true) = vec.get_transpose(); + 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(); + return cimg::type::nan(); + } + + static double mp_matrix_mul(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr1,l,k,1,1,true)*CImg(ptr2,m,l,1,1,true); + 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; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg U, S, V; + CImg(ptr1,k,l,1,1,true).SVD(U,S,V); + CImg(ptrd,k,l,1,1,true) = U; + CImg(ptrd + k*l,1,k,1,1,true) = S; + CImg(ptrd + k*l + k,k,k,1,1,true) = V; + return cimg::type::nan(); + } + + static double mp_max(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i=mp.mem.width()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds variable pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %u).", + mp.imgin.pixel_type(),siz,inc,off,eoff,mp.mem._width - 1); + return &mp.mem[off]; + } + + static float* _mp_memcopy_float(_cimg_math_parser& mp, const ulongT *const p_ref, + const longT siz, const long inc) { + const unsigned ind = (unsigned int)p_ref[1]; + const CImg &img = ind==~0U?mp.imgin:mp.listin[cimg::mod((int)mp.mem[ind],mp.listin.width())]; + const bool is_relative = (bool)p_ref[2]; + int ox, oy, oz, oc; + longT off = 0; + if (is_relative) { + ox = (int)mp.mem[_cimg_mp_slot_x]; + oy = (int)mp.mem[_cimg_mp_slot_y]; + oz = (int)mp.mem[_cimg_mp_slot_z]; + oc = (int)mp.mem[_cimg_mp_slot_c]; + off = img.offset(ox,oy,oz,oc); + } + if ((*p_ref)%2) { + const int + x = (int)mp.mem[p_ref[3]], + y = (int)mp.mem[p_ref[4]], + z = (int)mp.mem[p_ref[5]], + c = *p_ref==5?0:(int)mp.mem[p_ref[6]]; + off+=img.offset(x,y,z,c); + } else off+=(longT)mp.mem[p_ref[3]]; + const longT eoff = off + (siz - 1)*inc; + if (off<0 || eoff>=(longT)img.size()) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " + "Out-of-bounds image pointer " + "(length: %ld, increment: %ld, offset start: %ld, " + "offset end: %ld, offset max: %lu).", + mp.imgin.pixel_type(),siz,inc,off,eoff,img.size() - 1); + return (float*)&img[off]; + } + + static double mp_memcopy(_cimg_math_parser& mp) { + longT siz = (longT)_mp_arg(4); + const longT inc_d = (longT)_mp_arg(5), inc_s = (longT)_mp_arg(6); + const float + _opacity = (float)_mp_arg(7), + opacity = (float)cimg::abs(_opacity), + omopacity = 1 - std::max(_opacity,0.f); + if (siz>0) { + const bool + is_doubled = mp.opcode[8]<=1, + is_doubles = mp.opcode[15]<=1; + if (is_doubled && is_doubles) { // (double*) <- (double*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(double)); + else std::memmove(ptrd,ptrs,siz*sizeof(double)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,double) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } else if (is_doubled && !is_doubles) { // (double*) <- (float*) + double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + _opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else if (!is_doubled && is_doubles) { // (float*) <- (double*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); + if (_opacity>=1) while (siz-->0) { *ptrd = (float)*ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = (float)(omopacity**ptrd + opacity**ptrs); ptrd+=inc_d; ptrs+=inc_s; } + } else { // (float*) <- (float*) + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); + if (inc_d==1 && inc_s==1 && _opacity>=1) { + if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(float)); + else std::memmove(ptrd,ptrs,siz*sizeof(float)); + } else { + if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { + if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } + } else { // Overlapping buffers + CImg buf((unsigned int)siz); + cimg_for(buf,ptr,float) { *ptr = *ptrs; ptrs+=inc_s; } + ptrs = buf; + if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } + else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } + } + } + } + } + return _mp_arg(1); + } + + static double mp_min(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i vals(i_end - 3); + double *p = vals.data(); + 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.; + } + + static double mp_permutations(_cimg_math_parser& mp) { + return cimg::permutations((int)_mp_arg(2),(int)_mp_arg(3),(bool)_mp_arg(4)); + } + + 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 (!is_invalid_arguments) { + const int nbv = (int)_mp_arg(4); + if (nbv<=0) is_invalid_arguments = true; + else { + CImg points(nbv,2,1,1,0); + CImg color(img._spectrum,1,1,1,0); + float opacity = 1; + unsigned int i = 5; + cimg_foroff(points,k) if (i args(i_end - 4); + cimg_forX(args,k) args[k] = _mp_arg(4 + k); + if (ind==~0U) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '%s'. ", + mp.imgin.pixel_type(),args.value_string()._data); + else + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': " + "Invalid arguments '#%u%s%s'. ", + mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data); + } + return cimg::type::nan(); + } + + static double mp_pow(_cimg_math_parser& mp) { + const double v = _mp_arg(2), p = _mp_arg(3); + return std::pow(v,p); + } + + static double mp_pow0_25(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return std::sqrt(std::sqrt(val)); + } + + static double mp_pow3(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val; + } + + static double mp_pow4(_cimg_math_parser& mp) { + const double val = _mp_arg(2); + return val*val*val*val; + } + + static double mp_print(_cimg_math_parser& mp) { + const double val = _mp_arg(1); + const bool print_char = (bool)mp.opcode[3]; + cimg_pragma_openmp(critical(mp_print)) + { + 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::mutex(6); + if (print_char) + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g = '%c'",expr._data,val,(int)val); + else + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g",expr._data,val); + std::fflush(cimg::output()); + cimg::mutex(6,0); + } + return val; + } + + static double mp_prod(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i::nan(); + } + + static double mp_rot3d(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const float x = (float)_mp_arg(2), y = (float)_mp_arg(3), z = (float)_mp_arg(4), theta = (float)_mp_arg(5); + CImg(ptrd,3,3,1,1,true) = CImg::rotation_matrix(x,y,z,theta); + return cimg::type::nan(); + } + + static double mp_round(_cimg_math_parser& mp) { + return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4)); + } + + static double mp_self_add(_cimg_math_parser& mp) { + return _mp_arg(1)+=_mp_arg(2); + } + + static double mp_self_bitwise_and(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val & (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_left_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val<<(unsigned int)_mp_arg(2)); + } + + static double mp_self_bitwise_or(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val | (longT)_mp_arg(2)); + } + + static double mp_self_bitwise_right_shift(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = (double)((longT)val>>(unsigned int)_mp_arg(2)); + } + + static double mp_self_decrement(_cimg_math_parser& mp) { + return --_mp_arg(1); + } + + static double mp_self_increment(_cimg_math_parser& mp) { + return ++_mp_arg(1); + } + + static double mp_self_map_vector_s(_cimg_math_parser& mp) { // Vector += scalar + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2]; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode[2] = mp.opcode[4]; // Scalar argument + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1]; + while (siz-->0) { target = ptrd++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_map_vector_v(_cimg_math_parser& mp) { // Vector += vector + unsigned int + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,4); + l_opcode.swap(mp.opcode); + ulongT &target = mp.opcode[1], &argument = mp.opcode[2]; + while (siz-->0) { target = ptrd++; argument = ptrs++; (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_self_mul(_cimg_math_parser& mp) { + return _mp_arg(1)*=_mp_arg(2); + } + + static double mp_self_div(_cimg_math_parser& mp) { + return _mp_arg(1)/=_mp_arg(2); + } + + static double mp_self_modulo(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = cimg::mod(val,_mp_arg(2)); + } + + static double mp_self_pow(_cimg_math_parser& mp) { + double &val = _mp_arg(1); + return val = std::pow(val,_mp_arg(2)); + } + + static double mp_self_sub(_cimg_math_parser& mp) { + return _mp_arg(1)-=_mp_arg(2); + } + + static double mp_set_ioff(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + x = (int)_mp_arg(2), y = (int)_mp_arg(3), + z = (int)_mp_arg(4), c = (int)_mp_arg(5); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whds = (longT)img.size(); + const double val = _mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], + oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; + const int + x = (int)(ox + _mp_arg(2)), y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)), c = (int)(oc + _mp_arg(5)); + const double val = _mp_arg(1); + if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Ixyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const int + x = (int)_mp_arg(2), + y = (int)_mp_arg(3), + z = (int)_mp_arg(4); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_set_Joff_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T val = (T)_mp_arg(1); + if (off>=0 && off &img = mp.imgout; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; + const longT + off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const double *ptrs = &_mp_arg(1) + 1; + if (off>=0 && off::nan(); + } + + static double mp_set_Jxyz_s(_cimg_math_parser& mp) { + CImg &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const T val = (T)_mp_arg(1); + if (x>=0 && x=0 && y=0 && z &img = mp.imgout; + const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; + const int + x = (int)(ox + _mp_arg(2)), + y = (int)(oy + _mp_arg(3)), + z = (int)(oz + _mp_arg(4)); + const double *ptrs = &_mp_arg(1) + 1; + if (x>=0 && x=0 && y=0 && z::nan(); + } + + static double mp_shift(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const unsigned int siz = (unsigned int)mp.opcode[3]; + const int + shift = (int)_mp_arg(4), + boundary_conditions = (int)_mp_arg(5); + CImg(ptrd,siz,1,1,1,true) = CImg(ptrs,siz,1,1,1,true).shift(shift,0,0,0,boundary_conditions); + return cimg::type::nan(); + } + + static double mp_sign(_cimg_math_parser& mp) { + return cimg::sign(_mp_arg(2)); + } + + static double mp_sin(_cimg_math_parser& mp) { + return std::sin(_mp_arg(2)); + } + + static double mp_sinc(_cimg_math_parser& mp) { + return cimg::sinc(_mp_arg(2)); + } + + static double mp_sinh(_cimg_math_parser& mp) { + return std::sinh(_mp_arg(2)); + } + + static double mp_solve(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(3) + 1; + const unsigned int + k = (unsigned int)mp.opcode[4], + l = (unsigned int)mp.opcode[5], + m = (unsigned int)mp.opcode[6]; + CImg(ptrd,m,k,1,1,true) = CImg(ptr2,m,l,1,1,true).get_solve(CImg(ptr1,k,l,1,1,true)); + 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 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); + return cimg::type::nan(); + } + + static double mp_sqr(_cimg_math_parser& mp) { + return cimg::sqr(_mp_arg(2)); + } + + static double mp_sqrt(_cimg_math_parser& mp) { + return std::sqrt(_mp_arg(2)); + } + + static double mp_srand(_cimg_math_parser& mp) { + mp.rng = (ulongT)_mp_arg(2); + return cimg::type::nan(); + } + + static double mp_srand0(_cimg_math_parser& mp) { + cimg::srand(&mp.rng); +#ifdef cimg_use_openmp + 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; i0) mp.mem[ptrd++] = (double)*(ptrs++); + return cimg::type::nan(); + } + + static double mp_stov(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2); + const ulongT siz = (ulongT)mp.opcode[3]; + longT ind = (longT)_mp_arg(4); + const bool is_strict = (bool)_mp_arg(5); + double val = cimg::type::nan(); + if (ind<0 || ind>=(longT)siz) return val; + 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; + + int err = cimg_sscanf(ss,"%lf%c",&val,&sep); +#if cimg_OS==2 + // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able + // to read those particular values. + if (!err && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) { + bool is_positive = true; + const char *s = ss; + if (*s=='+') ++s; else if (*s=='-') { ++s; is_positive = false; } + if (!cimg::strcasecmp(s,"inf")) { val = cimg::type::inf(); err = 1; } + else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); err = 1; } + if (err==1 && !is_positive) val = -val; + } +#endif + if (is_strict && err!=1) return cimg::type::nan(); + return val; + } + + static double mp_sub(_cimg_math_parser& mp) { + return _mp_arg(2) - _mp_arg(3); + } + + static double mp_sum(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val = _mp_arg(3); + for (unsigned int i = 4; i(ptrs,k,k,1,1,true).trace(); + } + + static double mp_transp(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1; + const unsigned int + k = (unsigned int)mp.opcode[3], + l = (unsigned int)mp.opcode[4]; + CImg(ptrd,l,k,1,1,true) = CImg(ptrs,k,l,1,1,true).get_transpose(); + return cimg::type::nan(); + } + + static double mp_u(_cimg_math_parser& mp) { + return cimg::rand(_mp_arg(2),_mp_arg(3),&mp.rng); + } + + static double mp_uppercase(_cimg_math_parser& mp) { + return cimg::uppercase(_mp_arg(2)); + } + + 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; i::nan(); + } + + static double mp_vector_crop(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const longT + length = (longT)mp.opcode[3], + start = (longT)_mp_arg(4), + sublength = (longT)mp.opcode[5]; + if (start<0 || start + sublength>length) + throw CImgArgumentException("[" cimg_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)); + return cimg::type::nan(); + } + + static double mp_vector_init(_cimg_math_parser& mp) { + unsigned int + ptrs = 4U, + ptrd = (unsigned int)mp.opcode[1] + 1, + siz = (unsigned int)mp.opcode[3]; + switch (mp.opcode[2] - 4) { + case 0 : std::memset(mp.mem._data + ptrd,0,siz*sizeof(double)); break; // 0 values given + case 1 : { const double val = _mp_arg(ptrs); while (siz-->0) mp.mem[ptrd++] = val; } break; + default : while (siz-->0) { mp.mem[ptrd++] = _mp_arg(ptrs++); if (ptrs>=mp.opcode[2]) ptrs = 4U; } + } + return cimg::type::nan(); + } + + static double mp_vector_eq(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2) + 1, + *ptr2 = &_mp_arg(4) + 1; + unsigned int p1 = (unsigned int)mp.opcode[3], p2 = (unsigned int)mp.opcode[5], n; + const int N = (int)_mp_arg(6); + const bool case_sensitive = (bool)_mp_arg(7); + bool still_equal = true; + double value; + if (!N) return true; + + // Compare all values. + if (N<0) { + if (p1>0 && p2>0) { // Vector == vector + if (p1!=p2) return false; + if (case_sensitive) + while (still_equal && p1--) still_equal = *(ptr1++)==*(ptr2++); + else + while (still_equal && p1--) + still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p1--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && p2--) still_equal = *(ptr2++)==value; + return still_equal; + } else { // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + else return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + } + + // Compare only first N values. + if (p1>0 && p2>0) { // Vector == vector + n = cimg::min((unsigned int)N,p1,p2); + if (case_sensitive) + while (still_equal && n--) still_equal = *(ptr1++)==(*ptr2++); + else + while (still_equal && n--) still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); + return still_equal; + } else if (p1>0 && !p2) { // Vector == scalar + n = std::min((unsigned int)N,p1); + value = _mp_arg(4); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr1++)==value; + return still_equal; + } else if (!p1 && p2>0) { // Scalar == vector + n = std::min((unsigned int)N,p2); + value = _mp_arg(2); + if (!case_sensitive) value = cimg::lowercase(value); + while (still_equal && n--) still_equal = *(ptr2++)==value; + return still_equal; + } // Scalar == scalar + if (case_sensitive) return _mp_arg(2)==_mp_arg(4); + return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); + } + + static double mp_vector_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + return off>=0 && off<(int)siz?mp.mem[ptr + off]:cimg::type::nan(); + } + + static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[5] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(4); + l_opcode[2] = mp.opcode[4]; // Scalar argument1 + l_opcode.swap(mp.opcode); + ulongT &argument2 = mp.opcode[3]; + while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_v(_cimg_math_parser& mp) { // Operator(vector) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg l_opcode(1,3); + l_opcode.swap(mp.opcode); + ulongT &argument = mp.opcode[2]; + while (siz-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_map_vs(_cimg_math_parser& mp) { // Operator(vector,scalar) + unsigned int + siz = (unsigned int)mp.opcode[2], + ptrs = (unsigned int)mp.opcode[4] + 1; + double *ptrd = &_mp_arg(1) + 1; + mp_func op = (mp_func)mp.opcode[3]; + CImg 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); + l_opcode.swap(mp.opcode); + ulongT &argument1 = mp.opcode[2], &argument2 = mp.opcode[3]; + while (siz-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); } + l_opcode.swap(mp.opcode); + return cimg::type::nan(); + } + + static double mp_vector_neq(_cimg_math_parser& mp) { + return !mp_vector_eq(mp); + } + + 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); + const ulongT *ptrs = mp.opcode._data + 5; + 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); + unsigned int count = 0; + while (siz-->0) { + if (count>=64 && siz>=64) { + std::fprintf(cimg::output(),"...,"); + ptr = (unsigned int)mp.opcode[1] + 1 + siz0 - 64; + siz = 64; + } else std::fprintf(cimg::output(),"%g%s",mp.mem[ptr++],siz?",":""); + ++count; + } + if (print_string) { + CImg str(siz0 + 1); + ptr = (unsigned int)mp.opcode[1] + 1; + for (unsigned int k = 0; k::nan(); + } + + static double mp_vector_resize(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const unsigned int p1 = (unsigned int)mp.opcode[2], p2 = (unsigned int)mp.opcode[4]; + const int + interpolation = (int)_mp_arg(5), + boundary_conditions = (int)_mp_arg(6); + if (p2) { // Resize vector + const double *const ptrs = &_mp_arg(3) + 1; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p2,1,1,1,true). + get_resize(p1,1,1,1,interpolation,boundary_conditions); + } else { // Resize scalar + const double value = _mp_arg(3); + CImg(ptrd,p1,1,1,1,true) = CImg(1,1,1,1,value).resize(p1,1,1,1,interpolation, + boundary_conditions); + } + 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; + const unsigned int p1 = (unsigned int)mp.opcode[3]; + CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p1,1,1,1,true).get_mirror('x'); + return cimg::type::nan(); + } + + static double mp_vector_set_off(_cimg_math_parser& mp) { + const unsigned int + ptr = (unsigned int)mp.opcode[2] + 1, + siz = (unsigned int)mp.opcode[3]; + const int off = (int)_mp_arg(4); + if (off>=0 && off<(int)siz) mp.mem[ptr + off] = _mp_arg(5); + return _mp_arg(5); + } + + static double mp_vtos(_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]; + 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); + } + 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); + } else { // Scalar expression + str.assign(sizd + 1); + cimg_snprintf(str,sizd + 1,format,_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_while(_cimg_math_parser& mp) { + const ulongT + mem_body = mp.opcode[1], + mem_cond = mp.opcode[2]; + const CImg + *const p_cond = ++mp.p_code, + *const p_body = p_cond + mp.opcode[3], + *const p_end = p_body + mp.opcode[4]; + const unsigned int vsiz = (unsigned int)mp.opcode[5]; + bool is_cond = false; + if (mp.opcode[6]) { // Set default value for result and condition if necessary + if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); + else mp.mem[mem_body] = cimg::type::nan(); + } + if (mp.opcode[7]) mp.mem[mem_cond] = 0; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + do { + for (mp.p_code = p_cond; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; + is_cond = (bool)mp.mem[mem_cond]; + if (is_cond && !mp.break_type) // Evaluate body + for (mp.p_code = p_body; mp.p_code_data; + const ulongT target = mp.opcode[1]; + mp.mem[target] = _cimg_mp_defunc(mp); + } + if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; + } while (is_cond); + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return mp.mem[mem_body]; + } + + static double mp_Ioff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const longT + off = (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Ixyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double x = _mp_arg(2), y = _mp_arg(3), z = _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + + static double mp_Joff(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + boundary_conditions = (unsigned int)_mp_arg(3), + vsiz = (unsigned int)mp.opcode[4]; + const CImg &img = mp.imgin; + const int + ox = (int)mp.mem[_cimg_mp_slot_x], + oy = (int)mp.mem[_cimg_mp_slot_y], + oz = (int)mp.mem[_cimg_mp_slot_z]; + const longT + off = img.offset(ox,oy,oz) + (longT)_mp_arg(2), + whd = (longT)img.width()*img.height()*img.depth(); + const T *ptrs; + if (off>=0 && off::nan(); + } + if (img._data) switch (boundary_conditions) { + case 3 : { // Mirror + const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); + ptrs = &img[moff::nan(); + } + case 2 : // Periodic + ptrs = &img[cimg::mod(off,whd)]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + case 1 : // Neumann + ptrs = off<0?&img[0]:&img[whd - 1]; + cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return cimg::type::nan(); + default : // Dirichlet + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + std::memset(ptrd,0,vsiz*sizeof(double)); + return cimg::type::nan(); + } + + static double mp_Jxyz(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const unsigned int + interpolation = (unsigned int)_mp_arg(5), + boundary_conditions = (unsigned int)_mp_arg(6), + vsiz = (unsigned int)mp.opcode[7]; + const CImg &img = mp.imgin; + const double + ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], + x = ox + _mp_arg(2), y = oy + _mp_arg(3), z = oz + _mp_arg(4); + const ulongT whd = (ulongT)img._width*img._height*img._depth; + const T *ptrs; + if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation + case 3 : { // Mirror + const int + w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), + mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), + cx = mx::nan(); + } + +#undef _mp_arg + + }; // struct _cimg_math_parser {} + +#define _cimg_create_pointwise_functions(name,func,min_size) \ + CImg& name() { \ + if (is_empty()) return *this; \ + cimg_openmp_for(*this,func((double)*ptr),min_size); \ + return *this; \ + } \ + CImg get_##name() const { \ + return CImg(*this,false).name(); \ + } + + //! Compute the square value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square value \f$I_{(x,y,z,c)}^2\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqr().normalize(0,255)).display(); + \endcode + \image html ref_sqr.jpg + **/ + _cimg_create_pointwise_functions(sqr,cimg::sqr,524288) + + //! Compute the square root of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square root \f$\sqrt{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \par Example + \code + const CImg img("reference.jpg"); + (img,img.get_sqrt().normalize(0,255)).display(); + \endcode + \image html ref_sqrt.jpg + **/ + _cimg_create_pointwise_functions(sqrt,std::sqrt,8192) + + //! Compute the exponential of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its exponential \f$e^{I_{(x,y,z,c)}}\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(exp,std::exp,4096) + + //! Compute the logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its logarithm + \f$\mathrm{log}_{e}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log,std::log,262144) + + //! Compute the base-2 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-2 logarithm + \f$\mathrm{log}_{2}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log2,cimg::log2,4096) + + //! Compute the base-10 logarithm of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-10 logarithm + \f$\mathrm{log}_{10}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(log10,std::log10,4096) + + //! Compute the absolute value of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its absolute value \f$|I_{(x,y,z,c)}|\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(abs,cimg::abs,524288) + + //! Compute the sign of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sign + \f$\mathrm{sign}(I_{(x,y,z,c)})\f$. + \note + - The sign is set to: + - \c 1 if pixel value is strictly positive. + - \c -1 if pixel value is strictly negative. + - \c 0 if pixel value is equal to \c 0. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sign,cimg::sign,32768) + + //! Compute the cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its cosine \f$\cos(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cos,std::cos,8192) + + //! Compute the sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sine \f$\sin(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being in \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sin,std::sin,8192) + + //! Compute the sinc of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sinc + \f$\mathrm{sinc}(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinc,cimg::sinc,2048) + + //! Compute the tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its tangent \f$\tan(I_{(x,y,z,c)})\f$. + \note + - Pixel values are regarded as being exin \e radian. + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tan,std::tan,2048) + + //! Compute the hyperbolic cosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic cosine + \f$\mathrm{cosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(cosh,std::cosh,2048) + + //! Compute the hyperbolic sine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic sine + \f$\mathrm{sinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(sinh,std::sinh,2048) + + //! Compute the hyperbolic tangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic tangent + \f$\mathrm{tanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(tanh,std::tanh,2048) + + //! Compute the arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosine + \f$\mathrm{acos}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acos,std::acos,8192) + + //! Compute the arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arcsine + \f$\mathrm{asin}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asin,std::asin,8192) + + //! Compute the arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent + \f$\mathrm{atan}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atan,std::atan,8192) + + //! Compute the arctangent2 of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent2 + \f$\mathrm{atan2}(I_{(x,y,z,c)})\f$. + \param img Image whose pixel values specify the second argument of the \c atan2() function. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img_y,img_atan2).display(); + \endcode + **/ + template + CImg& atan2(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return atan2(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_atan2(const CImg& img) const { + return CImg(*this,false).atan2(img); + } + + //! Compute the hyperbolic arccosine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosineh + \f$\mathrm{acosh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(acosh,cimg::acosh,8192) + + //! Compute the hyperbolic arcsine of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arcsine + \f$\mathrm{asinh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(asinh,cimg::asinh,8192) + + //! Compute the hyperbolic arctangent of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic arctangent + \f$\mathrm{atanh}(I_{(x,y,z,c)})\f$. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ + _cimg_create_pointwise_functions(atanh,cimg::atanh,8192) + + //! In-place pointwise multiplication. + /** + Compute the pointwise multiplication between the image instance and the specified input image \c img. + \param img Input image, as the second operand of the multiplication. + \note + - Similar to operator+=(const CImg&), except that it performs a pointwise multiplication + instead of an addition. + - It does \e not perform a \e matrix multiplication. For this purpose, use operator*=(const CImg&) instead. + \par Example + \code + CImg + img("reference.jpg"), + shade(img.width,img.height(),1,1,"-(x-w/2)^2-(y-h/2)^2",false); + shade.normalize(0,1); + (img,shade,img.get_mul(shade)).display(); + \endcode + **/ + template + CImg& mul(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return mul(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_mul(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).mul(img); + } + + //! In-place pointwise division. + /** + Similar to mul(const CImg&), except that it performs a pointwise division instead of a multiplication. + **/ + template + CImg& div(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return div(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_div(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).div(img); + } + + //! Raise each pixel value to a specified power. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its power \f$I_{(x,y,z,c)}^p\f$. + \param p Exponent value. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + \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,img1,img2).display(); + \endcode + **/ + CImg& pow(const double p) { + if (is_empty()) 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.5) return sqrt(); + if (p==1) return *this; + if (p==2) return sqr(); + 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; + } + + //! Raise each pixel value to a specified power \newinstance. + CImg get_pow(const double p) const { + return CImg(*this,false).pow(p); + } + + //! Raise each pixel value to a power, specified from an expression. + /** + Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition. + **/ + CImg& pow(const char *const expression) { + return pow((+*this)._fill(expression,true,1,0,0,"pow",this)); + } + + //! Raise each pixel value to a power, specified from an expression \newinstance. + CImg get_pow(const char *const expression) const { + return CImg(*this,false).pow(expression); + } + + //! Raise each pixel value to a power, pointwisely specified from another image. + /** + Similar to operator+=(const CImg& img), except that it performs an exponentiation instead of an addition. + **/ + template + CImg& pow(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return pow(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_pow(const CImg& img) const { + return CImg(*this,false).pow(img); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(unsigned int), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::rol(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const unsigned int n=1) const { + return (+*this).rol(n); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift. + **/ + CImg& rol(const char *const expression) { + return rol((+*this)._fill(expression,true,1,0,0,"rol",this)); + } + + //! Compute the bitwise left rotation of each pixel value \newinstance. + CImg get_rol(const char *const expression) const { + return (+*this).rol(expression); + } + + //! Compute the bitwise left rotation of each pixel value. + /** + Similar to operator<<=(const CImg&), except that it performs a left rotation instead of a left shift. + **/ + template + CImg& rol(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return rol(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_rol(const CImg& img) const { + return (+*this).rol(img); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(unsigned int), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const unsigned int n=1) { + if (is_empty()) return *this; + cimg_openmp_for(*this,cimg::ror(*ptr,n),32768); + return *this; + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const unsigned int n=1) const { + return (+*this).ror(n); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift. + **/ + CImg& ror(const char *const expression) { + return ror((+*this)._fill(expression,true,1,0,0,"ror",this)); + } + + //! Compute the bitwise right rotation of each pixel value \newinstance. + CImg get_ror(const char *const expression) const { + return (+*this).ror(expression); + } + + //! Compute the bitwise right rotation of each pixel value. + /** + Similar to operator>>=(const CImg&), except that it performs a right rotation instead of a right shift. + **/ + template + CImg& ror(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return ror(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg get_ror(const CImg& img) const { + return (+*this).ror(img); + } + + //! Pointwise min operator between instance image and a value. + /** + \param val Value used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& min(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).min(value); + } + + //! Pointwise min operator between two images. + /** + \param img Image used as the reference argument of the min operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& min(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return min(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_min(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).min(img); + } + + //! Pointwise min operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& min(const char *const expression) { + return min((+*this)._fill(expression,true,1,0,0,"min",this)); + } + + //! Pointwise min operator between an image and an expression \newinstance. + CImg get_min(const char *const expression) const { + return CImg(*this,false).min(expression); + } + + //! Pointwise max operator between instance image and a value. + /** + \param val Value used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& max(const T& value) { + if (is_empty()) return *this; + 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& value) const { + return (+*this).max(value); + } + + //! Pointwise max operator between two images. + /** + \param img Image used as the reference argument of the max operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& max(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return max(+img); + T *ptrd = _data, *const ptre = _data + siz; + if (siz>isiz) for (ulongT n = siz/isiz; n; --n) + for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs + CImg<_cimg_Tt> get_max(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).max(img); + } + + //! Pointwise max operator between an image and an expression. + /** + \param expression Math formula as a C-string. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& max(const char *const expression) { + return max((+*this)._fill(expression,true,1,0,0,"max",this)); + } + + //! Pointwise max operator between an image and an expression \newinstance. + CImg get_max(const char *const expression) const { + return CImg(*this,false).max(expression); + } + + //! Return a reference to the minimum pixel value. + /** + **/ + T& min() { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min; + cimg_for(*this,ptrs,T) if (*ptrsmax_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the maximum pixel value \const. + const T& max() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max; + cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + return *ptr_max; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value. + /** + \param[out] max_val Maximum pixel value. + **/ + template + T& min_max(t& max_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value as well as the maximum pixel value \const. + template + const T& min_max(t& max_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "min_max(): Empty instance.", + cimg_instance); + const T *ptr_min = _data; + T min_value = *ptr_min, max_value = min_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the maximum pixel value as well as the minimum pixel value. + /** + \param[out] min_val Minimum pixel value. + **/ + template + T& max_min(t& min_val) { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "max_min(): Empty instance.", + cimg_instance); + const T *ptr_max = _data; + T max_value = *ptr_max, min_value = max_value; + cimg_for(*this,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val arr(*this,false); + ulongT l = 0, ir = size() - 1; + for ( ; ; ) { + if (ir<=l + 1) { + if (ir==l + 1 && arr[ir]>1; + cimg::swap(arr[mid],arr[l + 1]); + if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]); + if (arr[l + 1]>arr[ir]) cimg::swap(arr[l + 1],arr[ir]); + if (arr[l]>arr[l + 1]) cimg::swap(arr[l],arr[l + 1]); + ulongT i = l + 1, j = ir; + const T pivot = arr[l + 1]; + for ( ; ; ) { + do ++i; while (arr[i]pivot); + if (j=k) ir = j - 1; + if (j<=k) l = i; + } + } + } + + //! Return the median pixel value. + /** + **/ + T median() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "median(): Empty instance.", + cimg_instance); + const ulongT s = size(); + switch (s) { + case 1 : return _data[0]; + case 2 : return cimg::median(_data[0],_data[1]); + case 3 : return cimg::median(_data[0],_data[1],_data[2]); + case 5 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4]); + case 7 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6]); + case 9 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8]); + case 13 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8], + _data[9],_data[10],_data[11],_data[12]); + } + const T res = kth_smallest(s>>1); + return (s%2)?res:(T)((res + kth_smallest((s>>1) - 1))/2); + } + + //! Return the product of all the pixel values. + /** + **/ + double product() const { + if (is_empty()) return 0; + double res = 1; + cimg_for(*this,ptrs,T) res*=(double)*ptrs; + return res; + } + + //! Return the sum of all the pixel values. + /** + **/ + double sum() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res; + } + + //! Return the average pixel value. + /** + **/ + double mean() const { + double res = 0; + cimg_for(*this,ptrs,T) res+=(double)*ptrs; + return res/size(); + } + + //! Return the variance of the pixel values. + /** + \param variance_method Method used to estimate the variance. Can be: + - \c 0: Second moment, computed as + \f$1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2 = + 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right)\f$ + with \f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$. + - \c 1: Best unbiased estimator, computed as \f$\frac{1}{N - 1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 \f$. + - \c 2: Least median of squares. + - \c 3: Least trimmed of squares. + **/ + double variance(const unsigned int variance_method=1) const { + double foo; + return variance_mean(variance_method,foo); + } + + //! Return the variance as well as the average of the pixel values. + /** + \param variance_method Method used to estimate the variance (see variance(const unsigned int) const). + \param[out] mean Average pixel value. + **/ + template + double variance_mean(const unsigned int variance_method, t& mean) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_mean(): Empty instance.", + cimg_instance); + + double variance = 0, average = 0; + const ulongT siz = size(); + switch (variance_method) { + case 0 : { // Least mean square (standard definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = (S2 - S*S/siz)/siz; + average = S; + } break; + case 1 : { // Least mean square (robust definition) + double S = 0, S2 = 0; + cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } + variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + average = S; + } break; + case 2 : { // Least Median of Squares (MAD) + CImg buf(*this,false); + buf.sort(); + const ulongT siz2 = siz>>1; + const double med_i = (double)buf[siz2]; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val; + } + buf.sort(); + const double sig = (double)(1.4828*buf[siz2]); + variance = sig*sig; + } break; + default : { // Least trimmed of Squares + CImg buf(*this,false); + const ulongT siz2 = siz>>1; + cimg_for(buf,ptrs,Tfloat) { + const double val = (double)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val; + } + buf.sort(); + double a = 0; + const Tfloat *ptrs = buf._data; + for (ulongT j = 0; j0?variance:0; + } + + //! Return estimated variance of the noise. + /** + \param variance_method Method used to compute the variance (see variance(const unsigned int) const). + \note Because of structures such as edges in images it is + recommanded to use a robust variance estimation. The variance of the + noise is estimated by computing the variance of the Laplacian \f$(\Delta + I)^2 \f$ scaled by a factor \f$c\f$ insuring \f$ c E[(\Delta I)^2]= + \sigma^2\f$ where \f$\sigma\f$ is the noise variance. + **/ + double variance_noise(const unsigned int variance_method=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "variance_noise(): Empty instance.", + cimg_instance); + + const ulongT siz = size(); + if (!siz || !_data) return 0; + if (variance_method>1) { // Compute a scaled version of the Laplacian + CImg tmp(*this,false); + if (_depth==1) { + 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) { + tmp(x,y,c) = cste*((double)Inc + (double)Ipc + (double)Icn + + (double)Icp - 4*(double)Icc); + } + } + } else { + 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) { + tmp(x,y,z,c) = cste*( + (double)Incc + (double)Ipcc + (double)Icnc + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + } + } + } + return tmp.variance(variance_method); + } + + // Version that doesn't need intermediate images. + double variance = 0, S = 0, S2 = 0; + if (_depth==1) { + const double cste = 1./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 + + (double)Icn + (double)Icp - 4*(double)Icc); + S+=val; S2+=val*val; + } + } else { + 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 * + ((double)Incc + (double)Ipcc + (double)Icnc + + (double)Icpc + + (double)Iccn + (double)Iccp - 6*(double)Iccc); + S+=val; S2+=val*val; + } + } + if (variance_method) variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + else variance = (S2 - S*S/siz)/siz; + return variance>0?variance:0; + } + + //! Compute the MSE (Mean-Squared Error) between two images. + /** + \param img Image used as the second argument of the MSE operator. + **/ + template + double MSE(const CImg& img) const { + if (img.size()!=size()) + throw CImgArgumentException(_cimg_instance + "MSE(): Instance and specified image (%u,%u,%u,%u,%p) have different dimensions.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + double vMSE = 0; + const t* ptr2 = img._data; + cimg_for(*this,ptr1,T) { + const double diff = (double)*ptr1 - (double)*(ptr2++); + vMSE+=diff*diff; + } + const ulongT siz = img.size(); + if (siz) vMSE/=siz; + return vMSE; + } + + //! Compute the PSNR (Peak Signal-to-Noise Ratio) between two images. + /** + \param img Image used as the second argument of the PSNR operator. + \param max_value Maximum theoretical value of the signal. + **/ + template + double PSNR(const CImg& img, const double max_value=255) const { + const double vMSE = (double)std::sqrt(MSE(img)); + return (vMSE!=0)?(double)(20*std::log10(max_value/vMSE)):(double)(cimg::type::max()); + } + + //! Evaluate math formula. + /** + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. + **/ + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { + return _eval(this,expression,x,y,z,c,list_inputs,list_outputs); + } + + //! Evaluate math formula \const. + double eval(const char *const expression, + const double x=0, const double y=0, const double z=0, const double c=0, + const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { + return _eval(0,expression,x,y,z,c,list_inputs,list_outputs); + } + + double _eval(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) return 0; + if (!expression[1]) switch (*expression) { // Single-char optimization + case 'w' : return (double)_width; + case 'h' : return (double)_height; + case 'd' : return (double)_depth; + case 's' : return (double)_spectrum; + case 'r' : return (double)_is_shared; + } + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + const double val = mp(x,y,z,c); + mp.end(); + return val; + } + + //! Evaluate math formula. + /** + \param[out] output Contains values of output vector returned by the evaluated expression + (or is empty if the returned type is scalar). + \param expression Math formula, as a C-string. + \param x Value of the pre-defined variable \c x. + \param y Value of the pre-defined variable \c y. + \param z Value of the pre-defined variable \c z. + \param c Value of the pre-defined variable \c c. + \param list_inputs A list of input images attached to the specified math formula. + \param[out] list_outputs A pointer to a list of output 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); + } + + //! 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); + } + + 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=='<' || + *expression=='*' || *expression==':'),"eval", + *this,img_output,list_inputs,list_outputs,false); + output.assign(1,std::max(1U,mp.result_dim)); + mp(x,y,z,c,output._data); + mp.end(); + } + + //! Evaluate math formula on a set of variables. + /** + \param expression Math formula, as a C-string. + \param xyzc Set of values (x,y,z,c) used for the evaluation. + \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. + **/ + 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); + } + + //! 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); + } + + 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 { + 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_pragma_openmp(parallel if (res._height>=512)) + { + _cimg_math_parser + _mp = omp_get_thread_num()?mp:_cimg_math_parser(), + &lmp = omp_get_thread_num()?_mp:mp; + cimg_pragma_openmp(for) + for (unsigned int i = 0; i[min, max, mean, variance, xmin, ymin, zmin, cmin, xmax, ymax, zmax, cmax, sum, product]. + **/ + CImg get_stats(const unsigned int variance_method=1) const { + if (is_empty()) return CImg(); + const ulongT siz = size(); + const longT off_end = (longT)siz; + double S = 0, S2 = 0, P = 1; + longT offm = 0, offM = 0; + T m = *_data, M = m; + + 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) + for (longT off = 0; offlM) { lM = val; loffM = off; } + S+=_val; + S2+=_val*_val; + P*=_val; + } + cimg_pragma_openmp(critical(get_stats)) { + if (lmM || (lM==M && loffM1?(S2 - S*S/siz)/(siz - 1):0): + variance(variance_method)), + variance_value = _variance_value>0?_variance_value:0; + int + xm = 0, ym = 0, zm = 0, cm = 0, + xM = 0, yM = 0, zM = 0, cM = 0; + contains(_data[offm],xm,ym,zm,cm); + contains(_data[offM],xM,yM,zM,cM); + return CImg(1,14).fill((double)m,(double)M,mean_value,variance_value, + (double)xm,(double)ym,(double)zm,(double)cm, + (double)xM,(double)yM,(double)zM,(double)cM, + S,P); + } + + //! Compute statistics vector from the pixel values \inplace. + CImg& stats(const unsigned int variance_method=1) { + return get_stats(variance_method).move_to(*this); + } + + //@} + //------------------------------------- + // + //! \name Vector / Matrix Operations + //@{ + //------------------------------------- + + //! Compute norm of the image, viewed as a matrix. + /** + \param magnitude_type Norm type. Can be: + - \c -1: Linf-norm + - \c 0: L0-norm + - \c 1: L1-norm + - \c 2: L2-norm + **/ + double magnitude(const int magnitude_type=2) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "magnitude(): Empty instance.", + cimg_instance); + double res = 0; + switch (magnitude_type) { + case -1 : { + cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } + } break; + case 1 : { + cimg_for(*this,ptrs,T) res+=(double)cimg::abs(*ptrs); + } break; + default : { + cimg_for(*this,ptrs,T) res+=(double)cimg::sqr(*ptrs); + res = (double)std::sqrt(res); + } + } + return res; + } + + //! Compute the trace of the image, viewed as a matrix. + /** + **/ + double trace() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "trace(): Empty instance.", + cimg_instance); + double res = 0; + cimg_forX(*this,k) res+=(double)(*this)(k,k); + return res; + } + + //! Compute the determinant of the image, viewed as a matrix. + /** + **/ + double det() const { + if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "det(): Instance is not a square matrix.", + cimg_instance); + + switch (_width) { + case 1 : return (double)((*this)(0,0)); + case 2 : return (double)((*this)(0,0))*(double)((*this)(1,1)) - (double)((*this)(0,1))*(double)((*this)(1,0)); + case 3 : { + const double + a = (double)_data[0], d = (double)_data[1], g = (double)_data[2], + b = (double)_data[3], e = (double)_data[4], h = (double)_data[5], + c = (double)_data[6], f = (double)_data[7], i = (double)_data[8]; + return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e; + } + default : { + CImg lu(*this,false); + CImg indx; + bool d; + lu._LU(indx,d); + double res = d?(double)1:(double)-1; + cimg_forX(lu,i) res*=lu(i,i); + return res; + } + } + } + + //! Compute the dot product between instance and argument, viewed as matrices. + /** + \param img Image used as a second argument of the dot product. + **/ + template + 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 get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + CImg res; + if (res._height!=_spectrum) res.assign(1,_spectrum); + const ulongT whd = (ulongT)_width*_height*_depth; + const T *ptrs = data(x,y,z); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get (square) matrix-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \note - The spectrum() of the image must be a square. + **/ + CImg get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const { + const int n = (int)cimg::round(std::sqrt((double)_spectrum)); + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(n,n); + T *ptrd = res._data; + cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } + return res; + } + + //! Get tensor-valued pixel located at specified position. + /** + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + CImg get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + const T *ptrs = data(x,y,z,0); + const ulongT whd = (ulongT)_width*_height*_depth; + if (_spectrum==6) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd),*(ptrs + 3*whd),*(ptrs + 4*whd),*(ptrs + 5*whd)); + if (_spectrum==3) + return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd)); + return tensor(*ptrs); + } + + //! Set vector-valued pixel at specified position. + /** + \param vec Vector to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_vector_at(const CImg& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) { + if (x<_width && y<_height && z<_depth) { + const t *ptrs = vec._data; + const ulongT whd = (ulongT)_width*_height*_depth; + T *ptrd = data(x,y,z); + for (unsigned int k = std::min((unsigned int)vec.size(),_spectrum); k; --k) { + *ptrd = (T)*(ptrs++); ptrd+=whd; + } + } + return *this; + } + + //! Set (square) matrix-valued pixel at specified position. + /** + \param mat Matrix to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_matrix_at(const CImg& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + return set_vector_at(mat,x,y,z); + } + + //! Set tensor-valued pixel at specified position. + /** + \param ten Tensor to put on the instance image. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + **/ + template + CImg& set_tensor_at(const CImg& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + T *ptrd = data(x,y,z,0); + const ulongT siz = (ulongT)_width*_height*_depth; + if (ten._height==2) { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[3]; + } + else { + *ptrd = (T)ten[0]; ptrd+=siz; + *ptrd = (T)ten[1]; ptrd+=siz; + *ptrd = (T)ten[2]; ptrd+=siz; + *ptrd = (T)ten[4]; ptrd+=siz; + *ptrd = (T)ten[5]; ptrd+=siz; + *ptrd = (T)ten[8]; + } + return *this; + } + + //! Unroll pixel values along axis \c y. + /** + \note Equivalent to \code unroll('y'); \endcode. + **/ + CImg& 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. + **/ + CImg& diagonal() { + return get_diagonal().move_to(*this); + } + + //! Resize image to become a diagonal matrix \newinstance. + CImg get_diagonal() const { + if (is_empty()) return *this; + const unsigned int siz = (unsigned int)size(); + CImg res(siz,siz,1,1,0); + cimg_foroff(*this,off) res((unsigned int)off,(unsigned int)off) = (*this)[off]; + return res; + } + + //! Replace the image by an identity matrix. + /** + \note If the instance image is not square, it is resized to a square matrix using its maximum + dimension as a reference. + **/ + CImg& identity_matrix() { + return identity_matrix(std::max(_width,_height)).move_to(*this); + } + + //! Replace the image by an identity matrix \newinstance. + CImg get_identity_matrix() const { + return identity_matrix(std::max(_width,_height)); + } + + //! Fill image with a linear sequence of values. + /** + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + CImg& sequence(const T& a0, const T& a1) { + if (is_empty()) return *this; + const ulongT siz = size() - 1; + T* ptr = _data; + if (siz) { + const double delta = (double)a1 - (double)a0; + cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz); + } else *ptr = a0; + return *this; + } + + //! Fill image with a linear sequence of values \newinstance. + CImg get_sequence(const T& a0, const T& a1) const { + return (+*this).sequence(a0,a1); + } + + //! Transpose the image, viewed as a matrix. + /** + \note Equivalent to \code permute_axes("yxzc"); \endcode. + **/ + CImg& transpose() { + if (_width==1) { _width = _height; _height = 1; return *this; } + if (_height==1) { _height = _width; _width = 1; return *this; } + if (_width==_height) { + cimg_forYZC(*this,y,z,c) for (int x = y; x get_transpose() const { + return get_permute_axes("yxzc"); + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors. + /** + \param img Image used as the second argument of the cross product. + \note The first argument of the cross product is \c *this. + **/ + template + 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.", + cimg_instance, + img._width,img._height,img._depth,img._spectrum,img._data); + + const T x = (*this)[0], y = (*this)[1], z = (*this)[2]; + (*this)[0] = (T)(y*img[2] - z*img[1]); + (*this)[1] = (T)(z*img[0] - x*img[2]); + (*this)[2] = (T)(x*img[1] - y*img[0]); + return *this; + } + + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors \newinstance. + template + CImg<_cimg_Tt> get_cross(const CImg& img) const { + return CImg<_cimg_Tt>(*this).cross(img); + } + + //! Invert the instance image, viewed as a matrix. + /** + \param use_LU Choose the inverting algorithm. Can be: + - \c true: LU-based matrix inversion. + - \c false: SVD-based matrix inversion. + **/ + CImg& invert(const bool use_LU=true) { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "invert(): Instance is not a square matrix.", + cimg_instance); +#ifdef cimg_use_lapack + int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N]; + Tfloat + *const lapA = new Tfloat[N*N], + *const WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N + l] = (Tfloat)((*this)(k,l)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + else { + cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetri_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N + l]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] WORK; +#else + const double dete = _width>3?-1.: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. && _width==3) { + const double + a = _data[0], d = _data[1], g = _data[2], + b = _data[3], e = _data[4], h = _data[5], + c = _data[6], f = _data[7], i = _data[8]; + _data[0] = (T)((i*e - f*h)/dete), _data[1] = (T)((g*f - i*d)/dete), _data[2] = (T)((d*h - g*e)/dete); + _data[3] = (T)((h*c - i*b)/dete), _data[4] = (T)((i*a - c*g)/dete), _data[5] = (T)((g*b - a*h)/dete); + _data[6] = (T)((b*f - e*c)/dete), _data[7] = (T)((d*c - a*f)/dete), _data[8] = (T)((a*e - d*b)/dete); + } else { + if (use_LU) { // LU-based inverse computation + CImg A(*this,false), indx, col(1,_width); + bool d; + A._LU(indx,d); + cimg_forX(*this,j) { + col.fill(0); + col(j) = 1; + col._solve(A,indx); + cimg_forX(*this,i) (*this)(j,i) = (T)col(i); + } + } else { // SVD-based inverse computation + CImg U(_width,_width), S(1,_width), V(_width,_width); + SVD(U,S,V,false); + U.transpose(); + cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k]; + S.diagonal(); + *this = V*S*U; + } + } +#endif + return *this; + } + + //! Invert the instance image, viewed as a matrix \newinstance. + CImg get_invert(const bool use_LU=true) const { + return CImg(*this,false).invert(use_LU); + } + + //! 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 { + 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(); + cimg_forX(V,x) { + const Tfloat s = S(x), invs = s>tolerance?1/s:0; + cimg_forY(V,y) V(x,y)*=invs; + } + return V*U.transpose(); + } + + //! Solve a system of linear equations. + /** + \param A Matrix of the linear system. + \note Solve \c AX=B where \c B=*this. + **/ + template + CImg& solve(const CImg& A) { + if (_depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "solve(): Instance and specified matrix (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + if (A._width==A._height) { // Classical linear system + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } +#ifdef cimg_use_lapack + char TRANS = 'N'; + int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N]; + Ttfloat + *const lapA = new Ttfloat[N*N], + *const lapB = new Ttfloat[N], + *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*N + l] = (Ttfloat)(A(k,l)); + cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + + if (!INFO) { + cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO); + if (INFO) + cimg::warn(_cimg_instance + "solve(): LAPACK library function dgetrs_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK; +#else + CImg lu(A,false); + CImg indx; + bool d; + lu._LU(indx,d); + _solve(lu,indx); +#endif + } else { // Least-square solution for non-square systems +#ifdef cimg_use_lapack + if (_width!=1) { + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } + char TRANS = 'N'; + int INFO, N = A._width, M = A._height, LWORK = -1, LDA = M, LDB = M, NRHS = _width; + Ttfloat WORK_QUERY; + Ttfloat + * const lapA = new Ttfloat[M*N], + * const lapB = new Ttfloat[M*NRHS]; + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, &WORK_QUERY, LWORK, INFO); + LWORK = (int) WORK_QUERY; + Ttfloat *const WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*M + l] = (Ttfloat)(A(k,l)); + cimg_forXY(*this,k,l) lapB[k*M + l] = (Ttfloat)((*this)(k,l)); + cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, WORK, LWORK, INFO); + if (INFO != 0) + cimg::warn(_cimg_instance + "solve(): LAPACK library function sgels() returned error code %d.", + cimg_instance, + INFO); + assign(NRHS, N); + if (!INFO) + cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l]; + else + assign(A.get_pseudoinvert()*(*this)); + delete[] lapA; delete[] lapB; delete[] WORK; +#else + assign(A.get_pseudoinvert()*(*this)); +#endif + } + return *this; + } + + //! Solve a system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve(A); + } + + template + CImg& _solve(const CImg& A, const CImg& indx) { + typedef _cimg_Ttfloat Ttfloat; + const int N = (int)size(); + 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; + (*this)(i) = (T)sum; + } + for (int i = N - 1; i>=0; --i) { + sum = (*this)(i); + for (int j = i + 1; j + CImg& solve_tridiagonal(const CImg& A) { + const unsigned int siz = (unsigned int)size(); + if (A._width!=3 || A._height!=siz) + throw CImgArgumentException(_cimg_instance + "solve_tridiagonal(): Instance and tridiagonal matrix " + "(%u,%u,%u,%u,%p) have incompatible dimensions.", + cimg_instance, + A._width,A._height,A._depth,A._spectrum,A._data); + typedef _cimg_Ttfloat Ttfloat; + const Ttfloat epsilon = 1e-4f; + CImg B = A.get_column(1), V(*this,false); + for (int i = 1; i<(int)siz; ++i) { + const Ttfloat m = A(0,i)/(B[i - 1]?B[i - 1]:epsilon); + B[i] -= m*A(2,i - 1); + V[i] -= m*V[i - 1]; + } + (*this)[siz - 1] = (T)(V[siz - 1]/(B[siz - 1]?B[siz - 1]:epsilon)); + for (int i = (int)siz - 2; i>=0; --i) (*this)[i] = (T)((V[i] - A(2,i)*(*this)[i + 1])/(B[i]?B[i]:epsilon)); + return *this; + } + + //! Solve a tridiagonal system of linear equations \newinstance. + template + CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg& A) const { + return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(A); + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + const CImg& eigen(CImg& val, CImg &vec) const { + if (is_empty()) { val.assign(); vec.assign(); } + else { + if (_width!=_height || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "eigen(): Instance is not a square matrix.", + cimg_instance); + + if (val.size()<(ulongT)_width) val.assign(1,_width); + if (vec.size()<(ulongT)_width*_width) vec.assign(_width,_width); + switch (_width) { + case 1 : { val[0] = (t)(*this)[0]; vec[0] = (t)1; } break; + case 2 : { + const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a + d; + double f = e*e - 4*(a*d - b*c); + if (f<0) + cimg::warn(_cimg_instance + "eigen(): Complex eigenvalues found.", + cimg_instance); + + f = std::sqrt(f); + const double + l1 = 0.5*(e - f), + l2 = 0.5*(e + f), + b2 = b*b, + norm1 = std::sqrt(cimg::sqr(l2 - a) + b2), + norm2 = std::sqrt(cimg::sqr(l1 - a) + b2); + val[0] = (t)l2; + val[1] = (t)l1; + if (norm1>0) { vec(0,0) = (t)(b/norm1); vec(0,1) = (t)((l2 - a)/norm1); } else { vec(0,0) = 1; vec(0,1) = 0; } + if (norm2>0) { vec(1,0) = (t)(b/norm2); vec(1,1) = (t)((l1 - a)/norm2); } else { vec(1,0) = 1; vec(1,1) = 0; } + } break; + default : + throw CImgInstanceException(_cimg_instance + "eigen(): Eigenvalues computation of general matrices is limited " + "to 2x2 matrices.", + cimg_instance); + } + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. + /** + \return A list of two images [val; vec], whose meaning is similar as in eigen(CImg&,CImg&) const. + **/ + CImgList get_eigen() const { + CImgList res(2); + eigen(res[0],res[1]); + return res; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \param[out] val Vector of the estimated eigenvalues, in decreasing order. + \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. + **/ + template + 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); + + 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 + } + return *this; + } + + //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. + /** + \return A list of two images [val; vec], whose meaning are similar as in + symmetric_eigen(CImg&,CImg&) const. + **/ + CImgList get_symmetric_eigen() const { + CImgList res(2); + symmetric_eigen(res[0],res[1]); + return res; + } + + //! Sort pixel values and get sorting permutations. + /** + \param[out] permutations Permutation map used for the sorting. + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + **/ + template + CImg& sort(CImg& permutations, const bool is_increasing=true) { + permutations.assign(_width,_height,_depth,_spectrum); + if (is_empty()) return *this; + cimg_foroff(permutations,off) permutations[off] = (t)off; + return _quicksort(0,size() - 1,permutations,is_increasing,true); + } + + //! Sort pixel values and get sorting permutations \newinstance. + template + CImg get_sort(CImg& permutations, const bool is_increasing=true) const { + return (+*this).sort(permutations,is_increasing); + } + + //! Sort pixel values. + /** + \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. + \param axis Tells if the value sorting must be done along a specific axis. Can be: + - \c 0: All pixel values are sorted, independently on their initial position. + - \c 'x': Image columns are sorted, according to the first value in each column. + - \c 'y': Image rows are sorted, according to the first value in each row. + - \c 'z': Image slices are sorted, according to the first value in each slice. + - \c 'c': Image channels are sorted, according to the first value in each channel. + **/ + CImg& sort(const bool is_increasing=true, const char axis=0) { + if (is_empty()) return *this; + CImg perm; + switch (cimg::lowercase(axis)) { + case 0 : + _quicksort(0,size() - 1,perm,is_increasing,false); + break; + case 'x' : { + perm.assign(_width); + get_crop(0,0,0,0,_width - 1,0,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(perm[x],y,z,c); + } break; + case 'y' : { + perm.assign(_height); + get_crop(0,0,0,0,0,_height - 1,0,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,perm[y],z,c); + } break; + case 'z' : { + perm.assign(_depth); + get_crop(0,0,0,0,0,0,_depth - 1,0).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,perm[z],c); + } break; + case 'c' : { + perm.assign(_spectrum); + get_crop(0,0,0,0,0,0,0,_spectrum - 1).sort(perm,is_increasing); + CImg img(*this,false); + cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,z,perm[c]); + } break; + default : + throw CImgArgumentException(_cimg_instance + "sort(): Invalid specified axis '%c' " + "(should be { x | y | z | c }).", + cimg_instance,axis); + } + return *this; + } + + //! Sort pixel values \newinstance. + CImg get_sort(const bool is_increasing=true, const char axis=0) const { + return (+*this).sort(is_increasing,axis); + } + + template + CImg& _quicksort(const long indm, const long indM, CImg& permutations, + const bool is_increasing, const bool is_permutations) { + if (indm(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]>(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]>(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } else { + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + if ((*this)[mid]<(*this)[indM]) { + cimg::swap((*this)[indM],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); + } + if ((*this)[indm]<(*this)[mid]) { + cimg::swap((*this)[indm],(*this)[mid]); + if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); + } + } + if (indM - indm>=3) { + const T pivot = (*this)[mid]; + long i = indm, j = indM; + if (is_increasing) { + do { + while ((*this)[i]pivot) --j; + if (i<=j) { + if (is_permutations) cimg::swap(permutations[i],permutations[j]); + cimg::swap((*this)[i++],(*this)[j--]); + } + } while (i<=j); + } else { + do { + while ((*this)[i]>pivot) ++i; + while ((*this)[j] A; // Input matrix (assumed to contain some values) + CImg<> U,S,V; + A.SVD(U,S,V) + \endcode + **/ + 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 { + if (is_empty()) { U.assign(); S.assign(); V.assign(); } + else { + U = *this; + if (lambda!=0) { + const unsigned int delta = std::min(U._width,U._height); + for (unsigned int i = 0; i rv1(_width); + t anorm = 0, c, f, g = 0, h, s, scale = 0; + int l = 0, nm = 0; + + cimg_forX(U,i) { + l = i + 1; rv1[i] = scale*g; g = s = scale = 0; + if (i=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; --i) { + if (i=0; --i) { + l = i + 1; g = S[i]; + for (int j = l; j=0; --k) { + for (unsigned int its = 0; its=1; --l) { + nm = l - 1; + if ((cimg::abs(rv1[l]) + anorm)==anorm) { flag = false; break; } + if ((cimg::abs(S[nm]) + anorm)==anorm) break; + } + if (flag) { + c = 0; s = 1; + for (int i = l; i<=k; ++i) { + f = s*rv1[i]; rv1[i] = c*rv1[i]; + if ((cimg::abs(f) + anorm)==anorm) break; + g = S[i]; h = cimg::_hypot(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h; + cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c + z*s; U(i,j) = z*c - y*s; } + } + } + + const t z = S[k]; + if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; } + nm = k - 1; + t x = S[l], y = S[nm]; + g = rv1[nm]; h = rv1[k]; + f = ((y - z)*(y + z)+(g - h)*(g + h))/std::max((t)1e-25,2*h*y); + g = cimg::_hypot(f,(t)1); + f = ((x - z)*(x + z)+h*((y/(f + (f>=0?g:-g))) - h))/std::max((t)1e-25,x); + c = s = 1; + for (int j = l; j<=nm; ++j) { + const int i = j + 1; + g = rv1[i]; h = s*g; g = c*g; + t y = S[i]; + t z = cimg::_hypot(f,h); + rv1[j] = z; c = f/std::max((t)1e-25,z); s = h/std::max((t)1e-25,z); + f = x*c + g*s; g = g*c - x*s; h = y*s; y*=c; + cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c + z*s; V(i,jj) = z*c - x*s; } + z = cimg::_hypot(f,h); S[j] = z; + if (z) { z = 1/std::max((t)1e-25,z); c = f*z; s = h*z; } + f = c*g + s*y; x = c*y - s*g; + cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c + z*s; U(i,jj) = z*c - y*s; } + } + rv1[l] = 0; rv1[k]=f; S[k]=x; + } + } + + if (sorting) { + CImg permutations; + CImg tmp(_width); + S.sort(permutations,false); + cimg_forY(U,k) { + cimg_forY(permutations,y) tmp(y) = U(permutations(y),k); + std::memcpy(U.data(0,k),tmp._data,sizeof(t)*_width); + } + cimg_forY(V,k) { + cimg_forY(permutations,y) tmp(y) = V(permutations(y),k); + std::memcpy(V.data(0,k),tmp._data,sizeof(t)*_width); + } + } + } + return *this; + } + + //! Compute the SVD of the instance image, viewed as a general matrix. + /** + \return A list of three images [U; S; V], whose meaning is similar as in + SVD(CImg&,CImg&,CImg&,bool,unsigned int,float) const. + **/ + CImgList get_SVD(const bool sorting=true, + const unsigned int max_iteration=40, const float lambda=0) const { + CImgList res(3); + SVD(res[0],res[1],res[2],sorting,max_iteration,lambda); + return res; + } + + // [internal] Compute the LU decomposition of a permuted matrix. + template + CImg& _LU(CImg& indx, bool& d) { + const int N = width(); + int imax = 0; + CImg vv(N); + indx.assign(N); + d = true; + cimg_forX(*this,i) { + Tfloat vmax = 0; + cimg_forX(*this,j) { + const Tfloat tmp = cimg::abs((*this)(j,i)); + if (tmp>vmax) vmax = tmp; + } + if (vmax==0) { indx.fill(0); return fill(0); } + vv[i] = 1/vmax; + } + cimg_forX(*this,j) { + for (int i = 0; i=vmax) { vmax=tmp; imax=i; } + } + if (j!=imax) { + cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j)); + d =!d; + vv[imax] = vv[j]; + } + indx[j] = (t)imax; + if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20; + if (j + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + 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 " + "than number of nodes %u.", + pixel_type(),starting_node,nb_nodes); + CImg dist(1,nb_nodes,1,1,cimg::type::max()); + dist(starting_node) = 0; + previous_node.assign(1,nb_nodes,1,1,(t)-1); + previous_node(starting_node) = (t)starting_node; + CImg Q(nb_nodes); + cimg_forX(Q,u) Q(u) = (unsigned int)u; + cimg::swap(Q(starting_node),Q(0)); + unsigned int sizeQ = nb_nodes; + while (sizeQ) { + // Update neighbors from minimal vertex + const unsigned int umin = Q(0); + if (umin==ending_node) sizeQ = 0; + else { + const T dmin = dist(umin); + const T infty = cimg::type::max(); + for (unsigned int q = 1; qdist(Q(left))) || + (rightdist(Q(right)));) { + if (right + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + const unsigned int starting_node, const unsigned int ending_node=~0U) { + CImg foo; + return dijkstra(distance,nb_nodes,starting_node,ending_node,foo); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + /** + \param starting_node Indice of the starting node. + \param ending_node Indice of the ending node. + \param previous_node Array that gives the previous node indice in the path to the starting node + (optional parameter). + \return Array of distances of each node to the starting node. + \note image instance corresponds to the adjacency matrix of the graph. + **/ + template + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) { + return get_dijkstra(starting_node,ending_node,previous_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + template + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, + CImg& previous_node) const { + if (_width!=_height || _depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "dijkstra(): Instance is not a graph adjacency matrix.", + cimg_instance); + + return dijkstra(*this,_width,starting_node,ending_node,previous_node); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) { + return get_dijkstra(starting_node,ending_node).move_to(*this); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const { + CImg foo; + return get_dijkstra(starting_node,ending_node,foo); + } + + //! Return an image containing the Ascii codes of the specified string. + /** + \param str input C-string to encode as an image. + \param is_last_zero Tells if the ending \c '0' character appear in the resulting image. + \param is_shared Return result that shares its buffer with \p str. + **/ + static CImg string(const char *const str, const bool is_last_zero=true, const bool is_shared=false) { + if (!str) return CImg(); + 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 vector(const T& a0) { + CImg r(1,1); + r[0] = a0; + return r; + } + + //! Return a \c 1x2 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + **/ + static CImg vector(const T& a0, const T& a1) { + CImg r(1,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + return r; + } + + //! Return a \c 1x3 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + **/ + static CImg vector(const T& a0, const T& a1, const T& a2) { + CImg r(1,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + return r; + } + + //! Return a \c 1x4 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + \param a3 Fourth vector value. + **/ + static CImg 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; + 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; + 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; + 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; + 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; + 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; + return r; + } + + //! Return a \c 1x10 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) { + 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; + return r; + } + + //! Return a \c 1x11 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) { + 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; + return r; + } + + //! Return a \c 1x12 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) { + 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; + return r; + } + + //! Return a \c 1x13 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) { + 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; + return r; + } + + //! Return a \c 1x14 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) { + 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; + return r; + } + + //! Return a \c 1x15 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) { + 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; + return r; + } + + //! Return a \c 1x16 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) { + 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; + return r; + } + + //! Return a 1x1 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg matrix(const T& a0) { + return vector(a0); + } + + //! Return a 2x2 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, + const T& a2, const T& a3) { + CImg r(2,2); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; + *(ptr++) = a2; *(ptr++) = a3; + return r; + } + + //! Return a 3x3 matrix containing specified coefficients. + /** + \param a0 First matrix value. + \param a1 Second matrix value. + \param a2 Third matrix value. + \param a3 Fourth matrix value. + \param a4 Fifth matrix value. + \param a5 Sixth matrix value. + \param a6 Seventh matrix value. + \param a7 Eighth matrix value. + \param a8 Nineth matrix value. + **/ + static CImg matrix(const T& a0, const T& a1, const T& a2, + const T& a3, const T& a4, const T& a5, + const T& a6, const T& a7, const T& a8) { + CImg r(3,3); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; + *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; + return r; + } + + //! Return a 4x4 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13, const T& a14, const T& a15) { + CImg r(4,4); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15; + return r; + } + + //! Return a 5x5 matrix containing specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, + const T& a5, const T& a6, const T& a7, const T& a8, const T& a9, + const T& a10, const T& a11, const T& a12, const T& a13, const T& a14, + const T& a15, const T& a16, const T& a17, const T& a18, const T& a19, + const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) { + CImg r(5,5); T *ptr = r._data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; + *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9; + *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; + *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19; + *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24; + return r; + } + + //! Return a 1x1 symmetric matrix containing specified coefficients. + /** + \param a0 First matrix value. + \note Equivalent to vector(const T&). + **/ + static CImg tensor(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 symmetric matrix tensor containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2) { + return matrix(a0,a1,a1,a2); + } + + //! Return a 3x3 symmetric matrix containing specified coefficients. + static CImg tensor(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { + return matrix(a0,a1,a2,a1,a3,a4,a2,a4,a5); + } + + //! Return a 1x1 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1) { + return matrix(a0,0,0,a1); + } + + //! Return a 3x3 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2) { + return matrix(a0,0,0,0,a1,0,0,0,a2); + } + + //! Return a 4x4 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3) { + return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3); + } + + //! Return a 5x5 diagonal matrix containing specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { + return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4); + } + + //! Return a NxN identity matrix. + /** + \param N Dimension of the matrix. + **/ + static CImg identity_matrix(const unsigned int N) { + CImg res(N,N,1,1,0); + cimg_forX(res,x) res(x,x) = 1; + return res; + } + + //! Return a N-numbered sequence vector from \p a0 to \p a1. + /** + \param N Size of the resulting vector. + \param a0 Starting value of the sequence. + \param a1 Ending value of the sequence. + **/ + static CImg sequence(const unsigned int N, const T& a0, const T& a1) { + if (N) return CImg(1,N).sequence(a0,a1); + return CImg(); + } + + //! Return a 3x3 rotation matrix from an { axis + angle } or a quaternion. + /** + \param x X-coordinate of the rotation axis, or first quaternion coordinate. + \param y Y-coordinate of the rotation axis, or second quaternion coordinate. + \param z Z-coordinate of the rotation axis, or third quaternion coordinate. + \param w Angle of the rotation axis (in degree), or fourth quaternion coordinate. + \param is_quaternion Tell is the four arguments denotes a set { axis + angle } or a quaternion (x,y,z,w). + **/ + static CImg rotation_matrix(const float x, const float y, const float z, const float w, + const bool is_quaternion=false) { + double X, Y, Z, W, N; + if (is_quaternion) { + N = std::sqrt((double)x*x + (double)y*y + (double)z*z + (double)w*w); + if (N>0) { X = x/N; Y = y/N; Z = z/N; W = w/N; } + else { X = Y = Z = 0; W = 1; } + return CImg::matrix((T)(X*X + Y*Y - Z*Z - W*W),(T)(2*Y*Z - 2*X*W),(T)(2*X*Z + 2*Y*W), + (T)(2*X*W + 2*Y*Z),(T)(X*X - Y*Y + Z*Z - W*W),(T)(2*Z*W - 2*X*Y), + (T)(2*Y*W - 2*X*Z),(T)(2*X*Y + 2*Z*W),(T)(X*X - Y*Y - Z*Z + W*W)); + } + N = cimg::hypot((double)x,(double)y,(double)z); + if (N>0) { X = x/N; Y = y/N; Z = z/N; } + else { X = Y = 0; Z = 1; } + const double ang = w*cimg::PI/180, c = std::cos(ang), omc = 1 - c, s = std::sin(ang); + return CImg::matrix((T)(X*X*omc + c),(T)(X*Y*omc - Z*s),(T)(X*Z*omc + Y*s), + (T)(X*Y*omc + Z*s),(T)(Y*Y*omc + c),(T)(Y*Z*omc - X*s), + (T)(X*Z*omc - Y*s),(T)(Y*Z*omc + X*s),(T)(Z*Z*omc + c)); + } + + //@} + //----------------------------------- + // + //! \name Value Manipulation + //@{ + //----------------------------------- + + //! Fill all pixel values with specified value. + /** + \param val Fill value. + **/ + CImg& fill(const T& val) { + if (is_empty()) return *this; + if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val; + else std::memset(_data,(int)(ulongT)val,sizeof(T)*size()); // Double cast to allow val to be (void*) + return *this; + } + + //! Fill all pixel values with specified value \newinstance. + CImg get_fill(const T& val) const { + return CImg(_width,_height,_depth,_spectrum).fill(val); + } + + //! Fill sequentially all pixel values with specified values. + /** + \param val0 First fill value. + \param val1 Second fill value. + **/ + CImg& fill(const T& val0, const T& val1) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 1; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 2; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 3; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 4; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 5; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 6; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 7; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 8; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 9; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 10; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 11; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 12; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 13; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 14; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14); + } + + //! Fill sequentially all pixel values with specified values \overloading. + CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) { + if (is_empty()) return *this; + T *ptrd, *ptre = end() - 15; + for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5, + const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11, + const T& val12, const T& val13, const T& val14, const T& val15) const { + return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, + val11,val12,val13,val14,val15); + } + + //! Fill sequentially pixel values according to a given expression. + /** + \param expression C-string describing a math formula, or a sequence of values. + \param repeat_values In case a list of values is provided, tells if this list must be repeated for the filling. + \param allow_formula Tells that mathematical formulas are authorized for the filling. + \param list_inputs In case of a mathematical expression, attach a list of images to the specified expression. + \param[out] list_outputs In case of a math expression, list of images atatched to the specified expression. + **/ + CImg& 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?1:0,list_inputs,list_outputs,"fill",0); + } + + // 'formula_mode' = { 0 = does not allow formula | 1 = allow formula | + // 2 = allow formula but do not fill image values }. + CImg& _fill(const char *const expression, const bool repeat_values, const unsigned int formula_mode, + const CImgList *const list_inputs, CImgList *const list_outputs, + const char *const calling_function, const CImg *provides_copy) { + if (is_empty() || !expression || !*expression) return *this; + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + CImg is_error; + bool is_value_sequence = false; + cimg_abort_init; + + if (formula_mode) { + + // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser. + double value; + char sep; + const int err = cimg_sscanf(expression,"%lf %c",&value,&sep); + if (err==1 || (err==2 && sep==',')) { + if (err==1) return fill((T)value); + else is_value_sequence = true; + } + + // Try to fill values according to a formula. + _cimg_abort_init_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 + + bool do_in_parallel = false; +#ifdef cimg_use_openmp + cimg_openmp_if(*expression=='*' || *expression==':' || + (mp.is_parallelizable && _width>=(cimg_openmp_sizefactor)*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; + if (formula_mode==2) 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; + if (formula_mode==2) 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 cimg_openmp_collapse(2)) + cimg_forYZ(*this,y,z) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,0); + else { + 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 + } + + } else { // Scalar-valued expression + T *ptrd = *expression=='<'?end() - 1:_data; + if (*expression=='<') { + if (formula_mode==2) 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); } + } else if (*expression=='>' || !do_in_parallel) { + if (formula_mode==2) 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); } + } 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 cimg_openmp_collapse(3)) + cimg_forYZC(*this,y,z,c) _cimg_abort_try_omp { + cimg_abort_test; + if (formula_mode==2) cimg_forX(*this,x) lmp(x,y,z,c); + else { + 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 + } +#endif + } + } + mp.end(); + } catch (CImgException& e) { CImg::string(e._message).move_to(is_error); } + } + + // Try to fill values according to a value sequence. + if (!formula_mode || 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?1:0,list_inputs,list_outputs); + } + + //! Fill sequentially pixel values according to the values found in another image. + /** + \param values Image containing the values used for the filling. + \param repeat_values In case there are less values than necessary in \c values, tells if these values must be + repeated for the filling. + **/ + template + CImg& fill(const CImg& values, const bool repeat_values=true) { + if (is_empty() || !values) return *this; + T *ptrd = _data, *ptre = ptrd + size(); + for (t *ptrs = values._data, *ptrs_end = ptrs + values.size(); ptrs + CImg get_fill(const CImg& values, const bool repeat_values=true) const { + return repeat_values?CImg(_width,_height,_depth,_spectrum).fill(values,repeat_values): + (+*this).fill(values,repeat_values); + } + + //! Fill pixel values along the X-axis at a specified pixel position. + /** + \param y Y-coordinate of the filled column. + \param z Z-coordinate of the filled column. + \param c C-coordinate of the filled column. + \param a0 First fill value. + **/ + CImg& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const int a0, ...) { +#define _cimg_fill1(x,y,z,c,off,siz,t) { \ + va_list ap; va_start(ap,a0); T *ptrd = data(x,y,z,c); *ptrd = (T)a0; \ + for (unsigned int k = 1; k& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const double a0, ...) { + if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,double); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position. + /** + \param x X-coordinate of the filled row. + \param z Z-coordinate of the filled row. + \param c C-coordinate of the filled row. + \param a0 First fill value. + **/ + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const int a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,int); + return *this; + } + + //! Fill pixel values along the Y-axis at a specified pixel position \overloading. + CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) { + if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position. + /** + \param x X-coordinate of the filled slice. + \param y Y-coordinate of the filled slice. + \param c C-coordinate of the filled slice. + \param a0 First fill value. + **/ + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int); + return *this; + } + + //! Fill pixel values along the Z-axis at a specified pixel position \overloading. + CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) { + const ulongT wh = (ulongT)_width*_height; + if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position. + /** + \param x X-coordinate of the filled channel. + \param y Y-coordinate of the filled channel. + \param z Z-coordinate of the filled channel. + \param a0 First filling value. + **/ + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int); + return *this; + } + + //! Fill pixel values along the C-axis at a specified pixel position \overloading. + CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) { + const ulongT whd = (ulongT)_width*_height*_depth; + if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double); + return *this; + } + + //! Discard specified sequence of values in the image buffer, along a specific axis. + /** + \param values Sequence of values to discard. + \param axis Axis along which the values are discarded. If set to \c 0 (default value) + the method does it for all the buffer values and returns a one-column vector. + \note Discarded values will change the image geometry, so the resulting image + is returned as a one-column vector. + **/ + template + CImg& discard(const CImg& values, const char axis=0) { + if (is_empty() || !values) return *this; + return get_discard(values,axis).move_to(*this); + } + + template + CImg get_discard(const CImg& values, const char axis=0) const { + CImg res; + if (!values) return +*this; + if (is_empty()) return res; + const ulongT vsiz = values.size(); + const char _axis = cimg::lowercase(axis); + ulongT j = 0; + unsigned int k = 0; + int i0 = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) { + if ((*this)(i)!=(T)values[j]) { + if (j) --i; + res.draw_image(k,get_columns(i0,i)); + k+=i - i0 + 1; i0 = i + 1; j = 0; + } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = i + 1; } } + } + if (i0=vsiz) { j = 0; i0 = (int)i + 1; }} + } + const ulongT siz = size(); + if ((ulongT)i0& discard(const char axis=0) { + return get_discard(axis).move_to(*this); + } + + //! Discard neighboring duplicates in the image buffer, along the specified axis \newinstance. + CImg get_discard(const char axis=0) const { + CImg res; + if (is_empty()) return res; + const char _axis = cimg::lowercase(axis); + T current = *_data?(T)0:(T)1; + int j = 0; + res.assign(width(),height(),depth(),spectrum()); + switch (_axis) { + case 'x' : { + cimg_forX(*this,i) + if ((*this)(i)!=current) { res.draw_image(j++,get_column(i)); current = (*this)(i); } + res.resize(j,-100,-100,-100,0); + } break; + case 'y' : { + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { res.draw_image(0,j++,get_row(i)); current = (*this)(0,i); } + res.resize(-100,j,-100,-100,0); + } break; + case 'z' : { + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { res.draw_image(0,0,j++,get_slice(i)); current = (*this)(0,0,i); } + res.resize(-100,-100,j,-100,0); + } break; + case 'c' : { + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { res.draw_image(0,0,0,j++,get_channel(i)); current = (*this)(0,0,0,i); } + res.resize(-100,-100,-100,j,0); + } break; + default : { + res.unroll('y'); + cimg_foroff(*this,i) + if ((*this)[i]!=current) res[j++] = current = (*this)[i]; + res.resize(-100,j,-100,-100,0); + } + } + return res; + } + + //! Invert endianness of all pixel values. + /** + **/ + CImg& invert_endianness() { + cimg::invert_endianness(_data,size()); + return *this; + } + + //! Invert endianness of all pixel values \newinstance. + CImg get_invert_endianness() const { + return (+*this).invert_endianness(); + } + + //! Fill image with random values in specified range. + /** + \param val_min Minimal authorized random value. + \param val_max Maximal authorized random value. + \note Random variables are uniformely distributed in [val_min,val_max]. + **/ + CImg& 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_pragma_openmp(parallel cimg_openmp_if_size(size(),524288)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; + } + + //! Fill image with random values in specified range \newinstance. + CImg get_rand(const T& val_min, const T& val_max) const { + return (+*this).rand(val_min,val_max); + } + + //! Round pixel values. + /** + \param y Rounding precision. + \param rounding_type Rounding type. Can be: + - \c -1: Backward. + - \c 0: Nearest. + - \c 1: Forward. + **/ + CImg& round(const double y=1, const int rounding_type=0) { + if (y>0) cimg_openmp_for(*this,cimg::round(*ptr,y,rounding_type),8192); + return *this; + } + + //! Round pixel values \newinstance. + CImg get_round(const double y=1, const unsigned int rounding_type=0) const { + return (+*this).round(y,rounding_type); + } + + //! Add random noise to pixel values. + /** + \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the + global value range. + \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper, + \p 3=Poisson or \p 4=Rician). + \return A reference to the modified image instance. + \note + - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on + the image value itself. + - Function \p CImg::get_noise() is also defined. It returns a non-shared modified copy of the image instance. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_noise(40); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_noise.jpg + **/ + CImg& noise(const double sigma, const unsigned int noise_type=0) { + if (is_empty()) return *this; + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + Tfloat nsigma = (Tfloat)sigma, m = 0, M = 0; + if (nsigma==0 && noise_type!=3) return *this; + if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M); + if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.); + switch (noise_type) { + case 0 : { // Gaussian noise + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) { + Tfloat val = (Tfloat)(_data[off] + nsigma*cimg::grand(&rng)); + if (val>vmax) val = vmax; + if (valvmax) val = vmax; + if (val::is_float()) { --m; ++M; } + else { m = (Tfloat)cimg::type::min(); M = (Tfloat)cimg::type::max(); } + } + cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),131072)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) if (cimg::rand(100,&rng)vmax) val = vmax; + if (val get_noise(const double sigma, const unsigned int noise_type=0) const { + return (+*this).noise(sigma,noise_type); + } + + //! Linearly normalize pixel values. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(160,220); + (img,res).display(); + \endcode + \image html ref_normalize2.jpg + **/ + CImg& normalize(const T& min_value, const T& max_value) { + 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); + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm. + /** + \par Example + \code + const CImg img("reference.jpg"), res = img.get_normalize(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_normalize.jpg + **/ + CImg& normalize() { + const ulongT whd = (ulongT)_width*_height*_depth; + 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) { + const T *ptrs = ptrd; + float n = 0; + cimg_forC(*this,c) { n+=cimg::sqr((float)*ptrs); ptrs+=whd; } + n = (float)std::sqrt(n); + T *_ptrd = ptrd++; + if (n>0) cimg_forC(*this,c) { *_ptrd = (T)(*_ptrd/n); _ptrd+=whd; } + else cimg_forC(*this,c) { *_ptrd = (T)0; _ptrd+=whd; } + } + } + return *this; + } + + //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm \newinstance. + CImg get_normalize() const { + return CImg(*this,false).normalize(); + } + + //! Compute Lp-norm of each multi-valued pixel of the image instance. + /** + \param norm_type Type of computed vector norm (can be \p -1=Linf, or \p greater or equal than 0). + \par Example + \code + const CImg img("reference.jpg"), res = img.get_norm(); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_norm.jpg + **/ + CImg& norm(const int norm_type=2) { + if (_spectrum==1 && norm_type) return abs(); + return get_norm(norm_type).move_to(*this); + } + + //! Compute L2-norm of each multi-valued pixel of the image instance \newinstance. + CImg get_norm(const int norm_type=2) const { + if (is_empty()) return *this; + if (_spectrum==1 && norm_type) return get_abs(); + const ulongT whd = (ulongT)_width*_height*_depth; + CImg res(_width,_height,_depth); + switch (norm_type) { + 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { const Tfloat val = (Tfloat)cimg::abs(*_ptrs); if (val>n) n = val; _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 0 : { // L0-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + unsigned int n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=*_ptrs==0?0:1; _ptrs+=whd; } + *(ptrd++) = (Tfloat)n; + } + } + } break; + case 1 : { // L1-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; } + *(ptrd++) = n; + } + } + } break; + case 2 : { // L2-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n); + } + } + } break; + default : { // Linf-norm + cimg_pragma_openmp(parallel for 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; + Tfloat *ptrd = res._data + off; + cimg_forX(*this,x) { + Tfloat n = 0; + const T *_ptrs = ptrs++; + cimg_forC(*this,c) { n+=std::pow(cimg::abs((Tfloat)*_ptrs),(Tfloat)norm_type); _ptrs+=whd; } + *(ptrd++) = (Tfloat)std::pow((Tfloat)n,1/(Tfloat)norm_type); + } + } + } + } + return res; + } + + //! Cut pixel values in specified range. + /** + \param min_value Minimum desired value of the resulting image. + \param max_value Maximum desired value of the resulting image. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_cut(160,220); + (img,res).display(); + \endcode + \image html ref_cut.jpg + **/ + CImg& cut(const T& min_value, const T& max_value) { + if (is_empty()) return *this; + const T a = min_value get_cut(const T& min_value, const T& max_value) const { + return (+*this).cut(min_value,max_value); + } + + //! Uniformly quantize pixel values. + /** + \param nb_levels Number of quantization levels. + \param keep_range Tells if resulting values keep the same range as the original ones. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_quantize(4); + (img,res).display(); + \endcode + \image html ref_quantize.jpg + **/ + CImg& quantize(const unsigned int nb_levels, const bool keep_range=true) { + if (!nb_levels) + throw CImgArgumentException(_cimg_instance + "quantize(): Invalid quantization request with 0 values.", + cimg_instance); + + if (is_empty()) return *this; + Tfloat m, M = (Tfloat)max_min(m), range = M - m; + if (range>0) { + if (keep_range) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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; + } + + //! Uniformly quantize pixel values \newinstance. + CImg get_quantize(const unsigned int n, const bool keep_range=true) const { + return (+*this).quantize(n,keep_range); + } + + //! Threshold pixel values. + /** + \param value Threshold value + \param soft_threshold Tells if soft thresholding must be applied (instead of hard one). + \param strict_threshold Tells if threshold value is strict. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_threshold(128); + (img,res.normalize(0,255)).display(); + \endcode + \image html ref_threshold.jpg + **/ + CImg& threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) { + if (is_empty()) return *this; + if (strict_threshold) { + if (soft_threshold) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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(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(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(size(),65536)) + cimg_rofoff(*this,off) _data[off] = _data[off]>=value?(T)1:(T)0; + } + return *this; + } + + //! Threshold pixel values \newinstance. + CImg get_threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) const { + return (+*this).threshold(value,soft_threshold,strict_threshold); + } + + //! Compute the histogram of pixel values. + /** + \param nb_levels Number of desired histogram levels. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \note + - The histogram H of an image I is the 1D function where H(x) counts the number of 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. + \par Example + \code + const CImg img = CImg("reference.jpg").histogram(256); + img.display_graph(0,3); + \endcode + \image html ref_histogram.jpg + **/ + CImg& histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) { + return get_histogram(nb_levels,min_value,max_value).move_to(*this); + } + + //! Compute the histogram of pixel values \overloading. + CImg& histogram(const unsigned int nb_levels) { + return get_histogram(nb_levels).move_to(*this); + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) const { + if (!nb_levels || is_empty()) return CImg(); + const double + vmin = (double)(min_value res(nb_levels,1,1,1,0); + cimg_rof(*this,ptrs,T) { + const T val = *ptrs; + if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels - 1:(unsigned int)((val - vmin)*nb_levels/(vmax - vmin))]; + } + return res; + } + + //! Compute the histogram of pixel values \newinstance. + CImg get_histogram(const unsigned int nb_levels) const { + if (!nb_levels || is_empty()) return CImg(); + T vmax = 0, vmin = min_max(vmax); + return get_histogram(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values. + /** + \param nb_levels Number of histogram levels used for the equalization. + \param min_value Minimum pixel value considered for the histogram computation. + All pixel values lower than \p min_value will not be counted. + \param max_value Maximum pixel value considered for the histogram computation. + All pixel values higher than \p max_value will not be counted. + \par Example + \code + const CImg img("reference.jpg"), res = img.get_equalize(256); + (img,res).display(); + \endcode + \image html ref_equalize.jpg + **/ + CImg& equalize(const unsigned int nb_levels, const T& min_value, const T& max_value) { + if (!nb_levels || is_empty()) return *this; + const T + vmin = min_value hist = get_histogram(nb_levels,vmin,vmax); + ulongT cumul = 0; + cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; } + if (!cumul) cumul = 1; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(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; + } + + //! Equalize histogram of pixel values \overloading. + CImg& equalize(const unsigned int nb_levels) { + if (!nb_levels || is_empty()) return *this; + T vmax = 0, vmin = min_max(vmax); + return equalize(nb_levels,vmin,vmax); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels, const T& val_min, const T& val_max) const { + return (+*this).equalize(nblevels,val_min,val_max); + } + + //! Equalize histogram of pixel values \newinstance. + CImg get_equalize(const unsigned int nblevels) const { + return (+*this).equalize(nblevels); + } + + //! Index multi-valued pixels regarding to a specified colormap. + /** + \param colormap Multi-valued colormap used as the basis for multi-valued pixel indexing. + \param dithering Level of dithering (0=disable, 1=standard level). + \param map_indexes Tell if the values of the resulting image are the colormap indices or the colormap vectors. + \note + - \p img.index(colormap,dithering,1) is equivalent to img.index(colormap,dithering,0).map(colormap). + \par Example + \code + const CImg img("reference.jpg"), colormap(3,1,1,3, 0,128,255, 0,128,255, 0,128,255); + const CImg res = img.get_index(colormap,1,true); + (img,res).display(); + \endcode + \image html ref_index.jpg + **/ + template + CImg& index(const CImg& colormap, const float dithering=1, const bool map_indexes=false) { + return get_index(colormap,dithering,map_indexes).move_to(*this); + } + + //! Index multi-valued pixels regarding to a specified colormap \newinstance. + template + CImg::Tuint> + get_index(const CImg& colormap, const float dithering=1, const bool map_indexes=true) const { + if (colormap._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "index(): Instance and specified colormap (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + typedef typename CImg::Tuint tuint; + if (is_empty()) return CImg(); + const ulongT + 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 + const float ndithering = cimg::cut(dithering,0,1)/16; + Tfloat valm = 0, valM = (Tfloat)max_min(valm); + if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; } + CImg cache = get_crop(-1,0,0,0,_width,1,0,_spectrum - 1); + Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0); + const ulongT cwhd = (ulongT)cache._width*cache._height*cache._depth; + switch (_spectrum) { + case 1 : { // Optimized for scalars + cimg_forYZ(*this,y,z) { + if (yvalM?valM:_val0; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0valM?valM:_val0, + _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1, + _val2 = (Tfloat)*ptrs2, val2 = _val2valM?valM:_val2; + Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrpvalM?valM:_val; + dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd; + } + if (dist=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0=(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::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0=(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, + *ptrs_end = ptrs0 + _width; ptrs0::max(); const t *ptrmin0 = colormap._data; + for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, + *ptrp_end = ptrp1; ptrp0=(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::max(); const t *ptrmin = colormap._data; + for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrp img("reference.jpg"), + colormap1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255), + colormap2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255), + res = img.get_index(colormap1,0).map(colormap2); + (img,res).display(); + \endcode + \image html ref_map.jpg + **/ + template + CImg& map(const CImg& colormap, const unsigned int boundary_conditions=0) { + return get_map(colormap,boundary_conditions).move_to(*this); + } + + //! Map predefined colormap on the scalar (indexed) image instance \newinstance. + template + 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); + + const ulongT + whd = (ulongT)_width*_height*_depth, + cwhd = (ulongT)colormap._width*colormap._height*colormap._depth, + cwhd2 = 2*cwhd; + CImg res(_width,_height,_depth,colormap._spectrum==1?_spectrum:colormap._spectrum); + switch (colormap._spectrum) { + + case 1 : { // Optimized for scalars + const T *ptrs = _data; + switch (boundary_conditions) { + case 3 : // Mirror + cimg_for(res,ptrd,t) { + const ulongT ind = ((ulongT)*(ptrs++))%cwhd2; + *ptrd = colormap[ind& label(const bool is_high_connectivity=false, const Tfloat tolerance=0) { + return get_label(is_high_connectivity,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + CImg get_label(const bool is_high_connectivity=false, + const Tfloat tolerance=0) const { + if (is_empty()) return CImg(); + + // Create neighborhood tables. + int dx[13], dy[13], dz[13], nb = 0; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 0; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 0; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 0; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 0; + } + if (_depth>1) { // 3D version + dx[nb] = 0; dy[nb] = 0; dz[nb++]=1; + if (is_high_connectivity) { + dx[nb] = 1; dy[nb] = 1; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = -1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = -1; + dx[nb] = 0; dy[nb] = 1; dz[nb++] = -1; + + dx[nb] = 0; dy[nb] = 1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = -1; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 0; dz[nb++] = 1; + dx[nb] = 1; dy[nb] = 1; dz[nb++] = 1; + } + } + return _label(nb,dx,dy,dz,tolerance); + } + + //! Label connected components \overloading. + /** + \param connectivity_mask Mask of the neighboring pixels. + \param tolerance Tolerance used to determine if two neighboring pixels belong to the same region. + **/ + template + CImg& label(const CImg& connectivity_mask, const Tfloat tolerance=0) { + return get_label(connectivity_mask,tolerance).move_to(*this); + } + + //! Label connected components \newinstance. + template + CImg get_label(const CImg& connectivity_mask, + const Tfloat tolerance=0) const { + 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); + nb = 0; + cimg_forXYZ(connectivity_mask,x,y,z) if ((x || y || z) && + connectivity_mask(x,y,z)) { + dx[nb] = x; dy[nb] = y; dz[nb++] = z; + } + return _label(nb,dx,dy,dz,tolerance); + } + + CImg _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); + + // Init label numbers. + ulongT *ptr = _res.data(); + cimg_foroff(_res,p) *(ptr++) = p; + + // For each neighbour-direction, label. + for (unsigned int n = 0; n& _system_strescape() { +#define cimg_system_strescape(c,s) case c : if (p!=ptrs) CImg(ptrs,(unsigned int)(p-ptrs),1,1,1,false).\ + move_to(list); \ + CImg(s,(unsigned int)std::strlen(s),1,1,1,false).move_to(list); ptrs = p + 1; break + CImgList list; + const T *ptrs = _data; + cimg_for(*this,p,T) switch ((int)*p) { + cimg_system_strescape('\\',"\\\\"); + cimg_system_strescape('\"',"\\\""); + cimg_system_strescape('!',"\"\\!\""); + cimg_system_strescape('`',"\\`"); + cimg_system_strescape('$',"\\$"); + } + if (ptrs(ptrs,(unsigned int)(end()-ptrs),1,1,1,false).move_to(list); + return (list>'x').move_to(*this); + } + + //@} + //--------------------------------- + // + //! \name Color Base Management + //@{ + //--------------------------------- + + //! Return colormap \e "default", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_default.jpg + **/ + static const CImg& default_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,256,1,3); + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + colormap(0,index,0) = (Tuchar)r; + colormap(0,index,1) = (Tuchar)g; + colormap(0,index++,2) = (Tuchar)b; + } + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "HSV", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hsv.jpg + **/ + static const CImg& HSV_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + CImg tmp(1,256,1,3,1); + tmp.get_shared_channel(0).sequence(0,359); + colormap = tmp.HSVtoRGB(); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "lines", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_lines.jpg + **/ + static const CImg& 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; + } + + //! Return colormap \e "hot", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_hot.jpg + **/ + static const CImg& hot_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[1] = colormap[2] = colormap[3] = colormap[6] = colormap[7] = colormap[11] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cool", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cool.jpg + **/ + static const CImg& cool_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) colormap.assign(1,2,1,3).fill((T)0,(T)255,(T)255,(T)0,(T)255,(T)255).resize(1,256,1,3,3); + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "jet", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_jet.jpg + **/ + static const CImg& jet_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[2] = colormap[3] = colormap[5] = colormap[6] = colormap[8] = colormap[9] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "flag", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_flag.jpg + **/ + static const CImg& flag_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,4,1,3,(T)0); + colormap[0] = colormap[1] = colormap[5] = colormap[9] = colormap[10] = 255; + colormap.resize(1,256,1,3,0,2); + } + cimg::mutex(8,0); + return colormap; + } + + //! Return colormap \e "cube", containing 256 colors entries in RGB. + /** + \return The following \c 256x1x1x3 colormap is returned: + \image html ref_colormap_cube.jpg + **/ + static const CImg& cube_LUT256() { + static CImg colormap; + cimg::mutex(8); + if (!colormap) { + colormap.assign(1,8,1,3,(T)0); + colormap[1] = colormap[3] = colormap[5] = colormap[7] = + colormap[10] = colormap[11] = colormap[12] = colormap[13] = + colormap[20] = colormap[21] = colormap[22] = colormap[23] = 255; + colormap.resize(1,256,1,3,3); + } + cimg::mutex(8,0); + return colormap; + } + + //! Convert pixel values from sRGB to RGB color spaces. + CImg& sRGBtoRGB() { + if (is_empty()) return *this; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + sval = (Tfloat)_data[off]/255, + val = (Tfloat)(sval<=0.04045f?sval/12.92f:std::pow((sval + 0.055f)/(1.055f),2.4f)); + _data[off] = (T)cimg::cut(val*255,0,255); + } + return *this; + } + + //! Convert pixel values from sRGB to RGB color spaces \newinstance. + CImg get_sRGBtoRGB() const { + return CImg(*this,false).sRGBtoRGB(); + } + + //! 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(size(),32)) + cimg_rofoff(*this,off) { + const Tfloat + val = (Tfloat)_data[off]/255, + sval = (Tfloat)(val<=0.0031308f?val*12.92f:1.055f*std::pow(val,0.416667f) - 0.055f); + _data[off] = (T)cimg::cut(sval*255,0,255); + } + return *this; + } + + //! Convert pixel values from RGB to sRGB color spaces \newinstance. + CImg get_RGBtosRGB() const { + return CImg(*this,false).RGBtosRGB(); + } + + //! Convert pixel values from RGB to HSI color spaces. + CImg& RGBtoHSI() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSI(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSI color spaces \newinstance. + CImg get_RGBtoHSI() const { + return CImg(*this,false).RGBtoHSI(); + } + + //! Convert pixel values from HSI to RGB color spaces. + CImg& HSItoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSItoRGB(): Instance is not a HSI image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSItoRGB() const { + return CImg< Tuchar>(*this,false).HSItoRGB(); + } + + //! Convert pixel values from RGB to HSL color spaces. + CImg& RGBtoHSL() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSL(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSL color spaces \newinstance. + CImg get_RGBtoHSL() const { + return CImg(*this,false).RGBtoHSL(); + } + + //! Convert pixel values from HSL to RGB color spaces. + CImg& HSLtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSLtoRGB(): Instance is not a HSL image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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.f/3 - ntr):p, + G = 6*ntg<1?p + (q - p)*6*ntg:2*ntg<1?q:3*ntg<2?p + (q - p)*6*(2.f/3 - ntg):p, + B = 6*ntb<1?p + (q - p)*6*ntb:2*ntb<1?q:3*ntb<2?p + (q - p)*6*(2.f/3 - ntb):p; + p1[N] = (T)cimg::cut(255*R,0,255); + p2[N] = (T)cimg::cut(255*G,0,255); + p3[N] = (T)cimg::cut(255*B,0,255); + } + return *this; + } + + //! Convert pixel values from HSL to RGB color spaces \newinstance. + CImg get_HSLtoRGB() const { + return CImg(*this,false).HSLtoRGB(); + } + + //! Convert pixel values from RGB to HSV color spaces. + CImg& RGBtoHSV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoHSV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT 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); + } + return *this; + } + + //! Convert pixel values from RGB to HSV color spaces \newinstance. + CImg get_RGBtoHSV() const { + return CImg(*this,false).RGBtoHSV(); + } + + //! Convert pixel values from HSV to RGB color spaces. + CImg& HSVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "HSVtoRGB(): Instance is not a HSV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256)) + for (longT N = 0; N get_HSVtoRGB() const { + return CImg(*this,false).HSVtoRGB(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& RGBtoYCbCr() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYCbCr(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_RGBtoYCbCr() const { + return CImg(*this,false).RGBtoYCbCr(); + } + + //! Convert pixel values from RGB to YCbCr color spaces. + CImg& YCbCrtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YCbCrtoRGB(): Instance is not a YCbCr image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,512)) + for (longT N = 0; N get_YCbCrtoRGB() const { + return CImg(*this,false).YCbCrtoRGB(); + } + + //! Convert pixel values from RGB to YUV color spaces. + CImg& RGBtoYUV() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoYUV(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_RGBtoYUV() const { + return CImg(*this,false).RGBtoYUV(); + } + + //! Convert pixel values from YUV to RGB color spaces. + CImg& YUVtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "YUVtoRGB(): Instance is not a YUV image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,16384)) + for (longT N = 0; N get_YUVtoRGB() const { + return CImg< Tuchar>(*this,false).YUVtoRGB(); + } + + //! Convert pixel values from RGB to CMY color spaces. + CImg& RGBtoCMY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoCMY(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoCMY() const { + return CImg(*this,false).RGBtoCMY(); + } + + //! Convert pixel values from CMY to RGB color spaces. + CImg& CMYtoRGB() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoRGB(): Instance is not a CMY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_CMYtoRGB() const { + return CImg(*this,false).CMYtoRGB(); + } + + //! Convert pixel values from CMY to CMYK color spaces. + CImg& CMYtoCMYK() { + return get_CMYtoCMYK().move_to(*this); + } + + //! Convert pixel values from CMY to CMYK color spaces \newinstance. + CImg get_CMYtoCMYK() const { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "CMYtoCMYK(): Instance is not a CMY image.", + cimg_instance); + + CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT 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; } + pd1[N] = (Tfloat)cimg::cut(C,0,255), + pd2[N] = (Tfloat)cimg::cut(M,0,255), + pd3[N] = (Tfloat)cimg::cut(Y,0,255), + pd4[N] = (Tfloat)cimg::cut(K,0,255); + } + return res; + } + + //! Convert pixel values from CMYK to CMY color spaces. + CImg& CMYKtoCMY() { + return get_CMYKtoCMY().move_to(*this); + } + + //! Convert pixel values from CMYK to CMY color spaces \newinstance. + CImg get_CMYKtoCMY() const { + if (_spectrum!=4) + throw CImgInstanceException(_cimg_instance + "CMYKtoCMY(): Instance is not a CMYK image.", + cimg_instance); + + 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,1024)) + for (longT N = 0; N& RGBtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "RGBtoXYZ(): Instance is not a RGB image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_RGBtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).RGBtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to RGB color spaces. + /** + \param use_D65 Tell to use the D65 illuminant (D50 otherwise). + **/ + CImg& XYZtoRGB(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoRGB(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,2048)) + for (longT N = 0; N get_XYZtoRGB(const bool use_D65=true) const { + return CImg(*this,false).XYZtoRGB(use_D65); + } + + //! Convert pixel values from XYZ to Lab color spaces. + CImg& XYZtoLab(const bool use_D65=true) { +#define _cimg_Labf(x) (24389*(x)>216?cimg::cbrt(x):(24389*(x)/27 + 16)/116) + + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoLab(): Instance is not a XYZ image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N get_XYZtoLab(const bool use_D65=true) const { + return CImg(*this,false).XYZtoLab(use_D65); + } + + //! Convert pixel values from Lab to XYZ color spaces. + CImg& LabtoXYZ(const bool use_D65=true) { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "LabtoXYZ(): Instance is not a Lab image.", + cimg_instance); + const CImg 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 longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,128)) + for (longT N = 0; N216?cX*cX*cX:(116*cX - 16)*27/24389), + Y = (Tfloat)(27*L>216?cY*cY*cY:27*L/24389), + Z = (Tfloat)(24389*cZ>216?cZ*cZ*cZ:(116*cZ - 16)*27/24389); + p1[N] = (T)(X*white[0]); + p2[N] = (T)(Y*white[1]); + p3[N] = (T)(Z*white[2]); + } + return *this; + } + + //! Convert pixel values from Lab to XYZ color spaces \newinstance. + CImg get_LabtoXYZ(const bool use_D65=true) const { + return CImg(*this,false).LabtoXYZ(use_D65); + } + + //! Convert pixel values from XYZ to xyY color spaces. + CImg& XYZtoxyY() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "XYZtoxyY(): Instance is not a XYZ image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?sum:1; + p1[N] = (T)(X/nsum); + p2[N] = (T)(Y/nsum); + p3[N] = (T)Y; + } + return *this; + } + + //! Convert pixel values from XYZ to xyY color spaces \newinstance. + CImg get_XYZtoxyY() const { + return CImg(*this,false).XYZtoxyY(); + } + + //! Convert pixel values from xyY pixels to XYZ color spaces. + CImg& xyYtoXYZ() { + if (_spectrum!=3) + throw CImgInstanceException(_cimg_instance + "xyYtoXYZ(): Instance is not a xyY image.", + cimg_instance); + + T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); + const longT whd = (longT)width()*height()*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,4096)) + for (longT N = 0; N0?py:1; + p1[N] = (T)(px*Y/ny); + p2[N] = (T)Y; + p3[N] = (T)((1 - px - py)*Y/ny); + } + return *this; + } + + //! Convert pixel values from xyY pixels to XYZ color spaces \newinstance. + CImg get_xyYtoXYZ() const { + return CImg(*this,false).xyYtoXYZ(); + } + + //! Convert pixel values from RGB to Lab color spaces. + CImg& RGBtoLab(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoLab(use_D65); + } + + //! Convert pixel values from RGB to Lab color spaces \newinstance. + CImg get_RGBtoLab(const bool use_D65=true) const { + return CImg(*this,false).RGBtoLab(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces. + CImg& LabtoRGB(const bool use_D65=true) { + return LabtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from Lab to RGB color spaces \newinstance. + CImg get_LabtoRGB(const bool use_D65=true) const { + return CImg(*this,false).LabtoRGB(use_D65); + } + + //! Convert pixel values from RGB to xyY color spaces. + CImg& RGBtoxyY(const bool use_D65=true) { + return RGBtoXYZ(use_D65).XYZtoxyY(); + } + + //! Convert pixel values from RGB to xyY color spaces \newinstance. + CImg get_RGBtoxyY(const bool use_D65=true) const { + return CImg(*this,false).RGBtoxyY(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces. + CImg& xyYtoRGB(const bool use_D65=true) { + return xyYtoXYZ().XYZtoRGB(use_D65); + } + + //! Convert pixel values from xyY to RGB color spaces \newinstance. + CImg get_xyYtoRGB(const bool use_D65=true) const { + return CImg(*this,false).xyYtoRGB(use_D65); + } + + //! Convert pixel values from RGB to CMYK color spaces. + CImg& RGBtoCMYK() { + return RGBtoCMY().CMYtoCMYK(); + } + + //! Convert pixel values from RGB to CMYK color spaces \newinstance. + CImg get_RGBtoCMYK() const { + return CImg(*this,false).RGBtoCMYK(); + } + + //! Convert pixel values from CMYK to RGB color spaces. + CImg& CMYKtoRGB() { + return CMYKtoCMY().CMYtoRGB(); + } + + //! Convert pixel values from CMYK to RGB color spaces \newinstance. + CImg get_CMYKtoRGB() const { + return CImg(*this,false).CMYKtoRGB(); + } + + //@} + //------------------------------------------ + // + //! \name Geometric / Spatial Manipulation + //@{ + //------------------------------------------ + + static float _cimg_lanczos(const float x) { + if (x<=-2 || x>=2) return 0; + const float a = (float)cimg::PI*x, b = 0.5f*a; + return (float)(x?std::sin(a)*std::sin(b)/(a*b):1); + } + + //! Resize image to new dimensions. + /** + \param size_x Number of columns (new size along the X-axis). + \param size_y Number of rows (new size along the Y-axis). + \param size_z Number of slices (new size along the Z-axis). + \param size_c Number of vector-channels (new size along the C-axis). + \param interpolation_type Method of interpolation: + - -1 = no interpolation: raw memory resizing. + - 0 = no interpolation: additional space is filled according to \p boundary_conditions. + - 1 = nearest-neighbor interpolation. + - 2 = moving average interpolation. + - 3 = linear interpolation. + - 4 = grid interpolation. + - 5 = cubic interpolation. + - 6 = lanczos interpolation. + \param boundary_conditions Type of boundary conditions used if necessary. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100). + **/ + CImg& resize(const int size_x, const int size_y=-100, + const int size_z=-100, const int size_c=-100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + if (!size_x || !size_y || !size_z || !size_c) return assign(); + const unsigned int + _sx = (unsigned int)(size_x<0?-size_x*width()/100:size_x), + _sy = (unsigned int)(size_y<0?-size_y*height()/100:size_y), + _sz = (unsigned int)(size_z<0?-size_z*depth()/100:size_z), + _sc = (unsigned int)(size_c<0?-size_c*spectrum()/100:size_c), + sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1; + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return *this; + if (is_empty()) return assign(sx,sy,sz,sc,(T)0); + if (interpolation_type==-1 && sx*sy*sz*sc==size()) { + _width = sx; _height = sy; _depth = sz; _spectrum = sc; + return *this; + } + return get_resize(sx,sy,sz,sc,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c).move_to(*this); + } + + //! Resize image to new dimensions \newinstance. + CImg get_resize(const int size_x, const int size_y = -100, + const int size_z = -100, const int size_c = -100, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + if (centering_x<0 || centering_x>1 || centering_y<0 || centering_y>1 || + centering_z<0 || centering_z>1 || centering_c<0 || centering_c>1) + throw CImgArgumentException(_cimg_instance + "resize(): Specified centering arguments (%g,%g,%g,%g) are outside range [0,1].", + cimg_instance, + centering_x,centering_y,centering_z,centering_c); + + if (!size_x || !size_y || !size_z || !size_c) return CImg(); + const unsigned int + sx = std::max(1U,(unsigned int)(size_x>=0?size_x:-size_x*width()/100)), + sy = std::max(1U,(unsigned int)(size_y>=0?size_y:-size_y*height()/100)), + sz = std::max(1U,(unsigned int)(size_z>=0?size_z:-size_z*depth()/100)), + sc = std::max(1U,(unsigned int)(size_c>=0?size_c:-size_c*spectrum()/100)); + if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this; + if (is_empty()) return CImg(sx,sy,sz,sc,(T)0); + CImg res; + switch (interpolation_type) { + + // Raw resizing. + // + case -1 : + std::memcpy(res.assign(sx,sy,sz,sc,(T)0)._data,_data,sizeof(T)*std::min(size(),(ulongT)sx*sy*sz*sc)); + break; + + // No interpolation. + // + case 0 : { + const int + xc = (int)(centering_x*((int)sx - width())), + yc = (int)(centering_y*((int)sy - height())), + zc = (int)(centering_z*((int)sz - depth())), + cc = (int)(centering_c*((int)sc - spectrum())); + + switch (boundary_conditions) { + case 3 : { // Mirror + res.assign(sx,sy,sz,sc); + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),65536)) + cimg_forXYZC(res,x,y,z,c) { + const int + mx = cimg::mod(x - xc,w2), my = cimg::mod(y - yc,h2), + mz = cimg::mod(z - zc,d2), mc = cimg::mod(c - cc,s2); + res(x,y,z,c) = (*this)(mx sprite; + if (xc>0) { // X-backward + res.get_crop(xc,yc,zc,cc,xc,yc + height() - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc - 1; x>=0; --x) res.draw_image(x,yc,zc,cc,sprite); + } + if (xc + width()<(int)sx) { // X-forward + res.get_crop(xc + width() - 1,yc,zc,cc,xc + width() - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int x = xc + width(); x<(int)sx; ++x) res.draw_image(x,yc,zc,cc,sprite); + } + if (yc>0) { // Y-backward + res.get_crop(0,yc,zc,cc,sx - 1,yc,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc - 1; y>=0; --y) res.draw_image(0,y,zc,cc,sprite); + } + if (yc + height()<(int)sy) { // Y-forward + res.get_crop(0,yc + height() - 1,zc,cc,sx - 1,yc + height() - 1, + zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int y = yc + height(); y<(int)sy; ++y) res.draw_image(0,y,zc,cc,sprite); + } + if (zc>0) { // Z-backward + res.get_crop(0,0,zc,cc,sx - 1,sy - 1,zc,cc + spectrum() - 1).move_to(sprite); + for (int z = zc - 1; z>=0; --z) res.draw_image(0,0,z,cc,sprite); + } + if (zc + depth()<(int)sz) { // Z-forward + res.get_crop(0,0,zc +depth() - 1,cc,sx - 1,sy - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); + for (int z = zc + depth(); z<(int)sz; ++z) res.draw_image(0,0,z,cc,sprite); + } + if (cc>0) { // C-backward + res.get_crop(0,0,0,cc,sx - 1,sy - 1,sz - 1,cc).move_to(sprite); + for (int c = cc - 1; c>=0; --c) res.draw_image(0,0,0,c,sprite); + } + if (cc + spectrum()<(int)sc) { // C-forward + res.get_crop(0,0,0,cc + spectrum() - 1,sx - 1,sy - 1,sz - 1,cc + spectrum() - 1).move_to(sprite); + for (int c = cc + spectrum(); c<(int)sc; ++c) res.draw_image(0,0,0,c,sprite); + } + } break; + default : // Dirichlet + res.assign(sx,sy,sz,sc,(T)0).draw_image(xc,yc,zc,cc,*this); + } + break; + } break; + + // Nearest neighbor interpolation. + // + case 1 : { + res.assign(sx,sy,sz,sc); + CImg off_x(sx), off_y(sy + 1), off_z(sz + 1), off_c(sc + 1); + const ulongT + wh = (ulongT)_width*_height, + whd = (ulongT)_width*_height*_depth, + sxy = (ulongT)sx*sy, + sxyz = (ulongT)sx*sy*sz, + 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 = (x + one)*_width/sx; + *(poff_x++) = curr - old; + } + } + if (sy==_height) off_y.fill(_width); + else { + ulongT *poff_y = off_y._data, curr = 0; + cimg_forY(res,y) { + const ulongT old = curr; + curr = (y + one)*_height/sy; + *(poff_y++) = _width*(curr - old); + } + *poff_y = 0; + } + if (sz==_depth) off_z.fill(wh); + else { + ulongT *poff_z = off_z._data, curr = 0; + cimg_forZ(res,z) { + const ulongT old = curr; + curr = (z + one)*_depth/sz; + *(poff_z++) = wh*(curr - old); + } + *poff_z = 0; + } + if (sc==_spectrum) off_c.fill(whd); + else { + ulongT *poff_c = off_c._data, curr = 0; + cimg_forC(res,c) { + const ulongT old = curr; + curr = (c + one)*_spectrum/sc; + *(poff_c++) = whd*(curr - old); + } + *poff_c = 0; + } + + T *ptrd = res._data; + const T* ptrc = _data; + const ulongT *poff_c = off_c._data; + for (unsigned int c = 0; c tmp(sx,_height,_depth,_spectrum,0); + for (unsigned int a = _width*sx, b = _width, c = sx, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d; + if (!b) { + cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)/=_width; + ++t; + b = _width; + } + if (!c) { ++s; c = sx; } + } + tmp.move_to(res); + instance_first = false; + } + if (sy!=_height) { + CImg tmp(sx,sy,_depth,_spectrum,0); + for (unsigned int a = _height*sy, b = _height, c = sy, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d; + else + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d; + if (!b) { + cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)/=_height; + ++t; + b = _height; + } + if (!c) { ++s; c = sy; } + } + tmp.move_to(res); + instance_first = false; + } + if (sz!=_depth) { + CImg tmp(sx,sy,sz,_spectrum,0); + for (unsigned int a = _depth*sz, b = _depth, c = sz, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d; + else + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d; + if (!b) { + cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)/=_depth; + ++t; + b = _depth; + } + if (!c) { ++s; c = sz; } + } + tmp.move_to(res); + instance_first = false; + } + if (sc!=_spectrum) { + CImg tmp(sx,sy,sz,sc,0); + for (unsigned int a = _spectrum*sc, b = _spectrum, c = sc, s = 0, t = 0; a; ) { + const unsigned int d = std::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d; + else + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d; + if (!b) { + cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=_spectrum; + ++t; + b = _spectrum; + } + if (!c) { ++s; c = sc; } + } + tmp.move_to(res); + instance_first = false; + } + } break; + + // Linear interpolation. + // + case 3 : { + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *ptrs = data(0,y,z,c), *const ptrsmax = ptrs + _width - 1; + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + 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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height - 1)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + 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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth - 1)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrssc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + 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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum - 1)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrs resx, resy, resz, resc; + if (sx!=_width) { + if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + resx.assign(sx,_height,_depth,_spectrum,(T)0); + const int dx = (int)(2*sx), dy = 2*width(); + int err = (int)(dy + centering_x*(sx*dy/width() - dy)), xs = 0; + cimg_forX(resx,x) if ((err-=dy)<=0) { + cimg_forYZC(resx,y,z,c) resx(x,y,z,c) = (*this)(xs,y,z,c); + ++xs; + err+=dx; + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (sy<_height) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + resy.assign(sx,sy,_depth,_spectrum,(T)0); + const int dx = (int)(2*sy), dy = 2*height(); + int err = (int)(dy + centering_y*(sy*dy/height() - dy)), ys = 0; + cimg_forY(resy,y) if ((err-=dy)<=0) { + cimg_forXZC(resy,x,z,c) resy(x,y,z,c) = resx(x,ys,z,c); + ++ys; + err+=dx; + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (sz<_depth) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + resz.assign(sx,sy,sz,_spectrum,(T)0); + const int dx = (int)(2*sz), dy = 2*depth(); + int err = (int)(dy + centering_z*(sz*dy/depth() - dy)), zs = 0; + cimg_forZ(resz,z) if ((err-=dy)<=0) { + cimg_forXYC(resz,x,y,c) resz(x,y,z,c) = resy(x,y,zs,c); + ++zs; + err+=dx; + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (sc<_spectrum) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + resc.assign(sx,sy,sz,sc,(T)0); + const int dx = (int)(2*sc), dy = 2*spectrum(); + int err = (int)(dy + centering_c*(sc*dy/spectrum() - dy)), cs = 0; + cimg_forC(resc,c) if ((err-=dy)<=0) { + cimg_forXYZ(resc,x,y,z) resc(x,y,z,c) = resz(x,y,z,cs); + ++cs; + err+=dx; + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Cubic interpolation. + // + case 5 : { + const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - 1):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + 1):val1, + val3 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sx):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sx):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxy):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + val1 = (double)*ptrs, + val0 = ptrs>ptrs0?(double)*(ptrs - sxyz):val1, + val2 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val1, + val3 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Lanczos interpolation. + // + case 6 : { + const double vmin = (double)cimg::type::min(), vmax = (double)cimg::type::max(); + CImg off(cimg::max(sx,sy,sz,sc)); + CImg foff(off._width); + CImg resx, resy, resz, resc; + double curr, old; + + if (sx!=_width) { + if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); + else { + if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); + else { + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resx.size(),65536)) + cimg_forYZC(resx,y,z,c) { + const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1, + *const ptrsmax = ptrs0 + (_width - 2); + T *ptrd = resx.data(0,y,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forX(resx,x) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - 1):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + 1):val2, + val4 = ptrsvmax?vmax:val); + ptrs+=*(poff++); + } + } + } + } + } else resx.assign(*this,true); + + if (sy!=_height) { + if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); + else { + if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); + else { + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resy.size(),65536)) + cimg_forXZC(resy,x,z,c) { + const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx, + *const ptrsmax = ptrs0 + (_height - 2)*sx; + T *ptrd = resy.data(x,0,z,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forY(resy,y) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sx):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sx):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sx):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sx; + ptrs+=*(poff++); + } + } + } + } + resx.assign(); + } else resy.assign(resx,true); + + if (sz!=_depth) { + if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); + else { + if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); + else { + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resz.size(),65536)) + cimg_forXYC(resz,x,y,c) { + const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy, + *const ptrsmax = ptrs0 + (_depth - 2)*sxy; + T *ptrd = resz.data(x,y,0,c); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forZ(resz,z) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxy):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxy):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxy; + ptrs+=*(poff++); + } + } + } + } + resy.assign(); + } else resz.assign(resy,true); + + if (sc!=_spectrum) { + if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); + else { + if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); + else { + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(resc.size(),65536)) + cimg_forXYZ(resc,x,y,z) { + const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz, + *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; + T *ptrd = resc.data(x,y,z,0); + const unsigned int *poff = off._data; + const double *pfoff = foff._data; + cimg_forC(resc,c) { + const double + t = *(pfoff++), + w0 = _cimg_lanczos(t + 2), + w1 = _cimg_lanczos(t + 1), + w2 = _cimg_lanczos(t), + w3 = _cimg_lanczos(t - 1), + w4 = _cimg_lanczos(t - 2), + val2 = (double)*ptrs, + val1 = ptrs>=ptrsmin?(double)*(ptrs - sxyz):val2, + val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxyz):val1, + val3 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val2, + val4 = ptrsvmax?vmax:val); + ptrd+=sxyz; + ptrs+=*(poff++); + } + } + } + } + resz.assign(); + } else resc.assign(resz,true); + + return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; + } break; + + // Unknow interpolation. + // + default : + throw CImgArgumentException(_cimg_instance + "resize(): Invalid specified interpolation %d " + "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | " + "5=cubic | 6=lanczos }).", + cimg_instance, + interpolation_type); + } + return res; + } + + //! Resize image to dimensions of another image. + /** + \param src Reference image used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + template + CImg& resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of another image \newinstance. + template + CImg get_resize(const CImg& src, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window. + /** + \param disp Reference display window used for dimensions. + \param interpolation_type Interpolation method. + \param boundary_conditions Boundary conditions. + \param centering_x Set centering type (only if \p interpolation_type=0). + \param centering_y Set centering type (only if \p interpolation_type=0). + \param centering_z Set centering type (only if \p interpolation_type=0). + \param centering_c Set centering type (only if \p interpolation_type=0). + **/ + CImg& resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) { + return resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to dimensions of a display window \newinstance. + CImg get_resize(const CImgDisplay& disp, + const int interpolation_type=1, const unsigned int boundary_conditions=0, + const float centering_x = 0, const float centering_y = 0, + const float centering_z = 0, const float centering_c = 0) const { + return get_resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, + centering_x,centering_y,centering_z,centering_c); + } + + //! Resize image to half-size along XY axes, using an optimized filter. + CImg& resize_halfXY() { + return get_resize_halfXY().move_to(*this); + } + + //! Resize image to half-size along XY axes, using an optimized filter \newinstance. + CImg get_resize_halfXY() const { + if (is_empty()) return *this; + static const Tfloat kernel[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f, + 0.1231940459f, 0.1935127547f, 0.1231940459f, + 0.07842776544f, 0.1231940459f, 0.07842776544f }; + CImg I(9), res(_width/2,_height/2,_depth,_spectrum); + T *ptrd = res._data; + cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T) + if (x%2 && y%2) *(ptrd++) = (T) + (I[0]*kernel[0] + I[1]*kernel[1] + I[2]*kernel[2] + + I[3]*kernel[3] + I[4]*kernel[4] + I[5]*kernel[5] + + I[6]*kernel[6] + I[7]*kernel[7] + I[8]*kernel[8]); + return res; + } + + //! Resize image to double-size, using the Scale2X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_doubleXY() { + return get_resize_doubleXY().move_to(*this); + } + + //! Resize image to double-size, using the Scale2X algorithm \newinstance. + CImg get_resize_doubleXY() const { +#define _cimg_gs2x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=(res)._width, ptrd2+=(res)._width) + +#define _cimg_gs2x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs2x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(_width<<1,_height<<1,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width; + _cimg_gs2x_for3x3(*this,x,y,z,c,I,T) { + if (Icp!=Icn && Ipc!=Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = Ipc==Icn?Ipc:Icc; + *(ptrd2++) = Icn==Inc?Inc:Icc; + } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; } + } + } + return res; + } + + //! Resize image to triple-size, using the Scale3X algorithm. + /** + \note Use anisotropic upscaling algorithm + described here. + **/ + CImg& resize_tripleXY() { + return get_resize_tripleXY().move_to(*this); + } + + //! Resize image to triple-size, using the Scale3X algorithm \newinstance. + CImg get_resize_tripleXY() const { +#define _cimg_gs3x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound) - 1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=2*(res)._width, ptrd2+=2*(res)._width, ptrd3+=2*(res)._width) + +#define _cimg_gs3x_for3x3(img,x,y,z,c,I,T) \ + _cimg_gs3x_for3((img)._height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ + (I[3] = I[4] = (T)(img)(0,y,z,c)), \ + (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ + 1>=(img)._width?(img).width() - 1:1); \ + (_n1##x<(img).width() && ( \ + (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ + (I[5] = (T)(img)(_n1##x,y,z,c)), \ + (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(3*_width,3*_height,_depth,_spectrum); + CImg_3x3(I,T); + cimg_forZC(*this,z,c) { + T + *ptrd1 = res.data(0,0,z,c), + *ptrd2 = ptrd1 + res._width, + *ptrd3 = ptrd2 + res._width; + _cimg_gs3x_for3x3(*this,x,y,z,c,I,T) { + if (Icp != Icn && Ipc != Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc; + *(ptrd2++) = Icc; + *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc; + *(ptrd3++) = Ipc==Icn?Ipc:Icc; + *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc; + *(ptrd3++) = Icn==Inc?Inc:Icc; + } else { + *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc; + *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; + *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc; + } + } + } + return res; + } + + //! Mirror image content along specified axis. + /** + \param axis Mirror axis + **/ + CImg& mirror(const char axis) { + if (is_empty()) return *this; + T *pf, *pb, *buf = 0; + switch (cimg::lowercase(axis)) { + case 'x' : { + pf = _data; pb = data(_width - 1); + const unsigned int width2 = _width/2; + for (unsigned int yzv = 0; yzv<_height*_depth*_spectrum; ++yzv) { + for (unsigned int x = 0; x get_mirror(const char axis) const { + return (+*this).mirror(axis); + } + + //! Mirror image content along specified axes. + /** + \param axes Mirror axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& mirror(const char *const axes) { + for (const char *s = axes; *s; ++s) mirror(*s); + return *this; + } + + //! Mirror image content along specified axes \newinstance. + CImg get_mirror(const char *const axes) const { + return (+*this).mirror(axes); + } + + //! Shift image content. + /** + \param delta_x Amount of displacement along the X-axis. + \param delta_y Amount of displacement along the Y-axis. + \param delta_z Amount of displacement along the Z-axis. + \param delta_c Amount of displacement along the C-axis. + \param boundary_conditions Border condition. Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) { + if (is_empty()) return *this; + if (boundary_conditions==3) + return get_crop(-delta_x,-delta_y,-delta_z,-delta_c, + width() - delta_x - 1, + height() - delta_y - 1, + depth() - delta_z - 1, + spectrum() - delta_c - 1,3).move_to(*this); + if (delta_x) // Shift along X-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_x,width()), ndelta_x = (ml<=width()/2)?ml:(ml-width()); + if (!ndelta_x) return *this; + CImg buf(cimg::abs(ndelta_x)); + if (ndelta_x>0) cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(0,y,z,c),ndelta_x*sizeof(T)); + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + std::memcpy(data(_width-ndelta_x,y,z,c),buf,ndelta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memcpy(buf,data(_width + ndelta_x,y,z,c),-ndelta_x*sizeof(T)); + std::memmove(data(-ndelta_x,y,z,c),data(0,y,z,c),(_width + ndelta_x)*sizeof(T)); + std::memcpy(data(0,y,z,c),buf,-ndelta_x*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_x<0) { + const int ndelta_x = (-delta_x>=width())?width() - 1:-delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(_width - 1,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width())?width() - 1:delta_x; + if (!ndelta_x) return *this; + cimg_forYZC(*this,y,z,c) { + std::memmove(data(ndelta_x,y,z,c),data(0,y,z,c),(_width-ndelta_x)*sizeof(T)); + T *ptrd = data(0,y,z,c); + const T val = *ptrd; + for (int l = 0; l=width()) return fill((T)0); + if (delta_x<0) cimg_forYZC(*this,y,z,c) { + std::memmove(data(0,y,z,c),data(-delta_x,y,z,c),(_width + delta_x)*sizeof(T)); + std::memset(data(_width + delta_x,y,z,c),0,-delta_x*sizeof(T)); + } else cimg_forYZC(*this,y,z,c) { + std::memmove(data(delta_x,y,z,c),data(0,y,z,c),(_width-delta_x)*sizeof(T)); + std::memset(data(0,y,z,c),0,delta_x*sizeof(T)); + } + } + + if (delta_y) // Shift along Y-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_y,height()), ndelta_y = (ml<=height()/2)?ml:(ml-height()); + if (!ndelta_y) return *this; + CImg buf(width(),cimg::abs(ndelta_y)); + if (ndelta_y>0) cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,0,z,c),_width*ndelta_y*sizeof(T)); + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + std::memcpy(data(0,_height-ndelta_y,z,c),buf,_width*ndelta_y*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memcpy(buf,data(0,_height + ndelta_y,z,c),-ndelta_y*_width*sizeof(T)); + std::memmove(data(0,-ndelta_y,z,c),data(0,0,z,c),_width*(_height + ndelta_y)*sizeof(T)); + std::memcpy(data(0,0,z,c),buf,-ndelta_y*_width*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_y<0) { + const int ndelta_y = (-delta_y>=height())?height() - 1:-delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,_height-ndelta_y,z,c), *ptrs = data(0,_height - 1,z,c); + for (int l = 0; l=height())?height() - 1:delta_y; + if (!ndelta_y) return *this; + cimg_forZC(*this,z,c) { + std::memmove(data(0,ndelta_y,z,c),data(0,0,z,c),_width*(_height-ndelta_y)*sizeof(T)); + T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c); + for (int l = 0; l=height()) return fill((T)0); + if (delta_y<0) cimg_forZC(*this,z,c) { + std::memmove(data(0,0,z,c),data(0,-delta_y,z,c),_width*(_height + delta_y)*sizeof(T)); + std::memset(data(0,_height + delta_y,z,c),0,-delta_y*_width*sizeof(T)); + } else cimg_forZC(*this,z,c) { + std::memmove(data(0,delta_y,z,c),data(0,0,z,c),_width*(_height-delta_y)*sizeof(T)); + std::memset(data(0,0,z,c),0,delta_y*_width*sizeof(T)); + } + } + + if (delta_z) // Shift along Z-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_z,depth()), ndelta_z = (ml<=depth()/2)?ml:(ml-depth()); + if (!ndelta_z) return *this; + CImg buf(width(),height(),cimg::abs(ndelta_z)); + if (ndelta_z>0) cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,0,c),_width*_height*ndelta_z*sizeof(T)); + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,_depth-ndelta_z,c),buf,_width*_height*ndelta_z*sizeof(T)); + } else cimg_forC(*this,c) { + std::memcpy(buf,data(0,0,_depth + ndelta_z,c),-ndelta_z*_width*_height*sizeof(T)); + std::memmove(data(0,0,-ndelta_z,c),data(0,0,0,c),_width*_height*(_depth + ndelta_z)*sizeof(T)); + std::memcpy(data(0,0,0,c),buf,-ndelta_z*_width*_height*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_z<0) { + const int ndelta_z = (-delta_z>=depth())?depth() - 1:-delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,_depth-ndelta_z,c), *ptrs = data(0,0,_depth - 1,c); + for (int l = 0; l=depth())?depth() - 1:delta_z; + if (!ndelta_z) return *this; + cimg_forC(*this,c) { + std::memmove(data(0,0,ndelta_z,c),data(0,0,0,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); + T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c); + for (int l = 0; l=depth()) return fill((T)0); + if (delta_z<0) cimg_forC(*this,c) { + std::memmove(data(0,0,0,c),data(0,0,-delta_z,c),_width*_height*(_depth + delta_z)*sizeof(T)); + std::memset(data(0,0,_depth + delta_z,c),0,_width*_height*(-delta_z)*sizeof(T)); + } else cimg_forC(*this,c) { + std::memmove(data(0,0,delta_z,c),data(0,0,0,c),_width*_height*(_depth-delta_z)*sizeof(T)); + std::memset(data(0,0,0,c),0,delta_z*_width*_height*sizeof(T)); + } + } + + if (delta_c) // Shift along C-axis + switch (boundary_conditions) { + case 2 : { // Periodic + const int ml = cimg::mod(-delta_c,spectrum()), ndelta_c = (ml<=spectrum()/2)?ml:(ml-spectrum()); + if (!ndelta_c) return *this; + CImg buf(width(),height(),depth(),cimg::abs(ndelta_c)); + if (ndelta_c>0) { + std::memcpy(buf,_data,_width*_height*_depth*ndelta_c*sizeof(T)); + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + std::memcpy(data(0,0,0,_spectrum-ndelta_c),buf,_width*_height*_depth*ndelta_c*sizeof(T)); + } else { + std::memcpy(buf,data(0,0,0,_spectrum + ndelta_c),-ndelta_c*_width*_height*_depth*sizeof(T)); + std::memmove(data(0,0,0,-ndelta_c),_data,_width*_height*_depth*(_spectrum + ndelta_c)*sizeof(T)); + std::memcpy(_data,buf,-ndelta_c*_width*_height*_depth*sizeof(T)); + } + } break; + case 1 : // Neumann + if (delta_c<0) { + const int ndelta_c = (-delta_c>=spectrum())?spectrum() - 1:-delta_c; + if (!ndelta_c) return *this; + std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,_spectrum-ndelta_c), *ptrs = data(0,0,0,_spectrum - 1); + for (int l = 0; l=spectrum())?spectrum() - 1:delta_c; + if (!ndelta_c) return *this; + std::memmove(data(0,0,0,ndelta_c),_data,_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); + T *ptrd = data(0,0,0,1); + for (int l = 0; l=spectrum()) return fill((T)0); + if (delta_c<0) { + std::memmove(_data,data(0,0,0,-delta_c),_width*_height*_depth*(_spectrum + delta_c)*sizeof(T)); + std::memset(data(0,0,0,_spectrum + delta_c),0,_width*_height*_depth*(-delta_c)*sizeof(T)); + } else { + std::memmove(data(0,0,0,delta_c),_data,_width*_height*_depth*(_spectrum-delta_c)*sizeof(T)); + std::memset(_data,0,delta_c*_width*_height*_depth*sizeof(T)); + } + } + return *this; + } + + //! Shift image content \newinstance. + CImg get_shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, + const unsigned int boundary_conditions=0) const { + return (+*this).shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions); + } + + //! Permute axes order. + /** + \param order Axes permutations, as a C-string of 4 characters. + This function permutes image content regarding the specified axes permutation. + **/ + CImg& permute_axes(const char *const order) { + return get_permute_axes(order).move_to(*this); + } + + //! Permute axes order \newinstance. + CImg get_permute_axes(const char *const order) const { + const T foo = (T)0; + return _permute_axes(order,foo); + } + + template + CImg _permute_axes(const char *const order, const t&) const { + if (is_empty() || !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; + } + } + if (!res) + throw CImgArgumentException(_cimg_instance + "permute_axes(): Invalid specified permutation '%s'.", + cimg_instance, + order); + return res; + } + + //! Unroll pixel values along specified axis. + /** + \param axis Unroll axis (can be \c 'x', \c 'y', \c 'z' or c 'c'). + **/ + CImg& unroll(const char axis) { + const unsigned int siz = (unsigned int)size(); + if (siz) switch (cimg::lowercase(axis)) { + case 'x' : _width = siz; _height = _depth = _spectrum = 1; break; + case 'y' : _height = siz; _width = _depth = _spectrum = 1; break; + case 'z' : _depth = siz; _width = _height = _spectrum = 1; break; + default : _spectrum = siz; _width = _height = _depth = 1; + } + return *this; + } + + //! Unroll pixel values along specified axis \newinstance. + CImg get_unroll(const char axis) const { + return (+*this).unroll(axis); + } + + //! Rotate image with arbitrary angle. + /** + \param angle Rotation angle, in degrees. + \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle \newinstance. + CImg get_rotate(const float angle, const unsigned int interpolation=1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res; + 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) { + case 1 : { // 90 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(y,hm1 - x,z,c); + } break; + case 2 : { // 180 deg + res.assign(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - x,hm1 - y,z,c); + } break; + case 3 : { // 270 deg + res.assign(_height,_width,_depth,_spectrum); + T *ptrd = res._data; + cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - y,x,z,c); + } break; + default : // 0 deg + return *this; + } + } else { // Generic angle + const float + rad = (float)(nangle*cimg::PI/180.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad), + ux = cimg::abs((_width - 1)*ca), uy = cimg::abs((_width - 1)*sa), + vx = cimg::abs((_height - 1)*sa), vy = cimg::abs((_height - 1)*ca), + w2 = 0.5f*(_width - 1), h2 = 0.5f*(_height - 1); + res.assign((int)cimg::round(1 + ux + vx),(int)cimg::round(1 + uy + vy),_depth,_spectrum); + const float rw2 = 0.5f*(res._width - 1), rh2 = 0.5f*(res._height - 1); + _rotate(res,nangle,interpolation,boundary_conditions,w2,h2,rw2,rh2); + } + return res; + } + + //! Rotate image with arbitrary angle, around a center point. + /** + \param angle Rotation angle, in degrees. + \param cx X-coordinate of the rotation center. + \param cy Y-coordinate of the rotation center. + \param interpolation Type of interpolation, { 0=nearest | 1=linear | 2=cubic | 3=mirror }. + \param boundary_conditions Boundary conditions, { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) { + return get_rotate(angle,cx,cy,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate image with arbitrary angle, around a center point \newinstance. + CImg get_rotate(const float angle, const float cx, const float cy, + const unsigned int interpolation, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + _rotate(res,angle,interpolation,boundary_conditions,cx,cy,cx,cy); + return res; + } + + // [internal] Perform 2D rotation with arbitrary angle. + void _rotate(CImg& 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.), + ca = (float)std::cos(rad), sa = (float)std::sin(rad); + + switch (boundary_conditions) { + case 3 : { // Mirror + + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions. + 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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const unsigned int interpolation, const unsigned int boundary_conditions) const { + if (is_empty()) return *this; + CImg res; + const float + w1 = _width - 1, h1 = _height - 1, d1 = _depth -1, + w2 = 0.5f*w1, h2 = 0.5f*h1, d2 = 0.5f*d1; + CImg R = CImg::rotation_matrix(u,v,w,angle); + const CImg + X = R*CImg(8,3,1,1, + 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), + zm, zM = X.get_shared_row(2).max_min(zm); + const int + dx = (int)cimg::round(xM - xm), + dy = (int)cimg::round(yM - ym), + dz = (int)cimg::round(zM - zm); + R.transpose(); + res.assign(1 + dx,1 + dy,1 + dz,_spectrum); + const float rw2 = 0.5f*dx, rh2 = 0.5f*dy, rd2 = 0.5f*dz; + _rotate(res,R,interpolation,boundary_conditions,w2,h2,d2,rw2,rh2,rd2); + return res; + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point. + /** + \param u X-coordinate of the 3D rotation axis. + \param v Y-coordinate of the 3D rotation axis. + \param w Z-coordinate of the 3D rotation axis. + \param 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 }. + \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.f); + if (nangle==0.f) return *this; + return get_rotate(u,v,w,nangle,cx,cy,cz,interpolation,boundary_conditions).move_to(*this); + } + + //! Rotate volumetric image with arbitrary angle and axis, around a center point \newinstance. + CImg get_rotate(const float u, const float v, const float w, const float angle, + const float cx, const float cy, const float cz, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty()) return *this; + CImg res(_width,_height,_depth,_spectrum); + CImg R = CImg::rotation_matrix(u,v,w,-angle); + _rotate(res,R,interpolation,boundary_conditions,cx,cy,cz,cx,cy,cz); + return res; + } + + // [internal] Perform 3D rotation with arbitrary axis and angle. + void _rotate(CImg& res, const CImg& R, + const unsigned int interpolation, const unsigned int boundary_conditions, + const float w2, const float h2, const float d2, + const float rw2, const float rh2, const float rd2) const { + switch (boundary_conditions) { + case 3 : // Mirror + switch (interpolation) { + case 2 : { // Cubic interpolation + const float ww = 2.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{ 0=nearest | 1=linear | 2=cubic }. + \param boundary_conditions Boundary conditions { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + template + CImg& warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) { + return get_warp(warp,mode,interpolation,boundary_conditions).move_to(*this); + } + + //! Warp image content by a warping field \newinstance + template + CImg get_warp(const CImg& warp, const unsigned int mode=0, + const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { + if (is_empty() || !warp) return *this; + if (mode && !is_sameXYZ(warp)) + throw CImgArgumentException(_cimg_instance + "warp(): Instance and specified relative warping field (%u,%u,%u,%u,%p) " + "have different XYZ dimensions.", + cimg_instance, + warp._width,warp._height,warp._depth,warp._spectrum,warp._data); + + CImg res(warp._width,warp._height,warp._depth,_spectrum); + + if (warp._spectrum==1) { // 1D warping + if (mode>=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + cimg_pragma_openmp(parallel for 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),x + (float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atX(*(ptrs++),(float)*(ptrs0++),y,z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)); + if (X>=0 && X=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = x + (int)cimg::round(*(ptrs0++)), Y = y + (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=1) // Linear interpolation + 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); + cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),z,c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int X = (int)cimg::round(*(ptrs0++)), Y = (int)cimg::round(*(ptrs1++)); + if (X>=0 && X=0 && Y=3) { // Forward-relative warp + res.fill((T)0); + if (interpolation>=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++), + z + (float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = x + (int)cimg::round(*(ptrs0++)), + Y = y + (int)cimg::round(*(ptrs1++)), + Z = z + (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z=1) // Linear interpolation + 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 *ptrs = data(0,y,z,c); + cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); + } + else // Nearest-neighbor interpolation + cimg_forYZC(res,y,z,c) { + const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); + const T *ptrs = data(0,y,z,c); + cimg_forX(res,x) { + const int + X = (int)cimg::round(*(ptrs0++)), + Y = (int)cimg::round(*(ptrs1++)), + Z = (int)cimg::round(*(ptrs2++)); + if (X>=0 && X=0 && Y=0 && Z get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) const { + if (is_empty() || _depth<2) return +*this; + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + const CImg + img_xy = get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1), + img_zy = get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).permute_axes("xzyc"). + resize(_depth,_height,1,-100,-1), + img_xz = get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1); + return CImg(_width + _depth,_height + _depth,1,_spectrum,cimg::min(img_xy.min(),img_zy.min(),img_xz.min())). + draw_image(0,0,img_xy).draw_image(img_xy._width,0,img_zy). + draw_image(0,img_xy._height,img_xz); + } + + //! Construct a 2D representation of a 3D image, with XY,XZ and YZ views \inplace. + CImg& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) { + if (_depth<2) return *this; + return get_projections2d(x0,y0,z0).move_to(*this); + } + + //! Crop image region. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param y0 = Y-coordinate of the upper-left crop rectangle corner. + \param z0 = Z-coordinate of the upper-left crop rectangle corner. + \param c0 = C-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param y1 = Y-coordinate of the lower-right crop rectangle corner. + \param z1 = Z-coordinate of the lower-right crop rectangle corner. + \param c1 = C-coordinate of the lower-right crop rectangle corner. + \param boundary_conditions = Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + **/ + CImg& crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) { + return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,boundary_conditions).move_to(*this); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const unsigned int boundary_conditions=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "crop(): Empty instance.", + cimg_instance); + const int + nx0 = x0 res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0); + if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum()) + switch (boundary_conditions) { + case 3 : { // Mirror + const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); + cimg_pragma_openmp(parallel for 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), + my = cimg::mod(ny0 + y,h2), + mz = cimg::mod(nz0 + z,d2), + mc = cimg::mod(nc0 + c,s2); + res(x,y,z,c) = (*this)(mx=(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 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 + res.fill((T)0).draw_image(-nx0,-ny0,-nz0,-nc0,*this); + } + else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this); + return res; + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) { + return crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int y0, + const int x1, const int y1, + const unsigned int boundary_conditions=0) const { + return get_crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \overloading. + CImg& crop(const int x0, const int x1, const unsigned int boundary_conditions=0) { + return crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Crop image region \newinstance. + CImg get_crop(const int x0, const int x1, const unsigned int boundary_conditions=0) const { + return get_crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); + } + + //! Autocrop image region, regarding the specified background value. + CImg& autocrop(const T& value, const char *const axes="czyx") { + if (is_empty()) return *this; + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + const CImg coords = _autocrop(value,axis); + if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels + else switch (axis) { + case 'x' : { + const int x0 = coords[0], x1 = coords[1]; + if (x0>=0 && x1>=0) crop(x0,x1); + } break; + case 'y' : { + const int y0 = coords[0], y1 = coords[1]; + if (y0>=0 && y1>=0) crop(0,y0,_width - 1,y1); + } break; + case 'z' : { + const int z0 = coords[0], z1 = coords[1]; + if (z0>=0 && z1>=0) crop(0,0,z0,_width - 1,_height - 1,z1); + } break; + default : { + const int c0 = coords[0], c1 = coords[1]; + if (c0>=0 && c1>=0) crop(0,0,0,c0,_width - 1,_height - 1,_depth - 1,c1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background value \newinstance. + CImg get_autocrop(const T& value, const char *const axes="czyx") const { + return (+*this).autocrop(value,axes); + } + + //! Autocrop image region, regarding the specified background color. + /** + \param color Color used for the crop. If \c 0, color is guessed. + \param axes Axes used for the crop. + **/ + CImg& autocrop(const T *const color=0, const char *const axes="zyx") { + if (is_empty()) return *this; + 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); + if (_width==w && _height==h && _depth==d && _spectrum==s) { + const CImg col2 = get_vector_at(w - 1,h - 1,d - 1); + autocrop(col2,axes); + } + return *this; + } + for (const char *s = axes; *s; ++s) { + const char axis = cimg::lowercase(*s); + switch (axis) { + case 'x' : { + int x0 = width(), x1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'x'); + const int nx0 = coords[0], nx1 = coords[1]; + if (nx0>=0 && nx1>=0) { x0 = std::min(x0,nx0); x1 = std::max(x1,nx1); } + } + if (x0==width() && x1==-1) return assign(); else crop(x0,x1); + } break; + case 'y' : { + int y0 = height(), y1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'y'); + const int ny0 = coords[0], ny1 = coords[1]; + if (ny0>=0 && ny1>=0) { y0 = std::min(y0,ny0); y1 = std::max(y1,ny1); } + } + if (y0==height() && y1==-1) return assign(); else crop(0,y0,_width - 1,y1); + } break; + default : { + int z0 = depth(), z1 = -1; + cimg_forC(*this,c) { + const CImg coords = get_shared_channel(c)._autocrop(color[c],'z'); + const int nz0 = coords[0], nz1 = coords[1]; + if (nz0>=0 && nz1>=0) { z0 = std::min(z0,nz0); z1 = std::max(z1,nz1); } + } + if (z0==depth() && z1==-1) return assign(); else crop(0,0,z0,_width - 1,_height - 1,z1); + } + } + } + return *this; + } + + //! Autocrop image region, regarding the specified background color \newinstance. + CImg get_autocrop(const T *const color=0, const char *const axes="zyx") const { + return (+*this).autocrop(color,axes); + } + + //! Autocrop image region, regarding the specified background color \overloading. + template 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)) { + case 'x' : { + int x0 = -1, x1 = -1; + cimg_forX(*this,x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); } + if (x0>=0) { + for (int x = width() - 1; x>=0; --x) cimg_forYZC(*this,y,z,c) + if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); } + } + res = CImg::vector(x0,x1); + } break; + case 'y' : { + int y0 = -1, y1 = -1; + cimg_forY(*this,y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); } + if (y0>=0) { + for (int y = height() - 1; y>=0; --y) cimg_forXZC(*this,x,z,c) + if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); } + } + res = CImg::vector(y0,y1); + } break; + case 'z' : { + int z0 = -1, z1 = -1; + cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); } + if (z0>=0) { + for (int z = depth() - 1; z>=0; --z) cimg_forXYC(*this,x,y,c) + if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); } + } + res = CImg::vector(z0,z1); + } break; + default : { + int c0 = -1, c1 = -1; + cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); } + if (c0>=0) { + for (int c = spectrum() - 1; c>=0; --c) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; } + } + res = CImg::vector(c0,c1); + } + } + return res; + } + + //! Return specified image column. + /** + \param x0 Image column. + **/ + CImg get_column(const int x0) const { + return get_columns(x0,x0); + } + + //! Return specified image column \inplace. + CImg& column(const int x0) { + return columns(x0,x0); + } + + //! Return specified range of image columns. + /** + \param x0 Starting image column. + \param x1 Ending image column. + **/ + CImg& columns(const int x0, const int x1) { + return get_columns(x0,x1).move_to(*this); + } + + //! Return specified range of image columns \inplace. + CImg get_columns(const int x0, const int x1) const { + return get_crop(x0,0,0,0,x1,height() - 1,depth() - 1,spectrum() - 1); + } + + //! Return specified image row. + CImg get_row(const int y0) const { + return get_rows(y0,y0); + } + + //! Return specified image row \inplace. + /** + \param y0 Image row. + **/ + CImg& row(const int y0) { + return rows(y0,y0); + } + + //! Return specified range of image rows. + /** + \param y0 Starting image row. + \param y1 Ending image row. + **/ + CImg get_rows(const int y0, const int y1) const { + return get_crop(0,y0,0,0,width() - 1,y1,depth() - 1,spectrum() - 1); + } + + //! Return specified range of image rows \inplace. + CImg& rows(const int y0, const int y1) { + return get_rows(y0,y1).move_to(*this); + } + + //! Return specified image slice. + /** + \param z0 Image slice. + **/ + CImg get_slice(const int z0) const { + return get_slices(z0,z0); + } + + //! Return specified image slice \inplace. + CImg& slice(const int z0) { + return slices(z0,z0); + } + + //! Return specified range of image slices. + /** + \param z0 Starting image slice. + \param z1 Ending image slice. + **/ + CImg get_slices(const int z0, const int z1) const { + return get_crop(0,0,z0,0,width() - 1,height() - 1,z1,spectrum() - 1); + } + + //! Return specified range of image slices \inplace. + CImg& slices(const int z0, const int z1) { + return get_slices(z0,z1).move_to(*this); + } + + //! Return specified image channel. + /** + \param c0 Image channel. + **/ + CImg get_channel(const int c0) const { + return get_channels(c0,c0); + } + + //! Return specified image channel \inplace. + CImg& channel(const int c0) { + return channels(c0,c0); + } + + //! Return specified range of image channels. + /** + \param c0 Starting image channel. + \param c1 Ending image channel. + **/ + CImg get_channels(const int c0, const int c1) const { + return get_crop(0,0,0,c0,width() - 1,height() - 1,depth() - 1,c1); + } + + //! Return specified range of image channels \inplace. + CImg& channels(const int c0, const int c1) { + return get_channels(c0,c1).move_to(*this); + } + + //! Return stream line of a 2D or 3D vector field. + CImg get_streamline(const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false) const { + if (_spectrum!=2 && _spectrum!=3) + throw CImgInstanceException(_cimg_instance + "streamline(): Instance is not a 2D or 3D vector field.", + cimg_instance); + if (_spectrum==2) { + if (is_oriented_only) { + typename CImg::_functor4d_streamline2d_oriented func(*this); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true, + 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.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.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.f,_height - 1.f,_depth - 1.f); + } + + //! Return stream line of a 3D vector field. + /** + \param func Vector field function. + \param x X-coordinate of the starting point of the streamline. + \param y Y-coordinate of the starting point of the streamline. + \param z Z-coordinate of the starting point of the streamline. + \param L Streamline length. + \param dl Streamline length increment. + \param interpolation_type Type of interpolation. + Can be { 0=nearest int | 1=linear | 2=2nd-order RK | 3=4th-order RK. }. + \param is_backward_tracking Tells if the streamline is estimated forward or backward. + \param is_oriented_only Tells if the direction of the vectors must be ignored. + \param x0 X-coordinate of the first bounding-box vertex. + \param y0 Y-coordinate of the first bounding-box vertex. + \param z0 Z-coordinate of the first bounding-box vertex. + \param x1 X-coordinate of the second bounding-box vertex. + \param y1 Y-coordinate of the second bounding-box vertex. + \param z1 Z-coordinate of the second bounding-box vertex. + **/ + template + static CImg streamline(const tfunc& func, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=false, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + if (dl<=0) + throw CImgArgumentException("CImg<%s>::streamline(): Invalid specified integration length %g " + "(should be >0).", + pixel_type(), + dl); + + const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1); + if (L<=0 || (is_bounded && (xx1 || yy1 || zz1))) return CImg(); + const unsigned int size_L = (unsigned int)cimg::round(L/dl + 1); + CImg coordinates(size_L,3); + const float dl2 = dl/2; + float + *ptr_x = coordinates.data(0,0), + *ptr_y = coordinates.data(0,1), + *ptr_z = coordinates.data(0,2), + pu = (float)(dl*func(x,y,z,0)), + pv = (float)(dl*func(x,y,z,1)), + pw = (float)(dl*func(x,y,z,2)), + X = x, Y = y, Z = z; + + switch (interpolation_type) { + case 0 : { // Nearest integer interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + const int + xi = (int)(X>0?X + 0.5f:X - 0.5f), + yi = (int)(Y>0?Y + 0.5f:Y - 0.5f), + zi = (int)(Z>0?Z + 0.5f:Z - 0.5f); + float + u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)), + v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)), + w = (float)(dl*func((float)xi,(float)yi,(float)zi,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 1 : { // First-order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u = (float)(dl*func(X,Y,Z,0)), + v = (float)(dl*func(X,Y,Z,1)), + w = (float)(dl*func(X,Y,Z,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + case 2 : { // Second order interpolation + cimg_forX(coordinates,l) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u = (float)(dl*func(X + u0,Y + v0,Z + w0,0)), + v = (float)(dl*func(X + u0,Y + v0,Z + w0,1)), + w = (float)(dl*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } break; + default : { // Fourth order interpolation + cimg_forX(coordinates,x) { + *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; + float + u0 = (float)(dl2*func(X,Y,Z,0)), + v0 = (float)(dl2*func(X,Y,Z,1)), + w0 = (float)(dl2*func(X,Y,Z,2)); + if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } + float + u1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,0)), + v1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,1)), + w1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,2)); + if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; } + float + u2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,0)), + v2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,1)), + w2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; } + float + u3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,0)), + v3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,1)), + w3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,2)); + if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; } + const float + u = (u0 + u3)/3 + (u1 + u2)/1.5f, + v = (v0 + v3)/3 + (v1 + v2)/1.5f, + w = (w0 + w3)/3 + (w1 + w2)/1.5f; + if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } + if (is_bounded && (Xx1 || Yy1 || Zz1)) break; + } + } + } + if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0); + return coordinates; + } + + //! Return stream line of a 3D vector field \overloading. + static CImg streamline(const char *const expression, + const float x, const float y, const float z, + const float L=256, const float dl=0.1f, + const unsigned int interpolation_type=2, const bool is_backward_tracking=true, + const bool is_oriented_only=false, + const float x0=0, const float y0=0, const float z0=0, + const float x1=0, const float y1=0, const float z1=0) { + _functor4d_streamline_expr func(expression); + return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1); + } + + struct _functor4d_streamline2d_directed { + const CImg& ref; + _functor4d_streamline2d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0; + } + }; + + struct _functor4d_streamline3d_directed { + const CImg& ref; + _functor4d_streamline3d_directed(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref._linear_atXYZ(x,y,z,c); + } + }; + + struct _functor4d_streamline2d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline2d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,1,2); } + ~_functor4d_streamline2d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign2d(i,j) \ + if (I(i,j,0)*I(0,0,0) + I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z; + const float + dx = x - xi, + dy = y - yi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1); + I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1); + I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1); + I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1); + _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1); + } + return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0; + } + }; + + struct _functor4d_streamline3d_oriented { + const CImg& ref; + CImg *pI; + _functor4d_streamline3d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,2,3); } + ~_functor4d_streamline3d_oriented() { delete pI; } + float operator()(const float x, const float y, const float z, const unsigned int c) const { +#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0) + I(i,j,k,1)*I(0,0,0,1) + I(i,j,k,2)*I(0,0,0,2)<0) { \ + I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); } + int + xi = (int)x - (x>=0?0:1), nxi = xi + 1, + yi = (int)y - (y>=0?0:1), nyi = yi + 1, + zi = (int)z - (z>=0?0:1), nzi = zi + 1; + const float + dx = x - xi, + dy = y - yi, + dz = z - zi; + if (c==0) { + CImg& I = *pI; + if (xi<0) xi = 0; + if (nxi<0) nxi = 0; + if (xi>=ref.width()) xi = ref.width() - 1; + if (nxi>=ref.width()) nxi = ref.width() - 1; + if (yi<0) yi = 0; + if (nyi<0) nyi = 0; + if (yi>=ref.height()) yi = ref.height() - 1; + if (nyi>=ref.height()) nyi = ref.height() - 1; + if (zi<0) zi = 0; + if (nzi<0) nzi = 0; + if (zi>=ref.depth()) zi = ref.depth() - 1; + if (nzi>=ref.depth()) nzi = ref.depth() - 1; + I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1); + I(0,0,0,2) = (float)ref(xi,yi,zi,2); I(1,0,0,0) = (float)ref(nxi,yi,zi,0); + I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2); + I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1); + I(1,1,0,2) = (float)ref(nxi,nyi,zi,2); I(0,1,0,0) = (float)ref(xi,nyi,zi,0); + I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,nyi,zi,2); + I(0,0,1,0) = (float)ref(xi,yi,nzi,0); I(0,0,1,1) = (float)ref(xi,yi,nzi,1); + I(0,0,1,2) = (float)ref(xi,yi,nzi,2); I(1,0,1,0) = (float)ref(nxi,yi,nzi,0); + I(1,0,1,1) = (float)ref(nxi,yi,nzi,1); I(1,0,1,2) = (float)ref(nxi,yi,nzi,2); + I(1,1,1,0) = (float)ref(nxi,nyi,nzi,0); I(1,1,1,1) = (float)ref(nxi,nyi,nzi,1); + I(1,1,1,2) = (float)ref(nxi,nyi,nzi,2); I(0,1,1,0) = (float)ref(xi,nyi,nzi,0); + I(0,1,1,1) = (float)ref(xi,nyi,nzi,1); I(0,1,1,2) = (float)ref(xi,nyi,nzi,2); + _cimg_vecalign3d(1,0,0); _cimg_vecalign3d(1,1,0); _cimg_vecalign3d(0,1,0); + _cimg_vecalign3d(0,0,1); _cimg_vecalign3d(1,0,1); _cimg_vecalign3d(1,1,1); _cimg_vecalign3d(0,1,1); + } + return (float)pI->_linear_atXYZ(dx,dy,dz,c); + } + }; + + struct _functor4d_streamline_expr { + _cimg_math_parser *mp; + ~_functor4d_streamline_expr() { mp->end(); delete mp; } + _functor4d_streamline_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,"streamline",CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)(*mp)(x,y,z,c); + } + }; + + //! Return a shared-memory image referencing a range of pixels of the image instance. + /** + \param x0 X-coordinate of the starting pixel. + \param x1 X-coordinate of the ending pixel. + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_points(const unsigned int x0, const unsigned int x1, + const unsigned int y0=0, const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(x0,y0,z0,c0), + end = (unsigned int)offset(x1,y0,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_points(): Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).", + cimg_instance, + x0,x1,y0,z0,c0); + + return CImg(_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); + 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 rows of the image instance. + /** + \param y0 Y-coordinate of the starting row. + \param y1 Y-coordinate of the ending row. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_rows(const unsigned int y0, const unsigned int y1, + const unsigned int z0=0, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,y0,z0,c0), + end = (unsigned int)offset(0,y1,z0,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_rows(): Invalid request of a shared-memory subset " + "(0->%u,%u->%u,%u,%u).", + cimg_instance, + _width - 1,y0,y1,z0,c0); + + return CImg(_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); + 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 one row of the image instance. + /** + \param y0 Y-coordinate. + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared-memory image referencing one row of the image instance \const. + const CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const { + return get_shared_rows(y0,y0,z0,c0); + } + + //! Return a shared memory image referencing a range of slices of the image instance. + /** + \param z0 Z-coordinate of the starting slice. + \param z1 Z-coordinate of the ending slice. + \param c0 C-coordinate. + **/ + CImg get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) { + const unsigned int + beg = (unsigned int)offset(0,0,z0,c0), + end = (unsigned int)offset(0,0,z1,c0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_slices(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,%u->%u,%u).", + cimg_instance, + _width - 1,_height - 1,z0,z1,c0); + + return CImg(_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); + 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 one slice of the image instance. + /** + \param z0 Z-coordinate. + \param c0 C-coordinate. + **/ + CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing one slice of the image instance \const. + const CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) const { + return get_shared_slices(z0,z0,c0); + } + + //! Return a shared-memory image referencing a range of channels of the image instance. + /** + \param c0 C-coordinate of the starting channel. + \param c1 C-coordinate of the ending channel. + **/ + CImg get_shared_channels(const unsigned int c0, const unsigned int c1) { + const unsigned int + beg = (unsigned int)offset(0,0,0,c0), + end = (unsigned int)offset(0,0,0,c1); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException(_cimg_instance + "get_shared_channels(): Invalid request of a shared-memory subset " + "(0->%u,0->%u,0->%u,%u->%u).", + cimg_instance, + _width - 1,_height - 1,_depth - 1,c0,c1); + + return CImg(_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); + 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 one channel of the image instance. + /** + \param c0 C-coordinate. + **/ + CImg get_shared_channel(const unsigned int c0) { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory image referencing one channel of the image instance \const. + const CImg get_shared_channel(const unsigned int c0) const { + return get_shared_channels(c0,c0); + } + + //! Return a shared-memory version of the image instance. + CImg get_shared() { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Return a shared-memory version of the image instance \const. + const CImg get_shared() const { + return CImg(_data,_width,_height,_depth,_spectrum,true); + } + + //! Split image into a list along specified axis. + /** + \param axis Splitting axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param nb Number of splitted parts. + \note + - If \c nb==0, instance image is splitted into blocs of egal values along the specified axis. + - If \c nb<=0, instance image is splitted into blocs of -\c nb pixel wide. + - If \c nb>0, instance image is splitted into \c nb blocs. + **/ + CImgList 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 + 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>=(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); + } break; + case 'y': { + if (_height>dp) { + res.assign(_height/dp + (_height%dp?1:0),1,1); + const unsigned int pe = _height - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'z': { + if (_depth>dp) { + res.assign(_depth/dp + (_depth%dp?1:0),1,1); + const unsigned int pe = _depth - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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); + } break; + case 'c' : { + if (_spectrum>dp) { + res.assign(_spectrum/dp + (_spectrum%dp?1:0),1,1); + const unsigned int pe = _spectrum - dp; + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(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 + const unsigned int siz = _axis=='x'?_width:_axis=='y'?_height:_axis=='z'?_depth:_axis=='c'?_spectrum:0; + if ((unsigned int)nb>siz) + throw CImgArgumentException(_cimg_instance + "get_split(): Instance cannot be split along %c-axis into %u blocs.", + cimg_instance, + axis,nb); + if (nb==1) res.assign(*this); + else { + int err = (int)siz; + unsigned int _p = 0; + switch (_axis) { + case 'x' : { + cimg_forX(*this,p) if ((err-=nb)<=0) { + get_crop(_p,0,0,0,p,_height - 1,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'y' : { + cimg_forY(*this,p) if ((err-=nb)<=0) { + get_crop(0,_p,0,0,_width - 1,p,_depth - 1,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'z' : { + cimg_forZ(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,_p,0,_width - 1,_height - 1,p,_spectrum - 1).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } break; + case 'c' : { + cimg_forC(*this,p) if ((err-=nb)<=0) { + get_crop(0,0,0,_p,_width - 1,_height - 1,_depth - 1,p).move_to(res); + err+=(int)siz; + _p = p + 1U; + } + } + } + } + } else { // Split by egal values according to specified axis + T current = *_data; + switch (_axis) { + case 'x' : { + int i0 = 0; + cimg_forX(*this,i) + if ((*this)(i)!=current) { get_columns(i0,i - 1).move_to(res); i0 = i; current = (*this)(i); } + get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + int i0 = 0; + cimg_forY(*this,i) + if ((*this)(0,i)!=current) { get_rows(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,i); } + get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + int i0 = 0; + cimg_forZ(*this,i) + if ((*this)(0,0,i)!=current) { get_slices(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,i); } + get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + int i0 = 0; + cimg_forC(*this,i) + if ((*this)(0,0,0,i)!=current) { get_channels(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,0,i); } + get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + longT i0 = 0; + cimg_foroff(*this,i) + if ((*this)[i]!=current) { + CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); + i0 = (longT)i; current = (*this)[i]; + } + CImg(_data + i0,1,(unsigned int)(size() - i0)).move_to(res); + } + } + } + return res; + } + + //! Split image into a list of sub-images, according to a specified splitting value sequence and optionally axis. + /** + \param values Splitting value sequence. + \param axis Axis along which the splitting is performed. Can be '0' to ignore axis. + \param keep_values Tells if the splitting sequence must be kept in the splitted blocs. + **/ + template + CImgList get_split(const CImg& values, const char axis=0, const bool keep_values=true) const { + 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 + const T value = (T)*values; + switch (_axis) { + case 'x' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_width && (*this)(i)==value) ++i; + if (i>i0) { if (keep_values) get_columns(i0,i - 1).move_to(res); i0 = i; } + while (i<_width && (*this)(i)!=value) ++i; + if (i>i0) { get_columns(i0,i - 1).move_to(res); i0 = i; } + } while (i<_width); + } break; + case 'y' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_height && (*this)(0,i)==value) ++i; + if (i>i0) { if (keep_values) get_rows(i0,i - 1).move_to(res); i0 = i; } + while (i<_height && (*this)(0,i)!=value) ++i; + if (i>i0) { get_rows(i0,i - 1).move_to(res); i0 = i; } + } while (i<_height); + } break; + case 'z' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_depth && (*this)(0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_slices(i0,i - 1).move_to(res); i0 = i; } + while (i<_depth && (*this)(0,0,i)!=value) ++i; + if (i>i0) { get_slices(i0,i - 1).move_to(res); i0 = i; } + } while (i<_depth); + } break; + case 'c' : { + unsigned int i0 = 0, i = 0; + do { + while (i<_spectrum && (*this)(0,0,0,i)==value) ++i; + if (i>i0) { if (keep_values) get_channels(i0,i - 1).move_to(res); i0 = i; } + while (i<_spectrum && (*this)(0,0,0,i)!=value) ++i; + if (i>i0) { get_channels(i0,i - 1).move_to(res); i0 = i; } + } while (i<_spectrum); + } break; + default : { + const ulongT siz = size(); + ulongT i0 = 0, i = 0; + do { + while (ii0) { 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; + if (i>i1) { + if (i1>i0) get_columns(i0,i1 - 1).move_to(res); + if (keep_values) get_columns(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_width); + if (i0<_width) get_columns(i0,width() - 1).move_to(res); + } break; + case 'y' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,i)==*values) { + i1 = i; j = 0; + while (i<_height && (*this)(0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_rows(i0,i1 - 1).move_to(res); + if (keep_values) get_rows(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_height); + if (i0<_height) get_rows(i0,height() - 1).move_to(res); + } break; + case 'z' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,i)==*values) { + i1 = i; j = 0; + while (i<_depth && (*this)(0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_slices(i0,i1 - 1).move_to(res); + if (keep_values) get_slices(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_depth); + if (i0<_depth) get_slices(i0,depth() - 1).move_to(res); + } break; + case 'c' : { + unsigned int i0 = 0, i1 = 0, i = 0; + do { + if ((*this)(0,0,0,i)==*values) { + i1 = i; j = 0; + while (i<_spectrum && (*this)(0,0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=j; + if (i>i1) { + if (i1>i0) get_channels(i0,i1 - 1).move_to(res); + if (keep_values) get_channels(i1,i - 1).move_to(res); + i0 = i; + } else ++i; + } else ++i; + } while (i<_spectrum); + if (i0<_spectrum) get_channels(i0,spectrum() - 1).move_to(res); + } break; + default : { + ulongT i0 = 0, i1 = 0, i = 0; + const ulongT siz = size(); + do { + if ((*this)[i]==*values) { + i1 = i; j = 0; + while (i=vsiz) j = 0; } + i-=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); + i0 = i; + } else ++i; + } else ++i; + } while (i(_data + i0,1,(unsigned int)(siz - i0)).move_to(res); + } break; + } + } + return res; + } + + //! Append two images along specified axis. + /** + \param img Image to append with instance image. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Append alignment in \c [0,1]. + **/ + template + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,true).insert(img).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \specialization. + CImg& append(const CImg& img, const char axis='x', const float align=0) { + if (is_empty()) return assign(img,false); + if (!img) return *this; + return CImgList(*this,img,true).get_append(axis,align).move_to(*this); + } + + //! Append two images along specified axis \const. + template + CImg<_cimg_Tt> get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList<_cimg_Tt>(*this,true).insert(img).get_append(axis,align); + } + + //! Append two images along specified axis \specialization. + CImg get_append(const CImg& img, const char axis='x', const float align=0) const { + if (is_empty()) return +img; + if (!img) return +*this; + return CImgList(*this,img,true).get_append(axis,align); + } + + //@} + //--------------------------------------- + // + //! \name Filtering / Transforms + //@{ + //--------------------------------------- + + //! Correlate image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The correlation of the image instance \p *this by the kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} (*this)(x + i,y + j,z + k)*kernel(i,j,k). + **/ + template + CImg& correlate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_correlate(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + template + 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); + } + + //! 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; + 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>=(cimg_openmp_sizefactor)*32768, + is_outer_parallel = res_size>=(cimg_openmp_sizefactor)*32768; + _cimg_abort_init_omp; + cimg_abort_init; + + if (kernel._width==kernel._height && + ((kernel._depth==1 && kernel._width<=6) || (kernel._depth==kernel._width && kernel._width<=3))) { + + // 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); + + } 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(); + + 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; + } + } + } + } + + 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 cimg_openmp_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); + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0, N = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const Ttfloat _val = (Ttfloat)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); + val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm); + N+=_val*_val; + } + N*=M; + res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0); + } + } _cimg_abort_catch_omp2 + } else { // Classical correlation + cimg_pragma_openmp(parallel for cimg_openmp_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; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img._atXYZ(x + xm,y + ym,z + zm)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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))) { + Ttfloat val = 0; + for (int zm = -mz1; zm<=mz2; ++zm) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + val+=img.atXYZ(x + xm,y + ym,z + zm,0,(T)0)*K(mx1 + xm,my1 + ym,mz1 + zm); + res(x,y,z,c) = (Ttfloat)val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + } + cimg_abort_test; + return res; + } + + //! Convolve image by a kernel. + /** + \param kernel = the correlation kernel. + \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param is_normalized = enable local normalization. + \note + - The result \p res of the convolution of an image \p img by a kernel \p kernel is defined to be: + res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*kernel(i,j,k) + **/ + template + CImg& convolve(const CImg& kernel, const bool boundary_conditions=true, const bool is_normalized=false) { + if (is_empty() || !kernel) return *this; + return get_convolve(kernel,boundary_conditions,is_normalized).move_to(*this); + } + + //! Convolve image by a kernel \newinstance. + template + 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); + } + + //! Cumulate image values, optionally along specified axis. + /** + \param axis Cumulation axis. Set it to 0 to cumulate all values globally without taking axes into account. + **/ + CImg& cumulate(const char axis=0) { + switch (cimg::lowercase(axis)) { + case 'x' : + 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; + cimg_forX(*this,x) { cumul+=(Tlong)*ptrd; *(ptrd++) = (T)cumul; } + } + break; + case 'y' : { + const ulongT w = (ulongT)_width; + 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; + cimg_forY(*this,y) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=w; } + } + } break; + case 'z' : { + const ulongT wh = (ulongT)_width*_height; + cimg_pragma_openmp(parallel for 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; + cimg_forZ(*this,z) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=wh; } + } + } break; + case 'c' : { + const ulongT whd = (ulongT)_width*_height*_depth; + cimg_pragma_openmp(parallel for 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 + Tlong cumul = (Tlong)0; + cimg_for(*this,ptrd,T) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; } + } + } + return *this; + } + + //! Cumulate image values, optionally along specified axis \newinstance. + CImg get_cumulate(const char axis=0) const { + return CImg(*this,false).cumulate(axis); + } + + //! Cumulate image values, along specified axes. + /** + \param axes Cumulation axes, as a C-string. + \note \c axes may contains multiple characters, e.g. \c "xyz" + **/ + CImg& cumulate(const char *const axes) { + for (const char *s = axes; *s; ++s) cumulate(*s); + return *this; + } + + //! Cumulate image values, along specified axes \newinstance. + CImg get_cumulate(const char *const axes) const { + return CImg(*this,false).cumulate(axes); + } + + //! Erode image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the erosion in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_erode(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Erode image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_erode(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel) return *this; + if (!is_real && kernel==0) return CImg(width(),height(),depth(),spectrum(),0); + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2, + mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real erosion + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx1 + xm,my1 + ym,mz1 + zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) - mval); + if (cval=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); + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx1 + xm,my1 + ym,mz1 + zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval=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)) { + 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 + 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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _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 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val get_erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).erode(sx,sy,sz); + } + + //! Erode the image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& erode(const unsigned int s) { + return erode(s,s,s); + } + + //! Erode the image by a square structuring element of specified size \newinstance. + CImg get_erode(const unsigned int s) const { + return (+*this).erode(s); + } + + //! Dilate image by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + \param is_real Do the dilation in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) { + if (is_empty() || !kernel) return *this; + return get_dilate(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Dilate image by a structuring element \newinstance. + template + CImg<_cimg_Tt> get_dilate(const CImg& kernel, const bool boundary_conditions=true, + const bool is_real=false) const { + if (is_empty() || !kernel || (!is_real && kernel==0)) return *this; + typedef _cimg_Tt Tt; + CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); + const int + mx1 = kernel.width()/2, my1 = kernel.height()/2, mz1 = kernel.depth()/2, + mx2 = kernel.width() - mx1 - 1, my2 = kernel.height() - my1 - 1, mz2 = kernel.depth() - mz1 - 1, + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; + const bool + 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; + 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_real) { // Real dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = K(mx2 - xm,my2 - ym,mz2 - zm); + const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forYZ(*this,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,0,(T)0) + mval); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } else { // Binary dilation + 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) + for (int ym = -my1; ym<=my2; ++ym) + for (int xm = -mx1; xm<=mx2; ++xm) + if (K(mx2 - xm,my2 - ym,mz2 - zm)) { + const Tt cval = (Tt)img(x + xm,y + ym,z + zm); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } _cimg_abort_catch_omp2 + if (boundary_conditions) + cimg_pragma_openmp(parallel for cimg_openmp_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; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + else + cimg_pragma_openmp(parallel for cimg_openmp_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,0,(T)0); + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_omp2 + } + } _cimg_abort_catch_omp + cimg_abort_test; + return res; + } + + //! Dilate image by a rectangular structuring element of specified size. + /** + \param sx Width of the structuring element. + \param sy Height of the structuring element. + \param sz Depth of the structuring element. + **/ + CImg& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { + if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this; + if (sx>1 && _width>1) { // Along X-axis + const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(0,y,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(0,y,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sy>1 && _height>1) { // Along Y-axis + const int L = height(), off = width(), s = (int)sy, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,0,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,0,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + + if (sz>1 && _depth>1) { // Along Z-axis + const int L = depth(), off = width()*height(), s = (int)sz, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, + s2 = _s2>L?L:_s2; + CImg buf(L); + 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; + T cur = *ptrs; ptrs+=off; bool is_first = true; + for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { + const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } + } + *(ptrd++) = cur; + if (ptrs>=ptrse) { + T *pd = data(x,y,0,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } + } else { + for (int p = s1; p>0 && ptrd<=ptrde; --p) { + const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } + *(ptrd++) = cur; + } + for (int p = L - s - 1; p>0; --p) { + const T val = *ptrs; ptrs+=off; + if (is_first) { + const T *nptrs = ptrs - off; cur = val; + for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } + nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; + } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } + *(ptrd++) = cur; + } + ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; + for (int p = s1; p>0 && ptrs>=ptrsb; --p) { + const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; + } + *(ptrd--) = cur; + for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { + const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; + } + T *pd = data(x,y,0,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } + } + } + } + return *this; + } + + //! Dilate image by a rectangular structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + return (+*this).dilate(sx,sy,sz); + } + + //! Dilate image by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& dilate(const unsigned int s) { + return dilate(s,s,s); + } + + //! Dilate image by a square structuring element of specified size \newinstance. + CImg get_dilate(const unsigned int s) const { + return (+*this).dilate(s); + } + + //! Compute watershed transform. + /** + \param priority Priority map. + \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity + in 2D case, and between 6(false)- or 26(true)-connectivity in 3D case. + \note Non-zero values of the instance instance are propagated to zero-valued ones according to + specified the priority map. + **/ + template + CImg& watershed(const CImg& priority, const bool is_high_connectivity=false) { +#define _cimg_watershed_init(cond,X,Y,Z) \ + if (cond && !(*this)(X,Y,Z)) Q._priority_queue_insert(labels,sizeQ,priority(X,Y,Z),X,Y,Z,nb_seeds) + +#define _cimg_watershed_propagate(cond,X,Y,Z) \ + if (cond) { \ + if ((*this)(X,Y,Z)) { \ + ns = labels(X,Y,Z) - 1; xs = seeds(ns,0); ys = seeds(ns,1); zs = seeds(ns,2); \ + d = cimg::sqr((float)x - xs) + cimg::sqr((float)y - ys) + cimg::sqr((float)z - zs); \ + if (d labels(_width,_height,_depth,1,0), seeds(64,3); + CImg::type> Q; + unsigned int sizeQ = 0; + int px, nx, py, ny, pz, nz; + bool is_px, is_nx, is_py, is_ny, is_pz, is_nz; + const bool is_3d = _depth>1; + + // Find seed points and insert them in priority queue. + unsigned int nb_seeds = 0; + const T *ptrs = _data; + cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) { // 3D version + if (nb_seeds>=seeds._width) seeds.resize(2*seeds._width,3,1,1,0); + seeds(nb_seeds,0) = x; seeds(nb_seeds,1) = y; seeds(nb_seeds++,2) = z; + px = x - 1; nx = x + 1; + py = y - 1; ny = y + 1; + pz = z - 1; nz = z + 1; + is_px = px>=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz::inf(); + T label = (T)0; + _cimg_watershed_propagate(is_px,px,y,z); + _cimg_watershed_propagate(is_nx,nx,y,z); + _cimg_watershed_propagate(is_py,x,py,z); + _cimg_watershed_propagate(is_ny,x,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_pz,x,y,pz); + _cimg_watershed_propagate(is_nz,x,y,nz); + } + if (is_high_connectivity) { + _cimg_watershed_propagate(is_px && is_py,px,py,z); + _cimg_watershed_propagate(is_nx && is_py,nx,py,z); + _cimg_watershed_propagate(is_px && is_ny,px,ny,z); + _cimg_watershed_propagate(is_nx && is_ny,nx,ny,z); + if (is_3d) { + _cimg_watershed_propagate(is_px && is_pz,px,y,pz); + _cimg_watershed_propagate(is_nx && is_pz,nx,y,pz); + _cimg_watershed_propagate(is_px && is_nz,px,y,nz); + _cimg_watershed_propagate(is_nx && is_nz,nx,y,nz); + _cimg_watershed_propagate(is_py && is_pz,x,py,pz); + _cimg_watershed_propagate(is_ny && is_pz,x,ny,pz); + _cimg_watershed_propagate(is_py && is_nz,x,py,nz); + _cimg_watershed_propagate(is_ny && is_nz,x,ny,nz); + _cimg_watershed_propagate(is_px && is_py && is_pz,px,py,pz); + _cimg_watershed_propagate(is_nx && is_py && is_pz,nx,py,pz); + _cimg_watershed_propagate(is_px && is_ny && is_pz,px,ny,pz); + _cimg_watershed_propagate(is_nx && is_ny && is_pz,nx,ny,pz); + _cimg_watershed_propagate(is_px && is_py && is_nz,px,py,nz); + _cimg_watershed_propagate(is_nx && is_py && is_nz,nx,py,nz); + _cimg_watershed_propagate(is_px && is_ny && is_nz,px,ny,nz); + _cimg_watershed_propagate(is_nx && is_ny && is_nz,nx,ny,nz); + } + } + (*this)(x,y,z) = label; + labels(x,y,z) = ++nmin; + } + return *this; + } + + //! Compute watershed transform \newinstance. + template + CImg get_watershed(const CImg& priority, const bool is_high_connectivity=false) const { + return (+*this).watershed(priority,is_high_connectivity); + } + + // [internal] Insert/Remove items in priority queue, for watershed/distance transforms. + template + bool _priority_queue_insert(CImg& is_queued, unsigned int& siz, const tv value, + const unsigned int x, const unsigned int y, const unsigned int z, + const unsigned int n=1) { + if (is_queued(x,y,z)) return false; + is_queued(x,y,z) = (tq)n; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; + (*this)(siz - 1,1) = (T)x; + (*this)(siz - 1,2) = (T)y; + (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); + cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); + cimg::swap((*this)(pos,3),(*this)(par,3)); + } + return true; + } + + CImg& _priority_queue_remove(unsigned int& siz) { + (*this)(0,0) = (*this)(--siz,0); + (*this)(0,1) = (*this)(siz,1); + (*this)(0,2) = (*this)(siz,2); + (*this)(0,3) = (*this)(siz,3); + const float value = (*this)(0,0); + for (unsigned int pos = 0, left = 0, right = 0; + ((right=2*(pos + 1),(left=right - 1))(*this)(right,0)) { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } else { + cimg::swap((*this)(pos,0),(*this)(right,0)); + cimg::swap((*this)(pos,1),(*this)(right,1)); + cimg::swap((*this)(pos,2),(*this)(right,2)); + cimg::swap((*this)(pos,3),(*this)(right,3)); + pos = right; + } + } else { + cimg::swap((*this)(pos,0),(*this)(left,0)); + cimg::swap((*this)(pos,1),(*this)(left,1)); + cimg::swap((*this)(pos,2),(*this)(left,2)); + cimg::swap((*this)(pos,3),(*this)(left,3)); + pos = left; + } + } + return *this; + } + + //! Apply recursive Deriche filter. + /** + \param sigma Standard deviation of the filter. + \param order Order of the filter. Can be { 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 }. + **/ + CImg& deriche(const float sigma, const unsigned int order=0, const char axis='x', + const bool boundary_conditions=true) { +#define _cimg_deriche_apply \ + CImg Y(N); \ + Tfloat *ptrY = Y._data, yb = 0, yp = 0; \ + T xp = (T)0; \ + if (boundary_conditions) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \ + for (int m = 0; m=0; --n) { \ + const T xc = *(ptrX-=off); \ + const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \ + xa = xn; xn = xc; ya = yn; yn = yc; \ + *ptrX = (T)(*(--ptrY)+yc); \ + } + const char naxis = cimg::lowercase(axis); + const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + if (is_empty() || (nsigma<0.1f && !order)) return *this; + const float + nnsigma = nsigma<0.1f?0.1f:nsigma, + alpha = 1.695f/nnsigma, + ema = (float)std::exp(-alpha), + ema2 = (float)std::exp(-2*alpha), + b1 = -2*ema, + b2 = ema2; + float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0; + switch (order) { + case 0 : { + const float k = (1-ema)*(1-ema)/(1 + 2*alpha*ema-ema2); + a0 = k; + a1 = k*(alpha - 1)*ema; + a2 = k*(alpha + 1)*ema; + a3 = -k*ema2; + } break; + case 1 : { + const float k = -(1-ema)*(1-ema)*(1-ema)/(2*(ema + 1)*ema); + a0 = a3 = 0; + a1 = k*ema; + a2 = -a1; + } break; + case 2 : { + const float + ea = (float)std::exp(-alpha), + k = -(ema2 - 1)/(2*alpha*ema), + kn = (-2*(-1 + 3*ea - 3*ea*ea + ea*ea*ea)/(3*ea + 1 + 3*ea*ea + ea*ea*ea)); + a0 = kn; + a1 = -kn*(1 + k*alpha)*ema; + a2 = kn*(1 - k*alpha)*ema; + a3 = -kn*ema2; + } break; + default : + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified filter order %u " + "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).", + cimg_instance, + order); + } + coefp = (a0 + a1)/(1 + b1 + b2); + coefn = (a2 + a3)/(1 + b1 + b2); + switch (naxis) { + case 'x' : { + const int N = width(); + const ulongT off = 1U; + cimg_pragma_openmp(parallel for 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 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 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 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; } + } + } + return *this; + } + + //! 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 { + return CImg(*this,false).deriche(sigma,order,axis,boundary_conditions); + } + + // [internal] Apply a recursive filter (used by CImg::vanvliet()). + /* + \param ptr the pointer of the data + \param filter the coefficient of the filter in the following order [n,n - 1,n - 2,n - 3]. + \param N size of the data + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive, 2nd derivative, 3rd derivative + \param boundary_conditions Boundary conditions. Can be { 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 double + 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. - 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.) * (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. - 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; + } + } 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; + } + } 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; + } + } break; + } + } + + //! Van Vliet recursive Gaussian filter. + /** + \param sigma standard deviation of the Gaussian filter + \param order the order of the filter 0,1,2,3 + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \note dirichlet boundary condition has a strange behavior + + I.T. Young, L.J. van Vliet, M. van Ginkel, Recursive Gabor filtering. + IEEE Trans. Sig. Proc., vol. 50, pp. 2799-2805, 2002. + + (this is an improvement over Young-Van Vliet, Sig. Proc. 44, 1995) + + Boundary conditions (only for order 0) using Triggs matrix, from + B. Triggs and M. Sdika. Boundary conditions for Young-van Vliet + recursive filtering. IEEE Trans. Signal Processing, + vol. 54, pp. 2365-2367, 2006. + **/ + CImg& vanvliet(const float sigma, const unsigned int order, const char axis='x', + const bool boundary_conditions=true) { + if (is_empty()) return *this; + 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; + const double + nnsigma = nsigma<0.5f?0.5f:nsigma, + m0 = 1.16680, m1 = 1.10783, m2 = 1.40586, + m1sq = m1 * m1, m2sq = m2 * m2, + q = (nnsigma<3.556?-0.2568 + 0.5784*nnsigma + 0.0561*nnsigma*nnsigma:2.5091 + 0.9804*(nnsigma - 3.556)), + qsq = q * q, + scale = (m0 + q) * (m1sq + m2sq + 2 * m1 * q + qsq), + b1 = -q * (2 * m0 * m1 + m1sq + m2sq + (2 * m0 + 4 * m1) * q + 3 * qsq) / scale, + b2 = qsq * (m0 + 2 * m1 + 3 * q) / scale, + b3 = -qsq * q / scale, + B = ( m0 * (m1sq + m2sq) ) / scale; + double filter[4]; + filter[0] = B; filter[1] = -b1; filter[2] = -b2; filter[3] = -b3; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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; + } + + //! 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 { + return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions); + } + + //! Blur image. + /** + \param sigma_x Standard deviation of the blur, along the X-axis. + \param sigma_y Standard deviation of the blur, along the Y-axis. + \param sigma_z Standard deviation of the blur, along the Z-axis. + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \param is_gaussian Tells if the blur uses a gaussian (\c true) or quasi-gaussian (\c false) kernel. + \note + - The blur is computed as a 0-order Deriche filter. This is not a gaussian blur. + - This is a recursive algorithm, not depending on the values of the standard deviations. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) { + if (is_empty()) return *this; + if (is_gaussian) { + if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions); + if (_height>1) vanvliet(sigma_y,0,'y',boundary_conditions); + if (_depth>1) vanvliet(sigma_z,0,'z',boundary_conditions); + } else { + if (_width>1) deriche(sigma_x,0,'x',boundary_conditions); + if (_height>1) deriche(sigma_y,0,'y',boundary_conditions); + if (_depth>1) deriche(sigma_z,0,'z',boundary_conditions); + } + return *this; + } + + //! Blur image \newinstance. + CImg get_blur(const float sigma_x, const float sigma_y, const float sigma_z, + const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*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. + \see deriche(), vanvliet(). + **/ + CImg& blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) { + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur(nsigma,nsigma,nsigma,boundary_conditions,is_gaussian); + } + + //! Blur image isotropically \newinstance. + CImg get_blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) const { + return CImg(*this,false).blur(sigma,boundary_conditions,is_gaussian); + } + + //! Blur image anisotropically, directed by a field of diffusion tensors. + /** + \param G Field of square roots of diffusion tensors/vectors used to drive the smoothing. + \param amplitude Amplitude of the smoothing. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + template + CImg& blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=1) { + + // Check arguments and init variables + if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6)) + throw CImgArgumentException(_cimg_instance + "blur_anisotropic(): Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).", + cimg_instance, + G._width,G._height,G._depth,G._spectrum,G._data); + if (is_empty() || dl<0) return *this; + const float namplitude = amplitude>=0?amplitude:-amplitude*cimg::max(_width,_height,_depth)/100; + 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; + + 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 + cimg_forZC(*this,z,c) { + cimg_abort_test; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + veloc = (Tfloat)(G(x,y,0,0)*ixx + 2*G(x,y,0,1)*ixy + G(x,y,0,2)*iyy); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + } + if (veloc_max>0) *this+=(velocity*=dl/veloc_max); + } + } else { // LIC-based smoothing + const ulongT whd = (ulongT)_width*_height*_depth; + const float sqrt2amplitude = (float)std::sqrt(2*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.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.f:datmp; + for (float theta = 0; theta<360; (theta+=da2),++N) { + const float + thetar = (float)(theta*cimg::PI/180), + vx = (float)(std::cos(thetar)*std::cos(phir)), + vy = (float)(std::sin(thetar)*std::cos(phir)), + vz = (float)std::sin(phir); + const t + *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2), + *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(0,0,0,3); + cimg_forXYZ(G,xg,yg,zg) { + const t a = *(pa++), b = *(pb++), c = *(pc++), d = *(pd++), e = *(pe++), f = *(pf++); + const float + u = (float)(a*vx + b*vy + c*vz), + v = (float)(b*vx + d*vy + e*vz), + w = (float)(c*vx + e*vy + f*vz), + n = 1e-5f + cimg::hypot(u,v,w), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)(w*dln); + *(pd3++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for 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_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,z,3), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y, + Z = (float)z; + switch (interpolation_type) { + case 0 : { // Nearest neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f), + cz = (int)(Z + 0.5f); + const float + u = (float)W(cx,cy,cz,0), + v = (float)W(cx,cy,cz,1), + w = (float)W(cx,cy,cz,2); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,cz,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,cz,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u = (float)(W._linear_atXYZ(X,Y,Z,0)), + v = (float)(W._linear_atXYZ(X,Y,Z,1)), + w = (float)(W._linear_atXYZ(X,Y,Z,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + default : { // 2nd order Runge Kutta + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)), + v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)), + w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2)), + u = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,0)), + v = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,1)), + w = (float)(W._linear_atXYZ(X + u0,Y + v0,Z + w0,2)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); + S+=coef; + } + X+=u; Y+=v; Z+=w; + } + } break; + } + Tfloat *ptrd = res.data(x,y,z); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,z,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + } 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); + Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2); + cimg_forXY(G,xg,yg) { + const t a = *(pa++), b = *(pb++), c = *(pc++); + const float + u = (float)(a*vx + b*vy), + v = (float)(b*vx + c*vy), + n = std::max(1e-5f,cimg::hypot(u,v)), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)n; + } + + cimg_abort_test; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && _height>=2) + firstprivate(val)) + cimg_forY(*this,y) _cimg_abort_try_omp2 { + cimg_abort_test2; + cimg_forX(*this,x) { + val.fill(0); + const float + n = (float)W(x,y,0,2), + fsigma = (float)(n*sqrt2amplitude), + fsigma2 = 2*fsigma*fsigma, + length = gauss_prec*fsigma; + float + S = 0, + X = (float)x, + Y = (float)y; + switch (interpolation_type) { + case 0 : { // Nearest-neighbor + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const int + cx = (int)(X + 0.5f), + cy = (int)(Y + 0.5f); + const float + u = (float)W(cx,cy,0,0), + v = (float)W(cx,cy,0,1); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + case 1 : { // Linear interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u = (float)(W._linear_atXY(X,Y,0,0)), + v = (float)(W._linear_atXY(X,Y,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } break; + default : { // 2nd-order Runge-kutta interpolation + for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const float + u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)), + v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)), + u = (float)(W._linear_atXY(X + u0,Y + v0,0,0)), + v = (float)(W._linear_atXY(X + u0,Y + v0,0,1)); + if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } + else { + const float coef = (float)std::exp(-l*l/fsigma2); + cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); + S+=coef; + } + X+=u; Y+=v; + } + } + } + Tfloat *ptrd = res.data(x,y); + if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } + else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,0,c)); ptrd+=whd; } + } + } _cimg_abort_catch_omp2 + } + } + const Tfloat *ptrs = res._data; + cimg_for(*this,ptrd,T) { + const Tfloat val = *(ptrs++)/N; + *ptrd = valval_max?val_max:(T)val); + } + } + cimg_abort_test; + return *this; + } + + //! Blur image anisotropically, directed by a field of diffusion tensors \newinstance. + template + CImg get_blur_anisotropic(const CImg& G, + const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way. + /** + \param amplitude Amplitude of the smoothing. + \param sharpness Sharpness. + \param anisotropy Anisotropy. + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the diffusion process. + \param interpolation_type Interpolation scheme. + Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. + \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. + **/ + CImg& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool is_fast_approx=true) { + const float nalpha = alpha>=0?alpha:-alpha*cimg::max(_width,_height,_depth)/100; + const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; + return blur_anisotropic(get_diffusion_tensors(sharpness,anisotropy,nalpha,nsigma,interpolation_type!=3), + amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); + } + + //! Blur image anisotropically, in an edge-preserving way \newinstance. + CImg get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, + const float da=30, const float gauss_prec=2, + const unsigned int interpolation_type=0, + const bool is_fast_approx=true) const { + return CImg(*this,false).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec, + interpolation_type,is_fast_approx); + } + + //! Blur image, with the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_x Amount of blur along the X-axis. + \param sigma_y Amount of blur along the Y-axis. + \param sigma_z Amount of blur along the Z-axis. + \param sigma_r Amount of blur along the value axis. + \param sampling_x Amount of downsampling along the X-axis used for the approximation. + Defaults (0) to sigma_x. + \param sampling_y Amount of downsampling along the Y-axis used for the approximation. + Defaults (0) to sigma_y. + \param sampling_z Amount of downsampling along the Z-axis used for the approximation. + Defaults (0) to sigma_z. + \param sampling_r Amount of downsampling along the value axis used for the approximation. + Defaults (0) to sigma_r. + \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006 + (extended for 3D volumetric images). + It is based on the reference implementation http://people.csail.mit.edu/jiawen/software/bilateralFilter.m + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_bilateral(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || (!sigma_x && !sigma_y && !sigma_z)) return *this; + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return blur(sigma_x,sigma_y,sigma_z); + const float + edge_delta = (float)(edge_max - edge_min), + _sigma_x = sigma_x>=0?sigma_x:-sigma_x*_width/100, + _sigma_y = sigma_y>=0?sigma_y:-sigma_y*_height/100, + _sigma_z = sigma_z>=0?sigma_z:-sigma_z*_depth/100, + _sigma_r = sigma_r>=0?sigma_r:-sigma_r*(edge_max - edge_min)/100, + _sampling_x = sampling_x?sampling_x:std::max(_sigma_x,1.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, + derived_sigma_z = _sigma_z / _sampling_z, + derived_sigma_r = _sigma_r / _sampling_r; + const int + padding_x = (int)(2*derived_sigma_x) + 1, + padding_y = (int)(2*derived_sigma_y) + 1, + padding_z = (int)(2*derived_sigma_z) + 1, + padding_r = (int)(2*derived_sigma_r) + 1; + const unsigned int + bx = (unsigned int)((_width - 1)/_sampling_x + 1 + 2*padding_x), + by = (unsigned int)((_height - 1)/_sampling_y + 1 + 2*padding_y), + bz = (unsigned int)((_depth - 1)/_sampling_z + 1 + 2*padding_z), + br = (unsigned int)(edge_delta/_sampling_r + 1 + 2*padding_r); + if (bx>0 || by>0 || bz>0 || br>0) { + const bool is_3d = (_depth>1); + if (is_3d) { // 3D version of the algorithm + CImg bgrid(bx,by,bz,br), bgridw(bx,by,bz,br); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); bgridw.fill(0); + cimg_forXYZ(*this,x,y,z) { + const T val = (*this)(x,y,z,c); + const float edge = (float)_guide(x,y,z); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + Z = (int)cimg::round(z/_sampling_z) + padding_z, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,Z,R)+=(float)val; + bgridw(X,Y,Z,R)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + bgridw.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); + + cimg_pragma_openmp(parallel for 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 + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + Z = z/_sampling_z + padding_z, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZC(X,Y,Z,R), bval1 = bgridw._linear_atXYZC(X,Y,Z,R); + (*this)(x,y,z,c) = (T)(bval0/bval1); + } + } + } else { // 2D version of the algorithm + CImg bgrid(bx,by,br,2); + cimg_forC(*this,c) { + const CImg _guide = guide.get_shared_channel(c%guide._spectrum); + bgrid.fill(0); + cimg_forXY(*this,x,y) { + const T val = (*this)(x,y,c); + const float edge = (float)_guide(x,y); + const int + X = (int)cimg::round(x/_sampling_x) + padding_x, + Y = (int)cimg::round(y/_sampling_y) + padding_y, + R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; + bgrid(X,Y,R,0)+=(float)val; + bgrid(X,Y,R,1)+=1; + } + bgrid.blur(derived_sigma_x,derived_sigma_y,0,true).blur(0,0,derived_sigma_r,false); + + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(size(),4096)) + cimg_forXY(*this,x,y) { + const float edge = (float)_guide(x,y); + const float + X = x/_sampling_x + padding_x, + Y = y/_sampling_y + padding_y, + R = (edge - edge_min)/_sampling_r + padding_r; + const float bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1); + (*this)(x,y,c) = (T)(bval0/bval1); + } + } + } + } + return *this; + } + + //! Blur image, with the joint bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_x, const float sigma_y, + const float sigma_z, const float sigma_r, + const float sampling_x, const float sampling_y, + const float sampling_z, const float sampling_r) const { + return CImg(*this,false).blur_bilateral(guide,sigma_x,sigma_y,sigma_z,sigma_r, + sampling_x,sampling_y,sampling_z,sampling_r); + } + + //! Blur image using the joint bilateral filter. + /** + \param guide Image used to model the smoothing weights. + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_r Amount of blur along the value axis. + \param sampling_s Amount of downsampling along the XYZ-axes used for the approximation. Defaults to sigma_s. + \param sampling_r Amount of downsampling along the value axis used for the approximation. Defaults to sigma_r. + **/ + template + CImg& blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) { + const float _sigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100; + return blur_bilateral(guide,_sigma_s,_sigma_s,_sigma_s,sigma_r,sampling_s,sampling_s,sampling_s,sampling_r); + } + + //! Blur image using the bilateral filter \newinstance. + template + CImg get_blur_bilateral(const CImg& guide, + const float sigma_s, const float sigma_r, + const float sampling_s=0, const float sampling_r=0) const { + return CImg(*this,false).blur_bilateral(guide,sigma_s,sigma_r,sampling_s,sampling_r); + } + + // [internal] Apply a box filter (used by CImg::boxfilter() and CImg::blur_box()). + /* + \param ptr the pointer of the data + \param N size of the data + \param boxsize Size of the box filter (can be subpixel). + \param off the offset between two data point + \param order the order of the filter 0 (smoothing), 1st derivtive and 2nd derivative. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + */ + static void _cimg_blur_box_apply(T *ptr, const float boxsize, const int N, const ulongT off, + const int order, const bool boundary_conditions, + const unsigned int nb_iter) { + // Smooth. + if (boxsize>1 && nb_iter) { + const int w2 = (int)(boxsize - 1)/2; + const unsigned int winsize = 2*w2 + 1U; + const double frac = (boxsize - winsize)/2.; + CImg win(winsize); + for (unsigned int iter = 0; iter=N) return boundary_conditions?ptr[(N - 1)*off]:T(); + return ptr[x*off]; + } + + // Apply box filter of order 0,1,2. + /** + \param boxsize Size of the box window (can be subpixel) + \param order the order of the filter 0,1 or 2. + \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. + \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \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 nb_iter=1) { + if (is_empty() || !boxsize || (boxsize<=1 && !order)) return *this; + const char naxis = cimg::lowercase(axis); + const float nboxsize = boxsize>=0?boxsize:-boxsize* + (naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; + switch (naxis) { + case 'x' : { + cimg_pragma_openmp(parallel for 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 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 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 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); + } + } + return *this; + } + + // 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 nb_iter=1) const { + return CImg(*this,false).boxfilter(boxsize,order,axis,boundary_conditions,nb_iter); + } + + //! Blur image with a box filter. + /** + \param boxsize_x Size of the box window, along the X-axis (can be subpixel). + \param boxsize_y Size of the box window, along the Y-axis (can be subpixel). + \param boxsize_z Size of the box window, along the Z-axis (can be subpixel). + \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. + \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 nb_iter=1) { + if (is_empty()) return *this; + if (_width>1) boxfilter(boxsize_x,0,'x',boundary_conditions,nb_iter); + if (_height>1) boxfilter(boxsize_y,0,'y',boundary_conditions,nb_iter); + if (_depth>1) boxfilter(boxsize_z,0,'z',boundary_conditions,nb_iter); + return *this; + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, + const bool boundary_conditions=true) 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 + \see deriche(), vanvliet(). + **/ + CImg& blur_box(const float boxsize, const bool boundary_conditions=true) { + const float nboxsize = boxsize>=0?boxsize:-boxsize*cimg::max(_width,_height,_depth)/100; + return blur_box(nboxsize,nboxsize,nboxsize,boundary_conditions); + } + + //! Blur image with a box filter \newinstance. + CImg get_blur_box(const float boxsize, const bool boundary_conditions=true) const { + return CImg(*this,false).blur_box(boxsize,boundary_conditions); + } + + //! Blur image, with the image guided filter. + /** + \param guide Image used to guide the smoothing process. + \param radius Spatial radius. If negative, it is expressed as a percentage of the largest image size. + \param regularization Regularization parameter. + If negative, it is expressed as a percentage of the guide value range. + \note This method implements the filtering algorithm described in: + He, Kaiming; Sun, Jian; Tang, Xiaoou, "Guided Image Filtering," Pattern Analysis and Machine Intelligence, + IEEE Transactions on , vol.35, no.6, pp.1397,1409, June 2013 + **/ + template + CImg& blur_guided(const CImg& guide, const float radius, const float regularization) { + return get_blur_guided(guide,radius,regularization).move_to(*this); + } + + //! Blur image, with the image guided filter \newinstance. + template + CImg get_blur_guided(const CImg& guide, const float radius, const float regularization) const { + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_guided(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", + cimg_instance, + guide._width,guide._height,guide._depth,guide._spectrum,guide._data); + if (is_empty() || !radius) return *this; + const int _radius = radius>=0?(int)radius:(int)(-radius*cimg::max(_width,_height,_depth)/100); + float _regularization = regularization; + if (regularization<0) { + T edge_min, edge_max = guide.max_min(edge_min); + if (edge_min==edge_max) return *this; + _regularization = -regularization*(edge_max - edge_min)/100; + } + _regularization = std::max(_regularization,0.01f); + const unsigned int psize = (unsigned int)(1 + 2*_radius); + CImg + mean_p = get_blur_box(psize,true), + mean_I = guide.get_blur_box(psize,true).resize(mean_p), + cov_Ip = get_mul(guide).blur_box(psize,true)-=mean_p.get_mul(mean_I), + var_I = guide.get_sqr().blur_box(psize,true)-=mean_I.get_sqr(), + &a = cov_Ip.div(var_I+=_regularization), + &b = mean_p-=a.get_mul(mean_I); + a.blur_box(psize,true); + b.blur_box(psize,true); + return a.mul(guide)+=b; + } + + //! Blur image using patch-based space. + /** + \param sigma_s Amount of blur along the XYZ-axes. + \param sigma_p Amount of blur along the value axis. + \param patch_size Size of the 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, + const unsigned int lookup_size=4, const float smoothness=0, const bool is_fast_approx=true) { + if (is_empty() || !patch_size || !lookup_size) return *this; + return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,is_fast_approx).move_to(*this); + } + + //! Blur image using patch-based space \newinstance. + CImg get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, + const unsigned int lookup_size=4, const float smoothness=0, + const bool is_fast_approx=true) const { + +#define _cimg_blur_patch3d_fast(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) \ + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch3d(N) \ + cimg_for##N##XYZ(res,x,y,z) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \ + alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ + } + +#define _cimg_blur_patch2d_fast(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) \ + if (cimg::abs((Tfloat)img(x,y,0,0) - (Tfloat)img(p,q,0,0))3?0.f:1.f; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + +#define _cimg_blur_patch2d(N) \ + cimg_for##N##XY(res,x,y) { \ + T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ + float sum_weights = 0, weight_max = 0; \ + cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { \ + T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \ + float distance2 = 0; \ + pQ = Q._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, \ + alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)std::exp(-alldist); \ + if (weight>weight_max) weight_max = weight; \ + sum_weights+=weight; \ + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + } \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \ + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ + } + + if (is_empty() || !patch_size || !lookup_size) return +*this; + CImg 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 float + nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100, + sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p, + Pnorm = P.size()*sigma_p2; + const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1; + const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size; + cimg::unused(N2,N3); + if (_depth>1) switch (patch_size) { // 3D + case 2 : if (is_fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch3d_fast(3) else _cimg_blur_patch3d(3) break; + default : { + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) + private(P,Q)) + cimg_forXYZ(res,x,y,z) { // Fast + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) + if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + if (res._width>=32 && res._height*res._depth>=4) firstprivate(P,Q)) + cimg_forXYZ(res,x,y,z) { // Exact + P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { + (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, dz = (float)z - r, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); + } + } + } else switch (patch_size) { // 2D + case 2 : if (is_fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break; + case 3 : if (is_fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break; + case 4 : if (is_fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break; + case 5 : if (is_fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break; + case 6 : if (is_fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break; + case 7 : if (is_fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break; + case 8 : if (is_fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break; + case 9 : if (is_fast_approx) _cimg_blur_patch2d_fast(9) else _cimg_blur_patch2d(9) break; + default : { // Fast + const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; + if (is_fast_approx) + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Fast + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) + if ((Tfloat)cimg::abs(img(x,y,0) - (Tfloat)img(p,q,0))3?0.f:1.f; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); + } else + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) { // Exact + P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0, weight_max = 0; + cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { + (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2), + weight = (float)std::exp(-distance2); + if (weight>weight_max) weight_max = weight; + sum_weights+=weight; + cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); + if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + else cimg_forC(res,c) res(x,y,0,c) = (Tfloat)((*this)(x,y,c)); + } + } + } + return res; + } + + //! Blur image with the median filter. + /** + \param n Size of the median filter. + \param threshold Threshold used to discard pixels too far from the current pixel value in the median computation. + **/ + CImg& blur_median(const unsigned int n, const float threshold=0) { + if (!n) return *this; + return get_blur_median(n,threshold).move_to(*this); + } + + //! Blur image with the median filter \newinstance. + CImg get_blur_median(const unsigned int n, const float threshold=0) const { + if (is_empty() || n<=1) return +*this; + CImg res(_width,_height,_depth,_spectrum); + T *ptrd = res._data; + cimg::unused(ptrd); + const int hr = (int)n/2, hl = n - hr - 1; + if (res._depth!=1) { // 3D + if (threshold>0) + cimg_pragma_openmp(parallel for 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + const Tfloat val0 = (Tfloat)(*this)(x,y,z,c); + CImg values(n*n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXYZ(*this,nx0,ny0,nz0,nx1,ny1,nz1,p,q,r) + if (cimg::abs((*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 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; + res(x,y,z,c) = get_crop(nx0,ny0,nz0,c,nx1,ny1,nz1,c).median(); + } + } else { + if (threshold>0) + 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, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + const Tfloat val0 = (Tfloat)(*this)(x,y,c); + CImg values(n*n); + unsigned int nb_values = 0; + T *ptrd = values.data(); + cimg_for_inXY(*this,nx0,ny0,nx1,ny1,p,q) + if (cimg::abs((*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 { + const int + w1 = width() - 1, h1 = height() - 1, + w2 = width() - 2, h2 = height() - 2, + w3 = width() - 3, h3 = height() - 3, + w4 = width() - 4, h4 = height() - 4; + switch (n) { // Without threshold + case 3 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(9); + cimg_for_in3x3(*this,1,1,w2,h2,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6],I[7],I[8]); + cimg_for_borderXY(*this,x,y,1) + res(x,y,c) = get_crop(std::max(0,x - 1),std::max(0,y - 1),0,c, + std::min(w1,x + 1),std::min(h1,y + 1),0,c).median(); + } + } break; + case 5 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(25); + cimg_for_in5x5(*this,2,2,w3,h3,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4], + I[5],I[6],I[7],I[8],I[9], + I[10],I[11],I[12],I[13],I[14], + I[15],I[16],I[17],I[18],I[19], + I[20],I[21],I[22],I[23],I[24]); + cimg_for_borderXY(*this,x,y,2) + res(x,y,c) = get_crop(std::max(0,x - 2),std::max(0,y - 2),0,c, + std::min(w1,x + 2),std::min(h1,y + 2),0,c).median(); + } + } break; + case 7 : { + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) + cimg_forC(*this,c) { + CImg I(49); + cimg_for_in7x7(*this,3,3,w4,h4,x,y,0,c,I,T) + res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6], + I[7],I[8],I[9],I[10],I[11],I[12],I[13], + I[14],I[15],I[16],I[17],I[18],I[19],I[20], + I[21],I[22],I[23],I[24],I[25],I[26],I[27], + I[28],I[29],I[30],I[31],I[32],I[33],I[34], + I[35],I[36],I[37],I[38],I[39],I[40],I[41], + I[42],I[43],I[44],I[45],I[46],I[47],I[48]); + cimg_for_borderXY(*this,x,y,3) + res(x,y,c) = get_crop(std::max(0,x - 3),std::max(0,y - 3),0,c, + std::min(w1,x + 3),std::min(h1,y + 3),0,c).median(); + } + } break; + default : { + 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, + nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, + nx1 = x1>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; + res(x,y,c) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median(); + } + } + } + } + } + return res; + } + + //! Sharpen image. + /** + \param amplitude Sharpening amplitude + \param sharpen_type Select sharpening method. Can be { false=inverse diffusion | true=shock filters }. + \param edge Edge threshold (shock filters only). + \param alpha Gradient smoothness (shock filters only). + \param sigma Tensor smoothness (shock filters only). + **/ + CImg& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) { + if (is_empty()) return *this; + T val_min, val_max = max_min(val_min); + const float nedge = edge/2; + CImg velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum); + + 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 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); + CImg val, vec; + cimg_forX(G,x) { + G.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + if (val[2]<0) val[2] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = vec(0,2); + *(ptrG3++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1] + val[2],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + u = G(x,y,z,0), + v = G(x,y,z,1), + w = G(x,y,z,2), + amp = G(x,y,z,3), + ixx = Incc + Ipcc - 2*Iccc, + ixy = (Innc + Ippc - Inpc - Ipnc)/4, + ixz = (Incn + Ipcp - Incp - Ipcn)/4, + iyy = Icnc + Icpc - 2*Iccc, + iyz = (Icnn + Icpp - Icnp - Icpn)/4, + izz = Iccn + Iccp - 2*Iccc, + ixf = Incc - Iccc, + ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, + iyb = Iccc - Icpc, + izf = Iccn - Iccc, + izb = Iccc - Iccp, + itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else { // 2D + if (sharpen_type) { // Shock filters + CImg 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>=(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); + cimg_forX(G,x) { + G.get_tensor_at(x,y).symmetric_eigen(val,vec); + if (val[0]<0) val[0] = 0; + if (val[1]<0) val[1] = 0; + *(ptrG0++) = vec(0,0); + *(ptrG1++) = vec(0,1); + *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge); + } + } + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat + u = G(x,y,0), + v = G(x,y,1), + amp = G(x,y,2), + ixx = Inc + Ipc - 2*Icc, + ixy = (Inn + Ipp - Inp - Ipn)/4, + iyy = Icn + Icp - 2*Icc, + ixf = Inc - Icc, + ixb = Icc - Ipc, + iyf = Icn - Icc, + iyb = Icc - Icp, + itt = u*u*ixx + v*v*iyy + 2*u*v*ixy, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb), + veloc = -amp*cimg::sign(itt)*cimg::abs(it); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } else // Inverse diffusion + cimg_forC(*this,c) { + Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) { + const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc; + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } + _veloc_max[c] = veloc_max; + } + } + const Tfloat veloc_max = _veloc_max.max(); + if (veloc_max<=0) return *this; + return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this); + } + + //! Sharpen image \newinstance. + CImg get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, + const float alpha=0, const float sigma=0) const { + return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma); + } + + //! Return image gradient. + /** + \param axes Axes considered for the gradient computation, as a C-string (e.g "xy"). + \param scheme = Numerical scheme used for the gradient computation: + - -1 = Backward finite differences + - 0 = Centered finite differences + - 1 = Forward finite differences + - 2 = Using Sobel kernels + - 3 = Using rotation invariant kernels + - 4 = Using Deriche recusrsive filter. + - 5 = Using Van Vliet recusrsive filter. + **/ + CImgList 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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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.f))), b = (Tfloat)(0.5f*(std::sqrt(2.f) - 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 cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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 res; + for (unsigned int l = 0; axes[l]; ++l) { + const char axis = cimg::lowercase(axes[l]); + switch (axis) { + case 'x' : res.insert(grad[0]); break; + case 'y' : res.insert(grad[1]); break; + case 'z' : res.insert(grad[2]); break; + } + } + grad.assign(); + return res; + } + + //! Return image hessian. + /** + \param axes Axes considered for the hessian computation, as a C-string (e.g "xy"). + **/ + CImgList 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) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + + res.assign(lmax/2,_width,_height,_depth,_spectrum); + if (!cimg::strcasecmp(naxes,def_axes3d)) { // 3D + + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + const ulongT off = (ulongT)c*_width*_height*_depth; + Tfloat + *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off, + *ptrd3 = res[3]._data + off, *ptrd4 = res[4]._data + off, *ptrd5 = res[5]._data + off; + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipcc + Incc - 2*Iccc; // Ixx + *(ptrd1++) = (Ippc + Innc - Ipnc - Inpc)/4; // Ixy + *(ptrd2++) = (Ipcp + Incn - Ipcn - Incp)/4; // Ixz + *(ptrd3++) = Icpc + Icnc - 2*Iccc; // Iyy + *(ptrd4++) = (Icpp + Icnn - Icpn - Icnp)/4; // Iyz + *(ptrd5++) = Iccn + Iccp - 2*Iccc; // Izz + } + } + } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // 2D + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; + Tfloat *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off; + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) { + *(ptrd0++) = Ipc + Inc - 2*Icc; // Ixx + *(ptrd1++) = (Ipp + Inn - Ipn - Inp)/4; // Ixy + *(ptrd2++) = Icp + Icn - 2*Icc; // Iyy + } + } + } else for (unsigned int l = 0; laxis2) cimg::swap(axis1,axis2); + bool valid_axis = false; + if (axis1=='x' && axis2=='x') { // Ixx + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Ipc + Inc - 2*Icc; + } + } + else if (axis1=='x' && axis2=='y') { // Ixy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*1048576 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + Tfloat *ptrd = res[l2].data(0,0,z,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipp + Inn - Ipn - Inp)/4; + } + } + else if (axis1=='x' && axis2=='z') { // Ixz + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipcp + Incn - Ipcn - Incp)/4; + } + } + else if (axis1=='y' && axis2=='y') { // Iyy + valid_axis = true; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*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>=(cimg_openmp_sizefactor)*1048576 && + _spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res[l2].data(0,0,0,c); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Iccn + Iccp - 2*Iccc; + } + } + else if (!valid_axis) + throw CImgArgumentException(_cimg_instance + "get_hessian(): Invalid specified axes '%s'.", + cimg_instance, + naxes); + } + return res; + } + + //! Compute image Laplacian. + CImg& laplacian() { + return get_laplacian().move_to(*this); + } + + //! 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>=(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>=(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>=(cimg_openmp_sizefactor)*1048576 && + _height*_depth*_spectrum>=2)) + cimg_forC(*this,c) { + Tfloat *ptrd = res.data(0,0,0,c); + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc - 2*Icc; + } + } + return res; + } + + //! Compute the structure tensor field of an image. + /** + \param is_fwbw_scheme scheme. Can be { false=centered | true=forward-backward } + **/ + CImg& structure_tensors(const bool is_fwbw_scheme=false) { + return get_structure_tensors(is_fwbw_scheme).move_to(*this); + } + + //! Compute the structure tensor field of an image \newinstance. + CImg get_structure_tensors(const bool is_fwbw_scheme=false) const { + if (is_empty()) return *this; + CImg res; + if (_depth>1) { // 3D + res.assign(_width,_height,_depth,6,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ix = (Incc - Ipcc)/2, + iy = (Icnc - Icpc)/2, + iz = (Iccn - Iccp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=ix*iz; + *(ptrd3++)+=iy*iy; + *(ptrd4++)+=iy*iz; + *(ptrd5++)+=iz*iz; + } + } + } else { // Forward/backward finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(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), + *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { + const Tfloat + ixf = Incc - Iccc, ixb = Iccc - Ipcc, + iyf = Icnc - Iccc, iyb = Iccc - Icpc, + izf = Iccn - Iccc, izb = Iccc - Iccp; + *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; + *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4; + *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4; + *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2; + *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4; + *(ptrd5++)+=(izf*izf + izb*izb)/2; + } + } + } + } else { // 2D + res.assign(_width,_height,_depth,3,0); + if (!is_fwbw_scheme) { // Classical central finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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 + ix = (Inc - Ipc)/2, + iy = (Icn - Icp)/2; + *(ptrd0++)+=ix*ix; + *(ptrd1++)+=ix*iy; + *(ptrd2++)+=iy*iy; + } + } + } else { // Forward/backward finite differences (version 2) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(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; + } + } + } + } + return res; + } + + //! Compute field of diffusion tensors for edge-preserving smoothing. + /** + \param sharpness Sharpness + \param anisotropy Anisotropy + \param alpha Standard deviation of the gradient blur. + \param sigma Standard deviation of the structure tensor blur. + \param is_sqrt Tells if the square root of the tensor field is computed instead. + **/ + CImg& diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) { + CImg res; + const float + nsharpness = std::max(sharpness,1e-5f), + power1 = (is_sqrt?0.5f:1)*nsharpness, + power2 = power1/(1e-7f + 1 - anisotropy); + blur(alpha).normalize(0,(T)255); + + if (_depth>1) { // 3D + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for 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), + *ptrd3 = res.data(0,y,z,3), *ptrd4 = res.data(0,y,z,4), *ptrd5 = res.data(0,y,z,5); + CImg val(3), vec(3,3); + cimg_forX(*this,x) { + res.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + const float + _l1 = val[2], _l2 = val[1], _l3 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, l3 = _l3>0?_l3:0, + ux = vec(0,0), uy = vec(0,1), uz = vec(0,2), + vx = vec(1,0), vy = vec(1,1), vz = vec(1,2), + wx = vec(2,0), wy = vec(2,1), wz = vec(2,2), + n1 = (float)std::pow(1 + l1 + l2 + l3,-power1), + n2 = (float)std::pow(1 + l1 + l2 + l3,-power2); + *(ptrd0++) = n1*(ux*ux + vx*vx) + n2*wx*wx; + *(ptrd1++) = n1*(ux*uy + vx*vy) + n2*wx*wy; + *(ptrd2++) = n1*(ux*uz + vx*vz) + n2*wx*wz; + *(ptrd3++) = n1*(uy*uy + vy*vy) + n2*wy*wy; + *(ptrd4++) = n1*(uy*uz + vy*vz) + n2*wy*wz; + *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz; + } + } + } else { // for 2D images + get_structure_tensors().move_to(res).blur(sigma); + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(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); + cimg_forX(*this,x) { + res.get_tensor_at(x,y).symmetric_eigen(val,vec); + const float + _l1 = val[1], _l2 = val[0], + l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, + ux = vec(1,0), uy = vec(1,1), + vx = vec(0,0), vy = vec(0,1), + n1 = (float)std::pow(1 + l1 + l2,-power1), + n2 = (float)std::pow(1 + l1 + l2,-power2); + *(ptrd0++) = n1*ux*ux + n2*vx*vx; + *(ptrd1++) = n1*ux*uy + n2*vx*vy; + *(ptrd2++) = n1*uy*uy + n2*vy*vy; + } + } + } + return res.move_to(*this); + } + + //! Compute field of diffusion tensors for edge-preserving smoothing \newinstance. + CImg get_diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, + const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) const { + return CImg(*this,false).diffusion_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt); + } + + //! Estimate displacement field between two images. + /** + \param source Reference image. + \param smoothness Smoothness of estimated displacement field. + \param precision Precision required for algorithm convergence. + \param nb_scales Number of scales used to estimate the displacement field. + \param iteration_max Maximum number of iterations allowed for one scale. + \param is_backward If false, match I2(X + U(X)) = I1(X), else match I2(X) = I1(X - U(X)). + \param guide Image used as the initial correspondence estimate for the algorithm. + 'guide' may have a last channel with boolean values (0=false | other=true) that + tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask). + **/ + CImg& 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()) { + return get_displacement(source,smoothness,precision,nb_scales,iteration_max,is_backward,guide). + move_to(*this); + } + + //! Estimate displacement field between two images \newinstance. + CImg get_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()) const { + if (is_empty() || !source) return +*this; + if (!is_sameXYZC(source)) + throw CImgArgumentException(_cimg_instance + "displacement(): Instance and source image (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + source._width,source._height,source._depth,source._spectrum,source._data); + if (precision<0) + throw CImgArgumentException(_cimg_instance + "displacement(): Invalid specified precision %g " + "(should be >=0)", + cimg_instance, + precision); + + const bool is_3d = source._depth>1; + const unsigned int constraint = is_3d?3:2; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum0?nb_scales: + (unsigned int)cimg::round(std::log(mins/8.)/std::log(1.5),1,1); + + 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); + + CImg U, V; + floatT bound = 0; + for (int scale = (int)_nb_scales - 1; scale>=0; --scale) { + const float factor = (float)std::pow(1.5,(double)scale); + const unsigned int + _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1, + _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1, + _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1; + if (sw<5 && sh<5 && (!is_3d || sd<5)) continue; // Skip too small scales + const CImg + I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta, + I2 = (get_resize(I1,2)-=tm)/=tdelta; + if (guide._spectrum>constraint) guide.get_resize(I2._width,I2._height,I2._depth,-100,1).move_to(V); + if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3); + else { + if (guide) + guide.get_shared_channels(0,is_3d?2:1).get_resize(I2._width,I2._height,I2._depth,-100,2).move_to(U); + else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0); + } + + float dt = 2, energy = cimg::type::max(); + const CImgList dI = is_backward?I1.get_gradient():I2.get_gradient(); + cimg_abort_init; + + for (unsigned int iteration = 0; iteration=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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for 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 + _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; + if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; + if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; + bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; + bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; + bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; + } else { + if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; + if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; + if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; + bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; + bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; + bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints + U(x,y,z,0) = V(x,y,z,0)/factor; + U(x,y,z,1) = V(x,y,z,1)/factor; + U(x,y,z,2) = V(x,y,z,2)/factor; + } + } + } + } else { // 2D version + if (smoothness>=0) // Isotropic regularization + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + smoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } else { // Anisotropic regularization + const float nsmoothness = -smoothness; + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(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; + bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; + } else { + if (U(x,y,0)<-x) U(x,y,0) = -(float)x; + if (U(x,y,1)<-y) U(x,y,1) = -(float)y; + bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; + bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; + } + _energy+=delta_I*delta_I + nsmoothness*_energy_regul; + } + if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints + U(x,y,0) = V(x,y,0)/factor; + U(x,y,1) = V(x,y,1)/factor; + } + } + } + } + const float d_energy = (_energy - energy)/(sw*sh*sd); + if (d_energy<=0 && -d_energy<_precision) break; + if (d_energy>0) dt*=0.5f; + energy = _energy; + } + } + return U; + } + + //! Compute correspondence map between two images, using 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. + \param patch_height Height of the patch used for matching. + \param patch_depth Depth of the patch used for matching. + \param nb_iterations Number of patch-match iterations. + \param nb_randoms Number of randomization attempts (per pixel). + \param occ_penalization Penalization factor in score related patch occurrences. + \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. + **/ + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide,matching_score).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \newinstance. + template + 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 occ_penalization, + const CImg &guide, + CImg &matching_score) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization, + guide,true,matching_score); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,occ_penalization,guide).move_to(*this); + } + + //! Compute correspondence map between two images, using the patch-match algorithm \overloading. + template + 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=5, + const unsigned int nb_randoms=5, + const float occ_penalization=0, + const CImg &guide=CImg::const_empty()) const { + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,guide,occ_penalization,false,CImg::empty()); + } + + template + 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 occ_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 + "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 + "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 + "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, + patch_image._data); + const unsigned int + _constraint = patch_image._depth>1?3:2, + constraint = guide._spectrum>_constraint?_constraint:0; + + if (guide && + (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<_constraint)) + throw CImgArgumentException(_cimg_instance + "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 is_updated(_width,_height,_depth,1,3); + CImg score(_width,_height,_depth); + CImg occ, loop_order; + ulongT rng = (cimg::_rand(),cimg::rng()); + if (occ_penalization!=0) { + occ.assign(patch_image._width,patch_image._height,patch_image._depth,1,0); + loop_order.assign(_width,_height,_depth,_depth>1?3:2); + cimg_forXYZ(loop_order,x,y,z) { + loop_order(x,y,z,0) = x; + loop_order(x,y,z,1) = y; + if (loop_order._spectrum>2) loop_order(x,y,z,2) = z; + } + cimg_forXYZ(loop_order,x,y,z) { // Randomize loop order in case of constraints on patch occurrence + const unsigned int + X = (unsigned int)cimg::round(cimg::rand(loop_order._width - 1.,&rng)), + Y = (unsigned int)cimg::round(cimg::rand(loop_order._height - 1.,&rng)), + Z = loop_order._depth>1?(unsigned int)cimg::round(cimg::rand(loop_order._depth - 1.,&rng)):0U; + cimg::swap(loop_order(x,y,z,0),loop_order(X,Y,Z,0)); + cimg::swap(loop_order(x,y,z,1),loop_order(X,Y,Z,1)); + if (loop_order._spectrum>2) cimg::swap(loop_order(x,y,z,2),loop_order(X,Y,Z,2)); + } + } + 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 (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_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_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter2) z = loop_order(_x,_y,_z,2); else z = _z; + } else { x = _x; y = _y; 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 = map(x - 1,y,z,0); + v = map(x - 1,y,z,1); + w = map(x - 1,y,z,2); + if (u>=cx1 - 1 && u=cy1 && v=cz1 && w0 && (is_updated(x,y - 1,z)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,z,0); + v = map(x,y - 1,z,1); + w = map(x,y - 1,z,2); + if (u>=cx1 && u=cy1 - 1 && v=cz1 && w0 && (is_updated(x,y,z - 1)&cmask)) { // Compare with backward neighbor + u = map(x,y,z - 1,0); + v = map(x,y,z - 1,1); + w = map(x,y,z - 1,2); + if (u>=cx1 && u=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::inf()); + } else cimg_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + ulongT rng = (cimg::_rand(),cimg::rng()); +#ifdef cimg_use_openmp + 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; iter=(cimg_openmp_sizefactor)*64 && + iter0 && (is_updated(x - 1,y)&cmask)) { // Compare with left neighbor + u = map(x - 1,y,0); + v = map(x - 1,y,1); + if (u>=cx1 - 1 && u=cy1 && v0 && (is_updated(x,y - 1)&cmask)) { // Compare with up neighbor + u = map(x,y - 1,0); + v = map(x,y - 1,1); + if (u>=cx1 && u=cy1 - 1 && v=cx1 + 1 && u=cy1 && v=cx1 && u=cy1 + 1 && v& img1, const CImg& img2, const CImg& occ, + const unsigned int psizew, const unsigned int psizeh, + 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 occ_penalization, + const float max_score) { // 3D version + 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 - 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; + for (unsigned int k = 0; kmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + p1+=offy1; p2+=offy2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc,zc)); + } + + static float _matchpatch(const CImg& img1, const CImg& img2, const CImg& occ, + 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 occ_penalization, + const float max_score) { // 2D version + 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 - psizewc, + offx2 = (ulongT)img2._width - psizewc; + float ssd = 0; + for (unsigned int j = 0; jmax_score) return max_score; + p1+=offx1; p2+=offx2; + } + return occ_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + occ_penalization*occ(xc,yc)); + } + + //! Compute Euclidean distance function to a specified value. + /** + \param value Reference value. + \param metric Type of metric. Can be { 0=Chebyshev | 1=Manhattan | 2=Euclidean | 3=Squared-euclidean }. + \note + The distance transform implementation has been submitted by A. Meijster, and implements + the article 'W.H. Hesselink, A. Meijster, J.B.T.M. Roerdink, + "A general algorithm for computing distance transforms in linear time.", + In: Mathematical Morphology and its Applications to Image and Signal Processing, + J. Goutsias, L. Vincent, and D.S. Bloomberg (eds.), Kluwer, 2000, pp. 331-340.' + The submitted code has then been modified to fit CImg coding style and constraints. + **/ + CImg& distance(const T& value, const unsigned int metric=2) { + if (is_empty()) return *this; + if (cimg::type::string()!=cimg::type::string()) // 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 + } + return *this; + } + + //! Compute distance to a specified value \newinstance. + CImg get_distance(const T& value, const unsigned int metric=2) const { + return CImg(*this,false).distance((Tfloat)value,metric); + } + + static longT _distance_sep_edt(const longT i, const longT u, const longT *const g) { + return (u*u - i*i + g[u] - g[i])/(2*(u - i)); + } + + static longT _distance_dist_edt(const longT x, const longT i, const longT *const g) { + return (x - i)*(x - i) + g[i]; + } + + static longT _distance_sep_mdt(const longT i, const longT u, const longT *const g) { + return (u - i<=g[u] - g[i]?999999999:(g[u] - g[i] + u + i)/2); + } + + static longT _distance_dist_mdt(const longT x, const longT i, const longT *const g) { + return (x=0) && f(t[q],s[q],g)>f(t[q],u,g)) { --q; } + if (q<0) { q = 0; s[0] = u; } + else { const longT w = 1 + sep(s[q], u, g); if (w<(longT)len) { ++q; s[q] = u; t[q] = w; }} + } + for (int u = (int)len - 1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan + } + + CImg& _distance_core(longT (*const sep)(const longT, const longT, const longT *const), + longT (*const f)(const longT, const longT, const longT *const)) { + // Check for g++ 4.9.X, as OpenMP seems to crash for this particular function. I have no clues why. +#define cimg_is_gcc49x (__GNUC__==4 && __GNUC_MINOR__==9) + + const ulongT wh = (ulongT)_width*_height; +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) +#endif + cimg_forC(*this,c) { + CImg 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 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_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 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_forY(*this,y) g[y] = (longT)img(x,y,z,0,wh); + _distance_scan(_height,g,sep,f,s,t,dt); + cimg_forY(*this,y) img(x,y,z,0,wh) = (T)dt[y]; + } + } + if (_depth>1) { + g.assign(_depth); dt.assign(_depth); s.assign(_depth); t.assign(_depth); +#if defined(cimg_use_openmp) && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for 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_forZ(*this,z) g[z] = (longT)img(x,y,z,0,wh); + _distance_scan(_depth,g,sep,f,s,t,dt); + cimg_forZ(*this,z) img(x,y,z,0,wh) = (T)dt[z]; + } + } + } + return *this; + } + + //! Compute chamfer distance to a specified value, with a custom metric. + /** + \param value Reference value. + \param metric_mask Metric mask. + \note The algorithm code has been initially proposed by A. Meijster, and modified by D. Tschumperlé. + **/ + template + CImg& distance(const T& value, const CImg& metric_mask) { + if (is_empty()) return *this; + bool is_value = false; + cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,0:(T)999999999; + if (!is_value) return fill(cimg::type::max()); + const ulongT wh = (ulongT)_width*_height; + 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 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 y = height() - 1 - dy, ny = height() - 1; y>=0; --y,--ny) { + for (int x = width() - 1 - dx, nx = width() - 1; x>=0; --x,--nx) { + const T dd = img(nx,ny,nz,0,wh) + weight; + if (dd + CImg get_distance(const T& value, const CImg& metric_mask) const { + return CImg(*this,false).distance(value,metric_mask); + } + + //! Compute distance to a specified value, according to a custom metric (use dijkstra algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + \param is_high_connectivity Tells if the algorithm uses low or high connectivity. + \param[out] return_path An image containing the nodes of the minimal path. + **/ + template + CImg& distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) { + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm) \newinstance. + template + CImg::type> + get_distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, + CImg& return_path) const { + if (is_empty()) return return_path.assign(); + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_dijkstra(): image instance and metric map (%u,%u,%u,%u) " + "have incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + typedef typename cimg::superset::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); + + cimg_forC(*this,c) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + CImg path = return_path?return_path.get_shared_channel(c):CImg(); + unsigned int sizeQ = 0; + + // Detect initial seeds. + is_queued.fill(0); + cimg_forXYZ(img,x,y,z) if (img(x,y,z)==value) { + Q._priority_queue_insert(is_queued,sizeQ,0,x,y,z); + res(x,y,z) = 0; + if (path) path(x,y,z) = (to)0; + } + + // Start distance propagation. + while (sizeQ) { + + // Get and remove point with minimal potential from the queue. + const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3); + const td P = (td)-Q(0,0); + Q._priority_queue_remove(sizeQ); + + // Update neighbors. + td npot = 0; + if (x - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x - 1,y,z) + P),x - 1,y,z)) { + res(x - 1,y,z) = npot; if (path) path(x - 1,y,z) = (to)2; + } + if (x + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y - 1,z) + P),x,y - 1,z)) { + res(x,y - 1,z) = npot; if (path) path(x,y - 1,z) = (to)8; + } + if (y + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y,z - 1) + P),x,y,z - 1)) { + res(x,y,z - 1) = npot; if (path) path(x,y,z - 1) = (to)32; + } + if (z + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y - 1,z) + P)),x - 1,y - 1,z)) { + res(x - 1,y - 1,z) = npot; if (path) path(x - 1,y - 1,z) = (to)10; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y - 1,z) + P)),x + 1,y - 1,z)) { + res(x + 1,y - 1,z) = npot; if (path) path(x + 1,y - 1,z) = (to)9; + } + if (x - 1>=0 && y + 1=0) { // Diagonal neighbors on slice z - 1 + if (x - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z - 1) + P)),x - 1,y,z - 1)) { + res(x - 1,y,z - 1) = npot; if (path) path(x - 1,y,z - 1) = (to)34; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z - 1) + P)),x,y - 1,z - 1)) { + res(x,y - 1,z - 1) = npot; if (path) path(x,y - 1,z - 1) = (to)40; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z - 1) + P)), + x - 1,y - 1,z - 1)) { + res(x - 1,y - 1,z - 1) = npot; if (path) path(x - 1,y - 1,z - 1) = (to)42; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z - 1) + P)), + x + 1,y - 1,z - 1)) { + res(x + 1,y - 1,z - 1) = npot; if (path) path(x + 1,y - 1,z - 1) = (to)41; + } + if (x - 1>=0 && y + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z + 1) + P)),x - 1,y,z + 1)) { + res(x - 1,y,z + 1) = npot; if (path) path(x - 1,y,z + 1) = (to)18; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z + 1) + P)),x,y - 1,z + 1)) { + res(x,y - 1,z + 1) = npot; if (path) path(x,y - 1,z + 1) = (to)24; + } + if (y + 1=0 && y - 1>=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z + 1) + P)), + x - 1,y - 1,z + 1)) { + res(x - 1,y - 1,z + 1) = npot; if (path) path(x - 1,y - 1,z + 1) = (to)26; + } + if (x + 1=0 && + Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z + 1) + P)), + x + 1,y - 1,z + 1)) { + res(x + 1,y - 1,z + 1) = npot; if (path) path(x + 1,y - 1,z + 1) = (to)25; + } + if (x - 1>=0 && y + 1 + CImg& distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) { + return get_distance_dijkstra(value,metric,is_high_connectivity).move_to(*this); + } + + //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm). \newinstance. + template + CImg get_distance_dijkstra(const T& value, const CImg& metric, + const bool is_high_connectivity=false) const { + CImg return_path; + return get_distance_dijkstra(value,metric,is_high_connectivity,return_path); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + /** + \param value Reference value. + \param metric Field of distance potentials. + **/ + template + CImg& distance_eikonal(const T& value, const CImg& metric) { + return get_distance_eikonal(value,metric).move_to(*this); + } + + //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). + template + CImg get_distance_eikonal(const T& value, const CImg& metric) const { + if (is_empty()) return *this; + if (!is_sameXYZ(metric)) + throw CImgArgumentException(_cimg_instance + "distance_eikonal(): image instance and metric map (%u,%u,%u,%u) have " + "incompatible dimensions.", + cimg_instance, + metric._width,metric._height,metric._depth,metric._spectrum); + CImg result(_width,_height,_depth,_spectrum,cimg::type::max()), Q; + 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) { + const CImg img = get_shared_channel(c); + const CImg met = metric.get_shared_channel(c%metric._spectrum); + CImg res = result.get_shared_channel(c); + unsigned int sizeQ = 0; + state.fill(-1); + + // Detect initial seeds. + Tfloat *ptr1 = res._data; char *ptr2 = state._data; + cimg_for(img,ptr0,T) { if (*ptr0==value) { *ptr1 = 0; *ptr2 = 1; } ++ptr1; ++ptr2; } + + // Initialize seeds neighbors. + ptr2 = state._data; + cimg_forXYZ(img,x,y,z) if (*(ptr2++)==1) { + if (x - 1>=0 && state(x - 1,y,z)==-1) { + const Tfloat dist = res(x - 1,y,z) = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x - 1,y,z); + } + if (x + 1=0 && state(x,y - 1,z)==-1) { + const Tfloat dist = res(x,y - 1,z) = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y - 1,z); + } + if (y + 1=0 && state(x,y,z - 1)==-1) { + const Tfloat dist = res(x,y,z - 1) = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z - 1); + } + if (z + 1=0) { + if (x - 1>=0 && state(x - 1,y,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); + if (dist=0 && state(x,y - 1,z)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); + if (dist=0 && state(x,y,z - 1)!=1) { + const Tfloat dist = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); + if (dist& res, const Tfloat P, + const int x=0, const int y=0, const int z=0) const { + const Tfloat M = (Tfloat)cimg::type::max(); + T T1 = (T)std::min(x - 1>=0?res(x - 1,y,z):M,x + 11) { // 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 + 1T2) cimg::swap(T1,T2); + if (T2>T3) cimg::swap(T2,T3); + if (T1>T2) cimg::swap(T1,T2); + if (P<=0) return (Tfloat)T1; + if (T31) { // 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 + void _eik_priority_queue_insert(CImg& state, unsigned int& siz, const t value, + const unsigned int x, const unsigned int y, const unsigned int z) { + if (state(x,y,z)>0) return; + state(x,y,z) = 0; + if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } + (*this)(siz - 1,0) = (T)value; (*this)(siz - 1,1) = (T)x; (*this)(siz - 1,2) = (T)y; (*this)(siz - 1,3) = (T)z; + for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { + cimg::swap((*this)(pos,0),(*this)(par,0)); cimg::swap((*this)(pos,1),(*this)(par,1)); + cimg::swap((*this)(pos,2),(*this)(par,2)); cimg::swap((*this)(pos,3),(*this)(par,3)); + } + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE. + /** + \param nb_iterations Number of PDE iterations. + \param band_size Size of the narrow band. + \param time_step Time step of the PDE iterations. + **/ + CImg& distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) { + if (is_empty()) return *this; + CImg velocity(*this,false); + for (unsigned int iteration = 0; iteration1) { // 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)0?(Incc - Iccc):(Iccc - Ipcc), + iy = gy*sgn>0?(Icnc - Iccc):(Iccc - Icpc), + iz = gz*sgn>0?(Iccn - Iccc):(Iccc - Iccp), + ng = 1e-5f + cimg::hypot(gx,gy,gz), + ngx = gx/ng, + ngy = gy/ng, + ngz = gz/ng, + veloc = sgn*(ngx*ix + ngy*iy + ngz*iz - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } else { // 2D version + CImg_3x3(I,Tfloat); + cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) if (band_size<=0 || cimg::abs(Icc)0?(Inc - Icc):(Icc - Ipc), + iy = gy*sgn>0?(Icn - Icc):(Icc - Icp), + ng = std::max((Tfloat)1e-5,cimg::hypot(gx,gy)), + ngx = gx/ng, + ngy = gy/ng, + veloc = sgn*(ngx*ix + ngy*iy - 1); + *(ptrd++) = veloc; + if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; + } else *(ptrd++) = 0; + } + if (veloc_max>0) *this+=(velocity*=time_step/veloc_max); + } + return *this; + } + + //! Compute distance function to 0-valued isophotes, using the Eikonal PDE \newinstance. + CImg get_distance_eikonal(const unsigned int nb_iterations, const float band_size=0, + const float time_step=0.5f) const { + return CImg(*this,false).distance_eikonal(nb_iterations,band_size,time_step); + } + + //! Compute Haar multiscale wavelet transform. + /** + \param axis Axis considered for the transform. + \param invert Set inverse of direct transform. + \param nb_scales Number of scales used for the transform. + **/ + CImg& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(axis,invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg 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.f); + if (nb_scales==1) { + switch (cimg::lowercase(axis)) { // Single scale transform + case 'x' : { + const unsigned int w = _width/2; + if (w) { + if ((w%2) && w!=1) + throw CImgInstanceException(_cimg_instance + "haar(): Sub-image width %u is not even.", + cimg_instance, + w); + + res.assign(_width,_height,_depth,_spectrum); + if (invert) cimg_forYZC(*this,y,z,c) { // Inverse transform along X + for (unsigned int x = 0, xw = w, x2 = 0; x& haar(const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(invert,nb_scales).move_to(*this); + } + + //! Compute Haar multiscale wavelet transform \newinstance. + CImg get_haar(const bool invert=false, const unsigned int nb_scales=1) const { + CImg res; + if (nb_scales==1) { // Single scale transform + if (_width>1) get_haar('x',invert,1).move_to(res); + if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); } + if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_to(res); } + if (res) return res; + } else { // Multi-scale transform + if (invert) { // Inverse transform + res.assign(*this,false); + if (_width>1) { + if (_height>1) { + if (_depth>1) { + unsigned int w = _width, h = _height, d = _depth; + for (unsigned int s = 1; w && h && d && s1) { + unsigned int w = _width, d = _depth; + for (unsigned int s = 1; w && d && s1) { + if (_depth>1) { + unsigned int h = _height, d = _depth; + for (unsigned int s = 1; h && d && s1) { + unsigned int d = _depth; + for (unsigned int s = 1; d && s1) { + if (_height>1) { + if (_depth>1) + for (unsigned int s = 1, w = _width/2, h = _height/2, d = _depth/2; w && h && d && s1) for (unsigned int s = 1, w = _width/2, d = _depth/2; w && d && s1) { + if (_depth>1) + for (unsigned int s = 1, h = _height/2, d = _depth/2; h && d && s1) for (unsigned int s = 1, d = _depth/2; d && s get_FFT(const char axis, const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],axis,is_invert); + return res; + } + + //! Compute n-d Fast Fourier Transform. + /* + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + CImgList get_FFT(const bool is_invert=false) const { + CImgList res(*this,CImg()); + CImg::FFT(res[0],res[1],is_invert); + return res; + } + + //! Compute 1D Fast Fourier Transform, along a specified axis. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param axis Axis along which the FFT is computed. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + **/ + static void FFT(CImg& real, CImg& imag, const char axis, const bool is_invert=false) { + if (!real) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part is empty.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImg<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); +#ifdef cimg_use_fftw3 + cimg::mutex(12); + fftw_complex *data_in; + fftw_plan data_plan; + + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the X-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forYZC(real,y,z,c) { + T *ptrr = real.data(0,y,z,c), *ptri = imag.data(0,y,z,c); + double *ptrd = (double*)data_in; + cimg_forX(real,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); } + fftw_execute(data_plan); + const unsigned int fact = real._width; + if (is_invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); } + else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); } + } + } break; + case 'y' : { // Fourier along Y, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Y-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._height), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const unsigned int off = real._width; + cimg_forXZC(real,x,z,c) { + T *ptrr = real.data(x,0,z,c), *ptri = imag.data(x,0,z,c); + double *ptrd = (double*)data_in; + cimg_forY(real,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._height; + if (is_invert) + cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + case 'z' : { // Fourier along Z, using FFTW library + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u) along the Z-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._depth), + real._width,real._height,real._depth,real._spectrum); + + data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const ulongT off = (ulongT)real._width*real._height; + cimg_forXYC(real,x,y,c) { + T *ptrr = real.data(x,y,0,c), *ptri = imag.data(x,y,0,c); + double *ptrd = (double*)data_in; + cimg_forZ(real,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = real._depth; + if (is_invert) + cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } + else cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } + } + } break; + default : + throw CImgArgumentException("CImgList<%s>::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } + fftw_destroy_plan(data_plan); + fftw_free(data_in); + cimg::mutex(12,0); +#else + switch (cimg::lowercase(axis)) { + case 'x' : { // Fourier along X, using built-in functions + const unsigned int N = real._width, N2 = N>>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the X-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forYZC(real,y,z,c) { + cimg::swap(real(i,y,z,c),real(j,y,z,c)); + cimg::swap(imag(i,y,z,c),imag(j,y,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = delta>>1; + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Y-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXZC(real,x,z,c) { + cimg::swap(real(x,i,z,c),real(x,j,z,c)); + cimg::swap(imag(x,i,z,c),imag(x,j,z,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i>1; + if (((N - 1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " + "have non 2^N dimension along the Z-axis.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum); + + for (unsigned int i = 0, j = 0; ii) cimg_forXYC(real,x,y,c) { + cimg::swap(real(x,y,i,c),real(x,y,j,c)); + cimg::swap(imag(x,y,i,c),imag(x,y,j,c)); + if (j=m; j-=m, m = n, n>>=1) {} + } + for (unsigned int delta = 2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i = 0; i::FFT(): Invalid specified axis '%c' for real and imaginary parts " + "(%u,%u,%u,%u) " + "(should be { x | y | z }).", + pixel_type(),axis, + real._width,real._height,real._depth,real._spectrum); + } +#endif + } + + //! Compute n-d Fast Fourier Transform. + /** + \param[in,out] real Real part of the pixel values. + \param[in,out] imag Imaginary part of the pixel values. + \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + \param nb_threads Number of parallel threads used for the computation. + Use \c 0 to set this to the number of available cpus. + **/ + static void FFT(CImg& real, CImg& imag, const bool is_invert=false, const unsigned int nb_threads=0) { + if (!real) + throw CImgInstanceException("CImgList<%s>::FFT(): Empty specified real part.", + pixel_type()); + + if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); + if (!real.is_sameXYZC(imag)) + throw CImgInstanceException("CImgList<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " + "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(), + real._width,real._height,real._depth,real._spectrum,real._data, + imag._width,imag._height,imag._depth,imag._spectrum,imag._data); + +#ifdef cimg_use_fftw3 + cimg::mutex(12); +#ifndef cimg_use_fftw3_singlethread + const unsigned int _nb_threads = nb_threads?nb_threads:cimg::nb_cpus(); + static int fftw_st = fftw_init_threads(); + cimg::unused(fftw_st); + fftw_plan_with_nthreads(_nb_threads); +#else + cimg::unused(nb_threads); +#endif + fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth); + if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " + "for computing FFT of image (%u,%u,%u,%u).", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width* + real._height*real._depth*real._spectrum), + real._width,real._height,real._depth,real._spectrum); + + fftw_plan data_plan; + const ulongT w = (ulongT)real._width, wh = w*real._height, whd = wh*real._depth; + data_plan = fftw_plan_dft_3d(real._width,real._height,real._depth,data_in,data_in, + is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forC(real,c) { + T *ptrr = real.data(0,0,0,c), *ptri = imag.data(0,0,0,c); + double *ptrd = (double*)data_in; + for (unsigned int x = 0; 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); +#endif + } + + //@} + //------------------------------------- + // + //! \name 3D Objects Management + //@{ + //------------------------------------- + + //! Shift 3D object's vertices. + /** + \param tx X-coordinate of the 3D displacement vector. + \param ty Y-coordinate of the 3D displacement vector. + \param tz Z-coordinate of the 3D displacement vector. + **/ + CImg& shift_object3d(const float tx, const float ty=0, const float tz=0) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "shift_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + get_shared_row(0)+=tx; get_shared_row(1)+=ty; get_shared_row(2)+=tz; + return *this; + } + + //! Shift 3D object's vertices \newinstance. + CImg 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. + /** + \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.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2; + return *this; + } + + //! Shift 3D object's vertices, so that it becomes centered \newinstance. + CImg get_shift_object3d() const { + return CImg(*this,false).shift_object3d(); + } + + //! Resize 3D object. + /** + \param sx Width of the 3D object's bounding box. + \param sy Height of the 3D object's bounding box. + \param sz Depth of the 3D object's bounding box. + **/ + CImg& resize_object3d(const float sx, const float sy=-100, const float sz=-100) { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + if (xm0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; } + if (ym0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; } + if (zm0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; } + return *this; + } + + //! 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. + CImg resize_object3d() { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "resize_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); + float + xm, xM = (float)xcoords.max_min(xm), + ym, yM = (float)ycoords.max_min(ym), + zm, zM = (float)zcoords.max_min(zm); + const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz); + if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; } + return *this; + } + + //! Resize 3D object to unit size \newinstance. + CImg get_resize_object3d() const { + return CImg(*this,false).resize_object3d(); + } + + //! Merge two 3D objects together. + /** + \param[in,out] primitives Primitives data of the current 3D object. + \param obj_vertices Vertices data of the additional 3D object. + \param obj_primitives Primitives data of the additional 3D object. + **/ + template + CImg& append_object3d(CImgList& primitives, const CImg& obj_vertices, + const CImgList& obj_primitives) { + if (!obj_vertices || !obj_primitives) return *this; + if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Specified vertice image (%u,%u,%u,%u,%p) is not a " + "set of 3D vertices.", + cimg_instance, + obj_vertices._width,obj_vertices._height, + obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data); + + if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); } + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "append_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + + const unsigned int P = _width; + append(obj_vertices,'x'); + const unsigned int N = primitives._width; + primitives.insert(obj_primitives); + for (unsigned int i = N; i &p = primitives[i]; + switch (p.size()) { + case 1 : p[0]+=P; break; // Point + case 5 : p[0]+=P; p[1]+=P; break; // Sphere + case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment + case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle + case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle + } + } + return *this; + } + + //! Texturize primitives of a 3D object. + /** + \param[in,out] primitives Primitives data of the 3D object. + \param[in,out] colors Colors data of the 3D object. + \param texture Texture image to map to 3D object. + \param coords Texture-mapping coordinates. + **/ + template + const CImg& texturize_object3d(CImgList& primitives, CImgList& colors, + const CImg& texture, const CImg& coords=CImg::const_empty()) const { + if (is_empty()) return *this; + if (_height!=3) + throw CImgInstanceException(_cimg_instance + "texturize_object3d(): image instance is not a set of 3D points.", + cimg_instance); + if (coords && (coords._width!=_width || coords._height!=2)) + throw CImgArgumentException(_cimg_instance + "texturize_object3d(): Invalid specified texture coordinates (%u,%u,%u,%u,%p).", + cimg_instance, + coords._width,coords._height,coords._depth,coords._spectrum,coords._data); + CImg _coords; + if (!coords) { // If no texture coordinates specified, do a default XY-projection + _coords.assign(_width,2); + float + xmin, xmax = (float)get_shared_row(0).max_min(xmin), + ymin, ymax = (float)get_shared_row(1).max_min(ymin), + dx = xmax>xmin?xmax-xmin:1, + dy = ymax>ymin?ymax-ymin:1; + cimg_forX(*this,p) { + _coords(p,0) = (int)(((*this)(p,0) - xmin)*texture._width/dx); + _coords(p,1) = (int)(((*this)(p,1) - ymin)*texture._height/dy); + } + } else _coords = coords; + + int texture_ind = -1; + cimglist_for(primitives,l) { + CImg &p = primitives[l]; + const unsigned int siz = p.size(); + switch (siz) { + case 1 : { // Point + const unsigned int i0 = (unsigned int)p[0]; + const int x0 = _coords(i0,0), y0 = _coords(i0,1); + texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0).move_to(colors[l]); + } break; + case 2 : case 6 : { // Line + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,x0,y0,x1,y1).move_to(p); + } break; + case 3 : case 9 : { // Triangle + const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p); + } break; + case 4 : case 12 : { // Quadrangle + const unsigned int + i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2], i3 = (unsigned int)p[3]; + const int + x0 = _coords(i0,0), y0 = _coords(i0,1), + x1 = _coords(i1,0), y1 = _coords(i1,1), + x2 = _coords(i2,0), y2 = _coords(i2,1), + x3 = _coords(i3,0), y3 = _coords(i3,1); + if (texture_ind<0) colors[texture_ind=l].assign(texture,false); + else colors[l].assign(colors[texture_ind],true); + CImg::vector(i0,i1,i2,i3,x0,y0,x1,y1,x2,y2,x3,y3).move_to(p); + } break; + } + } + return *this; + } + + //! Generate a 3D elevation of the image instance. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param[out] colors The returned list of the 3D object colors. + \param elevation The input elevation map. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + CImgList colors3d; + const CImg points3d = img.get_elevation3d(faces3d,colors3d,img.get_norm()*0.2); + CImg().display_object3d("Elevation3d",points3d,faces3d,colors3d); + \endcode + \image html ref_elevation3d.jpg + **/ + template + CImg get_elevation3d(CImgList& primitives, CImgList& colors, const CImg& elevation) const { + if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1) + throw CImgArgumentException(_cimg_instance + "get_elevation3d(): Instance and specified elevation (%u,%u,%u,%u,%p) " + "have incompatible dimensions.", + cimg_instance, + elevation._width,elevation._height,elevation._depth, + elevation._spectrum,elevation._data); + if (is_empty()) return *this; + float m, M = (float)max_min(m); + if (M==m) ++M; + colors.assign(); + const unsigned int size_x1 = _width - 1, size_y1 = _height - 1; + for (unsigned int y = 0; y1?((*this)(x,y,1) - m)*255/(M-m):r), + b = (unsigned char)(_spectrum>2?((*this)(x,y,2) - m)*255/(M-m):_spectrum>1?0:r); + CImg::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.f,_height - 1.f,_width,_height); + } + + //! Generate the 3D projection planes of the image instance. + /** + \param[out] primitives Primitives data of the returned 3D object. + \param[out] colors Colors data of the returned 3D object. + \param x0 X-coordinate of the projection point. + \param y0 Y-coordinate of the projection point. + \param z0 Z-coordinate of the projection point. + \param normalize_colors Tells if the created textures have normalized colors. + **/ + template + CImg get_projections3d(CImgList& primitives, CImgList& colors, + const unsigned int x0, const unsigned int y0, const unsigned int z0, + const bool normalize_colors=false) const { + float m = 0, M = 0, delta = 1; + if (normalize_colors) { m = (float)min_max(M); delta = 255/(m==M?1:M-m); } + const unsigned int + _x0 = (x0>=_width)?_width - 1:x0, + _y0 = (y0>=_height)?_height - 1:y0, + _z0 = (z0>=_depth)?_depth - 1:z0; + CImg img_xy, img_xz, img_yz; + if (normalize_colors) { + ((get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1)-=m)*=delta).move_to(img_xy); + ((get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_width,_depth,1,-100,-1). + move_to(img_xz); + ((get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_height,_depth,1,-100,-1). + move_to(img_yz); + } else { + get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1).move_to(img_xy); + get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1).move_to(img_xz); + get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).resize(_height,_depth,1,-100,-1).move_to(img_yz); + } + CImg points(12,3,1,1, + 0,_width - 1,_width - 1,0, 0,_width - 1,_width - 1,0, _x0,_x0,_x0,_x0, + 0,0,_height - 1,_height - 1, _y0,_y0,_y0,_y0, 0,_height - 1,_height - 1,0, + _z0,_z0,_z0,_z0, 0,0,_depth - 1,_depth - 1, 0,0,_depth - 1,_depth - 1); + primitives.assign(); + CImg::vector(0,1,2,3,0,0,img_xy._width - 1,0,img_xy._width - 1,img_xy._height - 1,0,img_xy._height - 1). + move_to(primitives); + CImg::vector(4,5,6,7,0,0,img_xz._width - 1,0,img_xz._width - 1,img_xz._height - 1,0,img_xz._height - 1). + move_to(primitives); + CImg::vector(8,9,10,11,0,0,img_yz._width - 1,0,img_yz._width - 1,img_yz._height - 1,0,img_yz._height - 1). + move_to(primitives); + colors.assign(); + img_xy.move_to(colors); + img_xz.move_to(colors); + img_yz.move_to(colors); + return points; + } + + //! Generate a isoline of the image instance as a 3D object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x The number of subdivisions along the X-axis. + \param size_y The number of subdisivions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img("reference.jpg"); + CImgList faces3d; + const CImg points3d = img.get_isoline3d(faces3d,100); + CImg().display_object3d("Isoline3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isoline3d.jpg + **/ + template + CImg get_isoline3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a scalar image.", + cimg_instance); + if (_depth>1) + throw CImgInstanceException(_cimg_instance + "get_isoline3d(): Instance is not a 2D image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.f,height() - 1.f,width(),height()); + } else { + const _functor2d_float func(*this); + 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. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param isovalue The returned list of the 3D object colors. + \param size_x Number of subdivisions along the X-axis. + \param size_y Number of subdisivions along the Y-axis. + \param size_z Number of subdisivions along the Z-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + const CImg img = CImg("reference.jpg").resize(-100,-100,20); + CImgList faces3d; + const CImg points3d = img.get_isosurface3d(faces3d,100); + CImg().display_object3d("Isosurface3d",points3d,faces3d,colors3d); + \endcode + \image html ref_isosurface3d.jpg + **/ + template + CImg get_isosurface3d(CImgList& primitives, const float isovalue, + const int size_x=-100, const int size_y=-100, const int size_z=-100) const { + if (_spectrum>1) + throw CImgInstanceException(_cimg_instance + "get_isosurface3d(): Instance is not a scalar image.", + cimg_instance); + primitives.assign(); + if (is_empty()) return *this; + CImg 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.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.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. + /** + \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 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 CImg isosurface3d(CImgList& primitives, const tfunc& func, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int size_x=32, const int size_y=32, const int size_z=32) { + static const unsigned int edges[256] = { + 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 + }; + + static const int triangles[256][16] = { + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 }, + { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 }, + { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 }, + { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 }, + { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 }, + { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 }, + { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, + { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, + { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, + { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 }, + { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, + { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 }, + { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, + { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 }, + { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, + { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 }, + { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, + { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 }, + { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 }, + { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, + { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 }, + { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 }, + { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 }, + { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 }, + { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, + { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 }, + { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 }, + { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, + { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 }, + { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 }, + { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, + { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 }, + { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, + { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, + { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 }, + { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 }, + { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 }, + { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, + { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 }, + { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 }, + { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, + { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 }, + { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, + { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, + { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, + { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 }, + { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 }, + { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, + { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 }, + { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 }, + { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, + { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 }, + { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, + { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 }, + { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, + { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 }, + { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 }, + { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, + { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, + { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 }, + { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, + { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 }, + { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, + { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, + { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, + { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 }, + { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 }, + { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 }, + { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, + { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 }, + { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, + { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 }, + { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, + { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 }, + { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, + { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 }, + { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, + { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, + { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, + { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, + { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 }, + { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, + { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 }, + { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, + { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 }, + { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 }, + { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, + { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 }, + { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, + { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 }, + { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 }, + { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, + { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } + }; + + const unsigned int + _nx = (unsigned int)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)), + _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)), + _nz = (unsigned int)(size_z>=0?size_z:cimg::round((z1-z0)*-size_z/100 + 1)), + nx = _nx?_nx:1, + ny = _ny?_ny:1, + nz = _nz?_nz:1, + nxm1 = nx - 1, + nym1 = ny - 1, + nzm1 = nz - 1; + primitives.assign(); + if (!nxm1 || !nym1 || !nzm1) return CImg(); + 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; + + // Fill the first plane with function values + Y = y0; + cimg_forY(values1,y) { + X = x0; + cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=dx; } + Y+=dy; + } + + // Run Marching Cubes algorithm + Z = z0; nZ = Z + dz; + for (unsigned int zi = 0; zi::vector(Xi,Y,Z).move_to(vertices); + } + if ((edge&2) && indices1(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val1)*dy/(val2-val1); + indices1(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,Z).move_to(vertices); + } + if ((edge&4) && indices1(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val3)*dx/(val2-val3); + indices1(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,Z).move_to(vertices); + } + if ((edge&8) && indices1(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val0)*dy/(val3-val0); + indices1(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,Z).move_to(vertices); + } + if ((edge&16) && indices2(xi,yi,0)<0) { + const float Xi = X + (isovalue-val4)*dx/(val5-val4); + indices2(xi,yi,0) = vertices.width(); + CImg::vector(Xi,Y,nZ).move_to(vertices); + } + if ((edge&32) && indices2(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val5)*dy/(val6-val5); + indices2(nxi,yi,1) = vertices.width(); + CImg::vector(nX,Yi,nZ).move_to(vertices); + } + if ((edge&64) && indices2(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val7)*dx/(val6-val7); + indices2(xi,nyi,0) = vertices.width(); + CImg::vector(Xi,nY,nZ).move_to(vertices); + } + if ((edge&128) && indices2(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val4)*dy/(val7-val4); + indices2(xi,yi,1) = vertices.width(); + CImg::vector(X,Yi,nZ).move_to(vertices); + } + if ((edge&256) && indices1(xi,yi,2)<0) { + const float Zi = Z+ (isovalue-val0)*dz/(val4-val0); + indices1(xi,yi,2) = vertices.width(); + CImg::vector(X,Y,Zi).move_to(vertices); + } + if ((edge&512) && indices1(nxi,yi,2)<0) { + const float Zi = Z + (isovalue-val1)*dz/(val5-val1); + indices1(nxi,yi,2) = vertices.width(); + CImg::vector(nX,Y,Zi).move_to(vertices); + } + if ((edge&1024) && indices1(nxi,nyi,2)<0) { + const float Zi = Z + (isovalue-val2)*dz/(val6-val2); + indices1(nxi,nyi,2) = vertices.width(); + CImg::vector(nX,nY,Zi).move_to(vertices); + } + if ((edge&2048) && indices1(xi,nyi,2)<0) { + const float Zi = Z + (isovalue-val3)*dz/(val7-val3); + indices1(xi,nyi,2) = vertices.width(); + CImg::vector(X,nY,Zi).move_to(vertices); + } + + // Create triangles + for (const int *triangle = triangles[configuration]; *triangle!=-1; ) { + const unsigned int + p0 = (unsigned int)*(triangle++), + p1 = (unsigned int)*(triangle++), + p2 = (unsigned int)*(triangle++); + const tf + i0 = (tf)(_isosurface3d_indice(p0,indices1,indices2,xi,yi,nxi,nyi)), + i1 = (tf)(_isosurface3d_indice(p1,indices1,indices2,xi,yi,nxi,nyi)), + i2 = (tf)(_isosurface3d_indice(p2,indices1,indices2,xi,yi,nxi,nyi)); + CImg::vector(i0,i2,i1).move_to(primitives); + } + } + } + } + cimg::swap(values1,values2); + cimg::swap(indices1,indices2); + } + return vertices>'x'; + } + + //! Compute isosurface of a function, as a 3D object \overloading. + template + static CImg isosurface3d(CImgList& primitives, const char *const expression, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const int dx=32, const int dy=32, const int dz=32) { + const _functor3d_expr func(expression); + return isosurface3d(primitives,func,isovalue,x0,y0,z0,x1,y1,z1,dx,dy,dz); + } + + template + static int _isosurface3d_indice(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) { + case 0 : return indices1(x,y,0); + case 1 : return indices1(nx,y,1); + case 2 : return indices1(x,ny,0); + case 3 : return indices1(x,y,1); + case 4 : return indices2(x,y,0); + case 5 : return indices2(nx,y,1); + case 6 : return indices2(x,ny,0); + case 7 : return indices2(x,y,1); + case 8 : return indices1(x,y,2); + case 9 : return indices1(nx,y,2); + case 10 : return indices1(nx,ny,2); + case 11 : return indices1(x,ny,2); + } + return 0; + } + + // Define functors for accessing image values (used in previous functions). + struct _functor2d_int { + const CImg& ref; + _functor2d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref((int)x,(int)y); + } + }; + + struct _functor2d_float { + const CImg& ref; + _functor2d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref._linear_atXY(x,y); + } + }; + + struct _functor2d_expr { + _cimg_math_parser *mp; + ~_functor2d_expr() { mp->end(); delete mp; } + _functor2d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y) const { + return (float)(*mp)(x,y,0,0); + } + }; + + struct _functor3d_int { + const CImg& ref; + _functor3d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref((int)x,(int)y,(int)z); + } + }; + + struct _functor3d_float { + const CImg& ref; + _functor3d_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref._linear_atXYZ(x,y,z); + } + }; + + struct _functor3d_expr { + _cimg_math_parser *mp; + ~_functor3d_expr() { mp->end(); delete mp; } + _functor3d_expr(const char *const expr):mp(0) { + mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); + } + float operator()(const float x, const float y, const float z) const { + return (float)(*mp)(x,y,z,0); + } + }; + + struct _functor4d_int { + const CImg& ref; + _functor4d_int(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z, const unsigned int c) const { + return (float)ref((int)x,(int)y,(int)z,c); + } + }; + + //! Generate a 3D box object. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the box (dimension along the X-axis). + \param size_y The height of the box (dimension along the Y-axis). + \param size_z The depth of the box (dimension along the Z-axis). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::box3d(faces3d,10,20,30); + CImg().display_object3d("Box3d",points3d,faces3d); + \endcode + \image html ref_box3d.jpg + **/ + template + static CImg box3d(CImgList& primitives, + const float size_x=200, const float size_y=100, const float size_z=100) { + primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5); + return CImg(8,3,1,1, + 0.,size_x,size_x, 0., 0.,size_x,size_x, 0., + 0., 0.,size_y,size_y, 0., 0.,size_y,size_y, + 0., 0., 0., 0.,size_z,size_z,size_z,size_z); + } + + //! Generate a 3D cone. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the cone basis. + \param size_z The cone's height. + \param subdivisions The number of basis angular subdivisions. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cone3d(faces3d,50); + CImg().display_object3d("Cone3d",points3d,faces3d); + \endcode + \image html ref_cone3d.jpg + **/ + template + static CImg cone3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,size_z, + 0.,0.,0.); + 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. + /** + \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). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::cylinder3d(faces3d,50); + CImg().display_object3d("Cylinder3d",points3d,faces3d); + \endcode + \image html ref_cylinder3d.jpg + **/ + template + static CImg cylinder3d(CImgList& primitives, + const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImgList vertices(2,1,3,1,1, + 0.,0.,0., + 0.,0.,size_z); + 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); + } + return vertices>'x'; + } + + //! Generate a 3D torus. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius1 The large radius. + \param radius2 The small radius. + \param subdivisions1 The number of angular subdivisions for the large radius. + \param subdivisions2 The number of angular subdivisions for the small radius. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::torus3d(faces3d,20,4); + CImg().display_object3d("Torus3d",points3d,faces3d); + \endcode + \image html ref_torus3d.jpg + **/ + template + static CImg torus3d(CImgList& primitives, + const float radius1=100, const float radius2=30, + const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) { + primitives.assign(); + if (!subdivisions1 || !subdivisions2) return CImg(); + 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); + } + } + return vertices>'x'; + } + + //! Generate a 3D XY-plane. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param size_x The width of the plane (dimension along the X-axis). + \param size_y The height of the plane (dimensions along the Y-axis). + \param subdivisions_x The number of planar subdivisions along the X-axis. + \param subdivisions_y The number of planar subdivisions along the Y-axis. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::plane3d(faces3d,100,50); + CImg().display_object3d("Plane3d",points3d,faces3d); + \endcode + \image html ref_plane3d.jpg + **/ + template + static CImg plane3d(CImgList& primitives, + const float size_x=100, const float size_y=100, + const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) { + primitives.assign(); + if (!subdivisions_x || !subdivisions_y) return CImg(); + CImgList vertices; + const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1; + const float fx = (float)size_x/w, fy = (float)size_y/h; + for (unsigned int y = 0; y::vector(fx*x,fy*y,0).move_to(vertices); + for (unsigned int y = 0; y::vector(off1,off4,off3,off2).move_to(primitives); + } + return vertices>'x'; + } + + //! Generate a 3D sphere. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param radius The radius of the sphere (dimension along the X-axis). + \param subdivisions The number of recursive subdivisions from an initial icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg points3d = CImg::sphere3d(faces3d,100,4); + CImg().display_object3d("Sphere3d",points3d,faces3d); + \endcode + \image html ref_sphere3d.jpg + **/ + template + static CImg sphere3d(CImgList& primitives, + const float radius=50, const unsigned int subdivisions=3) { + + // Create initial icosahedron + primitives.assign(); + 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); + // edge - length/2 + float he = (float)a; + + // Recurse subdivisions + for (unsigned int i = 0; i::vector(nx0,ny0,nz0).move_to(vertices); i0 = vertices.width() - 1; } + if (i1<0) { CImg::vector(nx1,ny1,nz1).move_to(vertices); i1 = vertices.width() - 1; } + if (i2<0) { CImg::vector(nx2,ny2,nz2).move_to(vertices); i2 = vertices.width() - 1; } + primitives.remove(0); + CImg::vector(p0,i0,i1).move_to(primitives); + CImg::vector((tf)i0,(tf)p1,(tf)i2).move_to(primitives); + CImg::vector((tf)i1,(tf)i2,(tf)p2).move_to(primitives); + CImg::vector((tf)i1,(tf)i0,(tf)i2).move_to(primitives); + } + } + return (vertices>'x')*=radius; + } + + //! Generate a 3D ellipsoid. + /** + \param[out] primitives The returned list of the 3D object primitives + (template type \e tf should be at least \e unsigned \e int). + \param tensor The tensor which gives the shape and size of the ellipsoid. + \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron. + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). + \par Example + \code + CImgList faces3d; + const CImg tensor = CImg::diagonal(10,7,3), + points3d = CImg::ellipsoid3d(faces3d,tensor,4); + CImg().display_object3d("Ellipsoid3d",points3d,faces3d); + \endcode + \image html ref_ellipsoid3d.jpg + **/ + template + static CImg ellipsoid3d(CImgList& primitives, + const CImg& tensor, const unsigned int subdivisions=3) { + primitives.assign(); + if (!subdivisions) return CImg(); + CImg S, V; + tensor.symmetric_eigen(S,V); + const float orient = + (V(0,1)*V(1,2) - V(0,2)*V(1,1))*V(2,0) + + (V(0,2)*V(1,0) - V(0,0)*V(1,2))*V(2,1) + + (V(0,0)*V(1,1) - V(0,1)*V(1,0))*V(2,2); + if (orient<0) { V(2,0) = -V(2,0); V(2,1) = -V(2,1); V(2,2) = -V(2,2); } + const float l0 = S[0], l1 = S[1], l2 = S[2]; + CImg 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. + /** + \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, + const CImgList& colors, + const to& opacities, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg& object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) { + return get_object3dtoCImg3d(primitives,colors,full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + 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. + CImg& object3dtoCImg3d(const bool full_check=true) { + return get_object3dtoCImg3d(full_check).move_to(*this); + } + + //! Convert 3D object into a CImg3d representation \newinstance. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool full_check=true) const { + 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).", + cimg_instance,_width,primitives._width,error_message.data()); + CImg res(1,_size_object3dtoCImg3d(primitives,colors,opacities)); + float *ptrd = res._data; + + // Put magick number. + *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f; + *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f; + + // Put number of vertices and primitives. + *(ptrd++) = cimg::uint2float(_width); + *(ptrd++) = cimg::uint2float(primitives._width); + + // Put vertex data. + if (is_empty() || !primitives) return res; + const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2); + cimg_forX(*this,p) { + *(ptrd++) = (float)*(ptrx++); + *(ptrd++) = (float)*(ptry++); + *(ptrd++) = (float)*(ptrz++); + } + + // Put primitive data. + cimglist_for(primitives,p) { + *(ptrd++) = (float)primitives[p].size(); + const tp *ptrp = primitives[p]._data; + cimg_foroff(primitives[p],i) *(ptrd++) = cimg::uint2float((unsigned int)*(ptrp++)); + } + + // Put color/texture data. + const unsigned int csiz = std::min(colors._width,primitives._width); + for (int c = 0; c<(int)csiz; ++c) { + const CImg& color = colors[c]; + const tc *ptrc = color._data; + if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; } + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (color.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImgList& opacities, float *ptrd) const { + cimglist_for(opacities,o) { + const CImg& opacity = opacities[o]; + const to *ptro = opacity._data; + if (opacity.size()==1) *(ptrd++) = (float)*ptro; + else { + *(ptrd++) = -128.f; + int shared_ind = -1; + if (opacity.is_shared()) for (int i = 0; i + float* _object3dtoCImg3d(const CImg& opacities, float *ptrd) const { + const to *ptro = opacities._data; + cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++); + return ptrd; + } + + template + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImgList& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + if (colors[c].is_shared()) siz+=4; + else { const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; } + } + if (colors._width + unsigned int _size_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const CImg& opacities) const { + unsigned int siz = 8U + 3*_width; + cimglist_for(primitives,p) siz+=primitives[p].size() + 1; + for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { + const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; + } + if (colors._width + CImg get_object3dtoCImg3d(const CImgList& primitives, + const CImgList& colors, + const bool full_check=true) const { + CImgList opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert 3D object into a CImg3d representation \overloading. + template + CImg get_object3dtoCImg3d(const CImgList& primitives, + const bool full_check=true) const { + CImgList colors, opacities; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! 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); + cimglist_for(primitives,p) primitives(p,0) = p; + return get_object3dtoCImg3d(primitives,colors,opacities,full_check); + } + + //! Convert CImg3d representation into a 3D object. + /** + \param[out] primitives Primitives data of the 3D object. + \param[out] colors Colors data of the 3D object. + \param[out] opacities Opacities data of the 3D object. + \param full_check Tells if full checking of the 3D object must be performed. + **/ + template + CImg& CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) { + return get_CImg3dtoobject3d(primitives,colors,opacities,full_check).move_to(*this); + } + + //! Convert CImg3d representation into a 3D object \newinstance. + template + CImg get_CImg3dtoobject3d(CImgList& primitives, + CImgList& colors, + CImgList& opacities, + const bool full_check=true) const { + CImg error_message(1024); + if (!is_CImg3d(full_check,error_message)) + throw CImgInstanceException(_cimg_instance + "CImg3dtoobject3d(): image instance is not a CImg3d (%s).", + cimg_instance,error_message.data()); + const T *ptrs = _data + 6; + const unsigned int + nb_points = cimg::float2uint((float)*(ptrs++)), + nb_primitives = cimg::float2uint((float)*(ptrs++)); + const CImg points = CImg(ptrs,3,nb_points,1,1,true).get_transpose(); + ptrs+=3*nb_points; + primitives.assign(nb_primitives); + cimglist_for(primitives,p) { + const unsigned int nb_inds = (unsigned int)*(ptrs++); + primitives[p].assign(1,nb_inds); + tp *ptrp = primitives[p]._data; + for (unsigned int i = 0; i + 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 int nx0 = x0>0?x0:0, nx1 = x1=0) { + const tc *col = color; + const ulongT off = whd - dx - 1; + T *ptrd = data(nx0,y); + if (opacity>=1) { // ** Opaque drawing ** + if (brightness==1) { // Brightness==1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)*(col++); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)*(col++); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else if (brightness<1) { // Brightness<1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)(*(col++)*brightness); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } else { // Brightness>1 + if (sizeof(T)!=1) cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forC(*this,c) { + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + std::memset(ptrd,(int)val,dx + 1); + ptrd+=whd; + } + } + } else { // ** Transparent drawing ** + if (brightness==1) { // Brightness==1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else if (brightness<=1) { // Brightness<1 + cimg_forC(*this,c) { + const Tfloat val = *(col++)*brightness*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else { // Brightness>1 + cimg_forC(*this,c) { + const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*maxval)*nopacity; + for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } + } + } + return *this; + } + + //! Draw a 3D point. + /** + \param x0 X-coordinate of the point. + \param y0 Y-coordinate of the point. + \param z0 Z-coordinate of the point. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \note + - To set pixel values without clipping needs, you should use the faster CImg::operator()() function. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_point(50,50,color); + \endcode + **/ + template + CImg& draw_point(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_point(): Specified color is (null).", + cimg_instance); + if (x0>=0 && y0>=0 && z0>=0 && x0=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + return *this; + } + + //! Draw a 2D point \simplification. + template + CImg& draw_point(const int x0, const int y0, + const tc *const color, const float opacity=1) { + return draw_point(x0,y0,0,color,opacity); + } + + // Draw a points cloud. + /** + \param points Image of vertices coordinates. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_point(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_point(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),color,opacity); + } break; + default : { + cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),(int)points(i,2),color,opacity); + } + } + return *this; + } + + //! Draw a 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + \note + - Line routine uses Bresenham's algorithm. + - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,color); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; } + } + 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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 2D line, with z-buffering. + /** + \param zbuffer Zbuffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_line(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz && pattern&hatch) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 3D line. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if a reinitialization of the hash state must be done. + **/ + template + 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.f + nx1 - nx0; + ny0-=(int)((float)nx0*(1.f + ny1 - ny0)/D); + nz0-=(int)((float)nx0*(1.f + nz1 - nz0)/D); + nx0 = 0; + } + if (nx1>=width()) { + const float d = (float)nx1 - width(), D = 1.f + nx1 - nx0; + ny1+=(int)(d*(1.f + ny0 - ny1)/D); + nz1+=(int)(d*(1.f + 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.f + ny1 - ny0; + nx0-=(int)((float)ny0*(1.f + nx1 - nx0)/D); + nz0-=(int)((float)ny0*(1.f + nz1 - nz0)/D); + ny0 = 0; + } + if (ny1>=height()) { + const float d = (float)ny1 - height(), D = 1.f + ny1 - ny0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + nz1+=(int)(d*(1.f + 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.f + nz1 - nz0; + nx0-=(int)((float)nz0*(1.f + nx1 - nx0)/D); + ny0-=(int)((float)nz0*(1.f + ny1 - ny0)/D); + nz0 = 0; + } + if (nz1>=depth()) { + const float d = (float)nz1 - depth(), D = 1.f + nz1 - nz0; + nx1+=(int)(d*(1.f + nx0 - nx1)/D); + ny1+=(int)(d*(1.f + 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.f); + for (unsigned int t = 0; t<=dmax; ++t) { + if (!(~pattern) || (~pattern && pattern&hatch)) { + T* ptrd = data((unsigned int)x,(unsigned int)y,(unsigned int)z); + const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } + } + x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } + } + } + return *this; + } + + //! Draw a textured 2D line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + \note + - Line routine uses the well known Bresenham's algorithm. + \par Example: + \code + CImg img(100,100,1,3,0), texture("texture256x256.ppm"); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,texture,0,0,255,255); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + 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()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + if (pattern&hatch) { + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + const tc *col = &texture._atXY(tx,ty); + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction. + /** + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(const int x0, const int y0, const float z0, + const int x1, const 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 (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a textured 2D line, with perspective correction and z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the starting point. + \param y0 Y-coordinate of the starting point. + \param z0 Z-coordinate of the starting point + \param x1 X-coordinate of the ending point. + \param y1 Y-coordinate of the ending point. + \param z1 Z-coordinate of the ending point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch Tells if the hash variable must be reinitialized. + **/ + template + CImg& draw_line(CImg& zbuffer, + const int x0, const int y0, const float z0, + const int x1, const 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_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=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; + + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const tzfloat z = Z0 + x*dz/ndx; + if (z>=(tzfloat)*ptrz) { + *ptrz = (tz)z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); + T *ptrd = ptrd0; + cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a set of consecutive lines. + /** + \param points Coordinates of vertices, stored as a list of vectors. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If set to true, init hatch motif. + \note + - This function uses several call to the single CImg::draw_line() procedure, + depending on the vectors size in \p points. + **/ + template + 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 : + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i + CImg& draw_arrow(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const float angle=30, const float length=-10, + const unsigned int pattern=~0U) { + if (is_empty()) return *this; + const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v, + deg = (float)(angle*cimg::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.f, + l = (length>=0)?length:-length*(float)std::sqrt(sq)/100; + if (sq>0) { + const float + cl = (float)std::cos(ang - deg), sl = (float)std::sin(ang - deg), + cr = (float)std::cos(ang + deg), sr = (float)std::sin(ang + deg); + const int + xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl), + xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr), + xc = x1 + (int)((l + 1)*(cl + cr))/2, yc = y1 + (int)((l + 1)*(sl + sr))/2; + draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity); + } else draw_point(x0,y0,color,opacity); + return *this; + } + + //! Draw a 2D spline. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + \note + - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points + and corresponding velocity vectors. + - The spline is drawn as a serie of connected segments. The \p precision parameter sets the + average number of pixels in each drawn segment. + - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), + (\p xb,\p yb), (\p x1,\p y1) } where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point + and (\p xa,\p ya), (\p xb,\p yb) are two + \e control points. + The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from + the control points as + \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb). + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,255,255 }; + img.draw_spline(30,30,0,100,90,40,0,-100,color); + \endcode + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const tc *const color, const float opacity=1, + const float precision=0.25, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Specified color is (null).", + cimg_instance); + if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0; + for (float t = 0; t<1; t+=_precision) { + const float t2 = t*t, t3 = t2*t; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t + x0), + ny = (int)(ay*t3 + by*t2 + v0*t + y0); + draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; + } + return draw_line(ox,oy,x1,y1,color,opacity,pattern,false); + } + + //! Draw a 3D spline \overloading. + /** + \note + - Similar to CImg::draw_spline() for a 3D spline in a volumetric image. + **/ + template + 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. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param texture Texture image defining line pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param precision Curve drawing precision. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch if \c true, reinit hatch motif. + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const CImg& texture, + const int tx0, const int ty0, const int tx1, const int ty1, + const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_empty()) return *this; + if (is_overlapped(texture)) + return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch); + if (x0==x1 && y0==y1) + return draw_point(x0,y0,texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0),opacity); + bool ninit_hatch = init_hatch; + const float + ax = u0 + u1 + 2*(x0 - x1), + bx = 3*(x1 - x0) - 2*u0 - u1, + ay = v0 + v1 + 2*(y0 - y1), + by = 3*(y1 - y0) - 2*v0 - v1, + _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); + int ox = x0, oy = y0, otx = tx0, oty = ty0; + for (float t1 = 0; t1<1; t1+=_precision) { + const float t2 = t1*t1, t3 = t2*t1; + const int + nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0), + ny = (int)(ay*t3 + by*t2 + v0*t1 + y0), + ntx = tx0 + (int)((tx1 - tx0)*t1), + nty = ty0 + (int)((ty1 - ty0)*t1); + draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = nx; oy = ny; otx = ntx; oty = nty; + } + return draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false); + } + + //! Draw a set of consecutive splines. + /** + \param points Vertices data. + \param tangents Tangents data. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param is_closed_set Tells if the drawn spline set is closed. + \param precision Precision of the drawing. + \param pattern An integer whose bits describe the line pattern. + \param init_hatch If \c true, init hatch motif. + **/ + template + CImg& draw_spline(const CImg& points, const CImg& tangents, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || !tangents || points._width<2 || tangents._width<2) return *this; + bool ninit_hatch = init_hatch; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1); + int ox = x0, oy = y0; + float ou = u0, ov = v0; + for (unsigned int i = 1; i + CImg& draw_spline(const CImg& points, + const tc *const color, const float opacity=1, + const bool is_closed_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() || !points || points._width<2) return *this; + CImg tangents; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum,points._data); + case 2 : { + tangents.assign(points._width,points._height); + cimg_forX(points,p) { + const unsigned int + p0 = is_closed_set?(p + points._width - 1)%points._width:(p?p - 1:0), + p1 = is_closed_set?(p + 1)%points._width:(p + 1=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, + 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); + } + return *this; + } + + //! Draw a filled 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1); + return *this; + } + + //! Draw a outlined 2D triangle. + /** + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + draw_line(x0,y0,x1,y1,color,opacity,pattern,true). + draw_line(x1,y1,x2,y2,color,opacity,pattern,false). + draw_line(x2,y2,x0,y0,color,opacity,pattern,false); + return *this; + } + + //! Draw a filled 2D triangle, with z-buffering. + /** + \param zbuffer Z-buffer image. + \param x0 X-coordinate of the first vertex. + \param y0 Y-coordinate of the first vertex. + \param z0 Z-coordinate of the first vertex. + \param x1 X-coordinate of the second vertex. + \param y1 Y-coordinate of the second vertex. + \param z1 Z-coordinate of the second vertex. + \param x2 X-coordinate of the third vertex. + \param y2 Y-coordinate of the third vertex. + \param z2 Z-coordinate of the third vertex. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param brightness Brightness factor. + **/ + template + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + } + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param brightness0 Brightness factor of the first vertex (in [0,2]). + \param brightness1 brightness factor of the second vertex (in [0,2]). + \param brightness2 brightness factor of the third vertex (in [0,2]). + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + } + return *this; + } + + //! Draw a Gouraud-shaded 2D triangle, with z-buffering \overloading. + template + 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, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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; + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a color-interpolated 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color1 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the first vertex. + \param color2 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the seconf vertex. + \param color3 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the third vertex. + \param opacity Drawing opacity. + **/ + template + 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, + const float opacity=1) { + const unsigned char one = 1; + cimg_forC(*this,c) + get_shared_channel(c).draw_triangle(x0,y0,x1,y1,x2,y2,&one,color1[c],color2[c],color3[c],opacity); + return *this; + } + + //! Draw a textured 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param opacity Drawing opacity. + \param brightness Brightness factor of the drawing (in [0,2]). + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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 opacity=1, + const float brightness=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y, + nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + } + return *this; + } + + //! Draw a 2D textured triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + nbrightness = cimg::cut(brightness,0,2); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int xleft = xleft0, xright = xright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xright=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)*col; + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), + 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; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nbrightness**col); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval)); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + } + return *this; + } + + //! Draw a Phong-shaded 2D triangle, with z-buffering. + template + 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, + 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, + 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 + "draw_triangle(): Specified color is (null).", + cimg_instance); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; pzl = pzn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + *ptrd = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tc *col = color; + const tl *lig = &light._atXY(lxleft,lyleft); + cimg_forC(*this,c) { + const tl l = *lig; + const tc cval = *(col++); + const T val = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param brightness0 Brightness factor of the first vertex. + \param brightness1 Brightness factor of the second vertex. + \param brightness2 Brightness factor of the third vertex. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const 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, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y, + nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction \overloading. + template + 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, + 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, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright - cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc - (dx?dx*(dc/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errc = dx>>1; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + zleft-=xleft*(zright - zleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction and z-buffering \overloading. + template + 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, + 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, + 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 + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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.f?0.f:(brightness0>2.f?2.f:brightness0))*256.f), + nc1 = (int)((brightness1<0.f?0.f:(brightness1>2.f?2.f:brightness1))*256.f), + nc2 = (int)((brightness2<0.f?0.f:(brightness2>2.f?2.f:brightness2))*256.f); + 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)); + cimg_forC(*this,c) { + *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forC(*this,c) { + const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle. + /** + \param x0 X-coordinate of the first vertex in the image instance. + \param y0 Y-coordinate of the first vertex in the image instance. + \param x1 X-coordinate of the second vertex in the image instance. + \param y1 Y-coordinate of the second vertex in the image instance. + \param x2 X-coordinate of the third vertex in the image instance. + \param y2 Y-coordinate of the third vertex in the image instance. + \param texture Texture image used to fill the triangle. + \param tx0 X-coordinate of the first vertex in the texture image. + \param ty0 Y-coordinate of the first vertex in the texture image. + \param tx1 X-coordinate of the second vertex in the texture image. + \param ty1 Y-coordinate of the second vertex in the texture image. + \param tx2 X-coordinate of the third vertex in the texture image. + \param ty2 Y-coordinate of the third vertex in the texture image. + \param light Light image. + \param lx0 X-coordinate of the first vertex in the light image. + \param ly0 Y-coordinate of the first vertex in the light image. + \param lx1 X-coordinate of the second vertex in the light image. + \param ly1 Y-coordinate of the second vertex in the light image. + \param lx2 X-coordinate of the third vertex in the light image. + \param ly2 Y-coordinate of the third vertex in the light image. + \param opacity Drawing opacity. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2); + if (ny0>=height() || ny2<0) return *this; + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y, + nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (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; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = &texture._atXY(txleft,tyleft); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd - 1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (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 float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + zleft-=xleft*(zright - zleft)/dx; + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=width() - 1) xright = width() - 1; + T* ptrd = data(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a textured Phong-shaded 2D triangle, with perspective correction and z-buffering. + template + 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, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + typedef typename cimg::superset::type tzfloat; + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!is_sameXY(zbuffer)) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " + "different dimensions.", + cimg_instance, + zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); + if (texture._depth>1 || texture._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", + cimg_instance, + texture._width,texture._height,texture._depth,texture._spectrum,texture._data); + if (light._depth>1 || light._spectrum<_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", + cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); + if (is_overlapped(texture)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT + whd = (ulongT)_width*_height*_depth, + twh = (ulongT)texture._width*texture._height, + lwh = (ulongT)light._width*light._height, + offx = _spectrum*whd; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2; + tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=height() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))): + (ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))): + (ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + tzfloat + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + const bool is_bump = texture._spectrum>=_spectrum + 2; + const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); + + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr; + tzfloat zleft = zl, zright = zr; + if (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); + cimg_forC(*this,c) { + const tl l = *lig; + *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>=(tzfloat)*ptrz) { + *ptrz = (tz)zleft; + const tzfloat invz = 1/zleft; + const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); + const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; + const tl *lig = &light._atXY(lxleft + bx,lyleft + by); + cimg_forC(*this,c) { + const tl l = *lig; + const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whd; col+=twh; lig+=lwh; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a filled 4D rectangle. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param z0 Z-coordinate of the upper-left rectangle corner. + \param c0 C-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param z1 Z-coordinate of the lower-right rectangle corner. + \param c1 C-coordinate of the lower-right rectangle corner. + \param val Scalar value used to fill the rectangle area. + \param opacity Drawing opacity. + **/ + CImg& draw_rectangle(const int x0, const int y0, const int z0, const int c0, + const int x1, const int y1, const int z1, const int c1, + const T val, const float opacity=1) { + if (is_empty()) return *this; + const int + nx0 = x0=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.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; 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=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_rectangle(): Specified color is (null).", + cimg_instance); + cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,(T)color[c],opacity); + return *this; + } + + //! Draw an outlined 3D rectangle \overloading. + template + 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. + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1) { + return draw_rectangle(x0,y0,0,x1,y1,_depth - 1,color,opacity); + } + + //! Draw a outlined 2D rectangle \overloading. + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true); + if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true); + const int + nx0 = x0 + CImg& draw_polygon(const CImg& points, + const tc *const color, const float opacity=1) { + if (is_empty() || !points) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Specified color is (null).", + cimg_instance); + if (points._width==1) return draw_point((int)points(0,0),(int)points(0,1),color,opacity); + if (points._width==2) return draw_line((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1),color,opacity); + if (points._width==3) return draw_triangle((int)points(0,0),(int)points(0,1), + (int)points(1,0),(int)points(1,1), + (int)points(2,0),(int)points(2,1),color,opacity); + cimg_init_scanline(color,opacity); + int + xmin = 0, ymin = 0, + xmax = points.get_shared_row(0).max_min(xmin), + ymax = points.get_shared_row(1).max_min(ymin); + if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this; + if (ymin==ymax) return draw_line(xmin,ymin,xmax,ymax,color,opacity); + + ymin = std::max(0,ymin); + ymax = std::min(height() - 1,ymax); + CImg Xs(points._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 tn = an; + while (points(tn,1)==y1) (tn+=1)%=points._width; + + if (y0!=y1) { + const int + y2 = (int)points(tn,1), + x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1, + dy = cimg::sign(y01), + tmax = std::max(1,cimg::abs(y01)), + tend = tmax - (dy==cimg::sign(y12)); + unsigned int y = (unsigned int)y0 - ymin; + for (int t = 0; t<=tend; ++t, y+=dy) + if (yn; + n = nn; + nn = an; + } + + cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>=(cimg_openmp_sizefactor)*32)) + 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, + const tc *const color, const float opacity, const unsigned int pattern) { + if (is_empty() || !points || points._width<3) return *this; + bool ninit_hatch = true; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Invalid specified point set.", + cimg_instance); + case 2 : { // 2D version + CImg 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 + 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); + } + + //! Draw a filled 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity=1) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity); + } + + //! Draw an outlined 2D ellipse. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param r1 First radius of the ellipse. + \param r2 Second radius of the ellipse. + \param angle Angle of the first radius. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + 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); + return *this; + } + + //! Draw an outlined 2D ellipse \overloading. + /** + \param x0 X-coordinate of the ellipse center. + \param y0 Y-coordinate of the ellipse center. + \param tensor Diffusion tensor describing the ellipse. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern An integer whose bits describe the outline pattern. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity, + const unsigned int pattern) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)), + std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, + color,opacity,pattern); + } + + template + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_ellipse(): Specified color is (null).", + cimg_instance); + if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity); + if (r1==r2 && (float)(int)r1==r1) { + if (pattern) return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity,pattern); + else return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity); + } + cimg_init_scanline(color,opacity); + const float + nr1 = cimg::abs(r1) - 0.5, nr2 = cimg::abs(r2) - 0.5, + nangle = (float)(angle*cimg::PI/180), + u = (float)std::cos(nangle), + v = (float)std::sin(nangle), + rmax = std::max(nr1,nr2), + l1 = (float)std::pow(rmax/(nr1>0?nr1:1e-6),2), + l2 = (float)std::pow(rmax/(nr2>0?nr2:1e-6),2), + a = l1*u*u + l2*v*v, + b = u*v*(l1 - l2), + c = l1*v*v + l2*u*u; + const int + yb = (int)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 + Y = y - y0 + (y0?(float)std::sqrt(delta)/a:0.f, + 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 + 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 (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + cimg_init_scanline(color,opacity); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (y0>=0 && y0=0) { + const int x1 = x0 - x, x2 = x0 + x, y1 = y0 - y, y2 = y0 + y; + if (y1>=0 && y1=0 && y2=0 && y1=0 && y2 + 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 (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_circle(): Specified color is (null).", + cimg_instance); + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this; + if (!radius) return draw_point(x0,y0,color,opacity); + draw_point(x0 - radius,y0,color,opacity).draw_point(x0 + radius,y0,color,opacity). + draw_point(x0,y0 - radius,color,opacity).draw_point(x0,y0 + radius,color,opacity); + if (radius==1) return *this; + for (int f = 1 - radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x=0) { f+=(ddFy+=2); --y; } + ++x; ++(f+=(ddFx+=2)); + if (x!=y + 1) { + const int x1 = x0 - y, x2 = x0 + y, y1 = y0 - x, y2 = y0 + x, + x3 = x0 - x, x4 = x0 + x, y3 = y0 - y, y4 = y0 + y; + draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity). + draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity); + if (x!=y) + draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity). + draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity); + } + } + return *this; + } + + //! Draw an image. + /** + \param sprite Sprite image. + \param x0 X-coordinate of the sprite position. + \param y0 Y-coordinate of the sprite position. + \param z0 Z-coordinate of the sprite position. + \param c0 C-coordinate of the sprite position. + \param opacity Drawing opacity. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const t + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; x& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const float opacity=1) { + if (is_empty() || !sprite) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); + if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) + return assign(sprite,false); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const T + *ptrs = sprite._data + + (bx?-x0:0) + + (by?-y0*(ulongT)sprite.width():0) + + (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + + (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ), + slX = lX*sizeof(T); + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + 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; y + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,z0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,0,sprite,opacity); + } + + //! Draw an image \overloading. + template + CImg& draw_image(const CImg& sprite, const float opacity=1) { + return draw_image(0,sprite,opacity); + } + + //! Draw a masked image. + /** + \param sprite Sprite image. + \param mask Mask image. + \param x0 X-coordinate of the sprite position in the image instance. + \param y0 Y-coordinate of the sprite position in the image instance. + \param z0 Z-coordinate of the sprite position in the image instance. + \param c0 C-coordinate of the sprite position in the image instance. + \param mask_max_value Maximum pixel value of the mask image \c mask. + \param opacity Drawing opacity. + \note + - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite. + - Dimensions along x,y and z of \p sprite and \p mask must be the same. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int c0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + if (is_empty() || !sprite || !mask) return *this; + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_max_value); + if (is_overlapped(mask)) return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_max_value); + if (mask._width!=sprite._width || mask._height!=sprite._height || mask._depth!=sprite._depth) + throw CImgArgumentException(_cimg_instance + "draw_image(): Sprite (%u,%u,%u,%u,%p) and mask (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + sprite._width,sprite._height,sprite._depth,sprite._spectrum,sprite._data, + mask._width,mask._height,mask._depth,mask._spectrum,mask._data); + + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + const int + lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), + lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), + lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), + lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); + const ulongT + coff = (bx?-x0:0) + + (by?-y0*(ulongT)mask.width():0) + + (bz?-z0*(ulongT)mask.width()*mask.height():0) + + (bc?-c0*(ulongT)mask.width()*mask.height()*mask.depth():0), + ssize = (ulongT)mask.width()*mask.height()*mask.depth()*mask.spectrum(); + const ti *ptrs = sprite._data + coff; + const tm *ptrm = mask._data + coff; + const ulongT + offX = (ulongT)_width - lX, + soffX = (ulongT)sprite._width - lX, + offY = (ulongT)_width*(_height - lY), + soffY = (ulongT)sprite._width*(sprite._height - lY), + offZ = (ulongT)_width*_height*(_depth - lZ), + soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); + if (lX>0 && lY>0 && lZ>0 && lC>0) { + T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0); + for (int c = 0; c + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,y0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a image \overloading. + template + CImg& draw_image(const int x0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(x0,0,sprite,mask,opacity,mask_max_value); + } + + //! Draw an image. + template + CImg& draw_image(const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_max_value=1) { + return draw_image(0,sprite,mask,opacity,mask_max_value); + } + + //! Draw a text string. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Pointer to \c spectrum() consecutive values, defining the foreground drawing color. + \param background_color Pointer to \c spectrum() consecutive values, defining the background drawing color. + \param opacity Drawing opacity. + \param font Font used for drawing text. + **/ + template + CImg& 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent background is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \note A transparent foreground is used for the text. + **/ + template + 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; + 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); + } + + //! Draw a text string \overloading. + /** + \param x0 X-coordinate of the text in the image instance. + \param y0 Y-coordinate of the text in the image instance. + \param text Format of the text ('printf'-style format string). + \param foreground_color Array of spectrum() values of type \c T, + defining the foreground color (0 means 'transparent'). + \param background_color Array of spectrum() values of type \c T, + defining the background color (0 means 'transparent'). + \param opacity Drawing opacity. + \param font_height Height of the text font (exact match for 13,23,53,103, interpolated otherwise). + **/ + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc1 *const foreground_color, const tc2 *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + 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; + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const tc *const foreground_color, const int background_color=0, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + cimg::unused(background_color); + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",foreground_color,(const tc*)0,opacity,font_height,tmp._data); + } + + //! Draw a text string \overloading. + template + CImg& draw_text(const int x0, const int y0, + const char *const text, + const int, const tc *const background_color, + const float opacity=1, const unsigned int font_height=13, ...) { + if (!font_height) return *this; + CImg tmp(2048); + std::va_list ap; va_start(ap,font_height); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + return draw_text(x0,y0,"%s",(tc*)0,background_color,opacity,font_height,tmp._data); + } + + template + CImg& _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, + const bool is_native_font) { + if (!text) return *this; + if (!font) + throw CImgArgumentException(_cimg_instance + "draw_text(): Empty specified font.", + cimg_instance); + + const unsigned int text_length = (unsigned int)std::strlen(text); + if (is_empty()) { + // If needed, pre-compute necessary size of the image + int x = 0, y = 0, w = 0; + unsigned char c = 0; + for (unsigned int i = 0; iw) w = x; x = 0; break; + case '\t' : x+=4*font[' ']._width; break; + default : if (cw) w=x; + y+=font[0]._height; + } + assign(x0 + w,y0 + y,1,is_native_font?1:font[0]._spectrum,(T)0); + } + + int x = x0, y = y0; + for (unsigned int i = 0; i letter = font[c]; + if (letter) { + if (is_native_font && _spectrum>letter._spectrum) letter.resize(-100,-100,1,_spectrum,0,2); + const unsigned int cmin = std::min(_spectrum,letter._spectrum); + if (foreground_color) + for (unsigned int c = 0; c& __draw_text(const char *const text, const bool 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 label, labelmask; + const unsigned char labelcolor = 127; + const unsigned int fsize = 13; + label.draw_text(0,0,"%s",&labelcolor,0,1,fsize,tmp._data); + if (label) { + label.crop(2,0,label.width() - 1,label.height()); + ((labelmask = label)+=label.get_dilate(5)).max(80); + (label*=2).resize(-100,-100,1,3,1); + return draw_image(0,is_down?height() - fsize:0,label,labelmask,1,254); + } + return *this; + } + + //! Draw a 2D vector field. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Drawing opacity. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const t2 *const color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + return draw_quiver(flow,CImg(color,_spectrum,1,1,1,true),opacity,sampling,factor,is_arrow,pattern); + } + + //! Draw a 2D vector field, using a field of colors. + /** + \param flow Image of 2D vectors used as input data. + \param color Image of spectrum()-D vectors corresponding to the color of each arrow. + \param opacity Opacity of the drawing. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param is_arrow Tells if arrows must be drawn, instead of oriented segments. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const CImg& color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const bool is_arrow=true, const unsigned int pattern=~0U) { + if (is_empty()) return *this; + if (!flow || flow._spectrum!=2) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid dimensions of specified flow (%u,%u,%u,%u,%p).", + cimg_instance, + flow._width,flow._height,flow._depth,flow._spectrum,flow._data); + if (sampling<=0) + throw CImgArgumentException(_cimg_instance + "draw_quiver(): Invalid sampling value %g " + "(should be >0)", + cimg_instance, + sampling); + const bool colorfield = (color._width==flow._width && color._height==flow._height && + color._depth==1 && color._spectrum==_spectrum); + if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,is_arrow,pattern); + float vmax,fact; + if (factor<=0) { + float m, M = (float)flow.get_norm(2).max_min(m); + vmax = (float)std::max(cimg::abs(m),cimg::abs(M)); + if (!vmax) vmax = 1; + fact = -factor; + } else { fact = factor; vmax = 1; } + + for (unsigned int y = sampling/2; y<_height; y+=sampling) + for (unsigned int x = sampling/2; x<_width; x+=sampling) { + const unsigned int X = x*flow._width/_width, Y = y*flow._height/_height; + float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax; + if (is_arrow) { + const int xx = (int)(x + u), yy = (int)(y + v); + if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y)._data,opacity,45,sampling/5.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), + color.get_vector_at(X,Y)._data,opacity,pattern); + else draw_line((int)(x - 0.5*u),(int)(y - 0.5*v),(int)(x + 0.5*u),(int)(y + 0.5*v), + color._data,opacity,pattern); + } + } + return *this; + } + + //! Draw a labeled horizontal axis. + /** + \param values_x Values along the horizontal axis. + \param y Y-coordinate of the horizontal axis in the image instance. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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 + draw_line(0,y,_width - 1,y,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_x); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _xt = (width() - label.width())/2, + xt = _xt<3?3:_xt + label.width()>=width() - 2?width() - 3 - label.width():_xt; + draw_point(width()/2,y - 1,color,opacity).draw_point(width()/2,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_x[0]=width() - 2?width() - 3 - label.width():_xt; + draw_point(xi,y - 1,color,opacity).draw_point(xi,y + 1,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw a labeled vertical axis. + /** + \param x X-coordinate of the vertical axis in the image instance. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + if (is_empty()) return *this; + int siz = (int)values_y.size() - 1; + CImg txt(32); + CImg label; + if (siz<=0) { // Degenerated case + draw_line(x,0,x,_height - 1,color,opacity,pattern); + if (!siz) { + cimg_snprintf(txt,txt._width,"%g",(double)*values_y); + label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); + const int + _yt = (height() - label.height())/2, + yt = _yt<0?0:_yt + label.height()>=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,height()/2,color,opacity).draw_point(x + 1,height()/2,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } else { // Regular case + if (values_y[0]=height()?height() - 1-label.height():_yt, + _xt = x - 2 - label.width(), + xt = _xt>=0?_xt:x + 3; + draw_point(x - 1,yi,color,opacity).draw_point(x + 1,yi,color,opacity); + if (allow_zero || *txt!='0' || txt[1]!=0) + draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes. + /** + \param values_x Values along the X-axis. + \param values_y Values along the Y-axis. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for the X-axis. + \param pattern_y Drawing pattern for the Y-axis. + \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). + \param allow_zero Enable/disable the drawing of label '0' if found. + **/ + template + 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) { + 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; + if (sizx>=0) { + float ox = (float)*nvalues_x; + for (unsigned int x = sizx?1U:0U; x<_width; ++x) { + const float nx = (float)nvalues_x._linear_atX((float)x*sizx/wm1); + if (nx*ox<=0) { draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero); break; } + ox = nx; + } + } + const CImg nvalues_y(values_y._data,values_y.size(),1,1,1,true); + const int sizy = (int)values_y.size() - 1, hm1 = height() - 1; + if (sizy>0) { + float oy = (float)nvalues_y[0]; + for (unsigned int y = sizy?1U:0U; y<_height; ++y) { + const float ny = (float)nvalues_y._linear_atX((float)y*sizy/hm1); + if (ny*oy<=0) { draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero); break; } + oy = ny; + } + } + return *this; + } + + //! Draw labeled horizontal and vertical axes \overloading. + template + CImg& draw_axes(const float x0, const float x1, const float y0, const float y1, + const tc *const color, const float opacity=1, + const int subdivisionx=-60, const int subdivisiony=-60, + const float precisionx=0, const float precisiony=0, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U, + const unsigned int font_height=13) { + if (is_empty()) return *this; + const bool allow_zero = (x0*x1>0) || (y0*y1>0); + const float + dx = cimg::abs(x1 - x0), dy = cimg::abs(y1 - y0), + px = dx<=0?1:precisionx==0?(float)std::pow(10.,(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); + 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); + 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); + return *this; + } + + //! Draw 2D grid. + /** + \param values_x X-coordinates of the vertical lines. + \param values_y Y-coordinates of the horizontal lines. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \param pattern_x Drawing pattern for vertical lines. + \param pattern_y Drawing pattern for horizontal lines. + **/ + template + CImg& draw_grid(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) { + if (is_empty()) return *this; + if (values_x) cimg_foroff(values_x,x) { + const int xi = (int)values_x[x]; + if (xi>=0 && xi=0 && yi + CImg& draw_grid(const float delta_x, const float delta_y, + const float offsetx, const float offsety, + const bool invertx, const bool inverty, + const tc *const color, const float opacity=1, + const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) { + if (is_empty()) return *this; + CImg seqx, seqy; + if (delta_x!=0) { + const float dx = delta_x>0?delta_x:_width*-delta_x/100; + const unsigned int nx = (unsigned int)(_width/dx); + seqx = CImg::sequence(1 + nx,0,(unsigned int)(dx*nx)); + if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x) + offsetx,(float)_width); + if (invertx) cimg_foroff(seqx,x) seqx(x) = _width - 1 - seqx(x); + } + if (delta_y!=0) { + const float dy = delta_y>0?delta_y:_height*-delta_y/100; + const unsigned int ny = (unsigned int)(_height/dy); + seqy = CImg::sequence(1 + ny,0,(unsigned int)(dy*ny)); + if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y) + offsety,(float)_height); + if (inverty) cimg_foroff(seqy,y) seqy(y) = _height - 1 - seqy(y); + } + return draw_grid(seqx,seqy,color,opacity,pattern_x,pattern_y); + } + + //! Draw 1D graph. + /** + \param data Image containing the graph values I = f(x). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + + \param plot_type Define the type of the plot: + - 0 = No plot. + - 1 = Plot using segments. + - 2 = Plot using cubic splines. + - 3 = Plot with bars. + \param vertex_type Define the type of points: + - 0 = No points. + - 1 = Point. + - 2 = Straight cross. + - 3 = Diagonal cross. + - 4 = Filled circle. + - 5 = Outlined circle. + - 6 = Square. + - 7 = Diamond. + \param ymin Lower bound of the y-range. + \param ymax Upper bound of the y-range. + \param pattern Drawing pattern. + \note + - if \c ymin==ymax==0, the y-range is computed automatically from the input samples. + **/ + template + CImg& draw_graph(const CImg& data, + const tc *const color, const float opacity=1, + const unsigned int plot_type=1, const int vertex_type=1, + const double ymin=0, const double ymax=0, const unsigned int pattern=~0U) { + if (is_empty() || _height<=1) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_graph(): Specified color is (null).", + cimg_instance); + + // Create shaded colors for displaying bar plots. + CImg color1, color2; + if (plot_type==3) { + color1.assign(_spectrum); color2.assign(_spectrum); + cimg_forC(*this,c) { + color1[c] = (tc)std::min((float)cimg::type::max(),(float)color[c]*1.2f); + color2[c] = (tc)(color[c]*0.4f); + } + } + + // Compute min/max and normalization factors. + const ulongT + siz = data.size(), + _siz1 = siz - (plot_type!=3), + siz1 = _siz1?_siz1:1; + const unsigned int + _width1 = _width - (plot_type!=3), + width1 = _width1?_width1:1; + double m = ymin, M = ymax; + if (ymin==ymax) m = (double)data.max_min(M); + if (m==M) { --m; ++M; } + const float ca = (float)(M-m)/(_height - 1); + bool init_hatch = true; + + // Draw graph edges + switch (plot_type%4) { + case 1 : { // Segments + int oX = 0, oY = (int)((data[0] - m)/ca); + if (siz==1) { + const int Y = (int)((*data - m)/ca); + draw_line(0,Y,width() - 1,Y,color,opacity,pattern); + } else { + const float fx = (float)_width/siz1; + for (ulongT off = 1; off ndata(data._data,siz,1,1,1,true); + int oY = (int)((data[0] - m)/ca); + cimg_forX(*this,x) { + const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-m)/ca); + if (x>0) draw_line(x,oY,x + 1,Y,color,opacity,pattern,init_hatch); + init_hatch = false; + oY = Y; + } + } break; + case 3 : { // Bars + const int Y0 = (int)(-m/ca); + const float fx = (float)_width/siz1; + int oX = 0; + cimg_foroff(data,off) { + const int + X = (int)((off + 1)*fx) - 1, + Y = (int)((data[off] - m)/ca); + draw_rectangle(oX,Y0,X,Y,color,opacity). + draw_line(oX,Y,oX,Y0,color2.data(),opacity). + draw_line(oX,Y0,X,Y0,Y<=Y0?color2.data():color1.data(),opacity). + draw_line(X,Y,X,Y0,color1.data(),opacity). + draw_line(oX,Y,X,Y,Y<=Y0?color1.data():color2.data(),opacity); + oX = X + 1; + } + } break; + default : break; // No edges + } + + // Draw graph points + const unsigned int wb2 = plot_type==3?_width1/(2*siz):0; + const float fx = (float)_width1/siz1; + switch (vertex_type%8) { + case 1 : { // Point + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_point(X,Y,color,opacity); + } + } break; + case 2 : { // Straight Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y,X + 3,Y,color,opacity).draw_line(X,Y - 3,X,Y + 3,color,opacity); + } + } break; + case 3 : { // Diagonal Cross + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X - 3,Y - 3,X + 3,Y + 3,color,opacity).draw_line(X - 3,Y + 3,X + 3,Y - 3,color,opacity); + } + } break; + case 4 : { // Filled Circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity); + } + } break; + case 5 : { // Outlined circle + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity,0U); + } + } break; + case 6 : { // Square + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_rectangle(X - 3,Y - 3,X + 3,Y + 3,color,opacity,~0U); + } + } break; + case 7 : { // Diamond + cimg_foroff(data,off) { + const int + X = (int)(off*fx + wb2), + Y = (int)((data[off]-m)/ca); + draw_line(X,Y - 4,X + 4,Y,color,opacity). + draw_line(X + 4,Y,X,Y + 4,color,opacity). + draw_line(X,Y + 4,X - 4,Y,color,opacity). + draw_line(X - 4,Y,X,Y - 4,color,opacity); + } + } break; + default : break; // No points + } + return *this; + } + + bool _draw_fill(const int x, const int y, const int z, + const CImg& ref, const float tolerance2) const { + const T *ptr1 = data(x,y,z), *ptr2 = ref._data; + const unsigned long off = _width*_height*_depth; + float diff = 0; + cimg_forC(*this,c) { diff += cimg::sqr(*ptr1 - *(ptr2++)); ptr1+=off; } + return diff<=tolerance2; + } + + //! Draw filled 3D region with the flood fill algorithm. + /** + \param x0 X-coordinate of the starting point of the region to fill. + \param y0 Y-coordinate of the starting point of the region to fill. + \param z0 Z-coordinate of the starting point of the region to fill. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param[out] region Image that will contain the mask of the filled region mask, as an output. + \param tolerance Tolerance concerning neighborhood values. + \param opacity Opacity of the drawing. + \param is_high_connectivity Tells if 8-connexity must be used. + \return \c region is initialized with the binary mask of the filled region. + **/ + template + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity, + CImg ®ion, + const float tolerance = 0, + const bool is_high_connectivity = false) { +#define _draw_fill_push(x,y,z) if (N>=stack._width) stack.resize(2*N + 1,1,1,3,0); \ + stack[N] = x; stack(N,1) = y; stack(N++,2) = z +#define _draw_fill_pop(x,y,z) x = stack[--N]; y = stack(N,1); z = stack(N,2) +#define _draw_fill_is_inside(x,y,z) !_region(x,y,z) && _draw_fill(x,y,z,ref,tolerance2) + + if (!containsXYZC(x0,y0,z0,0)) return *this; + const float nopacity = cimg::abs((float)opacity), copacity = 1 - std::max((float)opacity,0.f); + const float tolerance2 = cimg::sqr(tolerance); + const CImg ref = get_vector_at(x0,y0,z0); + CImg stack(256,1,1,3); + CImg _region(_width,_height,_depth,1,0); + unsigned int N = 0; + int x, y, z; + + _draw_fill_push(x0,y0,z0); + while (N>0) { + _draw_fill_pop(x,y,z); + if (!_region(x,y,z)) { + const int yp = y - 1, yn = y + 1, zp = z - 1, zn = z + 1; + int xl = x, xr = x; + + // Using these booleans reduces the number of pushes drastically. + bool is_yp = false, is_yn = false, is_zp = false, is_zn = false; + for (int step = -1; step<2; step+=2) { + while (x>=0 && x=0 && _draw_fill_is_inside(x,yp,z)) { + if (!is_yp) { _draw_fill_push(x,yp,z); is_yp = true; } + } else is_yp = false; + if (yn1) { + if (zp>=0 && _draw_fill_is_inside(x,y,zp)) { + if (!is_zp) { _draw_fill_push(x,y,zp); is_zp = true; } + } else is_zp = false; + if (zn=0 && !is_yp) { + if (xp>=0 && _draw_fill_is_inside(xp,yp,z)) { + _draw_fill_push(xp,yp,z); if (step<0) is_yp = true; + } + if (xn0) is_yp = true; + } + } + if (yn=0 && _draw_fill_is_inside(xp,yn,z)) { + _draw_fill_push(xp,yn,z); if (step<0) is_yn = true; + } + if (xn0) is_yn = true; + } + } + if (depth()>1) { + if (zp>=0 && !is_zp) { + if (xp>=0 && _draw_fill_is_inside(xp,y,zp)) { + _draw_fill_push(xp,y,zp); if (step<0) is_zp = true; + } + if (xn0) is_zp = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zp)) { _draw_fill_push(x,yp,zp); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zp)) { _draw_fill_push(xp,yp,zp); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zp)) { _draw_fill_push(xp,yn,zp); } + if (xn=0 && _draw_fill_is_inside(xp,y,zn)) { + _draw_fill_push(xp,y,zn); if (step<0) is_zn = true; + } + if (xn0) is_zn = true; + } + + if (yp>=0 && !is_yp) { + if (_draw_fill_is_inside(x,yp,zn)) { _draw_fill_push(x,yp,zn); } + if (xp>=0 && _draw_fill_is_inside(xp,yp,zn)) { _draw_fill_push(xp,yp,zn); } + if (xn=0 && _draw_fill_is_inside(xp,yn,zn)) { _draw_fill_push(xp,yn,zn); } + if (xn + CImg& draw_fill(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,z0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw filled 2D region with the flood fill algorithm \simplification. + template + CImg& draw_fill(const int x0, const int y0, + const tc *const color, const float opacity=1, + const float tolerance=0, const bool is_high_connexity=false) { + CImg tmp; + return draw_fill(x0,y0,0,color,opacity,tmp,tolerance,is_high_connexity); + } + + //! Draw a random plasma texture. + /** + \param alpha Alpha-parameter. + \param beta Beta-parameter. + \param scale Scale-parameter. + \note Use the mid-point algorithm to render. + **/ + CImg& draw_plasma(const float alpha=1, const float beta=0, const unsigned int scale=8) { + if (is_empty()) return *this; + const int w = width(), h = height(); + const Tfloat m = (Tfloat)cimg::type::min(), M = (Tfloat)cimg::type::max(); + ulongT rng = (cimg::_rand(),cimg::rng()); + cimg_forZC(*this,z,c) { + CImg ref = get_shared_slice(z,c); + for (int delta = 1<1; delta>>=1) { + const int delta2 = delta>>1; + const float r = alpha*delta + beta; + + // Square step. + for (int y0 = 0; y0M?M:val); + } + + // Diamond steps. + for (int y = -delta2; yM?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. + /** + \param x0 X-coordinate of the upper-left pixel. + \param y0 Y-coordinate of the upper-left pixel. + \param x1 X-coordinate of the lower-right pixel. + \param y1 Y-coordinate of the lower-right pixel. + \param colormap Colormap. + \param opacity Drawing opacity. + \param z0r Real part of the upper-left fractal vertex. + \param z0i Imaginary part of the upper-left fractal vertex. + \param z1r Real part of the lower-right fractal vertex. + \param z1i Imaginary part of the lower-right fractal vertex. + \param iteration_max Maximum number of iterations for each estimated point. + \param is_normalized_iteration Tells if iterations are normalized. + \param is_julia_set Tells if the Mandelbrot or Julia set is rendered. + \param param_r Real part of the Julia set parameter. + \param param_i Imaginary part of the Julia set parameter. + \note Fractal rendering is done by the Escape Time Algorithm. + **/ + template + CImg& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1, + const CImg& colormap, const float opacity=1, + const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + if (is_empty()) return *this; + CImg palette; + if (colormap) palette.assign(colormap._data,colormap.size()/colormap._spectrum,1,1,colormap._spectrum,true); + if (palette && palette._spectrum!=_spectrum) + throw CImgArgumentException(_cimg_instance + "draw_mandelbrot(): Instance and specified colormap (%u,%u,%u,%u,%p) have " + "incompatible dimensions.", + cimg_instance, + colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); + + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.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 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; + const double x = z0r + p*(z1r-z0r)/_width, y = z0i + q*(z1i-z0i)/_height; + double zr, zi, cr, ci; + if (is_julia_set) { zr = x; zi = y; cr = param_r; ci = param_i; } + else { zr = param_r; zi = param_i; cr = x; ci = y; } + for (iteration=1; zr*zr + zi*zi<=4 && iteration<=iteration_max; ++iteration) { + const double temp = zr*zr - zi*zi + cr; + zi = 2*zr*zi + ci; + zr = temp; + } + if (iteration>iteration_max) { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette(0,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(0,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)0; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)((*this)(p,q,0,c)*copacity); + } + } else if (is_normalized_iteration) { + const float + normz = (float)cimg::abs(zr*zr + zi*zi), + niteration = (float)(iteration + 1 - std::log(std::log(normz))/ln2); + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._linear_atX(niteration,c); + else cimg_forC(*this,c) + (*this)(p,q,0,c) = (T)(palette._linear_atX(niteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)niteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(niteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } else { + if (palette) { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._atX(iteration,c); + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(iteration,c)*nopacity + (*this)(p,q,0,c)*copacity); + } else { + if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)iteration; + else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(iteration*nopacity + (*this)(p,q,0,c)*copacity); + } + } + } + return *this; + } + + //! Draw a quadratic Mandelbrot or Julia 2D fractal \overloading. + template + 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, + const unsigned int iteration_max=255, + const bool is_normalized_iteration=false, + const bool is_julia_set=false, + const double param_r=0, const double param_i=0) { + return draw_mandelbrot(0,0,_width - 1,_height - 1,colormap,opacity, + z0r,z0i,z1r,z1i,iteration_max,is_normalized_iteration,is_julia_set,param_r,param_i); + } + + //! Draw a 1D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param sigma Standard variation of the gaussian distribution. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float sigma, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forX(*this,x) { + const float dx = (x - xc), val = (float)std::exp(-dx*dx/sigma2); + T *ptrd = data(x,0,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 2D gaussian function. + /** + \param xc X-coordinate of the gaussian center. + \param yc Y-coordinate of the gaussian center. + \param tensor Covariance matrix (must be 2x2). + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified color is (null).", + cimg_instance); + typedef typename CImg::Tfloat tfloat; + 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + float dy = -yc; + cimg_forY(*this,y) { + float dx = -xc; + cimg_forX(*this,x) { + const float val = (float)std::exp(a*dx*dx + b*dx*dy + c*dy*dy); + T *ptrd = data(x,y,0,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + ++dx; + } + ++dy; + } + return *this; + } + + //! Draw a 2D gaussian function \overloading. + template + 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) { + const double + a = r1*ru*ru + r2*rv*rv, + b = (r1-r2)*ru*rv, + c = r1*rv*rv + r2*ru*ru; + const CImg tensor(2,2,1,1, a,b,b,c); + return draw_gaussian(xc,yc,tensor,color,opacity); + } + + //! 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. + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + typedef typename CImg::Tfloat tfloat; + if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.", + cimg_instance, + tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); + + const CImg 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.f); + const ulongT whd = (ulongT)_width*_height*_depth; + const tc *col = color; + cimg_forXYZ(*this,x,y,z) { + const float + dx = (x - xc), dy = (y - yc), dz = (z - zc), + val = (float)std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz); + T *ptrd = data(x,y,z,0); + if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } + col-=_spectrum; + } + return *this; + } + + //! Draw a 3D gaussian function \overloading. + template + 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. + /** + \param x0 X-coordinate of the 3D object position + \param y0 Y-coordinate of the 3D object position + \param z0 Z-coordinate of the 3D object position + \param vertices Image Nx3 describing 3D point coordinates + \param primitives List of P primitives + \param colors List of P color (or textures) + \param opacities Image or list of P opacities + \param render_type d Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud) + \param is_double_sided Tells if object faces have two sides or are oriented. + \param focale length of the focale (0 for parallel projection) + \param lightx X-coordinate of the light + \param lighty Y-coordinate of the light + \param lightz Z-coordinate of the light + \param specular_lightness Amount of specular light. + \param specular_shininess Shininess of the object + \param g_opacity Global opacity of the object. + **/ + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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,g_opacity,1); + } +#endif + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + //! Draw a 3D object \simplification. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool is_double_sided=false, const float focale=700, + const float lightx=0, const float lighty=0, const float lightz=-5e8, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + 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,g_opacity,CImg::empty()); + } + + template + CImg& draw_object3d(LibBoard::Board& board, + const float x0, const float y0, const float z0, + const CImg& vertices, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + 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,g_opacity,zbuffer); + } +#endif + + template + static float __draw_object3d(const CImgList& opacities, const unsigned int n_primitive, CImg& opacity) { + if (n_primitive>=opacities._width || opacities[n_primitive].is_empty()) { opacity.assign(); return 1; } + if (opacities[n_primitive].size()==1) { opacity.assign(); return opacities(n_primitive,0); } + opacity.assign(opacities[n_primitive],true); + return 1.f; + } + + template + static float __draw_object3d(const CImg& opacities, const unsigned int n_primitive, CImg& opacity) { + opacity.assign(); + 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 + CImg& _draw_object3d(void *const pboard, CImg& zbuffer, + const float X, const float Y, const float Z, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const unsigned int render_type, + const bool is_double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_lightness, const float specular_shininess, + const float 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).", + cimg_instance,vertices._width,primitives._width,error_message.data()); +#ifndef cimg_use_board + if (pboard) return *this; +#endif + if (render_type==5) cimg::mutex(10); // Static variable used in this case, breaks thread-safety + + const float + nspec = 1 - (specular_lightness<0.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; + + // Create light texture for phong-like rendering. + CImg light_texture; + if (render_type==5) { + if (colors._width>primitives._width) { + static CImg default_light_texture; + static const tc *lptr = 0; + static tc ref_values[64] = { 0 }; + const CImg& img = colors.back(); + bool is_same_texture = (lptr==img._data); + if (is_same_texture) + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + if (ref_values[r++]!=img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum)) { + is_same_texture = false; break; + } + if (!is_same_texture || default_light_texture._spectrum<_spectrum) { + (default_light_texture.assign(img,false)/=255).resize(-100,-100,1,_spectrum); + lptr = colors.back().data(); + for (unsigned int r = 0, j = 0; j<8; ++j) + for (unsigned int i = 0; i<8; ++i) + ref_values[r++] = img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum); + } + light_texture.assign(default_light_texture,true); + } else { + static CImg default_light_texture; + static float olightx = 0, olighty = 0, olightz = 0, ospecular_shininess = 0; + if (!default_light_texture || + lightx!=olightx || lighty!=olighty || lightz!=olightz || + specular_shininess!=ospecular_shininess || default_light_texture._spectrum<_spectrum) { + default_light_texture.assign(512,512); + const float + dlx = lightx - X, + dly = lighty - Y, + dlz = lightz - Z, + nl = cimg::hypot(dlx,dly,dlz), + nlx = (default_light_texture._width - 1)/2*(1 + dlx/nl), + nly = (default_light_texture._height - 1)/2*(1 + dly/nl), + white[] = { 1 }; + default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.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.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; + } + light_texture.assign(default_light_texture,true); + } + } + + // 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_size(projections.size(),4096)) + cimg_forX(projections,l) { // Perspective projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + const tpfloat projectedz = z + Z + absfocale; + projections(l,1) = Y + absfocale*y/projectedz; + projections(l,0) = X + absfocale*x/projectedz; + } + } else { + cimg_pragma_openmp(parallel for cimg_openmp_if_size(projections.size(),4096)) + cimg_forX(projections,l) { // Parallel projection + const tpfloat + x = (tpfloat)vertices(l,0), + y = (tpfloat)vertices(l,1), + z = (tpfloat)vertices(l,2); + if (z visibles(primitives._width,1,1,1,~0U); + CImg zrange(primitives._width); + 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_size(primitives.size(),4096)) + cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + switch (primitive.size()) { + case 1 : { // Point + CImg<_to> _opacity; + __draw_object3d(opacities,l,_opacity); + if (l<=colors.width() && (colors[l].size()!=_spectrum || _opacity)) is_forward = false; + const unsigned int i0 = (unsigned int)primitive(0); + const tpfloat z0 = Z + vertices(i0,2); + if (z0>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = z0; + } + } break; + case 5 : { // Sphere + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + Xc = 0.5f*((float)vertices(i0,0) + (float)vertices(i1,0)), + Yc = 0.5f*((float)vertices(i0,1) + (float)vertices(i1,1)), + Zc = 0.5f*((float)vertices(i0,2) + (float)vertices(i1,2)), + _zc = Z + Zc, + zc = _zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(i1,0) - vertices(i0,0), + vertices(i1,1) - vertices(i0,1), + vertices(i1,2) - vertices(i0,2))*(absfocale?absfocale/zc:1), + xm = xc - radius, + ym = yc - radius, + xM = xc + radius, + yM = yc + radius; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && _zc>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = _zc; + } + is_forward = false; + } break; + case 2 : case 6 : { // Segment + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2); + tpfloat xm, xM, ym, yM; + if (x0=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1)/2; + } + } break; + case 3 : case 9 : { // Triangle + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (y0yM) yM = y2; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) { + const tpfloat d = (x1-x0)*(y2-y0) - (x2-x0)*(y1-y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2)/3; + } + } + } break; + case 4 : 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); + const tpfloat + x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), + x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), + x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2), + x3 = projections(i3,0), y3 = projections(i3,1), z3 = Z + vertices(i3,2); + tpfloat xm, xM, ym, yM; + if (x0xM) xM = x2; + if (x3xM) xM = x3; + if (y0yM) yM = y2; + if (y3yM) yM = y3; + if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin && z3>zmin) { + const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0); + if (is_double_sided || d<0) { + visibles(l) = (unsigned int)l; + zrange(l) = (z0 + z1 + z2 + z3)/4; + } + } + } break; + default : + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Invalid primitive[%u] with size %u " + "(should have size 1,2,3,4,5,6,9 or 12).", + cimg_instance, + l,primitive.size()); + } + } + + // Force transparent primitives to be drawn last when zbuffer is activated + // (and if object contains no spheres or sprites). + if (is_forward) + cimglist_for(primitives,l) + if (___draw_object3d(opacities,l)!=1) zrange(l) = 2*zmax - zrange(l); + + // Sort only visibles primitives. + unsigned int *p_visibles = visibles._data; + tpfloat *p_zrange = zrange._data; + const tpfloat *ptrz = p_zrange; + cimg_for(visibles,ptr,unsigned int) { + if (*ptr!=~0U) { *(p_visibles++) = *ptr; *(p_zrange++) = *ptrz; } + ++ptrz; + } + const unsigned int nb_visibles = (unsigned int)(p_zrange - zrange._data); + if (!nb_visibles) { + if (render_type==5) cimg::mutex(10,0); + return *this; + } + CImg permutations; + CImg(zrange._data,nb_visibles,1,1,1,true).sort(permutations,is_forward); + + // Compute light properties + CImg lightprops; + switch (render_type) { + case 3 : { // Flat Shading + lightprops.assign(nb_visibles); + 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(); + if (psize==3 || psize==4 || psize==9 || psize==12) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nx = dy1*dz2 - dz1*dy2, + ny = dz1*dx2 - dx1*dz2, + nz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + (x0 + x1 + x2)/3 - lightx, + ly = Y + (y0 + y1 + y2)/3 - lighty, + lz = Z + (z0 + z1 + z2)/3 - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max(cimg::abs(-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } else lightprops[l] = 1; + } + } break; + + case 4 : // Gouraud Shading + case 5 : { // Phong-Shading + CImg vertices_normals(vertices._width,6,1,1,0); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + for (int l = 0; l<(int)nb_visibles; ++l) { + const CImg& primitive = primitives[visibles(l)]; + const unsigned int psize = (unsigned int)primitive.size(); + const bool + triangle_flag = (psize==3) || (psize==9), + quadrangle_flag = (psize==4) || (psize==12); + if (triangle_flag || quadrangle_flag) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2), + i3 = quadrangle_flag?(unsigned int)primitive(3):0; + const tpfloat + x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), + x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), + x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nnx = dy1*dz2 - dz1*dy2, + nny = dz1*dx2 - dx1*dz2, + nnz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + cimg::hypot(nnx,nny,nnz), + nx = nnx/norm, + ny = nny/norm, + nz = nnz/norm; + unsigned int ix = 0, iy = 1, iz = 2; + if (is_double_sided && nz>0) { ix = 3; iy = 4; iz = 5; } + vertices_normals(i0,ix)+=nx; vertices_normals(i0,iy)+=ny; vertices_normals(i0,iz)+=nz; + vertices_normals(i1,ix)+=nx; vertices_normals(i1,iy)+=ny; vertices_normals(i1,iz)+=nz; + vertices_normals(i2,ix)+=nx; vertices_normals(i2,iy)+=ny; vertices_normals(i2,iz)+=nz; + if (quadrangle_flag) { + vertices_normals(i3,ix)+=nx; vertices_normals(i3,iy)+=ny; vertices_normals(i3,iz)+=nz; + } + } + } + + if (is_double_sided) cimg_forX(vertices_normals,p) { + const float + nx0 = vertices_normals(p,0), ny0 = vertices_normals(p,1), nz0 = vertices_normals(p,2), + nx1 = vertices_normals(p,3), ny1 = vertices_normals(p,4), nz1 = vertices_normals(p,5), + n0 = nx0*nx0 + ny0*ny0 + nz0*nz0, n1 = nx1*nx1 + ny1*ny1 + nz1*nz1; + if (n1>n0) { + vertices_normals(p,0) = -nx1; + vertices_normals(p,1) = -ny1; + vertices_normals(p,2) = -nz1; + } + } + + if (render_type==4) { + lightprops.assign(vertices._width); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + lx = X + vertices(l,0) - lightx, + ly = Y + vertices(l,1) - lighty, + lz = Z + vertices(l,2) - lightz, + nl = 1e-5f + cimg::hypot(lx,ly,lz), + factor = std::max((-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } + } else { + const unsigned int + lw2 = light_texture._width/2 - 1, + lh2 = light_texture._height/2 - 1; + lightprops.assign(vertices._width,2); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) + cimg_forX(lightprops,l) { + const tpfloat + nx = vertices_normals(l,0), + ny = vertices_normals(l,1), + nz = vertices_normals(l,2), + norm = 1e-5f + cimg::hypot(nx,ny,nz), + nnx = nx/norm, + nny = ny/norm; + lightprops(l,0) = lw2*(1 + nnx); + lightprops(l,1) = lh2*(1 + nny); + } + } + } break; + } + + // Draw visible primitives + const CImg default_color(1,_spectrum,1,1,(tc)200); + CImg<_to> _opacity; + + for (unsigned int l = 0; l& primitive = primitives[n_primitive]; + const CImg + &__color = n_primitive(), + _color = (__color && __color.size()!=_spectrum && __color._spectrum<_spectrum)? + __color.get_resize(-100,-100,-100,_spectrum,0):CImg(), + &color = _color?_color:(__color?__color:default_color); + const tc *const pcolor = color._data; + float opacity = __draw_object3d(opacities,n_primitive,_opacity); + if (_opacity.is_empty()) opacity*=g_opacity; + else if (!_opacity.is_shared()) _opacity*=g_opacity; + +#ifdef cimg_use_board + LibBoard::Board &board = *(LibBoard::Board*)pboard; +#endif + + switch (primitive.size()) { + case 1 : { // Colored point or sprite + const unsigned int n0 = (unsigned int)primitive[0]; + const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1); + + if (_opacity.is_empty()) { // Scalar opacity + + if (color.size()==_spectrum) { // Colored point + draw_point(x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height()-(float)y0); + } +#endif + } else { // Sprite + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(color._width*factor), + _sh = (unsigned int)(color._height*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + 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); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } else { // Opacity mask + const tpfloat z = Z + vertices(n0,2); + const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); + const unsigned int + _sw = (unsigned int)(std::max(color._width,_opacity._width)*factor), + _sh = (unsigned int)(std::max(color._height,_opacity._height)*factor), + sw = _sw?_sw:1, sh = _sh?_sh:1; + const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; + if (sw<=3*_width/2 && sh<=3*_height/2 && + (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 + _sprite = (sw!=color._width || sh!=color._height)? + color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg(), + &sprite = _sprite?_sprite:color; + const CImg<_to> + _nopacity = (sw!=_opacity._width || sh!=_opacity._height)? + _opacity.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<_to>(), + &nopacity = _nopacity?_nopacity:_opacity; + draw_image(nx0,ny0,sprite,nopacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128); + board.setFillColor(LibBoard::Color::Null); + board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); + } +#endif + } + } + } break; + case 2 : { // Colored line + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity); + else draw_line(x0,y0,x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 5 : { // Colored sphere + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + is_wireframe = (unsigned int)primitive[2]; + const float + Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)), + Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)), + Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)), + zc = Z + Zc + _focale, + xc = X + Xc*(absfocale?absfocale/zc:1), + yc = Y + Yc*(absfocale?absfocale/zc:1), + radius = 0.5f*cimg::hypot(vertices(n1,0) - vertices(n0,0), + vertices(n1,1) - vertices(n0,1), + vertices(n1,2) - vertices(n0,2))*(absfocale?absfocale/zc:1); + switch (render_type) { + case 0 : + draw_point((int)xc,(int)yc,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot(xc,height() - yc); + } +#endif + break; + case 1 : + draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } +#endif + break; + default : + if (is_wireframe) draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); + else draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + if (!is_wireframe) board.fillCircle(xc,height() - yc,radius); + else { + board.setFillColor(LibBoard::Color::Null); + board.drawCircle(xc,height() - yc,radius); + } + } +#endif + break; + } + } break; + case 6 : { // Textured line + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for line primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + tx0 = (int)primitive[2], ty0 = (int)primitive[3], + tx1 = (int)primitive[4], ty1 = (int)primitive[5], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity); + else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + } +#endif + } else { + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + } +#endif + } + } break; + case 3 : { // Colored triangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity).draw_point(x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x0,y0,x2,y2,pcolor,opacity). + draw_line(x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)); + else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + } break; + } + } break; + case 4 : { // Colored quadrangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1), + x3 = (int)projections(n3,0), y3 = (int)projections(n3,1), + xc = (x0 + x1 + x2 + x3)/4, yc = (y0 + y1 + y2 + y3)/4; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity). + draw_point(x2,y2,pcolor,opacity).draw_point(x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opacity).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opacity); + else + draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x1,y1,x2,y2,pcolor,opacity). + draw_line(x2,y2,x3,y3,pcolor,opacity).draw_line(x3,y3,x0,y0,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity); + else + draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity,lightprops(l)); + else + _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)). + _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp),(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop2)/4; + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,xc,yc,zc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,lightprop0,lightprop1,lightpropc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,lightprop1,lightprop2,lightpropc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,lightprop2,lightprop3,lightpropc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,lightprop3,lightprop0,lightpropc,opacity); + +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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). + draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + else + draw_triangle(x0,y0,x1,y1,xc,yc,pcolor,light_texture,lx0,ly0,lx1,ly1,lxc,lyc,opacity). + draw_triangle(x1,y1,x2,y2,xc,yc,pcolor,light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). + draw_triangle(x2,y2,x3,y3,xc,yc,pcolor,light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). + draw_triangle(x3,y3,x0,y0,xc,yc,pcolor,light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + case 9 : { // Textured triangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for triangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + tx0 = (int)primitive[3], ty0 = (int)primitive[4], + tx1 = (int)primitive[5], ty1 = (int)primitive[6], + tx2 = (int)primitive[7], ty2 = (int)primitive[8], + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = vertices(n0,2) + Z + _focale, + z1 = vertices(n1,2) + Z + _focale, + z2 = vertices(n2,2) + Z + _focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, + lightprops(n0),lightprops(n1),lightprops(n2),opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), + (float)x1,height() - (float)y1,lightprops(n1), + (float)x2,height() - (float)y2,lightprops(n2)); + } +#endif + break; + case 5 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1), + opacity); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), + (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), + (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), + (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + } +#endif + break; + } + } break; + case 12 : { // Textured quadrangle + if (!__color) { + if (render_type==5) cimg::mutex(10,0); + throw CImgArgumentException(_cimg_instance + "draw_object3d(): Undefined texture for quadrangle primitive [%u].", + cimg_instance,n_primitive); + } + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + tx0 = (int)primitive[4], ty0 = (int)primitive[5], + tx1 = (int)primitive[6], ty1 = (int)primitive[7], + tx2 = (int)primitive[8], ty2 = (int)primitive[9], + tx3 = (int)primitive[10], ty3 = (int)primitive[11], + 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; + 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; + + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, + ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). + draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, + ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). + draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, + ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity). + draw_point(x3,y3,color.get_vector_at(tx3<=0?0:tx3>=color.width()?color.width() - 1:tx3, + ty3<=0?0:ty3>=color.height()?color.height() - 1:ty3)._data,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawDot((float)x0,height() - (float)y0); + board.drawDot((float)x1,height() - (float)y1); + board.drawDot((float)x2,height() - (float)y2); + board.drawDot((float)x3,height() - (float)y3); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). + draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). + draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); + board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); + board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); + board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = std::min(lightprops(l),1.f); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opacity*255)); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x1,height() - (float)y1, + (float)x2,height() - (float)y2); + board.fillTriangle((float)x0,height() - (float)y0, + (float)x2,height() - (float)y2, + (float)x3,height() - (float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), + lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop3)/4; + 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); + 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); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, + (float)x1,height() - (float)y1,lightprop1, + (float)x2,height() - (float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,lightprop0, + (float)x2,height() - (float)y2,lightprop2, + (float)x3,height() - (float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1), + 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,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); + 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); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), + l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), + l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), + l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); + board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, + (float)x1,height() - (float)y1,l1, + (float)x2,height() - (float)y2,l2); + board.fillGouraudTriangle((float)x0,height() -(float)y0,l0, + (float)x2,height() - (float)y2,l2, + (float)x3,height() - (float)y3,l3); + } +#endif + } break; + } + } break; + } + } + if (render_type==5) cimg::mutex(10,0); + return *this; + } + + //@} + //--------------------------- + // + //! \name Data Input + //@{ + //--------------------------- + + //! Launch simple interface to select a shape from an image. + /** + \param disp Display window to use. + \param feature_type Type of feature to select. Can be { 0=point | 1=line | 2=rectangle | 3=ellipse }. + \param XYZ Pointer to 3 values X,Y,Z which tells about the projection point coordinates, for volumetric images. + \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 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); + } + + //! 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 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); + } + + //! 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 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); + } + + //! 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 bool exit_on_anykey=false, + const bool is_deep_selection_default=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,XYZ,0,0,0,exit_on_anykey,true,false,is_deep_selection_default); + } + + CImg _select(CImgDisplay &disp, const char *const title, + const unsigned int feature_type, unsigned int *const XYZ, + const int origX, const int origY, const int origZ, + const bool exit_on_anykey, + const bool reset_view3d, + const bool force_display_z_coord, + const bool is_deep_selection_default) const { + if (is_empty()) return CImg(1,feature_type==0?3:6,1,1,-1); + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + } else { + if (title) disp.set_title("%s",title); + disp.move_inside_screen(); + } + + CImg thumb; + if (width()>disp.screen_width() || height()>disp.screen_height()) + get_resize(cimg_fitscreen(width(),height(),depth()),depth(),-100).move_to(thumb); + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0).set_wheel().show_mouse(); + + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + int area = 0, 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), + 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; + + bool is_deep_selection = is_deep_selection_default, + shape_selected = false, text_down = false, visible_cursor = true; + static CImg pose3d; + static bool is_view3d = false, is_axes = true; + if (reset_view3d) { pose3d.assign(); is_view3d = false; } + CImg points3d, opacities3d, sel_opacities3d; + CImgList primitives3d, sel_primitives3d; + CImgList colors3d, sel_colors3d; + CImg visu, visu0, view3d; + CImg text(1024); *text = 0; + + while (!key && !disp.is_closed() && !shape_selected) { + + // Handle mouse motion and selection + int + mx = disp.mouse_x(), + my = disp.mouse_y(); + + const float + mX = mx<0?-1.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 && mX=0 && mX=height()) { area = 2; X = mX; Z = mY - _height; Y = (float)(phase?Y1:Y0); } + if (mY>=0 && mX>=width() && mY=width() && mY>=height()) area = 4; + if (disp.button()) { if (!area_clicked) area_clicked = area; } else area_clicked = 0; + + CImg filename(32); + + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(1); key = 0; } break; + case cimg::keyPAGEDOWN : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(-1); key = 0; } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_axes = !is_axes; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + is_view3d = !is_view3d; disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot...",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + } + + switch (area) { + + case 0 : // When mouse is out of image range + mx = my = -1; X = Y = Z = -1; + break; + + case 1 : case 2 : case 3 : { // When mouse is over the XY,XZ or YZ projections + const unsigned int but = disp.button(); + const bool b1 = (bool)(but&1), b2 = (bool)(but&2), b3 = (bool)(but&4); + + if (b1 && phase==1 && area_clicked==area) { // When selection has been started (1st step) + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } + if (!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 (phase) { + if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; + } else { + if (_depth>1 && (X0!=(int)X || Y0!=(int)Y || Z0!=(int)Z)) visu0.assign(); + X0 = (int)X; Y0 = (int)Y; Z0 = (int)Z; + } + } + if (b3) { // Reset selection + 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 (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() && + !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT()) { + switch (area) { + case 1 : + if (phase) Z = (float)(Z1+=disp.wheel()); else Z = (float)(Z0+=disp.wheel()); + visu0.assign(); break; + case 2 : + if (phase) Y = (float)(Y1+=disp.wheel()); else Y = (float)(Y0+=disp.wheel()); + visu0.assign(); break; + case 3 : + if (phase) X = (float)(X1+=disp.wheel()); else X = (float)(X0+=disp.wheel()); + visu0.assign(); break; + } + disp.set_wheel(); + } else key = ~0U; + } + + if ((phase==0 && b1) || + (phase==1 && !b1) || + (phase==2 && b1)) switch (phase) { // Detect change of phase + case 0 : + if (area==area_clicked) { + X0 = X1 = (int)X; Y0 = Y1 = (int)Y; Z0 = Z1 = (int)Z; area_started = area; ++phase; + } break; + case 1 : + if (area==area_started) { + X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++phase; + if (_depth>1) { + if (disp.is_keyCTRLLEFT()) is_deep_selection = !is_deep_selection_default; + if (is_deep_selection) ++phase; + } + } else if (!b1) { X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = 0; visu0.assign(); } + break; + case 2 : ++phase; break; + } + } break; + + case 4 : // When mouse is over the 3D view + if (is_view3d && points3d) { + X3d = mx - width()*disp.width()/(width() + (depth()>1?depth():0)); + Y3d = my - height()*disp.height()/(height() + (depth()>1?depth():0)); + if (oX3d<0) { oX3d = X3d; oY3d = Y3d; } + // Left + right buttons: reset. + if ((disp.button()&3)==3) { pose3d.assign(); view3d.assign(); oX3d = oY3d = X3d = Y3d = -1; } + else if (disp.button()&1 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Left button: rotate + const float + R = 0.45f*std::min(view3d._width,view3d._height), + R2 = R*R, + u0 = (float)(oX3d - view3d.width()/2), + v0 = (float)(oY3d - view3d.height()/2), + u1 = (float)(X3d - view3d.width()/2), + v1 = (float)(Y3d - view3d.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + pose3d.draw_image(CImg::rotation_matrix(u,v,w,-alpha)*pose3d.get_crop(0,0,2,2)); + view3d.assign(); + } else if (disp.button()&2 && pose3d && oY3d!=Y3d) { // Right button: zoom + pose3d(3,2)+=(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 + pose3d(3,0)-=oX3d - X3d; pose3d(3,1)-=oY3d - Y3d; view3d.assign(); + } + oX3d = X3d; oY3d = Y3d; + } + mx = my = -1; X = Y = Z = -1; + break; + } + + if (phase) { + if (!feature_type) shape_selected = phase?true:false; + else { + if (_depth>1) shape_selected = (phase==3)?true:false; + else shape_selected = (phase==2)?true:false; + } + } + + if (X0<0) X0 = 0; + if (X0>=width()) X0 = width() - 1; + if (Y0<0) Y0 = 0; + if (Y0>=height()) Y0 = height() - 1; + if (Z0<0) Z0 = 0; + if (Z0>=depth()) Z0 = depth() - 1; + if (X1<1) X1 = 0; + if (X1>=width()) X1 = width() - 1; + if (Y1<0) Y1 = 0; + if (Y1>=height()) Y1 = height() - 1; + if (Z1<0) Z1 = 0; + if (Z1>=depth()) Z1 = depth() - 1; + + // Draw visualization image on the display + if (mx!=omx || my!=omy || !visu0 || (_depth>1 && !view3d)) { + + if (!visu0) { // Create image of projected planes + if (thumb) thumb._get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + else _get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); + visu0.resize(disp); + view3d.assign(); + points3d.assign(); + } + + if (is_view3d && _depth>1 && !view3d) { // Create 3D view for volumetric images + const unsigned int + _x3d = (unsigned int)cimg::round((float)_width*visu0._width/(_width + _depth),1,1), + _y3d = (unsigned int)cimg::round((float)_height*visu0._height/(_height + _depth),1,1), + x3d = _x3d>=visu0._width?visu0._width - 1:_x3d, + y3d = _y3d>=visu0._height?visu0._height - 1:_y3d; + CImg(1,2,1,1,64,128).resize(visu0._width - x3d,visu0._height - y3d,1,visu0._spectrum,3). + move_to(view3d); + if (!points3d) { + get_projections3d(primitives3d,colors3d,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0,true).move_to(points3d); + points3d.append(CImg(8,3,1,1, + 0,_width - 1,_width - 1,0,0,_width - 1,_width - 1,0, + 0,0,_height - 1,_height - 1,0,0,_height - 1,_height - 1, + 0,0,0,0,_depth - 1,_depth - 1,_depth - 1,_depth - 1),'x'); + CImg::vector(12,13).move_to(primitives3d); CImg::vector(13,14).move_to(primitives3d); + CImg::vector(14,15).move_to(primitives3d); CImg::vector(15,12).move_to(primitives3d); + CImg::vector(16,17).move_to(primitives3d); CImg::vector(17,18).move_to(primitives3d); + CImg::vector(18,19).move_to(primitives3d); CImg::vector(19,16).move_to(primitives3d); + CImg::vector(12,16).move_to(primitives3d); CImg::vector(13,17).move_to(primitives3d); + CImg::vector(14,18).move_to(primitives3d); CImg::vector(15,19).move_to(primitives3d); + colors3d.insert(12,CImg::vector(255,255,255)); + opacities3d.assign(primitives3d.width(),1,1,1,0.5f); + if (!phase) { + opacities3d[0] = opacities3d[1] = opacities3d[2] = 0.8f; + sel_primitives3d.assign(); + sel_colors3d.assign(); + sel_opacities3d.assign(); + } else { + if (feature_type==2) { + points3d.append(CImg(8,3,1,1, + X0,X1,X1,X0,X0,X1,X1,X0, + Y0,Y0,Y1,Y1,Y0,Y0,Y1,Y1, + Z0,Z0,Z0,Z0,Z1,Z1,Z1,Z1),'x'); + sel_primitives3d.assign(); + CImg::vector(20,21).move_to(sel_primitives3d); + CImg::vector(21,22).move_to(sel_primitives3d); + CImg::vector(22,23).move_to(sel_primitives3d); + CImg::vector(23,20).move_to(sel_primitives3d); + CImg::vector(24,25).move_to(sel_primitives3d); + CImg::vector(25,26).move_to(sel_primitives3d); + CImg::vector(26,27).move_to(sel_primitives3d); + CImg::vector(27,24).move_to(sel_primitives3d); + CImg::vector(20,24).move_to(sel_primitives3d); + CImg::vector(21,25).move_to(sel_primitives3d); + CImg::vector(22,26).move_to(sel_primitives3d); + CImg::vector(23,27).move_to(sel_primitives3d); + } else { + points3d.append(CImg(2,3,1,1, + X0,X1, + Y0,Y1, + Z0,Z1),'x'); + sel_primitives3d.assign(CImg::vector(20,21)); + } + sel_colors3d.assign(sel_primitives3d._width,CImg::vector(255,255,255)); + sel_opacities3d.assign(sel_primitives3d._width,1,1,1,0.8f); + } + points3d.shift_object3d(-0.5f*(_width - 1),-0.5f*(_height - 1),-0.5f*(_depth - 1)).resize_object3d(); + points3d*=0.75f*std::min(view3d._width,view3d._height); + } + + if (!pose3d) CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose3d); + CImg zbuffer3d(view3d._width,view3d._height,1,1,0); + const CImg rotated_points3d = pose3d.get_crop(0,0,2,2)*points3d; + if (sel_primitives3d) + view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width, + pose3d(3,1) + 0.5f*view3d._height, + pose3d(3,2), + rotated_points3d,sel_primitives3d,sel_colors3d,sel_opacities3d, + 2,true,500,0,0,0,0,0,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,1,zbuffer3d); + visu0.draw_image(x3d,y3d,view3d); + } + visu = visu0; + + if (X<0 || Y<0 || Z<0) { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + else { + if (is_axes) { if (visible_cursor) { disp.hide_mouse(); visible_cursor = false; }} + else { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} + const int d = (depth()>1)?depth():0; + int _vX = (int)X, _vY = (int)Y, _vZ = (int)Z; + if (phase>=2) { _vX = X1; _vY = Y1; _vZ = Z1; } + int + w = disp.width(), W = width() + d, + h = disp.height(), H = height() + d, + _xp = (int)(_vX*(float)w/W), xp = _xp + ((int)(_xp*(float)W/w)!=_vX), + _yp = (int)(_vY*(float)h/H), yp = _yp + ((int)(_yp*(float)H/h)!=_vY), + _xn = (int)((_vX + 1.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.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, + zyc = (zyp + zyn)/2, + xf = (int)(X*w/W), + yf = (int)(Y*h/H), + zxf = (int)((Z + width())*w/W), + zyf = (int)((Z + height())*h/H); + + if (is_axes) { // Draw axes + visu.draw_line(0,yf,visu.width() - 1,yf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,yf,visu.width() - 1,yf,background_color,0.7f,0x00FF00FF). + draw_line(xf,0,xf,visu.height() - 1,foreground_color,0.7f,0xFF00FF00). + draw_line(xf,0,xf,visu.height() - 1,background_color,0.7f,0x00FF00FF); + if (_depth>1) + visu.draw_line(zxf,0,zxf,yM,foreground_color,0.7f,0xFF00FF00). + draw_line(zxf,0,zxf,yM,background_color,0.7f,0x00FF00FF). + draw_line(0,zyf,xM,zyf,foreground_color,0.7f,0xFF00FF00). + draw_line(0,zyf,xM,zyf,background_color,0.7f,0x00FF00FF); + } + + // Draw box cursor. + if (xn - xp>=4 && yn - yp>=4) + visu.draw_rectangle(xp,yp,xn,yn,foreground_color,0.2f). + draw_rectangle(xp,yp,xn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,yp,xn,yn,background_color,1,0x55555555); + if (_depth>1) { + if (yn - yp>=4 && zxn - zxp>=4) + visu.draw_rectangle(zxp,yp,zxn,yn,background_color,0.2f). + draw_rectangle(zxp,yp,zxn,yn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(zxp,yp,zxn,yn,background_color,1,0x55555555); + if (xn - xp>=4 && zyn - zyp>=4) + visu.draw_rectangle(xp,zyp,xn,zyn,background_color,0.2f). + draw_rectangle(xp,zyp,xn,zyn,foreground_color,1,0xAAAAAAAA). + draw_rectangle(xp,zyp,xn,zyn,background_color,1,0x55555555); + } + + // Draw selection. + if (phase && (phase!=1 || area_started==area)) { + 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.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.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); + if (d) { + visu.draw_arrow(zxc0,yc0,zxc,yc,background_color,0.9f,30,5,0x55555555). + draw_arrow(zxc0,yc0,zxc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA). + draw_arrow(xc0,zyc0,xc,zyc,background_color,0.9f,30,5,0x55555555). + draw_arrow(xc0,zyc0,xc,zyc,foreground_color,0.9f,30,5,0xAAAAAAAA); + } + } break; + case 2 : { + visu.draw_rectangle(X0=0 && my<13) text_down = true; else if (my>=visu.height() - 13) text_down = false; + if (!feature_type || !phase) { + if (X>=0 && Y>=0 && Z>=0 && X1 || force_display_z_coord) + cimg_snprintf(text,text._width," Point (%d,%d,%d) = [ ",origX + (int)X,origY + (int)Y,origZ + (int)Z); + else cimg_snprintf(text,text._width," Point (%d,%d) = [ ",origX + (int)X,origY + (int)Y); + CImg values = get_vector_at((int)X,(int)Y,(int)Z); + const bool is_large_spectrum = values._height>16; + if (is_large_spectrum) + values.draw_image(0,8,values.get_rows(values._height - 8,values._height - 1)).resize(1,16,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," (...)"); + ctext += std::strlen(ctext); + } + *(ctext++) = ' '; *ctext = 0; + } + std::strcpy(text._data + std::strlen(text),"] "); + } + } else switch (feature_type) { + case 1 : { + const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), + length = cimg::round(cimg::hypot(dX,dY,dZ),0.1); + 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 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 ", + origX + (X01 || force_display_z_coord) + cimg_snprintf(text,text._width," Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d) ", + origX + X0,origY + Y0,origZ + Z0,origX + X1,origY + Y1,origZ + Z1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1),1 + cimg::abs(Z0 - Z1)); + else cimg_snprintf(text,text._width," Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d) ", + origX + X0,origY + Y0,origX + X1,origY + Y1, + 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1)); + } + if (phase || (mx>=0 && my>=0)) visu.__draw_text("%s",text_down,text._data); + } + + disp.display(visu); + } + if (!shape_selected) disp.wait(); + if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); } + omx = mx; omy = my; + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + + // Return result. + CImg res(1,feature_type==0?3:6,1,1,-1); + if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; } + if (shape_selected) { + if (feature_type==2) { + if (is_deep_selection) switch (area_started) { + case 1 : Z0 = 0; Z1 = _depth - 1; break; + case 2 : Y0 = 0; Y1 = _height - 1; break; + case 3 : X0 = 0; X1 = _width - 1; break; + } + if (X0>X1) cimg::swap(X0,X1); + if (Y0>Y1) cimg::swap(Y0,Y1); + if (Z0>Z1) cimg::swap(Z0,Z1); + } + if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1; + switch (feature_type) { + case 1 : case 2 : res[0] = X0; res[1] = Y0; res[2] = Z0; res[3] = X1; res[4] = Y1; res[5] = Z1; break; + case 3 : + res[3] = cimg::abs(X1 - X0); res[4] = cimg::abs(Y1 - Y0); res[5] = cimg::abs(Z1 - Z0); + res[0] = X0; res[1] = Y0; res[2] = Z0; + break; + default : res[0] = X0; res[1] = Y0; res[2] = Z0; + } + } + if (!exit_on_anykey || !(disp.button()&4)) disp.set_button(); + if (!visible_cursor) disp.show_mouse(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + if (key!=~0U) disp.set_key(key); + return res; + } + + // Return a visualizable uchar8 image for display routines. + CImg _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; + if (_depth>1) { + const int mdisp = std::min(disp.screen_width(),disp.screen_height()); + if (depth()>mdisp) { + crop.get_resize(-100,-100,mdisp,-100,0).move_to(img2d); + img2d.projections2d(x,y,z*img2d._depth/_depth); + } else crop.get_projections2d(x,y,z).move_to(img2d); + } else CImg(crop,false).move_to(img2d); + + // Check for inf and NaN values. + if (cimg::type::is_float() && normalization) { + bool is_inf = false, is_nan = false; + cimg_for(img2d,ptr,Tuchar) + if (cimg::type::is_inf(*ptr)) { is_inf = true; break; } + else if (cimg::type::is_nan(*ptr)) { is_nan = true; break; } + if (is_inf || is_nan) { + 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 + 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); + if (is_nan) + cimg_for(img2d,ptr,Tuchar) + 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 + } + } + + switch (normalization) { + case 1 : img2d.normalize((ucharT)0,(ucharT)255); break; + case 2 : { + const float m = disp._min, M = disp._max; + (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.f/(M - m>0?M - m:1); + } break; + } + if (img2d.spectrum()==2) img2d.channels(0,2); + return img2d; + } + + //! Select sub-graph in a graph. + CImg get_select_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "select_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title("CImg<%s>",pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth; + const unsigned int old_normalization = disp.normalization(); + disp.show().set_button().set_wheel()._normalization = 0; + + double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax; + if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; } + if (nymin==nymax) { --nymin; ++nymax; } + if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.; } + + 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 }; + + 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) { + ulongT 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; + CImg message(1024); + CImg_3x3(I,unsigned char); + + for (bool selected = false; !selected && !disp.is_closed() && !okey && !disp.wheel(); ) { + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + const unsigned int key = disp.key(), button = disp.button(); + + // Generate graph representation. + if (!visu0) { + visu0.assign(disp.width(),disp.height(),1,3,220); + const int gdimx = disp.width() - 32, gdimy = disp.height() - 32; + if (gdimx>0 && gdimy>0) { + graph.assign(gdimx,gdimy,1,3,255); + if (siz<32) { + if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0, + false,true,black,0.2f,0x33333333,0x33333333); + } else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333); + cimg_forC(*this,c) + graph.draw_graph(get_shared_channel(c),&colormap(0,c),(plot_type!=3 || _spectrum==1)?1:0.6f, + plot_type,vertex_type,nymax,nymin); + + axes.assign(gdimx,gdimy,1,1,0); + const float + dx = (float)cimg::abs(nxmax - nxmin), dy = (float)cimg::abs(nymax - nymin), + px = (float)std::pow(10.,(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); + + const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0); + axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero); + if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero); + if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero); + if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero); + if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero); + + cimg_for3x3(axes,x,y,0,0,I,unsigned char) + if (Icc) { + if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0; + else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3); + } + else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) + cimg_forC(graph,c) graph(x,y,c) = (unsigned char)((graph(x,y,c) + 511)/3); + + visu0.draw_image(16,16,graph); + visu0.draw_line(15,15,16 + gdimx,15,gray2).draw_line(16 + gdimx,15,16 + gdimx,16 + gdimy,gray2). + draw_line(16 + gdimx,16 + gdimy,15,16 + gdimy,white).draw_line(15,16 + gdimy,15,15,white); + } else graph.assign(); + text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1,13).resize(-100,-100,1,3); + visu0.draw_image((visu0.width() - text.width())/2,visu0.height() - 14,~text); + text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3); + visu0.draw_image(1,(visu0.height() - text.height())/2,~text); + visu.assign(); + } + + // Generate and display current view. + if (!visu) { + visu.assign(visu0); + if (graph && x0>=0 && x1>=0) { + const int + nx0 = x0<=x1?x0:x1, + nx1 = x0<=x1?x1:x0, + ny0 = y0<=y1?y0:y1, + ny1 = y0<=y1?y1:y0, + sx0 = (int)(16 + nx0*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sx1 = (int)(15 + (nx1 + 1)*(visu.width() - 32)/std::max((ulongT)1,siz - one)), + sy0 = 16 + ny0, + sy1 = 16 + ny1; + if (y0>=0 && y1>=0) + visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU); + else visu.draw_rectangle(sx0,0,sx1,visu.height() - 17,gray,0.5f). + draw_line(sx0,16,sx0,visu.height() - 17,black,0.5f,0xCCCCCCCCU). + draw_line(sx1,16,sx1,visu.height() - 17,black,0.5f,0xCCCCCCCCU); + } + if (mouse_x>=16 && mouse_y>=16 && mouse_x=7) + cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx, + (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2), + (double)(*this)(x,0,0,_spectrum - 4),(double)(*this)(x,0,0,_spectrum - 3), + (double)(*this)(x,0,0,_spectrum - 1)); + else { + cimg_snprintf(message,message._width,"Value[%u:%g] = ( ",x,cx); + cimg_forC(*this,c) cimg_sprintf(message._data + std::strlen(message),"%g ",(double)(*this)(x,0,0,c)); + cimg_sprintf(message._data + std::strlen(message),")"); + } + if (x0>=0 && x1>=0) { + const unsigned int + nx0 = (unsigned int)(x0<=x1?x0:x1), + nx1 = (unsigned int)(x0<=x1?x1:x0), + ny0 = (unsigned int)(y0<=y1?y0:y1), + ny1 = (unsigned int)(y0<=y1?y1:y0); + const double + cx0 = nxmin + nx0*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cx1 = nxmin + (nx1 + one)*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), + cy0 = nymax - ny0*(nymax - nymin)/(visu._height - 32), + cy1 = nymax - ny1*(nymax - nymin)/(visu._height - 32); + if (y0>=0 && y1>=0) + cimg_sprintf(message._data + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )", + x0,cx0,cy0,x1 + one,cx1,cy1); + else + cimg_sprintf(message._data + std::strlen(message)," - Range [ %u:%g - %u:%g ]", + x0,cx0,x1 + one,cx1); + } + text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3); + visu.draw_image((visu.width() - text.width())/2,1,~text); + } + visu.display(disp); + } + + // Test keys. + CImg filename(32); + switch (okey = key) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(CImgDisplay::screen_width()/2, + CImgDisplay::screen_height()/2,1),false)._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving snapshot... ",false).display(disp); + screen.save(filename); + (+screen).__draw_text(" Snapshot '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+screen).__draw_text(" Saving instance... ",false).display(disp); + save(filename); + (+screen).__draw_text(" Instance '%s' saved. ",false,filename._data).display(disp); + } + disp.set_key(key,false); okey = 0; + } break; + } + + // Handle mouse motion and mouse buttons. + if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) { + visu.assign(); + if (disp.mouse_x()>=0 && disp.mouse_y()>=0) { + const int + mx = (mouse_x - 16)*(int)(siz - one)/(disp.width() - 32), + cx = cimg::cut(mx,0,(int)(siz - 1 - one)), + my = mouse_y - 16, + cy = cimg::cut(my,0,disp.height() - 32); + if (button&1) { + if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; } + } + else if (button&2) { + if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; } + } + else if (obutton) { x1 = x1>=0?cx:-1; y1 = y1>=0?cy:-1; selected = true; } + } else if (!button && obutton) selected = true; + obutton = button; omouse_x = mouse_x; omouse_y = mouse_y; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (visu && visu0) disp.wait(); + if (!exit_on_anykey && okey && okey!=cimg::keyESC && + (okey!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + okey = 0; + } + } + + disp._normalization = old_normalization; + if (x1>=0 && x1(4,1,1,1,x0,y0,x1>=0?x1 + (int)one:-1,y1); + } + + //! Load image from a file. + /** + \param filename Filename, as a C-string. + \note The extension of \c filename defines the file format. If no filename + extension is provided, CImg::get_load() will try to load the file as a .cimg or .cimgz file. + **/ + CImg& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load(): Specified filename is (null).", + cimg_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimg_load_plugin + cimg_load_plugin(filename); +#endif +#ifdef cimg_load_plugin1 + cimg_load_plugin1(filename); +#endif +#ifdef cimg_load_plugin2 + cimg_load_plugin2(filename); +#endif +#ifdef cimg_load_plugin3 + cimg_load_plugin3(filename); +#endif +#ifdef cimg_load_plugin4 + cimg_load_plugin4(filename); +#endif +#ifdef cimg_load_plugin5 + cimg_load_plugin5(filename); +#endif +#ifdef cimg_load_plugin6 + cimg_load_plugin6(filename); +#endif +#ifdef cimg_load_plugin7 + cimg_load_plugin7(filename); +#endif +#ifdef cimg_load_plugin8 + cimg_load_plugin8(filename); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) load_dlm(filename); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) load_jpeg(filename); + else if (!cimg::strcasecmp(ext,"png")) load_png(filename); + else if (!cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"pnm") || + !cimg::strcasecmp(ext,"pbm") || + !cimg::strcasecmp(ext,"pnk")) load_pnm(filename); + else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename); + else if (!cimg::strcasecmp(ext,"cr2") || + !cimg::strcasecmp(ext,"crw") || + !cimg::strcasecmp(ext,"dcr") || + !cimg::strcasecmp(ext,"mrw") || + !cimg::strcasecmp(ext,"nef") || + !cimg::strcasecmp(ext,"orf") || + !cimg::strcasecmp(ext,"pix") || + !cimg::strcasecmp(ext,"ptx") || + !cimg::strcasecmp(ext,"raf") || + !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"dcm") || + !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) load_analyze(filename); + else if (!cimg::strcasecmp(ext,"par") || + !cimg::strcasecmp(ext,"rec")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"mnc")) load_minc2(filename); + else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) return load_cimg(filename); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + + // Image sequences + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded) { + std::FILE *file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to open file '%s'.", + cimg_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + cimg::fclose(file); + is_loaded = true; + try { + if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename); + else if (!cimg::strcasecmp(f_type,"pfm")) load_pfm(filename); + else if (!cimg::strcasecmp(f_type,"bmp")) load_bmp(filename); + else if (!cimg::strcasecmp(f_type,"inr")) load_inr(filename); + else if (!cimg::strcasecmp(f_type,"jpg")) load_jpeg(filename); + else if (!cimg::strcasecmp(f_type,"pan")) load_pandore(filename); + else if (!cimg::strcasecmp(f_type,"png")) load_png(filename); + else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename); + else if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file with other means. + if (!is_loaded) { + try { + load_other(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image from a file \newinstance. + static CImg get_load(const char *const filename) { + return CImg().load(filename); + } + + //! Load image from an Ascii file. + /** + \param filename Filename, as a C -string. + **/ + CImg& load_ascii(const char *const filename) { + return _load_ascii(0,filename); + } + + //! Load image from an Ascii file \inplace. + static CImg get_load_ascii(const char *const filename) { + return CImg().load_ascii(filename); + } + + //! Load image from an Ascii file \overloading. + CImg& load_ascii(std::FILE *const file) { + return _load_ascii(file,0); + } + + //! Loadimage from an Ascii file \newinstance. + static CImg get_load_ascii(std::FILE *const file) { + return CImg().load_ascii(file); + } + + CImg& _load_ascii(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_ascii(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg line(256); *line = 0; + int err = std::fscanf(nfile,"%255[^\n]",line._data); + unsigned int dx = 0, dy = 1, dz = 1, dc = 1; + cimg_sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc); + err = std::fscanf(nfile,"%*[^0-9.eEinfa+-]"); + if (!dx || !dy || !dz || !dc) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_ascii(): Invalid Ascii header in file '%s', image dimensions are set " + "to (%u,%u,%u,%u).", + cimg_instance, + filename?filename:"(FILE*)",dx,dy,dz,dc); + } + assign(dx,dy,dz,dc); + const ulongT siz = size(); + ulongT off = 0; + double val; + T *ptr = _data; + for (err = 1, off = 0; off& load_dlm(const char *const filename) { + return _load_dlm(0,filename); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(const char *const filename) { + return CImg().load_dlm(filename); + } + + //! Load image from a DLM file \overloading. + CImg& load_dlm(std::FILE *const file) { + return _load_dlm(file,0); + } + + //! Load image from a DLM file \newinstance. + static CImg get_load_dlm(std::FILE *const file) { + return CImg().load_dlm(file); + } + + CImg& _load_dlm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_dlm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + CImg delimiter(256), tmp(256); *delimiter = *tmp = 0; + unsigned int cdx = 0, dx = 0, dy = 0; + int err = 0; + double val; + assign(256,256,1,1,(T)0); + while ((err = std::fscanf(nfile,"%lf%255[^0-9eEinfa.+-]",&val,delimiter._data))>0) { + if (err>0) (*this)(cdx++,dy) = (T)val; + if (cdx>=_width) resize(3*_width/2,_height,1,1,0); + char c = 0; + if (!cimg_sscanf(delimiter,"%255[^\n]%c",tmp._data,&c) || c=='\n') { + dx = std::max(cdx,dx); + if (++dy>=_height) resize(_width,3*_height/2,1,1,0); + cdx = 0; + } + } + if (cdx && err==1) { dx = cdx; ++dy; } + if (!dx || !dy) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_dlm(): Invalid DLM file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + resize(dx,dy,1,1,0); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a BMP file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_bmp(const char *const filename) { + return _load_bmp(0,filename); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(const char *const filename) { + return CImg().load_bmp(filename); + } + + //! Load image from a BMP file \overloading. + CImg& load_bmp(std::FILE *const file) { + return _load_bmp(file,0); + } + + //! Load image from a BMP file \newinstance. + static CImg get_load_bmp(std::FILE *const file) { + return CImg().load_bmp(file); + } + + CImg& _load_bmp(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_bmp(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(54); + cimg::fread(header._data,54,nfile); + if (*header!='B' || header[1]!='M') { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_bmp(): Invalid BMP file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read header and pixel buffer + int + file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24), + offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24), + header_size = header[0x0E] + (header[0x0F]<<8) + (header[0x10]<<16) + (header[0x11]<<24), + dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24), + dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24), + compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24), + nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24), + bpp = header[0x1C] + (header[0x1D]<<8); + + if (!file_size || file_size==offset) { + cimg::fseek(nfile,0,SEEK_END); + file_size = (int)cimg::ftell(nfile); + cimg::fseek(nfile,54,SEEK_SET); + } + if (header_size>40) cimg::fseek(nfile,header_size - 40,SEEK_CUR); + + const int + dx_bytes = (bpp==1)?(dx/8 + (dx%8?1:0)):((bpp==4)?(dx/2 + (dx%2)):(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); + + CImg colormap; + if (bpp<16) { if (!nb_colors) nb_colors = 1<0) cimg::fseek(nfile,xoffset,SEEK_CUR); + + CImg buffer; + if (buf_size=2) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0x80, val = 0; + cimg_forX(*this,x) { + if (mask==0x80) val = *(ptrs++); + const unsigned char *col = (unsigned char*)(colormap._data + (val&mask?1:0)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask); + } + ptrs+=align_bytes; + } + } break; + case 4 : { // 16 colors + if (colormap._width>=16) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + unsigned char mask = 0xF0, val = 0; + cimg_forX(*this,x) { + if (mask==0xF0) val = *(ptrs++); + const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4)); + const unsigned char *col = (unsigned char*)(colormap._data + color); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask,4); + } + ptrs+=align_bytes; + } + } break; + case 8 : { // 256 colors + if (colormap._width>=256) for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char *col = (unsigned char*)(colormap._data + *(ptrs++)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + } + ptrs+=align_bytes; + } + } break; + case 16 : { // 16 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + const unsigned char c1 = *(ptrs++), c2 = *(ptrs++); + const unsigned short col = (unsigned short)(c1|(c2<<8)); + (*this)(x,y,2) = (T)(col&0x1F); + (*this)(x,y,1) = (T)((col>>5)&0x1F); + (*this)(x,y,0) = (T)((col>>10)&0x1F); + } + ptrs+=align_bytes; + } + } break; + case 24 : { // 24 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + } + ptrs+=align_bytes; + } + } break; + case 32 : { // 32 bits colors + for (int y = height() - 1; y>=0; --y) { + if (buf_size>=cimg_iobuffer) { + if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; + cimg::fseek(nfile,align_bytes,SEEK_CUR); + } + cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + ++ptrs; + } + ptrs+=align_bytes; + } + } break; + } + if (dy<0) mirror('y'); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a JPEG file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_jpeg(const char *const filename) { + return _load_jpeg(0,filename); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(const char *const filename) { + return CImg().load_jpeg(filename); + } + + //! Load image from a JPEG file \overloading. + CImg& load_jpeg(std::FILE *const file) { + return _load_jpeg(file,0); + } + + //! Load image from a JPEG file \newinstance. + static CImg get_load_jpeg(std::FILE *const file) { + return CImg().load_jpeg(file); + } + + // Custom error handler for libjpeg. +#ifdef cimg_use_jpeg + struct _cimg_error_mgr { + struct jpeg_error_mgr original; + jmp_buf setjmp_buffer; + char message[JMSG_LENGTH_MAX]; + }; + + typedef struct _cimg_error_mgr *_cimg_error_ptr; + + METHODDEF(void) _cimg_jpeg_error_exit(j_common_ptr cinfo) { + _cimg_error_ptr c_err = (_cimg_error_ptr) cinfo->err; // Return control to the setjmp point + (*cinfo->err->format_message)(cinfo,c_err->message); + jpeg_destroy(cinfo); // Clean memory and temp files + longjmp(c_err->setjmp_buffer,1); + } +#endif + + CImg& _load_jpeg(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_jpeg(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_jpeg + if (file) + throw CImgIOException(_cimg_instance + "load_jpeg(): Unable to load data from '(FILE*)' unless libjpeg is enabled.", + cimg_instance); + else return load_other(filename); +#else + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + struct jpeg_decompress_struct cinfo; + struct _cimg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr.original); + jerr.original.error_exit = _cimg_jpeg_error_exit; + if (setjmp(jerr.setjmp_buffer)) { // JPEG error + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_jpeg(): Error message returned by libjpeg: %s.", + cimg_instance,jerr.message); + } + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo,nfile); + jpeg_read_header(&cinfo,TRUE); + jpeg_start_decompress(&cinfo); + + if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) { + if (!file) { + cimg::fclose(nfile); + return load_other(filename); + } else + throw CImgIOException(_cimg_instance + "load_jpeg(): Failed to load JPEG data from file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + } + CImg buffer(cinfo.output_width*cinfo.output_components); + JSAMPROW row_pointer[1]; + try { assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); } + catch (...) { if (!file) cimg::fclose(nfile); throw; } + T *ptr_r = _data, *ptr_g = _data + 1UL*_width*_height, *ptr_b = _data + 2UL*_width*_height, + *ptr_a = _data + 3UL*_width*_height; + while (cinfo.output_scanline + // This is experimental code, not much tested, use with care. + CImg& load_magick(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_magick(): Specified filename is (null).", + cimg_instance); +#ifdef cimg_use_magick + Magick::Image image(filename); + const unsigned int W = image.size().width(), H = image.size().height(); + switch (image.type()) { + case Magick::PaletteMatteType : + case Magick::TrueColorMatteType : + case Magick::ColorSeparationType : { + assign(W,H,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + case Magick::PaletteType : + case Magick::TrueColorType : { + assign(W,H,1,3); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_g++) = (T)(pixels->green); + *(ptr_b++) = (T)(pixels->blue); + ++pixels; + } + } break; + case Magick::GrayscaleMatteType : { + assign(W,H,1,2); + T *ptr_r = data(0,0,0,0), *ptr_a = data(0,0,0,1); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + *(ptr_a++) = (T)(pixels->opacity); + ++pixels; + } + } break; + default : { + assign(W,H,1,1); + T *ptr_r = data(0,0,0,0); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (ulongT off = (ulongT)W*H; off; --off) { + *(ptr_r++) = (T)(pixels->red); + ++pixels; + } + } + } + return *this; +#else + throw CImgIOException(_cimg_instance + "load_magick(): Unable to load file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Load image from a file, using Magick++ library \newinstance. + static CImg get_load_magick(const char *const filename) { + return CImg().load_magick(filename); + } + + //! 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. + **/ + CImg& load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return _load_png(0,filename,bits_per_pixel); + } + + //! Load image from a PNG file \newinstance. + static CImg get_load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { + return CImg().load_png(filename,bits_per_pixel); + } + + //! 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); + } + + //! 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); + } + + // (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) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_png(): Specified filename is (null).", + cimg_instance); + +#ifndef cimg_use_png + cimg::unused(bits_per_pixel); + if (file) + throw CImgIOException(_cimg_instance + "load_png(): Unable to load data from '(FILE*)' unless libpng is enabled.", + cimg_instance); + + else return load_other(filename); +#else + // Open file and check for PNG validity +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb"); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"rb"); +#endif + unsigned char pngCheck[8] = { 0 }; + cimg::fread(pngCheck,8,(std::FILE*)nfile); + if (png_sig_cmp(pngCheck,0,8)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Invalid PNG file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Setup PNG structures for read + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn); + if (!png_ptr) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'png_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'info_ptr' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Failed to initialize 'end_info' structure for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + + // Error handling callback for png file reading + if (setjmp(png_jmpbuf(png_ptr))) { + if (!file) cimg::fclose((std::FILE*)nfile); + png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Encountered unknown fatal error in libpng for file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + png_set_sig_bytes(png_ptr, 8); + + // Get PNG Header Info up to data block + png_read_info(png_ptr,info_ptr); + png_uint_32 W, H; + int bit_depth, color_type, interlace_type; + bool is_gray = false; + png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0); + if (bits_per_pixel) *bits_per_pixel = (unsigned int)bit_depth; + + // Transforms to unify image data + if (color_type==PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + color_type = PNG_COLOR_TYPE_RGB; + bit_depth = 8; + } + if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + is_gray = true; + bit_depth = 8; + } + if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + color_type |= PNG_COLOR_MASK_ALPHA; + } + if (color_type==PNG_COLOR_TYPE_GRAY || color_type==PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + color_type |= PNG_COLOR_MASK_COLOR; + is_gray = true; + } + if (color_type==PNG_COLOR_TYPE_RGB) + png_set_filler(png_ptr,0xffffU,PNG_FILLER_AFTER); + + png_read_update_info(png_ptr,info_ptr); + if (bit_depth!=8 && bit_depth!=16) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0); + throw CImgIOException(_cimg_instance + "load_png(): Invalid bit depth %u in file '%s'.", + cimg_instance, + bit_depth,nfilename?nfilename:"(FILE*)"); + } + const int byte_depth = bit_depth>>3; + + // Allocate memory for image reading + png_bytep *const imgData = new png_bytep[H]; + for (unsigned int row = 0; row& load_pnm(const char *const filename) { + return _load_pnm(0,filename); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(const char *const filename) { + return CImg().load_pnm(filename); + } + + //! Load image from a PNM file \overloading. + CImg& load_pnm(std::FILE *const file) { + return _load_pnm(file,0); + } + + //! Load image from a PNM file \newinstance. + static CImg get_load_pnm(std::FILE *const file) { + return CImg().load_pnm(file); + } + + CImg& _load_pnm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pnm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + unsigned int ppm_type, W, H, D = 1, colormax = 255; + CImg item(16384,1,1,1,0); + int err, rval, gval, bval; + const longT cimg_iobuffer = (longT)24*1024*1024; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%u",&ppm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %u %u %u %u",&W,&H,&D,&colormax))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): WIDTH and HEIGHT fields undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (ppm_type!=1 && ppm_type!=4) { + if (err==2 || (err==3 && (ppm_type==5 || ppm_type==7 || ppm_type==8 || ppm_type==9))) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%u",&colormax)!=1) + cimg::warn(_cimg_instance + "load_pnm(): COLORMAX field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else { colormax = D; D = 1; } + } + std::fgetc(nfile); + + switch (ppm_type) { + case 1 : { // 2D b&w Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)(rval?0:255); else break; } + } break; + case 2 : { // 2D grey Ascii + assign(W,H,1,1); + T* ptrd = _data; + cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)rval; else break; } + } break; + case 3 : { // 2D color Ascii + assign(W,H,1,3); + T *ptrd = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forXY(*this,x,y) { + if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { + *(ptrd++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval; + } else break; + } + } break; + case 4 : { // 2D b&w binary (support 3D PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + unsigned int w = 0, h = 0, d = 0; + for (longT to_read = (longT)((W/8 + (W%8?1:0))*H*D); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + unsigned char mask = 0, val = 0; + for (ulongT off = (ulongT)raw._width; off || mask; mask>>=1) { + if (!mask) { if (off--) val = *(ptrs++); mask = 128; } + *(ptrd++) = (T)((val&mask)?0:255); + if (++w==W) { w = 0; mask = 0; if (++h==H) { h = 0; if (++d==D) break; }} + } + } + } break; + case 5 : case 7 : { // 2D/3D grey binary (support 3D PINK extension) + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } else { // 16 bits + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } + } break; + case 6 : { // 2D color binary + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { // 16 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer/2)); + cimg::fread(raw._data,raw._width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); + to_read-=raw._width; + const unsigned short *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } + } break; + case 8 : { // 2D/3D grey binary with int32 integers (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const int *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + case 9 : { // 2D/3D grey binary with float values (PINK extension) + CImg raw; + assign(W,H,D,1); + T *ptrd = data(0,0,0,0); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const float *ptrs = raw._data; + for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } break; + default : + assign(); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pnm(): PNM type 'P%d' found, but type is not supported.", + cimg_instance, + filename?filename:"(FILE*)",ppm_type); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PFM file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pfm(const char *const filename) { + return _load_pfm(0,filename); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(const char *const filename) { + return CImg().load_pfm(filename); + } + + //! Load image from a PFM file \overloading. + CImg& load_pfm(std::FILE *const file) { + return _load_pfm(file,0); + } + + //! Load image from a PFM file \newinstance. + static CImg get_load_pfm(std::FILE *const file) { + return CImg().load_pfm(file); + } + + CImg& _load_pfm(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pfm(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + char pfm_type; + CImg item(16384,1,1,1,0); + int W = 0, H = 0, err = 0; + double scale = 0; + while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item," P%c",&pfm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): PFM header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if ((err=cimg_sscanf(item," %d %d",&W,&H))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH and HEIGHT fields are undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } else if (W<=0 || H<=0) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pfm(): WIDTH (%d) and HEIGHT (%d) fields are invalid in file '%s'.", + cimg_instance,W,H, + filename?filename:"(FILE*)"); + } + if (err==2) { + while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); + if (cimg_sscanf(item,"%lf",&scale)!=1) + cimg::warn(_cimg_instance + "load_pfm(): SCALE field is undefined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + std::fgetc(nfile); + const bool is_color = (pfm_type=='F'), is_inverted = (scale>0)!=cimg::endianness(); + if (is_color) { + assign(W,H,1,3,(T)0); + CImg buf(3*W); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); + cimg_forY(*this,y) { + cimg::fread(buf._data,3*W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,3*W); + const float *ptrs = buf._data; + cimg_forX(*this,x) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { + assign(W,H,1,1,(T)0); + CImg buf(W); + T *ptrd = data(0,0,0,0); + cimg_forY(*this,y) { + cimg::fread(buf._data,W,nfile); + if (is_inverted) cimg::invert_endianness(buf._data,W); + const float *ptrs = buf._data; + cimg_forX(*this,x) *(ptrd++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return mirror('y'); // Most of the .pfm files are flipped along the y-axis + } + + //! Load image from a RGB file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(0,filename,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(filename,dimw,dimh); + } + + //! Load image from a RGB file \overloading. + CImg& load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(file,0,dimw,dimh); + } + + //! Load image from a RGB file \newinstance. + static CImg get_load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(file,dimw,dimh); + } + + CImg& _load_rgb(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgb(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,3); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/3UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a RGBA file. + /** + \param filename Filename, as a C-string. + \param dimw Width of the image buffer. + \param dimh Height of the image buffer. + **/ + CImg& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(0,filename,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(filename,dimw,dimh); + } + + //! Load image from a RGBA file \overloading. + CImg& load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(file,0,dimw,dimh); + } + + //! Load image from a RGBA file \newinstance. + static CImg get_load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(file,dimw,dimh); + } + + CImg& _load_rgba(std::FILE *const file, const char *const filename, + const unsigned int dimw, const unsigned int dimh) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_rgba(): Specified filename is (null).", + cimg_instance); + + if (!dimw || !dimh) return assign(); + const longT cimg_iobuffer = (longT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,4); + T + *ptr_r = data(0,0,0,0), + *ptr_g = data(0,0,0,1), + *ptr_b = data(0,0,0,2), + *ptr_a = data(0,0,0,3); + for (longT to_read = (longT)size(); to_read>0; ) { + raw.assign(std::min(to_read,cimg_iobuffer)); + cimg::fread(raw._data,raw._width,nfile); + to_read-=raw._width; + const unsigned char *ptrs = raw._data; + for (ulongT off = raw._width/4UL; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + *(ptr_a++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a TIFF file. + /** + \param filename Filename, as a C-string. + \param first_frame First frame to read (for multi-pages tiff). + \param last_frame Last frame to read (for multi-pages tiff). + \param step_frame Step value of frame reading. + \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. + - 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 + 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) { + 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.", + cimg_instance, + filename); + return load_other(filename); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimg_instance + "load_tiff(): File '%s' contains %u image(s) while specified frame range is [%u,%u] (step %u).", + cimg_instance, + filename,nb_images,nfirst_frame,nlast_frame,nstep_frame); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + TIFFSetDirectory(tif,0); + CImg frame; + for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) { + frame._load_tiff(tif,l,voxel_size,description); + if (l==nfirst_frame) + assign(frame._width,frame._height,1 + (nlast_frame - nfirst_frame)/nstep_frame,frame._spectrum); + if (frame._width>_width || frame._height>_height || frame._spectrum>_spectrum) + resize(std::max(frame._width,_width), + std::max(frame._height,_height),-100, + std::max(frame._spectrum,_spectrum),0); + draw_image(0,0,(l - nfirst_frame)/nstep_frame,frame); + } + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "load_tiff(): Failed to open file '%s'.", + cimg_instance, + filename); + return *this; +#endif + } + + //! Load image from a TIFF file \newinstance. + static CImg 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); + } + + // (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) { + 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) { + 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) { + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (row = 0; rowny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, 0); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0; rr + void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { + t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + uint32 row, rowsperstrip = (uint32)-1; + TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); + for (unsigned int vv = 0; vvny?ny - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, vv); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgIOException(_cimg_instance + "load_tiff(): Invalid strip in file '%s'.", + cimg_instance, + TIFFFileName(tif)); + } + const t *ptr = buf; + for (unsigned int rr = 0;rr& _load_tiff(TIFF *const tif, const unsigned int directory, + 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; + const char *const filename = TIFFFileName(tif); + const bool is_spp = (bool)TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel); + TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx); + TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny); + TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat); + TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample); + TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo); + if (voxel_size) { + const char *s_description = 0; + float vx = 0, vy = 0, vz = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) { + const char *s_desc = std::strstr(s_description,"VX="); + if (s_desc && cimg_sscanf(s_desc,"VX=%f VY=%f VZ=%f",&vx,&vy,&vz)==3) { // CImg format + voxel_size[0] = vx; voxel_size[1] = vy; voxel_size[2] = vz; + } + s_desc = std::strstr(s_description,"spacing="); + if (s_desc && cimg_sscanf(s_desc,"spacing=%f",&vz)==1) { // Fiji format + voxel_size[2] = vz; + } + } + TIFFGetField(tif,TIFFTAG_XRESOLUTION,voxel_size); + TIFFGetField(tif,TIFFTAG_YRESOLUTION,voxel_size + 1); + voxel_size[0] = 1.f/voxel_size[0]; + voxel_size[1] = 1.f/voxel_size[1]; + } + if (description) { + const char *s_description = 0; + if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) + CImg::string(s_description).move_to(*description); + } + const unsigned int spectrum = !is_spp || photo>=3?(photo>1?3:1):samplesperpixel; + assign(nx,ny,1,spectrum); + + if ((photo>=3 && sampleformat==1 && + (bitspersample==4 || bitspersample==8) && + (samplesperpixel==1 || samplesperpixel==3 || samplesperpixel==4)) || + (bitspersample==1 && samplesperpixel==1)) { + // Special case for unsigned color images. + uint32 *const raster = (uint32*)_TIFFmalloc(nx*ny*sizeof(uint32)); + if (!raster) { + _TIFFfree(raster); TIFFClose(tif); + throw CImgException(_cimg_instance + "load_tiff(): Failed to allocate memory (%s) for file '%s'.", + cimg_instance, + cimg::strbuffersize(nx*ny*sizeof(uint32)),filename); + } + TIFFReadRGBAImage(tif,nx,ny,raster,0); + switch (spectrum) { + case 1 : + cimg_forXY(*this,x,y) + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + break; + case 3 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 -y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 -y) + x]); + } + break; + case 4 : + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny - 1 - y) + x]); + (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny - 1 - y) + x]); + } + break; + } + _TIFFfree(raster); + } else { // Other cases + uint16 config; + TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config); + if (TIFFIsTiled(tif)) { + uint32 tw = 1, th = 1; + TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw); + TIFFGetField(tif,TIFFTAG_TILELENGTH,&th); + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else if (sampleformat==SAMPLEFORMAT_INT) + _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); + break; + } + } else { + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) + _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); + else _load_tiff_contig(tif,samplesperpixel,nx,ny); + break; + } else switch (bitspersample) { + case 8 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 16 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 32 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + case 64 : + if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); + else _load_tiff_separate(tif,samplesperpixel,nx,ny); + break; + } + } + } + return *this; + } +#endif + + //! Load image from a MINC2 file. + /** + \param filename Filename, as a C-string. + **/ + // (Original code by Haz-Edine Assemlal). + CImg& load_minc2(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_minc2(): Specified filename is (null).", + cimg_instance); +#ifndef cimg_use_minc2 + return load_other(filename); +#else + minc::minc_1_reader rdr; + rdr.open(filename); + assign(rdr.ndim(1)?rdr.ndim(1):1, + rdr.ndim(2)?rdr.ndim(2):1, + rdr.ndim(3)?rdr.ndim(3):1, + rdr.ndim(4)?rdr.ndim(4):1); + if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_byte(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_int(); + else if (cimg::type::string()==cimg::type::string()) + rdr.setup_read_double(); + else + rdr.setup_read_float(); + minc::load_standard_volume(rdr,this->_data); + return *this; +#endif + } + + //! Load image from a MINC2 file \newinstance. + static CImg get_load_minc2(const char *const filename) { + return CImg().load_analyze(filename); + } + + //! Load image from an ANALYZE7.5/NIFTI file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_analyze(const char *const filename, float *const voxel_size=0) { + return _load_analyze(0,filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(const char *const filename, float *const voxel_size=0) { + return CImg().load_analyze(filename,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \overloading. + CImg& load_analyze(std::FILE *const file, float *const voxel_size=0) { + return _load_analyze(file,0,voxel_size); + } + + //! Load image from an ANALYZE7.5/NIFTI file \newinstance. + static CImg get_load_analyze(std::FILE *const file, float *const voxel_size=0) { + return CImg().load_analyze(file,voxel_size); + } + + CImg& _load_analyze(std::FILE *const file, const char *const filename, float *const voxel_size=0) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_analyze(): Specified filename is (null).", + cimg_instance); + + std::FILE *nfile_header = 0, *nfile = 0; + if (!file) { + CImg body(1024); + const char *const ext = cimg::split_filename(filename,body); + if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file + nfile_header = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".img"); + nfile = cimg::fopen(body,"rb"); + } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file + nfile = cimg::fopen(filename,"rb"); + cimg_sprintf(body._data + std::strlen(body),".hdr"); + nfile_header = cimg::fopen(body,"rb"); + } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file + } else nfile_header = nfile = file; // File is a Niftii file + if (!nfile || !nfile_header) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid Analyze7.5 or NIFTI header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + // Read header. + bool endian = false; + unsigned int header_size; + cimg::fread(&header_size,1,nfile_header); + if (!header_size) + throw CImgIOException(_cimg_instance + "load_analyze(): Invalid zero-size header in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); } + + unsigned char *const header = new unsigned char[header_size]; + cimg::fread(header + 4,header_size - 4,nfile_header); + if (!file && nfile_header!=nfile) cimg::fclose(nfile_header); + if (endian) { + cimg::invert_endianness((short*)(header + 40),5); + cimg::invert_endianness((short*)(header + 70),1); + cimg::invert_endianness((short*)(header + 72),1); + cimg::invert_endianness((float*)(header + 76),4); + cimg::invert_endianness((float*)(header + 108),1); + cimg::invert_endianness((float*)(header + 112),1); + } + + if (nfile_header==nfile) { + const unsigned int vox_offset = (unsigned int)*(float*)(header + 108); + std::fseek(nfile,vox_offset,SEEK_SET); + } + + unsigned short *dim = (unsigned short*)(header + 40), dimx = 1, dimy = 1, dimz = 1, dimv = 1; + if (!dim[0]) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with zero dimensions.", + cimg_instance, + filename?filename:"(FILE*)"); + + if (dim[0]>4) + cimg::warn(_cimg_instance + "load_analyze(): File '%s' defines an image with %u dimensions, reading only the 4 first.", + cimg_instance, + filename?filename:"(FILE*)",dim[0]); + + if (dim[0]>=1) dimx = dim[1]; + if (dim[0]>=2) dimy = dim[2]; + if (dim[0]>=3) dimz = dim[3]; + if (dim[0]>=4) dimv = dim[4]; + float scalefactor = *(float*)(header + 112); if (scalefactor==0) scalefactor = 1; + const unsigned short datatype = *(unsigned short*)(header + 70); + if (voxel_size) { + const float *vsize = (float*)(header + 76); + voxel_size[0] = vsize[1]; voxel_size[1] = vsize[2]; voxel_size[2] = vsize[3]; + } + delete[] header; + + // Read pixel data. + assign(dimx,dimy,dimz,dimv); + const size_t pdim = (size_t)dimx*dimy*dimz*dimv; + switch (datatype) { + case 2 : { + unsigned char *const buffer = new unsigned char[pdim]; + cimg::fread(buffer,pdim,nfile); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 4 : { + short *const buffer = new short[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 8 : { + int *const buffer = new int[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 16 : { + float *const buffer = new float[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 64 : { + double *const buffer = new double[pdim]; + cimg::fread(buffer,pdim,nfile); + if (endian) cimg::invert_endianness(buffer,pdim); + cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_analyze(): Unable to load datatype %d in file '%s'", + cimg_instance, + datatype,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a .cimg[z] file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(const char *const filename, const char axis='z', const float align=0) { + return CImg().load_cimg(filename,axis,align); + } + + //! Load image from a .cimg[z] file \overloading. + CImg& load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a .cimg[z] file \newinstance + static CImg get_load_cimg(std::FILE *const file, const char axis='z', const float align=0) { + return CImg().load_cimg(file,axis,align); + } + + //! Load sub-images of a .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Starting frame. + \param n1 Ending frame (~0U for max). + \param x0 X-coordinate of the starting sub-image vertex. + \param y0 Y-coordinate of the starting sub-image vertex. + \param z0 Z-coordinate of the starting sub-image vertex. + \param c0 C-coordinate of the starting sub-image vertex. + \param x1 X-coordinate of the ending sub-image vertex (~0U for max). + \param y1 Y-coordinate of the ending sub-image vertex (~0U for max). + \param z1 Z-coordinate of the ending sub-image vertex (~0U for max). + \param c1 C-coordinate of the ending sub-image vertex (~0U for max). + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load sub-images of a .cimg file \overloading. + CImg& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + CImgList list; + list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load sub-images of a .cimg file \newinstance. + static CImg get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1, + const char axis='z', const float align=0) { + return CImg().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); + } + + //! Load image from an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param[out] voxel_size Pointer to the three voxel sizes read from the file. + **/ + CImg& load_inr(const char *const filename, float *const voxel_size=0) { + return _load_inr(0,filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(const char *const filename, float *const voxel_size=0) { + return CImg().load_inr(filename,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \overloading. + CImg& load_inr(std::FILE *const file, float *const voxel_size=0) { + return _load_inr(file,0,voxel_size); + } + + //! Load image from an INRIMAGE-4 file \newinstance. + static CImg get_load_inr(std::FILE *const file, float *voxel_size=0) { + return CImg().load_inr(file,voxel_size); + } + + static void _load_inr_header(std::FILE *file, int out[8], float *const voxel_size) { + CImg item(1024), tmp1(64), tmp2(64); + *item = *tmp1 = *tmp2 = 0; + out[0] = std::fscanf(file,"%63s",item._data); + out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1; + if (cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0) + throw CImgIOException("CImg<%s>::load_inr(): INRIMAGE-4 header not found.", + pixel_type()); + + while (std::fscanf(file," %63[^\n]%*c",item._data)!=EOF && std::strncmp(item,"##}",3)) { + cimg_sscanf(item," XDIM%*[^0-9]%d",out); + cimg_sscanf(item," YDIM%*[^0-9]%d",out + 1); + cimg_sscanf(item," ZDIM%*[^0-9]%d",out + 2); + cimg_sscanf(item," VDIM%*[^0-9]%d",out + 3); + cimg_sscanf(item," PIXSIZE%*[^0-9]%d",out + 6); + if (voxel_size) { + cimg_sscanf(item," VX%*[^0-9.+-]%f",voxel_size); + cimg_sscanf(item," VY%*[^0-9.+-]%f",voxel_size + 1); + cimg_sscanf(item," VZ%*[^0-9.+-]%f",voxel_size + 2); + } + if (cimg_sscanf(item," CPU%*[ =]%s",tmp1._data)) out[7] = cimg::strncasecmp(tmp1,"sun",3)?0:1; + switch (cimg_sscanf(item," TYPE%*[ =]%s %s",tmp1._data,tmp2._data)) { + case 0 : break; + case 2 : + out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; + std::strncpy(tmp1,tmp2,tmp1._width - 1); // 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 + default : + throw CImgIOException("CImg<%s>::load_inr(): Invalid pixel type '%s' defined in header.", + pixel_type(), + tmp2._data); + } + } + if (out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0) + throw CImgIOException("CImg<%s>::load_inr(): Invalid dimensions (%d,%d,%d,%d) defined in header.", + pixel_type(), + out[0],out[1],out[2],out[3]); + if (out[4]<0 || out[5]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete pixel type defined in header.", + pixel_type()); + if (out[6]<0) + throw CImgIOException("CImg<%s>::load_inr(): Incomplete PIXSIZE field defined in header.", + pixel_type()); + if (out[7]<0) + throw CImgIOException("CImg<%s>::load_inr(): Big/Little Endian coding type undefined in header.", + pixel_type()); + } + + CImg& _load_inr(std::FILE *const file, const char *const filename, float *const voxel_size) { +#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \ + if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \ + Ts *xval, *const val = new Ts[(size_t)fopt[0]*fopt[3]]; \ + cimg_forYZ(*this,y,z) { \ + cimg::fread(val,fopt[0]*fopt[3],nfile); \ + if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \ + xval = val; cimg_forX(*this,x) cimg_forC(*this,c) (*this)(x,y,z,c) = (T)*(xval++); \ + } \ + delete[] val; \ + loaded = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_inr(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + int fopt[8], endian = cimg::endianness()?1:0; + bool loaded = false; + if (voxel_size) voxel_size[0] = voxel_size[1] = voxel_size[2] = 1; + _load_inr_header(nfile,fopt,voxel_size); + assign(fopt[0],fopt[1],fopt[2],fopt[3]); + _cimg_load_inr_case(0,0,8,unsigned char); + _cimg_load_inr_case(0,1,8,char); + _cimg_load_inr_case(0,0,16,unsigned short); + _cimg_load_inr_case(0,1,16,short); + _cimg_load_inr_case(0,0,32,unsigned int); + _cimg_load_inr_case(0,1,32,int); + _cimg_load_inr_case(1,0,32,float); + _cimg_load_inr_case(1,1,32,float); + _cimg_load_inr_case(1,0,64,double); + _cimg_load_inr_case(1,1,64,double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_inr(): Unknown pixel type defined in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a EXR file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_exr(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_exr(): Specified filename is (null).", + cimg_instance); +#if defined(cimg_use_openexr) + Imf::RgbaInputFile file(filename); + Imath::Box2i dw = file.dataWindow(); + const int + inwidth = dw.max.x - dw.min.x + 1, + inheight = dw.max.y - dw.min.y + 1; + Imf::Array2D pixels; + pixels.resizeErase(inheight,inwidth); + file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y*inwidth, 1, inwidth); + file.readPixels(dw.min.y, dw.max.y); + assign(inwidth,inheight,1,4); + T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); + cimg_forXY(*this,x,y) { + *(ptr_r++) = (T)pixels[y][x].r; + *(ptr_g++) = (T)pixels[y][x].g; + *(ptr_b++) = (T)pixels[y][x].b; + *(ptr_a++) = (T)pixels[y][x].a; + } +#elif defined(cimg_use_tinyexr) + float *res; + const char *err = 0; + int width = 0, height = 0; + const int ret = LoadEXR(&res,&width,&height,filename,&err); + if (ret) throw CImgIOException(_cimg_instance + "load_exr(): Unable to load EXR file '%s'.", + cimg_instance,filename); + CImg(out,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this); + std::free(res); +#else + return load_other(filename); +#endif + return *this; + } + + //! Load image from a EXR file \newinstance. + static CImg get_load_exr(const char *const filename) { + return CImg().load_exr(filename); + } + + //! Load image from a PANDORE-5 file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_pandore(const char *const filename) { + return _load_pandore(0,filename); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(const char *const filename) { + return CImg().load_pandore(filename); + } + + //! Load image from a PANDORE-5 file \overloading. + CImg& load_pandore(std::FILE *const file) { + return _load_pandore(file,0); + } + + //! Load image from a PANDORE-5 file \newinstance. + static CImg get_load_pandore(std::FILE *const file) { + return CImg().load_pandore(file); + } + + CImg& _load_pandore(std::FILE *const file, const char *const filename) { +#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \ + cimg::fread(dims,nbdim,nfile); \ + if (endian) cimg::invert_endianness(dims,nbdim); \ + assign(nwidth,nheight,ndepth,ndim); \ + const size_t siz = size(); \ + stype *buffer = new stype[siz]; \ + cimg::fread(buffer,siz,nfile); \ + if (endian) cimg::invert_endianness(buffer,siz); \ + T *ptrd = _data; \ + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \ + buffer-=siz; \ + delete[] buffer + +#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \ + if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \ + else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \ + else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \ + else throw CImgIOException(_cimg_instance \ + "load_pandore(): Unknown pixel datatype in file '%s'.", \ + cimg_instance, \ + filename?filename:"(FILE*)"); } + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_pandore(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg header(32); + cimg::fread(header._data,12,nfile); + if (cimg::strncasecmp("PANDORE",header,7)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): PANDORE header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + unsigned int imageid, dims[8] = { 0 }; + int ptbuf[4] = { 0 }; + cimg::fread(&imageid,1,nfile); + const bool endian = imageid>255; + if (endian) cimg::invert_endianness(imageid); + cimg::fread(header._data,20,nfile); + + switch (imageid) { + case 2 : _cimg_load_pandore_case(2,dims[1],1,1,1,unsigned char,unsigned char,unsigned char,1); break; + case 3 : _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break; + case 4 : _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break; + case 5 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,unsigned char,unsigned char,unsigned char,1); break; + case 6 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break; + case 7 : _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break; + case 8 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,unsigned char,unsigned char,unsigned char,1); break; + case 9 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break; + case 10 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break; + case 11 : { // Region 1D + cimg::fread(dims,3,nfile); + if (endian) cimg::invert_endianness(dims,3); + assign(dims[1],1,1,1); + const unsigned siz = size(); + if (dims[2]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[2]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 12 : { // Region 2D + cimg::fread(dims,4,nfile); + if (endian) cimg::invert_endianness(dims,4); + assign(dims[2],dims[1],1,1); + const size_t siz = size(); + if (dims[3]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[3]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 13 : { // Region 3D + cimg::fread(dims,5,nfile); + if (endian) cimg::invert_endianness(dims,5); + assign(dims[3],dims[2],dims[1],1); + const size_t siz = size(); + if (dims[4]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[4]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = _data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 16 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,unsigned char,unsigned char,unsigned char,1); break; + case 17 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break; + case 18 : _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break; + case 19 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,unsigned char,unsigned char,unsigned char,1); break; + case 20 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break; + case 21 : _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break; + case 22 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 23 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4); break; + case 24 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 25 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break; + case 26 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned char,unsigned char,unsigned char,1); break; + case 27 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break; + case 28 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned long,unsigned int,unsigned short,4); break; + case 29 : _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break; + case 30 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned char,unsigned char,unsigned char,1); + break; + case 31 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break; + case 32 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned long,unsigned int,unsigned short,4); + break; + case 33 : _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break; + case 34 : { // Points 1D + cimg::fread(ptbuf,1,nfile); + if (endian) cimg::invert_endianness(ptbuf,1); + assign(1); (*this)(0) = (T)ptbuf[0]; + } break; + case 35 : { // Points 2D + cimg::fread(ptbuf,2,nfile); + if (endian) cimg::invert_endianness(ptbuf,2); + assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0]; + } break; + case 36 : { // Points 3D + cimg::fread(ptbuf,3,nfile); + if (endian) cimg::invert_endianness(ptbuf,3); + assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0]; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_pandore(): Unable to load data with ID_type %u in file '%s'.", + cimg_instance, + imageid,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image from a PAR-REC (Philips) file. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_parrec(const char *const filename, const char axis='c', const float align=0) { + CImgList list; + list.load_parrec(filename); + if (list._width==1) return list[0].move_to(*this); + return assign(list.get_append(axis,align)); + } + + //! Load image from a PAR-REC (Philips) file \newinstance. + static CImg get_load_parrec(const char *const filename, const char axis='c', const float align=0) { + return CImg().load_parrec(filename,axis,align); + } + + //! Load image from a raw binary file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the image buffer. + \param size_y Height of the image buffer. + \param size_z Depth of the image buffer. + \param size_c Spectrum of the image buffer. + \param is_multiplexed Tells if the image values are multiplexed along the C-axis. + \param invert_endianness Tells if the endianness of the image buffer must be inverted. + \param offset Starting offset of the read in the specified file. + **/ + CImg& load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(0,filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(const char *const filename, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \overloading. + CImg& load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return _load_raw(file,0,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + //! Load image from a raw binary file \newinstance. + static CImg get_load_raw(std::FILE *const file, + const unsigned int size_x=0, const unsigned int size_y=1, + const unsigned int size_z=1, const unsigned int size_c=1, + const bool is_multiplexed=false, const bool invert_endianness=false, + const ulongT offset=0) { + return CImg().load_raw(file,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); + } + + 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 ulongT offset) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename is (null).", + cimg_instance); + if (cimg::is_directory(filename)) + throw CImgArgumentException(_cimg_instance + "load_raw(): Specified filename '%s' is a directory.", + cimg_instance,filename); + + ulongT siz = (ulongT)size_x*size_y*size_z*size_c; + unsigned int + _size_x = size_x, + _size_y = size_y, + _size_z = size_z, + _size_c = size_c; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + if (!siz) { // Retrieve file size + const longT fpos = cimg::ftell(nfile); + if (fpos<0) throw CImgArgumentException(_cimg_instance + "load_raw(): Cannot determine size of input file '%s'.", + cimg_instance,filename?filename:"(FILE*)"); + cimg::fseek(nfile,0,SEEK_END); + siz = cimg::ftell(nfile)/sizeof(T); + _size_y = (unsigned int)siz; + _size_x = _size_z = _size_c = 1; + cimg::fseek(nfile,fpos,SEEK_SET); + } + cimg::fseek(nfile,offset,SEEK_SET); + assign(_size_x,_size_y,_size_z,_size_c,0); + if (siz && (!is_multiplexed || size_c==1)) { + cimg::fread(_data,siz,nfile); + if (invert_endianness) cimg::invert_endianness(_data,siz); + } else if (siz) { + CImg buf(1,1,1,_size_c); + cimg_forXYZ(*this,x,y,z) { + cimg::fread(buf._data,_size_c,nfile); + if (invert_endianness) cimg::invert_endianness(buf._data,_size_c); + set_vector_at(buf,x,y,z); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load image sequence from a YUV file. + /** + \param filename Filename, as a C-string. + \param size_x Width of the frames. + \param size_y Height of the frames. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param yuv2rgb Tells if the YUV to RGB transform must be applied. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + **/ + CImg& load_yuv(const char *const filename, + 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') { + return get_load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(const char *const filename, + 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') { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb).get_append(axis); + } + + //! Load image sequence from a YUV file \overloading. + CImg& load_yuv(std::FILE *const file, + 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') { + return get_load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); + } + + //! Load image sequence from a YUV file \newinstance. + static CImg get_load_yuv(std::FILE *const file, + 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') { + 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. + /** + \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 + CImg& load_off(CImgList& primitives, CImgList& colors, const char *const filename) { + return _load_off(primitives,colors,0,filename); + } + + //! 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. + 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. + template + static CImg get_load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { + return CImg().load_off(primitives,colors,file); + } + + template + CImg& _load_off(CImgList& primitives, CImgList& colors, + std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "load_off(): Specified filename is (null).", + cimg_instance); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0; + CImg line(256); *line = 0; + int err; + + // Skip comments, and read magic string OFF + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): OFF header not found in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Invalid number of vertices or primitives specified in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + } + + // Read points data + assign(nb_points,3); + float X = 0, Y = 0, Z = 0; + cimg_forX(*this,l) { + do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); + if ((err = cimg_sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "load_off(): Failed to read vertex %u/%u in file '%s'.", + cimg_instance, + l + 1,nb_points,filename?filename:"(FILE*)"); + } + (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z; + } + + // Read primitive data + primitives.assign(); + colors.assign(); + bool stop_flag = false; + while (!stop_flag) { + float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f; + unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0; + *line = 0; + if ((err = std::fscanf(nfile,"%u",&prim))!=1) stop_flag = true; + else { + ++nb_read; + switch (prim) { + case 1 : { + if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 2 : { + if ((err = std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line._data))<2) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 3 : { + if ((err = std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line._data))<3) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 4 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line._data))<4) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); + } + } break; + case 5 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line._data))<5) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 6 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line._data))<6) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 7 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i4,i3,i1).move_to(primitives); + CImg::vector(i0,i6,i5,i4).move_to(primitives); + CImg::vector(i3,i2,i1).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + case 8 : { + if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line._data))<7) { + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u from file '%s'.", + cimg_instance, + nb_read,nb_primitives,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); + CImg::vector(i0,i3,i2,i1).move_to(primitives); + CImg::vector(i0,i5,i4,i3).move_to(primitives); + CImg::vector(i0,i7,i6,i5).move_to(primitives); + colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + default : + cimg::warn(_cimg_instance + "load_off(): Failed to read primitive %u/%u (%u vertices) from file '%s'.", + cimg_instance, + nb_read,nb_primitives,prim,filename?filename:"(FILE*)"); + + err = std::fscanf(nfile,"%*[^\n] "); + } + } + } + if (!file) cimg::fclose(nfile); + if (primitives._width!=nb_primitives) + cimg::warn(_cimg_instance + "load_off(): Only %u/%u primitives read from file '%s'.", + cimg_instance, + primitives._width,nb_primitives,filename?filename:"(FILE*)"); + return *this; + } + + //! Load image sequence from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \param axis Alignment axis. + \param align Apending alignment. + **/ + CImg& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return get_load_video(filename,first_frame,last_frame,step_frame,axis,align).move_to(*this); + } + + //! Load image sequence from a video file, using OpenCV library \newinstance. + static CImg get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, + const char axis='z', const float align=0) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame).get_append(axis,align); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg'. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return get_load_ffmpeg_external(filename,axis,align).move_to(*this); + } + + //! Load image sequence using FFMPEG's external tool 'ffmpeg' \newinstance. + static CImg get_load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { + return CImgList().load_ffmpeg_external(filename).get_append(axis,align); + } + + //! Load gif file, using Imagemagick or GraphicsMagicks's external tools. + /** + \param filename Filename, as a C-string. + \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg& load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return get_load_gif_external(filename,axis,align).move_to(*this); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tool 'convert' \newinstance. + static CImg get_load_gif_external(const char *const filename, + const char axis='z', const float align=0) { + return CImgList().load_gif_external(filename).get_append(axis,align); + } + + //! Load image using GraphicsMagick's external tool 'gm'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_graphicsmagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + 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()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_graphicsmagick_external(): Failed to load file '%s' " + "with external command 'gm'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=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::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::graphicsmagick_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using GraphicsMagick's external tool 'gm' \newinstance. + static CImg get_load_graphicsmagick_external(const char *const filename) { + return CImg().load_graphicsmagick_external(filename); + } + + //! Load gzipped image file, using external tool 'gunzip'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimg_instance + "load_gzip_external(): Specified filename is (null).", + cimg_instance); + 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), + *const ext2 = cimg::split_filename(body,0); + + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load gzipped image file, using external tool 'gunzip' \newinstance. + static CImg get_load_gzip_external(const char *const filename) { + return CImg().load_gzip_external(filename); + } + + //! Load image using ImageMagick's external tool 'convert'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_imagemagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_imagemagick_external(): Specified filename is (null).", + cimg_instance); + 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::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_imagemagick_external(): Failed to load file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s%s \"%s\" \"%s\"", + cimg::imagemagick_path(), + !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", + s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::imagemagick_path()); + 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 " + "external command 'magick/convert'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image using ImageMagick's external tool 'convert' \newinstance. + static CImg get_load_imagemagick_external(const char *const filename) { + return CImg().load_imagemagick_external(filename); + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_medcon_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_medcon_external(): Specified filename is (null).", + cimg_instance); + 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=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::medcon_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + cimg::split_filename(filename_tmp,body); + + cimg_snprintf(command,command._width,"%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s.hdr",body._data); + file = cimg::std_fopen(command,"rb"); + if (!file) { + throw CImgIOException(_cimg_instance + "load_medcon_external(): Failed to load file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + load_analyze(command); + std::remove(command); + cimg::split_filename(command,body); + cimg_snprintf(command,command._width,"%s.img",body._data); + std::remove(command); + return *this; + } + + //! Load image from a DICOM file, using XMedcon's external tool 'medcon' \newinstance. + static CImg get_load_medcon_external(const char *const filename) { + return CImg().load_medcon_external(filename); + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw'. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_dcraw_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_dcraw_external(): Specified filename is (null).", + cimg_instance); + 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 + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\"", + cimg::dcraw_path(),s_filename.data()); + file = popen(command,"r"); + if (file) { + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_pnm(file); } catch (...) { + pclose(file); + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_dcraw_external(): Failed to load file '%s' with external command 'dcraw'.", + cimg_instance, + filename); + } + pclose(file); + return *this; + } +#endif + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.ppm", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\" > \"%s\"", + cimg::dcraw_path(),s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,cimg::dcraw_path()); + 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'.", + cimg_instance, + filename); + + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image from a RAW Color Camera file, using external tool 'dcraw' \newinstance. + static CImg get_load_dcraw_external(const char *const filename) { + return CImg().load_dcraw_external(filename); + } + + //! Load image from a camera stream, using OpenCV. + /** + \param camera_index Index of the camera to capture images from. + \param skip_frames Number of frames to skip before the capture. + \param release_camera Tells if the camera ressource must be released at the end of the method. + \param capture_width Width of the desired image. + \param capture_height Height of the desired image. + **/ + 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) { +#ifdef cimg_use_opencv + if (camera_index>99) + throw CImgArgumentException(_cimg_instance + "load_camera(): Invalid request for camera #%u " + "(no more than 100 cameras can be managed simultaneously).", + cimg_instance, + camera_index); + static CvCapture *capture[100] = { 0 }; + static unsigned int capture_w[100], capture_h[100]; + if (release_camera) { + cimg::mutex(9); + if (capture[camera_index]) cvReleaseCapture(&(capture[camera_index])); + capture[camera_index] = 0; + capture_w[camera_index] = capture_h[camera_index] = 0; + cimg::mutex(9,0); + return *this; + } + if (!capture[camera_index]) { + cimg::mutex(9); + capture[camera_index] = cvCreateCameraCapture(camera_index); + capture_w[camera_index] = 0; + capture_h[camera_index] = 0; + cimg::mutex(9,0); + if (!capture[camera_index]) { + throw CImgIOException(_cimg_instance + "load_camera(): Failed to initialize camera #%u.", + cimg_instance, + camera_index); + } + } + cimg::mutex(9); + if (capture_width!=capture_w[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_WIDTH,capture_width); + capture_w[camera_index] = capture_width; + } + if (capture_height!=capture_h[camera_index]) { + cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_HEIGHT,capture_height); + capture_h[camera_index] = capture_height; + } + const IplImage *img = 0; + for (unsigned int i = 0; 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++); + } + } + cimg::mutex(9,0); + return *this; +#else + cimg::unused(camera_index,skip_frames,release_camera,capture_width,capture_height); + throw CImgIOException(_cimg_instance + "load_camera(): This function requires the OpenCV library to run " + "(macro 'cimg_use_opencv' must be defined).", + cimg_instance); +#endif + } + + //! Load image from a camera stream, using OpenCV \newinstance. + static CImg 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); + } + + //! Load image using various non-native ways. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_other(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_other(): Specified filename is (null).", + cimg_instance); + + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + try { load_magick(filename); } + catch (CImgException&) { + try { load_imagemagick_external(filename); } + catch (CImgException&) { + try { load_graphicsmagick_external(filename); } + catch (CImgException&) { + try { load_cimg(filename); } + catch (CImgException&) { + try { + cimg::fclose(cimg::fopen(filename,"rb")); + } catch (CImgException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to open file '%s'.", + cimg_instance, + filename); + } + cimg::exception_mode(omode); + throw CImgIOException(_cimg_instance + "load_other(): Failed to recognize format of file '%s'.", + cimg_instance, + filename); + } + } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load image using various non-native ways \newinstance. + static CImg get_load_other(const char *const filename) { + return CImg().load_other(filename); + } + + //@} + //--------------------------- + // + //! \name Data Output + //@{ + //--------------------------- + + //! Display information about the image data. + /** + \param title Name for the considered image. + \param display_stats Tells to compute and display image statistics. + **/ + const CImg& print(const char *const title=0, const bool display_stats=true) const { + + int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0; + CImg st; + if (!is_empty() && display_stats) { + st = get_stats(); + xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7]; + xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11]; + } + + const ulongT siz = size(), msiz = siz*sizeof(T), siz1 = siz - 1, + mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U, width1 = _width - 1; + + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImg<%s>",pixel_type()); + + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = (%u,%u,%u,%u) [%lu %s], %sdata%s = (%s*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum, + (unsigned long)(mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20))), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) + std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end() - 1),_is_shared?"shared":"non-shared"); + else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared"); + + if (!is_empty()) cimg_foroff(*this,off) { + std::fprintf(cimg::output(),"%g",(double)_data[off]); + if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" "); + if (off==7 && siz>16) { off = siz1 - 8; std::fprintf(cimg::output(),"... "); } + } + if (!is_empty() && display_stats) + std::fprintf(cimg::output(), + " ], %smin%s = %g, %smax%s = %g, %smean%s = %g, %sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), " + "%scoords_max%s = (%u,%u,%u,%u).\n", + cimg::t_bold,cimg::t_normal,st[0], + cimg::t_bold,cimg::t_normal,st[1], + cimg::t_bold,cimg::t_normal,st[2], + cimg::t_bold,cimg::t_normal,std::sqrt(st[3]), + cimg::t_bold,cimg::t_normal,xm,ym,zm,vm, + cimg::t_bold,cimg::t_normal,xM,yM,zM,vM); + else std::fprintf(cimg::output(),"%s].\n",is_empty()?"":" "); + std::fflush(cimg::output()); + return *this; + } + + //! Display image into a CImgDisplay window. + /** + \param disp Display window. + **/ + const CImg& display(CImgDisplay& disp) const { + disp.display(*this); + return *this; + } + + //! Display image into a CImgDisplay window, in an interactive way. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(CImgDisplay &disp, const bool display_info, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + return _display(disp,0,display_info,XYZ,exit_on_anykey,false); + } + + //! Display image into an interactive window. + /** + \param title Window title + \param display_info Tells if image information are displayed on the standard output. + \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. + **/ + const CImg& display(const char *const title=0, const bool display_info=true, unsigned int *const XYZ=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display(disp,title,display_info,XYZ,exit_on_anykey,false); + } + + const CImg& _display(CImgDisplay &disp, const char *const title, const bool display_info, + unsigned int *const XYZ, const bool exit_on_anykey, + const bool exit_on_simpleclick) const { + unsigned int oldw = 0, oldh = 0, _XYZ[3] = { 0 }, key = 0; + int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1, + old_mouse_x = -1, old_mouse_y = -1; + + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); + if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); + else disp.set_title("%s",title); + } else if (title) disp.set_title("%s",title); + disp.show().flush(); + + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(dtitle); + + CImg zoom; + for (bool reset_view = true, resize_disp = false, is_first_select = true; !key && !disp.is_closed(); ) { + if (reset_view) { + if (XYZ) { _XYZ[0] = XYZ[0]; _XYZ[1] = XYZ[1]; _XYZ[2] = XYZ[2]; } + else { + _XYZ[0] = (unsigned int)(x0 + x1)/2; + _XYZ[1] = (unsigned int)(y0 + y1)/2; + _XYZ[2] = (unsigned int)(z0 + z1)/2; + } + x0 = 0; y0 = 0; z0 = 0; x1 = width() - 1; y1 = height() - 1; z1 = depth() - 1; + 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) { + if (is_empty()) zoom.assign(1,1,1,1,(T)0); else zoom.assign(); + } else zoom = get_crop(x0,y0,z0,x1,y1,z1); + + const CImg& visu = zoom?zoom:*this; + const unsigned int + dx = 1U + x1 - x0, dy = 1U + y1 - y0, dz = 1U + z1 - z0, + tw = dx + (dz>1?dz:0U), th = dy + (dz>1?dz:0U); + if (!is_empty() && !disp.is_fullscreen() && resize_disp) { + const 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 + 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; + } + oldw = tw; oldh = th; + + bool + go_up = false, go_down = false, go_left = false, go_right = false, + go_inc = false, go_dec = false, go_in = false, go_out = false, + go_in_center = false; + + disp.set_title("%s",dtitle._data); + if (_width>1 && visu._width==1) disp.set_title("%s | x=%u",disp._title,x0); + if (_height>1 && visu._height==1) disp.set_title("%s | y=%u",disp._title,y0); + if (_depth>1 && visu._depth==1) disp.set_title("%s | z=%u",disp._title,z0); + + disp._mouse_x = old_mouse_x; disp._mouse_y = old_mouse_y; + CImg selection = visu._select(disp,0,2,_XYZ,x0,y0,z0,true,is_first_select,_depth>1,true); + old_mouse_x = disp._mouse_x; old_mouse_y = disp._mouse_y; + is_first_select = false; + + if (disp.wheel()) { + if ((disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) && + (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT())) { + go_left = !(go_right = disp.wheel()>0); + } else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) { + go_down = !(go_up = disp.wheel()>0); + } else if (depth()==1 || disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + go_out = !(go_in = disp.wheel()>0); go_in_center = false; + } + disp.set_wheel(); + } + + const int + sx0 = selection(0), sy0 = selection(1), sz0 = selection(2), + sx1 = selection(3), sy1 = selection(4), sz1 = selection(5); + if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) { + x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; + x0+=sx0; y0+=sy0; z0+=sz0; + if ((sx0==sx1 && sy0==sy1) || (_depth>1 && sx0==sx1 && sz0==sz1) || (_depth>1 && sy0==sy1 && sz0==sz1)) { + if (exit_on_simpleclick && (!zoom || is_empty())) break; else reset_view = true; + } + resize_disp = true; + } else switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : key = 0; break; + case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) { + // Special mode: play stack of frames + const unsigned int + w1 = visu._width*disp.width()/(visu._width + (visu._depth>1?visu._depth:0)), + h1 = visu._height*disp.height()/(visu._height + (visu._depth>1?visu._depth:0)); + float frame_timing = 5; + bool is_stopped = false; + disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0; + for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) { + if (disp.is_resized()) disp.resize(false); + if (!timer) { + visu.get_slice((int)_XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),_XYZ[2])); + (++_XYZ[2])%=visu._depth; + } + if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U; + if (disp.wheel()) { frame_timing-=disp.wheel()/3.f; disp.set_wheel(); } + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break; + case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break; + case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true; + (_XYZ[2]+=visu._depth - 2)%=visu._depth; timer = 0; key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false). + toggle_fullscreen().set_key(key,false); key = 0; + } break; + } + frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing); + disp.wait(20); + } + const unsigned int + w2 = (visu._width + (visu._depth>1?visu._depth:0))*disp.width()/visu._width, + h2 = (visu._height + (visu._depth>1?visu._depth:0))*disp.height()/visu._height; + disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(dtitle.data()).set_key().set_button().set_wheel(); + key = 0; + } break; + case cimg::keyHOME : reset_view = resize_disp = true; key = 0; break; + case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break; + case cimg::keyPADSUB : go_out = true; key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break; + case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break; + case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break; + case cimg::keyPAD7 : go_up = go_left = true; key = 0; break; + case cimg::keyPAD9 : go_up = go_right = true; key = 0; break; + case cimg::keyPAD1 : go_down = go_left = true; key = 0; break; + case cimg::keyPAD3 : go_down = go_right = true; key = 0; break; + case cimg::keyPAGEUP : go_inc = true; key = 0; break; + case cimg::keyPAGEDOWN : go_dec = true; key = 0; break; + } + if (go_in) { + const int + mx = go_in_center?disp.width()/2:disp.mouse_x(), + my = go_in_center?disp.height()/2:disp.mouse_y(), + mX = mx*(width() + (depth()>1?depth():0))/disp.width(), + mY = my*(height() + (depth()>1?depth():0))/disp.height(); + int X = (int)_XYZ[0], Y = (int)_XYZ[1], Z = (int)_XYZ[2]; + if (mX=height()) { + X = x0 + mX*(1 + x1 - x0)/width(); Z = z0 + (mY - height())*(1 + z1 - z0)/depth(); + } + if (mX>=width() && mY4) { x0 = X - 3*(X - x0)/4; x1 = X + 3*(x1 - X)/4; } + if (y1 - y0>4) { y0 = Y - 3*(Y - y0)/4; y1 = Y + 3*(y1 - Y)/4; } + if (z1 - z0>4) { z0 = Z - 3*(Z - z0)/4; z1 = Z + 3*(z1 - Z)/4; } + } + if (go_out) { + const int + delta_x = (x1 - x0)/8, delta_y = (y1 - y0)/8, delta_z = (z1 - z0)/8, + ndelta_x = delta_x?delta_x:(_width>1), + ndelta_y = delta_y?delta_y:(_height>1), + ndelta_z = delta_z?delta_z:(_depth>1); + x0-=ndelta_x; y0-=ndelta_y; z0-=ndelta_z; + x1+=ndelta_x; y1+=ndelta_y; z1+=ndelta_z; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=width()) x1 = width() - 1; } + if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; } + if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; } + if (x1>=width()) { x0-=(x1 - width() + 1); x1 = width() - 1; if (x0<0) x0 = 0; } + if (y1>=height()) { y0-=(y1 - height() + 1); y1 = height() - 1; if (y0<0) y0 = 0; } + if (z1>=depth()) { z0-=(z1 - depth() + 1); z1 = depth() - 1; if (z0<0) z0 = 0; } + const float + ratio = (float)(x1-x0)/(y1-y0), + ratiow = (float)disp._width/disp._height, + sub = std::min(cimg::abs(ratio - ratiow),cimg::abs(1/ratio-1/ratiow)); + if (sub>0.01) resize_disp = true; + } + if (go_left) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + } + if (go_right) { + const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); + if (x1+ndelta1); + if (y0 - ndelta>=0) { y0-=ndelta; y1-=ndelta; } + else { y1-=y0; y0 = 0; } + } + if (go_down) { + const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1); + if (y1+ndelta1); + if (z0 - ndelta>=0) { z0-=ndelta; z1-=ndelta; } + else { z1-=z0; z0 = 0; } + } + if (go_dec) { + const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1); + if (z1+ndelta + const CImg& display_object3d(CImgDisplay& disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return _display_object3d(disp,0,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,render_static, + render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,colors,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + const CImg& vertices, + const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool is_double_sided=true, const float focale=700, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,primitives,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(CImgDisplay &disp, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(disp,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + //! Display object 3D in an interactive window \simplification. + template + const CImg& display_object3d(const char *const title, + 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, + const float light_x=0, const float light_y=0, const float light_z=-5e8f, + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const bool display_axes=true, float *const pose_matrix=0, + const bool exit_on_anykey=false) const { + return display_object3d(title,vertices,CImgList(),centering, + render_static,render_motion,is_double_sided,focale, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + + template + const CImg& _display_object3d(CImgDisplay& disp, const char *const title, + 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 float light_x, const float light_y, const float light_z, + const float specular_lightness, const float specular_shininess, + const bool display_axes, float *const pose_matrix, + const bool exit_on_anykey) const { + typedef typename cimg::superset::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); + } 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).", + 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, + light_x,light_y,light_z,specular_lightness,specular_shininess, + display_axes,pose_matrix,exit_on_anykey); + } + if (!disp) { + disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3); + if (!title) disp.set_title("CImg<%s> (%u vertices, %u primitives)", + pixel_type(),vertices._width,primitives._width); + } else if (title) disp.set_title("%s",title); + + // Init 3D objects and compute object statistics + CImg + pose, + rotated_vertices(vertices._width,3), + bbox_vertices, rotated_bbox_vertices, + axes_vertices, rotated_axes_vertices, + bbox_opacities, axes_opacities; + CImgList bbox_primitives, axes_primitives; + CImgList reverse_primitives; + CImgList bbox_colors, bbox_colors2, axes_colors; + unsigned int ns_width = 0, ns_height = 0; + int _is_double_sided = (int)is_double_sided; + bool ndisplay_axes = display_axes; + const CImg + background_color(1,1,1,_spectrum,0), + foreground_color(1,1,1,_spectrum,255); + float + Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1, + xm = 0, xM = vertices?vertices.get_shared_row(0).max_min(xm):0, + ym = 0, yM = vertices?vertices.get_shared_row(1).max_min(ym):0, + zm = 0, zM = vertices?vertices.get_shared_row(2).max_min(zm):0; + const float delta = cimg::max(xM - xm,yM - ym,zM - zm); + + rotated_bbox_vertices = bbox_vertices.assign(8,3,1,1, + xm,xM,xM,xm,xm,xM,xM,xm, + ym,ym,yM,yM,ym,ym,yM,yM, + zm,zm,zm,zm,zM,zM,zM,zM); + bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6); + bbox_colors.assign(6,_spectrum,1,1,1,background_color[0]); + bbox_colors2.assign(6,_spectrum,1,1,1,foreground_color[0]); + bbox_opacities.assign(bbox_colors._width,1,1,1,0.3f); + + rotated_axes_vertices = axes_vertices.assign(7,3,1,1, + 0,20,0,0,22,-6,-6, + 0,0,20,0,-6,22,-6, + 0,0,0,20,0,0,22); + axes_opacities.assign(3,1,1,1,1); + axes_colors.assign(3,_spectrum,1,1,1,foreground_color[0]); + axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3); + + // Begin user interaction loop + CImg 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; + int + x0 = 0, y0 = 0, x1 = 0, y1 = 0, + nrender_static = render_static, + nrender_motion = render_motion; + disp.show().flush(); + + while (!disp.is_closed() && !key) { + + // Init object pose + if (init_pose) { + const float + ratio = delta>0?(2.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); + else CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose); + if (pose_matrix) { + CImg pose0(pose_matrix,4,3,1,1,false); + pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0); + pose0(3,3) = pose(3,3) = 1; + (pose0*pose).get_crop(0,0,3,2).move_to(pose); + Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15]; + } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; } + init_pose = false; + redraw = true; + } + + // Rotate and draw 3D object + if (redraw) { + const float + r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0), + r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1), + r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2); + if ((clicked && nrender_motion>=0) || (!clicked && nrender_static>=0)) + cimg_forX(vertices,l) { + const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2); + rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + else cimg_forX(bbox_vertices,l) { + const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2); + rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30; + rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31; + rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32; + } + + // Draw objects + const bool render_with_zbuffer = !clicked && nrender_static>0; + visu = visu0; + if ((clicked && nrender_motion<0) || (!clicked && nrender_static<0)) + visu.draw_object3d(Xoff + visu._width/2.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.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.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.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 + n = 1e-8f + cimg::hypot(r00,r01,r02), + _r00 = r00/n, _r10 = r10/n, _r20 = r20/n, + _r01 = r01/n, _r11 = r11/n, _r21 = r21/n, + _r02 = r01/n, _r12 = r12/n, _r22 = r22/n, + Xaxes = 25, Yaxes = visu._height - 38.f; + cimg_forX(axes_vertices,l) { + const float + x = axes_vertices(l,0), + y = axes_vertices(l,1), + z = axes_vertices(l,2); + rotated_axes_vertices(l,0) = _r00*x + _r10*y + _r20*z; + rotated_axes_vertices(l,1) = _r01*x + _r11*y + _r21*z; + rotated_axes_vertices(l,2) = _r02*x + _r12*y + _r22*z; + } + axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.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)), + (int)(Yaxes + rotated_axes_vertices(4,1)), + "X",axes_colors[0]._data,0,axes_opacities(0,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(5,0)), + (int)(Yaxes + rotated_axes_vertices(5,1)), + "Y",axes_colors[1]._data,0,axes_opacities(1,0),13). + draw_text((int)(Xaxes + rotated_axes_vertices(6,0)), + (int)(Yaxes + rotated_axes_vertices(6,1)), + "Z",axes_colors[2]._data,0,axes_opacities(2,0),13); + } + visu.display(disp); + if (!clicked || nrender_motion==nrender_static) redraw = false; + } + + // Handle user interaction + disp.wait(); + if ((disp.button() || disp.wheel()) && disp.mouse_x()>=0 && disp.mouse_y()>=0) { + redraw = true; + if (!clicked) { x0 = x1 = disp.mouse_x(); y0 = y1 = disp.mouse_y(); if (!disp.wheel()) clicked = true; } + else { x1 = disp.mouse_x(); y1 = disp.mouse_y(); } + if (disp.button()&1) { + const float + R = 0.45f*std::min(disp.width(),disp.height()), + R2 = R*R, + u0 = (float)(x0 - disp.width()/2), + v0 = (float)(y0 - disp.height()/2), + u1 = (float)(x1 - disp.width()/2), + v1 = (float)(y1 - disp.height()/2), + n0 = cimg::hypot(u0,v0), + n1 = cimg::hypot(u1,v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)std::sqrt(std::max(0.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.f,R2 - nu1*nu1 - nv1*nv1)), + u = nv0*nw1 - nw0*nv1, + v = nw0*nu1 - nu0*nw1, + w = nv0*nu1 - nu0*nv1, + n = cimg::hypot(u,v,w), + alpha = (float)std::asin(n/R2)*180/cimg::PI; + (CImg::rotation_matrix(u,v,w,-alpha)*pose).move_to(pose); + x0 = x1; y0 = y1; + } + if (disp.button()&2) { + if (focale>0) Zoff-=(y0 - y1)*focale/400; + else { const float s = std::exp((y0 - y1)/400.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.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)) { + 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); + } + } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; } + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD: if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + if (!ns_width || !ns_height || + ns_width>(unsigned int)disp.screen_width() || ns_height>(unsigned int)disp.screen_height()) { + ns_width = disp.screen_width()*3U/4; + ns_height = disp.screen_height()*3U/4; + } + if (disp.is_fullscreen()) disp.resize(ns_width,ns_height,false); + else { + ns_width = disp._width; ns_height = disp._height; + disp.resize(disp.screen_width(),disp.screen_height(),false); + } + disp.toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; + } break; + case cimg::keyT : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Switch single/double-sided primitives. + if (--_is_double_sided==-2) _is_double_sided = 1; + if (_is_double_sided>=0) reverse_primitives.assign(); + else primitives.get_reverse_object3d().move_to(reverse_primitives); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyZ : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Enable/disable Z-buffer + if (zbuffer) zbuffer.assign(); + else zbuffer.assign(visu0.width(),visu0.height(),1,1,0); + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Show/hide 3D axes + ndisplay_axes = !ndisplay_axes; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF1 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to points + nrender_motion = (nrender_static==0 && nrender_motion!=0)?0:-1; nrender_static = 0; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF2 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to lines + nrender_motion = (nrender_static==1 && nrender_motion!=1)?1:-1; nrender_static = 1; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF3 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat + nrender_motion = (nrender_static==2 && nrender_motion!=2)?2:-1; nrender_static = 2; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF4 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat-shaded + nrender_motion = (nrender_static==3 && nrender_motion!=3)?3:-1; nrender_static = 3; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF5 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + // Set rendering mode to gouraud-shaded. + nrender_motion = (nrender_static==4 && nrender_motion!=4)?4:-1; nrender_static = 4; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyF6 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to phong-shaded + nrender_motion = (nrender_static==5 && nrender_motion!=5)?5:-1; nrender_static = 5; + disp.set_key(key,false); key = 0; redraw = true; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save snapshot + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving snapshot... ",false).display(disp); + visu.save(filename); + (+visu).__draw_text(" Snapshot '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.save_off(reverse_primitives?reverse_primitives:primitives,colors,filename); + (+visu).__draw_text(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",false).display(disp); + vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities). + save(filename); + (+visu).__draw_text(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving EPS snapshot... ",false).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,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=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving SVG snapshot... ",false,13).display(disp); + LibBoard::Board board; + (+visu)._draw_object3d(&board,zbuffer.fill(0), + 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.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(" Object '%s' saved. ",false,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; +#endif + } + if (disp.is_resized()) { + disp.resize(false); visu0 = get_resize(disp,1); + if (zbuffer) zbuffer.assign(disp.width(),disp.height()); + redraw = true; + } + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + if (pose_matrix) { + std::memcpy(pose_matrix,pose._data,12*sizeof(float)); + pose_matrix[12] = Xoff; pose_matrix[13] = Yoff; pose_matrix[14] = Zoff; pose_matrix[15] = sprite_scale; + } + disp.set_button().set_key(key); + return *this; + } + + //! Display 1D graph in an interactive window. + /** + \param disp Display window. + \param plot_type Plot type. Can be { 0=points | 1=segments | 2=splines | 3=bars }. + \param vertex_type Vertex type. + \param labelx Title for the horizontal axis, as a C-string. + \param xmin Minimum value along the X-axis. + \param xmax Maximum value along the X-axis. + \param labely Title for the vertical axis, as a C-string. + \param ymin Minimum value along the X-axis. + \param ymax Maximum value along the X-axis. + \param exit_on_anykey Exit function when any key is pressed. + **/ + const CImg& display_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + return _display_graph(disp,0,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + //! Display 1D graph in an interactive window \overloading. + const CImg& display_graph(const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _display_graph(disp,title,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); + } + + const CImg& _display_graph(CImgDisplay &disp, const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0, + const bool exit_on_anykey=false) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "display_graph(): Empty instance.", + cimg_instance); + if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). + set_title(title?"%s":"CImg<%s>",title?title:pixel_type()); + const ulongT siz = (ulongT)_width*_height*_depth, siz1 = std::max((ulongT)1,siz - 1); + const unsigned int old_normalization = disp.normalization(); + disp.show().flush()._normalization = 0; + + double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax; + if (nxmin==nxmax) { nxmin = 0; nxmax = siz1; } + int x0 = 0, x1 = width()*height()*depth() - 1, key = 0; + + for (bool reset_view = true; !key && !disp.is_closed(); ) { + if (reset_view) { x0 = 0; x1 = width()*height()*depth() - 1; y0 = ymin; y1 = ymax; reset_view = false; } + CImg zoom(x1 - x0 + 1,1,1,spectrum()); + cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg(data(x0,0,0,c),x1 - x0 + 1,1,1,1,true); + if (y0==y1) { y0 = zoom.min_max(y1); const double dy = y1 - y0; y0-=dy/20; y1+=dy/20; } + if (y0==y1) { --y0; ++y1; } + + const CImg selection = zoom.get_select_graph(disp,plot_type,vertex_type, + labelx, + nxmin + x0*(nxmax - nxmin)/siz1, + nxmin + x1*(nxmax - nxmin)/siz1, + labely,y0,y1,true); + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); + if (selection[0]>=0) { + if (selection[2]<0) reset_view = true; + else { + x1 = x0 + selection[2]; x0+=selection[0]; + if (selection[1]>=0 && selection[3]>=0) { + y0 = y1 - selection[3]*(y1 - y0)/(disp.height() - 32); + y1-=selection[1]*(y1 - y0)/(disp.height() - 32); + } + } + } else { + bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false; + switch (key = (int)disp.key()) { + case cimg::keyHOME : reset_view = true; key = 0; disp.set_key(); break; + case cimg::keyPADADD : go_in = true; go_out = false; key = 0; disp.set_key(); break; + case cimg::keyPADSUB : go_out = true; go_in = false; key = 0; disp.set_key(); break; + case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; go_right = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; go_left = false; key = 0; disp.set_key(); + break; + case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; go_down = false; key = 0; disp.set_key(); break; + case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; go_up = false; key = 0; disp.set_key(); break; + case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; disp.set_key(); break; + case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; disp.set_key(); break; + case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; disp.set_key(); break; + } + if (disp.wheel()) { + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) go_up = !(go_down = disp.wheel()<0); + else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0); + else go_out = !(go_in = disp.wheel()>0); + key = 0; + } + + if (go_in) { + const int + xsiz = x1 - x0, + mx = (mouse_x - 16)*xsiz/(disp.width() - 32), + cx = x0 + cimg::cut(mx,0,xsiz); + if (x1 - x0>4) { + x0 = cx - 7*(cx - x0)/8; x1 = cx + 7*(x1 - cx)/8; + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + const double + ysiz = y1 - y0, + my = (mouse_y - 16)*ysiz/(disp.height() - 32), + cy = y1 - cimg::cut(my,0.,ysiz); + y0 = cy - 7*(cy - y0)/8; y1 = cy + 7*(y1 - cy)/8; + } else y0 = y1 = 0; + } + } + if (go_out) { + if (x0>0 || x1<(int)siz1) { + const int delta_x = (x1 - x0)/8, ndelta_x = delta_x?delta_x:(siz>1); + const double ndelta_y = (y1 - y0)/8; + x0-=ndelta_x; x1+=ndelta_x; + y0-=ndelta_y; y1+=ndelta_y; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz1; } + if (x1>=(int)siz) { x0-=(x1 - siz1); x1 = (int)siz1; if (x0<0) x0 = 0; } + } + } + if (go_left) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + go_left = false; + } + if (go_right) { + const int delta = (x1 - x0)/5, ndelta = delta?delta:1; + if (x1 + ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; } + else { x0+=(siz1 - x1); x1 = (int)siz1; } + go_right = false; + } + if (go_up) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0+=ndelta; y1+=ndelta; + go_up = false; + } + if (go_down) { + const double delta = (y1 - y0)/10, ndelta = delta?delta:1; + y0-=ndelta; y1-=ndelta; + go_down = false; + } + } + if (!exit_on_anykey && key && key!=(int)cimg::keyESC && + (key!=(int)cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + disp.set_key(key,false); + key = 0; + } + } + disp._normalization = old_normalization; + return *this; + } + + //! Save image as a file. + /** + \param filename Filename, as a C-string. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + \note + - The used file format is defined by the file extension in the filename \p filename. + - Parameter \p number can be used to add a 6-digit number to the filename before saving. + + **/ + const CImg& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save(): Specified filename is (null).", + cimg_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:(number>=0)?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimg_save_plugin + cimg_save_plugin(fn); +#endif +#ifdef cimg_save_plugin1 + cimg_save_plugin1(fn); +#endif +#ifdef cimg_save_plugin2 + cimg_save_plugin2(fn); +#endif +#ifdef cimg_save_plugin3 + cimg_save_plugin3(fn); +#endif +#ifdef cimg_save_plugin4 + cimg_save_plugin4(fn); +#endif +#ifdef cimg_save_plugin5 + cimg_save_plugin5(fn); +#endif +#ifdef cimg_save_plugin6 + cimg_save_plugin6(fn); +#endif +#ifdef cimg_save_plugin7 + cimg_save_plugin7(fn); +#endif +#ifdef cimg_save_plugin8 + cimg_save_plugin8(fn); +#endif + // Ascii formats + if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn); + else if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) return save_dlm(fn); + else if (!cimg::strcasecmp(ext,"cpp") || + !cimg::strcasecmp(ext,"hpp") || + !cimg::strcasecmp(ext,"h") || + !cimg::strcasecmp(ext,"c")) return save_cpp(fn); + + // 2D binary formats + else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn); + else if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn); + else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn); + else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn); + else if (!cimg::strcasecmp(ext,"png")) return save_png(fn); + else if (!cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn); + else if (!cimg::strcasecmp(ext,"pnk")) return save_pnk(fn); + else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn); + else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn); + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); + + // 3D binary formats + else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn); + else if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) return save_analyze(fn); + else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn); + else if (!cimg::strcasecmp(ext,"mnc")) return save_minc2(fn); + else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn); + else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn); + + // Archive files + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + + // Image sequences + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); + return save_other(fn); + } + + //! Save image as an Ascii file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_ascii(const char *const filename) const { + return _save_ascii(0,filename); + } + + //! Save image as an Ascii file \overloading. + const CImg& save_ascii(std::FILE *const file) const { + return _save_ascii(file,0); + } + + const CImg& _save_ascii(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_ascii(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + std::fprintf(nfile,"%u %u %u %u\n",_width,_height,_depth,_spectrum); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g ",(double)*(ptrs++)); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .cpp source file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_cpp(const char *const filename) const { + return _save_cpp(0,filename); + } + + //! Save image as a .cpp source file \overloading. + const CImg& save_cpp(std::FILE *const file) const { + return _save_cpp(file,0); + } + + const CImg& _save_cpp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_cpp(): Specified filename is (null).", + cimg_instance); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + CImg varname(1024); *varname = 0; + if (filename) cimg_sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname._data); + if (!*varname) cimg_snprintf(varname,varname._width,"unnamed"); + std::fprintf(nfile, + "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n" + "%s data_%s[] = { %s\n ", + varname._data,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname._data, + is_empty()?"};":""); + if (!is_empty()) for (ulongT off = 0, siz = size() - 1; off<=siz; ++off) { + std::fprintf(nfile,cimg::type::format(),cimg::type::format((*this)[off])); + if (off==siz) std::fprintf(nfile," };\n"); + else if (!((off + 1)%16)) std::fprintf(nfile,",\n "); + else std::fprintf(nfile,", "); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a DLM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_dlm(const char *const filename) const { + return _save_dlm(0,filename); + } + + //! Save image as a DLM file \overloading. + const CImg& save_dlm(std::FILE *const file) const { + return _save_dlm(file,0); + } + + const CImg& _save_dlm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_dlm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is volumetric, values along Z will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_dlm(): Instance is multispectral, values along C will be unrolled in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + const T* ptrs = _data; + cimg_forYZC(*this,y,z,c) { + cimg_forX(*this,x) std::fprintf(nfile,"%.17g%s",(double)*(ptrs++),(x==width() - 1)?"":","); + std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a BMP file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_bmp(const char *const filename) const { + return _save_bmp(0,filename); + } + + //! Save image as a BMP file \overloading. + const CImg& save_bmp(std::FILE *const file) const { + return _save_bmp(file,0); + } + + const CImg& _save_bmp(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_bmp(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_bmp(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_bmp(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(54,1,1,1,0); + unsigned char align_buf[4] = { 0 }; + const unsigned int + align = (4 - (3*_width)%4)%4, + buf_size = (3*_width + align)*height(), + file_size = 54 + buf_size; + header[0] = 'B'; header[1] = 'M'; + header[0x02] = file_size&0xFF; + header[0x03] = (file_size>>8)&0xFF; + header[0x04] = (file_size>>16)&0xFF; + header[0x05] = (file_size>>24)&0xFF; + header[0x0A] = 0x36; + header[0x0E] = 0x28; + header[0x12] = _width&0xFF; + header[0x13] = (_width>>8)&0xFF; + header[0x14] = (_width>>16)&0xFF; + header[0x15] = (_width>>24)&0xFF; + header[0x16] = _height&0xFF; + header[0x17] = (_height>>8)&0xFF; + header[0x18] = (_height>>16)&0xFF; + header[0x19] = (_height>>24)&0xFF; + header[0x1A] = 1; + header[0x1B] = 0; + header[0x1C] = 24; + header[0x1D] = 0; + header[0x22] = buf_size&0xFF; + header[0x23] = (buf_size>>8)&0xFF; + header[0x24] = (buf_size>>16)&0xFF; + header[0x25] = (buf_size>>24)&0xFF; + header[0x27] = 0x1; + header[0x2B] = 0x1; + cimg::fwrite(header._data,54,nfile); + + const T + *ptr_r = data(0,_height - 1,0,0), + *ptr_g = (_spectrum>=2)?data(0,_height - 1,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,_height - 1,0,2):0; + + switch (_spectrum) { + case 1 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + const unsigned char val = (unsigned char)*(ptr_r++); + std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; + } + } break; + case 2 : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc(0,nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; + } + } break; + default : { + cimg_forY(*this,y) { + cimg_forX(*this,x) { + std::fputc((unsigned char)(*(ptr_b++)),nfile); + std::fputc((unsigned char)(*(ptr_g++)),nfile); + std::fputc((unsigned char)(*(ptr_r++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + ptr_r-=2*_width; ptr_g-=2*_width; ptr_b-=2*_width; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a JPEG file. + /** + \param filename Filename, as a C-string. + \param quality Image quality (in %) + **/ + const CImg& save_jpeg(const char *const filename, const unsigned int quality=100) const { + return _save_jpeg(0,filename,quality); + } + + //! Save image as a JPEG file \overloading. + const CImg& save_jpeg(std::FILE *const file, const unsigned int quality=100) const { + return _save_jpeg(file,0,quality); + } + + const CImg& _save_jpeg(std::FILE *const file, const char *const filename, const unsigned int quality) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_jpeg(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_jpeg(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + +#ifndef cimg_use_jpeg + if (!file) return save_other(filename,quality); + else throw CImgIOException(_cimg_instance + "save_jpeg(): Unable to save data in '(*FILE)' unless libjpeg is enabled.", + cimg_instance); +#else + unsigned int dimbuf = 0; + J_COLOR_SPACE colortype = JCS_RGB; + + switch (_spectrum) { + case 1 : dimbuf = 1; colortype = JCS_GRAYSCALE; break; + case 2 : dimbuf = 3; colortype = JCS_RGB; break; + case 3 : dimbuf = 3; colortype = JCS_RGB; break; + default : dimbuf = 4; colortype = JCS_CMYK; break; + } + + // Call libjpeg functions + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + jpeg_stdio_dest(&cinfo,nfile); + cinfo.image_width = _width; + cinfo.image_height = _height; + cinfo.input_components = dimbuf; + cinfo.in_color_space = colortype; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE); + jpeg_start_compress(&cinfo,TRUE); + + JSAMPROW row_pointer[1]; + CImg buffer(_width*dimbuf); + + while (cinfo.next_scanline& save_magick(const char *const filename, const unsigned int bytes_per_pixel=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_magick(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_magick + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_magick(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_magick(): Instance is multispectral, only the three first channels will be " + "saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_magick(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + Magick::Image image(Magick::Geometry(_width,_height),"black"); + image.type(Magick::TrueColorType); + image.depth(bytes_per_pixel?(8*bytes_per_pixel):(stmax>=256?16:8)); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = _spectrum>1?data(0,0,0,1):0, + *ptr_b = _spectrum>2?data(0,0,0,2):0; + Magick::PixelPacket *pixels = image.getPixels(0,0,_width,_height); + switch (_spectrum) { + case 1 : // Scalar images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++); + ++pixels; + } + break; + case 2 : // RG images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = 0; ++pixels; + } + break; + default : // RGB images + for (ulongT off = (ulongT)_width*_height; off; --off) { + pixels->red = (Magick::Quantum)*(ptr_r++); + pixels->green = (Magick::Quantum)*(ptr_g++); + pixels->blue = (Magick::Quantum)*(ptr_b++); + ++pixels; + } + } + image.syncPixels(); + image.write(filename); + return *this; +#else + cimg::unused(bytes_per_pixel); + throw CImgIOException(_cimg_instance + "save_magick(): Unable to save file '%s' unless libMagick++ is enabled.", + cimg_instance, + filename); +#endif + } + + //! Save image as a PNG file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving, when possible. + **/ + const CImg& save_png(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_png(0,filename,bytes_per_pixel); + } + + //! Save image as a PNG file \overloading. + const CImg& save_png(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_png(file,0,bytes_per_pixel); + } + + const CImg& _save_png(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_png(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + +#ifndef cimg_use_png + cimg::unused(bytes_per_pixel); + if (!file) return save_other(filename); + else throw CImgIOException(_cimg_instance + "save_png(): Unable to save data in '(*FILE)' unless libpng is enabled.", + cimg_instance); +#else + +#if defined __GNUC__ + const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning + std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb"); + volatile double stmin, stmax = (double)max_min(stmin); +#else + const char *nfilename = filename; + std::FILE *nfile = file?file:cimg::fopen(nfilename,"wb"); + double stmin, stmax = (double)max_min(stmin); +#endif + + if (_depth>1) + cimg::warn(_cimg_instance + "save_png(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + + if (_spectrum>4) + cimg::warn(_cimg_instance + "save_png(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename); + + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + filename,stmin,stmax); + + // Setup PNG structures for write + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, + user_warning_fn); + if (!png_ptr){ + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'png_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr,(png_infopp)0); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Failed to initialize 'info_ptr' structure when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + + const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8); + + int color_type; + switch (spectrum()) { + case 1 : color_type = PNG_COLOR_TYPE_GRAY; break; + case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; + case 3 : color_type = PNG_COLOR_TYPE_RGB; break; + default : color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + const int interlace_type = PNG_INTERLACE_NONE; + const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT; + const int filter_method = PNG_FILTER_TYPE_DEFAULT; + png_set_IHDR(png_ptr,info_ptr,_width,_height,bit_depth,color_type,interlace_type,compression_type,filter_method); + png_write_info(png_ptr,info_ptr); + const int byte_depth = bit_depth>>3; + const int numChan = spectrum()>4?4:spectrum(); + const int pixel_bit_depth_flag = numChan * (bit_depth - 1); + + // Allocate Memory for Image Save and Fill pixel data + png_bytep *const imgData = new png_byte*[_height]; + for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width]; + const T *pC0 = data(0,0,0,0); + switch (pixel_bit_depth_flag) { + case 7 : { // Gray 8-bit + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++); + } + } break; + case 14 : { // Gray w/ Alpha 8-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + } + } + } break; + case 21 : { // RGB 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x) { + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + } + } + } break; + case 28 : { // RGB x/ Alpha 8-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y){ + unsigned char *ptrd = imgData[y]; + cimg_forX(*this,x){ + *(ptrd++) = (unsigned char)*(pC0++); + *(ptrd++) = (unsigned char)*(pC1++); + *(ptrd++) = (unsigned char)*(pC2++); + *(ptrd++) = (unsigned char)*(pC3++); + } + } + } break; + case 15 : { // Gray 16-bit + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++); + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width); + } + } break; + case 30 : { // Gray w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1); + cimg_forY(*this,y){ + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width); + } + } break; + case 45 : { // RGB 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width); + } + } break; + case 60 : { // RGB w/ Alpha 16-bit + const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); + cimg_forY(*this,y) { + unsigned short *ptrd = (unsigned short*)(imgData[y]); + cimg_forX(*this,x) { + *(ptrd++) = (unsigned short)*(pC0++); + *(ptrd++) = (unsigned short)*(pC1++); + *(ptrd++) = (unsigned short)*(pC2++); + *(ptrd++) = (unsigned short)*(pC3++); + } + if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width); + } + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimg_instance + "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", + cimg_instance, + nfilename?nfilename:"(FILE*)"); + } + png_write_image(png_ptr,imgData); + png_write_end(png_ptr,info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + // Deallocate Image Write Memory + cimg_forY(*this,n) delete[] imgData[n]; + delete[] imgData; + + if (!file) cimg::fclose(nfile); + return *this; +#endif + } + + //! Save image as a PNM file. + /** + \param filename Filename, as a C-string. + \param bytes_per_pixel Force the number of bytes per pixels for the saving. + **/ + const CImg& save_pnm(const char *const filename, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(0,filename,bytes_per_pixel); + } + + //! Save image as a PNM file \overloading. + const CImg& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { + return _save_pnm(file,0,bytes_per_pixel); + } + + const CImg& _save_pnm(std::FILE *const file, const char *const filename, + const unsigned int bytes_per_pixel=0) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + double stmin, stmax = (double)max_min(stmin); + if (_depth>1) + cimg::warn(_cimg_instance + "save_pnm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pnm(): Instance is multispectral, only the three first channels will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) + cimg::warn(_cimg_instance + "save_pnm(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", + cimg_instance, + stmin,stmax,filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const ulongT buf_size = std::min((ulongT)(1024*1024),(ulongT)(_width*_height*(_spectrum==1?1UL:3UL))); + + std::fprintf(nfile,"P%c\n%u %u\n%u\n", + (_spectrum==1?'5':'6'),_width,_height,stmax<256?255:(stmax<4096?4095:65535)); + + switch (_spectrum) { + case 1 : { // Scalar image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PGM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr_r++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Binary PGM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned short)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + } break; + case 2 : { // RG image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = 0; + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } break; + default : { // RGB image + if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned char)*(ptr_r++); + *(ptrd++) = (unsigned char)*(ptr_g++); + *(ptrd++) = (unsigned char)*(ptr_b++); + } + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } else { // Binary PPM 16 bits + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size/3); + unsigned short *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (unsigned short)*(ptr_r++); + *(ptrd++) = (unsigned short)*(ptr_g++); + *(ptrd++) = (unsigned short)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PNK file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pnk(const char *const filename) const { + return _save_pnk(0,filename); + } + + //! Save image as a PNK file \overloading. + const CImg& save_pnk(std::FILE *const file) const { + return _save_pnk(file,0); + } + + const CImg& _save_pnk(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pnk(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum>1) + cimg::warn(_cimg_instance + "save_pnk(): Instance is multispectral, only the first channel will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + const ulongT buf_size = std::min((ulongT)1024*1024,(ulongT)_width*_height*_depth); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T *ptr = data(0,0,0,0); + + if (!cimg::type::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 + 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; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + unsigned char *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else if (!cimg::type::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); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + int *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (int)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } else { // Save as P9: Binary float-valued 3D + if (_depth>1) std::fprintf(nfile,"P9\n%u %u %u\n%g\n",_width,_height,_depth,(double)max()); + else std::fprintf(nfile,"P9\n%u %u\n%g\n",_width,_height,(double)max()); + CImg buf((unsigned int)buf_size); + for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr++); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a PFM file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_pfm(const char *const filename) const { + get_mirror('y')._save_pfm(0,filename); + return *this; + } + + //! Save image as a PFM file \overloading. + const CImg& save_pfm(std::FILE *const file) const { + get_mirror('y')._save_pfm(file,0); + return *this; + } + + const CImg& _save_pfm(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pfm(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_pfm(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + if (_spectrum>3) + cimg::warn(_cimg_instance + "save_pfm(): image instance is multispectral, only the three first channels will be saved " + "in file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptr_r = data(0,0,0,0), + *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, + *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; + const unsigned int buf_size = std::min(1024*1024U,_width*_height*(_spectrum==1?1:3)); + + std::fprintf(nfile,"P%c\n%u %u\n1.0\n", + (_spectrum==1?'f':'F'),_width,_height); + + switch (_spectrum) { + case 1 : { // Scalar image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const ulongT N = std::min((ulongT)to_write,(ulongT)buf_size); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr_r++); + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,N,nfile); + to_write-=N; + } + } break; + case 2 : { // RG image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } break; + default : { // RGB image + CImg buf(buf_size); + for (longT to_write = (longT)width()*height(); to_write>0; ) { + const unsigned int N = std::min((unsigned int)to_write,buf_size/3); + float *ptrd = buf._data; + for (ulongT i = N; i>0; --i) { + *(ptrd++) = (float)*(ptr_r++); + *(ptrd++) = (float)*(ptr_g++); + *(ptrd++) = (float)*(ptr_b++); + } + if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); + cimg::fwrite(buf._data,3*N,nfile); + to_write-=N; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a RGB file. + /** + \param filename Filename, as a C-string. + **/ + const CImg& save_rgb(const char *const filename) const { + return _save_rgb(0,filename); + } + + //! Save image as a RGB file \overloading. + const CImg& save_rgb(std::FILE *const file) const { + return _save_rgb(file,0); + } + + const CImg& _save_rgb(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgb(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=3) + cimg::warn(_cimg_instance + "save_rgb(): image instance has not exactly 3 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0; + switch (_spectrum) { + case 1 : { // Scalar image + for (ulongT k = 0; k& save_rgba(const char *const filename) const { + return _save_rgba(0,filename); + } + + //! Save image as a RGBA file \overloading. + const CImg& save_rgba(std::FILE *const file) const { + return _save_rgba(file,0); + } + + const CImg& _save_rgba(std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_rgba(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + if (_spectrum!=4) + cimg::warn(_cimg_instance + "save_rgba(): image instance has not exactly 4 channels, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT wh = (ulongT)_width*_height; + unsigned char *const buffer = new unsigned char[4*wh], *nbuffer = buffer; + const T + *ptr1 = data(0,0,0,0), + *ptr2 = _spectrum>1?data(0,0,0,1):0, + *ptr3 = _spectrum>2?data(0,0,0,2):0, + *ptr4 = _spectrum>3?data(0,0,0,3):0; + switch (_spectrum) { + case 1 : { // Scalar images + for (ulongT k = 0; k{ 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 use_bigtiff Allow to save big tiff files (>4Gb). + \note + - libtiff support is enabled by defining the precompilation + directive \c cimg_use_tif. + - When libtiff is enabled, 2D and 3D (multipage) several + channel per pixel are supported for + char,uchar,short,ushort,float and \c double pixel types. + - If \c cimg_use_tif 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) + throw CImgArgumentException(_cimg_instance + "save_tiff(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifdef cimg_use_tiff + const bool + _use_bigtiff = use_bigtiff && sizeof(ulongT)>=8 && size()*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + cimg_forZ(*this,z) _save_tiff(tif,z,z,compression_type,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimg_instance + "save_tiff(): Failed to open file '%s' for writing.", + cimg_instance, + filename); + return *this; +#else + cimg::unused(compression_type,voxel_size,description,use_bigtiff); + return save_other(filename); +#endif + } + +#ifdef cimg_use_tiff + +#define _cimg_save_tiff(types,typed,compression_type) if (!std::strcmp(types,pixel_type())) { \ + const typed foo = (typed)0; return _save_tiff(tif,directory,z,foo,compression_type,voxel_size,description); } + + // [internal] Save a plane into a tiff file + template + const CImg& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, const t& pixel_t, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + if (is_empty() || !tif || pixel_t) return *this; + const char *const filename = TIFFFileName(tif); + uint32 rowsperstrip = (uint32)-1; + uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric; + if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB; + else photometric = PHOTOMETRIC_MINISBLACK; + TIFFSetDirectory(tif,directory); + TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,_width); + TIFFSetField(tif,TIFFTAG_IMAGELENGTH,_height); + if (voxel_size) { + const float vx = voxel_size[0], vy = voxel_size[1], vz = voxel_size[2]; + TIFFSetField(tif,TIFFTAG_RESOLUTIONUNIT,RESUNIT_NONE); + TIFFSetField(tif,TIFFTAG_XRESOLUTION,1.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()); + } + if (description) TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,description); + TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT); + TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp); + if (cimg::type::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3); + else if (cimg::type::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1); + else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2); + double valm, valM = max_min(valm); + TIFFSetField(tif,TIFFTAG_SMINSAMPLEVALUE,valm); + TIFFSetField(tif,TIFFTAG_SMAXSAMPLEVALUE,valM); + TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp); + TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG); + TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric); + TIFFSetField(tif,TIFFTAG_COMPRESSION,compression_type==2?COMPRESSION_JPEG: + compression_type==1?COMPRESSION_LZW:COMPRESSION_NONE); + rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip); + TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip); + TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB); + TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg"); + + t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf) { + for (unsigned int row = 0; row<_height; row+=rowsperstrip) { + uint32 nrow = (row + rowsperstrip>_height?_height - row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif,row,0); + tsize_t i = 0; + for (unsigned int rr = 0; rr& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, + const unsigned int compression_type, const float *const voxel_size, + const char *const description) const { + _cimg_save_tiff("bool",unsigned char,compression_type); + _cimg_save_tiff("unsigned char",unsigned char,compression_type); + _cimg_save_tiff("char",char,compression_type); + _cimg_save_tiff("unsigned short",unsigned short,compression_type); + _cimg_save_tiff("short",short,compression_type); + _cimg_save_tiff("unsigned int",unsigned int,compression_type); + _cimg_save_tiff("int",int,compression_type); + _cimg_save_tiff("unsigned int64",unsigned int,compression_type); + _cimg_save_tiff("int64",int,compression_type); + _cimg_save_tiff("float",float,compression_type); + _cimg_save_tiff("double",float,compression_type); + const char *const filename = TIFFFileName(tif); + throw CImgInstanceException(_cimg_instance + "save_tiff(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + return *this; + } +#endif + + //! Save image as a MINC2 file. + /** + \param filename Filename, as a C-string. + \param imitate_file If non-zero, reference filename, as a C-string, to borrow header from. + **/ + const CImg& save_minc2(const char *const filename, + const char *const imitate_file=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_minc2(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_minc2 + cimg::unused(imitate_file); + return save_other(filename); +#else + minc::minc_1_writer wtr; + if (imitate_file) + wtr.open(filename, imitate_file); + else { + minc::minc_info di; + if (width()) di.push_back(minc::dim_info(width(),width()*0.5,-1,minc::dim_info::DIM_X)); + if (height()) di.push_back(minc::dim_info(height(),height()*0.5,-1,minc::dim_info::DIM_Y)); + if (depth()) di.push_back(minc::dim_info(depth(),depth()*0.5,-1,minc::dim_info::DIM_Z)); + if (spectrum()) di.push_back(minc::dim_info(spectrum(),spectrum()*0.5,-1,minc::dim_info::DIM_TIME)); + wtr.open(filename,di,1,NC_FLOAT,0); + } + if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_byte(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_int(); + else if (cimg::type::string()==cimg::type::string()) + wtr.setup_write_double(); + else + wtr.setup_write_float(); + minc::save_standard_volume(wtr, this->_data); + return *this; +#endif + } + + //! Save image as an ANALYZE7.5 or NIFTI file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 consecutive values that tell about the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_analyze(const char *const filename, const float *const voxel_size=0) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_analyze(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + std::FILE *file; + CImg hname(1024), iname(1024); + const char *const ext = cimg::split_filename(filename); + short datatype = -1; + if (!*ext) { + cimg_snprintf(hname,hname._width,"%s.hdr",filename); + cimg_snprintf(iname,iname._width,"%s.img",filename); + } + if (!cimg::strncasecmp(ext,"hdr",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(iname._data + std::strlen(iname) - 3,"img"); + } + if (!cimg::strncasecmp(ext,"img",3)) { + std::strcpy(hname,filename); + std::strncpy(iname,filename,iname._width - 1); + cimg_sprintf(hname._data + std::strlen(iname) - 3,"hdr"); + } + if (!cimg::strncasecmp(ext,"nii",3)) { + std::strncpy(hname,filename,hname._width - 1); *iname = 0; + } + + CImg header(*iname?348:352,1,1,1,0); + int *const iheader = (int*)header._data; + *iheader = 348; + std::strcpy(header._data + 4,"CImg"); + std::strcpy(header._data + 14," "); + ((short*)&(header[36]))[0] = 4096; + ((char*)&(header[38]))[0] = 114; + ((short*)&(header[40]))[0] = 4; + ((short*)&(header[40]))[1] = (short)_width; + ((short*)&(header[40]))[2] = (short)_height; + ((short*)&(header[40]))[3] = (short)_depth; + ((short*)&(header[40]))[4] = (short)_spectrum; + if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"unsigned int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16; + if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64; + if (datatype<0) + throw CImgIOException(_cimg_instance + "save_analyze(): Unsupported pixel type '%s' for file '%s'.", + cimg_instance, + pixel_type(),filename); + + ((short*)&(header[70]))[0] = datatype; + ((short*)&(header[72]))[0] = sizeof(T); + ((float*)&(header[108]))[0] = (float)(*iname?0:header.width()); + ((float*)&(header[112]))[0] = 1; + ((float*)&(header[76]))[0] = 0; + if (voxel_size) { + ((float*)&(header[76]))[1] = voxel_size[0]; + ((float*)&(header[76]))[2] = voxel_size[1]; + ((float*)&(header[76]))[3] = voxel_size[2]; + } else ((float*)&(header[76]))[1] = ((float*)&(header[76]))[2] = ((float*)&(header[76]))[3] = 1; + file = cimg::fopen(hname,"wb"); + cimg::fwrite(header._data,header.width(),file); + if (*iname) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); } + cimg::fwrite(_data,size(),file); + cimg::fclose(file); + return *this; + } + + //! Save image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param is_compressed Tells if the file contains compressed image data. + **/ + const CImg& save_cimg(const char *const filename, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(filename,is_compressed); + return *this; + } + + //! Save image as a .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, const bool is_compressed=false) const { + CImgList(*this,true).save_cimg(file,is_compressed); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file. + /** + \param filename Filename, as a C-string. + \param n0 Index of the image inside the file. + \param x0 X-coordinate of the sub-image location. + \param y0 Y-coordinate of the sub-image location. + \param z0 Z-coordinate of the sub-image location. + \param c0 C-coordinate of the sub-image location. + **/ + const CImg& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(filename,n0,x0,y0,z0,c0); + return *this; + } + + //! Save image as a sub-image into an existing .cimg file \overloading. + const CImg& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + CImgList(*this,true).save_cimg(file,n0,x0,y0,z0,c0); + return *this; + } + + //! Save blank image as a .cimg file. + /** + \param filename Filename, as a C-string. + \param dx Width of the image. + \param dy Height of the image. + \param dz Depth of the image. + \param dc Number of channels of the image. + \note + - All pixel values of the saved image are set to \c 0. + - Use this method to save large images without having to instanciate and allocate them. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(filename,1,dx,dy,dz,dc); + } + + //! Save blank image as a .cimg file \overloading. + /** + Same as save_empty_cimg(const char *,unsigned int,unsigned int,unsigned int,unsigned int) + with a file stream argument instead of a filename string. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return CImgList::save_empty_cimg(file,1,dx,dy,dz,dc); + } + + //! Save image as an INRIMAGE-4 file. + /** + \param filename Filename, as a C-string. + \param voxel_size Pointer to 3 values specifying the voxel sizes along the X,Y and Z dimensions. + **/ + const CImg& save_inr(const char *const filename, const float *const voxel_size=0) const { + return _save_inr(0,filename,voxel_size); + } + + //! Save image as an INRIMAGE-4 file \overloading. + const CImg& save_inr(std::FILE *const file, const float *const voxel_size=0) const { + return _save_inr(file,0,voxel_size); + } + + const CImg& _save_inr(std::FILE *const file, const char *const filename, const float *const voxel_size) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_inr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + int inrpixsize = -1; + const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { + inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"char")) { + inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { + inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"short")) { + inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; + } + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { + inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"int")) { + inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"float")) { + inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; + } + if (!cimg::strcasecmp(pixel_type(),"double")) { + inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; + } + if (inrpixsize<=0) + throw CImgIOException(_cimg_instance + "save_inr(): Unsupported pixel type '%s' for file '%s'", + cimg_instance, + pixel_type(),filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + CImg header(257); + int err = cimg_snprintf(header,header._width,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n", + _width,_height,_depth,_spectrum); + if (voxel_size) err+=cimg_sprintf(header._data + err,"VX=%g\nVY=%g\nVZ=%g\n", + voxel_size[0],voxel_size[1],voxel_size[2]); + err+=cimg_sprintf(header._data + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm"); + std::memset(header._data + err,'\n',252 - err); + std::memcpy(header._data + 252,"##}\n",4); + cimg::fwrite(header._data,256,nfile); + cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) cimg::fwrite(&((*this)(x,y,z,c)),1,nfile); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as an OpenEXR file. + /** + \param filename Filename, as a C-string. + \note The OpenEXR file format is described here. + **/ + const CImg& save_exr(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_exr(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_exr(): Instance is volumetric, only the first slice will be saved in file '%s'.", + cimg_instance, + filename); + +#ifndef cimg_use_openexr + return save_other(filename); +#else + Imf::Rgba *const ptrd0 = new Imf::Rgba[(size_t)_width*_height], *ptrd = ptrd0, rgba; + switch (_spectrum) { + case 1 : { // Grayscale image + for (const T *ptr_r = data(), *const ptr_e = ptr_r + (ulongT)_width*_height; ptr_rPandore file specifications + for more information). + **/ + const CImg& save_pandore(const char *const filename, const unsigned int colorspace=0) const { + return _save_pandore(0,filename,colorspace); + } + + //! Save image as a Pandore-5 file \overloading. + /** + Same as save_pandore(const char *,unsigned int) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_pandore(std::FILE *const file, const unsigned int colorspace=0) const { + return _save_pandore(file,0,colorspace); + } + + unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const { + unsigned int nbdims = 0; + if (id==2 || id==3 || id==4) { + dims[0] = 1; dims[1] = _width; nbdims = 2; + } + if (id==5 || id==6 || id==7) { + dims[0] = 1; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==8 || id==9 || id==10) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + if (id==16 || id==17 || id==18) { + dims[0] = 3; dims[1] = _height; dims[2] = _width; dims[3] = colorspace; nbdims = 4; + } + if (id==19 || id==20 || id==21) { + dims[0] = 3; dims[1] = _depth; dims[2] = _height; dims[3] = _width; dims[4] = colorspace; nbdims = 5; + } + if (id==22 || id==23 || id==25) { + dims[0] = _spectrum; dims[1] = _width; nbdims = 2; + } + if (id==26 || id==27 || id==29) { + dims[0] = _spectrum; dims[1] = _height; dims[2] = _width; nbdims=3; + } + if (id==30 || id==31 || id==33) { + dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; + } + return nbdims; + } + + const CImg& _save_pandore(std::FILE *const file, const char *const filename, + const unsigned int colorspace) const { + +#define __cimg_save_pandore_case(dtype) \ + dtype *buffer = new dtype[size()]; \ + const T *ptrs = _data; \ + cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \ + buffer-=size(); \ + cimg::fwrite(buffer,size(),nfile); \ + delete[] buffer + +#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \ + if (!saved && (sy?(sy==_height):true) && (sz?(sz==_depth):true) && \ + (sv?(sv==_spectrum):true) && !std::strcmp(stype,pixel_type())) { \ + unsigned int *iheader = (unsigned int*)(header + 12); \ + nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \ + cimg::fwrite(header,36,nfile); \ + if (sizeof(unsigned long)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned int)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else if (sizeof(unsigned short)==4) { CImg ndims(5); \ + for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \ + __cimg_save_pandore_case(unsigned char); \ + } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \ + if (sizeof(unsigned long)==4) { __cimg_save_pandore_case(unsigned long); } \ + else if (sizeof(unsigned int)==4) { __cimg_save_pandore_case(unsigned int); } \ + else if (sizeof(unsigned short)==4) { __cimg_save_pandore_case(unsigned short); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \ + if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \ + else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \ + else throw CImgIOException(_cimg_instance \ + "save_pandore(): Unsupported datatype for file '%s'.",\ + cimg_instance, \ + filename?filename:"(FILE*)"); \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_pandore(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0, + 0,0,0,0,'C','I','m','g',0,0,0,0,0, + 'N','o',' ','d','a','t','e',0,0,0,0 }; + unsigned int nbdims, dims[5] = { 0 }; + bool saved = false; + _cimg_save_pandore_case(1,1,1,"unsigned char",2); + _cimg_save_pandore_case(1,1,1,"char",3); + _cimg_save_pandore_case(1,1,1,"unsigned short",3); + _cimg_save_pandore_case(1,1,1,"short",3); + _cimg_save_pandore_case(1,1,1,"unsigned int",3); + _cimg_save_pandore_case(1,1,1,"int",3); + _cimg_save_pandore_case(1,1,1,"unsigned int64",3); + _cimg_save_pandore_case(1,1,1,"int64",3); + _cimg_save_pandore_case(1,1,1,"float",4); + _cimg_save_pandore_case(1,1,1,"double",4); + + _cimg_save_pandore_case(0,1,1,"unsigned char",5); + _cimg_save_pandore_case(0,1,1,"char",6); + _cimg_save_pandore_case(0,1,1,"unsigned short",6); + _cimg_save_pandore_case(0,1,1,"short",6); + _cimg_save_pandore_case(0,1,1,"unsigned int",6); + _cimg_save_pandore_case(0,1,1,"int",6); + _cimg_save_pandore_case(0,1,1,"unsigned int64",6); + _cimg_save_pandore_case(0,1,1,"int64",6); + _cimg_save_pandore_case(0,1,1,"float",7); + _cimg_save_pandore_case(0,1,1,"double",7); + + _cimg_save_pandore_case(0,0,1,"unsigned char",8); + _cimg_save_pandore_case(0,0,1,"char",9); + _cimg_save_pandore_case(0,0,1,"unsigned short",9); + _cimg_save_pandore_case(0,0,1,"short",9); + _cimg_save_pandore_case(0,0,1,"unsigned int",9); + _cimg_save_pandore_case(0,0,1,"int",9); + _cimg_save_pandore_case(0,0,1,"unsigned int64",9); + _cimg_save_pandore_case(0,0,1,"int64",9); + _cimg_save_pandore_case(0,0,1,"float",10); + _cimg_save_pandore_case(0,0,1,"double",10); + + _cimg_save_pandore_case(0,1,3,"unsigned char",16); + _cimg_save_pandore_case(0,1,3,"char",17); + _cimg_save_pandore_case(0,1,3,"unsigned short",17); + _cimg_save_pandore_case(0,1,3,"short",17); + _cimg_save_pandore_case(0,1,3,"unsigned int",17); + _cimg_save_pandore_case(0,1,3,"int",17); + _cimg_save_pandore_case(0,1,3,"unsigned int64",17); + _cimg_save_pandore_case(0,1,3,"int64",17); + _cimg_save_pandore_case(0,1,3,"float",18); + _cimg_save_pandore_case(0,1,3,"double",18); + + _cimg_save_pandore_case(0,0,3,"unsigned char",19); + _cimg_save_pandore_case(0,0,3,"char",20); + _cimg_save_pandore_case(0,0,3,"unsigned short",20); + _cimg_save_pandore_case(0,0,3,"short",20); + _cimg_save_pandore_case(0,0,3,"unsigned int",20); + _cimg_save_pandore_case(0,0,3,"int",20); + _cimg_save_pandore_case(0,0,3,"unsigned int64",20); + _cimg_save_pandore_case(0,0,3,"int64",20); + _cimg_save_pandore_case(0,0,3,"float",21); + _cimg_save_pandore_case(0,0,3,"double",21); + + _cimg_save_pandore_case(1,1,0,"unsigned char",22); + _cimg_save_pandore_case(1,1,0,"char",23); + _cimg_save_pandore_case(1,1,0,"unsigned short",23); + _cimg_save_pandore_case(1,1,0,"short",23); + _cimg_save_pandore_case(1,1,0,"unsigned int",23); + _cimg_save_pandore_case(1,1,0,"int",23); + _cimg_save_pandore_case(1,1,0,"unsigned int64",23); + _cimg_save_pandore_case(1,1,0,"int64",23); + _cimg_save_pandore_case(1,1,0,"float",25); + _cimg_save_pandore_case(1,1,0,"double",25); + + _cimg_save_pandore_case(0,1,0,"unsigned char",26); + _cimg_save_pandore_case(0,1,0,"char",27); + _cimg_save_pandore_case(0,1,0,"unsigned short",27); + _cimg_save_pandore_case(0,1,0,"short",27); + _cimg_save_pandore_case(0,1,0,"unsigned int",27); + _cimg_save_pandore_case(0,1,0,"int",27); + _cimg_save_pandore_case(0,1,0,"unsigned int64",27); + _cimg_save_pandore_case(0,1,0,"int64",27); + _cimg_save_pandore_case(0,1,0,"float",29); + _cimg_save_pandore_case(0,1,0,"double",29); + + _cimg_save_pandore_case(0,0,0,"unsigned char",30); + _cimg_save_pandore_case(0,0,0,"char",31); + _cimg_save_pandore_case(0,0,0,"unsigned short",31); + _cimg_save_pandore_case(0,0,0,"short",31); + _cimg_save_pandore_case(0,0,0,"unsigned int",31); + _cimg_save_pandore_case(0,0,0,"int",31); + _cimg_save_pandore_case(0,0,0,"unsigned int64",31); + _cimg_save_pandore_case(0,0,0,"int64",31); + _cimg_save_pandore_case(0,0,0,"float",33); + _cimg_save_pandore_case(0,0,0,"double",33); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a raw data file. + /** + \param filename Filename, as a C-string. + \param is_multiplexed Tells if the image channels are stored in a multiplexed way (\c true) or not (\c false). + \note The .raw format does not store the image dimensions in the output file, + so you have to keep track of them somewhere to be able to read the file correctly afterwards. + **/ + const CImg& save_raw(const char *const filename, const bool is_multiplexed=false) const { + return _save_raw(0,filename,is_multiplexed); + } + + //! Save image as a raw data file \overloading. + /** + Same as save_raw(const char *,bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_raw(std::FILE *const file, const bool is_multiplexed=false) const { + return _save_raw(file,0,is_multiplexed); + } + + const CImg& _save_raw(std::FILE *const file, const char *const filename, const bool is_multiplexed) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_raw(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!is_multiplexed) cimg::fwrite(_data,size(),nfile); + else { + CImg buf(_spectrum); + cimg_forXYZ(*this,x,y,z) { + cimg_forC(*this,c) buf[c] = (*this)(x,y,z,c); + cimg::fwrite(buf._data,_spectrum,nfile); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save image as a .yuv video file. + /** + \param filename Filename, as a C-string. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if pixel values of the instance image are RGB-coded (\c true) or YUV-coded (\c false). + \note Each slice of the instance image is considered to be a single frame of the output video file. + **/ + const CImg& save_yuv(const char *const filename, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(filename,chroma_subsampling,is_rgb); + return *this; + } + + //! Save image as a .yuv video file \overloading. + /** + Same as save_yuv(const char*,const unsigned int,const bool) const + with a file stream argument instead of a filename string. + **/ + const CImg& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + CImgList(*this,true).save_yuv(file,chroma_subsampling,is_rgb); + return *this; + } + + //! Save 3D object as an Object File Format (.off) file. + /** + \param filename Filename, as a C-string. + \param primitives List of 3D object primitives. + \param colors List of 3D object colors. + \note + - Instance image contains the vertices data of the 3D object. + - Textured, transparent or sphere-shaped primitives cannot be managed by the .off file format. + Such primitives will be lost or simplified during file saving. + - The .off file format is described here. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + const char *const filename) const { + return _save_off(primitives,colors,0,filename); + } + + //! Save 3D object as an Object File Format (.off) file \overloading. + /** + Same as save_off(const CImgList&,const CImgList&,const char*) const + with a file stream argument instead of a filename string. + **/ + template + const CImg& save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file) const { + return _save_off(primitives,colors,file,0); + } + + template + const CImg& _save_off(const CImgList& primitives, const CImgList& colors, + std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException(_cimg_instance + "save_off(): Specified filename is (null).", + cimg_instance); + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "save_off(): Empty instance, for file '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + + CImgList opacities; + 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).", + cimg_instance, + filename?filename:"(FILE*)",error_message.data()); + + const CImg default_color(1,3,1,1,200); + std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + unsigned int supported_primitives = 0; + cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives; + std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width); + cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n", + (float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2))); + cimglist_for(primitives,l) { + const CImg& color = l1?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; + case 2 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 3 : std::fprintf(nfile,"3 %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,2), + (unsigned int)primitives(l,1),r,g,b); break; + case 4 : std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,3), + (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); break; + case 5 : std::fprintf(nfile,"2 %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break; + case 6 : { + const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3); + const float + rt = color.atXY(xt,yt,0)/255.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.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); + } break; + case 12 : { + const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5); + const float + rt = color.atXY(xt,yt,0)/255.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); + } break; + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save volumetric image as a video, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImg& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { + if (is_empty()) { CImgList().save_video(filename,fps,codec,keep_open); return *this; } + CImgList list; + get_split('z').move_to(list); + list.save_video(filename,fps,codec,keep_open); + return *this; + } + + //! Save volumetric image as a video, using ffmpeg external binary. + /** + \param filename Filename, as a C-string. + \param fps Video framerate. + \param codec Video codec, as a C-string. + \param bitrate Video bitrate. + \note + - Each slice of the instance image is considered to be a single frame of the output video file. + - This method uses \c ffmpeg, an external executable binary provided by + FFmpeg. + It must be installed for the method to succeed. + **/ + const CImg& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImgList list; + get_split('z').move_to(list); + list.save_ffmpeg_external(filename,fps,codec,bitrate); + return *this; + } + + //! Save image using gzip external binary. + /** + \param filename Filename, as a C-string. + \note This method uses \c gzip, an external executable binary provided by + gzip. + It must be installed for the method to succeed. + **/ + const CImg& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_gzip_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimg_instance, + filename); + + else cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using GraphicsMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c gm, an external executable binary provided by + GraphicsMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_graphicsmagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + +#ifdef cimg_use_png +#define _cimg_sge_ext1 "png" +#define _cimg_sge_ext2 "png" +#else +#define _cimg_sge_ext1 "pgm" +#define _cimg_sge_ext2 "ppm" +#endif + CImg 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=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::graphicsmagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_graphicsmagick_external(): Failed to save file '%s' with external command 'gm'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image using ImageMagick's external binary. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note This method uses \c convert, an external executable binary provided by + ImageMagick. + It must be installed for the method to succeed. + **/ + const CImg& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_imagemagick_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick only writes the first image slice.", + cimg_instance,filename); +#ifdef cimg_use_png +#define _cimg_sie_ext1 "png" +#define _cimg_sie_ext2 "png" +#else +#define _cimg_sie_ext1 "pgm" +#define _cimg_sie_ext2 "ppm" +#endif + CImg 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=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::imagemagick_path(),quality, + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimg_instance + "save_imagemagick_external(): Failed to save file '%s' with " + "external command 'magick/convert'.", + cimg_instance, + filename); + + if (file) cimg::fclose(file); + std::remove(filename_tmp); + return *this; + } + + //! Save image as a Dicom file. + /** + \param filename Filename, as a C-string. + \note This method uses \c medcon, an external executable binary provided by + (X)Medcon. + It must be installed for the method to succeed. + **/ + const CImg& save_medcon_external(const char *const filename) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_medcon_external(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + CImg command(1024), filename_tmp(256), body(256); + std::FILE *file; + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand()); + 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::medcon_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command); + std::remove(filename_tmp); + cimg::split_filename(filename_tmp,body); + cimg_snprintf(filename_tmp,filename_tmp._width,"%s.img",body._data); + std::remove(filename_tmp); + + file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"m000-%s",filename); + file = cimg::std_fopen(command,"rb"); + if (!file) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimg_instance + "save_medcon_external(): Failed to save file '%s' with external command 'medcon'.", + cimg_instance, + filename); + } + } + cimg::fclose(file); + std::rename(command,filename); + return *this; + } + + // Save image for non natively supported formats. + /** + \param filename Filename, as a C-string. + \param quality Image quality (expressed in percent), when the file format supports it. + \note + - The filename extension tells about the desired file format. + - This method tries to save the instance image as a file, using external tools from + ImageMagick or + GraphicsMagick. + At least one of these tool must be installed for the method to succeed. + - It is recommended to use the generic method save(const char*, int) const instead, + as it can handle some file formats natively. + **/ + const CImg& save_other(const char *const filename, const unsigned int quality=100) const { + if (!filename) + throw CImgArgumentException(_cimg_instance + "save_other(): Specified filename is (null).", + cimg_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + if (_depth>1) + cimg::warn(_cimg_instance + "save_other(): File '%s', saving a volumetric image with an external call to " + "ImageMagick or GraphicsMagick only writes the first image slice.", + cimg_instance,filename); + + const unsigned int omode = cimg::exception_mode(); + bool is_saved = true; + cimg::exception_mode(0); + try { save_magick(filename); } + catch (CImgException&) { + try { save_imagemagick_external(filename,quality); } + catch (CImgException&) { + try { save_graphicsmagick_external(filename,quality); } + catch (CImgException&) { + is_saved = false; + } + } + } + cimg::exception_mode(omode); + if (!is_saved) + throw CImgIOException(_cimg_instance + "save_other(): Failed to save file '%s'. Format is not natively supported, " + "and no external commands succeeded.", + cimg_instance, + filename); + return *this; + } + + //! Serialize a CImg instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { + return CImgList(*this,true).get_serialize(is_compressed); + } + + // [internal] Return a 40x38 color logo of a 'danger' item. + static CImg _logo40x38() { + CImg res(40,38,1,3); + const unsigned char *ptrs = cimg::logo40x38; + T *ptr1 = res.data(0,0,0,0), *ptr2 = res.data(0,0,0,1), *ptr3 = res.data(0,0,0,2); + for (ulongT off = 0; off<(ulongT)res._width*res._height;) { + const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++); + for (unsigned int l = 0; l structure + # + # + # + #------------------------------------------ + */ + //! Represent a list of images CImg. + template + struct CImgList { + unsigned int _width, _allocated_width; + CImg *_data; + + //! Simple iterator type, to loop through each image of a list. + /** + \note + - 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 + for (CImgList<>::iterator it = list.begin(); it* iterator; + + //! Simple const iterator type, to loop through each image of a \c const list instance. + /** + \note + - The \c CImgList::const_iterator type is defined to be a const CImg*. + - Similar to CImgList::iterator, but for constant list instances. + **/ + typedef const CImg* const_iterator; + + //! Pixel value type. + /** + Refer to the pixels value type of the images in the list. + \note + - The \c CImgList::value_type type of a \c CImgList is defined to be a \c T. + It is then similar to CImg::value_type. + - \c CImgList::value_type is actually not used in %CImg methods. It has been mainly defined for + compatibility with STL naming conventions. + **/ + typedef T value_type; + + // Define common types related to template type T. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type uint64T; + typedef typename cimg::last::type int64T; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimglist_plugin +#include cimglist_plugin +#endif +#ifdef cimglist_plugin1 +#include cimglist_plugin1 +#endif +#ifdef cimglist_plugin2 +#include cimglist_plugin2 +#endif +#ifdef cimglist_plugin3 +#include cimglist_plugin3 +#endif +#ifdef cimglist_plugin4 +#include cimglist_plugin4 +#endif +#ifdef cimglist_plugin5 +#include cimglist_plugin5 +#endif +#ifdef cimglist_plugin6 +#include cimglist_plugin6 +#endif +#ifdef cimglist_plugin7 +#include cimglist_plugin7 +#endif +#ifdef cimglist_plugin8 +#include cimglist_plugin8 +#endif + + //@} + //-------------------------------------------------------- + // + //! \name Constructors / Destructor / Instance Management + //@{ + //-------------------------------------------------------- + + //! Destructor. + /** + Destroy current list instance. + \note + - Any allocated buffer is deallocated. + - Destroying an empty list does nothing actually. + **/ + ~CImgList() { + delete[] _data; + } + + //! Default constructor. + /** + Construct a new empty list instance. + \note + - An empty list has no pixel data and its dimension width() is set to \c 0, as well as its + image buffer pointer data(). + - An empty list may be reassigned afterwards, with the family of the assign() methods. + In all cases, the type of pixels stays \c T. + **/ + CImgList(): + _width(0),_allocated_width(0),_data(0) {} + + //! Construct list containing empty images. + /** + \param n Number of empty images. + \note Useful when you know by advance the number of images you want to manage, as + it will allocate the right amount of memory for the list, without needs for reallocation + (that may occur when starting from an empty list and inserting several images in it). + **/ + explicit CImgList(const unsigned int n):_width(n) { + if (n) _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + else { _allocated_width = 0; _data = 0; } + } + + //! Construct list containing images of specified size. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \note Pixel values are not initialized and may probably contain garbage. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + } + + //! Construct list containing images of specified size, and initialize pixel values. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val Initialization value for images pixels. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + } + + //! Construct list containing images of specified size, and initialize pixel values from a sequence of integers. + /** + \param n Number of images. + \param width Width of images. + \param height Height of images. + \param depth Depth of images. + \param spectrum Number of channels of images. + \param val0 First value of the initializing integers sequence. + \param val1 Second value of the initializing integers sequence. + \warning You must specify at least width*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...): + _width(0),_allocated_width(0),_data(0) { +#define _CImgList_stdarg(t) { \ + assign(n,width,height,depth,spectrum); \ + const ulongT siz = (ulongT)width*height*depth*spectrum, nsiz = siz*n; \ + T *ptrd = _data->_data; \ + va_list ap; \ + va_start(ap,val1); \ + for (ulongT l = 0, s = 0, i = 0; iwidth*height*depth*spectrum values in your argument list, + or you will probably segfault. + **/ + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const double val0, const double val1, ...): + _width(0),_allocated_width(0),_data(0) { + _CImgList_stdarg(double); + } + + //! Construct list containing copies of an input image. + /** + \param n Number of images. + \param img Input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of \c img. + **/ + template + CImgList(const unsigned int n, const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + } + + //! Construct list from one image. + /** + \param img Input image to copy in the constructed list. + \param is_shared Tells if the element of the list is a shared or non-shared copy of \c img. + **/ + template + explicit CImgList(const CImg& img, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(1); + _data[0].assign(img,is_shared); + } + + //! Construct list from two images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + } + + //! Construct list from three images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + } + + //! Construct list from four images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + } + + //! Construct list from five images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + } + + //! Construct list from six images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + } + + //! Construct list from seven images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + } + + //! Construct list from eight images. + /** + \param img1 First input image to copy in the constructed list. + \param img2 Second input image to copy in the constructed list. + \param img3 Third input image to copy in the constructed list. + \param img4 Fourth input image to copy in the constructed list. + \param img5 Fifth input image to copy in the constructed list. + \param img6 Sixth input image to copy in the constructed list. + \param img7 Seventh input image to copy in the constructed list. + \param img8 Eighth input image to copy in the constructed list. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false): + _width(0),_allocated_width(0),_data(0) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + } + + //! Construct list copy. + /** + \param list Input list to copy. + \note The shared state of each element of the constructed list is kept the same as in \c list. + **/ + template + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + } + + //! Construct list copy \specialization. + CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared); + } + + //! Construct list copy, and force the shared state of the list elements. + /** + \param list Input list to copy. + \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. + **/ + template + CImgList(const CImgList& list, const bool is_shared):_width(0),_allocated_width(0),_data(0) { + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],is_shared); + } + + //! Construct list by reading the content of a file. + /** + \param filename Filename, as a C-string. + **/ + explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) { + assign(filename); + } + + //! Construct list from the content of a display window. + /** + \param disp Display window to get content from. + \note Constructed list contains a single image only. + **/ + explicit CImgList(const CImgDisplay& disp):_width(0),_allocated_width(0),_data(0) { + assign(disp); + } + + //! Return a list with elements being shared copies of images in the list instance. + /** + \note list2 = list1.get_shared() is equivalent to list2.assign(list1,true). + **/ + CImgList get_shared() { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Return a list with elements being shared copies of images in the list instance \const. + const CImgList get_shared() const { + CImgList res(_width); + cimglist_for(*this,l) res[l].assign(_data[l],true); + return res; + } + + //! Destructor \inplace. + /** + \see CImgList(). + **/ + CImgList& assign() { + delete[] _data; + _width = _allocated_width = 0; + _data = 0; + return *this; + } + + //! Destructor \inplace. + /** + Equivalent to assign(). + \note Only here for compatibility with STL naming conventions. + **/ + CImgList& clear() { + return assign(); + } + + //! Construct list containing empty images \inplace. + /** + \see CImgList(unsigned int). + **/ + CImgList& assign(const unsigned int n) { + if (!n) return assign(); + if (_allocated_width(n<<2)) { + delete[] _data; + _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; + } + _width = n; + return *this; + } + + //! Construct list containing images of specified size \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int spectrum=1) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum); + return *this; + } + + //! Construct list containing images of specified size, and initialize pixel values \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const T). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const T& val) { + assign(n); + cimglist_apply(*this,assign)(width,height,depth,spectrum,val); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of integers \inplace. + /** + \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const int, const int, ...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) { + _CImgList_stdarg(int); + return *this; + } + + //! Construct list with images of specified size, and initialize pixel values from a sequence of doubles \inplace. + /** + \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const double,const double,...). + **/ + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int spectrum, + const double val0, const double val1, ...) { + _CImgList_stdarg(double); + return *this; + } + + //! Construct list containing copies of an input image \inplace. + /** + \see CImgList(unsigned int, const CImg&, bool). + **/ + template + CImgList& assign(const unsigned int n, const CImg& img, const bool is_shared=false) { + assign(n); + cimglist_apply(*this,assign)(img,is_shared); + return *this; + } + + //! Construct list from one image \inplace. + /** + \see CImgList(const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img, const bool is_shared=false) { + assign(1); + _data[0].assign(img,is_shared); + return *this; + } + + //! Construct list from two images \inplace. + /** + \see CImgList(const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const bool is_shared=false) { + assign(2); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); + return *this; + } + + //! Construct list from three images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false) { + assign(3); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + return *this; + } + + //! Construct list from four images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool is_shared=false) { + assign(4); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); + return *this; + } + + //! Construct list from five images \inplace. + /** + \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool is_shared=false) { + assign(5); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); + return *this; + } + + //! Construct list from six images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool is_shared=false) { + assign(6); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + return *this; + } + + //! Construct list from seven images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool is_shared=false) { + assign(7); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); + return *this; + } + + //! Construct list from eight images \inplace. + /** + \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, + const CImg&, const CImg&, bool). + **/ + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, + const bool is_shared=false) { + assign(8); + _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); + _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); + _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); + return *this; + } + + //! Construct list as a copy of an existing list and force the shared state of the list elements \inplace. + /** + \see CImgList(const CImgList&, bool is_shared). + **/ + template + CImgList& assign(const CImgList& list, const bool is_shared=false) { + cimg::unused(is_shared); + assign(list._width); + cimglist_for(*this,l) _data[l].assign(list[l],false); + return *this; + } + + //! Construct list as a copy of an existing list and force shared state of elements \inplace \specialization. + CImgList& assign(const CImgList& list, const bool is_shared=false) { + if (this==&list) return *this; + CImgList res(list._width); + cimglist_for(res,l) res[l].assign(list[l],is_shared); + return res.move_to(*this); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& assign(const char *const filename) { + return load(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& assign(const CImgDisplay &disp) { + return assign(CImg(disp)); + } + + //! Transfer the content of the list instance to another list. + /** + \param list Destination list. + \note When returning, the current list instance is empty and the initial content of \c list is destroyed. + **/ + template + CImgList& move_to(CImgList& list) { + list.assign(_width); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[l]); + assign(); + return list; + } + + //! Transfer the content of the list instance at a specified position in another list. + /** + \param list Destination list. + \param pos Index of the insertion in the list. + \note When returning, the list instance is empty and the initial content of \c list is preserved + (only images indexes may be modified). + **/ + template + CImgList& move_to(CImgList& list, const unsigned int pos) { + if (is_empty()) return list; + const unsigned int npos = pos>list._width?list._width:pos; + list.insert(_width,npos); + bool is_one_shared_element = false; + cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; + if (is_one_shared_element) cimglist_for(*this,l) list[npos + l].assign(_data[l]); + else cimglist_for(*this,l) _data[l].move_to(list[npos + l]); + assign(); + return list; + } + + //! Swap all fields between two list instances. + /** + \param list List to swap fields with. + \note Can be used to exchange the content of two lists in a fast way. + **/ + CImgList& swap(CImgList& list) { + cimg::swap(_width,list._width,_allocated_width,list._allocated_width); + cimg::swap(_data,list._data); + return list; + } + + //! Return a reference to an empty list. + /** + \note Can be used to define default values in a function taking a CImgList as an argument. + \code + void f(const CImgList& list=CImgList::empty()); + \endcode + **/ + static CImgList& empty() { + static CImgList _empty; + return _empty.assign(); + } + + //! Return a reference to an empty list \const. + static const CImgList& const_empty() { + static const CImgList _empty; + return _empty; + } + + //@} + //------------------------------------------ + // + //! \name Overloaded Operators + //@{ + //------------------------------------------ + + //! Return a reference to one image element of the list. + /** + \param pos Indice of the image element. + **/ + CImg& operator()(const unsigned int pos) { +#if cimg_verbosity>=3 + if (pos>=_width) { + cimg::warn(_cimglist_instance + "operator(): Invalid image request, at position [%u].", + cimglist_instance, + pos); + return *_data; + } +#endif + return _data[pos]; + } + + //! Return a reference to one image of the list. + /** + \param pos Indice of the image element. + **/ + const CImg& operator()(const unsigned int pos) const { + return const_cast*>(this)->operator()(pos); + } + + //! Return a reference to one pixel value of one image of the list. + /** + \param pos Indice of the image element. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note list(n,x,y,z,c) is equivalent to list[n](x,y,z,c). + **/ + T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) { + return (*this)[pos](x,y,z,c); + } + + //! Return a reference to one pixel value of one image of the list \const. + const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int c=0) const { + return (*this)[pos](x,y,z,c); + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + operator CImg*() { + return _data; + } + + //! Return pointer to the first image of the list \const. + operator const CImg*() const { + return _data; + } + + //! Construct list from one image \inplace. + /** + \param img Input image to copy in the constructed list. + \note list = img; is equivalent to list.assign(img);. + **/ + template + CImgList& operator=(const CImg& img) { + return assign(img); + } + + //! Construct list from another list. + /** + \param list Input list to copy. + \note list1 = list2 is equivalent to list1.assign(list2);. + **/ + template + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list from another list \specialization. + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Construct list by reading the content of a file \inplace. + /** + \see CImgList(const char *const). + **/ + CImgList& operator=(const char *const filename) { + return assign(filename); + } + + //! Construct list from the content of a display window \inplace. + /** + \see CImgList(const CImgDisplay&). + **/ + CImgList& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return a non-shared copy of a list. + /** + \note +list is equivalent to CImgList(list,false). + It forces the copy to have non-shared elements. + **/ + CImgList operator+() const { + return CImgList(*this,false); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end. + /** + \param img Image inserted at the end of the instance copy. + \note Define a convenient way to create temporary lists of images, as in the following code: + \code + (img1,img2,img3,img4).display("My four images"); + \endcode + **/ + template + CImgList& operator,(const CImg& img) { + return insert(img); + } + + //! Return a copy of the list instance, where image \c img has been inserted at the end \const. + template + CImgList operator,(const CImg& img) const { + return (+*this).insert(img); + } + + //! Return a copy of the list instance, where all elements of input list \c list have been inserted at the end. + /** + \param list List inserted at the end of the instance copy. + **/ + template + CImgList& operator,(const CImgList& list) { + return insert(list); + } + + //! Return a copy of the list instance, where all elements of input \c list have been inserted at the end \const. + template + CImgList& operator,(const CImgList& list) const { + return (+*this).insert(list); + } + + //! Return image corresponding to the appending of all images of the instance list along specified axis. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \note list>'x' is equivalent to list.get_append('x'). + **/ + CImg operator>(const char axis) const { + return get_append(axis,0); + } + + //! Return list corresponding to the splitting of all images of the instance list along specified axis. + /** + \param axis Axis used for image splitting. + \note list<'x' is equivalent to list.get_split('x'). + **/ + CImgList operator<(const char axis) const { + return get_split(axis); + } + + //@} + //------------------------------------- + // + //! \name Instance Characteristics + //@{ + //------------------------------------- + + //! Return the type of image pixel values as a C string. + /** + Return a \c char* string containing the usual type name of the image pixel values + (i.e. a stringified version of the template parameter \c T). + \note + - The returned string may contain spaces (as in \c "unsigned char"). + - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to size() but returns result as a (signed) integer. + **/ + int width() const { + return (int)_width; + } + + //! Return the size of the list, i.e. the number of images contained in it. + /** + \note Similar to width() but returns result as an unsigned integer. + **/ + unsigned int size() const { + return _width; + } + + //! Return pointer to the first image of the list. + /** + \note Images in a list are stored as a buffer of \c CImg. + **/ + CImg *data() { + return _data; + } + + //! Return pointer to the first image of the list \const. + const CImg *data() const { + return _data; + } + + //! Return pointer to the pos-th image of the list. + /** + \param pos Indice of the image element to access. + \note list.data(n); is equivalent to list.data + n;. + **/ +#if cimg_verbosity>=3 + CImg *data(const unsigned int pos) { + if (pos>=size()) + cimg::warn(_cimglist_instance + "data(): Invalid pointer request, at position [%u].", + cimglist_instance, + pos); + return _data + pos; + } + + const CImg *data(const unsigned int l) const { + return const_cast*>(this)->data(l); + } +#else + CImg *data(const unsigned int l) { + return _data + l; + } + + //! Return pointer to the pos-th image of the list \const. + const CImg *data(const unsigned int l) const { + return _data + l; + } +#endif + + //! Return iterator to the first image of the list. + /** + **/ + iterator begin() { + return _data; + } + + //! Return iterator to the first image of the list \const. + const_iterator begin() const { + return _data; + } + + //! Return iterator to one position after the last image of the list. + /** + **/ + iterator end() { + return _data + _width; + } + + //! Return iterator to one position after the last image of the list \const. + const_iterator end() const { + return _data + _width; + } + + //! Return reference to the first image of the list. + /** + **/ + CImg& front() { + return *_data; + } + + //! Return reference to the first image of the list \const. + const CImg& front() const { + return *_data; + } + + //! Return a reference to the last image of the list. + /** + **/ + const CImg& back() const { + return *(_data + _width - 1); + } + + //! Return a reference to the last image of the list \const. + CImg& back() { + return *(_data + _width - 1); + } + + //! Return pos-th image of the list. + /** + \param pos Indice of the image element to access. + **/ + CImg& at(const int pos) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "at(): Empty instance.", + cimglist_instance); + + return _data[cimg::cut(pos,0,width() - 1)]; + } + + //! Access to pixel value with Dirichlet boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZC(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions. + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions \const. + T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZC(): Empty instance.", + cimglist_instance); + + return _atNXYZC(pos,x,y,z,c); + } + + T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + T _atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZ(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z) \const. + T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXYZ(): Empty instance.", + cimglist_instance); + + return _atNXYZ(pos,x,y,z,c); + } + + T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + T _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXY(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. + T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNXY(): Empty instance.", + cimglist_instance); + + return _atNXY(pos,x,y,z,c); + } + + T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + T _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x) \const. + T atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atX(x,y,z,c,out_value); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x) \const. + T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atNX(): Empty instance.", + cimglist_instance); + + return _atNX(pos,x,y,z,c); + } + + T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + T _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \param out_value Default value returned if \c offset is outside image bounds. + \note 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); + } + + //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { + return (pos<0 || pos>=(int)_width)?out_value:(*this)(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos). + /** + \param pos Indice of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \param c C-coordinate of the pixel value. + \note 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=0, const int y=0, const int z=0, const int c=0) { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos) \const. + T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "atN(): Empty instance.", + cimglist_instance); + return _atN(pos,x,y,z,c); + } + + T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { + return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); + } + + //@} + //------------------------------------- + // + //! \name Instance Checking + //@{ + //------------------------------------- + + //! Return \c true if list is empty. + /** + **/ + bool is_empty() const { + return (!_data || !_width); + } + + //! Test if number of image elements is equal to specified value. + /** + \param size_n Number of image elements to test. + **/ + bool is_sameN(const unsigned int size_n) const { + return _width==size_n; + } + + //! Test if number of image elements is equal between two images lists. + /** + \param list Input list to compare with. + **/ + template + bool is_sameN(const CImgList& list) const { + return is_sameN(list._width); + } + + // Define useful functions to check list dimensions. + // (cannot be documented because macro-generated). +#define _cimglist_def_is_same1(axis) \ + bool is_same##axis(const unsigned int val) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(val); return res; \ + } \ + bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \ + return is_sameN(n) && is_same##axis(val); \ + } \ + +#define _cimglist_def_is_same2(axis1,axis2) \ + bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2(val1,val2); return res; \ + } \ + bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \ + return is_sameN(n) && is_same##axis1##axis2(val1,val2); \ + } \ + +#define _cimglist_def_is_same3(axis1,axis2,axis3) \ + bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, \ + const unsigned int val3) const { \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2##axis3(val1,val2,val3); \ + return res; \ + } \ + bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, \ + const unsigned int val2, const unsigned int val3) const { \ + return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \ + } \ + +#define _cimglist_def_is_same(axis) \ + template 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; \ + } \ + 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)); \ + } \ + template bool is_sameN##axis(const CImgList& list) const { \ + return (is_sameN(list) && is_same##axis(list)); \ + } + + _cimglist_def_is_same(XY) + _cimglist_def_is_same(XZ) + _cimglist_def_is_same(XC) + _cimglist_def_is_same(YZ) + _cimglist_def_is_same(YC) + _cimglist_def_is_same(XYZ) + _cimglist_def_is_same(XYC) + _cimglist_def_is_same(YZC) + _cimglist_def_is_same(XYZC) + _cimglist_def_is_same1(X) + _cimglist_def_is_same1(Y) + _cimglist_def_is_same1(Z) + _cimglist_def_is_same1(C) + _cimglist_def_is_same2(X,Y) + _cimglist_def_is_same2(X,Z) + _cimglist_def_is_same2(X,C) + _cimglist_def_is_same2(Y,Z) + _cimglist_def_is_same2(Y,C) + _cimglist_def_is_same2(Z,C) + _cimglist_def_is_same3(X,Y,Z) + _cimglist_def_is_same3(X,Y,C) + _cimglist_def_is_same3(X,Z,C) + _cimglist_def_is_same3(Y,Z,C) + + //! Test if dimensions of each image of the list match specified arguments. + /** + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameXYZC(const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + bool res = true; + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_sameXYZC(dx,dy,dz,dc); + return res; + } + + //! Test if list dimensions match specified arguments. + /** + \param n Number of images in the list. + \param dx Checked image width. + \param dy Checked image height. + \param dz Checked image depth. + \param dc Checked image spectrum. + **/ + bool is_sameNXYZC(const unsigned int n, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) const { + return is_sameN(n) && is_sameXYZC(dx,dy,dz,dc); + } + + //! Test if list contains one particular pixel location. + /** + \param n Index of the image whom checked pixel value belong to. + \param x X-coordinate of the checked pixel value. + \param y Y-coordinate of the checked pixel value. + \param z Z-coordinate of the checked pixel value. + \param c C-coordinate of the checked pixel value. + **/ + bool containsNXYZC(const int n, const int x=0, const int y=0, const int z=0, const int c=0) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width && x>=0 && x<_data[n].width() && y>=0 && y<_data[n].height() && + z>=0 && z<_data[n].depth() && c>=0 && c<_data[n].spectrum(); + } + + //! Test if list contains image with specified indice. + /** + \param n Index of the checked image. + **/ + bool containsN(const int n) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width; + } + + //! Test if one image of the list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \param[out] c C-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z,c). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const { + if (is_empty()) return false; + cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; } + return false; + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \param[out] z Z-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y,z). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z) const { + t c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \param[out] y Y-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x,y). + **/ + template + bool contains(const T& pixel, t& n, t& x, t&y) const { + t z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \param[out] x X-coordinate of the pixel value, if test succeeds. + \note If true, set coordinates (n,x). + **/ + template + bool contains(const T& pixel, t& n, t& x) const { + t y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + \param[out] n Index of image containing the pixel value, if test succeeds. + \note If true, set coordinates (n). + **/ + template + bool contains(const T& pixel, t& n) const { + t x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if one of the image list contains the specified referenced value. + /** + \param pixel Reference to pixel value to test. + **/ + bool contains(const T& pixel) const { + unsigned int n, x, y, z, c; + return contains(pixel,n,x,y,z,c); + } + + //! Test if the list contains the image 'img'. + /** + \param img Reference to image to test. + \param[out] n Index of image in the list, if test succeeds. + \note If true, returns the position (n) of the image in the list. + **/ + template + bool contains(const CImg& img, t& n) const { + if (is_empty()) return false; + const CImg *const ptr = &img; + cimglist_for(*this,i) if (_data + i==ptr) { n = (t)i; return true; } + return false; + } + + //! Test if the list contains the image img. + /** + \param img Reference to image to test. + **/ + bool contains(const CImg& img) const { + unsigned int n; + return contains(img,n); + } + + //@} + //------------------------------------- + // + //! \name Mathematical Functions + //@{ + //------------------------------------- + + //! Return a reference to the minimum pixel value of the instance list. + /** + **/ + T& min() { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the maximum pixel value of the instance list \const. + const T& max() const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T max_value = *ptr_max; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); + } + return *ptr_max; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + T& min_max(t& max_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well \const. + /** + \param[out] max_val Value of the maximum value found. + **/ + template + const T& min_max(t& max_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_min, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptr_min; + } + + //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well. + /** + \param[out] min_val Value of the minimum value found. + **/ + template + T& max_min(t& min_val) { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + const T& max_min(t& min_val) const { + 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(): %s.", + _data?"List of empty images":"Empty instance", + cimglist_instance); + T min_value = *ptr_max, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_for(img,ptrs,T) { + const T val = *ptrs; + if (val>max_value) { max_value = val; ptr_max = ptrs; } + if (val + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + if (is_shared) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified shared image " + "CImg<%s>(%u,%u,%u,%u,%p) at position %u (pixel types are different).", + cimglist_instance, + img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos); + + CImg *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 + _data = new_data; + *_data = img; + } else { + 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 + 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; + _data[npos] = img; + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \specialization. + CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if (npos>_width) + throw CImgArgumentException(_cimglist_instance + "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " + "at position %u.", + cimglist_instance, + img._width,img._height,img._depth,img._spectrum,img._data,npos); + CImg *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 + _data = new_data; + if (is_shared && img) { + _data->_width = img._width; + _data->_height = img._height; + _data->_depth = img._depth; + _data->_spectrum = img._spectrum; + _data->_is_shared = true; + _data->_data = img._data; + } else *_data = img; + } + else { + if (new_data) { // Insert with re-allocation + if (npos) std::memcpy((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)); + if (is_shared && img) { + new_data[npos]._width = img._width; + new_data[npos]._height = img._height; + new_data[npos]._depth = img._depth; + new_data[npos]._spectrum = img._spectrum; + new_data[npos]._is_shared = true; + new_data[npos]._data = img._data; + } else { + new_data[npos]._width = new_data[npos]._height = new_data[npos]._depth = new_data[npos]._spectrum = 0; + new_data[npos]._data = 0; + new_data[npos] = img; + } + std::memset((void*)_data,0,sizeof(CImg)*(_width - 1)); + delete[] _data; + _data = new_data; + } 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) { + _data[npos]._width = img._width; + _data[npos]._height = img._height; + _data[npos]._depth = img._depth; + _data[npos]._spectrum = img._spectrum; + _data[npos]._is_shared = true; + _data[npos]._data = img._data; + } else { + _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; + _data[npos]._data = 0; + _data[npos] = img; + } + } + } + return *this; + } + + //! Insert a copy of the image \c img into the current image list, at position \c pos \newinstance. + template + CImgList get_insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(img,pos,is_shared); + } + + //! Insert n empty images img into the current image list, at position \p pos. + /** + \param n Number of empty images to insert. + \param pos Index of the insertion. + **/ + CImgList& insert(const unsigned int n, const unsigned int pos=~0U) { + CImg empty; + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i get_insert(const unsigned int n, const unsigned int pos=~0U) const { + return (+*this).insert(n,pos); + } + + //! Insert \c n copies of the image \c img into the current image list, at position \c pos. + /** + \param n Number of image copies to insert. + \param img Image to insert by copy. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of \c img or not. + **/ + template + CImgList& insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + insert(img,npos,is_shared); + for (unsigned int i = 1; i + CImgList get_insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,img,pos,is_shared); + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos. + /** + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) { + const unsigned int npos = pos==~0U?_width:pos; + if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos + l,is_shared); + else insert(CImgList(list),npos,is_shared); + return *this; + } + + //! Insert a copy of the image list \c list into the current image list, starting from position \c pos \newinstance. + template + CImgList get_insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) const { + return (+*this).insert(list,pos,is_shared); + } + + //! Insert n copies of the list \c list at position \c pos of the current list. + /** + \param n Number of list copies to insert. + \param list Image list to insert. + \param pos Index of the insertion. + \param is_shared Tells if inserted images are shared copies of images of \c list or not. + **/ + template + CImgList& insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?_width:pos; + for (unsigned int i = 0; i + CImgList get_insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, + const bool is_shared=false) const { + return (+*this).insert(n,list,pos,is_shared); + } + + //! Remove all images between from indexes. + /** + \param pos1 Starting index of the removal. + \param pos2 Ending index of the removal. + **/ + CImgList& remove(const unsigned int pos1, const unsigned int pos2) { + const unsigned int + npos1 = pos1=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + else { + if (tpos2>=_width) + throw CImgArgumentException(_cimglist_instance + "remove(): Invalid remove request at positions %u->%u.", + cimglist_instance, + npos1,tpos2); + + for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign(); + const unsigned int nb = 1 + npos2 - npos1; + if (!(_width-=nb)) return assign(); + if (_width>(_allocated_width>>2) || _allocated_width<=16) { // Removing items without reallocation + if (npos1!=_width) + std::memmove((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; + 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); + if (npos1!=_width) + std::memcpy((void*)(new_data + npos1),(void*)(_data + npos2 + 1),sizeof(CImg)*(_width - npos1)); + if (_width!=_allocated_width) + std::memset((void*)(new_data + _width),0,sizeof(CImg)*(_allocated_width - _width)); + std::memset((void*)_data,0,sizeof(CImg)*(_width + nb)); + delete[] _data; + _data = new_data; + } + } + return *this; + } + + //! Remove all images between from indexes \newinstance. + CImgList get_remove(const unsigned int pos1, const unsigned int pos2) const { + return (+*this).remove(pos1,pos2); + } + + //! Remove image at index \c pos from the image list. + /** + \param pos Index of the image to remove. + **/ + CImgList& remove(const unsigned int pos) { + return remove(pos,pos); + } + + //! Remove image at index \c pos from the image list \newinstance. + CImgList get_remove(const unsigned int pos) const { + return (+*this).remove(pos); + } + + //! Remove last image. + /** + **/ + CImgList& remove() { + return remove(_width - 1); + } + + //! Remove last image \newinstance. + CImgList get_remove() const { + return (+*this).remove(); + } + + //! Reverse list order. + CImgList& reverse() { + for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width - 1 - l]); + return *this; + } + + //! Reverse list order \newinstance. + CImgList get_reverse() const { + return (+*this).reverse(); + } + + //! Return a sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList& images(const unsigned int pos0, const unsigned int pos1) { + return get_images(pos0,pos1).move_to(*this); + } + + //! Return a sublist \newinstance. + CImgList get_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l]); + return res; + } + + //! Return a shared sublist. + /** + \param pos0 Starting index of the sublist. + \param pos1 Ending index of the sublist. + **/ + CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a shared sublist \newinstance. + const CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) const { + if (pos0>pos1 || pos1>=_width) + throw CImgArgumentException(_cimglist_instance + "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", + cimglist_instance, + pos0,pos1); + CImgList res(pos1 - pos0 + 1); + cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); + return res; + } + + //! Return a single image which is the appending of all images of the current CImgList instance. + /** + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignment. + **/ + CImg get_append(const char axis, const float align=0) const { + if (is_empty()) return CImg(); + if (_width==1) return +((*this)[0]); + unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0; + CImg res; + switch (cimg::lowercase(axis)) { + case 'x' : { // Along the X-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx+=img._width; + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image(pos, + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._width; + } + } break; + case 'y' : { // Along the Y-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy+=img._height; + dz = std::max(dz,img._depth); + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + pos, + (int)(align*(dz - img._depth)), + (int)(align*(dc - img._spectrum)), + img); + pos+=img._height; + } + } break; + case 'z' : { // Along the Z-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz+=img._depth; + dc = std::max(dc,img._spectrum); + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + pos, + (int)(align*(dc - img._spectrum)), + img); + pos+=img._depth; + } + } break; + default : { // Along the C-axis + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) { + dx = std::max(dx,img._width); + dy = std::max(dy,img._height); + dz = std::max(dz,img._depth); + dc+=img._spectrum; + } + } + res.assign(dx,dy,dz,dc,(T)0); + if (res) cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + if (img) res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + pos, + img); + pos+=img._spectrum; + } + } + } + return res; + } + + //! Return a list where each image has been split along the specified axis. + /** + \param axis Axis to split images along. + \param nb Number of spliting parts for each image. + **/ + CImgList& split(const char axis, const int nb=-1) { + return get_split(axis,nb).move_to(*this); + } + + //! Return a list where each image has been split along the specified axis \newinstance. + CImgList get_split(const char axis, const int nb=-1) const { + CImgList res; + cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U); + return res; + } + + //! Insert image at the end of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_back(const CImg& img) { + return insert(img); + } + + //! Insert image at the front of the list. + /** + \param img Image to insert. + **/ + template + CImgList& push_front(const CImg& img) { + return insert(img,0); + } + + //! Insert list at the end of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_back(const CImgList& list) { + return insert(list); + } + + //! Insert list at the front of the current list. + /** + \param list List to insert. + **/ + template + CImgList& push_front(const CImgList& list) { + return insert(list,0); + } + + //! Remove last image. + /** + **/ + CImgList& pop_back() { + return remove(_width - 1); + } + + //! Remove first image. + /** + **/ + CImgList& pop_front() { + return remove(0); + } + + //! Remove image pointed by iterator. + /** + \param iter Iterator pointing to the image to remove. + **/ + CImgList& erase(const iterator iter) { + return remove(iter - _data); + } + + //@} + //---------------------------------- + // + //! \name Data Input + //@{ + //---------------------------------- + + //! Display a simple interactive interface to select images or sublists. + /** + \param disp Window instance to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(CImgDisplay &disp, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + return _select(disp,0,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + //! Display a simple interactive interface to select images or sublists. + /** + \param title Title of a new window used to display selection and user interface. + \param feature_type Can be \c false to select a single image, or \c true to select a sublist. + \param axis Axis along whom images are appended for visualization. + \param align Alignment setting when images have not all the same size. + \param exit_on_anykey Exit function when any key is pressed. + \return A one-column vector containing the selected image indexes. + **/ + CImg get_select(const char *const title, const bool feature_type=true, + const char axis='x', const float align=0, + const bool exit_on_anykey=false) const { + CImgDisplay disp; + return _select(disp,title,feature_type,axis,align,exit_on_anykey,0,false,false,false); + } + + CImg _select(CImgDisplay &disp, const char *const title, const bool feature_type, + const char axis, const float align, const bool exit_on_anykey, + const unsigned int orig, const bool resize_disp, + const bool exit_on_rightbutton, const bool exit_on_wheel) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "select(): Empty instance.", + cimglist_instance); + + // Create image correspondence table and get list dimensions for visualization. + CImgList _indices; + unsigned int max_width = 0, max_height = 0, sum_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg& img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + if (h>max_height) max_height = h; + sum_width+=w; sum_height+=h; + if (axis=='x') CImg(w,1,1,1,(unsigned int)l).move_to(_indices); + else CImg(h,1,1,1,(unsigned int)l).move_to(_indices); + } + const CImg indices0 = _indices>'x'; + + // Create display window. + if (!disp) { + if (axis=='x') disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1); + else disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1); + if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else { + if (title) disp.set_title("%s",title); + 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); + } + + const unsigned int old_normalization = disp.normalization(); + bool old_is_resized = disp.is_resized(); + disp._normalization = 0; + disp.show().set_key(0); + static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; + + // Enter event loop. + CImg visu0, visu; + CImg indices; + CImg positions(_width,4,1,1,-1); + int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1; + bool is_clicked = false, is_selected = false, text_down = false, update_display = true; + unsigned int key = 0; + + while (!is_selected && !disp.is_closed() && !key) { + + // Create background image. + if (!visu0) { + visu0.assign(disp._width,disp._height,1,3,0); visu.assign(); + (indices0.get_resize(axis=='x'?visu0._width:visu0._height,1)).move_to(indices); + unsigned int ind = 0; + const CImg onexone(1,1,1,1,(T)0); + if (axis=='x') + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)x0; + positions(ind,1) = positions(ind,3) = (int)(align*(visu0.height() - res.height())); + positions(ind,2)+=res._width; + positions(ind,3)+=res._height - 1; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + else + 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). + 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); + positions(ind,0) = positions(ind,2) = (int)(align*(visu0.width() - res.width())); + positions(ind,1) = positions(ind,3) = (int)y0; + positions(ind,2)+=res._width - 1; + positions(ind,3)+=res._height; + visu0.draw_image(positions(ind,0),positions(ind,1),res); + } + if (axis=='x') --positions(ind,2); else --positions(ind,3); + update_display = true; + } + + if (!visu || oindice0!=indice0 || oindice1!=indice1) { + if (indice0>=0 && indice1>=0) { + visu.assign(visu0,false); + const int indm = std::min(indice0,indice1), indM = std::max(indice0,indice1); + for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) { + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + background_color,0.2f); + if ((axis=='x' && positions(ind,2) - positions(ind,0)>=8) || + (axis!='x' && positions(ind,3) - positions(ind,1)>=8)) + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + foreground_color,0.9f,0xAAAAAAAA); + } + if (is_clicked) visu.__draw_text(" Images #%u - #%u, Size = %u",text_down, + orig + indm,orig + indM,indM - indm + 1); + else visu.__draw_text(" Image #%u (%u,%u,%u,%u)",text_down, + orig + indice0, + _data[indice0]._width, + _data[indice0]._height, + _data[indice0]._depth, + _data[indice0]._spectrum); + update_display = true; + } else visu.assign(); + } + if (!visu) { visu.assign(visu0,true); update_display = true; } + if (update_display) { visu.display(disp); update_display = false; } + disp.wait(); + + // Manage user events. + const int xm = disp.mouse_x(), ym = disp.mouse_y(); + int indice = -1; + + if (xm>=0) { + indice = (int)indices(axis=='x'?xm:ym); + if (disp.button()&1) { + if (!is_clicked) { is_clicked = true; oindice0 = indice0; indice0 = indice; } + oindice1 = indice1; indice1 = indice; + if (!feature_type) is_selected = true; + } else { + if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; } + else is_selected = true; + } + } else { + if (is_clicked) { + if (!(disp.button()&1)) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + else indice1 = -1; + } else indice0 = indice1 = -1; + } + + if (disp.button()&4) { is_clicked = is_selected = false; indice0 = indice1 = -1; } + if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; indice1 = indice0 = -1; } + if (disp.wheel() && exit_on_wheel) is_selected = true; + + CImg filename(32); + switch (key = disp.key()) { +#if cimg_OS!=2 + case cimg::keyCTRLRIGHT : +#endif + case 0 : case cimg::keyCTRLLEFT : key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.set_fullscreen(false). + resize(cimg_fitscreen(axis=='x'?sum_width:max_width,axis=='x'?max_height:sum_height,1),false). + _is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; + disp.set_key(key,false); key = 0; visu0.assign(); + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + if (visu0) { + (+visu0).__draw_text(" Saving snapshot... ",text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",text_down,filename._data).display(disp); + } + disp.set_key(key,false).wait(); key = 0; + } break; + case cimg::keyO : + if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { + static unsigned int snap_number = 0; + std::FILE *file; + do { +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#else + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); +#endif + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu0).__draw_text(" Saving instance... ",text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",text_down,filename._data).display(disp); + disp.set_key(key,false).wait(); key = 0; + } break; + } + if (disp.is_resized()) { disp.resize(false); visu0.assign(); } + if (ym>=0 && ym<13) { if (!text_down) { visu.assign(); text_down = true; }} + else if (ym>=visu.height() - 13) { if (text_down) { visu.assign(); text_down = false; }} + if (!exit_on_anykey && key && key!=cimg::keyESC && + (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { + key = 0; + } + } + CImg res(1,2,1,1,-1); + if (is_selected) { + if (feature_type) res.fill(std::min(indice0,indice1),std::max(indice0,indice1)); + else res.fill(indice0); + } + if (!(disp.button()&2)) disp.set_button(); + disp._normalization = old_normalization; + disp._is_resized = old_is_resized; + disp.set_key(key); + return res; + } + + //! Load a list from a file. + /** + \param filename Filename to read data from. + **/ + CImgList& load(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load(): Specified filename is (null).", + cimglist_instance); + + if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { + CImg filename_local(256); + load(cimg::load_network(filename,filename_local)); + std::remove(filename_local); + return *this; + } + + const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + bool is_loaded = true; + try { +#ifdef cimglist_load_plugin + cimglist_load_plugin(filename); +#endif +#ifdef cimglist_load_plugin1 + cimglist_load_plugin1(filename); +#endif +#ifdef cimglist_load_plugin2 + cimglist_load_plugin2(filename); +#endif +#ifdef cimglist_load_plugin3 + cimglist_load_plugin3(filename); +#endif +#ifdef cimglist_load_plugin4 + cimglist_load_plugin4(filename); +#endif +#ifdef cimglist_load_plugin5 + cimglist_load_plugin5(filename); +#endif +#ifdef cimglist_load_plugin6 + cimglist_load_plugin6(filename); +#endif +#ifdef cimglist_load_plugin7 + cimglist_load_plugin7(filename); +#endif +#ifdef cimglist_load_plugin8 + cimglist_load_plugin8(filename); +#endif + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !*ext) load_cimg(filename); + else if (!cimg::strcasecmp(ext,"rec") || + !cimg::strcasecmp(ext,"par")) load_parrec(filename); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_video(filename); + else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + else is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + + // If nothing loaded, try to guess file format from magic number in file. + if (!is_loaded && !is_stdin) { + std::FILE *const file = cimg::std_fopen(filename,"rb"); + if (!file) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to open file '%s'.", + cimglist_instance, + filename); + } + + const char *const f_type = cimg::ftype(file,filename); + 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 is_loaded = false; + } catch (CImgIOException&) { is_loaded = false; } + } + + // If nothing loaded, try to load file as a single image. + if (!is_loaded) { + assign(1); + try { + _data->load(filename); + } catch (CImgIOException&) { + cimg::exception_mode(omode); + throw CImgIOException(_cimglist_instance + "load(): Failed to recognize format of file '%s'.", + cimglist_instance, + filename); + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load a list from a file \newinstance. + static CImgList get_load(const char *const filename) { + return CImgList().load(filename); + } + + //! Load a list from a .cimg file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_cimg(const char *const filename) { + return _load_cimg(0,filename); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename) { + return CImgList().load_cimg(filename); + } + + //! Load a list from a .cimg file. + /** + \param file File to read data from. + **/ + CImgList& load_cimg(std::FILE *const file) { + return _load_cimg(file,0); + } + + //! Load a list from a .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file) { + return CImgList().load_cimg(file); + } + + CImgList& _load_cimg(std::FILE *const file, const char *const filename) { +#ifdef cimg_use_zlib +#define _cimgz_load_cimg_case(Tss) { \ + Bytef *const cbuf = new Bytef[csiz]; \ + cimg::fread(cbuf,csiz,nfile); \ + raw.assign(W,H,D,C); \ + uLongf destlen = (ulongT)raw.size()*sizeof(Tss); \ + uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \ + delete[] cbuf; \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ +} +#else +#define _cimgz_load_cimg_case(Tss) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Unable to load compressed data from file '%s' unless zlib is enabled.", \ + cimglist_instance, \ + filename?filename:"(FILE*)"); +#endif + +#define _cimg_load_cimg_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; csiz = 0; \ + if ((err = cimg_sscanf(tmp,"%u %u %u %u #%lu",&W,&H,&D,&C,&csiz))<4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'.", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:("(FILE*)")); \ + if (W*H*D*C>0) { \ + CImg 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; \ + } \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + + const ulongT cimg_iobuffer = (ulongT)24*1024*1024; + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + unsigned long csiz; + int i, err; + do { + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; + } while (*tmp=='#' && i>=0); + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + assign(N); + _cimg_load_cimg_case("bool",bool); + _cimg_load_cimg_case("unsigned_char",unsigned char); + _cimg_load_cimg_case("uchar",unsigned char); + _cimg_load_cimg_case("char",char); + _cimg_load_cimg_case("unsigned_short",unsigned short); + _cimg_load_cimg_case("ushort",unsigned short); + _cimg_load_cimg_case("short",short); + _cimg_load_cimg_case("unsigned_int",unsigned int); + _cimg_load_cimg_case("uint",unsigned int); + _cimg_load_cimg_case("int",int); + _cimg_load_cimg_case("unsigned_long",ulongT); + _cimg_load_cimg_case("ulong",ulongT); + _cimg_load_cimg_case("long",longT); + _cimg_load_cimg_case("unsigned_int64",uint64T); + _cimg_load_cimg_case("uint64",uint64T); + _cimg_load_cimg_case("int64",int64T); + _cimg_load_cimg_case("float",float); + _cimg_load_cimg_case("double",double); + + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a sublist list from a (non compressed) .cimg file. + /** + \param filename Filename to read data from. + \param n0 Starting index of images to read (~0U for max). + \param n1 Ending index of images to read (~0U for max). + \param x0 Starting X-coordinates of image regions to read. + \param y0 Starting Y-coordinates of image regions to read. + \param z0 Starting Z-coordinates of image regions to read. + \param c0 Starting C-coordinates of image regions to read. + \param x1 Ending X-coordinates of image regions to read (~0U for max). + \param y1 Ending Y-coordinates of image regions to read (~0U for max). + \param z1 Ending Z-coordinates of image regions to read (~0U for max). + \param c1 Ending C-coordinates of image regions to read (~0U for max). + **/ + CImgList& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sublist list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \overloading. + CImgList& load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + //! Load a sub-image list from a (non compressed) .cimg file \newinstance. + static CImgList get_load_cimg(std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { + return CImgList().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); + } + + 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 z0, const unsigned int c0, + const unsigned int x1, const unsigned int y1, + const unsigned int z1, const unsigned int c1) { +#define _cimg_load_cimg_case2(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l<=nn1; ++l) { \ + j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \ + W = H = D = C = 0; \ + if (cimg_sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \ + throw CImgIOException(_cimglist_instance \ + "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \ + cimglist_instance, \ + W,H,D,C,l,filename?filename:"(FILE*)"); \ + if (W*H*D*C>0) { \ + if (l=W || ny0>=H || nz0>=D || nc0>=C) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const unsigned int \ + _nx1 = nx1==~0U?W - 1:nx1, \ + _ny1 = ny1==~0U?H - 1:ny1, \ + _nz1 = nz1==~0U?D - 1:nz1, \ + _nc1 = nc1==~0U?C - 1:nc1; \ + if (_nx1>=W || _ny1>=H || _nz1>=D || _nc1>=C) \ + throw CImgArgumentException(_cimglist_instance \ + "load_cimg(): Invalid specified coordinates " \ + "[%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " \ + "because image [%u] in file '%s' has size (%u,%u,%u,%u).", \ + cimglist_instance, \ + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,l,filename?filename:"(FILE*)",W,H,D,C); \ + CImg raw(1 + _nx1 - nx0); \ + CImg &img = _data[l - nn0]; \ + img.assign(1 + _nx1 - nx0,1 + _ny1 - ny0,1 + _nz1 - nz0,1 + _nc1 - nc0); \ + T *ptrd = img._data; \ + ulongT skipvb = nc0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int c = 1 + _nc1 - nc0; c; --c) { \ + const ulongT skipzb = nz0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + _nz1 - nz0; z; --z) { \ + const ulongT skipyb = ny0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + _ny1 - ny0; y; --y) { \ + const ulongT skipxb = nx0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + cimg::fread(raw._data,raw._width,nfile); \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); \ + const Tss *ptrs = raw._data; \ + for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \ + const ulongT skipxe = (W - 1 - _nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const ulongT skipye = (H - 1 - _ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const ulongT skipze = (D - 1 - _nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const ulongT skipve = (C - 1 - _nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Specified filename is (null).", + cimglist_instance); + unsigned int + nn0 = std::min(n0,n1), nn1 = std::max(n0,n1), + nx0 = std::min(x0,x1), nx1 = std::max(x0,x1), + ny0 = std::min(y0,y1), ny1 = std::max(y0,y1), + nz0 = std::min(z0,z1), nz1 = std::max(z0,z1), + nc0 = std::min(c0,c1), nc1 = std::max(c0,c1); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", + &N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + nn1 = n1==~0U?N - 1:n1; + if (nn1>=N) + throw CImgArgumentException(_cimglist_instance + "load_cimg(): Invalid specified coordinates [%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " + "because file '%s' contains only %u images.", + cimglist_instance, + n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,filename?filename:"(FILE*)",N); + assign(1 + nn1 - n0); + _cimg_load_cimg_case2("bool",bool); + _cimg_load_cimg_case2("unsigned_char",unsigned char); + _cimg_load_cimg_case2("uchar",unsigned char); + _cimg_load_cimg_case2("char",char); + _cimg_load_cimg_case2("unsigned_short",unsigned short); + _cimg_load_cimg_case2("ushort",unsigned short); + _cimg_load_cimg_case2("short",short); + _cimg_load_cimg_case2("unsigned_int",unsigned int); + _cimg_load_cimg_case2("uint",unsigned int); + _cimg_load_cimg_case2("int",int); + _cimg_load_cimg_case2("unsigned_long",ulongT); + _cimg_load_cimg_case2("ulong",ulongT); + _cimg_load_cimg_case2("long",longT); + _cimg_load_cimg_case2("unsigned_int64",uint64T); + _cimg_load_cimg_case2("uint64",uint64T); + _cimg_load_cimg_case2("int64",int64T); + _cimg_load_cimg_case2("float",float); + _cimg_load_cimg_case2("double",double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_cimg(): Unsupported pixel type '%s' for file '%s'.", + cimglist_instance, + str_pixeltype._data,filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file. + /** + \param filename Filename to read data from. + **/ + CImgList& load_parrec(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_parrec(): Specified filename is (null).", + cimglist_instance); + + CImg body(1024), filenamepar(1024), filenamerec(1024); + *body = *filenamepar = *filenamerec = 0; + const char *const ext = cimg::split_filename(filename,body); + if (!std::strcmp(ext,"par")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.rec",body._data); + } + if (!std::strcmp(ext,"PAR")) { + std::strncpy(filenamepar,filename,filenamepar._width - 1); + cimg_snprintf(filenamerec,filenamerec._width,"%s.REC",body._data); + } + if (!std::strcmp(ext,"rec")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.par",body._data); + } + if (!std::strcmp(ext,"REC")) { + std::strncpy(filenamerec,filename,filenamerec._width - 1); + cimg_snprintf(filenamepar,filenamepar._width,"%s.PAR",body._data); + } + std::FILE *file = cimg::fopen(filenamepar,"r"); + + // Parse header file + CImgList st_slices; + CImgList st_global; + CImg line(256); *line = 0; + int err; + do { err = std::fscanf(file,"%255[^\n]%*c",line._data); } while (err!=EOF && (*line=='#' || *line=='.')); + do { + unsigned int sn,size_x,size_y,pixsize; + float rs,ri,ss; + err = std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&size_x,&size_y,&ri,&rs,&ss); + if (err==7) { + CImg::vector((float)sn,(float)pixsize,(float)size_x,(float)size_y,ri,rs,ss,0).move_to(st_slices); + unsigned int i; for (i = 0; i::vector(size_x,size_y,sn).move_to(st_global); + else { + CImg &vec = st_global[i]; + if (size_x>vec[0]) vec[0] = size_x; + if (size_y>vec[1]) vec[1] = size_y; + vec[2] = sn; + } + st_slices[st_slices._width - 1][7] = (float)i; + } + } while (err==7); + + // Read data + std::FILE *file2 = cimg::fopen(filenamerec,"rb"); + cimglist_for(st_global,l) { + const CImg& vec = st_global[l]; + CImg(vec[0],vec[1],vec[2]).move_to(*this); + } + + cimglist_for(st_slices,l) { + const CImg& vec = st_slices[l]; + const unsigned int + sn = (unsigned int)vec[0] - 1, + pixsize = (unsigned int)vec[1], + size_x = (unsigned int)vec[2], + size_y = (unsigned int)vec[3], + imn = (unsigned int)vec[7]; + const float ri = vec[4], rs = vec[5], ss = vec[6]; + switch (pixsize) { + case 8 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 16 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 32 : { + CImg buf(size_x,size_y); + cimg::fread(buf._data,size_x*size_y,file2); + if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + default : + cimg::fclose(file); + cimg::fclose(file2); + throw CImgIOException(_cimglist_instance + "load_parrec(): Unsupported %d-bits pixel type for file '%s'.", + cimglist_instance, + pixsize,filename); + } + } + cimg::fclose(file); + cimg::fclose(file2); + if (!_width) + throw CImgIOException(_cimglist_instance + "load_parrec(): Failed to recognize valid PAR-REC data in file '%s'.", + cimglist_instance, + filename); + return *this; + } + + //! Load a list from a PAR/REC (Philips) file \newinstance. + static CImgList get_load_parrec(const char *const filename) { + return CImgList().load_parrec(filename); + } + + //! Load a list from a YUV image sequence file. + /** + \param filename Filename to read data from. + \param size_x Width of the images. + \param size_y Height of the images. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param first_frame Index of first image frame to read. + \param last_frame Index of last image frame to read. + \param step_frame Step applied between each frame. + \param yuv2rgb Apply YUV to RGB transformation during reading. + **/ + CImgList& load_yuv(const char *const filename, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(0,filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from a YUV image sequence file \newinstance. + static CImgList get_load_yuv(const char *const filename, + 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) { + return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \overloading. + CImgList& load_yuv(std::FILE *const file, + const unsigned int size_x, const unsigned int size_y, + 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) { + return _load_yuv(file,0,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load a list from an image sequence YUV file \newinstance. + static CImgList get_load_yuv(std::FILE *const file, + 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) { + return CImgList().load_yuv(file,size_x,size_y,chroma_subsampling, + first_frame,last_frame,step_frame,yuv2rgb); + } + + CImgList& _load_yuv(std::FILE *const file, const char *const filename, + 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) { + 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'.", + 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_frame YUV(size_x,size_y,1,3), UV(size_x/cfx,size_y/cfy,1,2); + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool stop_flag = false; + int err; + if (nfirst_frame) { + err = cimg::fseek(nfile,(uint64T)nfirst_frame*(YUV._width*YUV._height + 2*UV._width*UV._height),SEEK_CUR); + if (err) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "load_yuv(): File '%s' doesn't contain frame number %u.", + cimglist_instance, + filename?filename:"(FILE*)",nfirst_frame); + } + } + unsigned int frame; + for (frame = nfirst_frame; !stop_flag && frame<=nlast_frame; frame+=nstep_frame) { + YUV.get_shared_channel(0).fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(YUV._data),1,(size_t)YUV._width*YUV._height,nfile); + if (err!=(int)(YUV._width*YUV._height)) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + UV.fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread! + err = (int)std::fread((void*)(UV._data),1,(size_t)UV.size(),nfile); + if (err!=(int)(UV.size())) { + stop_flag = true; + if (err>0) + cimg::warn(_cimglist_instance + "load_yuv(): File '%s' contains incomplete data or given image dimensions " + "(%u,%u) are incorrect.", + cimglist_instance, + filename?filename:"(FILE*)",size_x,size_y); + } else { + const ucharT *ptrs1 = UV._data, *ptrs2 = UV.data(0,0,0,1); + ucharT *ptrd1 = YUV.data(0,0,0,1), *ptrd2 = YUV.data(0,0,0,2); + const unsigned int wd = YUV._width; + switch (chroma_subsampling) { + case 420 : + cimg_forY(UV,y) { + cimg_forX(UV,x) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd1[wd] = U; *(ptrd1)++ = U; + ptrd2[wd] = V; *(ptrd2)++ = V; + ptrd2[wd] = V; *(ptrd2)++ = V; + } + ptrd1+=wd; ptrd2+=wd; + } + break; + case 422 : + cimg_forXY(UV,x,y) { + const ucharT U = *(ptrs1++), V = *(ptrs2++); + *(ptrd1++) = U; *(ptrd1++) = U; + *(ptrd2++) = V; *(ptrd2++) = V; + } + break; + default : + YUV.draw_image(0,0,0,1,UV); + } + if (yuv2rgb) YUV.YCbCrtoRGB(); + insert(YUV); + if (nstep_frame>1) cimg::fseek(nfile,(uint64T)(nstep_frame - 1)*(size_x*size_y + size_x*size_y/2),SEEK_CUR); + } + } + } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_yuv() : Missing data in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + if (stop_flag && nlast_frame!=~0U && frame!=nlast_frame) + cimg::warn(_cimglist_instance + "load_yuv(): Frame %d not reached since only %u frames were found in file '%s'.", + cimglist_instance, + nlast_frame,frame - 1,filename?filename:"(FILE*)"); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a video file, using OpenCV library. + /** + \param filename Filename, as a C-string. + \param first_frame Index of the first frame to read. + \param last_frame Index of the last frame to read. + \param step_frame Step value for frame reading. + \note If step_frame==0, the current video stream is forced to be released (without any frames read). + **/ + CImgList& load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { +#ifndef cimg_use_opencv + if (first_frame || last_frame!=~0U || step_frame>1) + throw CImgArgumentException(_cimglist_instance + "load_video() : File '%s', arguments 'first_frame', 'last_frame' " + "and 'step_frame' can be only set when using OpenCV " + "(-Dcimg_use_opencv must be enabled).", + cimglist_instance,filename); + return load_ffmpeg_external(filename); +#else + static CvCapture *captures[32] = { 0 }; + static CImgList filenames(32); + static CImg positions(32,1,1,1,0); + static int last_used_index = -1; + + // Detect if a video capture already exists for the specified filename. + cimg::mutex(9); + int index = -1; + if (filename) { + if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) { + index = last_used_index; + } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) { + index = l; break; + } + } else index = last_used_index; + cimg::mutex(9,0); + + // Release stream if needed. + if (!step_frame || (index>=0 && positions[index]>first_frame)) { + if (index>=0) { + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; filenames[index].assign(); positions[index] = 0; + if (last_used_index==index) last_used_index = -1; + index = -1; + cimg::mutex(9,0); + } else + if (filename) + cimg::warn(_cimglist_instance + "load_video() : File '%s', no opened video stream associated with filename found.", + cimglist_instance,filename); + else + cimg::warn(_cimglist_instance + "load_video() : No opened video stream found.", + cimglist_instance,filename); + if (!step_frame) return *this; + } + + // Find empty slot for capturing video stream. + if (index<0) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_video(): No already open video reader found. You must specify a " + "non-(null) filename argument for the first call.", + cimglist_instance); + else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); } + if (index<0) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', no video reader slots available. " + "You have to release some of your previously opened videos.", + cimglist_instance,filename); + cimg::mutex(9); + captures[index] = cvCaptureFromFile(filename); + CImg::string(filename).move_to(filenames[index]); + positions[index] = 0; + cimg::mutex(9,0); + if (!captures[index]) { + filenames[index].assign(); + 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::mutex(9); + const unsigned int nb_frames = (unsigned int)std::max(0.,cvGetCaptureProperty(captures[index], + CV_CAP_PROP_FRAME_COUNT)); + cimg::mutex(9,0); + assign(); + + // Skip frames if necessary. + bool go_on = true; + unsigned int &pos = positions[index]; + while (pos frame(src->width,src->height,1,3); + const int step = (int)(src->widthStep - 3*src->width); + const unsigned char* ptrs = (unsigned char*)src->imageData; + T *ptr_r = frame.data(0,0,0,0), *ptr_g = frame.data(0,0,0,1), *ptr_b = frame.data(0,0,0,2); + if (step>0) cimg_forY(frame,y) { + cimg_forX(frame,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); } + ptrs+=step; + } else for (ulongT siz = (ulongT)src->width*src->height; siz; --siz) { + *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); + } + frame.move_to(*this); + ++pos; + + bool skip_failed = false; + for (unsigned int i = 1; i=nb_frames)) { // Close video stream when necessary + cimg::mutex(9); + cvReleaseCapture(&captures[index]); + captures[index] = 0; + filenames[index].assign(); + positions[index] = 0; + index = -1; + cimg::mutex(9,0); + } + + cimg::mutex(9); + last_used_index = index; + cimg::mutex(9,0); + + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', unable to locate frame %u.", + cimglist_instance,filename,first_frame); + return *this; +#endif + } + + //! Load an image from a video file, using OpenCV library \newinstance. + static CImgList get_load_video(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + return CImgList().load_video(filename,first_frame,last_frame,step_frame); + } + + //! Load an image from a video file using the external tool 'ffmpeg'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_ffmpeg_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + 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=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::ffmpeg_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp2)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + unsigned int i = 1; + for (bool stop_flag = false; !stop_flag; ++i) { + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,i); + CImg img; + try { img.load_pnm(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + cimg::exception_mode(omode); + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_ffmpeg_external(): Failed to open file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + return *this; + } + + //! Load an image from a video file using the external tool 'ffmpeg' \newinstance. + static CImgList get_load_ffmpeg_external(const char *const filename) { + return CImgList().load_ffmpeg_external(filename); + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gif_external(const char *const filename) { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "load_gif_external(): Specified filename is (null).", + cimglist_instance); + 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(); } + if (is_empty()) + throw CImgIOException(_cimglist_instance + "load_gif_external(): Failed to open file '%s'.", + cimglist_instance,filename); + return *this; + } + + CImgList& _load_gif_external(const char *const filename, const bool use_graphicsmagick=false) { + 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()); + 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=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\"", + cimg::imagemagick_path(), + CImg::string(filename)._system_strescape().data(), + CImg::string(filename_tmp)._system_strescape().data()); + cimg::system(command,0); + const unsigned int omode = cimg::exception_mode(); + cimg::exception_mode(0); + assign(); + + // Try to read a single frame gif. + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png",filename_tmp._data); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + else { // Try to read animated gif + unsigned int i = 0; + for (bool stop_flag = false; !stop_flag; ++i) { + if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.%u",filename_tmp._data,i); + else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-%u.png",filename_tmp._data,i); + CImg img; + try { img.load_png(filename_tmp2); } + catch (CImgException&) { stop_flag = true; } + if (img) { img.move_to(*this); std::remove(filename_tmp2); } + } + } + cimg::exception_mode(omode); + return *this; + } + + //! Load gif file, using ImageMagick or GraphicsMagick's external tools \newinstance. + static CImgList get_load_gif_external(const char *const filename) { + return CImgList().load_gif_external(filename); + } + + //! Load a gzipped list, using external tool 'gunzip'. + /** + \param filename Filename to read data from. + **/ + CImgList& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Specified filename is (null).", + cimglist_instance); + 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), + *ext2 = cimg::split_filename(body,0); + std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + 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=cimg::std_fopen(filename_tmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimglist_instance + "load_gzip_external(): Failed to open file '%s'.", + cimglist_instance, + filename); + + } else cimg::fclose(file); + load(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load a gzipped list, using external tool 'gunzip' \newinstance. + static CImgList get_load_gzip_external(const char *const filename) { + 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] 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 + nfirst_frame = first_frame::get_load_tiff(filename)); +#else +#if cimg_verbosity<3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn(_cimglist_instance + "load_tiff(): Invalid specified frame range is [%u,%u] (step %u) since " + "file '%s' contains %u image(s).", + cimglist_instance, + nfirst_frame,nlast_frame,nstep_frame,filename,nb_images); + + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images - 1; + assign(1 + (nlast_frame - nfirst_frame)/nstep_frame); + TIFFSetDirectory(tif,0); + cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame,voxel_size,description); + TIFFClose(tif); + } else throw CImgIOException(_cimglist_instance + "load_tiff(): Failed to open file '%s'.", + cimglist_instance, + filename); + return *this; +#endif + } + + //! Load a multi-page TIFF file \newinstance. + static CImgList 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); + } + + //@} + //---------------------------------- + // + //! \name Data Output + //@{ + //---------------------------------- + + //! Print information about the list on the standard output. + /** + \param title Label set to the information displayed. + \param display_stats Tells if image statistics must be computed and displayed. + **/ + const CImgList& print(const char *const title=0, const bool display_stats=true) const { + unsigned int msiz = 0; + cimglist_for(*this,l) msiz+=_data[l].size(); + msiz*=sizeof(T); + const unsigned int mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U; + CImg _title(64); + if (!title) cimg_snprintf(_title,_title._width,"CImgList<%s>",pixel_type()); + std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = %u/%u [%u %s], %sdata%s = (CImg<%s>*)%p", + cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, + cimg::t_bold,cimg::t_normal,(void*)this, + cimg::t_bold,cimg::t_normal,_width,_allocated_width, + mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)), + mdisp==0?"b":(mdisp==1?"Kio":"Mio"), + cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); + if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end() - 1)); + else std::fprintf(cimg::output(),".\n"); + + char tmp[16] = { 0 }; + cimglist_for(*this,ll) { + cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll); + std::fprintf(cimg::output()," "); + _data[ll].print(tmp,display_stats); + if (ll==3 && width()>8) { ll = width() - 5; std::fprintf(cimg::output()," ...\n"); } + } + std::fflush(cimg::output()); + return *this; + } + + //! Display the current CImgList instance in an existing CImgDisplay window (by reference). + /** + \param disp Reference to an existing CImgDisplay instance, where the current image list will be displayed. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \param align Appending alignmenet. + \note This function displays the list images of the current CImgList instance into an existing + CImgDisplay window. + Images of the list are appended in a single temporarly image for visualization purposes. + The function returns immediately. + **/ + const CImgList& display(CImgDisplay &disp, const char axis='x', const float align=0) const { + disp.display(*this,axis,align); + return *this; + } + + //! Display the current CImgList instance in a new display window. + /** + \param disp Display window. + \param display_info Tells if image information are displayed on the standard output. + \param axis Alignment axis for images viewing. + \param align Apending alignment. + \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. + 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, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + bool is_exit = false; + return _display(disp,0,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + //! Display the current CImgList instance in a new display window. + /** + \param title Title of the opening display window. + \param display_info Tells if list information must be written on standard output. + \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. + \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. + **/ + const CImgList& display(const char *const title=0, const bool display_info=true, + const char axis='x', const float align=0, + unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { + CImgDisplay disp; + bool is_exit = false; + return _display(disp,title,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); + } + + const CImgList& _display(CImgDisplay &disp, const char *const title, const CImgList *const titles, + const bool display_info, const char axis, const float align, unsigned int *const XYZ, + const bool exit_on_anykey, const unsigned int orig, const bool is_first_call, + bool &is_exit) const { + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "display(): Empty instance.", + cimglist_instance); + if (!disp) { + if (axis=='x') { + unsigned int sum_width = 0, max_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + sum_width+=w; + if (h>max_height) max_height = h; + } + disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:titles?titles->__display()._data:0,1); + } else { + unsigned int max_width = 0, sum_height = 0; + cimglist_for(*this,l) { + const CImg &img = _data[l]; + const unsigned int + w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), + h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); + if (w>max_width) max_width = w; + sum_height+=h; + } + disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:titles?titles->__display()._data:0,1); + } + if (!title && !titles) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); + } else if (title) disp.set_title("%s",title); + else if (titles) disp.set_title("%s",titles->__display()._data); + const CImg dtitle = CImg::string(disp.title()); + if (display_info) print(disp.title()); + disp.show().flush(); + + if (_width==1) { + const unsigned int dw = disp._width, dh = disp._height; + if (!is_first_call) + disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false); + disp.set_title("%s (%ux%ux%ux%u)", + dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum); + _data[0]._display(disp,0,false,XYZ,exit_on_anykey,!is_first_call); + if (disp.key()) is_exit = true; + disp.resize(cimg_fitscreen(dw,dh,1),false).set_title("%s",dtitle.data()); + } else { + bool disp_resize = !is_first_call; + while (!disp.is_closed() && !is_exit) { + const CImg s = _select(disp,0,true,axis,align,exit_on_anykey,orig,disp_resize,!is_first_call,true); + disp_resize = true; + if (s[0]<0 && !disp.wheel()) { // No selections done + if (disp.button()&2) { disp.flush(); break; } + is_exit = true; + } else if (disp.wheel()) { // Zoom in/out + const int wheel = disp.wheel(); + disp.set_wheel(); + if (!is_first_call && wheel<0) break; + if (wheel>0 && _width>=4) { + const unsigned int + delta = std::max(1U,(unsigned int)cimg::round(0.3*_width)), + ind0 = (unsigned int)std::max(0,s[0] - (int)delta), + ind1 = (unsigned int)std::min(width() - 1,s[0] + (int)delta); + if ((ind0!=0 || ind1!=_width - 1) && ind1 - ind0>=3) { + const CImgList sublist = get_shared_images(ind0,ind1); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(ind0,ind1); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + ind0,false,is_exit); + } + } + } else if (s[0]!=0 || s[1]!=width() - 1) { + const CImgList sublist = get_shared_images(s[0],s[1]); + CImgList t_sublist; + if (titles) t_sublist = titles->get_shared_images(s[0],s[1]); + sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, + orig + s[0],false,is_exit); + } + disp.set_title("%s",dtitle.data()); + } + } + return *this; + } + + // [internal] Return string to describe display title. + CImg __display() const { + CImg res, str; + cimglist_for(*this,l) { + CImg::string(_data[l]).move_to(str); + if (l!=width() - 1) { + str.resize(str._width + 1,1,1,1,0); + str[str._width - 2] = ','; + str[str._width - 1] = ' '; + } + res.append(str,'x'); + } + if (!res) return CImg(1,1,1,1,0).move_to(res); + cimg::strellipsize(res,128,false); + if (_width>1) { + const unsigned int l = (unsigned int)std::strlen(res); + if (res._width<=l + 16) res.resize(l + 16,1,1,1,0); + cimg_snprintf(res._data + l,16," (#%u)",_width); + } + return res; + } + + //! Save list into a file. + /** + \param filename Filename to write data to. + \param number When positive, represents an index added to the filename. Otherwise, no number is added. + \param digits Number of digits used for adding the number to the filename. + **/ + const CImgList& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save(): Specified filename is (null).", + cimglist_instance); + // Do not test for empty instances, since .cimg format is able to manage empty instances. + const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); + const char *const ext = cimg::split_filename(filename); + CImg nfilename(1024); + const char *const fn = is_stdout?filename:number>=0?cimg::number_filename(filename,number,digits,nfilename): + filename; + +#ifdef cimglist_save_plugin + cimglist_save_plugin(fn); +#endif +#ifdef cimglist_save_plugin1 + cimglist_save_plugin1(fn); +#endif +#ifdef cimglist_save_plugin2 + cimglist_save_plugin2(fn); +#endif +#ifdef cimglist_save_plugin3 + cimglist_save_plugin3(fn); +#endif +#ifdef cimglist_save_plugin4 + cimglist_save_plugin4(fn); +#endif +#ifdef cimglist_save_plugin5 + cimglist_save_plugin5(fn); +#endif +#ifdef cimglist_save_plugin6 + cimglist_save_plugin6(fn); +#endif +#ifdef cimglist_save_plugin7 + cimglist_save_plugin7(fn); +#endif +#ifdef cimglist_save_plugin8 + cimglist_save_plugin8(fn); +#endif + if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); + else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); + else if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); +#ifdef cimg_use_tiff + else if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); +#endif + else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + else { + if (_width==1) _data[0].save(fn,-1); + else cimglist_for(*this,l) { _data[l].save(fn,is_stdout?-1:l); if (is_stdout) std::fputc(EOF,cimg::_stdout()); } + } + return *this; + } + + //! Tell if an image list can be saved as one single file. + /** + \param filename Filename, as a C-string. + \return \c true if the file format supports multiple images, \c false otherwise. + **/ + static bool is_saveable(const char *const filename) { + const char *const ext = cimg::split_filename(filename); + if (!cimg::strcasecmp(ext,"cimgz") || +#ifdef cimg_use_tiff + !cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff") || +#endif + !cimg::strcasecmp(ext,"yuv") || + !cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mp4") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"ogg") || + !cimg::strcasecmp(ext,"ogv") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return true; + return false; + } + + //! Save image sequence as a GIF animated file. + /** + \param filename Filename to write data to. + \param fps Number of desired frames per second. + \param nb_loops Number of loops (\c 0 for infinite looping). + **/ + const CImgList& save_gif_external(const char *const filename, const float fps=25, + const unsigned int nb_loops=0) { + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + +#ifdef cimg_use_png +#define _cimg_save_gif_ext "png" +#else +#define _cimg_save_gif_ext "ppm" +#endif + + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_ext,filename_tmp._data); + if ((file=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::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_snprintf(command,command._width,"%s -delay %u -loop %u", + 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()); + CImg::string(command).move_to(filenames); + CImg _command = filenames>'x'; + cimg_for(_command,p,char) if (!*p) *p = ' '; + _command.back() = 0; + + cimg::system(_command); + 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'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for_in(*this,1,filenames._width - 1,l) std::remove(filenames[l]); + return *this; + } + + //! Save list as a YUV image sequence file. + /** + \param filename Filename to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(const char *const filename=0, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(0,filename,chroma_subsampling,is_rgb); + } + + //! Save image sequence into a YUV file. + /** + \param file File to write data to. + \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. + \param is_rgb Tells if the RGB to YUV conversion must be done for saving. + **/ + const CImgList& save_yuv(std::FILE *const file, + const unsigned int chroma_subsampling=444, + const bool is_rgb=true) const { + return _save_yuv(file,0,chroma_subsampling,is_rgb); + } + + const CImgList& _save_yuv(std::FILE *const file, const char *const filename, + const unsigned int chroma_subsampling, + const bool is_rgb) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified filename is (null).", + cimglist_instance); + if (chroma_subsampling!=420 && chroma_subsampling!=422 && chroma_subsampling!=444) + throw CImgArgumentException(_cimglist_instance + "save_yuv(): Specified chroma subsampling %u is invalid, for file '%s'.", + cimglist_instance, + chroma_subsampling,filename?filename:"(FILE*)"); + if (is_empty()) { cimg::fempty(file,filename); return *this; } + const unsigned int + cfx = chroma_subsampling==420 || chroma_subsampling==422?2:1, + cfy = chroma_subsampling==420?2:1, + w0 = (*this)[0]._width, h0 = (*this)[0]._height, + width0 = w0 + (w0%cfx), height0 = h0 + (h0%cfy); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + cimglist_for(*this,l) { + const CImg &frame = (*this)[l]; + cimg_forZ(frame,z) { + CImg YUV; + if (sizeof(T)==1 && !is_rgb && + frame._width==width0 && frame._height==height0 && frame._depth==1 && frame._spectrum==3) + YUV.assign((unsigned char*)frame._data,width0,height0,1,3,true); + else { + YUV = frame.get_slice(z); + if (YUV._width!=width0 || YUV._height!=height0) YUV.resize(width0,height0,1,-100,0); + if (YUV._spectrum!=3) YUV.resize(-100,-100,1,3,YUV._spectrum==1?1:0); + if (is_rgb) YUV.RGBtoYCbCr(); + } + if (chroma_subsampling==444) + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height*3,nfile); + else { + cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height,nfile); + CImg UV = YUV.get_channels(1,2); + UV.resize(UV._width/cfx,UV._height/cfy,1,2,2); + cimg::fwrite(UV._data,(size_t)UV._width*UV._height*2,nfile); + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param filename Filename to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(const char *const filename, const bool is_compressed=false) const { + return _save_cimg(0,filename,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, const bool is_compressed) const { + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "save_cimg(): Unable to save compressed data in file '%s' unless zlib is enabled, " + "saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); +#endif + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little"; + if (std::strstr(ptype,"unsigned")==ptype) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",_width,ptype + 9,etype); + else std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = siz + siz/100 + 16; + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "save_cimg(): Failed to save compressed data for file '%s', saving them uncompressed.", + cimglist_instance, + filename?filename:"(FILE*)"); + else { + std::fprintf(nfile," #%lu\n",csiz); + cimg::fwrite(cbuf,csiz,nfile); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + if (failed_to_compress) { // Write in a non-compressed way + std::fputc('\n',nfile); + cimg::fwrite(ref._data,ref.size(),nfile); + } + } else std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save list into a .cimg file. + /** + \param file File to write data to. + \param is_compressed Tells if data compression must be enabled. + **/ + const CImgList& save_cimg(std::FILE *file, const bool is_compressed=false) const { + return _save_cimg(file,0,is_compressed); + } + + const CImgList& _save_cimg(std::FILE *const file, const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { +#define _cimg_save_cimg_case(Ts,Tss) \ + if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l0) { \ + if (l=W || y0>=H || z0>=D || c0>=D) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ + else { \ + const CImg& img = (*this)[l - n0]; \ + const T *ptrs = img._data; \ + const unsigned int \ + x1 = x0 + img._width - 1, \ + y1 = y0 + img._height - 1, \ + z1 = z0 + img._depth - 1, \ + c1 = c0 + img._spectrum - 1, \ + nx1 = x1>=W?W - 1:x1, \ + ny1 = y1>=H?H - 1:y1, \ + nz1 = z1>=D?D - 1:z1, \ + nc1 = c1>=C?C - 1:c1; \ + CImg raw(1 + nx1 - x0); \ + const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int v = 1 + nc1 - c0; v; --v) { \ + const unsigned int skipzb = z0*W*H*sizeof(Tss); \ + if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z = 1 + nz1 - z0; z; --z) { \ + const unsigned int skipyb = y0*W*sizeof(Tss); \ + if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y = 1 + ny1 - y0; y; --y) { \ + const unsigned int skipxb = x0*sizeof(Tss); \ + if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \ + raw.assign(ptrs, raw._width); \ + ptrs+=img._width; \ + if (endian) cimg::invert_endianness(raw._data,raw._width); \ + cimg::fwrite(raw._data,raw._width,nfile); \ + const unsigned int skipxe = (W - 1 - nx1)*sizeof(Tss); \ + if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \ + if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + saved = true; \ + } + + if (!file && !filename) + throw CImgArgumentException(_cimglist_instance + "save_cimg(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) + throw CImgInstanceException(_cimglist_instance + "save_cimg(): Empty instance, for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + + std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+"); + bool saved = false, endian = cimg::endianness(); + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N, W, H, D, C; + int i, err; + j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype._data,str_endian._data); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): CImg header not found in file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + const unsigned int lmax = std::min(N,n0 + _width); + _cimg_save_cimg_case("bool",bool); + _cimg_save_cimg_case("unsigned_char",unsigned char); + _cimg_save_cimg_case("uchar",unsigned char); + _cimg_save_cimg_case("char",char); + _cimg_save_cimg_case("unsigned_short",unsigned short); + _cimg_save_cimg_case("ushort",unsigned short); + _cimg_save_cimg_case("short",short); + _cimg_save_cimg_case("unsigned_int",unsigned int); + _cimg_save_cimg_case("uint",unsigned int); + _cimg_save_cimg_case("int",int); + _cimg_save_cimg_case("unsigned_int64",uint64T); + _cimg_save_cimg_case("uint64",uint64T); + _cimg_save_cimg_case("int64",int64T); + _cimg_save_cimg_case("float",float); + _cimg_save_cimg_case("double",double); + if (!saved) { + if (!file) cimg::fclose(nfile); + throw CImgIOException(_cimglist_instance + "save_cimg(): Unsupported data type '%s' for file '%s'.", + cimglist_instance, + filename?filename:"(FILE*)",str_pixeltype._data); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param filename Filename to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(0,filename,n0,x0,y0,z0,c0); + } + + //! Insert the image instance into into an existing .cimg file, at specified coordinates. + /** + \param file File to write data to. + \param n0 Starting index of images to write. + \param x0 Starting X-coordinates of image regions to write. + \param y0 Starting Y-coordinates of image regions to write. + \param z0 Starting Z-coordinates of image regions to write. + \param c0 Starting C-coordinates of image regions to write. + **/ + const CImgList& save_cimg(std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { + return _save_cimg(file,0,n0,x0,y0,z0,c0); + } + + static void _save_empty_cimg(std::FILE *const file, const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) { + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const ulongT siz = (ulongT)dx*dy*dz*dc*sizeof(T); + std::fprintf(nfile,"%u %s\n",nb,pixel_type()); + for (unsigned int i=nb; i; --i) { + std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dc); + for (ulongT off = siz; off; --off) std::fputc(0,nfile); + } + if (!file) cimg::fclose(nfile); + } + + //! Save empty (non-compressed) .cimg file with specified dimensions. + /** + \param filename Filename to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc); + } + + //! Save empty .cimg file with specified dimensions. + /** + \param file File to write data to. + \param nb Number of images to write. + \param dx Width of images in the written file. + \param dy Height of images in the written file. + \param dz Depth of images in the written file. + \param dc Spectrum of images in the written file. + **/ + static void save_empty_cimg(std::FILE *const file, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dc=1) { + return _save_empty_cimg(file,0,nb,dx,dy,dz,dc); + } + + //! Save list as a TIFF file. + /** + \param filename Filename to write data to. + \param compression_type Compression mode used to write data. + \param voxel_size Voxel size, to be stored in the filename. + \param description Description, to be stored in the filename. + \param use_bigtiff Allow to save big tiff files (>4Gb). + **/ + const CImgList& save_tiff(const char *const filename, const unsigned int compression_type=0, + const float *const voxel_size=0, const char *const description=0, + const bool use_bigtiff=true) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_tiff(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + +#ifndef cimg_use_tiff + if (_width==1) _data[0].save_tiff(filename,compression_type,voxel_size,description,use_bigtiff); + else cimglist_for(*this,l) { + CImg nfilename(1024); + cimg::number_filename(filename,l,6,nfilename); + _data[l].save_tiff(nfilename,compression_type,voxel_size,description,use_bigtiff); + } +#else + ulongT siz = 0; + cimglist_for(*this,l) siz+=_data[l].size(); + const bool _use_bigtiff = use_bigtiff && sizeof(siz)>=8 && siz*sizeof(T)>=1UL<<31; // No bigtiff for small images + TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); + if (tif) { + for (unsigned int dir = 0, l = 0; l<_width; ++l) { + const CImg& img = (*this)[l]; + cimg_forZ(img,z) img._save_tiff(tif,dir++,z,compression_type,voxel_size,description); + } + TIFFClose(tif); + } else + throw CImgIOException(_cimglist_instance + "save_tiff(): Failed to open stream for file '%s'.", + cimglist_instance, + filename); +#endif + return *this; + } + + //! Save list as a gzipped file, using external tool 'gzip'. + /** + \param filename Filename to write data to. + **/ + const CImgList& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Specified filename is (null).", + cimglist_instance); + CImg command(1024), filename_tmp(256), body(256); + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } else { + if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); + else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + } + if ((file=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::gzip_path(), + CImg::string(filename_tmp)._system_strescape().data(), + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", + cimglist_instance, + filename); + else cimg::fclose(file); + std::remove(filename_tmp); + } else { + CImg nfilename(1024); + cimglist_for(*this,l) { + cimg::number_filename(body,l,6,nfilename); + if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext); + _data[l].save_gzip_external(nfilename); + } + } + return *this; + } + + //! Save image sequence, using the OpenCV library. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). + \param keep_open Tells if the video writer associated to the specified filename + must be kept open or not (to allow frames to be added in the same file afterwards). + **/ + const CImgList& save_video(const char *const filename, const unsigned int fps=25, + const char *codec=0, const bool keep_open=false) const { +#ifndef cimg_use_opencv + cimg::unused(codec,keep_open); + return save_ffmpeg_external(filename,fps); +#else + static CvVideoWriter *writers[32] = { 0 }; + static CImgList 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; + 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; + 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++); + } + } + cvWriteFrame(writers[index],ipl); + } + cvReleaseImage(&ipl); + cimg::mutex(9,0); + } + + cimg::mutex(9); + if (!keep_open) { + cvReleaseVideoWriter(&writers[index]); + writers[index] = 0; + filenames[index].assign(); + sizes(index,0) = sizes(index,1) = 0; + last_used_index = -1; + } else last_used_index = index; + cimg::mutex(9,0); + + return *this; +#endif + } + + //! Save image sequence, using the external tool 'ffmpeg'. + /** + \param filename Filename to write data to. + \param fps Number of frames per second. + \param codec Type of compression. + \param bitrate Output bitrate + **/ + const CImgList& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, + const char *const codec=0, const unsigned int bitrate=2048) const { + if (!filename) + throw CImgArgumentException(_cimglist_instance + "save_ffmpeg_external(): Specified filename is (null).", + cimglist_instance); + if (is_empty()) { cimg::fempty(0,filename); return *this; } + + const char + *const ext = cimg::split_filename(filename), + *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video"; + + CImg command(1024), filename_tmp(256), filename_tmp2(256); + CImgList filenames; + std::FILE *file = 0; + cimglist_for(*this,l) if (!_data[l].is_sameXYZ(_data[0])) + throw CImgInstanceException(_cimglist_instance + "save_ffmpeg_external(): Invalid instance dimensions for file '%s'.", + cimglist_instance, + filename); + do { + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", + cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data); + if ((file=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.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_snprintf(command,command._width,"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\"", + cimg::ffmpeg_path(), + CImg::string(filename_tmp)._system_strescape().data(), + _codec,bitrate,fps, + CImg::string(filename)._system_strescape().data()); + cimg::system(command); + file = cimg::std_fopen(filename,"rb"); + if (!file) + throw CImgIOException(_cimglist_instance + "save_ffmpeg_external(): Failed to save file '%s' with external command 'ffmpeg'.", + cimglist_instance, + filename); + else cimg::fclose(file); + cimglist_for(*this,l) std::remove(filenames[l]); + return *this; + } + + //! Serialize a CImgList instance into a raw CImg buffer. + /** + \param is_compressed tells if zlib compression must be used for serialization + (this requires 'cimg_use_zlib' been enabled). + **/ + CImg get_serialize(const bool is_compressed=false) const { +#ifndef cimg_use_zlib + if (is_compressed) + cimg::warn(_cimglist_instance + "get_serialize(): Unable to compress data unless zlib is enabled, " + "storing them uncompressed.", + cimglist_instance); +#endif + CImgList 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::string(tmpstr,false).move_to(stream); + cimglist_for(*this,l) { + const CImg& img = _data[l]; + cimg_snprintf(tmpstr,tmpstr._width,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); + CImg::string(tmpstr,false).move_to(stream); + if (img._data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool failed_to_compress = true; + if (is_compressed) { +#ifdef cimg_use_zlib + const ulongT siz = sizeof(T)*ref.size(); + uLongf csiz = (ulongT)compressBound(siz); + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) + cimg::warn(_cimglist_instance + "get_serialize(): Failed to save compressed data, saving them uncompressed.", + cimglist_instance); + else { + cimg_snprintf(tmpstr,tmpstr._width," #%lu\n",csiz); + CImg::string(tmpstr,false).move_to(stream); + CImg(cbuf,csiz).move_to(stream); + delete[] cbuf; + failed_to_compress = false; + } +#endif + } + 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); + } + } else CImg::string("\n",false).move_to(stream); + } + cimglist_apply(stream,unroll)('y'); + return stream>'y'; + } + + //! Unserialize a CImg serialized buffer into a CImgList list. + template + static CImgList get_unserialize(const CImg& buffer) { +#ifdef cimg_use_zlib +#define _cimgz_unserialize_case(Tss) { \ + Bytef *cbuf = 0; \ + if (sizeof(t)!=1 || cimg::type::string()==cimg::type::string()) { \ + cbuf = new Bytef[csiz]; Bytef *_cbuf = cbuf; \ + for (ulongT i = 0; i::get_unserialize(): Unable to unserialize compressed data " \ + "unless zlib is enabled.", \ + pixel_type()); +#endif + +#define _cimg_unserialize_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l::unserialize(): Invalid specified size (%u,%u,%u,%u) for " \ + "image #%u in serialized buffer.", \ + pixel_type(),W,H,D,C,l); \ + if (W*H*D*C>0) { \ + CImg 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 { \ + 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 (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ + } \ + } \ + loaded = true; \ + } + + if (buffer.is_empty()) + 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(); + bool loaded = false, endian = cimg::endianness(), is_bytef = false; + CImg tmp(256), str_pixeltype(256), str_endian(256); + *tmp = *str_pixeltype = *str_endian = 0; + unsigned int j, N = 0, W, H, D, C; + uint64T csiz; + int i, err; + cimg::unused(is_bytef); + do { + j = 0; while ((i=(int)*stream)!='\n' && stream::get_unserialize(): CImg header not found in serialized buffer.", + pixel_type()); + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + res.assign(N); + _cimg_unserialize_case("bool",bool); + _cimg_unserialize_case("unsigned_char",unsigned char); + _cimg_unserialize_case("uchar",unsigned char); + _cimg_unserialize_case("char",char); + _cimg_unserialize_case("unsigned_short",unsigned short); + _cimg_unserialize_case("ushort",unsigned short); + _cimg_unserialize_case("short",short); + _cimg_unserialize_case("unsigned_int",unsigned int); + _cimg_unserialize_case("uint",unsigned int); + _cimg_unserialize_case("int",int); + _cimg_unserialize_case("unsigned_int64",uint64T); + _cimg_unserialize_case("uint64",uint64T); + _cimg_unserialize_case("int64",int64T); + _cimg_unserialize_case("float",float); + _cimg_unserialize_case("double",double); + if (!loaded) + throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unsupported pixel type '%s' defined " + "in serialized buffer.", + pixel_type(),str_pixeltype._data); + return res; + } + + //@} + //---------------------------------- + // + //! \name Others + //@{ + //---------------------------------- + + //! Return a CImg pre-defined font with requested height. + /** + \param font_height Height of the desired font (exact match for 13,23,53,103). + \param is_variable_width Decide if the font has a variable (\c true) or fixed (\c false) width. + **/ + static const CImgList& 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. + CImg &basef = font_base[ind]; + if (!basef) { + basef.assign(256*font_width[ind],font_height[ind]); + + 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). + 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 }; + ind = ~0U; + for (int i = 0; i<16; ++i) + 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 + fonts->assign(); + std::memmove(fonts,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 + } + CImgList &font = fonts[ind]; + + // Render requested font. + if (!font) { + const unsigned int padding_x = requested_height<=64?1U:requested_height<=128?2U:3U; + is_variable_widths[ind] = is_variable_width; + font = basef.get_split('x',256); + 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, + font[0]._height>requested_height?2: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_forXY(letter,x,y) if (letter(x,y)) { if (xxmax) xmax = x; } + if (xmin<=xmax) letter.crop(xmin,0,xmax,letter._height - 1); + } + font[' '].resize(font['f']._width,-100,-100,-100,0); + if (' ' + 256& FFT(const char axis, const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],axis,invert); + return *this; + } + + //! Compute a 1-D Fast Fourier Transform, along specified axis \newinstance. + CImgList get_FFT(const char axis, const bool invert=false) const { + return CImgList(*this,false).FFT(axis,invert); + } + + //! Compute a n-d Fast Fourier Transform. + /** + \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed. + **/ + CImgList& FFT(const bool invert=false) { + if (is_empty()) return *this; + if (_width==1) insert(1); + if (_width>2) + cimg::warn(_cimglist_instance + "FFT(): Instance has more than 2 images", + cimglist_instance); + + CImg::FFT(_data[0],_data[1],invert); + return *this; + } + + //! Compute a 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. + /** + **/ + CImgList& reverse_object3d() { + cimglist_for(*this,l) { + CImg& p = _data[l]; + switch (p.size()) { + case 2 : case 3: cimg::swap(p[0],p[1]); break; + case 6 : cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break; + case 9 : cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break; + case 4 : cimg::swap(p[0],p[1],p[2],p[3]); break; + case 12 : cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break; + } + } + return *this; + } + + //! Reverse primitives orientations of a 3D object \newinstance. + CImgList get_reverse_object3d() const { + return (+*this).reverse_object3d(); + } + + //@} + }; // struct CImgList { ... + + /* + #--------------------------------------------- + # + # Completion of previously declared functions + # + #---------------------------------------------- + */ + +namespace cimg { + + // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. + // (throw a CImgIOException when macro 'cimg_use_r' is defined). + inline FILE* _stdin(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdin; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdin(): Reference to 'stdin' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stdout(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stdout; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stdout(): Reference to 'stdout' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + inline FILE* _stderr(const bool throw_exception) { +#ifndef cimg_use_r + cimg::unused(throw_exception); + return stderr; +#else + if (throw_exception) { + cimg::exception_mode(0); + throw CImgIOException("cimg::stderr(): Reference to 'stderr' stream not allowed in R mode " + "('cimg_use_r' is defined)."); + } + return 0; +#endif + } + + // Open a file (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 + // Try alternative method, with wide-character string. + int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0); + if (err) { + CImg wpath(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(err); + if (MultiByteToWideChar(CP_UTF8,0,mode,-1,wmode,err)) + return _wfopen(wpath,wmode); + } + } + } +#endif + return 0; + } + + //! 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 + _cimg_test_temporary_path("/tmp"); + _cimg_test_temporary_path("/var/tmp"); +#endif + if (!path_found) { + *s_path = 0; + std::strncpy(tmp,filename_tmp,tmp._width - 1); + if ((file=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"); + } + } + 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). +#if !defined(__INTEL_COMPILER) + if (!SHGetSpecialFolderPathA(0,s_path,0x0026,false)) { + const char *const pfPath = std::getenv("PROGRAMFILES"); + if (pfPath) std::strncpy(s_path,pfPath,MAX_PATH - 1); + else std::strcpy(s_path,"C:\\PROGRA~1"); + } +#else + std::strcpy(s_path,"C:\\PROGRA~1"); +#endif + } + cimg::mutex(7,0); + return s_path; + } +#endif + + //! Get/set path to the ImageMagick's \c convert binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c convert binary. + **/ + inline const char* imagemagick_path(const char *const user_path, const bool reinit_path) { + static CImg 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=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 + 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 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 + const char *const pf_path = 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\\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\\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\\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,"%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,"%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,"%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:\\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:\\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:\\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,"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,"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,"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:\\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:\\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:\\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:\\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 + 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); + } + 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; +#if cimg_OS==2 + const char *const pf_path = 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,"./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 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=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 \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=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,"./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 \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=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,"./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); + } + 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=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 \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=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,"./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); + } + cimg::mutex(7,0); + return s_path; + } + + //! Get/set path to the \c curl binary. + /** + \param user_path Specified path, or \c 0 to get the path currently used. + \param reinit_path Force path to be recalculated (may take some time). + \return Path containing the \c curl binary. + **/ + inline const char *curl_path(const char *const user_path, const bool reinit_path) { + static CImg s_path; + cimg::mutex(7); + if (reinit_path) s_path.assign(); + if (user_path) { + if (!s_path) s_path.assign(1024); + std::strncpy(s_path,user_path,1023); + } else if (!s_path) { + s_path.assign(1024); + bool path_found = false; + std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + std::strcpy(s_path,".\\curl.exe"); + if ((file=cimg::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; } + } + if (!path_found) std::strcpy(s_path,"curl"); +#endif + winformat_string(s_path); + } + cimg::mutex(7,0); + return s_path; + } + + // [internal] Sorting function, used by cimg::files(). + inline int _sort_files(const void* a, const void* b) { + const CImg &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 +#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); + } + } + } 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); +#endif + + // Sort resulting list by lexicographic order. + if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg),_sort_files); + + return res; + } + + //! Try to guess format from an image file. + /** + \param file Input file (can be \c 0 if \c filename is set). + \param filename Filename, as a C-string (can be \c 0 if \c file is set). + \return C-string containing the guessed file format, or \c 0 if nothing has been guessed. + **/ + inline const char *ftype(std::FILE *const file, const char *const filename) { + if (!file && !filename) + throw CImgArgumentException("cimg::ftype(): Specified filename is (null)."); + static const char + *const _pnm = "pnm", + *const _pfm = "pfm", + *const _bmp = "bmp", + *const _gif = "gif", + *const _jpg = "jpg", + *const _off = "off", + *const _pan = "pan", + *const _png = "png", + *const _tif = "tif", + *const _inr = "inr", + *const _dcm = "dcm"; + const char *f_type = 0; + CImg 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; } + } + f_type = 0; break; + } + } + } 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)."); + + 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); +#endif + + CImg command((unsigned int)std::strlen(url) + 64); + cimg::unused(try_fallback); + + // Try with 'curl' first. + if (timeout) { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,timeout,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -m %u -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } else { + if (referer) + cimg_snprintf(command,command._width,"%s -e %s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),referer,filename_local, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),timeout,filename_local, + CImg::string(url)._system_strescape().data()); + } 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, + CImg::string(url)._system_strescape().data()); + else + cimg_snprintf(command,command._width,"%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),filename_local, + CImg::string(url)._system_strescape().data()); + } + cimg::system(command); + + if (!(file=cimg::std_fopen(filename_local,"rb"))) + throw CImgIOException("cimg::load_network(): Failed to load file '%s' with external commands " + "'wget' or 'curl'.",url); + cimg::fclose(file); + + // Try gunzip it. + cimg_snprintf(command,command._width,"%s.gz",filename_local); + std::rename(filename_local,command); + cimg_snprintf(command,command._width,"%s --quiet \"%s.gz\"", + gunzip_path(),filename_local); + cimg::system(command); + file = 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"); + } + } + 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.), + 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; + } + + // 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 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."); +#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; +#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); + } + + // End of cimg:: namespace +} + + // End of cimg_library:: namespace +} + +//! Short alias name. +namespace cil = cimg_library_suffixed; + +#ifdef _cimg_redefine_False +#define False 0 +#endif +#ifdef _cimg_redefine_True +#define True 1 +#endif +#ifdef _cimg_redefine_min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_PI +#define PI 3.141592653589793238462643383 +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif +// Local Variables: +// mode: c++ +// End: diff --git a/tutorial_4/CMakeLists.txt b/tutorial_4/CMakeLists.txt new file mode 100644 index 0000000..f1af58e --- /dev/null +++ b/tutorial_4/CMakeLists.txt @@ -0,0 +1,51 @@ +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# sets versions and names of project +cmake_minimum_required(VERSION 3.7) +project(opencl_tutorial_1) + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) +# specify our compiled binaries name +set(BINARY_NAME tut4) +# specify all our cpp files names +set(CPP_FILES tut4.cpp utils.h CImg.h) + +# build our project executable, and link opencl lib +find_package(OpenCL 2 REQUIRED) +# find x11 for linux desktop +find_package(X11) +# get thread library used in the tutorials +find_package(Threads REQUIRED) +# find boost for tut4 and up +find_package(Boost REQUIRED) + +add_executable(${BINARY_NAME} ${CPP_FILES}) +target_link_libraries(tut4 OpenCL::OpenCL ${X11_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + +# copy kernel files over to binary directory so it can access them +# configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kernels/my_kernels.cl ${CMAKE_CURRENT_BINARY_DIR}/kernels/my_kernels.cl COPYONLY) +# copy our images where they should be relative to the compiled binary from above +# configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/test.ppm ${CMAKE_CURRENT_BINARY_DIR}/test.ppm COPYONLY) diff --git a/tutorial_4/Tutorial 4.vcxproj b/tutorial_4/Tutorial 4.vcxproj deleted file mode 100644 index 7e2f433..0000000 --- a/tutorial_4/Tutorial 4.vcxproj +++ /dev/null @@ -1,169 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {E95D4B5A-1F3F-4A31-931F-7A99CE219124} - Win32Proj - BComputeExamples - 10.0 - Tutorial 4 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - C:\local\boost_1_60_0;C:\Program Files %28x86%29\Intel\OpenCL SDK\5.3\include;%(AdditionalIncludeDirectories) - - - Console - true - C:\local\boost_1_60_0\lib64-msvc-14.0;%(AdditionalLibraryDirectories) - - - - - - - Level3 - Disabled - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - $(BOOST_INCLUDEDIR);$(INTELOCLSDKROOT)include;..\include;%(AdditionalIncludeDirectories) - 4996 - - - Console - true - $(BOOST_LIBRARYDIR);$(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - $(BOOST_INCLUDEDIR);$(INTELOCLSDKROOT)include;..\include; - - - Console - true - true - true - $(BOOST_LIBRARYDIR);$(INTELOCLSDKROOT)lib\x64;%(AdditionalLibraryDirectories) - OpenCL.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tutorial_4/Tutorial 4.vcxproj.filters b/tutorial_4/Tutorial 4.vcxproj.filters deleted file mode 100644 index 707ff58..0000000 --- a/tutorial_4/Tutorial 4.vcxproj.filters +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - {ba2bbd9b-6f55-4a90-8bc6-289f2bf5ddd5} - - - - - include - - - \ No newline at end of file diff --git a/tutorial_4/build.sh b/tutorial_4/build.sh new file mode 100755 index 0000000..ffafe4a --- /dev/null +++ b/tutorial_4/build.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# @Author: GeorgeRaven +# @Date: 2020-02-24T17:54:52+00:00 +# @Last modified by: archer +# @Last modified time: 2020-02-24T23:49:06+00:00 +# @License: please see LICENSE file in project root + +# MIT License +# =========== +# +# Copyright (c) 2019 George Onoufriou (GeorgeRaven, archer, DreamingRaven) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +rm -r build/ +mkdir -p build +cd build/ +cmake .. +make +./tut4 +cd .. diff --git a/tutorial_4/Tutorial 4.cpp b/tutorial_4/tut4.cpp similarity index 80% rename from tutorial_4/Tutorial 4.cpp rename to tutorial_4/tut4.cpp index b17efcc..9015a01 100644 --- a/tutorial_4/Tutorial 4.cpp +++ b/tutorial_4/tut4.cpp @@ -1,4 +1,14 @@ -#include "Utils.h" +/** + * @Author: GeorgeRaven + * @Date: 2020-02-24T23:43:45+00:00 + * @Last modified by: archer + * @Last modified time: 2020-02-24T23:46:58+00:00 + * @License: please see LICENSE file in project root + */ + + + +#include "utils.h" #include #include #include diff --git a/tutorial_4/utils.h b/tutorial_4/utils.h new file mode 100644 index 0000000..1539094 --- /dev/null +++ b/tutorial_4/utils.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#define CL_HPP_MINIMUM_OPENCL_VERSION 120 +#define CL_HPP_TARGET_OPENCL_VERSION 120 +#define CL_HPP_ENABLE_EXCEPTIONS + +#include + +using namespace std; + +template +ostream& operator<< (ostream& out, const vector& v) { + if (!v.empty()) { + out << '['; + copy(v.begin(), v.end(), ostream_iterator(out, ", ")); + out << "\b\b]"; + } + return out; +} + +string GetPlatformName(int platform_id) { + vector platforms; + cl::Platform::get(&platforms); + return platforms[platform_id].getInfo(); +} + +string GetDeviceName(int platform_id, int device_id) { + vector platforms; + cl::Platform::get(&platforms); + vector devices; + platforms[platform_id].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + return devices[device_id].getInfo(); +} + +const char *getErrorString(cl_int error) { + switch (error){ + // run-time and JIT compiler errors + case 0: return "CL_SUCCESS"; + case -1: return "CL_DEVICE_NOT_FOUND"; + case -2: return "CL_DEVICE_NOT_AVAILABLE"; + case -3: return "CL_COMPILER_NOT_AVAILABLE"; + case -4: return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; + case -5: return "CL_OUT_OF_RESOURCES"; + case -6: return "CL_OUT_OF_HOST_MEMORY"; + case -7: return "CL_PROFILING_INFO_NOT_AVAILABLE"; + case -8: return "CL_MEM_COPY_OVERLAP"; + case -9: return "CL_IMAGE_FORMAT_MISMATCH"; + case -10: return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; + case -11: return "CL_BUILD_PROGRAM_FAILURE"; + case -12: return "CL_MAP_FAILURE"; + case -13: return "CL_MISALIGNED_SUB_BUFFER_OFFSET"; + case -14: return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST"; + case -15: return "CL_COMPILE_PROGRAM_FAILURE"; + case -16: return "CL_LINKER_NOT_AVAILABLE"; + case -17: return "CL_LINK_PROGRAM_FAILURE"; + case -18: return "CL_DEVICE_PARTITION_FAILED"; + case -19: return "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; + + // compile-time errors + case -30: return "CL_INVALID_VALUE"; + case -31: return "CL_INVALID_DEVICE_TYPE"; + case -32: return "CL_INVALID_PLATFORM"; + case -33: return "CL_INVALID_DEVICE"; + case -34: return "CL_INVALID_CONTEXT"; + case -35: return "CL_INVALID_QUEUE_PROPERTIES"; + case -36: return "CL_INVALID_COMMAND_QUEUE"; + case -37: return "CL_INVALID_HOST_PTR"; + case -38: return "CL_INVALID_MEM_OBJECT"; + case -39: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; + case -40: return "CL_INVALID_IMAGE_SIZE"; + case -41: return "CL_INVALID_SAMPLER"; + case -42: return "CL_INVALID_BINARY"; + case -43: return "CL_INVALID_BUILD_OPTIONS"; + case -44: return "CL_INVALID_PROGRAM"; + case -45: return "CL_INVALID_PROGRAM_EXECUTABLE"; + case -46: return "CL_INVALID_KERNEL_NAME"; + case -47: return "CL_INVALID_KERNEL_DEFINITION"; + case -48: return "CL_INVALID_KERNEL"; + case -49: return "CL_INVALID_ARG_INDEX"; + case -50: return "CL_INVALID_ARG_VALUE"; + case -51: return "CL_INVALID_ARG_SIZE"; + case -52: return "CL_INVALID_KERNEL_ARGS"; + case -53: return "CL_INVALID_WORK_DIMENSION"; + case -54: return "CL_INVALID_WORK_GROUP_SIZE"; + case -55: return "CL_INVALID_WORK_ITEM_SIZE"; + case -56: return "CL_INVALID_GLOBAL_OFFSET"; + case -57: return "CL_INVALID_EVENT_WAIT_LIST"; + case -58: return "CL_INVALID_EVENT"; + case -59: return "CL_INVALID_OPERATION"; + case -60: return "CL_INVALID_GL_OBJECT"; + case -61: return "CL_INVALID_BUFFER_SIZE"; + case -62: return "CL_INVALID_MIP_LEVEL"; + case -63: return "CL_INVALID_GLOBAL_WORK_SIZE"; + case -64: return "CL_INVALID_PROPERTY"; + case -65: return "CL_INVALID_IMAGE_DESCRIPTOR"; + case -66: return "CL_INVALID_COMPILER_OPTIONS"; + case -67: return "CL_INVALID_LINKER_OPTIONS"; + case -68: return "CL_INVALID_DEVICE_PARTITION_COUNT"; + + // extension errors + case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; + case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; + case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; + case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; + case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; + case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; + default: return "Unknown OpenCL error"; + } +} + +void CheckError(cl_int error) { + if (error != CL_SUCCESS) { + cerr << "OpenCL call failed with error " << getErrorString(error) << endl; + exit(1); + } +} + +void AddSources(cl::Program::Sources& sources, const string& file_name) { + //TODO: add file existence check + ifstream file(file_name); + string* source_code = new string(istreambuf_iterator(file), (istreambuf_iterator())); + sources.push_back((*source_code).c_str()); +} + +string ListPlatformsDevices() { + + stringstream sstream; + vector platforms; + + cl::Platform::get(&platforms); + + sstream << "Found " << platforms.size() << " platform(s):" << endl; + + for (unsigned int i = 0; i < platforms.size(); i++) + { + sstream << "\nPlatform " << i << ", " << platforms[i].getInfo() << ", version: " << platforms[i].getInfo(); + + sstream << ", vendor: " << platforms[i].getInfo() << endl; + // sstream << ", extensions: " << platforms[i].getInfo() << endl; + + vector devices; + + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + sstream << "\n Found " << devices.size() << " device(s):" << endl; + + for (unsigned int j = 0; j < devices.size(); j++) + { + sstream << "\n Device " << j << ", " << devices[j].getInfo() << ", version: " << devices[j].getInfo(); + + sstream << ", vendor: " << devices[j].getInfo(); + cl_device_type device_type = devices[j].getInfo(); + sstream << ", type: "; + if (device_type & CL_DEVICE_TYPE_DEFAULT) + sstream << "DEFAULT "; + if (device_type & CL_DEVICE_TYPE_CPU) + sstream << "CPU "; + if (device_type & CL_DEVICE_TYPE_GPU) + sstream << "GPU "; + if (device_type & CL_DEVICE_TYPE_ACCELERATOR) + sstream << "ACCELERATOR "; + sstream << ", compute units: " << devices[j].getInfo(); + sstream << ", clock freq [MHz]: " << devices[j].getInfo(); + sstream << ", max memory size [B]: " << devices[j].getInfo(); + sstream << ", max allocatable memory [B]: " << devices[j].getInfo(); + + sstream << endl; + } + } + sstream << "----------------------------------------------------------------" << endl; + + return sstream.str(); +} + +cl::Context GetContext(int platform_id, int device_id) { + vector platforms; + + cl::Platform::get(&platforms); + + for (unsigned int i = 0; i < platforms.size(); i++) + { + vector devices; + platforms[i].getDevices((cl_device_type)CL_DEVICE_TYPE_ALL, &devices); + + for (unsigned int j = 0; j < devices.size(); j++) + { + if ((i == platform_id) && (j == device_id)) + return cl::Context({ devices[j] }); + } + } + + return cl::Context(); +} + +enum ProfilingResolution { + PROF_NS = 1, + PROF_US = 1000, + PROF_MS = 1000000, + PROF_S = 1000000000 +}; + +string GetFullProfilingInfo(const cl::Event& evnt, ProfilingResolution resolution) { + stringstream sstream; + + sstream << "Queued " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Submitted " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Executed " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + sstream << ", Total " << (evnt.getProfilingInfo() - evnt.getProfilingInfo()) / resolution; + + switch (resolution) { + case PROF_NS: sstream << " [ns]"; break; + case PROF_US: sstream << " [us]"; break; + case PROF_MS: sstream << " [ms]"; break; + case PROF_S: sstream << " [s]"; break; + default: break; + } + + return sstream.str(); +} From dbe99c897bfd5fc6f9dd50e35d601a634c6bfb45 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:53:17 +0000 Subject: [PATCH 58/69] Added boost to dockerfile Now dockerfile will work with boost, for tutorial 4 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 551aa98..6ab3dc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ FROM nvidia/opencl RUN mkdir -p /opencl_tutorials && \ apt update -y && \ apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev \ - fish neovim && \ + fish neovim libboost-all-dev && \ git clone https://github.com/KhronosGroup/opencl-clhpp && \ cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ rm -r /opencl-clhpp && \ From a0c9ecd18dd81b941c4262428e83efb66d5c9953 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:55:47 +0000 Subject: [PATCH 59/69] Adding boost libraries They do not appear to be 100% necessary but I am adding them for the sake of completion. --- tutorial_4/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial_4/CMakeLists.txt b/tutorial_4/CMakeLists.txt index f1af58e..ac9cc30 100644 --- a/tutorial_4/CMakeLists.txt +++ b/tutorial_4/CMakeLists.txt @@ -43,7 +43,7 @@ find_package(Threads REQUIRED) find_package(Boost REQUIRED) add_executable(${BINARY_NAME} ${CPP_FILES}) -target_link_libraries(tut4 OpenCL::OpenCL ${X11_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(tut4 OpenCL::OpenCL ${X11_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) # copy kernel files over to binary directory so it can access them # configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kernels/my_kernels.cl ${CMAKE_CURRENT_BINARY_DIR}/kernels/my_kernels.cl COPYONLY) From 613d2033c49a14cb9538d6f5f99adca7abf3b685 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Mon, 24 Feb 2020 23:59:52 +0000 Subject: [PATCH 60/69] Corrected tutorial 3 naming --- tutorial_3/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial_3/README.md b/tutorial_3/README.md index fb944b4..1f85ee7 100644 --- a/tutorial_3/README.md +++ b/tutorial_3/README.md @@ -1,4 +1,4 @@ -# Tutorial 2 +# Tutorial 3 This directory "tutorial_3" is a completely self contained OpenCL example. It has CMake to build itself and Docker to automate all the dependencies and building for you. To build this project without docker simply run the build.sh file. From 5363042584399d9a164d1bc15cb3c64f8566ab96 Mon Sep 17 00:00:00 2001 From: DreamingRaven Date: Tue, 25 Feb 2020 00:02:42 +0000 Subject: [PATCH 61/69] Changed indent --- tutorial_4/tut4.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tutorial_4/tut4.cpp b/tutorial_4/tut4.cpp index 9015a01..da8bc27 100644 --- a/tutorial_4/tut4.cpp +++ b/tutorial_4/tut4.cpp @@ -2,7 +2,7 @@ * @Author: GeorgeRaven * @Date: 2020-02-24T23:43:45+00:00 * @Last modified by: archer - * @Last modified time: 2020-02-24T23:46:58+00:00 + * @Last modified time: 2020-02-25T00:01:55+00:00 * @License: please see LICENSE file in project root */ @@ -35,7 +35,8 @@ int main() { compute::copy(B.begin(), B.end(), devB.begin()); // perform C = A + B - compute::transform(devA.begin(), devA.end(), devB.begin(), devC.begin(), compute::plus()); + compute::transform(devA.begin(), devA.end(), devB.begin(), devC.begin(), + compute::plus()); // copy data back to the host compute::copy(devC.begin(), devC.end(), C.begin()); From e21dbed0c04d972c0909c919e153f5e90a9bca21 Mon Sep 17 00:00:00 2001 From: GeorgeRaven Date: Mon, 15 Feb 2021 14:23:27 +0000 Subject: [PATCH 62/69] Added new CL files + renaming --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6ab3dc9..afe5267 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ RUN mkdir -p /opencl_tutorials && \ apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev \ fish neovim libboost-all-dev && \ git clone https://github.com/KhronosGroup/opencl-clhpp && \ - cp opencl-clhpp/include/CL/cl2.hpp /usr/include/CL/cl2.hpp && \ + cp opencl-clhpp/include/CL/* /usr/include/CL/ && \ rm -r /opencl-clhpp && \ echo "cd /opencl_tutorials\nexec fish" >> /root/.bashrc From 2821d970cab1a73e03f7ecbaa069b2ff51e5540f Mon Sep 17 00:00:00 2001 From: GeorgeRaven Date: Mon, 15 Feb 2021 14:36:49 +0000 Subject: [PATCH 63/69] Added libx11-dev for compilation It should be noted you cant pop-up view images from within docker as its terminal only. So it will error when running as there is no screen to pop-up to. But as long as you are aware of that it works as expected. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index afe5267..7c6a190 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ FROM nvidia/opencl RUN mkdir -p /opencl_tutorials && \ apt update -y && \ apt install -y cmake make gcc git build-essential ocl-icd-opencl-dev \ - fish neovim libboost-all-dev && \ + fish neovim libboost-all-dev libx11-dev && \ git clone https://github.com/KhronosGroup/opencl-clhpp && \ cp opencl-clhpp/include/CL/* /usr/include/CL/ && \ rm -r /opencl-clhpp && \ From da79642acd87c2ecc897bab95ded6246b8ec526d Mon Sep 17 00:00:00 2001 From: GeorgeRaven Date: Mon, 3 May 2021 17:52:15 +0100 Subject: [PATCH 64/69] Added opencl simulator for experimenting --- opencl-simulator.py | 110 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100755 opencl-simulator.py diff --git a/opencl-simulator.py b/opencl-simulator.py new file mode 100755 index 0000000..e081194 --- /dev/null +++ b/opencl-simulator.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 + +# @Author: GeorgeRaven +# @Date: 2021-05-03T16:04:49+01:00 +# @Last modified by: archer +# @Last modified time: 2021-05-03T17:51:26+01:00 +# @License: please see LICENSE file in project root + +import sys +import numpy as np # https://pypi.org/project/numpy/ +import configargparse # https://pypi.org/project/ConfigArgParse/ + + +def main(args): + # create data and generate predefined id arrays + vector = np.arange(-(2**(args["scale"]-1)), 2**(args["scale"]-1)) + output = np.zeros(vector.shape) + global_ids = np.arange(len(vector)) + group_ids = np.arange(2**args["group_scale"]) + # shuffle groups and vector + np.random.shuffle(vector) + np.random.shuffle(global_ids) + np.random.shuffle(group_ids) + # now using this information mimic opencl behaviour + opencl(_vector=vector, + _output=output, + _global_ids=global_ids, + _group_ids=group_ids) + + +def opencl(_vector, _output, _group_ids, _global_ids): + """Call OpenCL mimic.""" + print("Input Elements:", _vector.size) + print("Local/ Group Size:", _group_ids.size) + + def get_global_size(_i: int): + # if this errors you have called the opencl like tuple wrong try 0 + return (_vector.size,)[_i] + + def get_local_size(_i: int): + # if this errors you have called the opencl like tuple wrong try 0 + return (_group_ids.size,)[_i] + + _id = 0 + _gid = 0 + while(_id < _vector.size): + + def get_group_id(_i: int): + # if this errors you have called the opencl wrong + return (_gid,)[_i] + + _scratch = np.zeros(_group_ids.size) + + for _lid in _group_ids: + + def get_global_id(_i: int): + # if this errors you have called the opencl wrong + return (_global_ids[_id],)[_i] + + def get_local_id(_i: int): + # if this errors you have called the opencl wrong + return (_lid,)[_i] + + def kernel(A: np.ndarray, B: np.ndarray, scratch: np.ndarray): + """Simulate OpenCL Kernel.""" + id = get_global_id(0) + lid = get_local_id(0) + N = get_global_size(0) + L = get_local_size(0) + # YOUR CODE GOES FROM HERE + B[id] = A[id] + # TO HERE + + kernel(A=_vector, B=_output, scratch=_scratch) + + _id += 1 + _gid += 1 + + print("A:", _vector) + print("Stats: A.max={}, A.min={}, A.std={}".format( + _vector.max(), + _vector.min(), + np.std(_vector) + )) + print("B:", _output) + + +def arg_handler(argv=[], description=None): + """Quick argument handler just to make everything easier to work with.""" + description = description if description is not None else "api args" + parser = configargparse.ArgumentParser(description=description) + + parser.add_argument("-s", "--scale", + type=int, + default=7, + env_var="PY_KERNEL_SCALE", + help="Scale (S) in N=2^S for vector length.") + parser.add_argument("-g", "--group-scale", + type=int, + default=2, + env_var="PY_KERNEL_GROUP_SCALE", + help="Group scale (G) in L=2^G for local/ group size.") + + args = vars(parser.parse_args(argv)) + return args + + +if __name__ == "__main__": + main(arg_handler(sys.argv[1:], + description="Python Kernel Experimenter.")) From cb542e4e10987ad3ea4e4ff873f218d27fb42687 Mon Sep 17 00:00:00 2001 From: Serguei Mokhov Date: Sun, 22 Jun 2025 23:06:48 -0400 Subject: [PATCH 65/69] [multi_os] sync renames Tutorial X to tutorial_X to avoid spaces --- {Tutorial 1 => tutorial_1}/CMakeLists.txt | 0 {Tutorial 1 => tutorial_1}/Tutorial 1.cpp | 0 {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj | 0 {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj.filters | 0 {Tutorial 1 => tutorial_1}/kernels/my_kernels.cl | 0 {Tutorial 1 => tutorial_1}/src/Tutorial 1.cpp | 0 {Tutorial 1 => tutorial_1}/src/kernels/my_kernels.cl | 0 {Tutorial 2 => tutorial_2}/CMakeLists.txt | 0 {Tutorial 2 => tutorial_2}/Tutorial 2.cpp | 0 {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj | 0 {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj.filters | 0 {Tutorial 2 => tutorial_2}/kernels/my_kernels.cl | 0 {Tutorial 2 => tutorial_2}/src/Tutorial 2.cpp | 0 {Tutorial 2 => tutorial_2}/src/kernels/my_kernels.cl | 0 {Tutorial 2 => tutorial_2}/test.ppm | 0 {Tutorial 2 => tutorial_2}/test_large.ppm | 0 {Tutorial 3 => tutorial_3}/CMakeLists.txt | 0 {Tutorial 3 => tutorial_3}/Tutorial 3.cpp | 0 {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj | 0 {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj.filters | 0 {Tutorial 3 => tutorial_3}/kernels/my_kernels.cl | 0 {Tutorial 3 => tutorial_3}/src/Tutorial 3.cpp | 0 {Tutorial 3 => tutorial_3}/src/kernels/my_kernels.cl | 0 {Tutorial 4 => tutorial_4}/CMakeLists.txt | 0 {Tutorial 4 => tutorial_4}/Tutorial 4.cpp | 0 {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj | 0 {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj.filters | 0 {Tutorial 4 => tutorial_4}/src/Tutorial 4.cpp | 0 28 files changed, 0 insertions(+), 0 deletions(-) rename {Tutorial 1 => tutorial_1}/CMakeLists.txt (100%) rename {Tutorial 1 => tutorial_1}/Tutorial 1.cpp (100%) rename {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj (100%) rename {Tutorial 1 => tutorial_1}/Tutorial 1.vcxproj.filters (100%) rename {Tutorial 1 => tutorial_1}/kernels/my_kernels.cl (100%) rename {Tutorial 1 => tutorial_1}/src/Tutorial 1.cpp (100%) rename {Tutorial 1 => tutorial_1}/src/kernels/my_kernels.cl (100%) rename {Tutorial 2 => tutorial_2}/CMakeLists.txt (100%) rename {Tutorial 2 => tutorial_2}/Tutorial 2.cpp (100%) rename {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj (100%) rename {Tutorial 2 => tutorial_2}/Tutorial 2.vcxproj.filters (100%) rename {Tutorial 2 => tutorial_2}/kernels/my_kernels.cl (100%) rename {Tutorial 2 => tutorial_2}/src/Tutorial 2.cpp (100%) rename {Tutorial 2 => tutorial_2}/src/kernels/my_kernels.cl (100%) rename {Tutorial 2 => tutorial_2}/test.ppm (100%) rename {Tutorial 2 => tutorial_2}/test_large.ppm (100%) rename {Tutorial 3 => tutorial_3}/CMakeLists.txt (100%) rename {Tutorial 3 => tutorial_3}/Tutorial 3.cpp (100%) rename {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj (100%) rename {Tutorial 3 => tutorial_3}/Tutorial 3.vcxproj.filters (100%) rename {Tutorial 3 => tutorial_3}/kernels/my_kernels.cl (100%) rename {Tutorial 3 => tutorial_3}/src/Tutorial 3.cpp (100%) rename {Tutorial 3 => tutorial_3}/src/kernels/my_kernels.cl (100%) rename {Tutorial 4 => tutorial_4}/CMakeLists.txt (100%) rename {Tutorial 4 => tutorial_4}/Tutorial 4.cpp (100%) rename {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj (100%) rename {Tutorial 4 => tutorial_4}/Tutorial 4.vcxproj.filters (100%) rename {Tutorial 4 => tutorial_4}/src/Tutorial 4.cpp (100%) diff --git a/Tutorial 1/CMakeLists.txt b/tutorial_1/CMakeLists.txt similarity index 100% rename from Tutorial 1/CMakeLists.txt rename to tutorial_1/CMakeLists.txt diff --git a/Tutorial 1/Tutorial 1.cpp b/tutorial_1/Tutorial 1.cpp similarity index 100% rename from Tutorial 1/Tutorial 1.cpp rename to tutorial_1/Tutorial 1.cpp diff --git a/Tutorial 1/Tutorial 1.vcxproj b/tutorial_1/Tutorial 1.vcxproj similarity index 100% rename from Tutorial 1/Tutorial 1.vcxproj rename to tutorial_1/Tutorial 1.vcxproj diff --git a/Tutorial 1/Tutorial 1.vcxproj.filters b/tutorial_1/Tutorial 1.vcxproj.filters similarity index 100% rename from Tutorial 1/Tutorial 1.vcxproj.filters rename to tutorial_1/Tutorial 1.vcxproj.filters diff --git a/Tutorial 1/kernels/my_kernels.cl b/tutorial_1/kernels/my_kernels.cl similarity index 100% rename from Tutorial 1/kernels/my_kernels.cl rename to tutorial_1/kernels/my_kernels.cl diff --git a/Tutorial 1/src/Tutorial 1.cpp b/tutorial_1/src/Tutorial 1.cpp similarity index 100% rename from Tutorial 1/src/Tutorial 1.cpp rename to tutorial_1/src/Tutorial 1.cpp diff --git a/Tutorial 1/src/kernels/my_kernels.cl b/tutorial_1/src/kernels/my_kernels.cl similarity index 100% rename from Tutorial 1/src/kernels/my_kernels.cl rename to tutorial_1/src/kernels/my_kernels.cl diff --git a/Tutorial 2/CMakeLists.txt b/tutorial_2/CMakeLists.txt similarity index 100% rename from Tutorial 2/CMakeLists.txt rename to tutorial_2/CMakeLists.txt diff --git a/Tutorial 2/Tutorial 2.cpp b/tutorial_2/Tutorial 2.cpp similarity index 100% rename from Tutorial 2/Tutorial 2.cpp rename to tutorial_2/Tutorial 2.cpp diff --git a/Tutorial 2/Tutorial 2.vcxproj b/tutorial_2/Tutorial 2.vcxproj similarity index 100% rename from Tutorial 2/Tutorial 2.vcxproj rename to tutorial_2/Tutorial 2.vcxproj diff --git a/Tutorial 2/Tutorial 2.vcxproj.filters b/tutorial_2/Tutorial 2.vcxproj.filters similarity index 100% rename from Tutorial 2/Tutorial 2.vcxproj.filters rename to tutorial_2/Tutorial 2.vcxproj.filters diff --git a/Tutorial 2/kernels/my_kernels.cl b/tutorial_2/kernels/my_kernels.cl similarity index 100% rename from Tutorial 2/kernels/my_kernels.cl rename to tutorial_2/kernels/my_kernels.cl diff --git a/Tutorial 2/src/Tutorial 2.cpp b/tutorial_2/src/Tutorial 2.cpp similarity index 100% rename from Tutorial 2/src/Tutorial 2.cpp rename to tutorial_2/src/Tutorial 2.cpp diff --git a/Tutorial 2/src/kernels/my_kernels.cl b/tutorial_2/src/kernels/my_kernels.cl similarity index 100% rename from Tutorial 2/src/kernels/my_kernels.cl rename to tutorial_2/src/kernels/my_kernels.cl diff --git a/Tutorial 2/test.ppm b/tutorial_2/test.ppm similarity index 100% rename from Tutorial 2/test.ppm rename to tutorial_2/test.ppm diff --git a/Tutorial 2/test_large.ppm b/tutorial_2/test_large.ppm similarity index 100% rename from Tutorial 2/test_large.ppm rename to tutorial_2/test_large.ppm diff --git a/Tutorial 3/CMakeLists.txt b/tutorial_3/CMakeLists.txt similarity index 100% rename from Tutorial 3/CMakeLists.txt rename to tutorial_3/CMakeLists.txt diff --git a/Tutorial 3/Tutorial 3.cpp b/tutorial_3/Tutorial 3.cpp similarity index 100% rename from Tutorial 3/Tutorial 3.cpp rename to tutorial_3/Tutorial 3.cpp diff --git a/Tutorial 3/Tutorial 3.vcxproj b/tutorial_3/Tutorial 3.vcxproj similarity index 100% rename from Tutorial 3/Tutorial 3.vcxproj rename to tutorial_3/Tutorial 3.vcxproj diff --git a/Tutorial 3/Tutorial 3.vcxproj.filters b/tutorial_3/Tutorial 3.vcxproj.filters similarity index 100% rename from Tutorial 3/Tutorial 3.vcxproj.filters rename to tutorial_3/Tutorial 3.vcxproj.filters diff --git a/Tutorial 3/kernels/my_kernels.cl b/tutorial_3/kernels/my_kernels.cl similarity index 100% rename from Tutorial 3/kernels/my_kernels.cl rename to tutorial_3/kernels/my_kernels.cl diff --git a/Tutorial 3/src/Tutorial 3.cpp b/tutorial_3/src/Tutorial 3.cpp similarity index 100% rename from Tutorial 3/src/Tutorial 3.cpp rename to tutorial_3/src/Tutorial 3.cpp diff --git a/Tutorial 3/src/kernels/my_kernels.cl b/tutorial_3/src/kernels/my_kernels.cl similarity index 100% rename from Tutorial 3/src/kernels/my_kernels.cl rename to tutorial_3/src/kernels/my_kernels.cl diff --git a/Tutorial 4/CMakeLists.txt b/tutorial_4/CMakeLists.txt similarity index 100% rename from Tutorial 4/CMakeLists.txt rename to tutorial_4/CMakeLists.txt diff --git a/Tutorial 4/Tutorial 4.cpp b/tutorial_4/Tutorial 4.cpp similarity index 100% rename from Tutorial 4/Tutorial 4.cpp rename to tutorial_4/Tutorial 4.cpp diff --git a/Tutorial 4/Tutorial 4.vcxproj b/tutorial_4/Tutorial 4.vcxproj similarity index 100% rename from Tutorial 4/Tutorial 4.vcxproj rename to tutorial_4/Tutorial 4.vcxproj diff --git a/Tutorial 4/Tutorial 4.vcxproj.filters b/tutorial_4/Tutorial 4.vcxproj.filters similarity index 100% rename from Tutorial 4/Tutorial 4.vcxproj.filters rename to tutorial_4/Tutorial 4.vcxproj.filters diff --git a/Tutorial 4/src/Tutorial 4.cpp b/tutorial_4/src/Tutorial 4.cpp similarity index 100% rename from Tutorial 4/src/Tutorial 4.cpp rename to tutorial_4/src/Tutorial 4.cpp From 9b6ac36d98863ef202acef591fefdfb3352f2685 Mon Sep 17 00:00:00 2001 From: Serguei Mokhov Date: Sun, 22 Jun 2025 23:24:24 -0400 Subject: [PATCH 66/69] [multi_os] remove duplicates after merge; saving diffs --- tutorial_1/src/Tutorial 1.cpp | 99 ------------- tutorial_1/src/kernels/my_kernels.cl | 24 ---- tutorial_2/src/Tutorial 2.cpp | 114 --------------- tutorial_2/src/kernels/my_kernels.cl | 75 ---------- tutorial_3/src/Tutorial 3.cpp | 117 --------------- tutorial_3/src/kernels/my_kernels.cl | 207 --------------------------- tutorial_4/src/Tutorial 4.cpp | 38 ----- 7 files changed, 674 deletions(-) delete mode 100644 tutorial_1/src/Tutorial 1.cpp delete mode 100644 tutorial_1/src/kernels/my_kernels.cl delete mode 100644 tutorial_2/src/Tutorial 2.cpp delete mode 100644 tutorial_2/src/kernels/my_kernels.cl delete mode 100644 tutorial_3/src/Tutorial 3.cpp delete mode 100644 tutorial_3/src/kernels/my_kernels.cl delete mode 100644 tutorial_4/src/Tutorial 4.cpp diff --git a/tutorial_1/src/Tutorial 1.cpp b/tutorial_1/src/Tutorial 1.cpp deleted file mode 100644 index 8f78753..0000000 --- a/tutorial_1/src/Tutorial 1.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include - -#include "Utils.h" - -void print_help() { - std::cerr << "Application usage:" << std::endl; - - std::cerr << " -p : select platform " << std::endl; - std::cerr << " -d : select device" << std::endl; - std::cerr << " -l : list all platforms and devices" << std::endl; - std::cerr << " -h : print this message" << std::endl; -} - -int main(int argc, char **argv) { - //Part 1 - handle command line options such as device selection, verbosity, etc. - int platform_id = 0; - int device_id = 0; - - for (int i = 1; i < argc; i++) { - if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } - else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } - else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } - else if (strcmp(argv[i], "-h") == 0) { print_help(); return 0; } - } - - //detect any potential exceptions - try { - //Part 2 - host operations - //2.1 Select computing devices - cl::Context context = GetContext(platform_id, device_id); - - //display the selected device - std::cout << "Runinng on " << GetPlatformName(platform_id) << ", " << GetDeviceName(platform_id, device_id) << std::endl; - - //create a queue to which we will push commands for the device - cl::CommandQueue queue(context); - - //2.2 Load & build the device code - cl::Program::Sources sources; - - AddSources(sources, "kernels/my_kernels.cl"); - - cl::Program program(context, sources); - - //build and debug the kernel code - try { - program.build(); - } - catch (const cl::Error& err) { - std::cout << "Build Status: " << program.getBuildInfo(context.getInfo()[0]) << std::endl; - std::cout << "Build Options:\t" << program.getBuildInfo(context.getInfo()[0]) << std::endl; - std::cout << "Build Log:\t " << program.getBuildInfo(context.getInfo()[0]) << std::endl; - throw err; - } - - //Part 4 - memory allocation - //host - input - std::vector A = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //C++11 allows this type of initialisation - std::vector B = { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 }; - - size_t vector_elements = A.size();//number of elements - size_t vector_size = A.size()*sizeof(int);//size in bytes - - //host - output - std::vector C(vector_elements); - - //device - buffers - cl::Buffer buffer_A(context, CL_MEM_READ_WRITE, vector_size); - cl::Buffer buffer_B(context, CL_MEM_READ_WRITE, vector_size); - cl::Buffer buffer_C(context, CL_MEM_READ_WRITE, vector_size); - - //Part 5 - device operations - - //5.1 Copy arrays A and B to device memory - queue.enqueueWriteBuffer(buffer_A, CL_TRUE, 0, vector_size, &A[0]); - queue.enqueueWriteBuffer(buffer_B, CL_TRUE, 0, vector_size, &B[0]); - - //5.2 Setup and execute the kernel (i.e. device code) - cl::Kernel kernel_add = cl::Kernel(program, "add"); - kernel_add.setArg(0, buffer_A); - kernel_add.setArg(1, buffer_B); - kernel_add.setArg(2, buffer_C); - - queue.enqueueNDRangeKernel(kernel_add, cl::NullRange, cl::NDRange(vector_elements), cl::NullRange); - - //5.3 Copy the result from device to host - queue.enqueueReadBuffer(buffer_C, CL_TRUE, 0, vector_size, &C[0]); - - std::cout << "A = " << A << std::endl; - std::cout << "B = " << B << std::endl; - std::cout << "C = " << C << std::endl; - } - catch (cl::Error err) { - std::cerr << "ERROR: " << err.what() << ", " << getErrorString(err.err()) << std::endl; - } - - return 0; -} \ No newline at end of file diff --git a/tutorial_1/src/kernels/my_kernels.cl b/tutorial_1/src/kernels/my_kernels.cl deleted file mode 100644 index 02ba7e3..0000000 --- a/tutorial_1/src/kernels/my_kernels.cl +++ /dev/null @@ -1,24 +0,0 @@ -//a simple OpenCL kernel which adds two vectors A and B together into a third vector C -kernel void add(global const int* A, global const int* B, global int* C) { - int id = get_global_id(0); - C[id] = A[id] + B[id]; -} - -//a simple smoothing kernel averaging values in a local window (radius 1) -kernel void avg_filter(global const int* A, global int* B) { - int id = get_global_id(0); - B[id] = (A[id - 1] + A[id] + A[id + 1])/3; -} - -//a simple 2D kernel -kernel void add2D(global const int* A, global const int* B, global int* C) { - int x = get_global_id(0); - int y = get_global_id(1); - int width = get_global_size(0); - int height = get_global_size(1); - int id = x + y*width; - - printf("id = %d x = %d y = %d w = %d h = %d\n", id, x, y, width, height); - - C[id]= A[id]+ B[id]; -} diff --git a/tutorial_2/src/Tutorial 2.cpp b/tutorial_2/src/Tutorial 2.cpp deleted file mode 100644 index 313837d..0000000 --- a/tutorial_2/src/Tutorial 2.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include - -#include "Utils.h" -#include "CImg.h" - -using namespace cimg_library; - -void print_help() { - std::cerr << "Application usage:" << std::endl; - - std::cerr << " -p : select platform " << std::endl; - std::cerr << " -d : select device" << std::endl; - std::cerr << " -l : list all platforms and devices" << std::endl; - std::cerr << " -f : input image file (default: test.ppm)" << std::endl; - std::cerr << " -h : print this message" << std::endl; -} - -int main(int argc, char **argv) { - //Part 1 - handle command line options such as device selection, verbosity, etc. - int platform_id = 0; - int device_id = 0; - string image_filename = "test.ppm"; - - for (int i = 1; i < argc; i++) { - if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } - else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } - else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } - else if ((strcmp(argv[i], "-f") == 0) && (i < (argc - 1))) { image_filename = argv[++i]; } - else if (strcmp(argv[i], "-h") == 0) { print_help(); return 0; } - } - - cimg::exception_mode(0); - - //detect any potential exceptions - try { - CImg image_input(image_filename.c_str()); - CImgDisplay disp_input(image_input,"input"); - - //a 3x3 convolution mask implementing an averaging filter - std::vector convolution_mask = { 1.f / 9, 1.f / 9, 1.f / 9, - 1.f / 9, 1.f / 9, 1.f / 9, - 1.f / 9, 1.f / 9, 1.f / 9 }; - - //Part 3 - host operations - //3.1 Select computing devices - cl::Context context = GetContext(platform_id, device_id); - - //display the selected device - std::cout << "Runing on " << GetPlatformName(platform_id) << ", " << GetDeviceName(platform_id, device_id) << std::endl; - - //create a queue to which we will push commands for the device - cl::CommandQueue queue(context); - - //3.2 Load & build the device code - cl::Program::Sources sources; - - AddSources(sources, "kernels/my_kernels.cl"); - - cl::Program program(context, sources); - - //build and debug the kernel code - try { - program.build(); - } - catch (const cl::Error& err) { - std::cout << "Build Status: " << program.getBuildInfo(context.getInfo()[0]) << std::endl; - std::cout << "Build Options:\t" << program.getBuildInfo(context.getInfo()[0]) << std::endl; - std::cout << "Build Log:\t " << program.getBuildInfo(context.getInfo()[0]) << std::endl; - throw err; - } - - //Part 4 - device operations - - //device - buffers - cl::Buffer dev_image_input(context, CL_MEM_READ_ONLY, image_input.size()); - cl::Buffer dev_image_output(context, CL_MEM_READ_WRITE, image_input.size()); //should be the same as input image -// cl::Buffer dev_convolution_mask(context, CL_MEM_READ_ONLY, convolution_mask.size()*sizeof(float)); - - //4.1 Copy images to device memory - queue.enqueueWriteBuffer(dev_image_input, CL_TRUE, 0, image_input.size(), &image_input.data()[0]); -// queue.enqueueWriteBuffer(dev_convolution_mask, CL_TRUE, 0, convolution_mask.size()*sizeof(float), &convolution_mask[0]); - - //4.2 Setup and execute the kernel (i.e. device code) - cl::Kernel kernel = cl::Kernel(program, "identityND"); - kernel.setArg(0, dev_image_input); - kernel.setArg(1, dev_image_output); -// kernel.setArg(2, dev_convolution_mask); - - queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(image_input.width(), image_input.height(), image_input.spectrum()), cl::NullRange); - - vector output_buffer(image_input.size()); - //4.3 Copy the result from device to host - queue.enqueueReadBuffer(dev_image_output, CL_TRUE, 0, output_buffer.size(), &output_buffer.data()[0]); - - CImg output_image(output_buffer.data(), image_input.width(), image_input.height(), image_input.depth(), image_input.spectrum()); - CImgDisplay disp_output(output_image,"output"); - - while (!disp_input.is_closed() && !disp_output.is_closed() - && !disp_input.is_keyESC() && !disp_output.is_keyESC()) { - disp_input.wait(1); - disp_output.wait(1); - } - - } - catch (const cl::Error& err) { - std::cerr << "ERROR: " << err.what() << ", " << getErrorString(err.err()) << std::endl; - } - catch (CImgException& err) { - std::cerr << "ERROR: " << err.what() << std::endl; - } - - return 0; -} diff --git a/tutorial_2/src/kernels/my_kernels.cl b/tutorial_2/src/kernels/my_kernels.cl deleted file mode 100644 index c29f64b..0000000 --- a/tutorial_2/src/kernels/my_kernels.cl +++ /dev/null @@ -1,75 +0,0 @@ -//a simple OpenCL kernel which copies all pixels from A to B -kernel void identity(global const uchar* A, global uchar* B) { - int id = get_global_id(0); - B[id] = A[id]; -} - -kernel void filter_r(global const uchar* A, global uchar* B) { - int id = get_global_id(0); - int image_size = get_global_size(0)/3; //each image consists of 3 colour channels - int colour_channel = id / image_size; // 0 - red, 1 - green, 2 - blue - - B[id] = A[id]; -} - -//simple ND identity kernel -kernel void identityND(global const uchar* A, global uchar* B) { - int width = get_global_size(0); //image width in pixels - int height = get_global_size(1); //image height in pixels - int image_size = width*height; //image size in pixels - int channels = get_global_size(2); //number of colour channels: 3 for RGB - - int x = get_global_id(0); //current x coord. - int y = get_global_id(1); //current y coord. - int c = get_global_id(2); //current colour channel - - int id = x + y*width + c*image_size; //global id in 1D space - - B[id] = A[id]; -} - -//2D averaging filter -kernel void avg_filter2D(global const uchar* A, global uchar* B) { - int width = get_global_size(0); //image width in pixels - int height = get_global_size(1); //image height in pixels - int image_size = width*height; //image size in pixels - int channels = get_global_size(2); //number of colour channels: 3 for RGB - - int x = get_global_id(0); //current x coord. - int y = get_global_id(1); //current y coord. - int c = get_global_id(2); //current colour channel - - int id = x + y*width + c*image_size; //global id in 1D space - - ushort result = 0; - - for (int i = (x-1); i <= (x+1); i++) - for (int j = (y-1); j <= (y+1); j++) - result += A[i + j*width + c*image_size]; - - result /= 9; - - B[id] = (uchar)result; -} - -//2D 3x3 convolution kernel -kernel void convolution2D(global const uchar* A, global uchar* B, constant float* mask) { - int width = get_global_size(0); //image width in pixels - int height = get_global_size(1); //image height in pixels - int image_size = width*height; //image size in pixels - int channels = get_global_size(2); //number of colour channels: 3 for RGB - - int x = get_global_id(0); //current x coord. - int y = get_global_id(1); //current y coord. - int c = get_global_id(2); //current colour channel - - int id = x + y*width + c*image_size; //global id in 1D space - - ushort result = 0; - - for (int i = (x-1); i <= (x+1); i++) - for (int j = (y-1); j <= (y+1); j++) - result += A[i + j*width + c*image_size]*mask[i-(x-1) + j-(y-1)]; - - B[id] = (uchar)result; -} \ No newline at end of file diff --git a/tutorial_3/src/Tutorial 3.cpp b/tutorial_3/src/Tutorial 3.cpp deleted file mode 100644 index 9b9aede..0000000 --- a/tutorial_3/src/Tutorial 3.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include -#include - -#include "Utils.h" - -void print_help() { - std::cerr << "Application usage:" << std::endl; - - std::cerr << " -p : select platform " << std::endl; - std::cerr << " -d : select device" << std::endl; - std::cerr << " -l : list all platforms and devices" << std::endl; - std::cerr << " -h : print this message" << std::endl; -} - -int main(int argc, char **argv) { - //Part 1 - handle command line options such as device selection, verbosity, etc. - int platform_id = 0; - int device_id = 0; - - for (int i = 1; i < argc; i++) { - if ((strcmp(argv[i], "-p") == 0) && (i < (argc - 1))) { platform_id = atoi(argv[++i]); } - else if ((strcmp(argv[i], "-d") == 0) && (i < (argc - 1))) { device_id = atoi(argv[++i]); } - else if (strcmp(argv[i], "-l") == 0) { std::cout << ListPlatformsDevices() << std::endl; } - else if (strcmp(argv[i], "-h") == 0) { print_help(); return 0;} - } - - //detect any potential exceptions - try { - //Part 2 - host operations - //2.1 Select computing devices - cl::Context context = GetContext(platform_id, device_id); - - //display the selected device - std::cout << "Runinng on " << GetPlatformName(platform_id) << ", " << GetDeviceName(platform_id, device_id) << std::endl; - - //create a queue to which we will push commands for the device - cl::CommandQueue queue(context); - - //2.2 Load & build the device code - cl::Program::Sources sources; - - AddSources(sources, "kernels/my_kernels.cl"); - - cl::Program program(context, sources); - - //build and debug the kernel code - try { - program.build(); - } - catch (const cl::Error& err) { - std::cout << "Build Status: " << program.getBuildInfo(context.getInfo()[0]) << std::endl; - std::cout << "Build Options:\t" << program.getBuildInfo(context.getInfo()[0]) << std::endl; - std::cout << "Build Log:\t " << program.getBuildInfo(context.getInfo()[0]) << std::endl; - throw err; - } - - typedef int mytype; - - //Part 3 - memory allocation - //host - input - std::vector A(10, 1);//allocate 10 elements with an initial value 1 - their sum is 10 so it should be easy to check the results! - - //the following part adjusts the length of the input vector so it can be run for a specific workgroup size - //if the total input length is divisible by the workgroup size - //this makes the code more efficient - size_t local_size = 10; - - size_t padding_size = A.size() % local_size; - - //if the input vector is not a multiple of the local_size - //insert additional neutral elements (0 for addition) so that the total will not be affected - if (padding_size) { - //create an extra vector with neutral values - std::vector A_ext(local_size-padding_size, 0); - //append that extra vector to our input - A.insert(A.end(), A_ext.begin(), A_ext.end()); - } - - size_t input_elements = A.size();//number of input elements - size_t input_size = A.size()*sizeof(mytype);//size in bytes - size_t nr_groups = input_elements / local_size; - - //host - output - std::vector B(input_elements); - size_t output_size = B.size()*sizeof(mytype);//size in bytes - - //device - buffers - cl::Buffer buffer_A(context, CL_MEM_READ_ONLY, input_size); - cl::Buffer buffer_B(context, CL_MEM_READ_WRITE, output_size); - - //Part 4 - device operations - - //4.1 copy array A to and initialise other arrays on device memory - queue.enqueueWriteBuffer(buffer_A, CL_TRUE, 0, input_size, &A[0]); - queue.enqueueFillBuffer(buffer_B, 0, 0, output_size);//zero B buffer on device memory - - //4.2 Setup and execute all kernels (i.e. device code) - cl::Kernel kernel_1 = cl::Kernel(program, "reduce_add_1"); - kernel_1.setArg(0, buffer_A); - kernel_1.setArg(1, buffer_B); -// kernel_1.setArg(2, cl::Local(local_size*sizeof(mytype)));//local memory size - - //call all kernels in a sequence - queue.enqueueNDRangeKernel(kernel_1, cl::NullRange, cl::NDRange(input_elements), cl::NDRange(local_size)); - - //4.3 Copy the result from device to host - queue.enqueueReadBuffer(buffer_B, CL_TRUE, 0, output_size, &B[0]); - - std::cout << "A = " << A << std::endl; - std::cout << "B = " << B << std::endl; - } - catch (cl::Error err) { - std::cerr << "ERROR: " << err.what() << ", " << getErrorString(err.err()) << std::endl; - } - - return 0; -} \ No newline at end of file diff --git a/tutorial_3/src/kernels/my_kernels.cl b/tutorial_3/src/kernels/my_kernels.cl deleted file mode 100644 index 06293b1..0000000 --- a/tutorial_3/src/kernels/my_kernels.cl +++ /dev/null @@ -1,207 +0,0 @@ -//fixed 4 step reduce -kernel void reduce_add_1(global const int* A, global int* B) { - int id = get_global_id(0); - int N = get_global_size(0); - - B[id] = A[id]; //copy input to output - - barrier(CLK_GLOBAL_MEM_FENCE); //wait for all threads to finish copying - - //perform reduce on the output array - //modulo operator is used to skip a set of values (e.g. 2 in the next line) - //we also check if the added element is within bounds (i.e. < N) - if (((id % 2) == 0) && ((id + 1) < N)) - B[id] += B[id + 1]; - - barrier(CLK_GLOBAL_MEM_FENCE); - - if (((id % 4) == 0) && ((id + 2) < N)) - B[id] += B[id + 2]; - - barrier(CLK_GLOBAL_MEM_FENCE); - - if (((id % 8) == 0) && ((id + 4) < N)) - B[id] += B[id + 4]; - - barrier(CLK_GLOBAL_MEM_FENCE); - - if (((id % 16) == 0) && ((id + 8) < N)) - B[id] += B[id + 8]; -} - -//flexible step reduce -kernel void reduce_add_2(global const int* A, global int* B) { - int id = get_global_id(0); - int N = get_global_size(0); - - B[id] = A[id]; - - barrier(CLK_GLOBAL_MEM_FENCE); - - for (int i = 1; i < N; i *= 2) { //i is a stride - if (!(id % (i * 2)) && ((id + i) < N)) - B[id] += B[id + i]; - - barrier(CLK_GLOBAL_MEM_FENCE); - } -} - -//reduce using local memory (so called privatisation) -kernel void reduce_add_3(global const int* A, global int* B, local int* scratch) { - int id = get_global_id(0); - int lid = get_local_id(0); - int N = get_local_size(0); - - //cache all N values from global memory to local memory - scratch[lid] = A[id]; - - barrier(CLK_LOCAL_MEM_FENCE);//wait for all local threads to finish copying from global to local memory - - for (int i = 1; i < N; i *= 2) { - if (!(lid % (i * 2)) && ((lid + i) < N)) - scratch[lid] += scratch[lid + i]; - - barrier(CLK_LOCAL_MEM_FENCE); - } - - //copy the cache to output array - B[id] = scratch[lid]; -} - -//reduce using local memory + accumulation of local sums into a single location -//works with any number of groups - not optimal! -kernel void reduce_add_4(global const int* A, global int* B, local int* scratch) { - int id = get_global_id(0); - int lid = get_local_id(0); - int N = get_local_size(0); - - //cache all N values from global memory to local memory - scratch[lid] = A[id]; - - barrier(CLK_LOCAL_MEM_FENCE);//wait for all local threads to finish copying from global to local memory - - for (int i = 1; i < N; i *= 2) { - if (!(lid % (i * 2)) && ((lid + i) < N)) - scratch[lid] += scratch[lid + i]; - - barrier(CLK_LOCAL_MEM_FENCE); - } - - //we add results from all local groups to the first element of the array - //serial operation! but works for any group size - //copy the cache to output array - if (!lid) { - atomic_add(&B[0],scratch[lid]); - } -} - -//a very simple histogram implementation -kernel void hist_simple(global const int* A, global int* H) { - int id = get_global_id(0); - - //assumes that H has been initialised to 0 - int bin_index = A[id];//take value as a bin index - - atomic_inc(&H[bin_index]);//serial operation, not very efficient! -} - -//Hillis-Steele basic inclusive scan -//requires additional buffer B to avoid data overwrite -kernel void scan_hs(global int* A, global int* B) { - int id = get_global_id(0); - int N = get_global_size(0); - global int* C; - - for (int stride = 1; stride < N; stride *= 2) { - B[id] = A[id]; - if (id >= stride) - B[id] += A[id - stride]; - - barrier(CLK_GLOBAL_MEM_FENCE); //sync the step - - C = A; A = B; B = C; //swap A & B between steps - } -} - -//a double-buffered version of the Hillis-Steele inclusive scan -//requires two additional input arguments which correspond to two local buffers -kernel void scan_add(__global const int* A, global int* B, local int* scratch_1, local int* scratch_2) { - int id = get_global_id(0); - int lid = get_local_id(0); - int N = get_local_size(0); - local int *scratch_3;//used for buffer swap - - //cache all N values from global memory to local memory - scratch_1[lid] = A[id]; - - barrier(CLK_LOCAL_MEM_FENCE);//wait for all local threads to finish copying from global to local memory - - for (int i = 1; i < N; i *= 2) { - if (lid >= i) - scratch_2[lid] = scratch_1[lid] + scratch_1[lid - i]; - else - scratch_2[lid] = scratch_1[lid]; - - barrier(CLK_LOCAL_MEM_FENCE); - - //buffer swap - scratch_3 = scratch_2; - scratch_2 = scratch_1; - scratch_1 = scratch_3; - } - - //copy the cache to output array - B[id] = scratch_1[lid]; -} - -//Blelloch basic exclusive scan -kernel void scan_bl(global int* A) { - int id = get_global_id(0); - int N = get_global_size(0); - int t; - - //up-sweep - for (int stride = 1; stride < N; stride *= 2) { - if (((id + 1) % (stride*2)) == 0) - A[id] += A[id - stride]; - - barrier(CLK_GLOBAL_MEM_FENCE); //sync the step - } - - //down-sweep - if (id == 0) - A[N-1] = 0;//exclusive scan - - barrier(CLK_GLOBAL_MEM_FENCE); //sync the step - - for (int stride = N/2; stride > 0; stride /= 2) { - if (((id + 1) % (stride*2)) == 0) { - t = A[id]; - A[id] += A[id - stride]; //reduce - A[id - stride] = t; //move - } - - barrier(CLK_GLOBAL_MEM_FENCE); //sync the step - } -} - -//calculates the block sums -kernel void block_sum(global const int* A, global int* B, int local_size) { - int id = get_global_id(0); - B[id] = A[(id+1)*local_size-1]; -} - -//simple exclusive serial scan based on atomic operations - sufficient for small number of elements -kernel void scan_add_atomic(global int* A, global int* B) { - int id = get_global_id(0); - int N = get_global_size(0); - for (int i = id+1; i < N; i++) - atomic_add(&B[i], A[id]); -} - -//adjust the values stored in partial scans by adding block sums to corresponding blocks -kernel void scan_add_adjust(global int* A, global const int* B) { - int id = get_global_id(0); - int gid = get_group_id(0); - A[id] += B[gid]; -} \ No newline at end of file diff --git a/tutorial_4/src/Tutorial 4.cpp b/tutorial_4/src/Tutorial 4.cpp deleted file mode 100644 index b17efcc..0000000 --- a/tutorial_4/src/Tutorial 4.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "Utils.h" -#include -#include -#include -#include - -namespace compute = boost::compute; -using namespace std; - -int main() { - typedef int mytype; - - // create vectors on the host - vector A = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - vector B = { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 }; - vector C(A.size()); - - // create vectors on the device - compute::vector devA(A.size()); - compute::vector devB(B.size()); - compute::vector devC(C.size()); - - // copy input data to the device - compute::copy(A.begin(), A.end(), devA.begin()); - compute::copy(B.begin(), B.end(), devB.begin()); - - // perform C = A + B - compute::transform(devA.begin(), devA.end(), devB.begin(), devC.begin(), compute::plus()); - - // copy data back to the host - compute::copy(devC.begin(), devC.end(), C.begin()); - - cout << "A = " << A << endl; - cout << "B = " << B << endl; - cout << "C = " << C << endl; - - return 0; -} From 52773ce1ae6a3e7cdecacf4f8593fa2db5aa2497 Mon Sep 17 00:00:00 2001 From: Serguei Mokhov Date: Sun, 22 Jun 2025 23:28:30 -0400 Subject: [PATCH 67/69] [multi_is] temporarily restore the original folder names. This will simplify the subsequent merge. --- {tutorial_1 => Tutorial 1}/CMakeLists.txt | 0 {tutorial_1 => Tutorial 1}/Tutorial 1.cpp | 0 {tutorial_1 => Tutorial 1}/Tutorial 1.vcxproj | 0 {tutorial_1 => Tutorial 1}/Tutorial 1.vcxproj.filters | 0 {tutorial_1 => Tutorial 1}/kernels/my_kernels.cl | 0 {tutorial_2 => Tutorial 2}/CMakeLists.txt | 0 {tutorial_2 => Tutorial 2}/Tutorial 2.cpp | 0 {tutorial_2 => Tutorial 2}/Tutorial 2.vcxproj | 0 {tutorial_2 => Tutorial 2}/Tutorial 2.vcxproj.filters | 0 {tutorial_2 => Tutorial 2}/kernels/my_kernels.cl | 0 {tutorial_2 => Tutorial 2}/test.ppm | 0 {tutorial_2 => Tutorial 2}/test_large.ppm | 0 {tutorial_3 => Tutorial 3}/CMakeLists.txt | 0 {tutorial_3 => Tutorial 3}/Tutorial 3.cpp | 0 {tutorial_3 => Tutorial 3}/Tutorial 3.vcxproj | 0 {tutorial_3 => Tutorial 3}/Tutorial 3.vcxproj.filters | 0 {tutorial_3 => Tutorial 3}/kernels/my_kernels.cl | 0 {tutorial_4 => Tutorial 4}/CMakeLists.txt | 0 {tutorial_4 => Tutorial 4}/Tutorial 4.cpp | 0 {tutorial_4 => Tutorial 4}/Tutorial 4.vcxproj | 0 {tutorial_4 => Tutorial 4}/Tutorial 4.vcxproj.filters | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename {tutorial_1 => Tutorial 1}/CMakeLists.txt (100%) rename {tutorial_1 => Tutorial 1}/Tutorial 1.cpp (100%) rename {tutorial_1 => Tutorial 1}/Tutorial 1.vcxproj (100%) rename {tutorial_1 => Tutorial 1}/Tutorial 1.vcxproj.filters (100%) rename {tutorial_1 => Tutorial 1}/kernels/my_kernels.cl (100%) rename {tutorial_2 => Tutorial 2}/CMakeLists.txt (100%) rename {tutorial_2 => Tutorial 2}/Tutorial 2.cpp (100%) rename {tutorial_2 => Tutorial 2}/Tutorial 2.vcxproj (100%) rename {tutorial_2 => Tutorial 2}/Tutorial 2.vcxproj.filters (100%) rename {tutorial_2 => Tutorial 2}/kernels/my_kernels.cl (100%) rename {tutorial_2 => Tutorial 2}/test.ppm (100%) rename {tutorial_2 => Tutorial 2}/test_large.ppm (100%) rename {tutorial_3 => Tutorial 3}/CMakeLists.txt (100%) rename {tutorial_3 => Tutorial 3}/Tutorial 3.cpp (100%) rename {tutorial_3 => Tutorial 3}/Tutorial 3.vcxproj (100%) rename {tutorial_3 => Tutorial 3}/Tutorial 3.vcxproj.filters (100%) rename {tutorial_3 => Tutorial 3}/kernels/my_kernels.cl (100%) rename {tutorial_4 => Tutorial 4}/CMakeLists.txt (100%) rename {tutorial_4 => Tutorial 4}/Tutorial 4.cpp (100%) rename {tutorial_4 => Tutorial 4}/Tutorial 4.vcxproj (100%) rename {tutorial_4 => Tutorial 4}/Tutorial 4.vcxproj.filters (100%) diff --git a/tutorial_1/CMakeLists.txt b/Tutorial 1/CMakeLists.txt similarity index 100% rename from tutorial_1/CMakeLists.txt rename to Tutorial 1/CMakeLists.txt diff --git a/tutorial_1/Tutorial 1.cpp b/Tutorial 1/Tutorial 1.cpp similarity index 100% rename from tutorial_1/Tutorial 1.cpp rename to Tutorial 1/Tutorial 1.cpp diff --git a/tutorial_1/Tutorial 1.vcxproj b/Tutorial 1/Tutorial 1.vcxproj similarity index 100% rename from tutorial_1/Tutorial 1.vcxproj rename to Tutorial 1/Tutorial 1.vcxproj diff --git a/tutorial_1/Tutorial 1.vcxproj.filters b/Tutorial 1/Tutorial 1.vcxproj.filters similarity index 100% rename from tutorial_1/Tutorial 1.vcxproj.filters rename to Tutorial 1/Tutorial 1.vcxproj.filters diff --git a/tutorial_1/kernels/my_kernels.cl b/Tutorial 1/kernels/my_kernels.cl similarity index 100% rename from tutorial_1/kernels/my_kernels.cl rename to Tutorial 1/kernels/my_kernels.cl diff --git a/tutorial_2/CMakeLists.txt b/Tutorial 2/CMakeLists.txt similarity index 100% rename from tutorial_2/CMakeLists.txt rename to Tutorial 2/CMakeLists.txt diff --git a/tutorial_2/Tutorial 2.cpp b/Tutorial 2/Tutorial 2.cpp similarity index 100% rename from tutorial_2/Tutorial 2.cpp rename to Tutorial 2/Tutorial 2.cpp diff --git a/tutorial_2/Tutorial 2.vcxproj b/Tutorial 2/Tutorial 2.vcxproj similarity index 100% rename from tutorial_2/Tutorial 2.vcxproj rename to Tutorial 2/Tutorial 2.vcxproj diff --git a/tutorial_2/Tutorial 2.vcxproj.filters b/Tutorial 2/Tutorial 2.vcxproj.filters similarity index 100% rename from tutorial_2/Tutorial 2.vcxproj.filters rename to Tutorial 2/Tutorial 2.vcxproj.filters diff --git a/tutorial_2/kernels/my_kernels.cl b/Tutorial 2/kernels/my_kernels.cl similarity index 100% rename from tutorial_2/kernels/my_kernels.cl rename to Tutorial 2/kernels/my_kernels.cl diff --git a/tutorial_2/test.ppm b/Tutorial 2/test.ppm similarity index 100% rename from tutorial_2/test.ppm rename to Tutorial 2/test.ppm diff --git a/tutorial_2/test_large.ppm b/Tutorial 2/test_large.ppm similarity index 100% rename from tutorial_2/test_large.ppm rename to Tutorial 2/test_large.ppm diff --git a/tutorial_3/CMakeLists.txt b/Tutorial 3/CMakeLists.txt similarity index 100% rename from tutorial_3/CMakeLists.txt rename to Tutorial 3/CMakeLists.txt diff --git a/tutorial_3/Tutorial 3.cpp b/Tutorial 3/Tutorial 3.cpp similarity index 100% rename from tutorial_3/Tutorial 3.cpp rename to Tutorial 3/Tutorial 3.cpp diff --git a/tutorial_3/Tutorial 3.vcxproj b/Tutorial 3/Tutorial 3.vcxproj similarity index 100% rename from tutorial_3/Tutorial 3.vcxproj rename to Tutorial 3/Tutorial 3.vcxproj diff --git a/tutorial_3/Tutorial 3.vcxproj.filters b/Tutorial 3/Tutorial 3.vcxproj.filters similarity index 100% rename from tutorial_3/Tutorial 3.vcxproj.filters rename to Tutorial 3/Tutorial 3.vcxproj.filters diff --git a/tutorial_3/kernels/my_kernels.cl b/Tutorial 3/kernels/my_kernels.cl similarity index 100% rename from tutorial_3/kernels/my_kernels.cl rename to Tutorial 3/kernels/my_kernels.cl diff --git a/tutorial_4/CMakeLists.txt b/Tutorial 4/CMakeLists.txt similarity index 100% rename from tutorial_4/CMakeLists.txt rename to Tutorial 4/CMakeLists.txt diff --git a/tutorial_4/Tutorial 4.cpp b/Tutorial 4/Tutorial 4.cpp similarity index 100% rename from tutorial_4/Tutorial 4.cpp rename to Tutorial 4/Tutorial 4.cpp diff --git a/tutorial_4/Tutorial 4.vcxproj b/Tutorial 4/Tutorial 4.vcxproj similarity index 100% rename from tutorial_4/Tutorial 4.vcxproj rename to Tutorial 4/Tutorial 4.vcxproj diff --git a/tutorial_4/Tutorial 4.vcxproj.filters b/Tutorial 4/Tutorial 4.vcxproj.filters similarity index 100% rename from tutorial_4/Tutorial 4.vcxproj.filters rename to Tutorial 4/Tutorial 4.vcxproj.filters From ef95d81c2f4ce83c5313343af3ba05b720126132 Mon Sep 17 00:00:00 2001 From: Serguei Mokhov Date: Mon, 23 Jun 2025 20:01:19 -0400 Subject: [PATCH 68/69] [build] fix outer cmake file --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3f9cad..12c4e29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.6) project("OpenCL Tutorials") -add_subdirectory("Tutorial 1") -add_subdirectory("Tutorial 2") -add_subdirectory("Tutorial 3") -add_subdirectory("Tutorial 4") +add_subdirectory("tutorial_1") +add_subdirectory("tutorial_2") +add_subdirectory("tutorial_3") +add_subdirectory("tutorial_4") From 2f333a1962c8584170fa08e9853d7fe865124e29 Mon Sep 17 00:00:00 2001 From: Serguei Mokhov Date: Mon, 23 Jun 2025 20:21:46 -0400 Subject: [PATCH 69/69] [build] allow optioncal cmake arguments in build.sh scripts. This allows for external automation to call scripts as benchmarks. --- tutorial_1/build.sh | 8 +++++++- tutorial_2/build.sh | 8 +++++++- tutorial_3/build.sh | 8 +++++++- tutorial_4/build.sh | 8 +++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tutorial_1/build.sh b/tutorial_1/build.sh index 48f5705..f4b2697 100755 --- a/tutorial_1/build.sh +++ b/tutorial_1/build.sh @@ -23,10 +23,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +CMAKE_OPTS="" + +if [[ $# -ge 1 ]]; then + CMAKE_OPTS="$1" +fi + rm -r build/ mkdir -p build cd build/ -cmake .. +cmake .. $CMAKE_OPTS make ./tut1 cd .. diff --git a/tutorial_2/build.sh b/tutorial_2/build.sh index 3713097..3e7f218 100755 --- a/tutorial_2/build.sh +++ b/tutorial_2/build.sh @@ -23,10 +23,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +CMAKE_OPTS="" + +if [[ $# -ge 1 ]]; then + CMAKE_OPTS="$1" +fi + rm -r build/ mkdir -p build cd build/ -cmake .. +cmake .. $CMAKE_OPTS make ./tut2 cd .. diff --git a/tutorial_3/build.sh b/tutorial_3/build.sh index 702c299..bcdb45f 100755 --- a/tutorial_3/build.sh +++ b/tutorial_3/build.sh @@ -29,10 +29,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +CMAKE_OPTS="" + +if [[ $# -ge 1 ]]; then + CMAKE_OPTS="$1" +fi + rm -r build/ mkdir -p build cd build/ -cmake .. +cmake .. $CMAKE_OPTS make ./tut3 cd .. diff --git a/tutorial_4/build.sh b/tutorial_4/build.sh index ffafe4a..1a42e58 100755 --- a/tutorial_4/build.sh +++ b/tutorial_4/build.sh @@ -29,10 +29,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +CMAKE_OPTS="" + +if [[ $# -ge 1 ]]; then + CMAKE_OPTS="$1" +fi + rm -r build/ mkdir -p build cd build/ -cmake .. +cmake .. $CMAKE_OPTS make ./tut4 cd ..

    .D5 4)&6,$8-2&"8,*@42F;+<1(8-3@1.;-DUF%7$$7#,@*%;%?U;>W96P,4L'MeAXmMYrNRkCF`4XsFJf8^yKOj=c~OEb/fIBa Tt&_{2[w5,FHe6B_/9T*(?';/C+01D$,@!' +_v[/H+;"4K7. $. *%=)&>)+H51I=%81(+B5+A4$;/)<7/A;*%.((;5$5.#52+'*'2,*?8%<1.F7%B-/K5C`F@^A?X:3M4!9$0M6EcH=V96P4)C, ',G64",@]FGdL ="*E* *.M09 'A'1),7*+1$-$(8,'#"6?X0VqDcNKh7Rn;Pm6]zHNm8Jk6YzI7S*-Fp#z/08c0+Xor*_z"MiWy*j?{INY@CRj5JZt3$@ &A3O'.L%A@UtK=.JC^/a}K +)9V%]{NB]33P'xkOiD+B %93H&,Cwc`zIF`0H_6J^6>V,0H=W(Fc2Ie6)CD_34PmItGOj& 79KOh&f=]1Tu#v>{B{=]F_ {GQl&Sj%m=Cj#j~s%q dx`rd8CR.TpMj HVp&)AI\#b':l:]cw/KayBAx9hw.T`dn0AR9OIb!5P ;V5M@ZWo%Per5MoB;O&< )A i@/G\sbv_r dw$^t$>R Pc Ka Pd&Ti*Xi+:LFZYh!>R Vi%F\J_i7`w'G^DbM .MY%6F9I;OAU$Ld1:'> 1D1I5J0D4F);4C'8 ++<22NZvF*)2&?A\'Xo@/ &'6,E!ZuKGd35R5S?^0N+H Gf&Yx7Jk'Gg#Gh"Jj#>^Bc#C\}2Bc?^C_ =Vb|.p9C^ ]y$=\;vNobs(t.Di9 3"8%9$:#6.A #7*@?U /C 5J 3Jht&Up |:E] OgF]Uq_3 0J+8 ;F_3-DNh26P0]|/HLpak,y6v/|4RLmZSlf~"lgPMedxq/r3g{%Dv"Qg& ?_*WOk'Vl.k4]j6s-b2#B3S'E C`Lkp9t:n4Vx\^7Yp@xOCb!Db'=[ Ml1;YAaTv([~,e;b>kPD\.7N"H]6,BVnG.H!;V)841O1Q!,L? Jk3Ij+ -9X 9S4-&$(($)?&Ea5c@>]?]|V3Ul(|sD@^-9(+@?X6|nMo;hWOo?Nj7kN%D =]#WwA6U)!;-F*#;.H"1L#7R$eP(FXu;Us6Ec(C`'Hc%Xt0oK7 63(D)EUr>`GaIj[ A!1PB0JG#5;#7E@Sc'\ZC^]5ON3JJXkn(<=1GGDZZ0FD7MJ7KJ3JG0IE:RN9NL0DD1DF(;@&9?*>@=QR-BF#8;CZY-GCD`W@^O>^M=[MIeZ9OK.B?+?:6IBI_SCYK9P==TBE\KLbNKdNQiPfa|w`y\FaARlO]z\XuWfj^vb_xga}mD]W1GI1IO7TW4QT:TTXY:STNfi9QX=U^6NU^z|KecRmhC\YAYUKd^ZsjZthVqdhobfFcJqwce}|igmiXvVpmf_dYWuMrkwwmnmqbffjptgjfk`|e]ycXs^fktveeghyt|vf^xoWzVWw\ips~fxo[{oZwlctUugYzkXwig|}PqiEebLljTus-KP)ER2M_7Tg,IYHfqQqtDcfIhjXvyWw~JluFiw;_pUyj.RdwrtgMt{Mt{CdmMjrHhjSusluBdl:I5C4?4A >F5UXMmlg\{z!?;Pqg`sQqdOpc[zkjybolut{stwvi}gvanydiXte[QpKD_?WrRh`]}Nr^T9!="^>IkNGhJAcBJlF[{Rf[pcVsAGd3lXzgrbn`gSmXmweufCb7UtIfYsgc[wrbvc`Ii{b‹Т֪yjpadVTpH[wNvxpEf?KkGRvSXzYTuXPqVMnTTt]<\ILl[CbTJi[=ZO5QCB_ROp_ny1P>On\]|j>[J@aQ@^OOj[ =+[@NjRa}f=YFD`Q=YK7SG;VNFdZOlaA]N8-,F@1LH\9B`:LkB_TkaYzQ@_[@SqS`^PoV(H72.!67$8;3HK(@A-AG*DF'AC.KK46;> 8=6<%?@-FH4NL-HG$?>'?<$=:0HF>VU!743ID>SL>TL?XP)B<#>90KJ'>>.AD'=>!:9*D@#?:)A>,FC$:: 29+3*=D3GK1EF+<>3GF-AA0FE:PO3JD:PFF]N@XFUoZYr]RlPQlM\wVXrOYsN]vSE_@TnRQgQ>SC;ND7HBUgFTgGLa@OgBe|YHc@MdFMaGQ`F^nSXiL[nRTkOOfKCVW:)A',E.&=+/F53G;1$!6+"2(/$&=1-C7 6)*=2/@45G:6F3.?-;L;4G4*=)2E./C*6L1Y?2*B/#:UD8#%=(6 &?,2(@1.J78&2 (' #;,(@1%=,/$%!1&).=(5B2%8&)"6C:)7Lc>Lg=+%:d~V'@6S$Hc3E`6>Y/Hc9Li:UwG[{N7R+7T-3L(Lc@D^5Ok;XsAOh:czNLb6Ok5b~C>\!_{GNj;Gb6Rn>t`QmFb2]wHRm=Ka6]pE[kEt[xtg\VK/-nv‰lqFRd,Ue/`s})PjI5zlI]Xq+t"bGdzJcpGd Tt2h>k;]B>Z[s5To+0K#>-I#!=5 /N+>\>5;@[+v_Ge0Ik9E`:Kf@;Y2+J He8B]1yi-E,D-G!cM=W*4ISj?*>Rn;E_,=Y):V&Kh3|\Vt-f7^x1!:AX?XNm)wNc3d-h0i1MvQ[]v&SVr.h~"TxOl𗒨F3:Tb%-7=>x\6lM=+LUoB}^tz.La +?W :P-I(D7/1, +=%);7N$Gc.On/4R.L'B Hg'Rq'lVnYv Bj(\xZsTg.b{'U@T}Ao5s}IOj.Jj2Rg?QZ^p0;SLd)e=A\xLPk8PDYk2z7J`7L 5L=Ti4Hs)EPbUd ^q49KFWXj/@RJ[%EWGW!K\"K_k9Pd$H\ay6^v/Zr#Xo`t^rb;K9H4F*8 3D5G!9!8 +#7 8G&= AT!,@ Gb7@]+He,Xv79W,I3Q;X 2O8WFe'DcQn$Yy-Kl*G A]"Zt3Yu*l:i5`O":Jd yFZwC`GrGDc#Nl1Hf( <Jgg)eW}XrD +#$$*) .B(*?/E6M.F:P8Q +Gq'Hgq.=YTlh~0Pr>o090J',BX-=Y)Rn=\xF;\)3U!Kl>cdw :304)XR=YT4RNFc`;UU?X[7OT2LP5NR4LP=PU*=C5HN*>C6LO.BF0CD/BD*A>-GDC]Y1HI0FH3GM(;A5HNDX])G]SAXKG_OMfT>VFJbQ]tbTlVNfKWpRf`vnHcDHcD]z[_|]_{c^vcZrcb{lXngVkhUojVslZxoZxpYvnb}u[ypKh`C`[Ec`HecNleLl`YwiTsiovm|Ssbesq|kq{mquu[Z_[^]VtQ~h\dYcXolrnmizynlzzpsimks^|ddlWr`Xtbb}kc~l`{fjqgjkgzgbwstoLoLNkPb|d`}dnucjhpVw\SsWTw]W{_FiJ[|eq]sAb^IigWwv@\c.GW0J^4Od&CRNluWvxBadB`cNkqXw[}Gjv-Q]W{url]hGotDjtCesMl{B_kEclTtwgWywffTsvSptRnnZztavbvezhyduhuY|d\|gYwhdqRp^9WAjkjkrk~p}h{exexdq[iT|iР4P&2O$wizpXkrbTnB6Q,uzkEc9db`eQn\Nm_GfXCbW=]RCbY\{nPr^DdMD_IGcMA]F7T>)F/GbP.J8YubQpWQoV{|vuml]|Ya\oh}tbaUtS@`?YxXZxVC`?3O(Oi>oXnTQk9Ol<^zJ_~KiRu_yfeR|xgUtEmvj_Sf\nam`g\zqncRqBfQ~h~e}csVy^mSoXq[zfoIf7@[4LhBEb;yslaX]}TmgBaB^|cJgRJgTLiYMi\;WM7RK4PI>XR9TN9ULJh\EdS]|kTs`r~*H81MB;VJ/J=*F4ms]`^]`^__abUvWRtT[~_aeX|^HkNAcDHkLPsTKkS4Q=>UG=UG6R?NjXYteWqdHcX'A6,E;7SE>^NBbO5TBSo]D`S3MG7PN5NL*DC"9;!46PifUqeJjX\yezUq[JfNYw]LkP_}aB`EIgNMkRKiOPmPWuTXvRdZdZ]{R^V]T`Vnbteud~l|mj_LjAaU{pvm>^:[xY4Q74N=4O=8RB5O?2N=1L8B]GSoSOmL\{^;XC2)68(=B5939,46<#=B$@C8:69'?D 7<#@>(DB2KJ'B?(A>";8 95+EA3KJ5JH&<::LK)=;-@B 38!7<.GK8<16.043+EB%A;&>;75 79!39!4;"5<-?F,=C"46';<(;?+>B1GF0ED+F;.I:AZJLdQHaKMgJYsTUpN[sU[sVWnSHdKVnYAYI5JA6HB4CC;KI5ED7KI1FA>RL7NF5KF2025/CF-FB,ED3KQ*>E%;?(?=0KB:TI(?:+",ZjUljbyZmgkmjyTl|Xk{Ww`hm|PpS}bmk{cy`|clybn~WpYx`exOt\sZk}St_l~Yx|peuY_lSUbHP_F`oYH[EFTBXgUXeQVdMgsZLYI]BBS;UhOQcKHW>PbGShHOgFPjI@W=2K43H4;S=4K65L:'>.(>21&'<0$4*"3))>3%9.)?35K?2F9!5)!3%,=,0@1+?,4G3@S?AT>9M4,B&JeGYrNMh9A\)>V!49Q!+@54K B[56O*Id:Ic.Zy3r@t->v1YwNd9q?VQk$&>/CtJxVn4  :'+ $8.#:, <*9U?DaL5)& %6/.C8,B5.' ""01,+ 52%:8(>7/))$.+/)!7+'=.(B->X@5S7:V;+G1/K49R;#>#)13!%3$)E54%&?/"9)0$5*)-(, " '=$";!51&#3(&#3'0@/# !+8.7F1rfF_7YuL`{RXoFIc=1J#-F1N>[)Ke<3J'9T0W2Lh9meINi6C\,ZsE_{EiJ^|B=, FP/pb[pEpGZkQj6}u}-C4gz-{:zC\y'f0zANs@d-Rut,0b:5KUyOd bz.Fna=RIq;Ec dDWw-j8n3jHkSk 4N + =&A#@$<SpI4)I#JhAB^0A_,Kj8<]/OjD+D$&C3S-6Q*2N"{ku2M%KgAUs>o+EoV8pUkM5P)GruWd=vIZu*vMf"pKmOq&yJLkZWJp5e~,l8o6^w#SlOeyXqD +Dj/L9Ag&>[Ti +xob .a}E1,1"7Vr:Fc(7V57 ,9iPp)+F (B">.E'B-L 8W zAi-l?#*" K]7CV#H`(Vm8B[#=zCUw=[m5QlUNi]u'Vl"[q#Wn$\x-Khb^{,~ N`#Ug(Qe!Ui!dz25MRk"Zv,\|/Xv*p?E]CZ?VQj3@V$%[w6c/s8Me 3Ej-BX>RPeLb|7<|+Cl6,>.? ?O ^o8Sf+FWEV 7KO_)2D SM`H]9L8O8N LdWn |ABW;N=N8I8K'7 1A#4)'<HY0/>!66J$9 -= +<'71A3QAa(<[Wu8,H[uF)@Qh@ZXv/tF^}18U d~>/I +Ha%1ILem6GSq;e+]~!I:.M-J6#> 2M4N+HY|j&t7m=!DY=:Q$Mf2Ne:-(< 6N'1K$=2L=Xt7CPm=Xs KdUkXtYw!W#D)E2 Of53Nb+.CYs3\y-r>r?<_TuStSRpw37@bt5BYy3b{DHGb}$|&(4pW_v>l?PdP>Pp/'E +Ii-Cb!B`k>C`<[OpMo@Fd I[~%Dgb<R..@_sP0 69T'\yFWt7Uu95W 5Z}A>a# 4\iH-F* 5-'69%&,<17J95G`>4L!^u=Lg _Ic|%1I1/$#2+1((-D*6N*9R'C]-4 /D .D 5N)&CcKy_Ec2YuH5P#E`4Pl<8T%;V+b}SIeE/BI+?H(;D%8@2EM2GL.BH3HJ1FH6PN7PP.EH'!4=3DNB@UX2LI5RJ1MD/MC9SJC\R>VL8ME[ogE\O:RDMeWRj\E^PJaRNdSJaMHaEKcFZtRpdkeYuV^{]UpUYs\Xo\Vn_t~_rmhzvH^YWpgNk^PnbRndQp`_~nYzjSugJk`Gi_Ij^GiZFeVCcWK4PYPmqIhiCbdDciLitRs}Mo{^g_X{}_U}|j[JpyAbq>]o@_p@^nIesLkrSuusfm^~zlgzeu^h^f\~e\~fs}eoakYvbRm]B^McmryvwY{]glkqnnpf|ltdcUpkTbHwZfv[fMkYVyBnXkR{aijycu_w`ot\zVUpY?]L[znMjb3PK6UO<[TNnb?_Q5SDE\P>VJ>UGOgWOgW3K;8P@`ziKhRHfN`~e[z[`_YxWJhF^{ZD_[>FaCa|V}{ykPFb/>Z*vas|ua=X+q^lWu|l[UPrNVtVOlNIfFHiDlcbWhU{izh]sJe{Ps\zchQnV|cs\|jugJf@A[8KgAnb}qynyfe@_G>[M@\N@]T;UPHd_Lec5PL0JH/IF>YT6RK0OEA`QNm\^}m/M=Pk`dtDaQMjUafQsVOrORvS[}\QsSVwZRtVJkORsYBdIWy_BbK@`I:[CWw`OkX,F64JA9PI]F)H/ns}lv%>4'?=%=@"<=/202QifIeYGdRUq^YubTpYDbJGeLQoXJhPB_GB^ILhSCaJ:X?[w\a~_a]YvSVtNUuN}uohRvJi`sgzueo]sao`yjpcdZ>];=YAFcP-G<-H;4ND/I>/I>0J;8S@AYC:X8B`DIeR6-)@C2FO.8-9+65<"B A'CA :7%?=(A>&?<.GD#=< 97)B?@XS+A.DB0CE%8>!6:&:>&8>->C!49"48-AB.DE6LI1KAUM)&110BD#=9-.'=C$;A02'>91I;4#,]kYw_eol}_krhut_hxVgvUqt^s[z]tjgx`zb{bmzderOs^pYr]oXs\j}Qr\xeiyZiw]O\FAPK\HjxdP\IWdM[fNVcHMY?L[@M_C_rUAX8SkKRlKXqQUnQMdJPdIPdFShGCY=;R:6L6+>'5H,YoNOe@Jd?KcAC];.G*=U?NaQCYIbvd3L6C[@DZ??U;P;M_GRdLR_Kdw\ShKB[:6O2BZ@:P<9M<=SB1I6-C4!8(1G7&:/-@40B66H>+=2)?3+@4$9-%9-0%4F8?PB/A2/@17J75G20C*5I-7O.;T7>X2Y/F%w^hgA^y8_y8f?YuNnEwNe7m6w3@={3t5`)m9]e1j7i6zE^'h*t.?ys_SYAV\r*~>ic.gp!9To/hKSu1l>e,EM.]4i2vq7p7}BWpl3w>QGAv0Wxv2Gnu8s6l$3S*Q`avUjHdF!6)PSmmꃆ[%(G21 +2M"dF,K,F .G)D 6 5 $?+H Vv*}BZsAV@R # 1?/C3F1 6 9WrEo4q7\w'JeSo$@]Ibi}<\q0Ka Mg%Nj(e9Xs(m?h}:HZ`s3fy6Zo,cx7yCi<2Ga;FA[cwq g|H;N AR3EIU*AR"@S;KEX%); FY*.< @RL^!6GBU 7JCZj~=E[J^7K*>4E$71F(9/@0BH_1(?&7 *2FF]E[Z;Q+= (9,<?\*Qp9Vu 7T'C?Z%Vr4Xq.)GKhLiRm$3M!9(AC\Yu%.N7VA`f1\~(7V0P; 8+1Jo=qJ&D Fd%4S +PpPo_|%{;o']mBx0F"46 '?Y1#@;<.\|@^8V* :"1$0!"3%)"95';f\u6]w)n_c{5'$8/+@4 6)(=*ReJbuRNd:3H #94M$=W-,GB`&Xx;Db/XtDE_.?Z(YtB:U$Ke8Jc?C[;5M.&@+Yp5mBnK>!?yt;[46V/Bb;^U!?#=E(ZT1OIB^YLg_Nia0HA5LH5ML%><7OKF`V7PD:QJRjdDZXH][AVW+?AE2FL3HM,BE/HI/GJ+?F$9A0=!1>2@L1BN2CL$8>,BF?UW,ED5OK3LH1JC;RJF^T@VMOdZ>TGAWGH`KJbKKcI`y[XrOd_WqTUrT[x]wzQjVOfZTg`k~zZrj[vlId\TofRraUud_nbuTwiMnbYznUvgNnaVtkKibKjbOngDcYHh\RtbRw]RwZ\`X}ZEgHEeI]z^OmPUrSUrQKiKqsjmWrZhigiioRoXYv^fjbk[|dUv^^}iRj_SmbNj^TpcQl^Oj[EaSgsbj[z^^\xvnl]['D#ToOqjd\Y|P`OsjTTzD}jxalcZzy]}cFgT_~sRph\vr?YZ%BCHffAa]@a[Kki4TZJkxSuSxdo`ddihDko<`lHfzDavFdw?^oA`nQq|\}mp_y;[SLj^Rr]kqZ}\xzY|_JkR_gkrltVvbDaP^yl|fuamdoPp\[zfaiWuX`{]kak`c}Xm^iz_}\vU~]pURv@VwGOq@VzBeJuWiQzdb|Kw]ynrkNkUSn`+E9LdVc{mJbU@XII`SKhVMiVWs\NkO;Y:PnPhhTrTOhNZtYGbFUrWGdK>ZAKhQ(E+NkKf[yev\luwmkTt]rvc[tHo[{keSle;Y@LiUC_KC_J,I,^~Sncwj_yPRjDk]n_h~WiWlZzf~guqufpcPkG[vPk^te]zNj^~EbGQm^5QB5PF7QL6OL'?B#<=+DF.FG3NL0IH4OJ*E=D`TPpa(E:@[ORn_(C2Zv`JhNNnQWyYYz[OnSJjOJkRRrZEeOBbMCdOFeQNlW\Ahk0J;0+%<<9826#8:-GCVrfJjX[xdUp]LiRNkVLiRC^IB_JB_JA]LC^MPlW=ZBNkRYuXZwXKkHKjHRqNXyV\~YVxTNoJ\}Ug^cWsf`S|rTwIh[}ryw'A,/F?.E=.GA1KD1KD.:)8$2+?J3JT$>E$AD8;8;+0'@C"<;75)B?-FC*C@;TQ+EE/-:2%@7=WNLe]:PL&;>08!3=%<@05(,0HG6PK6QK.HC0JE8PMCXVDXW 55#79!67#67%6934&:<$9:#97+B<>ZN8TDGePRmU3O7MiPKfNB[E7P=7NB@XKOk[WID\N7ND7KF)=<*>?&=:"8:3411#94?VFB[Dcw\|dv\n~Rvmahijq{ep_dsRfuUvaxaw]~c|asiyazcjva`mJ{g}hnW^mFcsKoZ}gxn\crVZhNWeQ\kZKWGUbQO\JJZG>P9SeLTgNP^GQ^HMZABP6WeKYiMNaGCX41F<]j=Vr!c.p.?t0{:Li +Au2k)Al)UpCi;Jd(w4K.# 2 "5$'A+80J56((!!   $*) &  ! %" #(<64%>UE0G23K45M5*F,-F-;Y@5##<&?W@/#:*(>1%;//"4M<'%9431$4G3vr6Q)[vLb:^tMO`;DU3P_>R^@P]>xeH_}A]{@gKqQ|[sQ]z7a;gBhD^}:Rq(Vt!x:ZdBUk-Z{"k5g1Zz*Z|(xBUrm3`"l,bLli.Fol:]u'Dgi'l؂uAb}CdM]zBjB^z)f)?IKVb8W6T2O"2L%6<<^6-|%oOE\ Uj:N:O1I7O4 4N$6Q3N:S1J@W%%74J$#9/E%>'C'C9 5Q)5O(3%!9 4. '=';$?Mi1;ZB_l8e:K)<)8!/ "5 $8"7)f6h/wDZs+qIIh$Jc#Wq2Uh. 0%E5T%B ;W"2K2 ((A4O%:QTo*B[Od#Ibn(bpWi9Jg{&[rE^Ul|E5I +@SN4F7H0DO_08KTg&F[)<%8:O=TNc!8N 35G2C`E[c>N +<+A1H,A /ChF^ay){I3I8L=N4F)D$@<$@1L!)A-A,C #>:/4V`JRnD6-D'/H$*B%>=V08"<5N(Z)Fj.I:t%jLf_^x Qo2BZIc RWqf~V"PfrOA^ e$AYJ_l*`x 0 (.I+H>_q5Oa'@_9VeJs!Gp7b/4V +Su.No,=]mIZ~0JrJq >bmAc9qNb4NVmD[uMJc=@X65, +1L+Fa;-H#!;Ge5Jj.&o: +# 6O/!92M,fV~/ 5)?-DUm4[s,ns#5#%7.*?33&2#')<\uKPi@,GMi7mS`|FdEbB-J5P Ga0^zFeIa}F*D615J09K-qVYKi$Ff0/N)29!?!86T1?%CPl@MgL(A>&=D/DR(@H3IPJ_d7LK3GE+?>NfcGa^4NKF`\Fa[Jd_?WR1HC*BA9QRLeaE4GOE(G.73DQ1@%9F1< 5>*?F(=C&:?"4:+?B,@G2FO*;I.<$3B&2C/;06"6<0EH0FD$:87NK7MK7LI4IC5KC3H>AWK-C7?TL>SM5JA2E;4H=:RAZTD`Z>]QOo`IjZIl]PpfLkcRriZyoLkbUtnUtoOmjUtpRphSsiRqbY|bMoSekZ~_LnSHgOZx]C`FGcKXuYosbgTq[ZuaYq\Wq[`{iUp]a}lepWtb_}k]zjVqdTncVqeVsgSqf?^PZxiLgYSn`MiXYv\mhZ|Teasmsllb`Uuj_RmZj|ccMeOz]houf{tjiqv]yeYyht|Vxlru@`d\|Np`UzUzninj\|Vy|;\iA]qDaw:ZmPqMn|Op}Rq|`gjSsi[}k]asoqmeims\dOoUenAcMZ{ixfyasPnbYxk|ZyhSq\[w^d~a`x[azZ`zWk]uaqXkOy^v[dMveQsDUwKl`s`xem\^xMc~QllnWr`]xVLhRSo`0M?8TJ(C;4SIJh[7UJ-HD1JI1ID>XO;SLNeZKcUF_Q>SHF]QNfYHaQWs^IeMGcJC_FOhRLdPH_N9R@@YGHaO6O=KdOAZFD]GWuSdZscrZy`s\t`{ybu^pZoh~RoY]uEhi|sh@_@FbN?WHMfU.K0}rwkmaqfZtRtlg]g[pcpc`S{lp^p^r~qcZaWPlCQl@VpDZtLrhsn|zVpW9RA$;4-CA.DG/CI.DH-EG)@B)@B&@=?XTFaZ9TN*I?'A7a}pA\MHcOQmWQoVWu\JhPCcMCdMIhU?^L<[K8WG>ZKYNZ{\QsTKlNStXTuXKjLWvTgbleZ}UNrIBg9SyGk]cWoiPgQ+@9*?8(?9'>:.E@/G=,D7:SB=YA?]EBbK;UM&>A!2@*8%8D&;F%=D378:*GI,HK8< :>!;;54,FA/FC%?;)A?/II,JE5NH4ND=YMHdX4NG44*0*349/6+.(@>0IH%@=&A>10*)+??+??%;8"<9!89(;=!46#6:'<<(<=)=<(?:0J?GcTKhXQnWB_HB^GA]GHdND^M=SGBZMB^NJdU9OG.D>0DA,A<@UOUic=TF6OB6MB/F<*A93JE9NI(<:!63 42(=8*" VjPqfd~eniz`hcymp]jzVp]n~Ym}XqZkz`y^}a}`w^y`|gt`vb{gk{Wq\n~Ws[jxTzcs_gvWfvXZgOYgPYfPXiTWePIUB=M8P7`tYcz]AZP;6D.AT:=Q6G[@MbF:M8&<+3G;1E<$8.1D9MeSD[G.E1)@+;N<:O24F7,>05G9)?-*?-F]B5P+Jf9So;'DJe0Id-<Cag6]{$r7LEQ^{PjBc7`-xCTpl-e\wu0U;v-y1g |14_zPk%KiD`  &9"4*B*3$ +'+#"74*)'32?SO("+"&/E=.G75L:3I54L6)@*=VA?ZE/7$*C-6" 5%"6*#9-/E8/ 5(($, 2L-YwL\yL"=dH\{7Wt.>\Ol+Pq/fDPt3fN,E% "$PjDA^-[x@_z@kKgDzZEa'Hf+Qn4If+x[Fc+Jf.dEz[Uo7^yGbxP;P,3G,3G17N99O90J)3M'Kg3Hd0Ki1bHGg-hMdK!?=Y)Ea/\xFQm;ZvJ=U.9O,e}D^{=fI`}=kFhFhI2M{W^}2wA|;}64y0vv>Ur>rGm9B@_q1e'Gh eDVG^؎YUm1l*Lԁ`E[Vp?Ie36SHe!i5x>l'x4Zh3bKk)!? 'C 9&B!YxVjiImAUtL6U.,L'5S3GgE5T19Q14P1+29#:A`7Hg^-ZzILj5{Kf.>Z^|=Ur5G_,nQuVNo,Yx5Po,Ge#Gf%Kh,Us3Ps)j;xB\t#OE[ QRep2k,jf^]0EWSB^b'~CHG?ZD`ZvMtM3m +4;@!>dwa}:R)?,C .E'=3 ?V-0F '='>7/ (.C9L(=T-3H"/D&?2J!Ok@9T(:W-7P*jW=Y-* 0 4,BD[5$>-I#@ +50I(+!1 .?$9+"f\k;D\7M*C Nk1Ib'Qj*GZO^*\j88F@N9I3B 6IH[&Tf*^p2Wh1:L"4&9 *A7O$He68V)3P#$A(B,F0 +&;=U*9P')?Mf5$:@X"wKWnDV6I#6Siq)0IWqp>-D;N)$3 .<2B1B2E.E 8LMb `q,TjFjQL+@?T-B,H^#_g1f|7"4+fA΂@`y,[q"g3sB $Ts=\z?0M,I7QD[$$=Ed j@[~-sDj:]-=]i<8V lDCa7U;X_|x5d~',E#;Y;T (?XoRE*v\Mw%x.X=Rs3zEOZt>v?h)IFnkQpQ~;e-{L5U=[8Va?St.`;Lr%Pv,3WLn$Rt)gE8X^yM19R-Kd>l^:S-1 (@1K#3N&Je/C5)>2 3)#3#.D&[xN5T)Nh>^{`}A.HF]/7OnLoMy^=S+4I*>R64D)CT(b|6Uz^;2!8&-F3+G,!>eZn_Ab+b\0*!:A2JW2LT2IN/DG:OM-@=5HHAUSA[W0JG3MJ;VQE_ZG`[Jb[-FD8PS?YV4MF.E=>PQG(:D)(;A2:$6A*;H-=!->(:$0?RVKa[I^Z,@;%73=PIDXNCYLI`U:PF0EA:OIDWL1E8I^JH_HSkPNgNNgPNgMZsVTmSYt[JfNRnU`|egjg~obvotzpjh}h}h~bxfxrqkqif~e~XxsKjgKhiPolJjbYynVwgZ{gJlT]fXzdHiU?]KFcO_|g:VA_|dgnXv_dkZtbPiUVo[b~hWt`UqbJf[[uk^yqNi`Lg^Jd[Je[IfZNl`UuiIh\Jh\KfYXvcYw[zyqzqyk\vfcSdUiVv`s\xexdxljpl[yoidkhmnmwBePOr^`th2PU:XgJixAdsJpzJotqlkEgi6UaMh~>[qCdwBdtJnzKl{Ln|Yy_}~VuoWvl_nxy|rj{tkj_c5K?CZOBZNAXKC[J8Q=`ya}zd[fVp]nZaNm[m[q[pY}gvcwOnkf}K~bvsOnF*E,E\IQjVKdNSnQUnOd}]NgHYqTVoRWrQ\wSwnbZ`VVtJ`}ShXnaqei_k`PjCWqHa|O[vK\vPRmEi[h`qqJbU%;8+@F.AL5JS%>?":<#;>+BC5NM.HE7ROC_YPkc`{o?YK>XHQjXD_LKgTC`MFeTFeV9XI2RC5SF8TG9UJMhZ4O?HaUHdXHeW:VH6RH6PN.HJ,@A$86Th_d{krtlnQkU=W@@YEF`KFbI>\>PkS_vbNh\(A@8QOC[[6RLKj^No]KhVEaN=\G>^JIhVMlYPo]BaO@]JIgUOp^EdPIfQHgMHgLSuZNnSDfLCdLBcLOpYIeO:XA0JM)AB0HH/ED%<9%<9%>;2OM>[XCaYFdY8VJLj^C^V54!9=1919-4%8<3556'@B%=?69)>A24%79(>>$<<&AA**24#8<#5:':;)<<,B@:TK7J0I`D?Y;1J/QkMPjL6O4:S;9Q9@WF^A1D-BT=P<6I;@UK/C<(<3+?2@WC(@*$=&@WC0H61E4;M86H2I^KL;CP?BO?>M@0A5-?43F9/A4-A4&.>46F;&7,&6,!4',>2+=/+=/*;/&<*4I3*C%9U+^zGA_!Po,[w2kG{XIf!Wu)Om\x"}DJr?sCnA|Q_VQ[} `} y8b{$|8s&8|0|.ael"?B^d([P~TLb-9O"TmE&>#!,$*&*&&0$*@4)"  4/.C:*A5*C56PA"<.2J95N;0G4*A,3 *C2 ( !'=)   1"%=+'<3( .kdKh=0Fi.ZxQ3@O4<},cJwJId'Ql4Ml-uG]d3Sl"Og*Uo:4S3P}Px@l1XOkn@)B,I2O1M%0K+a{_2N09X6;a;OrM4S.@a9=]58[4QqM:W82I-/I)`w_&A("?#A`x9?V{18@c{{,Mer+>AbV?_Nnu2* 20E"0/#9*@/3#9%/ #<-B)C3M .H*F73 96561F -F 5 + $"< +2Pa~COh$dy@CY =U$ .Vh>6LLa6*@'0M>\?W;S*@ BZ)4PUo33JNc#?MFTDO8C[m}7BU>SVj/Xh1Sd-.@-5E6L*C1L#,F3 <4/H%2G!5H#!8/E()>%7L//HXs)Jb*Cax*u4]Nj*JdRj%Ui&Oc$!2(8 0@Sf52F 9$85K=T}/k~l^u Tks#C[JXn,/G +[q.G]GbwK`s9%;vOb|3Y0-H'JdF/0.A.-&23I/ =,K_}>kEJj&mI'D/G3]{8Dd#Jl"Im]([~%Bd]|2@`Yw+7T#@ 3P*FQl(^w/,BUo!K^}!nEt/g$Qnha},l$r/ISD\x*2Lh!;R 2H=~ |$8GESmei/RWr;Y87X'KBf8Z7]Sm_zs+}>AatFXv,Tq, ?5SNl*=_U{5?`Qq-e<]Kk(9YE_6";-E&@V1hWE_7!;5O/8P1/H .Jdsa0 $<C[> "6 /P*N 0_3aEe'-ITqD3M)3I">UG_#}"0%8HF*;3$7*#7'1I/KfAWuQ4 _{R.J~aVs1Xv1qNOk1)>= 449MM,ED;VT0MI5OK.HEC[W=TP%:9+@A=SQ0EC1FD1EE/BF0BI1DM"6@.;1=/BL)>F-DL4IT*=I%9D%9D)"6?-@J):D*;C-3(;C1=,:/<+9+:D*V[;V]]r>\nQr{Z{xïfcyxnRuIyvNrQ1R2DbBikv~hwRsfIhaKjfDc_NigGa\F`VNk[PmVXrYPjMTmQOgKNgGWtMfYiZeVbTvmzt`\\yY`}^dad~bLfLIbGWpR`zWTlHh]a{Ylkr{]zk]yk\xjXtfRnaAaV9WN3NI*C@Kf\AXN3ID6LG;QLBXR-B>@WPA^QE`O]wcZv_JeP9RA@XJ=TG:OH5LBB[OSna?\L-K94R<_~bTwRZ|V[}UZ{RUuJka]~ScUhXtwoY}gx`hoj}dIeC9U9cfYw\hjVrV@Y?LdJ^v\ay^\vZXtU_zWUpNNmILmHZzTog\xQtgeYm\zpT]yDjTjRqq[m^[uWks!50/2'9C0<$9>%YJ;VGA\MA]M@\L=]M 41>SKQhYKdM_z]e~caybYq^E\P@XH5N:OiPVpUVpW`|fgr'@;,FF?X]*ED[I4R@3R@*'@)%@"PoMFbCDaH?ZJ %!,.,1+16<)BF%>B$=B$=B,EJ%>B(AA*BC.CE4GK5HJ 46!8;.HH(CE1LE1JD9OH)?9'?876 8=.DJ$7;-21625251266$<;,AB5JK0II3KK1KH1JG%>;0HD4NG:SI=XMHbV>[K7QCA\M:QE,F<,F:XqdKaX+B;3JB:QF4LA.F9,C9'@;&@<$:4 8.jr.!9L9xm\dzPvbeh~dzdxeolzlVk}TaqMua{fiyazzbrXt\x_tYt^iwOgwQr`uc`pOapMm{Xn}Vybr~dm{XfrUrfGV>KZ@P`Cj|\teG[=K]AYkO_sTOdGLcIOeNLaEFZ?AV<\oWFY@AV<8K6F[DCYBNdNG^DBZ=@X>?R=6I68I70A05E46G56F39J85H4&;,+?3$8- 5(:O>?WC4K71G3*A+)?*:P<>P=K^JIZHARBCTDCSFEQG3@8/<3/=5&7,,<31C;'81*<30@70?2>L?2?6,=6'703D:,=0.>/-?.,<0AUC5L3,E&0J#*EmyJq:]x/Gf^|5rFZ{/|OR|NrEj=`8kGkE_~4`~4^^r=Op:[{$`$q0w/9Mv+dm%5n"p'Yw{;9^x1d~D3M C]>4.)?51*%>1'@1 8,6+  #&;,3G9,C5%>/&>0/#'<0*@3%9-6)/!*."#;/9NC*A/4K7/E5#/)/$":*-$ #b~YJg:XyDbHeI<]"fGSs0`~;Rp0[{`DOm1\z;qRRm68S s\Rp:Ef/fK[w=oOdFUr6Ri=3F*="4E39H7,?+6J55M1=Y-Lf9Rn>Je5Gb1[xDlUkSTrE=[,Fb4YsI;S.4D'@O6YePS^HKV@=H1YdMAN8O]HJY@GU<•zfx[Sn5Yu:bDXv=Xx<[y=fIGb/;S#bXu2}Mm/;MGEbNnStb-Hkj2PCNz1Nj fw9VpYu4Wu@>^)Fi0Kp5]DKl.Ji)Jg&_y9kEa|;{Il>f6c|0i8pCIb':P%!; :"<- % )A(H`IAX@4U5FdEMkKHgD*G&6R01K/.F-8S86L5&?',F,GcD:X6MmH:X3:Z2-H 5O"4MId/If/Cb)iOKf5-JDa,If0-JSo7Xs9>YLh*7TNl$nDId2Mwe~!x)T?[Ql fWnXs#&r:WA2Vr2Kv1n*Hx n ,>q[wwD^d{7KLb!Nd3!:3 +./+1, )4)A#$8*L`=TjI"74$#8%;0- 3#;,%,3 >T"sR4RNj-8Rd;Klj:Zt1mNaqQ/@-B *B'<4J$&7 : 9)A&:S5-83.F"*E#=. "8S),F!$>%*41!) 23"= (G =Y'Tq4Nm-Lm'Po,8R4O0M5S*I Vb/Wz"JlSs(.b6^{8Ea,:: +Kb,l@7L0E0H4N k6ZzDh\n: $:EW1K\)ARK\ Im5k7xI" $FAq)@gMj gu26L ]FfS)G.J >Z!z\:ZKj(@_Kk&Jj"`:Tr3YwB- %&@:S1>W0D]1iY$@(B&?%<2 +&Bp +) ##>?^1Fi0frEZ*\~-f]{;Ca+7R%,EYp:f~B^v= +-+!/*!3(.C544M.@\; =9i]#?Ws6[Jf*\x=Yv@3NA\,0L0LsVE_)q[4S$\yHKi3mPuKtB+O0!;&7!8D_6Gg/rV9[);(/1,AI0?.:,=E)=@*??,@B.1,@C:TS1LJ)CB;US3KH*@<9OM"79%9:2IG1FC/DB-AA'9<'9A+>F%:D/:3EQ.@L5KT6MU0CO(;G%9D)=I&9D"3@(7F0>"3B&6E"3> 2<3<(]OFdUSm``}klw`~kYygbnPn_JfWQj\KcW@[NHbXF`VGb[OhdUpkC\YAYV=RQG^YKaY9SGYse_yjOkZ9WDIiQ`i`fCfJKnRKlOEgFgcaZ]Uqkrlsmpjpjg_vlwj~ntcz}XwXCeNDgV7\N?cYX}sVxsOqqEgf|qw}pLlx9Yn8VqFdFdyMmzMowmvm]njqlme`voidnmjk~orhrZyiJj`IhcLmj@^_>ZZ@]YB[UKfZPlZGcMQkQRlSMfPPjOToMndogb[rlSrOJhJEdH>ZAFcJVoUMgN3L75N8OhKZuTi`uokj[vaHcTB\QA\S@[Q?YP6QJ=YT=XR.FC$<8Pkd?WRH]Z6LI/HF1-$;8DYUKf[MiXJfRVq]:RB:QD:PDF\P=RK?VKE`RFcTKhZ@]M8VB^|dLpMQsQA`?MkK]{X^|YOkHRlIsfocncoanw`|gp}ilWhUqd9U4]z^fiLkOUtWFbIBZDd{fOeORjQMhL\vWSoNQnPRoOPpOUtSVsQPmJdZm_~fz[vUlLhJyYuV^Izjme[r\4I;=RJ/BA%:;#7:":?59#AD">@-JJ8:1IJ>WPC]SRl_=XJTpaQl]:VH1PC:YL>\R9WN-KB0JC4OJ/JF8QM+HB,GC1JE(B=:QN,@B+?B*?ACXSaxmKbTQiTE^CLeLSjUQhX=VH*C5[tapqji5R63N89SC7,,, :<)CC:SG:UJ2QA8XFGhSRpYB`H=YD*F5LgYPjWB^K4R>;YG=TGBYHB[F9W=KmL@b>cZxqelarjSsLOmDtOnDYxN^}TLk>xdKj7YzGLl>8#2*566;/IMA[Z%?>(AB7=063375"9</2-2.3)/.2(BE2KM!<8,E@9NH=SM,D?.GD %)/6 57$9;#7;"6:$9;&>>&?;-EB14$9:(>>)@A=VR/HE.GB:TO6RN)B?&??&??&@<8SO3NI.IA3MD5RE3O@?[L8TE4PA*C:'?992?YN0K?(?7;RKH_Y7NF:QH1GA3JC)A=%<6'>40"DZFf|fso]g{Vucr|f{by_w^yamxjr`avO_sJ`rLetPo|Y|blkw_nUw]s[|dizPcpHpWzbcyPk{Xqzg{cpjxdr]jyYiw[_lSFV;N`DYlNcyUb{UPgFZsRTkLKa?F`LeHG]@D[?VoS]sZM`J1D19L9/B//E2$9&(=+-*?.2G4?U@5K4]qWCVANaNJ]J;M?1A1:J90@//A0FXH:O=5I:*@4@UHAYHBZG9O<9N;K`N?U?NaM>R=EVC5.;2*!.=7,;6);5,=75H>-=2:K?&3**8//;44C8'8-1A42B5,>.3J0RjKMi@,J ?Lk(j:OpYz,:\a8\^;`gAVv+Ffe8Nl:wEr@k7XyXzSrf#:v+;d4z-|.:Ek.j>:W4R'C.G%.0#"=-">*!8'./G+ :-D*AVB,C0(@12%, #6+2(2(*%.#!8+*?4& $*B+#: !9" %)4"0G5$ D_7gT7T!%G gL6Y$AoRYz:]{Ro2Ts6Xu?Wp?B^/dNC`/Nk7ZvAF_)Qn4oRTk>?U02D+3B0/>-%6#0C08Q7Kg=Mg=B]1Gb4Fa0Lh7Vr@Rm;Hd:E^5Kg8CY/:N+apRAO8ITC@J9` We.Mnc%y/P5D`Bp2N|GGb7S(@.E&!0';$88&'HYu7Ld*Tm0Dc!Mj.)A%'=1H ;R"*CMd0Sj7>W#BY,$:&:;NZn-cXl\Wep|7\h&.qHPc-7M:N9P 2H4I!3 5*!3)$8* F^.kK0C Z/A#( I[1Oh0>SE[g~5q>Wo Ib [uw3_x$AYLeHb6Oq+w/ 0hz*s28JRhqZ"/1 +!;(D'D`7a1Uw'Ih.KUr./M8S#(-8L%Ui9f:P&G^+ 9/LvyE!C}Jb$w:uCia ?v!cf&QpVr)|RkD;WLi-?^4SHi%cAOo)Lj%qJaAs_RlBubB]3$?;U+VsB1N: ++E ! '/4Up\RlA#@1P"'HW|2~KW+Ja~T*1* )&(#3C:.E.3L,=[9(B&tn1?['}Fc,gPIe5^zJiQ^}B3QWsAwDvS8%>$BYCBZ9XtDZ{;gDd9,G;*AD)"1*=E5IO4MM*DC"<;3JK@VTQ,?O2HV4C&=K0BR+=Q,=S7Ga3B]1A\/>Y.=T4FU?Rb=P^CW_M`dRfaSj]b}l[|d]}hPp]9XJ=XOHcZ#>4Mi]Nl`Qoc]{p`{sPlbKf^D`V?ZQ>UL@VQ;PNQgeXnlAVX5JLPgeQjeCZVHbZNh]G`SOjZJeSGdPixWwcMmZDcRCcQGgS=\Ctuei^aY{\QtU`eglejZ|]ghfea_b\mgkczptkwonnNqYGjV=cQX|mHlaJne{vkgfhac\}zhySu~6Vi9Vo6Vk?almVx{h{fUwn=^JRpWLjOOkQ:V>B^HQoXQpXjnij`~fZ{fWwiRqjBb]Nnm9YX,HIC`]5RKB^SNg[MeULeRPiUIaMJcLQkP\wY_|^[y^Xv\UAOdQXm[YrZ^w\XrUVtTljedKjK@_@TrU]z[_|[Y}NRxAsVqQvXiMeCqRcOm[zlzqc{_)VmWPiZ30+EE-FL-GL;?,EH'AA63/KAKf[NjYC`NNjZ>^N1PD3QJ4OH[S-IF*EA7PGGaTB^P;WJ7VK,KA/LB?^P5QC6RCB^OB[Q0M>@\N?\KB_IGeKGeMIgP:'9T>Yu_>ZF.G7;RE8P@1I5B`GVyWOqLTwOpetirhvmujq{edKeKY|AeLqUtX_ClOtX_H~sB_E!910060,F>4+<5:7Ea],(744426-3+4+5/8*AG7:'@=/FAC[SJeY:SL7PM)-+2.2$:>5JO2GKCXZ8QP:TO.ID&>>!67"780DD1FF,CB4NJ5NJ+HD"?;'DB,HF6NN1JG)C>(C>)C=0KB3OD9XI:XK-H>(A>%>:0-Qhb0GA-C@6LIM;K^NJ]L?QC5D:->3:IB=LD7C;.:4*:3"0*.))%'70*7.0=4.:0(4*$0&!/&%3++;4(:*.)D5R$eL_|@Po/oJYxL^YiDvTeICh+Ll6No<\}DYvBQn8a~F^{?eB[vNY_g8sCUtF_.nP,D\5>W.9T%AW)F[,jNYvBK`56K'2E-@Q=:J87J6!51I1C]:VpJVpI5P$B]0Fb3Id6hVH^9H^5{h;R)EZ5]lQCP[*]{C\wA_zFQk<6Q ?["h?>] s;Hv2s3j-A` +nu;S^q'Ql n/lh0RlE^ ك[Ws3-IR&+A3Fj;AYJah}6,<-=L^5JEZ"4D-?7I!3G"3G&%8)& &!3 _vJ^w!:U04 5M-1J#!> Ml*[{0<^GgKk%2O 284N+ (4H"$;\t7tEv?^{"9W@b l,f8)@+=7KiFX#]s,zARZ6O Fa~>e'x : wURi+fz3FB(Sh7H ?Qbw2k9Pg@V +Ip#=RpSo(9xWVm0Rg$f}-et+n%|*1.5ARB[4NB]j [6UQp*Ml&Wv12Q jA`).I=W*b}KiUD`38R&t^Jd3+K3UA^*y^hFkd@Dg&9Z.K-J$AKl4Qu+;:Y7S1wPkFjJayCRi0Sm*Fcx&<! (+$1-;N;0L*?\C/DH1GK4928*>B,@F&9?"4:"5:5IK*>>/BB'8:%7<*;D-@K,?K2DQ/AO4ES(>H#9C3;$.9.AJ>WY5OO*DC!998OQ3FJ%8<,>B&8>*=D*VFG_OE\JI_M/G77L@>QF5I>BWIT0>W4C^5B^5C^3A[2AZ2CWCTfCVeFWbO`c[oi_via}j<]ITRUI;SDAZKJeSLfTHdNRnYYtc@\K;UI;VK.G@E_W;RJBYORk\=UDnuc|i[wbWr^KeR@[LVofJe_C^Z+CE4GN,@G*=D8NR1IK/GG9LM0FC8SL@XS-FB7QLA]Y3PL4OJ93IAH`W@[R8RL6PL0KG,HBC\RJfUC^L1I76O;OgRF]HH`KPhRPiPYq]G^M7N=WoYXqTf`MgGa~\_}YLjFjf\y[HbJ>W@Wq[d}h[q^K`Q^re^sfe|he~f[vX_}_OmOZy]VsYKhOB`GHgNLjNPpN@a46Y"RvXM7QJ3OF;ZR0MC7SEUqcC_S5OE6SFKg[C`QGeN=[AcepvJhP#B(^}eiqC_N@ZK6NAE`NJiP^a]~]XzYWzVc^^XTxO`Zreo]fNfLFm-Dk/^FqXwUhC^;V5YB]}R#.1N7IgNbi#C,8&Kj^2PE6TK$>;%>=38*2.6%:A4:/DG?YV*?=7OF>XM/KB20+1-5+2-5-418),0385)B?8745-/!26$7;"67+EC/JH:842:789!>=&C@)ED%A>)C?/JE.LE1NG0NE-GB)AA4412.-%:81FG5JL.DD*B<&=6/F>6*PkY~~xth~`VmPa{\^wTp|~jp\jzV}irj|`j{Qvdk[h~[vjxsas_qmw_i{Ncdx\v[sWz`e{`y`t^zdp|Yp|Yjua]jPT`GCR5M[>XgHSbDN`?ZlM[mMH_=9S28S3D_BD^AA\=9W6EdALhERkH_wWNeG1I/4I36H6.@21C8+@35H<@PF2D83F9=O@i{jYlY8K63-C7'=/*A4!9++B50I66N:9N:5L67K7@R>H[H@P@DUDGWHASC-<4-<7-<61>63A8+;1"2(#3*8E<3@79C<,7.)6('3''3+,&(8-9N5Ic;jTZ{>Nr-Vz/g@pLpK^~WD(>,%<-#7(4("3)1B8(<14'$'B%ZuQZuG;X&Mh=G_>OfJ-2N"Mi3Qp2Qt6Tw:Df-Kk3He/Kk1Fe,Sr8=Z$Dc,Ih.Kj.Wv9Kk0XsF-IHd8Lg8Hc4a}L\xLE`7=L.BV49O(:T)YoB@W'Jc3F`18R%*B=P5K^F1D-F\EKaF.F+=X8G`>Ke@2M#Hc8E`4D_3Kh=Kc<5J#VlAQh=K`7RdF3B,5B3&3%RaO0>-?K;7F6>O:ViPShH[wH`DWw9Vt:Ww?Jf,Gd/azKHb3]xG@Z-Pj=Ia5Ok1ʃ\h2Mlw5f&Kil4Zf(UDy;FxAd2vImDLj%Vt.Mk)W;X+4P >Z+D_1:W(1P:Y#qVhYy8Pr'Ux$Iy2v)KEZ{gV} @eT{ @h 7_;aNq>94Lnb'_z#Q|CkKs6Khz)Tj,@8P00F+B/( # +4)?.C"#9+?#"715F-)  %'''%"  !$ 4($>D_1)C-  erQYiI. 3D%bsO^oEGZ,Xh5Sh/DZ"@W [t7pN,)?&?6SI\yKf6P >Y;R Riv4h})c{.zMOk*XBZCZ6O Sk)6P +Nd,]u5jF( 4 $Xq1Z'8%UsRKiB5U'Kj8%B@]-[yE">.Fd.Rp:)F"<4N,$;#&.   /!<7P0, ;TsYt.[w'Pla|+7EG!5( 3C>P#La";QCdx;_|6Ki0PVvd ~5N}.>0M +";*B)@ +*==Qf{$Pecg~6$8DYJ^Qh D]^u"au#m {>Mi$7P 1GCW%_FZk.\u`s:PZxhgn$V6k!?= $%>#,&=&`xa9P85MfN0D\vY~e &.E(%D4GM#7=0DJ.50718#5=/73FN.5#7:)==0CE2EF)9?$5?3FP&9E&8F3DU$6E2FS%;E&>E?V^4JT,CM+@L&8F'7(9'7)9'7E7JT;QX.EJ&:@$6>(7E,9,:+:"3?4IM/HJ0JJ,FE-EH%=@(/1:.6/:!2:/61DH1CC/B@6JB6K@@TIBYKF^MHaKG`K.F4=SEAWJRfZShZW7G\2B[;N`I]j6IZ,=R7F]7F]5C[:I]7F\:J`>Qc@Sa?RYK`^^sigoHgQA^M1NA3NH(A?!99ZsqQlhKgbB`\C\\7MM4GJ9MO9MO9NN6NN>VV?WV>WT-FC9PM>TRAWRRiaMi^PkdGb\Gb\=XP>YKWra;WB?`L2Q@/K>5PI3NIB\SjwLmWblZ|fSvbHkYLm\Ll\GgWZyhgqSu]Rt]_hdkjn`boouwUw\isUxd[l_rTyiNriTswTs{KlwHhvRrVvIexLi|Vwa^jw~Nq/QdZ}IjsQr{Z}JkuPtwSurIh^3MB@YR5NH9PL0KBFbTQn[QoZTta]|nIi_Poj=[Z+IK5SV;ZYDa^;WS=XRUKNg[JgWQl]MiYRm^BYN1H@6MJ;QNKdaF\\:OMF\WSl_`xi_whF_PC_N5PBRL.A@+?;>TOBYS53.D?@WOMdX2K9KdPTiVAXDOfSMcQBYK8NE3I@]sdKcOPiQPlPRqS[yZurEdEB^A;U[A=[8?a3:\*8Z)Pr?X{EHm/\?nSkPWx?m]c~Uj\ry6*!;9.FLE]d7OQ)D?MhbHhXMl\?_L6XE@aPAaU6RN$??$>?*DC2JJ@YX0IH,GE$>=+ED/FG'?@69,@A430GAcyo9RG4ODNj]5RC?ZLYvcD_Q7PC2HA=SHQh_?UJB[J@ZDD`ILgSLjW3P;Nk\Ml]B^P@VMLe\2NB5QHCcY,J@7UHE`U4OG.IB1K@B\Q>YK=ZF@+DD3HE,C;Ni_7QJ53,3)4+9%2!.&0)0/5%:>6MN.HG1026.2$7:(>@+DF21::::88)EF(DE66">?&CB-IF+GC*HD6SP)FB#<;%==69.5-1.37IP3FL,@C-B?$:1:VBRmXvzbbSpPZuUb{^cz^mf{owbtMgwQ|u{SdBQaAZAF^J@XBHcHRmNRnKYrL]vR?W4>U85L4.C0-B38L@;OE2F=/?70?7.?48K=3E65J74G31D18K89I:+@//C4-A6+?3#8/%;0&9Q940=20@6-=16E;9E<4@70;16?8=G=9E6#.$+!9L64L#Tq99_rQyQ`5vLhBAd"`F.MB`+Pn9YxFHd6;W.B^8Ea=a"8[iOJi32PfNWv;P4E\@E]@:T5FaC6N-XrM7P)@[2Fa8YtJTpGF`9Kd;XqDhQ(=auSReKTeRzw-=+6F45G4:K9=Q8EZ;Hb?F_<3N)6Q/2N%Pm>^3Nk=Ea0Uo@D^/2L!AZ3)C#3L/(?$5)4! %9M=CVL6J?#8(-B-C]@'B!(G'$A# &`y[KdG-G+6S50O-3S.;Y1@a4A^1Ec5gWVqGFa5Je;B^1B^/8U$5RBb'=]a;l>Y~"]``mR7D>\d#Yz_|7Ei :\ Il$(N0Ud1T?e)K4Z DOGaBa!#?= +7\:\?tO5\{$}({,)AF]F\*0GPi99N $,:)<* . 20%7+? ' + + + +' 4- 7/D2#"*/A)0#9*BD`(Un7]s?1 +  +,,<4G$@U.YmE!8Tg3`t>?X!Vm56PyE_t9(@ #8D]"A\D^"'@Pf(Tg*Zm3O_+DU",<7G$+;'<(<0'"#5 4.B'8K06I-"3!1>Pk3NbWe&ARL`'Jc*Tn00J5O=X4N +F5P +d<+F08T2N9T8P &;:L :J>PW18R6OOf$bx/5J/D)A-Gi~=cz7Od$A[uFwG\9N?Z'(Eb*e>{M!5KfD2!@6U,-L1N *G7T$Qm:5S < >\&Us?2P!+F=V4G`A0B)*"/,+0":"9N2F]=.JTr6Ee&Tr8)'<0 +,B=S,1Eb$B`A`?^;YnE2 Gd8:Q5  /%8;Q)E]("<Tn%Pi4NkStbPm*-0./>7I:N$8v>Im3Sp c}?t8Fh"l|%=4S2K(@ )5JNe ez/Yp'C[}Bp>p>yB]u,q4t,rWn$Slc%+K/P<[B^Tl)HgsA]}01M Li(nEuETvrCPq$d8Vx,X{*Xy.|*E-Fm2LSm?6Hb2|No7 @!? ,-N,O<\&$9:9WKk$Kl";[uC/I_uM5J @X'Xo?(@Um3tHg^+(C&8")' .A%v'@'= .KdG/>U? 8"+7!>Q6RiA/H4MmWF_74L,?V5":'=hVKf2Ec Ow8F*5&8A0:1=%7A-?I/9,6*4(2'2%4<#5<&7?.67JP5IJ/BD0DI#5<+>H#5B 2A.>+;&62FT(>K*@KSH=VC\u`JcM.F3P.>T,=L]KGhRPr_Mn]3TF@_TA`V?\T2QBhsYzdQs]`lbnwlx`jXydMn[Ii[CeVY{lNpb?aUAb_GdnFdqA^pBbs@`nFetIey^zYzgbfjjOrLpGm{LmzMly@amKmxJjqCc`Ed]0ME(CA@\Y?[X3MGIeXHdU?\L>\OPoeRrk5SQ5SU4RT?^`MjkMig;UR;VP6OK>WPF_YB[TE^VIfZHcXMf[Ph_WmhBVU5IJ1FG.DEBZNKcT?VF9ND'<;AVQ>VK;RBYr]LePE^JMfTC[ME[R1EA2GA?UID]MZu`Vr\MkSVtWkkGfGVsWF^HWn[KcPEYMG[PI]QF\ODZLSjZKdQIfPEbKMhSHdS7TE6RC9VE4Q?5R?0L10N,rfMk?.M4T#Kk9Lo6hN]AfIUy>\zMJhBC`<4KFawoQi\8SE;WK7SGA]NGcT5QB7RD/H<8QI@ZQ6PF5OE?UJ;SCC_NQm]A^M.I8CaOKkXOi\KbVPd\G[U4ME>ZN5QG:VM0OD-K?1LA/IB(B<'>4/JA=WL1LVA-C5&<-)?,0H.D]6,;4-=2*<02C8.C60A22D48J:;P?&:,-B5)=3 4+0+0&$8,-D71H;/E8,C14K7=V@-F0BZD?VAH^M0B26H:1C66H75G:3E9+;1.=6+;6&62-85(7-(8-;L?BRDA\8*D!Mk5:X%Ge0Ig/Jh3Fe2Uq?C_.5Q>\,Kg6Nj<+ED^HY:Lb=ZuMFa6So?jTGa2VrD6Q$B\5KcC6O1:S4E]=KdD1I,=Y7D^;9S0;U12M%C]7OiASmFOmDHc:Ea4mYRlBYrLYqRLdHCW=,>)0C,0@.L^I;O6G\;.F Mi6`@a@Nl1Vu:cFVq=Qg<>W1=V/:R.?X1DZ1Oj:Ni3A]"?XBbr:Oa}&nZzE`~7Jg `~7fAhIMj2Ml6:\%Ab1Nm[.6S,A[99S03M)'C ;X0Da5;Y(A^+Ac/Cc29Y*Bb6A_0>\->Y+D_1yg~6O',K&7S22L.8S7('6O<5N; &+7O>%?'5P19T1KfC@Z:91I+,D(9S6LiK(G%UvPPnFMi?8T*:V.4N&2L$7S*7P(8S+9R)C]2)C5RMl18\W|,Q] V~[AhS|w$Ah#GNsVz# @<\X{,<5)I , A -N +M +Np*31Rh9JtBiUz]Tv,tCV`4?Gm +y7=c8~/Rk,Hb&1H8M"6O,E.1F9N(*= %6)8zAW/ 1!3!)*='1,@,'  %'9%'?#&6K&6K%@]&Ha%3MNb.z!) + -): 0?S4_tSAV//-2K*A J`!g=T3%= 2LTl23Yp86N3FEV!Xj:CR%.>4E0 +AH]61 .)=($ + % >Q'Rc1D]H^>Q,@-C Jb+7T+H 2K .H c~>Mg(8R7ONf2Ne4.I6Ig%NfPeU=M(;9LVl!^v/Uo/Oc+8L,06O6N:O24I\p1(A +*BXk3;R;N8P2LZu)Zv$Q3$* +%~XWs/AXi}34!<1M*$AA_1,I6 *>[+cR!?6R%*H;W-"8$<BW<*!$%9&1-%#$86P$[z<9WLi0-7O'1G -BQg<*B5Zu6iB1OXw-Nl)Ml5'B& **'9$)< 5E].Oi/=WHc5P @\:Y=ExK 3& )8(1Xn,53Kd|%t6C`a"VvA^Ct%~)=g#5OKa)?m9Yq'/GF`5#<8M TkE2p$GGMcx.AKg%:ZKxDYAWRi4og|4k 5,=,W7QH` Sks$SNUpKc w5AIg@_ s:WpEa x;d+?af>[;Nl+Z|/^-Jll^2Bd|QLn$kL-H=T+Ja8,FUo?s^Ri@/JB_,Ux;Ik,4R!$A *YwH1PgKJb?mSnVt1u8ZuHCZ32G pH_4Ka5cw=]GN#= +1E(O_GHW;;M-#8,5uk7P-ZtO@[96P25)@,(@(@W5_xPC]1$>B]1#>$>ziB\3-'D5R+H +JjEN#AKg/Jh/=_ˎ /2K2Pf^.2/9/>/<%5A'9C&6E!1=/91;'9C+5(3)5#0'/.5+RX3FM%6F$4F);!2A+@N*AN&N;L[0?/@O$5C1=-71<*6+7)2$5A3DQ$7B*@H*AF.CH'?A2KL9&92AUJAZIPjTQjT3K9LaTOeWSjW@XBYq[QjTQjTAXCSiVaxfRhXTlZc}n^ONncHf_Zwr#=<.GFD_W`om{iwQrbVvgOoeTslKjdIi`Ed_NnlCa`Ca_Ecb9XX=[_A_i;Xh_mDdsGbxEd|WzbdpptkkZAdqDbqCcr:[i@`iBabPoiFda?]\B_`Hgf`wMj`C`T>\RRpj?]X>][-KN3PSB_aHde9UV2LN7PS5MM6NM9RN@ZX>XUIc_5QL9SOC[W4JG8KJ;OP2EI.@F3HKCWZ4HJ,A@-A?Ja[H^Y>WQ/JE:TQ?XU5OL)CC7QQ*DE-GH*DD&>A2JO)BF$AA*HD8TPGb])D=?XQA[T7RK2OF2MH-AA4HF2LC:UEQjWBZI;SGCWQ2FD+@?E\Y>WS9RM@ZX@Z[+EF1KI400IAA\Q=WF/J5PlUKfQ>YHE_PG_U:QJ"74(<6+@5Un`LhVEbQB_M1O7TqUXuZc~gE^LYo_PdWJ^SFZNOeUKbTLdSKeUVp`@[K9UA:UD@ZM1L@5RD1M?B_OJjU4Q59X5[zP_T-O#3T)<[.De3]KV|@ZAcPPpE]zY>[<`~_a`>YD 7.5IG>SN7OD0L<:XFFcQB`N4TDEfW<\O/JC5PK+FC?*?A)?@$=>440JGJb]F]UF`VC^QYO6QI/JA<[O9WHSnaGdV:UI-B9-D=;RI2MAIdQMiS\E:[D6VA@_N-H;-G>9UGLjZGfPRoS>Z@.L03Q5SqTB_BIgH]{]OqPOrH`URyFZ~LDh;7Z/?^7=^39^0:_-\KdPX~CY~CSy?Ot;YGCk2U|Fi]"F#E-&G7*L<1PA.N@-K>4RJ/JE'@=#=91JJ#9:*,27%7</407-42:,8(6,:+8)22946'@@*BB&=@37 7;5;%=C29!6='?D6:8;!=@'CF*FI+EG)FF'DB$A?!>9;7%?=&?;..,0.2#69,/ &7/uvjc^yW\wUXqS[sVG\D4E25E37I4TeK}juaq_`pNevTcvQ|ipezSh~Xj\^qLmZo^jYucjg~PlVq[v`Lf5D],J`4H^3mYhxSt`q|[w|crwcY`PuzpPXKNWHQ]MR^Lkt[_hPP[CNZBP^DJY?RaDMaA4M0'?'!7$, .$ 3&@S=LaDn\rcawS[sT^t[?WC&=0$;02)#70+>5(91*813E<&:.+?41E99N@H\N0E3. 4*2' 4, 1-!3)-B6*=33H=+>3,A16M9>V?.F0JbK6L96G83E86G99I=/A27G:3B8-=3*92*52,98(41(:/6F;3C8&6(.;.>J>?KA?H@AMC,8/FRG>M;:O3$@oXeH_CfM^@kJiJbDBa)9Z*:X/OjB6Q-0I)2K.&>!JcB[tRIf&>!2D4)-$->8 4, 5+6(*@1)@0';0%5*  $:'21+FC^3Ie5A],Tp>]xK7R*Ic@^xR8S)>\*UvBCd2Pn;=[(Uq?=[,2PFd/Jh4A^.Je:3N&7P*6P*D`5 A[C\8Q37P32M*9S/[tP:W/PkBB[2XnFVmEBY23M90 ! #'A*,F((B QoMTsO0I)'@#*D&JcG7T5.N+8"@,G!C`8NhD*D (D5L*>V2 %C613<Uu-Gj9[ +Ru$#DTu,Ps$7\^/ +1P +Cb#Cd*{81XO\wA^">1M%A D\!hi:)D$ !;S!ρXiA5(>)/& !' + +0.6O."7)"8|cAV DYB\vRco !0..@ 9K&r\,Mc2 Oa1Se2?T18O9PRk8Of3 8)B6;T8SHd(Sl1C\"7.6 4 %B_&|PF`cx2p5fz'Jo4cy,8QG_EZ**>/E 0X7L=RF[#"5!3;S:SF_`|+Oi~<oIqI5Q .C m9526!=B_2A]3.68,H#7V,<4Q-7P794I0"1*9'&7%2F0%=".%9+>!#79Q&Mj/Ed$5O"#;4,B=R+1F+AV%nUs^-K0PQoE2PdP=[)A^/'A!&4+E!: +1PZGc+5O#'>1F 4L$;T.7J"Pe)AYLRJd(#8G\:/Ewd{GIa1AW2KdBdUQm@i[9S-!,D)7N3ylQlB.Ib~Pya~OB_%e:e.QpqC^z;Rp+a;Mq5~t #-01:,:+9"2>/9&3B/= 2:.8#5> 0:.8)5&4-5/4+;B2EL.@F=QS6LN8MR9KS-;1>1@+<"5/A/DQ)>L$8F)?L#9E.<0>/>/?%6E/<.<1? 3>/;(7'8)62;(9D3GP%?F(@G-CH0DJ%:?&?@2IK(8GD9MF[seZs_OhRD]J5L@E\ORjT]uZlm\x]SnVKcNJ`LG]NRg\hqlv>RV*;I;LWsk}8KLJ\`ARV9JR;JZ3CV.@V4FZ:O_7IZ>M\-ECOjdHe\@\W4MN6PR3MR/FO1FR/BM6GS8JV)>I7KT/EJAWXCYRAXKTj\\sbC^OA\S.FF5MR2LO5MJ@[Q0L<7R@D\M4N?1KAVrjJi`EeWGhW5TFDbX[xvB`_4NP1IM:PVG`^CcTLl]<]M:ZN>^WHggFch;X_9[^8X^1OU-IP*GK0KR:U`>ZdXv}Ebq?[rJgz]~HexC^vHf^^pw{koiCfpCao?\mBdt?^k\z~=\Z1TR=`b0QQ9XVCd\=\R0NE3OI3PL?\Z7TV7UY4PS>Z\9WV/KL/HN3JQ4KP3HL3KM.FH5PO:VV3LM8QQ@WWATU=NS;MQ)9A,?D.CG-AG,>C-AC,@@:NO;OR1HIAZZZK;WF7RC9SH3MB"=1.J:7UB:[E7Y>1R10S-ZzT2R.DcB4T-<^5:_/`P_L`P=]7ce<$77#45 25)=>.BB 45$>A1II;TP/HA5NDNh_:UL3LG,GA,EA.GC+A87NC+B82IB8OENh[EbR=YJ5OBG_U-D[;@^?RtTY|UNsF9^/)M Ei=<_6 @/M+>_6$JPuA2X!Fk7?c4@d6<`08\,Ci6W}KT|J7\5EjO9($F6#E53TB1QA+K@*HA%B>0NL6TQ%4:'?C1IK/HH.GG*DF5:6;6<"6=28*44=37";@"@B9<7#@@#@>$A>'E@+EB*E@%<9*?>)@>)>>,+00J[UxevYVoNWnQ[rV]rX:M79H5MYIk[aU;X15N0,E*0H/'@#3O/@Z:6P,OkDNnC6W+Kl@Bb7Cb9bX/N";[1FeX,Mh[36T52O3AZ=B_?4M->X:@W@6H4VhQM_D;K.6M-9S/@[5E]:CY66K+1F(@\7A[=7P6:T9:T9C]@3M1;T98U0*B%-F+0I/4M4;T88Q/WqN7R,@X4>S.F\7:N+@T0cwSexV1E.8M30C.+<*"3!>P8SiKG]9`xJ`{BUt6Vs;[wDA])B[.0J#7O-7D]1"* +:R>3M/KhGGfC/N/9S1.F)0H)1&D$/N-!A4S/94N.6N0#= 8:Q14N+:P,9L&(>-E@Z07U#Yx;Gj#&,D'%<.*> !.#84IyEb+F@U"- " +! -L]5?M'>Z&iV"M^1Tg24J%; /DE].YsBQl;Sk;6I!E[3EY36J""8Lg7[uF#<%=, !XqGg3.& + 5NE\.ZkD''<Ga'=V&> +(9 +IU\n+L\PaWg}44J@T J]Qh+Ch6Eao>\x/Yr,La )<_u9Vk1Tg3I`)Me)Xq.Oi Vs$l7akJPj'Lk"Ql$Rh"Sb%Jd>&@!$?4O)0K#:U.[wM:9 (H.)G"'D$/2!+@-'>&12$)<).3G-'9/A&6I(G^4.IMj.7T#=''>?T/;P%I_/%>%? Xt3Eb!Mj%Pn+5R/F ."$%#2(7 %7-B!: Ie(yLYGec/Ko k)Rwv@Sr. &60B\n>AU"Od#bx1`.GYum+YPH1KRq k)Wp^v#^w9MnXy-g?f:f9uR!< 6VkK\rLRi@ZtE/Iz7Q#XqH2 )hO(F8T,)A%* !1*#,#)EX1qQPi0'@"7VkBBZ)7L%=R-G[3[D[N\ &=S.?O,'87NXt2_=;Y'1K&/H%9S0:bOnY,AdzQD_0*G5 +=X-j"> 3L++@N 6F0@1C$:H/=0=&2*;+?*9"0-?K"4A-D#6=-5 2:DX^0DJ7MM9OL=RO:OK2HC+?=+><3FH2FC@VJ;RF:TE>[J>YH>ZGF^IKdLOjN_y]nl]w\WnYYp^[qcyRgaASW!3UigzwXTH`[Jc_6MN9PR;QW0CL+@I+>I0BN(9G3>+?F.@FIZ\DVTOaY]nbeziTj\+C?"9@.CM5IP5JJTkbUn`Oj\7PEH_VKd[Ih]CdX@_WA`Z=\V6QR/GO:P\6MX$[[8TR6OO5JN/FK2LQ9SXB\a6RW7PS,EI.CH.DH(@F(@D,CI-DJ-DJ'?D/IHC^_MfjH`d>SZ-AI$8C&:B'<@7JOG^a0FG-DDF.EJ6NP3MJ.FD-FF";8>WU@XU0FD.BA*=@';9G^S=UG:RCF^M9RCF\Q+?:H^YAWT>TS;QR@UY)@A%>9D^U7SE@]FinVw[nrXv_>ZI:TG0G?-A?-D?Kf\GbTHeU@]KLhWZugHcU,H8B]ISn[Sk]LaUYmc=QH8LAXnaNbVNeVNfWHaT;VH=0HF1JGE[W3IG&?<&A<-GD6NL-GC;UO>WQ2ME&A:'@@&=;$984JHSjd2ID)C<"<65JI@WOF`Q>UH;RK6LJ7KI+?>1HF1JF$@5AaPVwbMnW=]G:YGLh[3MC!:0/I=FbR;ZD8T?3O=3RD$@5(B;/FA@UNH`QEaI.N)}kyveso\cX2R,KlM?]<\|VGg@h`[Roh\V@f6g`]V[Q2V(PtGRuJ;_46Z,:^1Ci5Nu=Hp9IlB6VB5RH>_Q5VG;^MA30(72$>;$>:*DC/GJ)/-7/9$7A5;483716/6/7(0)=C*?B1333,DE)EC4MO+DH#8=06+2+41829,237!9=%=A45"9887-FH)ED,FC/GD2KG"72'""40aqldpcUbPVcMXhMSeI\nSasXhv_n}iP^LP^NZgSp~gYgMQbCctR{i~oueva}gzeuaq_tcxfwep\fQqYuaimXkVp[?]-1L#8$74&53%50);1-?35J=3',@87JF1CA':20@73C7>NA6F9:K;4H86M:=S?0F0=O5G62A64A9&31&21'&'3,$6.+<4,:48H?.;16B7&.(=F@1;2&3)2B30G,+F[yHVuDCc5Kj=OoC6S(5Q&aR'DXtH=@`8Dd?3O1!?"-G-4K2:S6D]@6Q1.K*4R3?`<<]94S-;Y3LjA5T+;X1B\7F_;GaY64P'So?Ea2=X,E`36S%5R'7Q*BW=2K*F`=6R02I-0B-0B/-@,4D1R*Vp>;W!Ut:Ro;5R$C^3;S*BZ.Jf4;T,BZ3BX-S..C'Bx\_~:DeBbXt9_|E,I3SunD^ H`12K$#;.A(.L&5T-5S/HgAF`?HcD">3M03S+Ec<=[3>^4D_76R) , /E 4J"9S&WrBPn4>^3UVx%Ln3SCfk1Tx ,K6R!>'C)G81 416 $= ++4$A$CTs*Gb4TXw,Ba:\ Hh\k.Mo!?FeCa4U{AQuo>b+n6o/EawI4CZ.d|P/E&?X)ayJNh6G_,jQJe6$<2(CVn? !0 %CV!*< (8%56GH[(3J#= +7 %3K=U!*B/FWm>5K!4H#-A"7 #&- &7 #3/AT\q5G\tAj5{>s4h/p:FeRsC`Vr'g=f|. +$> .76*%@[xGXyG; #B ,(B$)  -1(1!."5"4H0+A%"6);$8Mf9Je.=YD_(. &-CCY.8O 7LF_(Xq79VSr*]|18; + #'+4m3bxt%Rh.5Kts&T1KKf`LUo!BZ 0G>TNc$/Ee}'|--Oq,V9X uE\}10Hi67 +Qp9Ml%pASu%7Y ck>6X ;2$8*?:P/Nh@4M#@[.7O( "%:&?[z?h#A0$#23),!!%0,'8 CX3;V)3K$+?^qJF/AM/AN/AM%6@.7,7 0>)8*; 3B1? 4A#9G!7E4B0?3A,9"0%5%7'7'51<,>J0>#3&6+8&8D&7D%8C3=2;"5>#5?"57*>70J=2N?=UEE_LJaOKbNOjPTnT\v]no_wa`wfWk_`ujCUlgwr{QibH[[I]aJ[e[`2NX1M[5Q_6T_?\i=Zh.KY'AR/J\Miw?Zi:Te5OeXVUS0GH+CCA[Z,?G.CH,DC*C@+DG7NOEZZ:NN7LM5KG*?>(>:LfY:TF4K=H`R;RGAVM=QKXni9RL;QO7KN*AC434LFetKgW`}fciRsZSsZKhS?\MAZP8NI8LKB[TB]P]}l=\MA`QPoa1OA4QC1L?Mi[@ZL9OE@TLEZTYM.H>8KE6NH8RN3MJ9SN1LD'D;%C8.L:1M82Q2ZyX=]:@^>5S7'C*8V86T6JiH@]9NR+?A+?A9OM@VT5KI%;8&=:.A@-DXM=SO+B='@3djuoo`eKnszbt[}hBg7<]8QqMjeniUvO<]55X1[~Vg]=a89[6:]6HkC?b8Di@NsI?`3LpBQxDIp8j[=`8,!<7#>8)G=,MC8YN'F>/*81'D?">;67&,)2,706497:::360628#:?24553416*/*. 3:4;%7?+4-7(0,318084:!8<#69,@C2GH/HH%@@ ;:)BB(@< 50Oe\xbmigrlQ\TIUHP^N[jU_mQ_oSaqTjz^k~cng]nXO]Iyn}dew[ylwtckZyh~fpZoZycvbq_pan[q]wbrv`aJmVq]p\UsB>Y/*A"J[L=Q@+B/BXEE[E0C/:L;;L=CTF6F97J;4D61A45B9DPN'33.+"1+.( 2,"3.2@;.:1/922<8>FB2:22?37I4VnLm]RsB4R(.I"Da9>Z2AZ49S-?Y3Ha<]wP.K#QpJCb>!= ,F)0H.0H.0G-0G+3M/-F)*F(@^?2P12P/7R/.I&A`;7P/-F&(@ 5B[=>W74O.6V07T(Zw@Xw9iaE4NUlY.:V+Fa8@[2E_8D`7A]5Jg>;W0%@>V7AZ;/D(B\<9R4@[9O3(?%$;#89P5.F,+C+6L5(?(-?-=O@):)8H76H61G4:P:I[D4I.;O39O33L33L10H/=U:=V;C]@JfFFa@C\:[sM&>%6!7O:3J.>W:C]AGcCLgC5N)=U3Y0So=WuA+F/H"F[6(?,GC^(cEOk22RhNUs9Hg,5QBZ2J:S'Fb80I$:Q01M)>Z61L(>Y8=X73N/%>!&=!'F!?^9:Y3/K$7R*6R(;V-Ii>,IlPe8*AX3>X6)B$2J1<-K/'B&@Z>JdIC\AQkS;T;B]G9 ?\AF`D!;"6N63L2*A&+G(-H'7S2/I'4O'XsKIf:\xL0K.(C%-F&#=.F"6K+'<,?",@'6K'#> +;V0L2OKk%6V 1Q_ 7Z >` LnHjGc@em/Q{B}=(H2PIjaz2G -(06M%G]4kVeOB^-^uCAY&mX7 2G'=K`7K_/hxN2DPb2qMUf0#47G0B7K%< /H!? Wn;(@8P9QMf/%-B+-/2)="2$#2&, +'$: 5N. ;0M=W]z*N{M_{.C_Tn$sJNc!&'8j&Wo4Kd#]w2;U"7F]Sn&;Te~DMd+Tl08R4 7166 8-F!$>&Hoa7 1O(5) + $  1$&8',=)-A&(#9!4%:.FJe2@\"6PF`.,G"=+D;R$; ?U'Ph3Oj(C`j9rK3Q#-0E-0"4&$$5 12780n;:Y/Ng61 :V:[ +=o-/G (>@Q*- /C +Xo(;U@] GeIfIg=Tqe&y7~9YsazKd m&Igz.gAbv.k03JG_\q:0Ie~+:ep/Rtl:4M D[ _uld}1-C AV|8];p$bv%Rsx4Jv9)Egi@3&??VNd#=R +w;CEbau0{Da2dzQ2S1T@c#?clHuIY|(oBl?Mp)_|J0H)/C-8J11D).E%BZ5]vN?X.5,D +C"/K%DIldA$ # $"#+"&4,%AX8D\=OgCBV0?N*r]Y>S +g~3n=7!;/#(7.'%8 /G|aSu:. 9U4%69%7:(<=!48)=B'9A 1;4DO%5? .8-5)2-6$4A,:-:/:$5C$7E3A.;(6*8(6/#4-:(6&4#5B)7$3&3)7C 1>'4->J!4?#4>0: /:+5 19,7-8 /9#1<+>D1DJ':%84#8430+A<2IC2HB2G>3J=7MA:RA>UEH_JIbKRkUikWnYKbPVk]^rgNaY;RL^tqCYXVoeczkZsaPf]I]ZQde@RZ@R_EUcl|v{yrhsorf~cuo~uvprs+D5 675IQ-?J1DL/BH.AF-@F:MR;NT6JQ+@I.@M):J 1A 2@5GQ4FL4HI8JIGWNQaSM\NPd[FZY%:A0>$7F&:C6;%=;=TMCXW:NM;TQ2LI9VR7TQ8QR=U[+AI/EQ#H^o1R_1PYCag4QV*DI'@D4LP-FJ/HM4NR?X]7OW4JU,CM*@K%;F)SU0EF0FF-CA5LFPk]C]PNgY6MB4JC?SL?TMDZR:RL=VR5NL-EE9SPPkdIeWRn`Qn\@^JIjS=\GIdU.H=Sid8OLAVN+C:4REFdW4SH?^T8VO/MF*I?(G=8UK#?40L<>\FGfGGfF,K/.L5%A,3M82M3&A"3O*SpDhSwvʔjY7R*]wRRmO=]DCcO7SC6SD;WN9TO*DA1KG.ID.HE.JG,EB+DA.GC6KJ+@B0HI%>>9SS-EE41.GD0ED)=>9NO,@A 8:$<=(@A%=>+FE0IG$?<";8';=01/DD/DD*>?/DD%:9#76&:9-C:8RDA\N.G>6KG/CA-@?*>=*@=%@7GgXIiX<\I8WG6TF2OD2LC6NH=VNA_Q@_O7VE6QC4PD1K@2+(0I8ecgZmYv_u^v^fvYpVnVxdW{M6V->^9HgEwtBbA9.M1HjK.O4/P4/P1:[98Z5-R,9\7OnDCh;PuCXIdUJmC# /'-.32";77SO(F@Hg_:ZP4SJ50)(+.&,+3/6 8<#<@&CA*CE*=A(>A!:<+CC%==.BC-AI/BJ*4!2<2BP/<)70:-1/5/5/43747)A?/CB*A@.GD+EE1JH*B?,&\mggro,657AA0;9=GCOZObo`fu_oaufwUfxYfy[lbew[arYg{_ofqgxijZf|Tm[s_h~fkkuaubs_q]q\p[jV_{JkUhTeRcQkYPiA,A!ViPysCN?Y_VT\QJVF>H<DPHP\TP]SO\REPD@N=IWE?O:ShMZnO\rQi|YeyRi}ZqcK\@7I0G[CWoTTlNB[=?V<1J//F01H71G:)?22+'94$74/+(;78FD"1/2.1."51 00$33)<9(<62,/($82/(:K>M^O?P>;L;N^NAUB:N;C[E>WB:P;7J7DWD5F4:K76G6;L@4C9=K=IVLANF*:4!/-#31.+'#,#(80ERK?JF)41073/7/,8)"5G`;A\4>^3;Z3B^=G`=' %4&,1G3/E1=S;#:Ea;6R(Dd7Ee;-H+F6Q$Jg5A_,So=Da/>Y1V;E]B6R4;V6C_=B[4@X5/H$/ 0E'3K/;S8/H,=X:IcAJdA9Q1=S6[nSL_DPhK:M2AX:H^BH^B:O60B):H,XgE:T(Ol>;V(h\?[53L'>Y5ZrMKk?Lg@8V+Jj7q\Hf7!<cyL4J,C8Q(/I;W%9Y (H /QIi0\z:mG_+Kf:OkC9UnO1J*1I):S2+E'0D(!8Fb? <)E#5P,+G":V/0O&QpGaLFb12K5N#Ic;VoK7P28Q60L3%?&5/H/SjR.H0)B((F+:X:$A#/L-'@&"=$ 8$0H07&@,G""Aocr]}LYxJHc?#>*B$>W5rdzO=S-0B%!53 ^nAAasGnEVs.6241P2%C1J" & 41N" )67 )D G2SD\$CZ!81Vs3;X @=V8Z4YUy!Cfs8Ts %DB0S`)-H52&'-# !,BV$k;p6c~*PjVH^(Xq=Le0?W$ayIE[,]sGG]47O5L1INf6Ja.@Y%2I+BlQXh9dvD(7GJY*=N5G:K9J;K2E'9 5Le6Tn>@V+1F1F7N%< Nd4K_41G  % %5'7)#*8"( ' Gc'w_z2Nk!Pi".J)EXt(Vs'g8`{5'<-&nJ7OTm+6P +d4n," &4&0A,PcGCZ6(A$:(=6O$Oj7Ie+=YHc-':/A)> +h>c~-E4VMinZ`zx8`!Mj A8z/]xLkCdMTw[z[ER5/E&=V/Ke7\wEcL5; No/pBMo!wVYwC[sN + 0,'23&($2)#6.7P33L'fyX' *={\r&o<~Kax4{+E +%## 0#,Lh4|`[}K")B)9Q8@[67Q$)@h5I9Q"E^,XsAhT1 /'5+8$1?,;%3@&6D"3?"3<+50:-8+5 .9+6!-3BLFX_3GL+?B5IJ$76)=;0DA3HC=SKVmd'A92JC3JC9MDAUKFYOJ_QCZGC[F@XCWoZsuTiWI^MPdXozG\U@ZQe}xc|uNiZ^wd\u``vd1G;D[Oavqq|n~n|a~kb}ed}`f_tl1J62-+>D->H(8G,=G,=E/AG8IQ:NU7IT.@L+\f8XbGcqDbo>]f;Xc@^m=Zj>Ym3Mc-H`,G`7Rj2Mg:Sq8Ro2Kg+Eb.Gm5N~?ZMkSqKmjytxHn>b/Rm"BZ-Jb"@[(Gb)De=]|:^v0Rh6Vf@_i5P\5OY)@M9R[1MP6RVD_d4MR4JS-AM,@L);J(?L#9G!3BDWd:NX8LW4HS.BM1FO6KV1EP0=&:G%7E*1JM,EH@XZ5PP/HL%>B=V[2LN&@A99$??6QS-IK"?B-HK)@F'>G!4B0>1=1EN;QS8QP.CF'0EDD\SFaQD^R@ZO2HA,C<^V.OI/NJ(FC%E?'FB'D?1KG5NK?WU2/63 98%;<':=*A@4MI)D=1M>;XD8U>/L//M13)G03N;2I6F_HXsSyub@yU|YiјfQoTrPBaI7VF3OB3ND8SL6OM(B?1LH;VP2MH2LJ,FB)C?$<<-BB 894LL:QR)C@3LIOheI_]DZY.BC0DE(:?":?)BE1JL"<=!;:%?>#=9'A>35!67-@D)<@&8<03%8<)<'A;9VJ2RB?^M:YJ,J?.MD/JE/HC2MG.M@9ZJPo_?\N,F:4$tyGbHcaoehZradUYKW~GfQzbxahQgUm[m_Gg>ZyW9V7;"6!.K81N8/L7HeP=^D9[:>`@@bA+L,GfCDd?7[/Ai3[Klb,K*,"26.034/.2.!?91MI,JD"B<+EE35(/+2-4&?C7;77!9: 59!7<55134412&9@2CJ%-'0*3 1:2;-41707#6= 9<&>@"<7,%%;34/'?;#=9'C?!749KF}PZWHPRS[^>HJAJJ8B=TcT|xsiwexrcl]i}Zo`j^sgs|whm]ratamX{e~deu[yaxbt_r]dPhQpWjRlUiRm[fSQl@SmCMc>UhJiw`^jZ^h`LTQGPL>J@-;17D;1@92A82?62?4?LAGSBL[HGZARgJZoNezWbsQ`pOfvRWiFZnMShGJaCSlOB]?DaBF_FHaJ6N;9PA&;/"6."60':3%;40D@%73/+"41)=9-@<"510@=&:6"54%86(:6(;5-A6G[P5G5EXEFVDJ[I:K99L69K71H/3L.2J06K78J8.A.4D50A1%6&4D:9J>:G:;H94D5-=2!2-$45"0+*!/=/.;1JXN*4/6@8@JA'+8'-@$_zSMj>Gg=Db?1N10G+3L.*C%*C%":8S3:U4OjHBa97O.*E%6O2+B(+B''>$4I/,@&&="(?%+F+5R6*G,5N3%?"3M0BX=/B(-A);S9#;#07N9@[>'@!&??Y+Qm;gOkRQj:hUqXiKOm.FcPn(Lk#IgNs_$Hk _%x?`!Cy:Fj KZs- Rf-H["[02R(:V0/H$-G"5O(@]2Qm>XvED`1:V.5O(=W3AX66O,6P.;T4:S1Ea?Q<6M8%=&";)";(3 /2 ->/>O@1A11A.@S=6M61H.3E.=P5-E*,E):T7%=D^:0I%*C3M(>X4A\7;T-=W/.G}56M52J48R3?Y:.G(,D'3J/)@%.F+1E,3F.>W9=V6;T3KdD9O0#5!2F^6TpGPkA*G>Y5JfFF_@PhK>[4QmG;@c0Ou>]|E;\v-2H6M.C"9 $@ >.MQp8Ot1a9$> +/H Ic#Gb/(A-G).G'+C&+C%-F)&>!1H,);9N39 :4M05N/2N+2L)8V1)H 2Q"Da49T&Hc8A[2$@!9#:!$=)+D.,D./H010L26O4rtB_@=Z:GdD7Q54"9## /G(C__b9Ww+6V +I6 +  *9#51-'A!:4$Lk,i9sB۔Kd!A]-M:2A^$7U0O -MDd(I3S~=V0"<+G#D3W@,ONsBebA{>8R@^Dg 2T/P ]})3Q/N%H@f a+* 11" +4K\t3Ro!?[[w-i1Xs(B^Mg)Ke }MvOJ_,*@.C>U*.D-E 3FWo4Nd2y^Wj4):'=Q4J^A/FVq>_}AWv5_|:;ZlGO8Y/6T _{I&+'$#'4%$=(A$9R3*5D)K_+^PZp+Nh d~:g2$! +*&)94,?,D^4cL'H' 0'?,~g}U3Yl>Of6^wA9R$:5J-34J%>Zs9Kd/@Y"Tp1QPo 8V +~Na(`Mm iAzUvT}]yY~ft]xf{hzuw[s^_tb[t`JaNYoZlmaxeQgY3*" +/!1;/8%2< 09%7C.;"/+<+7H&3C/;,9#5D#4H,?#2&."36,./7,8"1>-:,7,:,8$2?(5B->J&7A6GR/8-5.7*7".:&3>#1;(5?&6>#6=I[b1GI2GG3JGBXTAYSA\P:TGB^O1J?5L@O'BQ,ES#9J$=J&DH7SV=VZ'?E'=F!4A#5C$6F&Q]1EP4HS2GQ,?K-@O6IX!2B/?"3B$4D0CQ.@L&:@+?E-CH(=B#:?*@F#8@,4#;A7PT6PS.KM0LM'CE&>B!:>$:C#8E#6E.= 2@.BK4IM3IK.CF$8=':@(:A3GKKb_Zpn8PFHcU?YMAXP;RL:NL:NK7KILa[B\Y9RO:TQ7QN/JD@[RFbT8TH?[P;VL>ZQ?ZRPie9RO+?DCWY7NL:TQ:UQ1PJ7VQ.MG)HB'FA*JE-MF/KH.GDF_[F^XWmdLdYB]P;UJ;VL2JE+D?1JF>WW:RS(BA'A@'A@0JK*AA)AA7QN'B=*F:2M>3O94Q7&D';Z^C8R]F3RC/JA$?8-GE,FE*DB5NK:UQ0KE*EB1JG-EE'<<$9:87:RR1HH,DC/GD=TR.DA.AA2EG+=?'9>$9=#:='@@ 88 :9(BB)D@/HD34!58#6:#6:&9=/3 36//4JCKc[E_SLiY9UH3LD5NM6PJ?]RAaRBdSEcT,[w_~ri]gZ[SHnBGm@OtHUyKGl9ta`LzflWbNqaQpFsm!?>ZAE_N0J=$?/6$4R 5<"8=//&<9ZrhHaSbzkYnc;RH 8/+B3?<1A<0>99E=JWNO[OMYLSbOM^ESfI`tTl}ZjxWboOfzTVkDTkEc|VHcAB_@6U7GdJ]ybEaK'>.*@5"8.#61&82%92!7/1E@$86 31%:8+>92F@,?9&6/$6030 31#60+?6.C69O=CTAGXF?P>;M8:L55I-5M,Ld?Rn?%@:Q67L82D24G68J;5F:/?32C69F86D0)<#':# 5&/)&0,3@7'5'0;/5A8(2+9B9.7/'1*5D3j_Fa72Q#'F+I'B_AGaB0J-/H*AZ=QkN:U61O0*G&:X3:X72P1>X=-H.-D-4L2-E+6M3#; )B%/L/,J,; %@(&A&'> DXT>IbH4K11F'+DLg7RoY61J+;T4*D#8>[0_yM=W,:X,5P)3M(7O/?W::P36O15N00F2Nd%iv/7O +7O8T+G +%@.KHe06R!1M3Q1OBc7U+L:`xD.35A`~QUVy#[%OtFkOsZzm(YvUto9'J?]/N ;,J -MBf|ET+ *(;*UhSXn?9RHc)A_2L 7S ^{85OIcOj!+;/ 32 I_3ctH0B)B Qg3CY.Kc3Ph9,D>S(@U-3D>O)Lc<@U.,B?V.e}OSk7Tk4)C +Se2I\+GZ)1EYm9at?dvB9K3E/ . '" ' % ( ,=/E3 / %  +% " 27K )B *D =U4J4-@I\)Pi/Vn/Nh b}1SjvEtCg6Zq)MdVH`Wo(oAV 6Oc(=N ;LhzE\p.[r$#=588!9, -E!.H; &C'CSpA.H 2K,7"+&3'$'-6J)6L$:Q'Ne=1H4MHb#Qm+Zx49T=ZWfI/(>)=$66J6O i6c7EZ2,'@ % )'*$80GmD#Am5[~!T=Z )Em.o)y7'B +"@Y(*C,Dk0Toj51O)CB\{=ea~$/JJb!AX u.1lDGi2oN-*=-Tf"v,HJe$0D Jadz9Tj E4~18QsV83J/9@[)Xv;A_!nH=Z~X4Q % /-*>*(<$1G&F_2nPoNNm(Uu3<g=Mn+;%CpUbzQ+'# xs=R8&=(>TA/D0 4pT';oFVTm)}V 5-B?-/ $'-%fycC]9g`  +'3G9H_F%<OdB]pLnVH_/lN1&5G0`rN^tC`wCAX Oi2Ni5Rn3wP[Xv+9X |CQu~Mc8tN__zGfRhUgTazPfSkXcySavQ^tS]uS]wVb}]kglhrnsosnXsUppf~q=PM%99%7< 39!4:!5<#5>'2"/(4$1(4'2.>&8)<(28KL\pi2E@(,-7,9*5'2'5%2@3AO'5A)7B%3<)8A/7"4<&.+4!.7%4<,3&5<.6"18,@?1HEH_W>WK7RC3N?7N?9O@>RAVgTXkU_u\XpTSlPjff}bVlTTgSUiXQfWXl`PfYOk\Pk\dn_{db{_|vlfqg{qm]v__xgJbTG^RVkb_uldtTpb[wehn_v_XpSWpSwovyim#83'9?'8A*9D.6%8>,@G0AL*;I%5E/B&4J#/H /@,?FWkm3CDEURXg^]ka_qfJ`Te}sWml&.*;.?1ER)BI'0INFcd5TT'EG">C*GN-IR?Ze2LW0LV2MU[y0Nk+Jd#?U3I3E0B(?Q.ES&BI.GN6OV&-<.@O/AM%6@,>G,?E/BH:LR.BJ#4@2=4>%=C7RVC_b4OR%>BC[_K/:5IQ-BF*?B%9>':@)<@?RV0EC0GB3JC;SJ7OGD[U0D@;NM3GE>QO>SP=TQ6PL5OL7QN2MH5PIHc[5PJC^W?ZSNidMgd7ON/CE!49':@.DG9RR0LI-LG/NI)IC$B<2QL4TO)FC)CB6NNF[Z>UOH`WNh\@ZP4MD2JE*BB-BC)=?46+DE/GG*ED%==(??0FD7MJ8KI/CB+=@"48,@@.BE+AA"<8#=<(CA/GF77!69#8;!37/3,0''*?>F[X7NF9SJ0KB>ZN;WJ2LC5IG4II,BA3541#?8A\S5PG,GA2LH/HD(A?,A?.GA,J?=_MJkX.M:9V>cXp`Z{MUxPUwSInKQvMHmFNqHWxJiWq\u^t^p\UuD^}Pui:X4%B&&C/9'.I90N<6S=;\?=*I'XxX=Z>9U=+J/=_=MqAXFKp8<].2Q//".'.,02*,,-/3$;>*GE,DG06.3(?E-34849(*"26.125#%+/&(02.1-3-5-6091;$8?28+/13///'7OB%<,=TCIaN>UDKbS^ui4DBHVT:DF=GK7BCUc__pfhzirjm`xhpbe|WgZpbl`xkzld|UdyPiVnZzgmjnZqZq[oXsZkTdNfQ`MlWlVt^jTlU_~I`}KTmERiGBS;(:#2@/1?11>53@<>HI3=?0<<-89-<:4C?/>:?MFFSJANA@N@KZGUeKTdGn__pM[kIdvRZrKbyRfYLkBCd>CbAVwXDfK=ZB+H1)A2'=1*=70C<,?:-B=$73.,1/#87100C>%921C:5G<$6-&81/@:->7,>4$8(9J:5C3FTC4D17G.OcA_uJ'B6Qe^zF6P)2.<0FQF2=4/<01NfBB`3PoDLmC>]73Q1 =5R3IfJ3L2)C+'@* 9!%=%'?&&=&"92O14R4*J+=!&C*+G./F+1E+6J-*>#0I,%= /F-3J01H. 8$AY2Oj:Pm;ZsF8P(:T/C_1,HC_%;W@\"`x:\x6=[Rr!IfEbIiMpSw`Ms_:k)_~fx+m!v1MiQi00F 2J*0K)Hd?Gc>:T/H_?!9F]@'A/K#Id9Nh=Ec1P;0B/#2"/'?*8O;*@-!7"%7%2C15E75H6,A-;N91E03E.,B*+D,-D2=U;=X,cՐRo&h:vH`+DRm%~Hq9Kf!/HrjY5?V<0H2(@-6L72E/7J4,?->U:UnN5O,`yTXnK3L$,DPm?Da6;V.]vPGa:9S/6O0.H,AW:,C(%>thcMUt/_"-@E5XDDcKc6T8Tsyac~50KIhAC`<3Q12O2;U89R4E^?8P/8Q/2J+!9+E'X<@Z= :WoFq,F!*DUrHLn<:]*Gh5`NnhULh>Ed;Ee8A_,Xu>ʛOc#8&;4/ $;0 '+L^X2D !2K: 2'C.I!= #?&A2O5R8q=e12! b?*J<c5Ux'6X?b -ROr:5r%6To9{;z<6*F 3KRk*{S>Q+% 6K% 0$4"2+:&8 ++0A'% + )- 2 5I^9Jb5,>)5+:%.>HZ400G=T9SYo8@S*.B*> 4HIc"n>Vn#Zs%`v-Ja^w(_x)NgNrAMc!l>!,@ \o0$@Q\q4cw1l80H") ;0 0 .G"8Q(8R">Z&-I(C ;85P&;X3!)*8*!-!#''<6 8 !*02F?V^y79h0j3HcA]~:Zg"C`?X)ij75O Og)`{3?Z +$A 7S +Ok+Ifd,t?G`I`CY\t(&At/P?Mh ^v:R6D0E#6KJb +p#v$wp#Hz:Fej/]w5(>tUWv5;\Pr75U7Vh/LNj48P('( &!( 2 +(=*I_H,B&:P*Pd4Zp51E +kIdbsb&( &%!% (-':)L^N-,CK_=;O*Pg8>V'xMd7-SfNH\7Nb0f~IJa244M9S!Hb):W|MtANl>24&;<646PM+IE)C=;TI9TD! EbEOoNFiFYzWHgH7V7,J*g]Op=cJQr:Hl2iPpb~OdZOnL>_G8SE:TL3QK2OK(BB78!;57/DH'>77$<=":-F=.I@+E?1LE2LC8SG8RJ2JI-EE5JI.FG&>=:SOId]1LD.IB3OJ*B?!86)?<4OEFaT<]GAaK3T=Zz\UwVOqITwNBc@LmOIjO=^APtPLnKJiGNmDJg8Pn;mVt_QsB^~M|kvduiIiD;!$B./J90L8.L4HkKJjGPnM-J,(A'B^G7U<<^>PsG\O;`,TsJmmQnY9- 8722-)85251488*DC)CE+EG7913":;+,,(&:4+@;8LG%<66/+>=! ,, 4347+?D 39%9=+?@%861*"8-@WHPhSAWBF[H?TB8M?OeXbwi@SHWg\5E=AQJ9JE;LGPaZx~~rqlfh^j[l]j[fXrercsf|nrdo`o\mv|kno\p\t^xdnXmWeL_{HfQv`o\eQhTb~NgS_~OXsJufXiO.<+DRCFSH1>7$0-CON1?@)681<<1>>4C@):43D:8I>K[OQ`TM]JQaLYhN[kONaAQhBMd>UnF^zN_{PTsGLlBOoHOpMDdD:Y;3O8*G0/F7.B80C>3FB+=:#:4$64&97$66'9:-++&2C;HWL;K?2B74E;9K@6G=1A73C7?PCERD7F4CS=7H(5Lm~W+L^*nG)G.G3M!\uH(=4K%BW52E+*< Qg<}\oMbI6M(!3".!"3',>,*>$4E*4E01@0&8"QhFb~RIiGd0yW`~4e/~Fnm6Um!MbrgdƑ-E2I,*B-/F3)?)-A)9K1CX:DY:7M+=T3+B F^:%?>Z/Oo>Gf:>Y1D_6Id:maF_?7P4TnV3&?4K(.F&!7,C'-E,0I.1K/*E('@"0I,C\@ ; ' rr-I+=W:!<69N9@S@1D13J5XrYUsV>[B.J15Q8 9"$?%tmKf=2K*$>Eb8cV+LHh8Fg5Da/Li9C`1Dd4Qr@kSEb.Wo?"8 Re7Sj7Vr9+H$B,I03"D]+Dl `+xP'J &gL/9R,C';* %Ti]Xj7X0";3 &?%@4O">Y**E,H!?6^O*Kmxrh> >9Y.M +QPs(e1p7Vzc@%D*Io.~;Yu&: =$A=^]"|5DQ #2"91E()= +"4L5M.J)F&<1H(4K&;Qf(nBUl4  p4LJb*d}DpQYr@CX+Oe5[rDSc:CU.7H%6F)V'jS9P!$82C+9/?*;()% +3A*'7 " +)'8!7/&<8R!9S&8L'`qLH[0qT=P$+=2J2L(E Xs:Kf.Ri0"8Xj1#4!7*>]w5l>tAJbsAZq#Iw=Zre}-xE>P(8!1Xl/|L\l*2E Ui-_v2Tl!(?-2!;)B%>$>7RHa(6PNk,Xs6Fb)9 5R&)'%4%$#2 2C-5I+( +1$9.B.D:QIb#Pj*E^':U'DKk(pL5T"; :O'J`/tPJj"Z}/>6X ^G`u1d:}%BEKjr7m)A_-F;R1Et/$4%E@'6NmN5M +G^.Aar"AWIz*^6CZ 9OOd#q3Rk +iG"?#Ph?, 8J*VgCEX)AVKd']~KwB7U =]dpO #6%87J)7L!2K]{@>^%E 3T!&E5RNl2iIVq=0F%; ,.;57D<+#6"2J-3J) 7:Q$@U!r9L dy.5Ps">+E+#:' +   )#" %%"-0!-)(XnL8N%Lb;Vl>Nf3h~P,BAW.(;)%Xk;Xn9hTXpD5O:W'?[*Ca rC](Z{'Nn%+ILl(xQmIe}He}LjSkVlYq]h~W[nNXkM\pNRkHOhFNiFPjKJdDKcDF^CE_DGaED_A?\?B$38+;<:JJ2B>=PG5F=8KA@UD@UDC[GTlUXqV_x[ZsTWqPUnI]uS^tW^qWWlUYpZRjUUnXlpWp]OiUNiOZvWsc|d}ckW^yKfTlb]{]QoVXrbIbVSm`\wjRo^HbOrsomNgJjan{ft\wi`|`.$0/':A$9A$8?+?F->F,S?La$4G8IQcqs\kg1@9DTHbrcM`OF\GUnSqo;S@8LHLZ`'4>/:%5D+9#/,?GBZ\5LN)AD4JO2IK-DK8B 8F3KYC]k^5U|3Ps0Kh:Rr5Ll7No(?_8O#;U1M!6P!4O0D*;4JX;Q_)?J,?I1CL9KU2GR%8F%8J!6M.CU6IX6IW*=K.?Q 0F,C&8N'6N(4P&2K,B5EW/AP':H%8C,?H)=D59.FJ,DJ*AI 4@"3B&;F'AF5OS$>AE^b3GN/CJ,EH$:C*=J%7E&8E**@F3KOCXZQN:MIAVR2FD2FE,@A/CE3IJ4LM9RR3ML-EDF^_.EF-HD@ZY+ED)DE 7:26(:>3FJ<)B?*FB1KE.G=;VG-H5B^EQpSDfGIkJWuT6R6XtWLj8WwBCc0Cf/Jt3}gb~Q[zUXv[@`L5OD0JF&B>(EA'CB%>@":=#:<00$::/EE!68&9=*=D"8=#8<";<1IH%=?0HH7LI2HE0DB)=:):;(9;2DF1EF(=>'@@(AC#WN;XG>^G?_F[|aabTwWBeFEgM?aKDgP=]D4U99[@IiOKhMVpPa{TfVp]sb[xNlZq^dPwevPrQ;\D6RA3Q:2S98Y:LkISpO2-F+=YB>]C< ?b;=b3UxLEdB527,3122F_[83 # 1241(A>:72064202KH-FA$;.9OC8PC :-*G9!4H=%90-C<& & (:6&5/6G>=TC=U?J`ERiLby]VmW3I9DYN6LA3G=5C:1?5"/&9H>YlbilUoWeameh\pbqcsehWq_sdwhjZfWp_p|inm[l[bSo_jYlYnZgRa}LfRkVkUiT^xKeWjZa|R\wMNj@c{WYkPNZE6D6/<12>78DC,87*98;JF0?>.;;'74.?:.A8-B5CXIShW\q]fzabv]RcJK]@TfG]uM`}QZyKgS^{LNnBMoCUuO\|X^}[OmL3O89R=0F7+>5&95&86#844/ 42///1'))($40V.CW:?T4k[Ji7>_.9Z/GgEQnQ&C'&>'(A+(=*"4"(-*&6(!8$$9%0 *++ 31-.(@&3M2%@$":"&>)0..-9Q6D]@.J+2M05&>'9AV4YrG;Y%Jf5-H .E'+C"+CAZ,9S%E^0Pk63PMk!QuJ]y!";^{1u6u*CCu)v-38p,}>Y|f&o.w;d56P.G,H=Z/>X/.C/E 8P)D_0=Y"sTSp2pRDb((A*?3H))=!,C&)B$8N7*@*.C.#7%-B20B57G>"5E96F8$;(/A/5E41>.9F9*9,(*'=+%=*#:((?-8M<5F5*@,#9$=T=&9.?'&/D'?X7.IZvI?\01Mi5nJpB\t&r6Da{#v7~9Zt^vRgoF(?)A IaK)B-*A(6I/,=#Kc7 78N'FY8FY;BZ7[vOPnCRsCcV\wPB_6WrGPkDMcF;S7TnPHa@:R)2M9U#Ie)Uu$[rOl72IZFu Nj H'HJ 6W'$A,I =Y59Q19R58O/8L,9P,?Y0D[81G^A(?&'>&)D).H."<8N4$;"+;Y;ed+H(kbPnJMhI.C+/A.-.C01J14&@(0*. &,8Q+B]0.F3 #?<\44W+/N"Eb2Hg4B_0A^,Y{FY{EUs@Ok:BY(pXeFZ.[uA5RBb.%FIk-8V_4PwNu/Z]87\$"A c~G2Mj1 + +& +o@Whh^(/ +!:3 5Q$ +"#>['Pk/!?)Ry']1Ff.N0=YD_ .K"? + BHj1>a!/Q +AbQs]y*7-M/Py;q4{L)G >`^4;_2WPr jG)->$%:"#63':8M$Ja1/G>V%3,Ng%;QOe(L`+9O [u8ue1 Kb3G`0=X%=X'Sk=Tl;6LZp?d{L>S(HY3%6=L02H!=R-DX.0E7M 0E,B/?%8_qKiUQj:,DSk6Sf6L^0!9qKa49P"Lc4%6 %4 +(5#3$ #2%5(9 $$8.(( %: jS$:4">5 3J?ULb&DX$4H7U <[(2P83^Fe!Mm)On1Nm1]{B8Z(F`;"6  "!#18K1PgJ& +*?!'=2I .FHa'@_Ws3#< 1eFSt,=]Fe*'BB^%Kj*Ig)Km(W{+BIj; ',+);'(:#;O,/C\&2O#> -J +Cdp9Jh %0J?X (\o&)g!h%Os}M&F8SG_\q2.C}={,-{ 1)No[DZ>S AX Vn a{v)jw&u(u%z&l:]Hd ;TVn,CZp&S2Zwd +20?ZJOiqU_y'"=q7{8CyG-E Of++D?Z\y@n%M6  3EX4lUWo5Qf,Oc+CY`w1}HYu"FbRnzC7L2 *ZpH\vEHc/Kh.Gd*C^)5Ok:0 +.G"4*<"O^D  ).@4-*VkMqhLwNe)Tj,aT%@ *@+4 *>'OdBZsM)A+D&)!3/ + *).<.xmH^1\t>Ul7'@va+CB\5?W5=P8>R85I)>P)AX(d~ITo<5Q!Mh5fB\z4xMk?>^Lk'Zz2mCiBiJb{Df~J_wCaxEZn@_tG\pGezObyMiTaxM[sI]uKZpGZpGYoJKfDKeCD^B]:IeFLgKB]BVqTUnP[sYBZB;S=1J49R=6N@.F:*?5!5))$4H4UkP`xWB[=. ")" +(.)+!--%& .,*:8!3./*1,-&"2+#3.+,APR3BD#&+* .(,BTAI\HMfA\sSC[;LdGIcEB[?5N2/G+WkPI\8Qc[H6QD5OC@YLIaTSjXVnXUlSXsV~wdZsfyiyu1F@0CE6JN6IQ3HS'9H%5D(7H&4H 0@L]fsm~sqog}a`wZh`{pnbqeg`heAUG,(8JI8JJ4FH9JK;NI8NC%92,-01"84)+#,"/0;3=5?/8+:+;*=N.@R&9K)`-A^";T"9T,A\!7R*D!3M);Q=Nb2BS2CR"1ABT`7JX.@P,=O.@R2I'8L2CU%5H+=O';L#5F#4H0D#2J$2L ,F'@#90=O/.EN0GM0FN0DL*?G&;F*8,=M%8F$5@/BI3EJAUV@TS.EB.CB5JH=RP>STI\]:MMBWS7LEEZS2FD5IH/CD-EB8QO>YV=ZX7RP1HK8LP8KP/GG;TV6NP$>?0JI5MN-CA7PMMgf:UT.HI-EI(?C+@G"5=!4;+=F.8,7*5$,;RRKe`0KD6QL5PM4NN1JK8:&=@4IL0EG2HH4HI2FF+>?1-(A>@\U,G@1KG,E<3MA:WEIiR9[C*J19YA(H4FcKUsU@_;Hi=QuDX|IJn8Dj3VvG]}WHgHCaJ@]M0LD.HF8US1NL(DF(CE/IM(@E"6:/CG*=>!56.AE'=B(=B$:>$<=-EG 9;22*>=(?<0EE+?A*=A*=@(<=)@B&@C$=A&=D 4<*0077KQ#8>#89?XT=ZQ5PF9UK+F?6PJ.FB%97!714LB;WE:ZC:\CPqV^bRxXPuV<^@Rt\AaK?`HFfQ?_J;XB1Up`OlV = ?\:QoJRpN9<CaG.P67Y84S-Ef@6U6%@("=/3-!65";9/LCKh^&C<'#.+'% +#(@;5,9+1#>\K1&2C50aw_upjd@Z8\uQrcq^jXa|OgWfVl\qaq^dO`zLwdzhksXjUbQdUi\rehZ_|Rk]rbm[r_q^s_lYcO`{Qm`^zRTmH`xUmahw]fr`6C;1>94@A-;=-:<,;='76&67#65!62 4.'=10@7CUJ8J>4F:,>+EY@BY;SmK\ySUtIYzKZ|JYxFTvDOpB[|KRsCFb8FaB@XAKbLAYG*?2#8+1'2) 4.+>:**''$''(**+;5ARG&8,0B3(7)(8,9E<)6.3A81A56G9,=,/A,4C$+B`Xw&f)y7i&Cr<<^ Vd8[{1kBVv5Jh*_~BNk+Nk%Kk!Tt)Sq14L" (>[3Ii9oWsWs^hX^zNIe9SsG=\30Q+/L+*H)(C&3J/*C'6N68N:+ 2(/!.6$0"/,!&9$0E1.,071%=",B,-, 1*#<C]9-G%2L+7N3-@)2M#Jf8Fb4Nk8Xu1:WUNl J|6]|y1s$z(3y.l#m+t7`+[|'Ce5S]})k1P\Gd#>[$:U%gN6P2I4N]zD[x>bCe@{[y\Id08M$7L(0H(1J-)D'6)@(5L90H7/D2,A26J=0C94H7/C2*=+%8%%6$6C5- !  "4 0E:/#-"4#"4'&9)*A-.F15L7VjN3I+B=T(#<0G#%<"9[r=h~9Rj`w)i2~Ey=@cb}r=Vp*Xr6Nk53:R76J/YqAZp0‡Wh=-@ U52I++F!9Q#0D$7*;RGb[yF-@+,Phjn`y^u\Dpm1&?E`"4Q "@+H-4O3!;,+@7Q&MdE#=#(A&-G+,D.%=&$:50. +" +9Q7XuT0N*GgAKlE@]<$%$=%%?$5O<('+7d^.K#C\6@\/r_Gg6Mm<6W&Ea1Lg4Kh8Ca/:Y&Ba/Eb1Me:oCT.DR62 $)FPp0/P 3T8WHoa2U{%Fm=V|8=b 0UYx;/M&>Pe7/H +6;W,J4SYs{`BQ!6,D2./0I"?>Y')FNj.`BSr21N/M 4R"? 3Mg56 94 #@6 *0Mf8c==.J 3O-K^|/62N.L&FFkY~ `70 )), 41I:S!2M-5jB=T>RI^'\t8Tl-Oe.Tl7CY)]sE?V(4I1FI^:[l@5K 6K=R)K_9AU1,=;L+9K%=R,;Q';T$ZtCd|InUH\.9J'CS-l~T\qACW%@T}\Wk8mKTi:#6 2F>M!&9 "2%3E-':VgF( +* + +,*9'7- 8O)Xp Sh,bx5Wo)J`h~5_u,Ti%k@1D FZk7ay&q.v6dv%hy2Uc"o}>Zi'4EKZ]k39IKY!CR2'?8R3"<B_6=Z+Jf4Mk3Lm.#DKj.,K4SEg+-O+G+  +$4&*+30"6':6K&9O"1J[x4Zw3=Z<=]Z}0Ss->Z Gb/Xt?Qm4Qo67UWx,k@^6-M9. 0%8 62.DDZ)=W!?qS3P[lPlIsg-w$; +)@(AUnAJKy4DCg @;WOg))?;Ray/p|"[{ L$(s%W4]7g^s t#5P1g ++`2/L >V=RRi"k)~.)y(Vu c)*dMi,[v"UC` 4QYv .Ki/m83K9Oay?wFOl?=Ao7!36I.TjB9R!Je*Nf0]r:8MG]TqTQ/O_?M[=kzW]qKQh]zKTsKDbDC^F3M72J3;R;@X?;S:XrU`y]vrj`aWlbaRHg9YxSmq+B5/B@7KL4IO>R[';C$7C,>J&7F!/?(1O^[I[K]rUe}Xpa\wKxxdjU{s|wm|Tbc@QRPcf]rrf{zlmasxeycwUhsFXdDVaCVa=S_8K^&:O&9S/I"5O-E)A(B$6S+>_8Kq9Lu%;ePeUkH_axXoIb]vWpE_Jd7Qv7Ps4T5S,E&:S"4L"4M,AY,C,D'?'?,D(>-C$5F$4D*:I*9I$8F)'8J/AR0B.>!3C+;H3EL7IM;OP3HI7LL2KH6NK2KJAXX7JK3GF?TO8MHLa[;OM5IH1FG@ZV?YU<[W/LK,HI+BE 39%8>+@C%@";8MqCUxNKlL@`E@]H6RD2LF1LH+HE-JH;:$@B#=A 6;';A#8;)<> 37#7=!6:38*@D*AD!:=26+.!683HI8LM,@B7JO(;?#8=+BF$5*F7mvUv^FhNOqVBgIAfI=_D:[ABaG;[C3S=BbN<\H8TA6Q@=TC6O9IbERmKmdFb/8K7lj{tqf`{SXrIlXwep[g|OnXlvgwgzg{hr[oy{ekVkUjPfO{igY^yR]yQfYk_l_cRcRn[jVvbo[r^^|N^yQ_{XOhHYoSfw_eq_XbV-85.87?'57%36+;:$33"65-@>"616F@Zjb&0@81B8CYCYqUToN[xSYyQ]SUyG^MZ{KQqE]}PaRa~RFa=1I/*?.(?/+A4$9/3**@9'<5#639KK-.()#"#.,0A:9K@$7*6H<*9.-:02?5>JA5D8@R@7I53F+DX4`uE+Dq?h0Sv^"q6c*v@tCi9Tt'Vv)[}.a8]}5Rq-Ts/lFTs.Ts.Yw5Lg5Ig5UrCVxDKm3B`&?Z)4O$Db3Eb6@a78V1+I(%B%)H(8R7$; 4L02J0/G18O>&;.);-0 8$5#3!4!3I31G/2J3+C+37O7>VA&>%0H03&..:S61L(YuKNjA1K&%;0F,*B!*BMh;Jg6Ea0[vH[wK#;7c|U:V-5S%;X+(Cbw>p,m(q+o'@r)o$cUv +u.TtOp1QXx+Z|0Il!5SWu+Ss"<]Vu&Sp,/K?\#xZ]z@1KoMk2If*If)b~>Qr/Lj-Ur9gO5N#BY61K'A[:e\0I./G2(=0+@1#8&&;//$!8',A/'8(&:% 2!2C2$2%DP@5I2&<(!7%6H2-<+'9*"7"#<&,'>Kb-cz5wD[t._Yt-2 +2 '. JaH_^s*m?O$9Tbs>l=b|75 0 +kKg'V|Z &7"!7!#;"ZvQ/J UqJFa=>V8Ea30M?Z*Hg5Kh69W#Pk;$9+; 3 @Z'Cb&qMnJ(H Aa U{/0VGhCd='J 9]2Z&D + (7}OjQ^XOt@g*?":482 >V,#=*G>[(2$E)E9T3N ="@+G5 +5 +, ;"?; #B 7]#9Tn.a|0Pl!5.!8+G-L @`Swp(B%  +* +%@5Fd,1`~<7TQl)F^8L Um,gA:U=SYp;BX'?T'3J*<!23C"Qd:J\4@R,F[6?T17G&3D$(9L]8AV/.D-InVE\*mR-E=N*CU/0BI\+G[)^q?`tAYn9Rc2>O"#4 +0@{[`u@5JdxEtS/ctK\lF+HW9jz[ /?%YpAXp9.G[t=Jb.Wq9uMXl.|R5D.,D5(C#?4T;RKcwFk:lw>yU8;Q7*2(&91F)A Rl.a~8HfGi:\ Gh[z?/J'?&=*E-G-ICakA_4Aa9T!; 3MOj:>W%?W?Y[t;e@Hf% $,I(C^y"d' #_:U(B/F-DC[#[5g/l-W}#F)H 4OSi-C[ r2DRma +nNo]y.I8Qy*Ib;/6Zwzx%1Q(Cg6Yu2=Y)B 6L|OK`}#Rm>YCgj0Gs&QkKdl/|:w5l-k'682J4K8W_|#n(Q 5IOe)h~Yp't?Tm Hc{LqI^y6Gd#6U_Kj'mIHd,3N&('1(1?2&6#N`I1D,/A/$8+-%6K8NcAVo:pKSk/G_"E_oqL!; 7!AUF/#<9U&xgVrI3CXE)=-!/B)46P$Mg:jSfFVs5bAUu951!(9)!4Le@-HcL2O?[*Mh8.#; g>tElA]yI}JVYRUYVYZ]^Y_zS[^dY}Z``gejwo{sy_{cwbwcvc^}KePfPfOoWqWtVrSdvWg}^xSbf|xxyrz{gp{kr{rr~ivy|rl{atVb^|\lRgTucr_`|KOk8^zHHd6Kf8Mh5Yu@lNtSlJhHfFJg+`{EsWm@V)9M*:K..="'8lWublɗlbhcyma[qJTkB1G!H`VCh~swcyjXobu~i{gxfygxnu{op`undvbu\tbyjh}tnaw\qaylf|UkHZkPes0CQ=O]HZhASa8JX/?N5EU+:J*6G1BQ$6D/>N$4A$4A);G"6B-<+<#3!22".?%4E-A#.D(5K$1K(1N &E#'H!%E!(D19T8A[9DY)9I1@P4FT0@N->M"/A/;N%5J'7K$3H.C/B,?2A'9H(;H*@ZU@[V/LI/KJ&AC'=C2=*EB@]X8SN!.=*7.8+7 0>.6297KM9OM@XS4QL0MJ,HF)BB(AC79!6<5:56.BE':>34#<;*D@(D>)E=(E>'A:*F:0M?%F5"A1%@14&<+7T>[y`GfF8U4B`='CD&>C5928);B2EH!48).3:07/CI"7=4:28)/05,?C;6725&:>3KKD_]0NL%AC 8=01&@A'A?.IE7SM2MF1MC2MB&<7(@5!=,`H2S;7X@5U>Ww_IgP2S>2RIjD=_:*I(7U9*H01O59W8DbDPnUMiS*J2CaP:VG.M;KmRGlN@bI?`IB_JEbLC`J=\BLkOZ|[[~[a\]}Sj`]P_RfUm]o^o\qXoYv\t^q[hly|qmj[d{Ui~^XjNeu[j|bxi^_xO`xMmZlw\jQt\lwbq^|lp`rYu\|d{bhOfMu[mTs\vagWcWeZg[fZ`|ShXlYgTmYePfRa~L]zK]}PUrKMgILdLM`M>NAhrl9B>0993>>0:;%1/#23)98!02#34/.#43-=:0@::K@1%&;,CZFeaTqN^~VQrHUyLZPRwDNt@RoF_{Oa{TWpJ_yVLfJ6N6*C1#9+!7,$919NH&;5)<8'::(7:))!/0*-*)(86%6-(=0@UD:JA->41=32@5;I;2@0/A)*?!4G"Uk;~VNxAVy\%GlNtMqJkNm"Sp&o@m>Zw-f9d:EdOn)_9Jk#Ml)Ml+Ml/Rp7XyE7X!/Q9V!Mi[@$>'6"4 0F01I17O5?X>(@'+C,>V@'?'"9.E,+A)(!:7P.@\3^|JQn>!<9T+Ia:?X1*Ea}LOj8Da-WsA^zJ>Z*6O MhKj'Vw/Da=[d:YB_%Rs/jI;VHd*Sq9Yx=0NgDkFJh)Cb'mTXtB;U(*F=V.Rk@#;4J5$8(&<-*<.->42D9(>/*?02C20$'8*,8,SaT6H4%= 3J-+C#)=+1A17H5#=q)A_t7@YM9s@_5^xR>Z6V7?W8=U44L,C`,i5Mz!SL[e,Vvl'Gv,Xs`{r+Fo8WrVoz33L}-HBV1:(E1orMWq911C-8O83L0%?!%@'!8$#9%%>&$<$$<&WoT+D%4P)6V,NnDDc>GeDNgJazbc{d8O8/L/:W:RqU7 4!1I7?XCZvZ@`9?Y2Pl=Gi5>^,Ih;7W-.M0M4PrB/N 1#=^y/KfJf#`z=CZ#E[)AW+7L&L]7>O+1BFW3.=,@>O*3D$50A 3D'5F'?N-BW34'A9 +?V&Ld2Rh6?O(K]26H@W&cvCexFG[(H])duI'8 ;L!EU(CW"Uj1Lb(lHYl9Ma1L]2kW YmFDS.;L+Oc@'=/?Z$Ok0bCPa# +(.%= 8:'D'D6T5&AT W)@@X{Ngb{=HeHc|Vo.;w Ut//G+A +&fGc$B?[ \y&}-O(v=:m,w?Pl|3Yt e~\b5.ovJ4l)X0.H&E`7Nk4_@BcdF. (1B;#6'"7"je4/ta{hRnD/GNg5fq>QS|J}H|IRP~ORXY}UXU|QaW|T`azQRh*iBaW{UqN|ZuTtSpPyZ{\xYb`phcmy]}`awVzZvXwYzXqLuOjI.GPj;jPgNjO|apVjMhKfGnPYrL`/:Q(5L%3O)D#@<&*H$A'A ,C*6J$1B.>N.=M2@Q.9J&2C,5J&2I(4J%0G+B'=,>-@*<.>+;K*:J$0D"/E(6J3@R8GU6IQ6IP:PU>SY9OQ8PR6LN1FI0CH0DFDXX6JI;ON8KM/DG3HK2LH>XU5RQ*GH&BF'?G#6B*>J*;F2HMAYYFa\Ed]8UP6SN8PQ7IL.@E*=C(;B$4>"1>-=#4A'2$5?M^j9KTBW]4HJ,A?9QN@]X-JF+HF,DD#;>(?A36!7:%:<'[H;XG>[M0IA*EA"?;)FB,HH'BC":=#;@0628&9<2908 2< 2<.6,409)4/7*;C1BH2EI/BF.AE-@E%5?$7="9<";;137;-4 2;-41818'<=-GC2MH+F@#<;%9; 37K^b:OS.HG0JG%?@79$;=&>@553LJ9UP/JB:VI5RF*A:"9+GcNJeL]|_AdG@`I8YB8WCIiT/O:3O;IfQHeP:XD;XD0K;6PA^:HhCKkDXxIXxH^~M_NeRlXcMdLkNwY|^z^ejn|gsdiYk]ayQbxQauPo~j{hxd\tIb}OiTnXsYy^gh}anWnYo[mZq]x]|cjR]{DgPrY}aizefS`}ReXcWdWl_cVhUlZb}Qr_r]`|LcPZwFRsGIhEQiQXl`>LEJUU9AE+2:MTXCKN/:::GD2A?)98&66-1)*$22#3/5F>*?11H4OfMb}_RsIOpBSuETuFY{MUwJKn?VwGgW[uH^vLdzS]qSOeJ.F,'A),C2 6)%;.(=30((:4#32((&*%'*-))(953).A6AWG9K?6F<0=24B4=J<7I3HZ?czTI`0Tm4LgHgTu|Jf;@bCdTs-Fd!Jg$D]6QOh&Yr.Qp&a9Qp)[y4Mn':[<_Hj*^?Hj.Ef/@a+Qn<_}Kn`UnIKc:QkC8S-'B##>#%>' 6"4!.%<%8R6)D(/G0*3,B,7S50K11I313!*A+&=#3M.*C&'?&%?'(A(90K*:R0%?.H$NjC,I#@[yE\zFFa0Jd7Ql=Kg6[wD_|EOl5Qn7Uq?@[):V%=Y'8T&2M"5P',FLe%a{-`y"E_Ywc$]~$Yz!^~%Gk`&b-pEB`!+H"?3Q8X = +Ol2A_!Yx78U1PWu;Dg*a?Hf'Qm2Oj4Fc+Kh.Ro5@\"Rq.hI`BUw##8&.->/-?0(:/2"2$(>.!3'-?33B5*9-'7(46 Eb0";d}B1F2  aZM;eZ=UށCp+|1eo-G]Rg/Ob.Pd0vUMc"Wm(bxW2;U0;Q/;S-0H2PSn"k 8\ZuYv%Zu'Yv `}#Sq]xC`y4RA|:So%v(.]Fa/3E, 8+IaNhnDK\3(:;R3*A#$>8$=% =V68Q0SlLNgERmIEa:5R.8Y4Db>Ca?C`@=Y;"A!8W8NnO5S4.M-(E(6M8&=) ",J.@^UvG9Z*Dd6D`6+G 0L$Fb:UqGeU9U$YuDJh0+HmVPk:bxK\yEB_*<0M 'D52#>&E)JIn-?c&7X(E'C2O6S0P 3V1[4Z\~.Mq5T4O&> 7 +'+))67$= 833 6Ia9;7 6 +$<'E&B0M$9*I>\.2$DN=]_w6.G#@1N(F,G :,+H c3Ca*La:7T5VRu5Bd-2P,J Ql$_}%wG2M }Kj2Jg@N*?P-DU5;K.;K,9L*0B"2H'7L*)CBY*4LWn8rTAX'BX&_wAcyDf}GUh3Rg4ctHEW(7G2E3H^s88Me{<>R;OqYavKLa8Qf>XmACW/I_8*BX*1I?XB\[t;Mh0I\$lLb"5J M^Vh&Ym&Um%@V MeWq-az:9XWq0Yq,\x+"8Yo-Sg'I]@RGZEWcv6wHQg$Nc`w/Qh!k?\*@`#;)-=X+]yG*HD`8JeC"< !-8N3-D'@X8%<1J%.D!?R31 4 K`1=V(BVs6Km~R&,1 )3AU7%<1H)B 5Ph9wHRr#Xx*tFNoAcXv0i>Ws)v@p:`*_ D<^$? :S,( (+<# " 22IPi(Rp"Os}Y.K0IhAMTk9MPfx,y'nlc~?8a|#;S r()_kSo r-c}jf 3Bb 6RH`,zQKb/%:451J)?S3+= / - L]:Sd@Vh@I\-;Oi|>xHbz,az-`x*Kf`z/^T;Wu2_|?oV;S/$8-/ BQ?RcCYoJ*? ,.$*.D!z`,F 4G5023M6JbG 'PjG53K#Yq7Sg0k8xGPPK\Y]N|K}MU[[S~Q}RzQpEyOdp`jc_[d_[g_}]}]}[_xWz_cy^{`jjmuTuRyni^fgi|\cag}ZufpwtbzU^l[_fajbl~NvJZiAWaZlCkCc|08,/F@*?<)>8 0B2crZl{Ytyn[szsw}{{­ŴʣԹ̽׷ϴӮЦ̲ڡʃϟšêˬˣƞ}zou|nwlyl}ofxqdtu|gu\h}^m]oam[d}SYt`gU`tYi{[l}O^mCUcGXeEVcBS`@P]?N\7DT3@S0=O&2F+8L1=Q*9M&6I%7G(8J)9I%4E)=!,D'3I,9J-:K,>I6HQ;MW8KS2EM4GL5IN1CI)<@5HK3GJ7KL.@D4GL1FJ7NQ=WU6PO2MN.KN&?E#9B#6D(7+82ELE\\L3>/9-CK'>D1HL*AC#;:&?<@^Z'CB'A@3LL%=='?A)AC&:>(=?#8>$6=26'AB1NL"<97RN+IC*D?.GC#=7*E?;1%=5;TGC_P1L?"=.6R>5Q:6Q;8S=4O=#?.9TFIdV@[L9SG9SG4KD/GD'EA,IG#A?$>?%?@7:17+1-1"5<291;/;/:1= 3>(6(4'/,=D/BE(;@#6:&8=)=B)?@&A=.KFDa]8UR1ML/HK-3-5*1"57*B?-FB$=:(=>';<03+>A0FE?XV+FA$><45(@B&>@46773MI4OH?\O5SF1JB*E6dkOkQRpSCbG?`I1Q>)G8CaQ6RA3P>@]J>YHY@LhNUpXGbGMkIQqHpeb[ot,L8=0=./K=(E1(F,VvVDdDIlIGg?;Z5Cb@Ac?MnJidBa=&D#,P.9W::W:=\=FhD[{VWuPi`a}Ub|Vi[OkAXuF^{JdPgQu_hSnXkToVz`pWqVc~`}_ey_r\q^p_dUo_dWl^udlj{_y_g|Mr[oZoWoUjk^cqWu^mYnX~it{ejdQxevbxcnWs[kWdSfWthb~Vh\k^kZo\o]n\gVZzK^}LYyH]}LWtKD]?VkZQ`[:CGKQXMT]GJUAHO5>A.89#0-7FA$33!34*. ,/()0))=/D\DRlLZxR]|RRtBTwAZ{DQs;ZzI[wL_zT\vP\vM\rHp]\mHYkLDY>F^D+E-+B2'>0(>3!6,!6,"60/-++$('*#$.,!50?RJAUI6K;9K<2C47D69G8AO=>P61E%XpDiGGd"Yu0vKVv*Hh Gh#8X3QIg'Ws6@\D\#=SDZ#\t^+?]-UrDFa5C]7=W4Kd<9T.F_?7Q3'A)%>)2!5%4 %>)8S9(?'7.+B+9O8$>"2I..E+2-1/C),C$,H$!;%?""<983P#a}S:Y1TrIGg7Nn3Ne~:GbWoYu"^{$l4St@bSt$Np PpHg:\:X&E3O#'D4R/M%B 4QKi1X'F]*{bVoD- .E,[qZ,'8%#5%$9&) 5&/$%6+.@3*9,,=,5vf~{hl6Ne~ '(('rNh|!Jd/6x,p&y7UOc+*?dzCqPj~E_s=eyK`wJ8M,4Lc0^9#;A_h  #/H1OlDA_/Db2B\0;V-;R/>V17Q)/zh5`~t'gB` +QqStRnJe Trf$Hb z=wW@Z PgJcg~**XS<}Je,;i9c&|>Zr"Yn+-3I0I$0K#8T)"@iQ*FX?,3L7HdH7U39U-^'.SYz@2R !> +&,.-K%K +%F ;j>n;P**AWq6pQ#?4 &4-E"403%* 99 0(A;-G : %/!Mj3M0 :)0M#BBa"`{BIk,(K0Q 1Q Tp3 +#&"90I&B ;\:[)H!> 3N[w.Xt%7Nm7j0D@Yay3]u9Ng0Tl<>U,6J'1A"2@!?L)Nc*Nc)>S"0CF^0DY0VkBI`5@W,?V)Qc9@R'Pd1uRTm*]s5G_$BV"GY#Se.CY ':O_#>Qi6vBBX zD4mE>XCZ!=VE`Pe'?VJ]"=RNc'Uf+Rf)Ui+l~>E[G[]u1[q0xK^u/[s,vIi}>7J1(9 ?SXk(n5) !5G\A$=!;,H&@&DHf*Ro7">#>Fa0Gc.-J""=0J,3&=('.8P01K%!=+E9Q&,A0FPg8\tB&>)C?ab7X}8;4G[9,!5"6';8M)D\-Sm4Rl.Jf!Kj!GgCcf:Q!@Zx01NTs'n6l/Ch~'~'D +5'>& +8I-9I.' '9"-@Zq5Nj\{#Pp$Fb((+B~P]r/ObH\Vmb|VoZpVn UqABF(@?V6q&Heim#ol My1,JmXJ`(fa׏= 9U2I!83Ck +Qm y1]u*AY \u!{2Ul0E4U >"9Og&pBXFOoCVz"7^>$?/F)AQ83@':I*' !5 ,>"8K+XkDI\,9LUk2by0{B{>|:q4t;d*9XFb"Un0?ZpVNe:#6-& I[=DY6:O5$/$/A15H)Ti=Tm9Um8,AaxP1H&?( +.0F,!4%9%:P:2 ! 7G;/@1$5@X4/Hv&@8M+'=@[%vP8V5 1E)$6 $72%;%4J6' # +!945NyGKMj1m7o7MOy@POYROV[Ta`UTxFwFvFxIg~:tHsGlA}StMsM}WnJ~\}ZvU{XhCtPxWuUrUivWsTgD{T}Sc|;wQlGhBsKg[d[nG^r7Uh.qG|Q~PX}Wdyxugkenujly~t|z~xɀ|~`dfjaYZ\VsIjBzVlJxU~UnBg:k:vEX^lχԐ՛`jthequɁń[cnf|zfIa<8M+%9VhNI\D(9'"2%)#,+2CZ/Fb33N$.I%=W58S/>Z1]zJfOZx?Zw>bDziSoN2N;;UH;RJ(>6~la^rKy|av^vbq_r]wOujsq]~ln 6UiM:P:Wkb#01*P`IXiH[jSdtmycqUbrSbrWivXlw]q{QeoI\gSfnZnwTfqFXcRbnUaq@L]MZjJXiN]m?P`HYjDUfK\6BQ0>K.F0AI/CH4GLARY.CH=WV9RQ1JK5MS1GM&;A%:B3GP4IS'2<1IP3JO,EH.GI2KL'?@3NL-GF&?>'@@%>=(@?.GI/EH(<@"6<#8=157RS/KJ!;9/HE%?;0,'<:84+EA*D=2LCDZN-H:,E;0J>7RB-C:-D:&>4Ne[AXM5LB7MH0JG&@?&A?(CA78!;C&9=$7<&9?18(:E1> 2>&8E,8*6(2(0'8>1604$79&:;/EB2LH4PH*JA>^V2RK/MH7626,1#()**B?*DA,B@/CD2ED5JI7KL,C@8SN,GB%>:34/DH'=@"7<(,.EC5MHEbU8WH6OFD_PUp]KgOLkR>\E6TA-I:2RB9XG/J<.K;?[K?ZHHcPD`M7Q>?ZI:UD1L6E`ILgNLgNMiRLhPA]BB_@=\3XxLGh>=9":(6'3OAMj[7T=-J0HfG:Y;7V0c[UyMW{MRuEX{LeYk_eZbUhXfVQrB`}NhSs\fz]kb]]xUxU{ZkKpP`tVrR}]a~[d~`pRjQkSd~OXtEraud}lzi{gpUl|Pz\xZy]uZmx^uZx_u[y_s[q\kVyelmgQiV{iq`n\o[taoZs[c~OhWp^_{Qn^l]o]lYhUgSbRp`[zKOo@RrBVwHGa;D\B?REBMM19+@3,>-BSC8H8?K=5F12F(7M&Oj7Po$^~5Zy/Tu+Sr,Nm*Dc Hf(6TFd)Nk2E_(Qh5AX(AV(AX+A](@[&Ea(A`!6TeFoT=[(<2N'4 =\1Mm.N8U }TSo#`1nEDa%"?7T)[vI:V&XtB-JXoMG_=34)?'!7#$9'#8**!2)(:/'9+*;+*C"`IOsuAeǀdYcT\X6E~*`z f[un Sm +M{9|FPd'Wk0_u65Mh~JK].*9'<8J4+>!(@ OAM'Hw.! &3$(;)/OhFLh7Li9Oi::R+>V3e~X_wPHc4Mn'r*F'8R72M-'B#4O/&B%>\=8V:6S7,I+VvU4Q2C`GEeF+I)@Z88S0A\:7R.3N&3O$Fb4Y/E_3]tEB[*-EWo=xb &.G&Ff91M%#>6 %)C;*H.!;0 %=70 5 4S!&H 75 6+G23-ILj55S"7 .6Sa|4xHf~8EcKg.qT+5+E!:.(A, +--3 6)$?2 6 %>7 + Wx0c:%<2;T"%@ 8V;L*0 5I%,?#6,A'40F%3I".E1J=T#d}C=VC\"Un6;TI`,Vm8BU"K_/^q;I]'kHRg1G[%kHE[!5F@S%L_1McVn*mJ_ar),)$=B]4(C"<*GSp;Fc+1M/Mk3D\.5K "8U_5Fl">]&=V.&;,?-B$1E.(&:e|QX$hN1OHi'Vw/lB4;Y:Y Tx ?d [z&,bCe!A"5E1 /*):Re.E^~F`}+Ok&+CMb)-';.@8L Mc=U;QEZ l/f~%B\y (PjJcVp@Zx0c2StjeIe:RQJfk?Vp-^v8h~HnT1,A2H!F\6OeC(*+>"8J%Jb2zZc|A'=U"-F1H-Ey_* +)?8N* 92.1B*)ViE:QUp(wG+D Mb2L^5$9-D}b0Tk>4K +AWo6n@V=MRPW[NUQUYYOlmp\d][c\ahfy[]h^cvM{T\ZwP]|qc_``kClDU~MxIwJ_dmfQbwNnHwPThosixynicnnlssrvjrftofnjmgeoobg`T^yvrwpd`giibXcockaOT{PaUlczsfSgMH^?7P,D_2Hd1tqUqN[>OY8GSEUbDUbCT`:KT8IS:JR/@G);C/@H3DM'8B(;D%8B5JQ,CI1KO,EJ*AG*@J2FQ 3?.<+<)7-AI@V[=RX4IP(:D.AM)I1>"3A1?#6D/<.91FM9MT';A*?D3HM6NQ,EF/GH0EE1FG,BC+CE-FG":<)>B%:?"7;#-H9]wb_|eIgS8RG5IE4. 6/=TL1K@7PI4MJ)B@";=&>@5678!9; 8701$9;/BG#6;/6%7B"4?,80;.:+5 4;/AG-@C"79'<=.0'::BZRF`VNh_9XO5TK7UL.IB;TO";734?RS"65(?=+B@,A?2GH1GE2FH2FH2JG.GC,FC.025BVX0BH06+1,.#=8E`X/M@&?6B\OHbS>YF8UB7TD6SD.J;(G63N@*F8-J;C^O8SCD_NFaM:WAB_IJgQMkS;Y?@]CEbI3Q:[w\@^BA^?OmFh]Z{QDd?^d1Q:2!9+:,:WF'D.4R9GeI3U4=_76Z*Ou>YHOw=Jo5]IlVh~ctYv\hNiKsRvUxTwQzSzT|WtPuP|U{UyT}YrOtT`yV~ZqOiJsUuZiQrZoY~gzct_v\j~PpRvVm}]`bakOw]w_s[lWzfq_r`}ln\hVnoufl[o^o]hSiRkTiTnYmZl\iX^{J`{KjVoZ_|LYyJOtFQuFRvIRvIHj17H;-=00@.9K2$:4Yw9Wy+Tv)Wx.Rs-Kj&Nl,@\B]#>\$?[$:W >Z';U%9P#:N#:O'?Y,A[-Ql:Gf-eIeNDb29V-3O/1J(+D"7>X6C\88R,$>#<0I*%?"6'C+'?*(A-#=($;*-C/#:%%=*0":%7O70&=d}QJe0c~FVnCXnC VoAJi7ZwHOo><])3SZ~@x^5U6V)Ab4?_0=Z,RoADa55S&>[.Qn<:V%A]/[tJE_52K"RnBb}Q0JNi8C].,F=W$eBSp,Ok)Vp,`|3=] Z{+Ff=]5P?\:T8V_}7He*7 6Q*2K#2K!.Ig{im>-EC`&Lj!LkUu$Vu)b=Rp8B]/%@;W-9T+3N(,G&4O00.5N50I2$=#< 3Q4KhLaz_@]ADbF&E'2O0AZ<0K-.H+5O,E^8^xNKe:/I$;U3AX0}h-+B 0GWr=;W(A_/?]-@Z/,$*";'A,J*F;,) %7 Ro:>^%?\+2 +3M':U-3O#/K&A"?"<#?,F"-E;V6R 5UQo05Q%A3NA^+&BB`%#=4N$^zG-KPm<0L!<$?8 ,G*E3)G#C3R Hb,E +03)I B_&#B_0o3C`Rl4Vr;/L9[gf4=S%Th.H[Mc$g~8ay-xB9Q ^x4Og(D\!\tA2IF\4G]Zn-au6I^!FXVf&ew0"5': .. >Y1 ;#?%@)F%@B_)(C /KFb'-I +1H.# " +3NiG-L%4O&Ic5F`/?Y%.E'?%;%0\{;fD$)@)(=%;(;%  +-Pf?=T+6M)1+ 8#?8V#!@Sr.^{5Nl&e:=` Ilg3Ps z$*/N9V!*?#. +,)+:):Mbb,M7+1I(>3?S"G[%FZ"3N Qi$BZ=Q@S Ofnr=~~$Pc~b~0~!53PkZނ^WC_wDi0m?JQk5o7r>m>oAolBWk+^r6_x?Th/Si)vGe1Q_v.zE~ACf";Tr:m9dz6f}8hj2n7zHZr(PPG6;HLWSSXTaJS_\PNL~KJs9t>k5Ub`RZ[juL{RuLYqncfgdkfRdkqehjp_eayywjsunsp[zQZX`ZTTLb^ZTWd^d_Le^hjinojljiuyxqjlxkfg\XJHU_\RY][^T]Qcaj]]n_qjbici}}wuzZvS[x5Sn-So0mObFoRCa$?^Yw7iGnI^rOpTfQl^`{TJf?j[uhMqSjJqghJxWuz{Zng{VyTcgej~Ƅ[tN-B;7JN=Qc>Sp:Op9Ov@U~DYJ\G[~C[wA^v^}ff`@`|1Oj*C[1F\-AU*=L.AQ,>M(:F.707%18*:>6FN?NTIY`aw{k~~hzmęzzybu~]qydw}VipYlsrh}ry^pxvn~nurewk~UhwXkzYo~_tl~gxYjxFUebql|n~XiuO`j0?N0AP.>M$7E1@0=,:(9.="5>+@F(;B!4;7KQ1EJ/EG0EG2GH/CD1FG*CD,FG";>!68#8< 5:%@B&BC%AA(B@63';>27*2/30/&A75OC;SD5M@*A9/&%@2NjSWu]+I6-I<+A< 712JC5PF9TH2MD,JE%@?1EI 687878-FG!98 853HG&8;+006 3;$5@,:!/+9#*/72EK/BE#98"77';=8KJCZO8RH2LF.JD2MH-HB*A=8QISog,)AUU>RP';:(>:1GF?RV1IG8MO4JM0HH%@?%@>47&:>-AC$7;!4;*WTDaY,J?,ETtXMjPPlNC`AGc?_~Tth^WSvTXx^5#4';-2O@KhS9"6T>7XBf9:Y77Q8VjaLZYGUSQ^YXj]UgUEXBL^HK_H@W?;Q;5L76M8=TAA\EVsUUuLVxEV{FdUZOb[WxJMo5d;RvENh*Ne4axLOd7Qe@h~Yu]uJa+:O,DVB?P;&9'$1!2"$8+$/+/121&=:/FC,A?#92!4-)92$5,.?60@4*:)EX<~koRvRuPIj%Tu/a(*@-+B.20"%!8%&>&!9-HhLwRKj!d?y\2M4OfMYx?_EiMCh%Nt0cGEh,>`/?a/Dc6A]1;X,?\26Q(8T)\zK8S(3L$B]5W/XqIKg<@W+mQnR0KId.]z9Kj'Qn1@Y1O <[wI)I Gg$?[C\;Q=WJh$jM1L!"<2J&,C%>,Fd~OOi=%?ZvD?\&St;Gf66Q Y(=Z&Ki3Jh28UUp@Fb2Pl;_{I=Z)7Q)YtPUjP(3%/%+?7AQH1C7,Nk:ʁKq#@`!?]vYQNpKr8Vx v6E >S*aEP`Tb&gzCqL\t@Of3Ul8Lb/7IQe8Jb+p@y.Wu4p+Mmw=vXfԈcYt4Zw?a}EMj+Sr+g4Ydw*w2i0}GsA6V[}'\yD\t9e~,\t W?O9pVr }2{4l#Gr%w.i#Zx>hs(w&G9PSB|4o4GVhJ^:N +AY|TqI.H 5OnHTn2^{>?W'E[.2I6L"?T*YjC5E!3A +9/@!'78M'XmG8M'8P-E\:Th@GZ6G^8BW1+C?W&H_0;R&Jc0=U"9R#Rj:XoBUj=7L 5LTi?H]-[t>U5)C-K!C^/H_-Kb/Zt>(D9T*#A%ENm,&A. ,XhI!5  7DY; #5 $6"2 &E\4Ie.Sq/:Z"CSs)Il]& Bz2c{).y,63:N4' (> 44KZ7S_{$^ZKg,E ++A 47JK`+?UJbRk9Q>U@Xdyy!vk <;r@=),JAV"8`yUHD`4O]8w%n,2Q^|)\*H )CA\j4Vx JnRx4IoPX~Ce k4d&Zv}1d&"Es:W*3(!  +'%("2(" /1?$6A=Og},p+g%g,@Y Rnr9s??Z \v*Rkh0r6CZOf"h;&<AUh}?f{88N x@KGV96B=>@@A|2<BGKqI?=AINIYSUCKVUQALMKF[cOSQUc[\p@sAitWc|1`n^OfQk]K`rvqlggdhcggjf^UwRh[gdbbU[OPTXTyFvBKg[bgfnbikmg\tsofbpraw`xornlmrmpjhdm_g|9Xn0\r2[q0h=i_v4`w7]|RzKfa_`daizX^vGc[o\dgzPjClFsP|WzQps^xvmlZdg`u[sKupqbcvgXddžkirLl^y^ØxLdO*A=6M[0G`6NmD]C]}E`~DbxZwRpzpdhTxXuC`.If2K`,@P5GV6JW;NZ6HR4IQ2GM2FL5IJ.@E)2:)@G%:B2:)6&5#3F%92>N1?M9HUBQ]>N\WhtYkvHYh^o~n~ugw}x|||ntuqsyau~h~`vG[d8LU-AK+=F2DK8LS9KR3EP)X_@Y`O_.?N':G'9G&8E%7E.<):+;/<"1>%5B'5#1,:!.?+6B)4A1>J/=H6DP/?H0@I*B.AD5KK0IJ.EI+@F*?E!5:$9>$9=&<>%>=$>>#<9&A=(@@':>263645 440GACZO1I=*B:+D<*D7NiXJeRE`M3N>0H=,A<'>73LDH'8B#5;;NQ"88'=<'<=/BE.@E%7:!35*+6IH$?;(C 4:05':@(;>5IJ:ON5NJ:VP)GB<32ND1PA*E:+F>$>6^}SXxP8[9Ss\0M9$>2 8.7,9+7$-K46X8/Q)SzClUdI^DoYfOrXnRNo/Vt8{^y^vZgdrOd[xSmIiFnKmIpKlIwUmMpRtXfdjz`hj|aw]qSuRtRctRdipzV}\tRyWdbw\s\san`qcn`obn`m_fYj]]sKxeybt\x^y_xas]yfn]o^s`pZiR_~LcPRt@NrETyRMuNOyOZXSwOKpKFiGEdIRnYXq^RiSg}dg}apjczYNfFG^BF]D@Z>FcBLjD^UFjBNsFIn=Ns@^N`RVxINnBYvI\{F_w>Kfh;^x6_oPOg2`~>Zb%>.D':&;z.G3L,F3H,/!632213+).,+%-*'73 0)):02A56F55I.Ib7B_#2S :]Ih*@\8R/J8Q'>V-(A:W):W'Li7;Y&Tt2Z|5Lm&iEWr;7 +Ea-Lh5;X#Vu:Uv7Vy8[~?Gi,Jn7Hk6Mn?Ca7'G7R+4N'UpFLg86P%9O(NcBpd'<f%FW{ h+HOBQ*%Z@YR+?O`"Zk9Se3Qf2_wCVp7|WrNe|Da{;Mw7o oA->1:Wei7tA]o4]v$\s,Smn1jDis+~Le 0HOhKhP憔R1J60 w_qT>[Vu4uj^0;R>&9'):+2A8" !&>/4Q<1P:5U5/L30I25M-4L1,'4N$8S($;+C'%9!4K`4i5{V]|9fC`<}W!? '3M #=2)-+I+I9?](6S=Z%5R)G +<*H $4)B#/ +!<3,#;#<,F.MpO#B6"=5 'B>ZTo05R3RJg+Nk39WKi29U&&D5T#Tu:Eh)&D?4OTp4@^#Ep)]{"8S/KJ#D>^ Wyr/t(?>%> #53SdfA]wEKBez(CYg{<]p9=Q8OAZ$Ec'bzBG^1DZ.:Q)E]5=R+(>?T.1A!1F".C!1@-@6L&B\5*?;P.DX2Mc<.E@S/AT6.A!$8=P+0DFZ.?T&CX)0DTk=F]/`xH6M"7O Vk;H_.`tCCU":JBR j}CGY!Tg/La2G\2YmH,A$7>X$%= Of0ay?DYOf*[p8La(AV4M=TQk0sM';>Q[l.EX=QOe+]t==UOd1>R!I\*VnR!Pb1>R:O=PmGXg+`o0ey0i}4i~7AT`q0Yi'p~@M., 3L*Ea8:V+-I#</J6Q&7 1NkSSs9/P0RRsBB_71YvO$?"B3O+ !(2D-*B&. &@9P& 5 Rj:6R!;Y!Kh'/N]|86/( (7TfD)@ RiM)'/&+.A'*A9 [{;eA2U Jm}GTwc(Qtn!t ,2|H: )<$4H,TlED\12LzKr@8RQmy:b(1L(@1(;#7 +?URj!Ogk2a{!Yq):*-3j|1LJ42Vo!4IPi"Fl&r;D_O5V.L!@7Tw*D :SIc'{M5T_+Mn` 7x'Bkz5)LA_j)mPrxJHg#.H73!$<,, +&)' "# $ !KWD+9KZ&cw-P}9};s1o,^xw6u7VoQT\aIVKFCLJF>@P???>?;;<@DB184?;9;44<;57<>?EMPIJNK@AECH]FEJOM\RWQTUXFQ`d]`_V`Q_c`YRgf\P[\qmjdcigi^p7YbY~CZ^cdYhaba]i\j~ETf2vQg_a][cglmdgupuwnktmjpjllhkxBav2Vk)tLuMXZn0qH_[gqhkpebkggchdvlg|=xssndY}N}MX^dmtci{M|M`O^p?dXvF}PtIxMlEfAySrGlsfj|Nimdqq\i|Xnj^fxPyJrbbgiDa{?quUgHvv]o6QIazxIfpPlxPn{Zywx|rqXw]~Rn:SeATaH[e>T[5MU4GQ7KU7KS;TW:RS?VX2DM8MW5LU5LR?V\7NT2HQ-?M/@Q+;N,;K.=/?K3CP4GS(;F+=J$5F,<.<'5&4+".+8*:G.7#08,:?@NSAPU?QXGXaIXcI/@K4FP.@J,'5?,8KO1CG'*?A'@B)CD:QS2FH 655LJ%<88OI-D;?VL3I?3KD&>51M>GbPJeQD`M3L?-C:1E@'>76QH6SL.MD2NK)EE";<#7;%:=26 9:*FF+GH&BB!<;5:+=E!2>$.*1/CF.D@/E@2KF.DA%98!452FG 33"65 43-D>8PK.GC(A>%>;'@=)HB7VMC^T6PG2KF1HE6KJ#86)=<8ML=QS5IH&=:.EC,AB(=>+@C.4'0$7>.AD';:6LH6OL7QO&B?&D<6RI:VK?\O,K>)G;,F@)B;+J;4QB9RE:QC-E7)?53JB/I>3"QnZEaHA^BEbB7S5?[=,I*6Q0LfHA\>>Y9KjEQpKFgEQsVIkR'F1&B3-F9>VG,H4,J2>]@SvLSyKbN^EW:fIhNfNkPbGiLs{]Vt=iQaJdElLyXrOmHiEiDjFfBnJpLpP|]}_{bmuWrWfwKsXsU}]fb}Ybe{Zbb{[~^gprfpgwbq^tcn^wjo`d}TgVsbtkYvav[qUw[~c|et]s_vdpap`iTjUhWbT\P_[bfV^cdU{RW{W[~\fbUuTcccbld]xRc|U[tLRkBE^6Jc?QjFGd>Jk?bRsbaT[LPs@hWUvDYyIXxI\zPQk>Pm;{T}@m'N\RmEM;X Ljs9}Ch8Oi+-C!:d]MpDy $+%""&&.-%44'72."4E83F4/A/)=%6P)Fc-kMIk2@]$B]'>T%&@ :+B!-F"6R)7S(Jg6]zIC^17P'7P,5L,3K,-E$*C 8U*ZwH0M$,I%)D$*D#.H)3M,-H$&AB_6\yMNj>Ea:3L+:S6@[>(F(,K.9V=D]G0I3&"1'," $;(*C)D^@2N%Rp=Ii+Bb#Ss2Vt4?[$,FLg89T&9T$?^)Ed,;]!`IRs>X{HKl>:Z0>\6>\5@[78R+B\4=U(;U*5I$4H)$8,?$5I,9Q3;R,[qESe6Qd,Nf-Me,?XOk,B`Gf Jko>Ps f9c:]{7Hf%1NOk/Pl28/)iZpUMf7'@2KIe5C`.Ba.>[,=Y-,I 2L)/H(3J.9O3=Q1M`C>Q0W%7PdzG\t>Jc&Vr2Ck@.4SrwF7HSi?Va|2Uۀ{Ipl'u5D[f*C_MhUqg)Vo/m.d~%Wr3 ;8U -4!:- , #=?[#mQ1N251#>%@.52VOt/X}5%D=\!2O`Zy%zX0G`7.E)>:M(7M)8I&0A"K^;,A:Q)2H#8M-*>/D1D!/B 9L/%:)8AQ.6H"6GGX.CT)8M!J_5;R'Md8?V*:Q$E[,Xn@;N9LDS%FT&J^&cuAKa+hP?S,0D .DZj@*B/DBZ$Ld-ezBOa.;MHY'Od0Ym9Ld,Wo6c}AVj0Rg+Sc*Uf-8JRf27O*?.E:K%96OTk;>R N_,4FJ\*4EDT(CW(Re2?SF\$:MHY!\j-Sb$Ma]q-n>m;\n+_n0_l1T^&+BPh<-J!>4S*Ee:@\0&B5P$7R&-J6>\'.L$C9Ee4;+Kp@c+dQ!(-!8@Y:51&?Lh:If,DaHf!Zy/Z{0Tx2Kj5;/) L@ / &  +%8!.D"+E(E c@Uw/&IIm@dy?Z|X|m&4q+xK/-//Nh49QB[H`!Tm(C_ c$w:u;p:";!6 4.C rCaz)\v;Pko%(en @:Tg$g+&1A @Rz9t2JF_ =Sv"6e?k@qVAZ-Wo?cXo)p_{&Fd Ge>^Ok.^@y4Qso7N.vFYw7*D7O-4,/"$%# #+ %!&!"1(8L])i~5B?VGCH8DE??:HOFKLJGG<6;:;1<:>AD<@??EB<@=@9=9GH?A@:@:<;=2CL,=G"4=/AL2DQ!3B*=K':I.>-=/AP.?M6ETQW@RZBR[F'7C-?I,E.BI)=>0DC6KH0E@8NG1FA1HB*@:6JHOf`E^T6RD5Q?8SD.H:1H@1DB'>7/JC6SK0NI/MI$A?#<=%:="79$:< 8;+DH+HH$??560617/7,2#7:3IG1JD1JA#>41+2-41*?=#94"84/G>-HA.JC)C?*C@&@?3/6TO:YQ1EF2FF1EF,AB*@A"7:!6:05&119#6;>ST5IJ>SP9SO+DA)C@%>;HaYF`W*H;>5)C>2KF0MCA\P0J>1K?%<13JB2JD!:56PH2M>,H1A]@YxUf`VuR\zWRkHC^;1L)Ed@LlG8Z8>_B,O6#E- @*!<'4O:.J1.K+SsMRvIYH]JeLqTjMgHrUmRoR^}Aw\xaRn=fRt`Qs=`~CwZyXyUrMwQxW_zUoK\sSguZ]uBbxGoMqSy\aa_ee]hlaaaatgcfjx`t^r_tbvbsbg~Wwg~k}ht`wdvaycy^y]w[|`y`t]q]g}Wa{S^zQXuJ`S]|QYzPY}UX}YNsUX~]\\Y}UhbYzS[zPf]b[k^xis_q[t]a~KePfRmWs\nVmSv\tYeJX{?St;XvBKh:XtG\vLFTc=;T^gXA:{,PubMk(>^+6P)+ '),-$410D9=SA6K74G02E+&>F_9`{MA_-0N=W'>U( 7 8(A$5N2/E*1.F%2L$3L%&A4)? ,?#..7(FJk=Bb5=`6+L&(F!KhAJh>Eb8)HE`5WtH$4L\/Se4Ne0Ga(:SjIYx8Gh$CdRt&Ux+>_Gg#pOTr2]a~>Eb%jKuZMj:Wv5Tp8+G/JA^-Mi8Ca/:X)i]Lf@B[:;T71G,*A$)@#/H'5M+;Q.4M(8Q*9Q(Kg72N=Z(9U$>Z*Ea/Vr=^{E[wDPj;7O&XoQ6*0'*;2*#)%'Lm 9Z >_Ww$Oj+8959DJb +UuJj<[ _*=^pF9]r1*0AlRJ`5]uFmS:Q%f}LMk+Wv(OpICHRj>+qc|9FfkXpvCgX焹m3k*5P:SH`o2u2gl8Faz?r5k.m-Vsu7b}.Zu#]x"z;n.y6Qk{9Zw\}!h2Cc HcFc@Z Xt^&Ea,3Q!>#<#=97X)6U("<0I#$=!(A#"<, (C"1L'2 +9 ;W(.F*@*+, ! 794O*,2 /*#=61MDh)Af wQ'K :< 2=2O +Ts"AaXz&EhT=a Ehp>w7@_JjVuLk{6WxNqj/Gd MjS|?;XqQ3"5.>"&60BBT+O!G[)FZ&[o:Mb+[p7Vh0K]'FX$Wg7DW*54I/F&7 3 #= $<[rC6ItU5H6F4DMb2AU bx>DVdx?ar=_m6_n5@Rhz>Pe$]q0Yn,ev;4@ 8E 6!:6 +%Db<95U'0&AKg9$?9 4Jg:(F0/9*1;^Vw? # "2&;pa1J5Q0LTt1Xx-FcEePt(?ddG'D0 + :Q'VpE /%6#'(DX8#;6SEc&Hh-e7ZBX|]#Twh1mXyp="81D%'; $=X KGLi~E}EMfWm.$9;P;Q j1\v>WKehy!8S$4"=j ]{"<>V!… 2B/Au~.3G[lS"=Z{9 +# !0D%[q%cl/KhXw#l0@`Dftx.gl1Pq#EaHal>Rr&Jf,";67 1(=/"2)-#(*! 1( 1*'8.)910C-AU*tLbw)j~#J@BD?FJHKHGE>@G?E>C><;9=A@9;51:;2:BC<89J@AG?:E6=>6AB@DD@E=E?;EGD?AGHIMNLNUTTPKQWUSWYRQVUUYUbUQ]`QZ\XWPel[V_bZYWS_VT\_][[[\daZOl}:l{>}Z_dpdkgfVhgba`icccbfiiiigppuv„Ȋyrv{fuFjz9Yknmhfmpillgd[eqZj~:vI{Og4{GzG}LeqC}OUYRxFyGcp@qC`g>cVVJcc:RQ6PKD`[9TN1JH5NO/HM)BG/IN0HO*BH/FN6IU4GS5HW+>L1CQ9MX9KW>P\;MY8KW7LWAT`:KX5HS2DP0BP1BR9JX1AK3DL3DL9KQ5FM5FM.AI+=H-RV4GL1DI8JO6HM:MR1DK1DK3GN2JP8PV1JO'AF(AI4@1@#5C&6B+=H,?F6KQ5JN3IL#8=(=B*0':A8LSGZ`F[a_ux^w{]uxcw[nuShnM_eTgkCVW@3EE.A?5HF;LM2BF3DH0BC5II6JJ(?9E[SBYN3HC+@=#76'=92JE;TP7QL1LJ&>@592506 59,2";@2KO#0JE3PC3&"=21MA8/-G>1KC;VLE`U?ZO8XL.LD)EB#@<;9:583812LE4MJ1IF4HJ.@E(;>$5<$3:/=C1BF)@32';7Tme>ZQ&B8";78PN/KD)D<8RI7PH*@80GA5OK1IG3IA8PD2O9\y[NlJea^|YB_Ahamg`ZjbGg@BdBPsW@&(K.@aD4U6;Y7Jj@Lm>Sv?XCgIgJfHgFkJuR}ZsOkIcEx``zL\wJlp]lYkWmQvWvRzTzVzVnKqP`fGa}CFa)bzCvYb]hkdflgec[^bhipf^{\x]~fjr`zfv`kp_wfr`wcu^{et`s^pYsXc~cx]t]n~Xj{WizW]tPZvPWwO[|UZzS^}Y^}Z\{]TuXVwYXwS`V^{Ruiuci\jZmZoZ~clrS{^pQdH_}BpSehJlM|YzVtPb=j_~@x\zaE`-Xv7b2a#}9N:4psB13}0q%kx&v%s 6x&w*n*|@Ms"k.H&.*)87*=11F33H5:Q82J*3J&5M&0K"D_2>Z15Q)A[7=X7E_B*C, $.&%9,""/F376#=Da2@^.Y0RkE=U2=R3S;CT65E!N\3oRCZ&CZ&Kb-@[#Qp0Nn+Z|6Ei a=Jm*Kl1Db)Hh*Ts6Pp2kL{WbB{ZHf)tM_>lS,G2M-H2N"1L!&@832K.+D'3L.'?"!::S59R2;T39S/C]6UnE?Z*Hb5;V)>Z,Lg8Ro=fQD`/C]0.G3;R6PeQ/! 3"&8"*:,La5B[ c?O?Yy$y.dr*fj8@8Ms:Rs 9_[|/Jns(B@p2zbx2Rg,^t@Tl7@Z#4-F D`Yv'k/_b~@w$t5{!LQGHJ:n6`~)N}>[LTq/L2Pqthx.Y{SCZ@X-Es7q5c'NjId!D_vC6Qk0d'r6Mhj;8YWy%Zy*=\5PJfXr(6P44L,7J+!4 ->4$8/ !.E05 1/.&%>')B.3L4*D#3 $*?)@`6Nl!Ih@bIk.Q +$A*G .L64 4 5 = )&E5VKl2+L670 *'G2:6 9?V314, &+7"?3&*=( / Zq?!7 %ZYs=86 :11L&,I^6*D6Y\~=4U"C8 +909 ,36T5U8Pp#?`;"; =@]FcQr>^Mp2S8WtzB@] +Rso;Pto6r:Lb Td6i{Q;N"Pj8BX*O6(;",< DU78J')<DU+9K$9I%.B"%73J$1G!3I#-C?W-3HK`3I\/BR&FX+8I2C@R,?O+`qOCX3-BCY.]oDYl?@R%HY+Wl:G[*Wk5@U EV$T+@U+1G4E)<$>'=+@/BdsGk}N:K ?O*Rc5K]*|Pe)WMcPd& 3,@F]ax(`x$j#k 2u#;+'_>#B(@ =S-CYn.@`PhG]5-;WHc*4.)7P^/?P ^tw7j/Rs [|/A`!`8l2Cl#}@ChCc!Kd#|PwKsC[-10+'8-&6+#$5));/(;/0$,!&/FX5qLGZ dxIEEFGD?=:93=;>>58<:@4=9=BD9=1EGDM@;I>?I>A@D?FFFHQEGJG9HIEFCELPEDGKKQHCQTPMSPSOXQYSTQRPMWSNTTYW]]X]hKJb`eb]\UT^Z`\ZZe]^[\[]lkm^IZbcdha_YcgkegokempqlfaeiciikghrsqeoƉ|wGRYXjngktpkfghZtmg[oEqBq@uEbougwoAxHNtCuG|Uf@d?{WSs/gErNtMiyM^yPvMiyHOE?DSDFG`Th;qKVogwepuZce]w9uVtTrPvUmNmP~bpsazmxmujYi]le|v|v|yuKitUlyJYeals/D?>VSHb^D^Z=XPHcZ?ZQ9UN;VS2KN=UZ4MR8QYPZ:KTJZb;LSSXPV=PW4GM.AG=SX=VZ2JP/HM%>F&=F)D+;@0?D6GKPab:MM'9< 37-?A9KK1DC3HF#88!481DJ!4;*=D.@J#4>"4@ 5@,AK+?K 4@0;/CL%:A"79)C?/HB-J=FcU6UF>]N1QBEdV9YK7WG:ZK4TE5XI0RE-KD/NI&C@73)F@2LE0JF/GE-B@,@B/AF3CK*2 .6)7A/=D$5<+=D#6?/;*6"0/;#4?/7(8A4FK5GK.BB,AB$7=35,*.'0KB5PH/H@!95%>8"<63MD)C:$;3+D>3LI*C?3IALdV6O;>\?EbEUrUedWqW=V<:T6RnI\|RDd;e`][FiF>`sRrXNh:fTnZ_}Iq[jWt[oRvS}YYbqPhHTn6Oh1sYnlyYa~XdZnjbaed~Vknqi_a\~]f|f|i}gv_v_t^yfp`l}[j}TiVnZp^c{PnZqXpYjyRt]p~\k{VjyVj}XazSla_~X\|W[zY]{ZVrUMjLA[?NiK]xRp`m\p]fTc~SfXp]v\nQeqPxZZy=bEaA`uUiJdBnGrJpByIXvHoGiMj&rEY_`q15NGy#|"*.E|.{'w6s!r z*Al(BRztqW++/A69N;3L22H3BY>YqM^xKVq@Ie2@]+Hd5/I3I"-B%<7 :"$<'41#0$% "#9BY<`X`XIlASwKMlC=^48Y/Cc9Fk=<`2Mn>Il8Ji9Gh9Fa7B^4=Y0UqI?Y2@[6'A"5O7-".)!-)" 3.%@"b}VSqD3N$6P+?Y3.I (@>V7(@!)B$6&B"Ea>9S4+G' :42(E-4Q5=!'@$2N.>Y9?X6$>4L--A%ZnSJ^G+@'!7[lLgvP2BWh8CY(2JE`,C\&5V7V=^Mr0Lp/Hj05W!Gc1Mk8_|HKi2Gf+Po0Dd$hFPo*jD=aGe.@^,4N#?Z/@[2@Z21M",F;U04L.9/F,9P69S63K.;T87P32K,,E$:T.7P&*B+G9T+7Q(+F;W*?Z,@Y/F`9[uN.E&#+YnBbUHPrPJH@_ z;\Kn OpHj n3n4Ru%H7]wP=cz59w ` *AayI_!7H_$[t/kc-ji6g1Ys]vhy'{#EL8UG]azVrnnjQ3(.!!6$+A-,D0&;'#;$ 6H9)D+#;&=X<- kY "uU\}+Rt":Y <` uDf:Qp+">,G,-' !;:/+K/P0R: "?)E"<8 <89Y+5#@$?82G#/1+D" ='E5Q&.+$54j_z$E_z'*E != *F'@*E5 /4J0,/8U(B$>+,9^ $$6F,+:$3,<")9 0Pa@2Fu\0B!1*=':(=H`:&;BX2O%,;*<Te5*L_&3D :JZj<8GDT'1D=N?QbtA;OTe/N`( 33G(24$<7 -I(D-H2M ))C#>=X,7R #?1 +8'C)G%A !$ $*;%:Q0/E+E^x@:W%A +&C:Xa2h-;^`&=[[w$m;Z2E..D'>.W(D3L F^)%:Oe"Ul(Si-%;J`*Nb(1D9Lcv=\&.Dk3Kcg#l r"r2Qg*%yZ^}& =(E4Q,EZp0f}!iLs8=5m[?[&):*: Vf(SdcF_;YIh2+8n0:[D`1U ,"<.C$>$9)8L&)'8') ."3 12D1UfS+  +%7%#% pLjy/=OB??hE\z8~XZw4^hCc=wMuFvAOGKPHOZOn;{Nibq^\mQp>TaaiDnMdFzZdBlHk|]mRr]vfylun|{~XvzOhoObk=JRbknĻq|3ID?VQAZV@ZTMg^F`V>XQ?XTB\YF_`Q]2FR:LYBUcO->Q'8L*;L/BR4HS8LT3FM1DK4GM:MT:NU/BI8LP=PSFY[5JJCWX8MMBVY;NR7JP,?FAU[@X\2KO3MQ,CJ&H5EN7JP8LQ>TV>ST5JK3HI5IM)8@&4=3AM-;G%6B!2?'9D-5-5$7>)>C>RU/CB-A?@TS/FE6MG:OG(<6 20*)9JJ>QRBUQ9GBL[WRc]Uf_gyvFYUL_]FYWK]WUd^RaY>ME?KD9HD4BB0=A,;D$2:'4>*7@,;B-0@E-;?'8<*."19.N!-=*;$4"3)<,="0!3> 4:0DGKa_FaY=XM?[M2RC,K;0QAFfW6VG2RC9YJ5UF6XJ4WH0OD1QH-JC/ME,HC3MH(B@*BA2FF1FI5IM.@H08-6#4>-7"3>'8C#5B+:%6#3+;)6-7&7>6HO8LO-AA*=@):B#77%862JA"<28SH;TJ,C:)@95-$<4+B8(?9/FA6KJ,B@'<0?WIHaL=UA=V>>Xu0X{q/80>Yx5z*BA63,+/E[b$V{!Nn Lg,.C9M2 ";!;!9!52.I^?QgGCY5?X1D]5Gc7Gn<2X&>c4:_2:Y27U/>Y28W,>_1Fh;9Z-;Y-/N"Ef:RlEWsKIe;WrJ1J$A\9GdD,D. +!!,)  %.0J+TlM@]76P'+D+D'9P4%=8Q0AZ<9.F+/G-(@&&?#*D&.I,74 :!5GbFC]B8%>!,G)>X:8Q42I-.A(-B(CW=BU9;O1)=atJBU&H_+Ia.Pi8>Y&6QA^%Ll1^}BLl0Ca+6U#8T(?\0-H C`3Qo?Ml7Po8dEuVUu5b?Sr5Ih0=X)C^29T,)CAZ6Pl@D_58R-6";'?&2J2/E-)B'*B(2I-,C&-D&?W6Le?4M&-H 4N)+E!/I#W,GNj$IdPk$:TAY>^Qq->]iAKi&?YMi'G`Pj Pk#Oi"[v.Hb:U 0M +E`3O 7R `|2Ws&=Z A_nV ;'@#)C"8)EC["f}Bo+3!3 ,A2(=**)4-;U*/KtPXv.}EDi7WRq%Yz-g9Uy)?_He''=,"6,#='G5 +<#A3Q"39W,/ +29T'2M-Nqa^{K&BXs@%< =Q'(</*C:SZt6h@lAI]%z~Ur28k 8ZvZf848R"),E\,>S 6#; +!8 %;1 +")@]#So#?[Zw&Hd*5P SoLfl0H>`YwGi Ps Qt&IA;WC^ .K-L4T:Xi:uBE[6H8H:N"8N!:O!/D9O(G[8)=%;8N,,A!?W9%=J^?'9!0&79I,>R20B!+=K]4Pb9:K'+<.B$%85H(5J)7L+-C-A4J#1C9H"L\85F:K5FIZ18IDU.Mc9M^84D 4E!*;Tf@Pb:@U,2F+@!7(; A\)[x?A`f2j&y1A\@[ #-?!BP,@W(?Zc~2_{$f6P *CHUm 8r='>H^ #5D4GjB@TPek2i*E`{0q"=WPe| =ZveRq$4R 9?VtI!0$5T)Ecb|6e{5xIbu1j19<8:F}@Wn&Ldj 4DGEFvAOho5UkAT5F,A2 %-*9+ + 1A8F%-9\V;HDAB9>E@CC?<8;?<flKoNc{\dmUfUj]ka{hVkZwj˼ʋj~|mvdngʷ 6'AWKLcVOgXB[IC^LE`QRY8KR6IQ:NV8MT9MT=RU?UX@VXDY_NbhFY`EXbAS`9KZ8JW=O[BT`DV_CV]BU[>QW>SXDY_;RY4JS4KS7KT;NZ:MY>R]3GS6IW5FW0AS.@S,?O7JX>RZ:NU2EO0AK0BL2DM=PW5HN3FK=PT@UU:NO;OP7KL=PR4FK5FM3EM:NS?TX9SW3OR+FI*AF)=D-?F6IO.AG';=/DDCWX8MN4II7JN1DH-=D-:D(6B&3@#2?$5B$6B$7C%6@3EMCV[7IL5IH8KJ$75'<6;PJ?SLPc_FWVRba^mmeurhxruo{nx~{eyt`tnUl^MbVL^UN_VARJRd]CVO-:!-7*7A-5)1(1!-7)7)7!.0%6"6!8$:)="7%6"1/7$6;6LI>UP0H?1K?6RF4PD9WJ9XI8XH5VF>]N9[K;^P9YK3SC6WI6TJ9WO6QL8RP3HK-BD+@C3GK5HL)%7? 2=,:.!1/$0 ,(:A-@F*>A2FF*>>(>A31*?;H_T3N?HbSD[O1F;/E=%:11I?5KB5KB)?9'=8)?:+C4,D72M?6QA&@0#;*MdS9M<7O8_xZFc=Kj@XxIGh7Jk5s[hSnVqUtWkLrPd?^~9fAeA_=aArOlIvQsN~Y^_tDsY~eqWpRwW{\`yX}]e]vP^ujiOwbgk|^uU}]~]^b_fdko{S|Tejjc_^~^qUnS}clbxZsXqZp\ubxdp[lYlZlXxb~hur\l}Vj|Wl~Yg|Ug|Qb|KdQdV\vOXrL_zRdVo_p]e{Q[uI_xIVrAkRq[bNeOxhoegqQmNUt8}cbGQo3lMiHf?l@n8o3z9~:r1@R^D405{6da<:7Jcdim#y-:0tCx0Mq[$Rs%$D>\eJ^zK;S48P:2G4(>$4M&Hd1Ca#A`!@]$3O/JZ1A[49U18R1QoO;V<2! +)!+"+ 0!&=)*C*9R3+C%@\68Q)2M)+B%;Q64K.&@%-E/0F0%=(":'"9$1I2-E--F/!9#3L5'?&9 /I-8R6-G,76N6-E*/H06M6BT>1F,-@#+AY.1L$.G%$>/I!2K'IbB:Q5)?$/G,#9#+A+5N22J/)@(.E)BY=+C%5M)@X4Jd=4N*$=>Z2Ke>=X,Ql=G`0Rn;5Q Nh:WrBTl89U@_b/2RPrTw<_ i2Z{$EdSm$Qp'On%BbTs-\{5Qr(Sp'Qo&Rr+<\8XQn(>Z5Rb~!NlUVT>gx*6Xw]Fb Zv&OfUm t4CcR5lY]wKcx%v&c!Tu]%XyFf4^z-Wr%D`>WNi BX8O(@ +=WHe#:WC_"=Y!Ge%4Q:X?[/KUm1;V>VId?X@ZG`A[\v.mBPi&[w0^z/\v,a|1KgZt4Gc,2%>X1]xO\vFJd&/J2L#[uHxWOo'pCm=ZVq;*E.FAV&9L'+*49Hd)%BWx+i/t9ED)DLhc#p3NlPnc,6Wd,"Ce+Ork*v6i/Zw-Jg(;Y3Q-Le5Qg$.BN`,4F5KBY#؟//D1E$/B&+?#"6+B%!8-E##60?%,<"(7%6+=+:8G'8F*"2"0!2%5?P2AN4'8(9=N*+<;L,#3/?$,?#/E#1F&Ka=)=Mb9J))<.?!E]5.B;M*Ra=O_::J#Sf?@R)K_4Md8$FV.Td?-<2D0F 2E"-@ 9H(4E%-?,7M%>T+9I(* +BX1:N$awGx_[mA=O&'9-?6H>MCR&We;>O%1A?P$BS&(9 Pa7Qb45ECR\/,  .4$=,D`> +"76 "& %> 'B$&E#@1N#%E,NWyGNq<,2 +B":7M*C%AB`Mn$gC@d'0R1* 9N+'@ $>!)=$". 2!%#%;Ld,_{:gF!='D": 'B%Bf8aes&;S 0G&RaAbz;FdXte)}A\y$`{!|5;_x%s2Wqpbt7)#:LG[ 8L,A@VRb|D`i#BnF2 H_x3r!@%9P$-? 3>eI+t(Hb|+F4J9N cx.s34y'+AGNTi_TMNJB7LO]bcJPz>`w%v9p3s:\m&ct+x:St<G`m*r*:=ACAABD?55:;9H?BFAM=D=?9=?FCG@AD@>??CDDAGKMNKGIJSLUXMIJLJLJHLSONQNQRSUVWX[[XXYbd\Z`\PY\R\X[WXXc^[W^][XXX]X\_WVeh\^bZaab\Zec_gXc`^hfaege``_[\]hXTT[\ea_g`^]a[hnnomvsnkig`^a_pyr[}HwExG{L^cj^`yKvYboCa|9ca}8pIwOtIrDRZWLy?bj6g;kE{UQo)Zw2y^|6qNbAbAZw6Vs/d=h=j=OOh{5L?U\m*IWj?nJpKlHzRqEPoAxJL^kVXv7Wv:aC`}AoTbtYt\lZ]yPohys{s_w`μ呣p|kn|dTlO]uYd|cgeKgGXsTWrUD_GMfVIdZIb_;RW8OU;NV>QWATZ>QWU[9PV4KQ8LVDXa6KU3DS6GX4DX,=P-AQ6JURSBVW:NO[[0LM/FK/BH-@G5HN*=B,?B+@@6JK6KL:MR3FJ1FF.AE->F)6B&4B#2@!2>$7C+<+:2?H&6NYKZe/?H%2<(6B%2@!-? ->&4!2"73#7#:54*1"/9(9>-A@9MH6LG7NFZM=YM;XL7WM<^O;\K<[K>]L?ZP@[V8RQ3FM9NR1GI=SU=RU.CH/CJ-BI!5:0DJ0DJ!4</61<&5D&7)9#0$21:.BH7JN&<<'=;&;< 52AUO:QD;VFB^NH_O.D7-A92H<-D8;TF,D9-D:+E<1KA,F:*B31H92G4AY>YrRTpHC`4p`kXkPuVoQhLiLlNmOlMgItTuSzY|]}_~_e|YoKdgc|arWwZ|^`}XuR_fksnebjiMuhU`yKhOqTvZnakdge^Uf2nJzsfnhe}\wXe}`dab[wXrVs\pZrZqYp[p\kUm~TnUizPctI]mDm}UkXjVmTmT[vFb{QYrLWpHk[gS[wGXqAOi:[vGiTq^{`pScI[x@fJsRjGoIrMpKfDTt8[~CbIhLjJZy7a:{MQr39{4x4t/9ADGLBho'k$66t y'w+dY`j(U} j{)u#1Lp.n3Or}JbEf?]/9R7)>)%;#,D So=If*Vt7B\%A\+A[)9P&/E")C#7%? -C'#5",, 3J`>AW/Mg9@^.Da3RpE9W.-L#Ba=OmN5U4@`8HgX/'@(,>..G41H6(*@+ 3,=%-@4H ^wGkPOm1Sn1A_"Qp6;\%B`,Pm:1KA\/%<)CB]4NhB?[6+F 9V+Y"Gc)To2^{;nM:YLo ;\hDtJ2PFb#>\FcVo3'C +G +4QNl+9W@Zj@Jk$Jf"4M)%? +b(bg_?]1PJ_O,L|>Fd TorBXt&\jOf?Yqs:s@F_Oj'C^E`HaHfz*-D2N 6QhHx,Mmx*bh+Rv|AZ{&Dai6GaSm$NhVp,*=H\#BY$F^):X>[4PLg.Tk29V #@&@3 32K`,?BZ*7MwEau#/CJ]m2Xn MhTnZv@Gq y'{.u+OhNj#\y6jG;V44 +,Oi,Zu3#BzN}MANi5R BbkF6VZx`| GiTo#Qp >_ \~aYTwh,Ln<["@;<1N Oi"m##6+A&"<6D':K1.=#4C(CS59J./<$,="* O`==L+=N-,!1;M3"5)=1E!)<+?.>4C$1@1D&5&4(5%4->+>!); $:%8#31?6C#,<5F!;Q)3J`vKhyM?N(K[62B1C9N.,?5H+9K0?N2/?GW38M'EZ4BR1$5VgA4J5KiM?V%cxLTdA8J";L R4* *A$,B'5' ,E(c|[ <;)D"$@#B3)JCe+eM+EB]2A[0&>":B[4C]/Mi2*F +Hf$5VIl#2WBf(u].- 1H+ 3/! + AWC.2D+H[9D_)1OLj#nKIg&7T'C0K;WNnMt,GzF*B% +%$uKn8Ok \t1cz4|OJe ^;Sg$f)Rk Vmcy)3H 6Vl(g}8Xp(D^ LflC_Tnh/7Mj}[U>F]{//M7 )>>O!FZg|?!7G:@;=94:>;BAGB=DDA?DECDJHHEDLAMORMKLUQGRRQYPOVRV\R]UU[UX^U[_Z[YdQNMUW[Y_^`e_`]]ZWY\WXV\ZX\[b^YQ[\[bY[g]ad`^_g__^XYa^aaZi[V]\Z_^a[V`hgUhe`cfholokjlogibZahgkgklXajoihHm|7l}8q@wGWVVZvJvJ]vIrEmd:c;YsFNJ~F{@{>BmItAb4tFkiIh&Vt5Wu6Xt7vTPl/Ke'Lc%]s1cy5k?uF~B\FT?t8oQWRckSdlObbH\_=RV@TY@UWBVX@VTI_^I_]H^\LabDY[>SX?S[:L\6HV;PY:MV8KS;NS0BGCWZCWY9OOSY=PVCWX@STFXWPV9OP8MN4MN7QR4KN/HJ;TS5KI5IJ2FH4GK-@C9LO:MN9KM7JK8LN3FJ1AH.>F):E#5@!1@,<.>&6C4DO.>G0AH/BF.@C-?B'893EEGW7@S1:JKT]HT]FUY4ED;MJ=PL:PIH\UE\QE]PF^OBZL5NB7RE@]O?\L:XIC^QAYOCZTOcbNbc0DG=OT0DJ>RX9LS1DK7LNBUZ7HP:MR 36HYaCS\(:D%4<,6+4*4"3<'9@':<';<$6730(?5@XH4P:E]K8OA(?2-B6-C6(>1-C:1G@+C;!91'>6'=0*B6%=0,C5.F73J:\ 4O(9R90H*SoBQp4Hh%3QHd/,F0G/G(>'>,F(D9!<6+%7# " 9P18S+>[-Jf77S*?[4:V5,J(-K(WxQWwOLpGKjHB_B6O34M08S4#>0I)Kd=:V40K,0J. :2K-+)B-,G16 *2";%;U7;R:(>)0WlGZsD2NSp;Ca&Kh-Ur8Ge*<[!-IEa1Ie8OiA;V.9U,7R-,G&)E%(A%"< (F$B_=]xV7O,9R(Ic5E`0Tp>Eb1>Z+Kh;?^1:U.B]81H-8%>!"8!#6#4H3#6#. 1+?+0C-,>(#6 %8#+.C%B[9Y(Je1Qn6Vv:b?Mm'Ee!4R\x<8TDc!Pm.">+H =1M@^Db")D8U@\"2ND`$eA2N 8SSr,5T0M ^`E`/J:u)z(L@,`hSp|Dc}.H_OdVn%e}1h0n7_u(}MyOQj)Un0@Y!DY#;Qglaq+_y)3I,fPonZ{n)0Mq4r4\xWr?XJeLfOi$ 9yS2IBZSm2Le,2O)D =Z!3NMi.Ok1[u?9U@Y"6Q.FC]h=V@ZNg&=SUl%=S +MeGae6:ShD2I7Q#/L/K"-J 3P"2MB` Us)msY+*A-- " + +Ki Y|j2t>{KYqCY{/hAKj$Dc`55U4Q)G7T$9U$(C&@.H(B6"=-F' $83 (9#?9/I/I%A < ?Z-5 *<apCgwK=O 0COe;Tt-_t j0El+`{w0Oidt'2H_e},MeMi"%C4PB\-!8/KC_Vr*e/[u#Lheb+Yy%6BcA`Ok }7Khw8Q5V QrFC_~!g((87G*2G*.B%'7"0))( 1EX;#51 /">Q316L)DY5&<':,=8I% 2 ->#12C"3D#3C& 0%<(<'74F&EY8"57J*4E$=O)0BDW04E 3D *:)90?'@S8)<"$926I(=Q04G'5G'1E$4D$/B6G%->0B,B*BM'@O)N_2Oa1ar@zT/6[a:6  +! #\rT" # # 1*'  "9*B" 7!<2L$+G < )G2QKm0\yC7T @\*#<,EUq<.J +If'>^@_Qu)<_&E<\&Ca0]zO%;+>*(%:!-6F_5b~D\y35SeD9WFe'1Ea%Kh!]~(Kq2?`42$:1)Of-}U'>$8&;!6az(E`A]ZtMPk VGa^y*/IOgM^y$a{R;Y3OJbPgNbxFNewSl$Sm5)D$=)?-? >TC&R.A0e|?HXZNC8IMMICD>CV[L279176OE=A76<:9:5D>>;B=:674.;?;<>8=99=?FECF?@EDCFEDCD>@EDHILKMLORTSRSSQSQRPWWSUOUVZRV^TJQWWdUV]ZZYYY[VTVZZY[XXZ[X]`^]]_[W^`[]Z_]`e`]_dZb_aa[\\\]]XZm^f^[dci_a^gegm`bie^Yi^\TVa]Zlnklefigkojlp~f`Kr9bx,m:g8e8mBd;f:m>c~4TY`8U_WTNm5D|AMVQk6sDa6Lj(Nl/Pn3cE[w:uQfCRj-rMdx8]s4Uj+m?yA|?{:x2TMQd~?Uq7Ys6Yp5^tOB[kEtJYoE|T|WfbAgLw^lSqVbG~bpxd|¨|nuۮ|骿xhv}žɧWkgTkjE[ZF[[L`bEYZVjkXlmH]`AUXG\]E\YE[XJa\K`^TkgNfaOebLbcH\d?S_FZeBU\DX`@UZBUYBVWFYZH]^F[[>XT=UX>VZF6FM5GJ.AB2EF+?A%9:+, !" "$(,-&&./**31!65,- 56330.$9:!%!&!" 436MI%?8*B@.DC5KJG\YZnjTifUigM_aBWXThjj^ss`svObgSdl=OW6BMLZc .7"19(4>+7@0->A1DH=OT0BG4GK/BE@RV=NWJ\`7HK2DE2DCSdf8JM1CE-??,-"050?C!1511*?=!93"71 7+@YI9U?9W>HbM8RD2J=0F9-C7+A42$4'2L:,F2";%(>'-D1*@1+C5/G9AXHLcPIaI0K+QqIUwEx_x[uVwToMrTrQfcaaoMhHmOy\mPlOsRpNsRpm~NqVqpYw\uW__~\}\aoQyW|[gvO[isi}krhZyXq|Sp{Qlwqkh]zWfgfjff}^h}_~abz]{`}h~fgu\nWr\w_nVeyGy`kRqXr\czN]xIa}Md~N_|KbMjRkSb~Kb~JYv@bLgNnVpZmVcK_{EiPtW{]y[jMoRmKzRSP]{3d;l?b1tBuCe2t@~Ep0CQ;p'ac`i!s(p"~-9x,m!n.7GkSyy9~>e(>dUy `)s3i#v/q(p&v0p'x+o$OTu++G)C!(BYwGEd%Ij%Nm1B_%V 2'?c}LIe35R"If97;Y4(+'" +#(C2P7T"8T*4O,6S4*(;E\/a}FTq6-N2N9V5R7T3O5Q2M!8Q+>[2.J"0K$8R0>X9%?"(B%,F*;4S1F`A@Z6Ia8?[.Gb2Ie5Id7>Y-=Z.2R)*I$)C$,D(#;!4/&8&+<+'7&,.?-,=+/C-);%1D13D/E]B2I*ayW":$<U=YIe);X2O sQUs0;ZDc ?\Kh*Pl1Lh.7S(E7T8DbQ=NccZo.?2t*e^~WtWrAc~'^z"Fa f0[u+Lf@XJc"E]3L 8RAZ/K\wA9T6QVr8X$WEYi:Vm&aw/Wq']v-Xq1'>>S.-D#ZuT97)D 6Q&;0N`z:Od2#aBc5U.K 80M=\ 4Q '14 +#0I#="<1L)1L(%A ;/ * + 5Q)(EBc81 "?9(@'C2/#7[iH+5 [l?K^x4^r|*Te*@Rjf&s1MiBi*Qj 6C|0~?Pi{G8V(DEb*4P'D9W{D~8_yNjYv KmU2Pw1EUq#QjFo2b'q2\~!Ef i0:ZKlD^7">Ge,6&D8 )FOj,A\8P:OFZ";Q*D\w67P 9k8H%Tc@ % +4+?( 2);P0/E6I)/=$# (8 1!21B()80=''($ SfG0F%G]A!3&7M[ABV820F%7L+5K&*>:K(GX58H'6F#XhJ 1S3;L/"-&4!0.-)96G*8I.-,/@$&7*9,:%3.?2D!%:8M'?P,3E :J&WeA5B;I%6D" . Tc=*;.@Vg5dp?DNEJ! +#4  *#$  "  (&.!(,' 4?W.Hb3(B.FKj/#D6'D&AG`96N$:RTq/qLPo)@cAcMo(2Q'D/$?2 +- QeK,$;( >R0_wL13OjH>Z&3N :W"Yv?,FWs-8SGhw4n(h+E +2JYq5oK,>*"2(; J_*La%o@FaYu#Ys!WoYs Lh'?m'Z`|&az u:Qm\zx.Ff-K1MH` Oj|) "!62J1G'/Ecx95I +ex?YLA913(ECA(/5/8;:BEBFOLCFB>>>E@@FC9;A;5;47/58;:7D7=477:;8;?AAAAAEE?BDFDEDDIFMLOOUPPROMNQPLTPPTQQUQRPMJFFR\[[MSYVYZ[\c\VZ_PRDLPUZX\[\[WZYTZd^YXU[`b]Z\b^_^Yc]ZbXbceZc]ZX_cdchrlhkjjhitr|hlffnsljkn}ofbehjkeehedieksҀփЀv_tCb|6`y7i=ib|3Tn%[w1Li&Tt-a7c7b3d6k8b~-a~,~FT}ERxHLg$Mg&Qk/Xq8Mf-Ok-Yx4\x5Mi&Um0aw=e}=mEf}9|Hcq+Eh`x>Sm1So1iE}VuJ`z6wPnI~YMk.Wt>VrQWI\bCV]6JQ>SXH\bDX_B5GK8KN4GK7IM3DJ.?F0AH);E&8B%8?'8C*:F#4?'8@7JP2DH8LL9MK0EE/DE,AB.DF0CG3FL-@E*.@C,?@+@>3IE/D?(<9 51/E=CXN8MC,B:&97*;<10';:4HH1EE,?B'9=.55FMTelL\dfv}OafTdiFVZ9HK3BAAQQP^aM^^M]]ewwSffCYWD_ZF^ZE[VDYS;PI>PLDWR6ID?SL>SJI]XOa]L[ZHVUCQR;IJ2CC9IIPO=QOFXU9LF9NEJ_R^sh{exn\oeKaU9QD;SC9V?:WAIgOJjP;\AHeN+G7-F=/F;-D7 9'5NhITmMRmIfYc|SHbB.E.*A23L=JcNMfJ5N.Ih=vhkWrYrUwYuUvVnRkLfF\{:mJmJlGsPqQhKjNoPkJvTfhu[ls^h~SqWhmtXmNlOapOmqNwPyS{WqeuT]p8vRp~KesCb~\`yldchlwX{\|_y^|bpV}cz[dy^w[{_}by]f{epYu^kWp[n[t^lWp[xaxbwcjUmViTiSlTiQgQfOlVlSs[qWmUmTjNiQiPUq;`HoUnO]|?nSfMgIfBf:aPi5h5`x9u3Fn+~;S?6z/r(x,:l&g$h$u1{1p$o#r'7mgx+{8Im i.aQvCh\}(Xz%Xz^ d$j)q,t/B9v+p+{BVv/!=#?[wCOn/Aa9YRs3Kk)Kf)`{@Qk3Rn5Yw=Dc.?])Bb2;]+1Q$+I%A:V/Li>1R#Db0A^,=]1)G&"?"9V:7T55S33S3)H')C*%<&.%@$)C(1I.6O40I/'?(7!$<$)C'@Y:&> @Z9IgC/J,6P4/I-E`B7O17D]>#@ #A!"@ =Z;(F*6 6M95L:9P>6#!;'2"!6+*%;.2"3 <%*F.)F0*B1%>.'>/(@/,-5J6=T37ORo3dAOl/>Z$?[(?[(?\(-I:U'2L%2J'%A,F$(F#5S3GcE1J-!; RlP?]=)G':U4#=C]3D_3WBY G]-CX(BY'9QuVTk/Zn.Ne#l4mN`hz9tls%o#~4Jh^}[zUsj2FaUqKg[w'@ZVo%Ke9RQj&A\B\9RA]!1M0K9T 7R;X *D6R.J6NAV&4KJ]+;T(??XZ,Yv:Sn*]w7, %.-Xv;c7^&]&Cg'IGiHk2S +)I3#B <$> *E?Z"(.+0 88- 7 ?5T+&B1 #;*J)J,L >0 #3#=A\.Ld9%&%/ & 5I};Yn r%@*AZt$r9\y!q93PIeg*f%]|}.9TnE0!@kGe9UV@u(>'EQq@a v9l,l'cs0g,RpPqAb =\\}(6U\z-0PJi"_x:%.D'0,%%# +MaB2K%&:2C),8!%5&6:I2#3+-;$#12B':N0@T34I'9O00.[mP#61&;9O.CX7CV5:J)0A!>R.!52B!0A->8I$QcA$4/=#"17D),: !04*93@(+& +!/,: ,<$("1"/*' /.&2 ,(/,,0<$'7-<,=*<3E @R/FU48H&6C#.<_Fg^~7A_&B @Z'1GBX*1H?X0(C 5#5 4;R)C^,1Na~E !.1K7O>XvDVt@k'8>X'c{Ce~8uC^ +M^8mWj7Uk2lm?Wq*\u(tBwCu=e0H=WIQMJPn?_jMg*6R +?[>-(4HBX^t/`y4B\Jbp4y08E;,-A55;8<1>;<<98?A<03/6DHJEBC>A5?;:?=CCA>G@:77625:@;CD:?FBEDBADBACHFIGEBKIBFHGGJKOUKONPTVSUUUOTVXRTQROUVSQSYY`]hc`[]\SZ^ZTTR\Z\[ZRYUU[d`[afql_d_[^\X^`ecaYackjghdbb_keebjkikrp`b]fd`f^gjeaegebbanjbhcmbidglkjolmqegcehjiklhyS{?cF]]tDk;QqBnDNl'Vu2qH_5Ss(\z0k=g9]y-Pp:d0`Tn$eUh%q;o{4ILuEWm1jJ^v?D[#bw@d|BUp5pO]\g;az6g@]|6uQtTKh1bIeJ]yCc}HoRkcsLմzۼÛ{`~{П}fqzl̜w`hs̯]onevyew~j|QecOdbTjbPe\Sh_VjbSf_Vk`dyozqvYnoDWXMaaH]\J^\RgeLb_Mc`OfbGa^G_\F\[CWXCWXEY[BV\8KS5GR1CM4FP3EO:LU6IO8KPBUY6JP:MR?QVDX_=OZ4FS8IT8KRPbhEY\@TUAUV@SSAUT=QREY[?RXB2DK6IO8KO5HL3GI4FL/AF.AG0DI/BH&8@$6@#4A'9C6IP2EI7LK5KI/EC+A@.CE"7;*=C-@F':B)9@+B+=A/>C,;>8GI/@@.AA3GD1E@0D?+@9(=4OeWPgY1F=.C>0BD#76/CB8LK.BA3DE*<>#59+2*1,2*-252GG5JHausi{zk{zfuui{z}~{vpuhxWljYlj]ooO``HVVCSS4CC=LK6FE@PMGZV7JG9GJ*8>5CICRTGVX?OR;NN;OJPeZOcSiksuqsH_JRjUd{gbzdgk|}bc_}aFiI@cBPtSQrW5UA'E7&=4&>,5O5\xTqwaq]\xEkPjY1K' 9*D+3M0Gb?YvMTvFeV]IW}@TzdBnLgCoJiFTx3gEqNa{,&>)":#2=X9:T4X@( :%2K8*A/'?/":*-C7+%*$&(7'7S?:&8&#:+"9)!9'- :$(@'(?"@Z/E]$Kh(If%A] ;X#:V$0L>Z+UqA7T()E%@(C"'D%#A!,I-$A$&B&%>#3M1>Y;+F'*E%/K$5Q&Jg:p]RqB%?9S,A\;(C'%?% :#6 9 !8"4"*.1B//@,"2!/B..C0.0+A,/2*A%H_A=Y3-I"0L$4N*'@1L);T,%>C].eMmO>[Ec"Jg'Ga"/I +0M:V=\:X;[4PHe'0MOl-uW;U 4N:VIf%j;tARgCYE^nBa/c!GYc7<'E +&FVx.}Pg~SQl!Hb!7P;O@W3K Wp2?U$.F+BBZ/\sJ 7 +CZ(?V\r1f},TYr w'`w~7a{ Vrd%[w_z#[y&So LhPlwBIgs>IeQk'Oi :Ga`z3Tr+9VLh+Hd*,G;V":U *F$@&C /JA](;S"(>9KNb31H.E/Ib|=7R@[[s0^w2Rm"[w-hJc7XpP1L%qPuM1M)H&C4TSs0+LRs.8Y42Q=6/K?\!Pl3-I1"='@$>94$E'G2Q(:!<Jg;A3= >!;+2!6 .D\sC'9"2)60;Rc4)<c{#c{x1_t"]u&e3Nm7UYs$QlUo!SpYu]{i>]g!y8x"&<(.%',$%",(! "!/!.'*6.:"#0%56F()8,;)8+:6D'$/2@$BK209#2?N)lRc5/<EO+#)D ! + +6K6#:32F1 + +&,*?+":#0!75!9/AW:!5&7O#Sn:9U13U@_(Nj9.H( (ZqNFb10OJi'Ih!JjmB9W3LxXSe56GpU5GKb(~Ve_k9:89D:<>88989=>EJPNTD?A?:4=;CEB9?=CB@A?C?BJDEFCCIHFIHJWPNGNMFDCJMHIFIDKSTURNSUXXYUPVTXVWVVTTWM[RW[[^]_]a\V]`^\_defdb\_bib^a^``^]CQd]ab]e^fabbefjhqhqjnpcidfcb]fckeahdcebflmkg`rfe`_cpi^kghgfjrqlljkkefe_^hdcifn]oc}Ju@Tl=tDxIxG{P_~8Xv0Xx1Yy0k@sIb8koJ{YQm.Oh)Ucd>]|5Zy4kHgJWt>ZxBaInTziħvϮz~g|`vZnegKrVtWzmfQl}h}ddlҥٻzzo~Wig\pk[ogTi`ZmeTg]Znbbsht{h|ySfdVgfI\X^rmFYVYnlPdbdzuUjgNc_TgcZmjMa^G[\>RW9LT6HS5GR5FPATZ:MS=PU8KP7JNEZ[BUX@SY>PX9KV5GQ9KRFZ_?RUAVV@TU:KKATS9NMCWV>ON=PQ;NP;MQ6HKBVVF[S?WNG\X@TP=RM@UT=QP4GJ1DI3EJ9LP:MQ7KK3GI5IK1EF3GK7LO1EG+>D*=E'9A3FN7JP:MP5IG?TR3GEBYV0EG&;>*?0CD+?>0BD0?E-<@.=?2@A-<<.>=/??5FD2DC0C>.A:2G;MdSMcU.F9,C?(<> 453GE2FE2EE0AC*;,FA4NJ?ZU8OLAWT]ppql}~vr|WijnVdl6EH9HL@OT5FF>RM[pb}yhdNfKTkQmjTlRlj\vZXvVUtTDgGEjGRxTChG1R8-K5$?,.I1qjc~SbNmT_{FiNrSlSvcWsH0L$*F%@Fd;UwEW|GSzB^K[FYC_HlSuWhMcFbDeFrQpKvQzTpHxRuLnDrIkzXkvYyZuStQrMTl3jJe|DlI\icwRlEwnwpdhi^mggmhrc]ipf}cqWx`qXtZoYn[byMr[zcgPeQgTc~NlXlYWyFTyESxDbQbRcRaPfUVpCc~OoZr\hV\xH_}MiYeSp][~HYGfUcRcQdRfQhTkUgOx^kRSu>Ps;Xy>Zz6Zn8}Es:z;g!u*g7Bz,\Sy c h'f#u0m"/]!Ba%9W&: &A5Q .KDd-Ml2Lm1Jm-Cf(4YCi*=`%:[%He2;X&Hh6RpF <1M17S;8V89W8;U9/F201!(=..E27T#Nk?1M%8*G*.K-"?#!=%/L1-G.)C))C(,G)3-I(,G .'<*)?*.E/4L0.D)5O,Hb:?[25N&-F!E^;2K$4 +%:,@X&2K-G X[s#=Vg'B|:OCRmOph0=`c&Nb}+A[A[9V}MCa:^w7/I 6e8B\5O *C:V9X=[#@4#? &@: 5 0367@Z*6$>%@$>0*@*?1,%> <*E1L6NI_/*$6%6%!3%2"("6%;1L'IbA+A&. &"+&(3@+3E).?&(<" 21J.4K0-D(=U:3.0E)=O4!%5%50?(%8)=IZ81C'69J$3B#0>$/>&&4'"1&3&7$%4#"-,&+8!'50@&$ *?Q:!%'%!+&(%$0'2)#1 /-%4$3!07D*1?&!/" $0(5;I'5D;J#:I-=.9 (?[1(3K/#:3 " # 5748'?7-H!.0 0 +5 Jf/Aa":Z5U >.J-G "5:S)Xb\|/4SA^ ;Q" 5 +,BMf/a9?_vW%Wm=#: 2HNe.%>Y~QAW+La7)AatHnCNc%td}bVq3A[em@HbY]O\f2n[w-POIBq)JMp:Nol@o +~'Zq o#im))=>@?93-+)-2905;BDDILIGGGHFJKNLHQLJLOPONOIJUVNKKSQOPOLPS_UXWXXRTQUZ_VXUPVTS\][[\\^\Z^ZX\ce`ibcifbbaaaaab`bfT[ake\^]^aa`_c_fnJXlolghaieceghfd_\osifd\_]djkrigknmqbfhognglbcbae_b_cafccni[[YTg>5ucPi8g9ha@Uq8Pm7XwAnVrYsYc~KlTo\dvQSa:xpUfrVyYpRoxTq:pToRz\qU~hvvavamTb}GbcݵǬмʲ~ǿзӰ~{vpDVW?RV=PW8LS8KSRS@USPWAUYEX[?TSBSTBTTCTU9KKEWWASS=PNBRR7GK8KO=OQK`^AXRG\UF[UEZTBWRCWU8LK3FH5HK8LO=QR;OO7KJ6JJ5II0EF.CE2EE0DE*=B,@D4GL:NO;OO6JH5HG4HG2EE*>?';?&9?$7>$6?$7>'3@D0?A2BA/?<0@=,<:-=:8HE9MJ.B?3H>7M?:TAAZI=TH1GA*==22.AA4HG1EE6JK-?B):?*7A%2;$19(7>+=D-?E-?B"49 2:$5=.5,0"13-?>/C@,?;+@>0CD#87NfdUokpiVrkKe^]vp]sq\pooYml^robts^rp_sph|w`rtUemIX`9EM2AG1?E$47^=^Ml"Ehi3[(W&e2j2p8Vz NrNpKk$Jh)Qp3A_&@]+A`+4R&B6 Hd96Q':2P Ig0=\ Cb%Bb$Be&Hj-Gf/)/I1 9""<$4#9%%9'*>/;NA 4(1$'-"#8-1(*$  .%6-2& 9+5+1E>/ ,D'`yP2NXt;Xt7Tr0;W5Q/J75RHe/Qm'$>%&>&7N4=W:ga9U.RoBB_0Gd4Db1Ng<=W0, ": 5 3(>)3J423$&6),+ 0 0(;("9&(=)(>-,B1 5#.A. 4)?&0H*1I)&=;Q+2I"/F#0F#2(=)B*C,D4M1K5MQj%B\Nm&6S @^jAOh$Tm./D5$:":3PNn%Ux#g6Ji@^d4MC[%zQHd~?\y@[=ZwULk C^a|1Ws)A\A]FaZFbIeKgOj LhXq'h8]x,B_Po'He$lP;X[w*p?3L+ vA`|(f0]w2*G +(,J0N!@ .>6"GBiW|/>cDc$)G#@64 +H!B? =]- * '. +0K'20 2 8;U."8 "'|Sg4&1KR;D!,_m1J_Mg~@4L0FJdNjj:A[[u*b|3f7]x)Yt {?X$?&?a|9C^2L AZ0I ,F )B 705 64- *./57.DSk>.E/)&:2H.+0InU?Y(,H"8 +AY*. )<" -#2-:+$/>S8;U3(B$:O6"36H06J3#!0 -$0(7"'5% 11C/"5(?(1.F/,%: 0+=$(8 $1+!.':(8'8IY4>N)+<$2'4"4-*7!%5"1 4 ),&%0&!"/+&4!2.D++?' +  &!-*!.)+"/3B%8G(&5'6#3&5!1.+7.) @N,+:;J"]l@&6XbBAK2.I!&:!'1,(?!.*%@!9<5S,&B=V/9+F+E'A27 7U,N4RJf8(E2)C8' +25D`%]{9>^CcFd"&C\x?E`)5NiCc~7EcQq%Ue7*n>^W]zKY^{ETNi$0FYyPQC7A'/-0412.-1/0891=;9F;@>85/434.:57HBBAE@;=>656:89B<><56:98:A=AEB?BGHDFIKOLJHPLOLRKOWKMOOQNTWMWTWTPTVV^W]RUXTWYY`^[RZX]\^g^`d\_gbekacjie]`hghece^ab``bjagi`cnkjdgcgeba`ftd]YbSPR]`delf]jqifnjggcXffdfa_d[_ZZcf_`_c[dg`a`jlkknrhhgihcgihexUBF{4SeYeyli/SyAJj`2nCeQWEX_K]fBU]RQ;OM>QR;NR8JQ7JRCV[@UV>QS@SSBUS;NL;OM;MN9LL9KH?PG4E9Wh^.@9SR?TQ>SJH]TAVNG\TLb[;OM;ON;OO8LN;ON9MLCWV=QP>UR:PN5JI3GH4IJ/CC7LJ>RQ 68#89&;<2FE1ED(::,=@2A@0@>;LF2D<->9-=8,@;1G@;RIJaSC\GBZE=VC=WK2IB0DC-B>/FA9NM6IH4HH1DF-?C-=F'7?%5='7?,>E->B%7;':>#6<"5<$4;&5:(680?>-==.>+=<%974HF?VQDZTKa[Og]NcZThbWign}}wm|yN^^ARQ8HG@QP9JKDQYn|-9E5@K5@L2@I9FM*89N_WFYJ@SA>PFW@BX?E]DKiNGjKFiIHkJAbGEcI-K1NkNTqOTpGRmEYtJ\wLWsGXtFeQ\zGNl9\zGQq>RtBRuAPv=WDOw:W}AfNhOtWsShHpNfEiGuSkIeD\:hBQt*Ll rDwGuHyObfk`fYR\qpdnlrScknhyuaqv`Xo}v~lsghffgkh~dgfQhUp`xfgRkViUgVo]ubd~PjWaPfUiUiWmXm[jXsbiZSpGVqHFa:Fe?Ge?F`=LgDLgDIg>Ed:A`5?^2Fd:Cb7Nn?WwEjV`|G\yC^}EXyAfOcMWy=Tt4Wv0k@a3g3HCo/u1~57Gy3QvEj CfDgk5s6_"IlXy%Om%C`C]2MC_GgWy'Orf-~Bl.Cd St#*Je9xKDd1R7U <^`*PDlKpRwLpW{%JlIk`;%D"A @]$Eb+;V&2O*G<,I3P&2L!6Q(2N!(DLi1Kk/Kh08U:V"Fa1Ge73O#;W/?]8/L,#B#-K2;Y?'C,/G1!7&*))+?3!9(#6+)!! (:.!6'0I3(?&$> A[@/L2&A,#>+"<-5"%?*'@+&?)#<'50'( $ %-!/#1&)#.A5$=,8*#<-&,$8$';(*+*=*1E01/+,?Qh:3J/E5N-H +Vt2Ql*>^Fd Rp3:Y!*E!; (@,B5J V\uAC]#ZsBUn62f@Y`)Uw]z&`}!v-k%f#p2e8/I4O +Rm(Tm'[u0Lg!C^HcD]C^/HPkGaB^Lhg7Qr9Uz9Z:Ag!8]=^+I(E$?*C*3P!!= =!C 52";$<- , . 40F*@>T&:LVk^w!h0Zs'Vp)KePm"6Q FaE`-GC^b}1PmSqVnw3^vc|!]x]}F5O1M3M `zW.*C/F;N+# +3@0''%# 0F).G%$;-E,1$74F.)#2'% /#2.,-,);%'<%$?*%7!7J6"5!, ,$%#$516E%>N(+;8H%&4!01 /&2! &6#/$1!"0/;)$/$'$8D.)/@$'>%3G,"5?O5>L4"/"$0#0'4&6 0*85D&6D#+=#21+C-E6R1O ;[j)6LUk@Mg9Lf5/H.D0~b%'= D[&[>W!6 \m2,05,/+96OIE70+./27IOE<9:8=<;9>97:>:?=:B;:157<@@CB=AHABBIKHGKIMHMRLGKIUMMLNRQSRXV\]XTRTTSQUVYTY\R[WSSRYZZ^^\^Z]X]\b`jJe^abi`Waeaf\a_^b`i]_ahe`eaa`b^b`_cb__ec^cdhi\Yaageaadhkbgrfg_O`Xaab^c\Za_addieh]geljbb]hkcjkihkdmwttnonRDq1Mkl.x@p>d}=Kf*:U?Y$?Z!Uq6Ni-Ib'Nh,e?f~@mE@YNf,\vTE\Vw!Ijc`q<Le.Ph2fbUm6}biNtRhGvQtPrPmMnOyZ[y=lRfMhQjP]yD]zJj[jYd~Ov_`{E]x@|[e}C~Z~VsYyKMe$˽ʿηϾ̿™|ey}VipRU7KK5IH:OPG]ZBUR=QO;OS;NU5HNQQCUT>PO>NM>ML6FG=NMCVJrp^rX3H7?TP9PK?SJF[QJ_UF[Q?TM8NG9MJ:OM5JINOAOO-?F1EJ.BI,?E/BF,>B,BA+A?2EE8JJ/AA3DE6HF9MF>PH;KA5F=2AD5?HIX@P]FQcIF]CE`GOpTAcE1S76W>5R<7U;TqTZwYMhHWsNNiAUoG\wONkAIfQpFGh9Kh9Us?VsY%:T$)C)EaP2P0N.!<Ql@eMIf.Bb*5SC_-Kg98S)(D!@'G!+F)=Z=@^D'E+"@'. "''("6*":)/$&"!-.%% $2G62% $$$!,0E81$!6+#6,(<1-E2!?&*-%!  0@)Vm?V'1E)? mD^x1Mf#Rk*e{93H 8QWq-,I?\ -J%C +YXr12K:T]w-9Ta|+Yw%6T;W Zd0Vv'Po+%@*A/HLd19R8Kd%a]w)l@3I La(Vp0Yt+d2Xy,\z3Rl(Up*Yv0kC,=g`w>]w=_{A(D ;Y1Pp>^|#w:u;;c'u;r:SvWzvF7V(D1NA].'A ).K5W#)L=-P7W;X'A":0 (>Z*8S#"A <Ln'kYvA$.6KHZ]s&Qhw1}8v}.`hEP IY r2RlA^ nA:R4L Ws'Lk$=\C`5SNk%Rp&[{+Bb x36u,e~#Vqc Kg:R -I B\$!900K'D#?,IRi.Sj-Ja#Mf)5O(A(@ /G.F&<(@$9&=, "1 *-=W(/J8/E* (6+A4K*E ]{:Nl+17 +. $'A-C$)9!(5$'$! +1C)>X5/.- 1H)!4!3!0''!# ++*,)%8 +-9K9..%)"/:UdD6E#4D M-$2!0, -#. $4 +7'",$!*$-" +6B-!/2C+204H*BQ4JV;,!,#!#/,=&"1& +-5G((5(9&:$83G+/5D'8I).>#4.(7^mAU`=6M)" !'0C*BZ;/02, QoE29Z*/O )F@]**F0L8T2P6W^~:#@,?Z,A\'vQDe?Cb$7X1R j;Qt#.N "@:V050!7%>2 +$A BaFeg8H`$)@0 +#:$=)1E$*=jQ`|CSp2[s8zIX3_oBd{E6LpT^x@gId~CoNglNxZkMrSgFf@_RxNVsHhpHF`b|;bUG?Zv2/_.D50BTSEOH;0.,*./.0**&>\M|'2)D+29..8584<9>@\&Pm:Gd1:U!Kh2[v:fEuivSWo6iPTk:c|BjIoNmM{]~^yWqNkInM`~@dG`}HpZWoBYrGKe8jmWgMb}IiMhJbmVr2WXt,ZwTuף|lWknXmoSjmRhjOdc8MMCXY@UU?TSOL9LJ5GG4FF-AB/CG6IN6KN1FJ7LO/CG+>B+@A1ED5HG0CC1EB4HC3I?7K>?SE9P@6L95M=4K>;SDD\HD^I=ZC@[EGbOC]K&7>#4;&6= 27!37"45 43&&'(('! $2-=LE>MI;KCM\Uiynbshu~kvrm{JV\7AJ/7@@HSFNYU_iEPY\hon{s~AJK?J@Yf[_m]XjVSeNZhQboY\jSNaEXkSF]G6Q?/L:=ZFB`D;Z=RpT=[@=[=JeF>Y3;Ig8Fc25R%>[1Nj>B_,Qo4`sN[y4tRnKVz:Mm.Vt8eDa=Yy3{Lc.Y|^#_&f,w9T~NvHoi7c4Pr&c:Nq&Hg$9WDa#@^@^Ebk>ZOsW{$Lh)g%_!q8Lrd6^/Y{,Ru$rASvIe-s:j/h)s8wEmDIo'/TOp8Wx?Zy@Sp; > "?*G4Q:Y:X]|A3Q2L 3N&*E/H5M&AZ34M)5&@'B"#?!/M18V: >&6!-J6#@, +&12N;B^H*B/*$8,/'  &54*+:S;/I39#6O>:UD-I6%B,!>(&B,(D.*F0*H1&?0"$ (# !"7)9OB+@6-E8'?1">*!<)'C/;Q@*''% ++A'WpD4M/GAZ$9T&D B`&Tt8Wu;fQ; 9V%9T$A],;V+5Q(1P+8R/-H-6-'?%,C(6N4AW@/!8 %=#6R.4R$Tn@A]07R'@Z(7QSl;mSa{H"; 5%$.!-C-,A+&9$$6"-@),@-1,." 1#.@)0@N_8Th)ՇߌPyan=Yq4Uo8Jg+?Z`y9G`#D_$'C*G +'F +8U,H)D&>68M +A,C":0]Yy,a3Kh]|,Zt#HdOh E_Oj+0MVp1[yLE_A[Md)-G Hb!8OOe k5Uq]z!vX'CGdc8Sq'Qn*mBLeH^Yl-Lg b5j>`5^~2a~Oe0Ca +w=h.FLm0Q%H_0c3He(D "=:U'2+E)(A '3O$,I--I4M# :1 . (=/&E2TRt-^0Lk:S}T5F %6 . '8 !1q>[l"dw"ZnBRBOAIDJ>K`oG[ +:P &@g;T.61$?(=(>0 "6&9) 1?#* ## & /";?Z.@Y,0J !67L$ 6-C8;WTt0vN0R 3&>1J!:T+WlE. 0*%2 ,%)9"M0IW>#! +  %4! .$ *: "2) +3E$-? /@%*: BQ62E&AS2%6'4O])JY''7 !/ +VcB%   &&# + # *' 36R*Kk=0QRr@Cc2= Ee,a}C%@)F -KAa#pK;X ";4 'B Yy4[0Kp#Hh!:YGhSu#~Q+J'B, % +.*/7Lh1/L:Z DaOj15 "93J&'?-C$';.A0(Ur5#=, +.i2GJa3Lb5Qg8fdFgHlJnNa{CXu:Vs7eGiIvSdtJqGvLuNrtNiBYq4h@Rr\n)08#8GKDCD@F>5,)/)*+*19<<*6M4 (R_7=-2,'106D?679C=:>>;649<49?A;>A=;;?==:===BFIJOXNSUOR\PQdURTRTRSKSWU][OW\\WVPMLKNUZXTZWZYYV]Z[Y^\]`_```a`_abhd_g`cfb]\b\`emjgcrldk`aa^da`dX`_^dbg[`c^c\Z[[W_djhj_ehinqfbT\Z[_ed]c``e\ddcU;Sa^k^\XY_^TW^^ke]ereRTm|+q3v:|@^s%JHi4Uo%{MUr-pP:U!2MJf5He.Vs8@]$Nk5Vp:Vo5Ng-hD]^Lk'mFXpIVx.Vu.oIAa!A_$>\%E`*C^#Ql,mA^}1Wx$xCJQLjHejD[)? 4H]qGPe7^u>c{?l~Ccu3r=KT*CVm;g}P\sDRj97SDb-9V$fQRo;Ur;a~CkoJdgvWmWd~K|[pdgG_|?pMuR^yVmGlJeHNj5Ga0Wo@Kc3Rl<]wGayJ]uEUm;hMdlMkM^x`^w6Le)yWLh2F]1xlݽʹȫ{h}bw|ey~TgmI\`7IM6GJ5FH8JTN=SK6JEQN8KG7JI6GH2EE.CD7JL.DE.CD1EI*=B-?C2DF->@7HH1ED1GE3ID:MD8L@=RCDZHAYEF_L?ZEA]FPkSb~cMjMQmQHfIHeIRlUSlWJcQD[L>TH6JF7KJ+?>/CB.?B.?E+9?/=DHEOW|n}eswbkpckrckpmusdpiaodcvcYjWAP;XdSer^ew`cvbH\KVn_XqaG`MBZEB[ECYB8P8BYAH`F9O87J48K7:M6>P6L^D9L1?W5E_9Jh=[|JeUcQ]K`L|dqYz^{^d^\pNsTlOiMfJlQoR`C_CaGgMr\Y|B>`)Km;SvZtIo[o_bxQZsJ`{PUpCXrBXrFWsHTpEJf8Sm=kVzf{e~iudq`q`t`oZnYcxNc{QVnGC^::T8=X@0K5'C,*F.>[D=YAA\?8T2ziry_heLj5Rq9oMrLxKSg{P}WmM^~@Vu9Hf+Rm4jHc9m9Uyd-a+LqRy`-[)CiBiVy)Nq$?bCb4SFd"7U2N A\b8FePo wCk4a'Ar1b&QuCjEgLn%=_=]2R>b Ow#JsV~!{D^%W{#IoGk&(K? <^&4VZ)$@'?77!; +#2&?",0 =%%D'$A++ *9(1N:6Q>1L:0/-( !($$$  1H96M8&=)#:(-($@,-I20L6 ='2&B,&B+4K<% !* ;RDG^P/8#&B,+F4)' ".#!1+% +G\5>X%+D0H7S"+GEa,Ba*(F9U%Oj<;V)>Y,>Z,3N#+H 1M*(C *E*"9$/.6L-0%=#":2 9?X86R)Ni=1KMg:Lg36RRo-)HTzR 2&(4&!7$'*&.!':&:L9./ 3 -?)"4K`2[p-ax*J\ %8BWRZs,Lb+0K;W @V"1G7I5I;P6L 4Ha$azB29 Pg8Ne)%?.E X(CP@r-3P5Sx9d+FYuEj 6SCc\y%Yv(a{.Uu*]Xw'=\>]D_Sp#k>@YHd /I 4P2N*D9QFa:SMe&C[!9SD^G[2Lh3]y#g-0LTo#\v/Up)e8Mh!HcOk"Ld!0G AW"E`'-G3K:[t-Yt(6L2BSYn6Rj16NoMVs/Wu.4R?_Ljd~3~K'? ]y-Ue5]-]}/Rn%@[wCh1GjJj\,4S-M +1PY{-n@6;$7 +* $,2 &7R+2 5*8Q+,E![{?Ua*:[C\Jb dyUHV +m|=M\+(\Vb$aq/BTVk!EW6DBM ELY_Fpz!R` y>Ng D`0J1J6SCb"6VEc!5P%?PfF]F] buo(r-^xB_ i}6J\6*A2L&C-K1PQo7 = +Jf)1N2M8Up]Vt2Su09T ',2 RgB/ + 8E2 121E([nS1E,!:3L.* !2!*<)(:& 3&4#6 +*2&9$09L26H30% +-% ) 2!.)7$ .)7 &4'3"*&3"*0<+*8'&$% +!#(  /=(4':&5BS2?N0/<#(#! )$3$0".%6&6'89J&&80?1E)3G'CZ1qVt=IVe)as9)Te6! + +  $" &=$.()@$#;SpF=[*4SdM3V,LGf%Mk),I 5R*F5hQ;Q#0/)G +KpU{(c9`7_|2Rs!Ux(_~@2 + *(( $:?W`}0Dc0M$@)A";_yV+(?